mirror of
https://github.com/divkit/divkit.git
synced 2026-06-06 20:07:59 +00:00
Expression validators cleanup
This commit is contained in:
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user