From 292ba8b10a4dc77a94d71d2dfd5f3c1027af15e5 Mon Sep 17 00:00:00 2001 From: pkurchatov Date: Wed, 27 Dec 2023 11:36:29 +0300 Subject: [PATCH] Expression validators cleanup --- .../CopyToClipboardActionHandler.swift | 10 +++--- .../ios/DivKit/Actions/DivActionHandler.swift | 2 +- .../ios/DivKit/Expressions/Expression.swift | 9 ----- .../DivKit/Expressions/ExpressionLink.swift | 7 ++-- .../Expressions/ExpressionResolver.swift | 35 +++++++------------ .../Serialization/DictionaryExtensions.swift | 12 +++---- .../Serialization/Expression+Helpers.swift | 14 +++----- .../Serialization/FieldExtensions.swift | 20 ++++------- .../Extensions/DivAction/DivActionBase.swift | 9 ++++- .../Templates/SerializationExtensions.swift | 28 +++++---------- .../DivKitTests/DivStateManagerTests.swift | 6 +--- .../Entities/SimplePropertiesTests.swift | 6 ++-- 12 files changed, 59 insertions(+), 99 deletions(-) diff --git a/client/ios/DivKit/Actions/CopyToClipboardActionHandler.swift b/client/ios/DivKit/Actions/CopyToClipboardActionHandler.swift index 67834dd0b..b7e91ea11 100644 --- a/client/ios/DivKit/Actions/CopyToClipboardActionHandler.swift +++ b/client/ios/DivKit/Actions/CopyToClipboardActionHandler.swift @@ -5,17 +5,19 @@ import UIKit #endif final class CopyToClipboardActionHandler { - func handle(_ action: DivActionCopyToClipboard) { + func handle( + _ action: DivActionCopyToClipboard, + context: DivActionHandlingContext + ) { #if os(iOS) let pasteboard = UIPasteboard.general - switch action.content { case let .contentText(text): - if let text = text.value.rawValue { + if let text = text.resolveValue(context.expressionResolver) { pasteboard.string = text } case let .contentUrl(url): - if let url = url.value.rawValue { + if let url = url.resolveValue(context.expressionResolver) { pasteboard.url = url } } diff --git a/client/ios/DivKit/Actions/DivActionHandler.swift b/client/ios/DivKit/Actions/DivActionHandler.swift index 215f74a48..db082674b 100644 --- a/client/ios/DivKit/Actions/DivActionHandler.swift +++ b/client/ios/DivKit/Actions/DivActionHandler.swift @@ -129,7 +129,7 @@ public final class DivActionHandler { case let .divActionArrayRemoveValue(action): arrayRemoveValueActionHandler.handle(action, context: context) case let .divActionCopyToClipboard(action): - copyToClipboardActionHandler.handle(action) + copyToClipboardActionHandler.handle(action, context: context) case .none: isHandled = false default: diff --git a/client/ios/DivKit/Expressions/Expression.swift b/client/ios/DivKit/Expressions/Expression.swift index 65835a473..bdc5858a5 100644 --- a/client/ios/DivKit/Expressions/Expression.swift +++ b/client/ios/DivKit/Expressions/Expression.swift @@ -4,15 +4,6 @@ import Foundation public enum Expression { case value(T) case link(ExpressionLink) - - public var rawValue: T? { - switch self { - case let .value(value): - return value - case .link: - return nil - } - } } extension Expression: Equatable where T: Equatable { diff --git a/client/ios/DivKit/Expressions/ExpressionLink.swift b/client/ios/DivKit/Expressions/ExpressionLink.swift index a206bcfb7..10124b5c6 100644 --- a/client/ios/DivKit/Expressions/ExpressionLink.swift +++ b/client/ios/DivKit/Expressions/ExpressionLink.swift @@ -1,6 +1,7 @@ import Foundation import CommonCorePublic +import Serialization public struct ExpressionLink { enum Item { @@ -12,13 +13,12 @@ public struct ExpressionLink { let items: [Item] let variablesNames: [String] let rawValue: String - let validator: ExpressionValueValidator? - let errorTracker: ExpressionErrorTracker? + let validator: AnyValueValidator? @usableFromInline init?( rawValue: String, - validator: ExpressionValueValidator? = nil, + validator: AnyValueValidator? = nil, errorTracker: ExpressionErrorTracker? = nil, resolveNested: Bool = true ) throws { @@ -76,7 +76,6 @@ public struct ExpressionLink { self.variablesNames = variablesNames self.rawValue = rawValue self.validator = validator - self.errorTracker = errorTracker } } diff --git a/client/ios/DivKit/Expressions/ExpressionResolver.swift b/client/ios/DivKit/Expressions/ExpressionResolver.swift index e9ee09bd7..939d50244 100644 --- a/client/ios/DivKit/Expressions/ExpressionResolver.swift +++ b/client/ios/DivKit/Expressions/ExpressionResolver.swift @@ -2,8 +2,6 @@ import Foundation import CommonCorePublic -public typealias ExpressionValueValidator = (T) -> Bool - public typealias ExpressionErrorTracker = (ExpressionError) -> Void public final class ExpressionResolver { @@ -173,11 +171,7 @@ public final class ExpressionResolver { return nil } do { - return try validatedValue( - value: evaluate(parsedExpression), - validator: link.validator, - rawValue: link.rawValue - ) + return try validatedValue(value: evaluate(parsedExpression), link: link) } catch let error as CalcExpression.Error { let expression = parsedExpression.description errorTracker( @@ -192,7 +186,7 @@ public final class ExpressionResolver { private func evaluateString( link: ExpressionLink, - initializer: (String) -> T? + initializer: (String) -> T? = { $0 } ) -> T? { var stringValue = "" for item in link.items { @@ -213,19 +207,13 @@ public final class ExpressionResolver { case let .string(value): stringValue += value case let .nestedCalcExpression(link): - if let expression = evaluateString( - link: link, - initializer: { $0 } - ) { + if let expression = evaluateString(link: link) { let link = try? ExpressionLink( rawValue: "@{\(expression)}", - errorTracker: link.errorTracker, + errorTracker: errorTracker, resolveNested: false ) - if let link = link, let value = evaluateString( - link: link, - initializer: { $0 } - ) { + if let link = link, let value = evaluateString(link: link) { stringValue += value } } @@ -240,7 +228,7 @@ public final class ExpressionResolver { ) return nil } - return validatedValue(value: result, validator: link.validator, rawValue: link.rawValue) + return validatedValue(value: result, link: link) } private func evaluate(_ parsedExpression: ParsedCalcExpression) throws -> T { @@ -253,14 +241,15 @@ public final class ExpressionResolver { private func validatedValue( value: T?, - validator: ExpressionValueValidator?, - rawValue: String + link: ExpressionLink ) -> T? { - if let validator = validator, let value = value { - if validator(value) { + if let validator = link.validator, let value = value { + if validator.isValid(value) { return value } else { - errorTracker(ExpressionError("Failed to validate value: \(value)", expression: rawValue)) + errorTracker( + ExpressionError("Failed to validate value: \(value)", expression: link.rawValue) + ) return nil } } diff --git a/client/ios/DivKit/Expressions/Serialization/DictionaryExtensions.swift b/client/ios/DivKit/Expressions/Serialization/DictionaryExtensions.swift index b3cccee1b..c902a17fd 100644 --- a/client/ios/DivKit/Expressions/Serialization/DictionaryExtensions.swift +++ b/client/ios/DivKit/Expressions/Serialization/DictionaryExtensions.swift @@ -18,13 +18,11 @@ extension Dictionary where Key == String, Value == Any { func getOptionalExpressionField( _ key: String, - transform: (U) -> T?, - validator: AnyValueValidator>? = nil + transform: (U) -> T? ) throws -> Field>? { try getOptionalField( key, - transform: { expressionTransform($0, transform: transform) }, - validator: validator + transform: { expressionTransform($0, transform: transform) } ) } @@ -36,15 +34,13 @@ extension Dictionary where Key == String, Value == Any { func getOptionalExpressionArray( _ key: String, - transform: (U) -> T?, - validator: AnyArrayValueValidator>? = nil + transform: (U) -> T? ) throws -> Field<[Expression]>? { try getOptionalArray( key, transform: { (value: U) in expressionTransform(value, transform: transform) - }, - validator: validator + } ) } } diff --git a/client/ios/DivKit/Expressions/Serialization/Expression+Helpers.swift b/client/ios/DivKit/Expressions/Serialization/Expression+Helpers.swift index 220cfdd92..19edf0afc 100644 --- a/client/ios/DivKit/Expressions/Serialization/Expression+Helpers.swift +++ b/client/ios/DivKit/Expressions/Serialization/Expression+Helpers.swift @@ -7,7 +7,7 @@ import Serialization func expressionTransform( _ value: Any?, transform: (U) -> T?, - validator: ExpressionValueValidator? = nil + validator: AnyValueValidator? = nil ) -> Expression? { do { if let rawValue = value as? String, @@ -32,7 +32,7 @@ func expressionTransform( return nil } - if let transformedValue = transform(value), validator?(transformedValue) != false { + if let transformedValue = transform(value), validator?.isValid(transformedValue) != false { return .value(transformedValue) } @@ -56,7 +56,7 @@ func deserialize( guard let result: Expression = expressionTransform( value, transform: transform, - validator: validator?.isValid + validator: validator ) else { return .failure(NonEmptyArray(.typeMismatch( expected: "Expression<\(U.self)>", @@ -76,11 +76,7 @@ func deserialize( deserialize( value, transform: { (rawElement: T) -> DeserializationResult> in - deserialize( - rawElement, - transform: transform, - validator: nil - ) + deserialize(rawElement, transform: transform) }, validator: validator ) @@ -94,7 +90,7 @@ func deserialize( guard let result: Expression = expressionTransform( value, transform: T.init(rawValue:), - validator: validator?.isValid + validator: validator ) else { return .failure(NonEmptyArray(.typeMismatch( expected: "Expression<\(T.self)>", diff --git a/client/ios/DivKit/Expressions/Serialization/FieldExtensions.swift b/client/ios/DivKit/Expressions/Serialization/FieldExtensions.swift index 8afdb39d4..a628ce1b9 100644 --- a/client/ios/DivKit/Expressions/Serialization/FieldExtensions.swift +++ b/client/ios/DivKit/Expressions/Serialization/FieldExtensions.swift @@ -25,12 +25,9 @@ extension Field { valueForLink: { safeValueForLink( { try context.templateData.getField( $0, - transform: { expressionTransform( - $0, - transform: E.init(rawValue:), - validator: validator?.isValid - ) }, - validator: nil + transform: { + expressionTransform($0, transform: E.init(rawValue:), validator: validator) + } ) }, link: $0 ) } @@ -47,12 +44,9 @@ extension Field { valueForLink: { safeValueForLink( { try context.templateData.getField( $0, - transform: { expressionTransform( - $0, - transform: transform, - validator: validator?.isValid - ) }, - validator: nil + transform: { + expressionTransform($0, transform: transform, validator: validator) + } ) }, link: $0 ) } @@ -75,7 +69,7 @@ extension Field { return context.templateData.getArray( link, transform: { (value: U) in - expressionTransform(value, transform: transform, validator: nil) + expressionTransform(value, transform: transform) }, validator: validator ) diff --git a/client/ios/DivKit/Extensions/DivAction/DivActionBase.swift b/client/ios/DivKit/Extensions/DivAction/DivActionBase.swift index ff96ebb27..2bf5adec6 100644 --- a/client/ios/DivKit/Extensions/DivAction/DivActionBase.swift +++ b/client/ios/DivKit/Extensions/DivAction/DivActionBase.swift @@ -25,7 +25,14 @@ extension DivActionBase { ) -> UserInterfaceAction.Payload { // url parameter is used for backward compatibility, it should be removed // when all custom div-action handlers will be replaced - let url = url?.rawValue.map { $0.adding(cardId: cardId.rawValue) } + let url: URL? + switch self.url { + case let .value(value): + url = value.adding(cardId: cardId.rawValue) + case .link, .none: + url = nil + } + return .divAction( params: UserInterfaceAction.DivActionParams( action: .object(toDictionary().typedJSON()), diff --git a/client/ios/DivKit/Templates/SerializationExtensions.swift b/client/ios/DivKit/Templates/SerializationExtensions.swift index 724b5e0be..a2599a5c1 100644 --- a/client/ios/DivKit/Templates/SerializationExtensions.swift +++ b/client/ios/DivKit/Templates/SerializationExtensions.swift @@ -29,12 +29,10 @@ extension Dictionary where Key == String, Value == Any { @inlinable func getOptionalField( _ key: String, - transform: (U) -> T?, - validator: AnyValueValidator? = nil + transform: (U) -> T? ) throws -> Field? { Field.makeOptional( - valueGetter: (try? self.getOptionalField(key, transform: transform, validator: validator)) - .flatMap { $0 }, + valueGetter: (try? self.getOptionalField(key, transform: transform)).flatMap { $0 }, linkGetter: link(for: key) ) } @@ -58,13 +56,11 @@ extension Dictionary where Key == String, Value == Any { @inlinable func getOptionalField( _ key: String, - templateToType: [TemplateName: String], - validator: AnyValueValidator? = nil + templateToType: [TemplateName: String] ) throws -> Field? { try getOptionalField( key, - transform: { (dict: Self) in try? T(dictionary: dict, templateToType: templateToType) }, - validator: validator + transform: { (dict: Self) in try? T(dictionary: dict, templateToType: templateToType) } ) } } @@ -113,13 +109,11 @@ extension Dictionary where Key == String, Value == Any { @inlinable func getOptionalArray( _ key: String, - templateToType: [TemplateName: String], - validator: AnyArrayValueValidator? = nil + templateToType: [TemplateName: String] ) throws -> Field<[T]>? { try getOptionalArray( key, - transform: { (dict: Self) in try? T(dictionary: dict, templateToType: templateToType) }, - validator: validator + transform: { (dict: Self) in try? T(dictionary: dict, templateToType: templateToType) } ) } } @@ -128,13 +122,11 @@ extension Dictionary where Key == String, Value == Any { @inlinable func getField( _ key: String, - templateToType: [TemplateName: String], - validator: AnyValueValidator? = nil + templateToType: [TemplateName: String] ) throws -> T { try getField( key, - transform: { (dict: Self) in try T(dictionary: dict, templateToType: templateToType) }, - validator: validator + transform: { (dict: Self) in try T(dictionary: dict, templateToType: templateToType) } ) } } @@ -163,7 +155,6 @@ func deserialize( _ value: Any, templates: [TemplateName: Any], templateToType: [TemplateName: String], - validator: AnyValueValidator? = nil, type: T.Type ) -> DeserializationResult { deserialize( @@ -172,8 +163,7 @@ func deserialize( templates: templates, templateToType: templateToType, type: type - ), - validator: validator + ) ) } diff --git a/client/ios/DivKitTests/DivStateManagerTests.swift b/client/ios/DivKitTests/DivStateManagerTests.swift index 8befcb607..ac3158378 100644 --- a/client/ios/DivKitTests/DivStateManagerTests.swift +++ b/client/ios/DivKitTests/DivStateManagerTests.swift @@ -7,12 +7,8 @@ import LayoutKit final class DivStateManagerTests: XCTestCase { private let stateManager = DivStateManager() - private let transition = DivAppearanceTransition.divFadeTransition(DivFadeTransition()) - - private let visibilityExpression = Expression.link( - try! ExpressionLink(rawValue: "@{visibility}", validator: nil)! - ) + private let visibilityExpression: Expression = expression("@{visibility}") func test_shouldBlockAppearWithTransition_isFalseForNewBlock() { XCTAssertFalse( diff --git a/client/ios/DivKitTests/Entities/SimplePropertiesTests.swift b/client/ios/DivKitTests/Entities/SimplePropertiesTests.swift index 345779049..7b3509220 100644 --- a/client/ios/DivKitTests/Entities/SimplePropertiesTests.swift +++ b/client/ios/DivKitTests/Entities/SimplePropertiesTests.swift @@ -8,19 +8,19 @@ final class SimplePropertiesTests: XCTestCase { // TODO: boolean property represented by integer value must fail // XCTAssertNil(entity!.boolean!.rawValue!) - XCTAssertTrue(entity!.boolean!.rawValue!) + XCTAssertEqual(entity!.boolean, .value(true)) } func test_BooleanIntAsBoolean() throws { let entity = try readEntity("boolean_int_as_boolean") - XCTAssertTrue(entity!.booleanInt!.rawValue!) + XCTAssertEqual(entity!.booleanInt, .value(true)) } func test_BooleanIntAsInt() throws { let entity = try readEntity("boolean_int_as_int") - XCTAssertTrue(entity!.booleanInt!.rawValue!) + XCTAssertEqual(entity!.booleanInt, .value(true)) } }