Expression validators cleanup

This commit is contained in:
pkurchatov
2023-12-27 11:39:42 +03:00
parent c3d18f7ca0
commit 292ba8b10a
12 changed files with 59 additions and 99 deletions
@@ -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
}
}
@@ -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:
@@ -4,15 +4,6 @@ import Foundation
public enum Expression<T> {
case value(T)
case link(ExpressionLink<T>)
public var rawValue: T? {
switch self {
case let .value(value):
return value
case .link:
return nil
}
}
}
extension Expression: Equatable where T: Equatable {
@@ -1,6 +1,7 @@
import Foundation
import CommonCorePublic
import Serialization
public struct ExpressionLink<T> {
enum Item {
@@ -12,13 +13,12 @@ public struct ExpressionLink<T> {
let items: [Item]
let variablesNames: [String]
let rawValue: String
let validator: ExpressionValueValidator<T>?
let errorTracker: ExpressionErrorTracker?
let validator: AnyValueValidator<T>?
@usableFromInline
init?(
rawValue: String,
validator: ExpressionValueValidator<T>? = nil,
validator: AnyValueValidator<T>? = nil,
errorTracker: ExpressionErrorTracker? = nil,
resolveNested: Bool = true
) throws {
@@ -76,7 +76,6 @@ public struct ExpressionLink<T> {
self.variablesNames = variablesNames
self.rawValue = rawValue
self.validator = validator
self.errorTracker = errorTracker
}
}
@@ -2,8 +2,6 @@ import Foundation
import CommonCorePublic
public typealias ExpressionValueValidator<T> = (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<T>(
link: ExpressionLink<T>,
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<String>(
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<T>(_ parsedExpression: ParsedCalcExpression) throws -> T {
@@ -253,14 +241,15 @@ public final class ExpressionResolver {
private func validatedValue<T>(
value: T?,
validator: ExpressionValueValidator<T>?,
rawValue: String
link: ExpressionLink<T>
) -> 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
}
}
@@ -18,13 +18,11 @@ extension Dictionary where Key == String, Value == Any {
func getOptionalExpressionField<T, U>(
_ key: String,
transform: (U) -> T?,
validator: AnyValueValidator<Expression<T>>? = nil
transform: (U) -> T?
) throws -> Field<Expression<T>>? {
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<T, U>(
_ key: String,
transform: (U) -> T?,
validator: AnyArrayValueValidator<Expression<T>>? = nil
transform: (U) -> T?
) throws -> Field<[Expression<T>]>? {
try getOptionalArray(
key,
transform: { (value: U) in
expressionTransform(value, transform: transform)
},
validator: validator
}
)
}
}
@@ -7,7 +7,7 @@ import Serialization
func expressionTransform<T, U>(
_ value: Any?,
transform: (U) -> T?,
validator: ExpressionValueValidator<T>? = nil
validator: AnyValueValidator<T>? = nil
) -> Expression<T>? {
do {
if let rawValue = value as? String,
@@ -32,7 +32,7 @@ func expressionTransform<T, U>(
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<T: ValidSerializationValue, U>(
guard let result: Expression<U> = expressionTransform(
value,
transform: transform,
validator: validator?.isValid
validator: validator
) else {
return .failure(NonEmptyArray(.typeMismatch(
expected: "Expression<\(U.self)>",
@@ -76,11 +76,7 @@ func deserialize<T: ValidSerializationValue, U>(
deserialize(
value,
transform: { (rawElement: T) -> DeserializationResult<Expression<U>> in
deserialize(
rawElement,
transform: transform,
validator: nil
)
deserialize(rawElement, transform: transform)
},
validator: validator
)
@@ -94,7 +90,7 @@ func deserialize<T: RawRepresentable>(
guard let result: Expression<T> = expressionTransform(
value,
transform: T.init(rawValue:),
validator: validator?.isValid
validator: validator
) else {
return .failure(NonEmptyArray(.typeMismatch(
expected: "Expression<\(T.self)>",
@@ -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
)
@@ -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()),
@@ -29,12 +29,10 @@ extension Dictionary where Key == String, Value == Any {
@inlinable
func getOptionalField<T, U>(
_ key: String,
transform: (U) -> T?,
validator: AnyValueValidator<T>? = nil
transform: (U) -> T?
) throws -> Field<T>? {
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<T: TemplateValue>(
_ key: String,
templateToType: [TemplateName: String],
validator: AnyValueValidator<T>? = nil
templateToType: [TemplateName: String]
) throws -> Field<T>? {
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<T: TemplateValue>(
_ key: String,
templateToType: [TemplateName: String],
validator: AnyArrayValueValidator<T>? = 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<T: TemplateValue>(
_ key: String,
templateToType: [TemplateName: String],
validator: AnyValueValidator<T>? = 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<T: TemplateValue>(
_ value: Any,
templates: [TemplateName: Any],
templateToType: [TemplateName: String],
validator: AnyValueValidator<T.ResolvedValue>? = nil,
type: T.Type
) -> DeserializationResult<T.ResolvedValue> {
deserialize(
@@ -172,8 +163,7 @@ func deserialize<T: TemplateValue>(
templates: templates,
templateToType: templateToType,
type: type
),
validator: validator
)
)
}
@@ -7,12 +7,8 @@ import LayoutKit
final class DivStateManagerTests: XCTestCase {
private let stateManager = DivStateManager()
private let transition = DivAppearanceTransition.divFadeTransition(DivFadeTransition())
private let visibilityExpression = Expression<DivVisibility>.link(
try! ExpressionLink(rawValue: "@{visibility}", validator: nil)!
)
private let visibilityExpression: Expression<DivVisibility> = expression("@{visibility}")
func test_shouldBlockAppearWithTransition_isFalseForNewBlock() {
XCTAssertFalse(
@@ -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))
}
}