Added untyped template resolver

commit_hash:299c999f2d6caad74d8568059e890770d7a6b1f2
This commit is contained in:
feldspar
2026-02-13 11:57:25 +03:00
parent 8e6a34ef63
commit e234cd83fa
269 changed files with 4644 additions and 153 deletions
+2
View File
@@ -16419,6 +16419,7 @@
"client/ios/DivKit/Templates/TemplateError.swift":"divkit/public/client/ios/DivKit/Templates/TemplateError.swift",
"client/ios/DivKit/Templates/TemplateToType.swift":"divkit/public/client/ios/DivKit/Templates/TemplateToType.swift",
"client/ios/DivKit/Templates/TemplateValue.swift":"divkit/public/client/ios/DivKit/Templates/TemplateValue.swift",
"client/ios/DivKit/Templates/UntypedDivTemplateResolver.swift":"divkit/public/client/ios/DivKit/Templates/UntypedDivTemplateResolver.swift",
"client/ios/DivKit/Timers/DivTimerAction.swift":"divkit/public/client/ios/DivKit/Timers/DivTimerAction.swift",
"client/ios/DivKit/Timers/DivTimerController.swift":"divkit/public/client/ios/DivKit/Timers/DivTimerController.swift",
"client/ios/DivKit/Timers/DivTimerStorage.swift":"divkit/public/client/ios/DivKit/Timers/DivTimerStorage.swift",
@@ -16982,6 +16983,7 @@
"client/ios/DivKitTests/Actions/ScrollActionHandlerTests.swift":"divkit/public/client/ios/DivKitTests/Actions/ScrollActionHandlerTests.swift",
"client/ios/DivKitTests/Actions/SetStoredValueActionHandlerTests.swift":"divkit/public/client/ios/DivKitTests/Actions/SetStoredValueActionHandlerTests.swift",
"client/ios/DivKitTests/Actions/SubmitActionHandlerTests.swift":"divkit/public/client/ios/DivKitTests/Actions/SubmitActionHandlerTests.swift",
"client/ios/DivKitTests/DictionarySerializationBehaviorTests.swift":"divkit/public/client/ios/DivKitTests/DictionarySerializationBehaviorTests.swift",
"client/ios/DivKitTests/DivBlockModelingContextTests.swift":"divkit/public/client/ios/DivKitTests/DivBlockModelingContextTests.swift",
"client/ios/DivKitTests/DivBlockStateStorageTests.swift":"divkit/public/client/ios/DivKitTests/DivBlockStateStorageTests.swift",
"client/ios/DivKitTests/DivDataParsingTests.swift":"divkit/public/client/ios/DivKitTests/DivDataParsingTests.swift",
@@ -150,8 +150,11 @@ class SwiftGenerator(Generator):
def __entity_enumeration_extensions_declaration(self, entity_enumeration: EntityEnumeration) -> List[str]:
access_modifier = self._access_level.value
args_decl = swift_template_deserializable_args_decl(entity_enumeration.mode)
context_arg = '' if entity_enumeration.mode.is_template else ', context: ParsingContext'
deserializable_extension = Text(f'extension {entity_enumeration.prefixed_declaration} {{')
deserializable_extension += f' {access_modifier}init(dictionary: [String: Any]{args_decl}) throws {{'
deserializable_extension += (
f' {access_modifier}init(dictionary: [String: Any]{args_decl}{context_arg}) throws {{'
)
body = Text()
if entity_enumeration.mode.is_template:
body += 'let receivedType = try dictionary.getField("type") as String'
@@ -164,8 +167,9 @@ class SwiftGenerator(Generator):
obj_t = entity[1].prefixed_declaration if entity[1] is not None else utils.capitalize_camel_case(entity[0])
low_name = utils.lower_camel_case(name)
args = swift_template_deserializable_args(entity_enumeration.mode)
context_param = '' if entity_enumeration.mode.is_template else ', context: context'
body += f'case {obj_t}.type:'
body += f' self = .{low_name}(try {obj_t}(dictionary: dictionary{args}))'
body += f' self = .{low_name}(try {obj_t}(dictionary: dictionary{args}{context_param}))'
body += 'default:'
args = f'field: "{entity_enumeration.name}", representation: dictionary'
body += f' throw DeserializationError.invalidFieldRepresentation({args})'
@@ -194,7 +198,10 @@ class SwiftGenerator(Generator):
equatable_extension = str(equatable_extension)
if self.generate_serialization:
if entity_enumeration.mode is GenerationMode.NORMAL_WITHOUT_TEMPLATES:
if entity_enumeration.mode in [
GenerationMode.NORMAL_WITHOUT_TEMPLATES,
GenerationMode.NORMAL_WITH_TEMPLATES,
]:
result = [deserializable_extension, equatable_extension]
elif entity_enumeration.mode.is_template:
result = [deserializable_extension]
@@ -252,8 +259,12 @@ class SwiftGenerator(Generator):
result += validator_decl.indented()
result += EMPTY
if self.generate_serialization and (entity.generation_mode is GenerationMode.NORMAL_WITHOUT_TEMPLATES or
entity.generation_mode.is_template):
if self.generate_serialization and (
entity.generation_mode in [
GenerationMode.NORMAL_WITHOUT_TEMPLATES,
GenerationMode.NORMAL_WITH_TEMPLATES,
] or entity.generation_mode.is_template
):
result += entity.deserializing_constructor_declaration.indented()
result += EMPTY
@@ -122,16 +122,21 @@ class SwiftEntity(Entity):
def deserializing_constructor_declaration(self) -> Text:
props = self.instance_properties_swift
args_decl = swift_template_deserializable_args_decl(self.generation_mode)
context_arg = '' if self.generation_mode.is_template else ', context: ParsingContext'
if not props:
return Text(f'public init(dictionary: [String: Any]{args_decl}) throws {{}}')
result = Text(f'public convenience init(dictionary: [String: Any]{args_decl}) throws {{')
return Text(f'public init(dictionary: [String: Any]{args_decl}{context_arg}) throws {{}}')
result = Text(f'public convenience init(dictionary: [String: Any]{args_decl}{context_arg}) throws {{')
lines = Text('self.init(')
for prop in props:
tail = '' if prop == props[-1] else ','
if prop.name == PARENT_PROPERTY.name:
lines += f' {prop.declaration_name}: dictionary["type"] as? String{tail}'
else:
lines += f' {prop.declaration_name}: dictionary.{prop.deserialization_expression}{tail}'
throw_prefix = '' if self.generation_mode.is_template else 'try '
lines += (
f' {prop.declaration_name}: '
f'{throw_prefix}dictionary.{prop.deserialization_expression_for_mode(self.generation_mode)}{tail}'
)
lines += ')'
result += lines.indented()
result += '}'
@@ -587,19 +592,54 @@ class SwiftProperty(Property):
@property
def deserialization_expression(self) -> str:
return self.deserialization_expression_for_mode(self.mode)
def deserialization_expression_for_mode(self, mode: GenerationMode) -> str:
is_normal_mode = mode in [
GenerationMode.NORMAL_WITH_TEMPLATES,
GenerationMode.NORMAL_WITHOUT_TEMPLATES,
]
if is_normal_mode and self.property_type.can_be_templated:
validator_arg_string = self.validator_arg(entity_name='Self', mode=mode)
declaration_mode = SwiftProperty.SwiftMode(value=mode, use_expressions=False)
if isinstance(self.property_type, Array):
item_type = cast(SwiftPropertyType, self.property_type.property_type).prefixed_declaration(
declaration_mode
)
optional_suffix = 'Optional' if self.parsed_value_is_optional else ''
expr = (
f'"{self.dict_field}", transform: '
f'{{ (dict: [String: Any]) in try? {item_type}(dictionary: dict, context: context) }}'
f'{validator_arg_string}'
)
return f'get{optional_suffix}Array({expr})'
value_type = cast(SwiftPropertyType, self.property_type).prefixed_declaration(
declaration_mode
)
optional_suffix = 'Optional' if self.parsed_value_is_optional else ''
expr = (
f'"{self.dict_field}", transform: '
f'{{ (dict: [String: Any]) in try {value_type}(dictionary: dict, context: context) }}'
f'{validator_arg_string}'
)
return f'get{optional_suffix}Field({expr})'
if isinstance(self.property_type, Array):
type_suffix = 'Array'
else:
type_suffix = 'Field'
if self.property_type.can_be_templated:
template_to_type_arg = swift_template_deserializable_args(self.mode)
template_to_type_arg = swift_template_deserializable_args(mode)
else:
template_to_type_arg = ''
optional_suffix = 'Optional' if self.parsed_value_is_optional or self.mode.is_template else ''
optional_suffix = 'Optional' if self.parsed_value_is_optional or mode.is_template else ''
expression_suffix = 'Expression' if self.supports_expressions else ''
validator_arg_string = self.validator_arg(entity_name='Self', mode=self.mode)
validator_arg_string = self.validator_arg(entity_name='Self', mode=mode)
transformed = cast(SwiftPropertyType, self.property_type).transform_arg
expr = f'"{self.dict_field}"{template_to_type_arg}{transformed}{validator_arg_string}'
context_arg = ', context: context' if is_normal_mode else ''
expr = f'"{self.dict_field}"{template_to_type_arg}{transformed}{validator_arg_string}{context_arg}'
return f'get{optional_suffix}{expression_suffix}{type_suffix}({expr})'
def add_default_value_to(self, declaration: str, public_default_value: bool = False) -> str:
@@ -71,47 +71,47 @@ public enum Entity: Sendable {
}
extension Entity {
public init(dictionary: [String: Any]) throws {
public init(dictionary: [String: Any], context: ParsingContext) throws {
let blockType = try dictionary.getField("type") as String
switch blockType {
case EntityWithArray.type:
self = .entityWithArray(try EntityWithArray(dictionary: dictionary))
self = .entityWithArray(try EntityWithArray(dictionary: dictionary, context: context))
case EntityWithArrayOfEnums.type:
self = .entityWithArrayOfEnums(try EntityWithArrayOfEnums(dictionary: dictionary))
self = .entityWithArrayOfEnums(try EntityWithArrayOfEnums(dictionary: dictionary, context: context))
case EntityWithArrayOfExpressions.type:
self = .entityWithArrayOfExpressions(try EntityWithArrayOfExpressions(dictionary: dictionary))
self = .entityWithArrayOfExpressions(try EntityWithArrayOfExpressions(dictionary: dictionary, context: context))
case EntityWithArrayOfNestedItems.type:
self = .entityWithArrayOfNestedItems(try EntityWithArrayOfNestedItems(dictionary: dictionary))
self = .entityWithArrayOfNestedItems(try EntityWithArrayOfNestedItems(dictionary: dictionary, context: context))
case EntityWithArrayWithTransform.type:
self = .entityWithArrayWithTransform(try EntityWithArrayWithTransform(dictionary: dictionary))
self = .entityWithArrayWithTransform(try EntityWithArrayWithTransform(dictionary: dictionary, context: context))
case EntityWithComplexProperty.type:
self = .entityWithComplexProperty(try EntityWithComplexProperty(dictionary: dictionary))
self = .entityWithComplexProperty(try EntityWithComplexProperty(dictionary: dictionary, context: context))
case EntityWithComplexPropertyWithDefaultValue.type:
self = .entityWithComplexPropertyWithDefaultValue(try EntityWithComplexPropertyWithDefaultValue(dictionary: dictionary))
self = .entityWithComplexPropertyWithDefaultValue(try EntityWithComplexPropertyWithDefaultValue(dictionary: dictionary, context: context))
case EntityWithEntityProperty.type:
self = .entityWithEntityProperty(try EntityWithEntityProperty(dictionary: dictionary))
self = .entityWithEntityProperty(try EntityWithEntityProperty(dictionary: dictionary, context: context))
case EntityWithOptionalComplexProperty.type:
self = .entityWithOptionalComplexProperty(try EntityWithOptionalComplexProperty(dictionary: dictionary))
self = .entityWithOptionalComplexProperty(try EntityWithOptionalComplexProperty(dictionary: dictionary, context: context))
case EntityWithOptionalProperty.type:
self = .entityWithOptionalProperty(try EntityWithOptionalProperty(dictionary: dictionary))
self = .entityWithOptionalProperty(try EntityWithOptionalProperty(dictionary: dictionary, context: context))
case EntityWithOptionalStringEnumProperty.type:
self = .entityWithOptionalStringEnumProperty(try EntityWithOptionalStringEnumProperty(dictionary: dictionary))
self = .entityWithOptionalStringEnumProperty(try EntityWithOptionalStringEnumProperty(dictionary: dictionary, context: context))
case EntityWithPropertyWithDefaultValue.type:
self = .entityWithPropertyWithDefaultValue(try EntityWithPropertyWithDefaultValue(dictionary: dictionary))
self = .entityWithPropertyWithDefaultValue(try EntityWithPropertyWithDefaultValue(dictionary: dictionary, context: context))
case EntityWithRawArray.type:
self = .entityWithRawArray(try EntityWithRawArray(dictionary: dictionary))
self = .entityWithRawArray(try EntityWithRawArray(dictionary: dictionary, context: context))
case EntityWithRequiredProperty.type:
self = .entityWithRequiredProperty(try EntityWithRequiredProperty(dictionary: dictionary))
self = .entityWithRequiredProperty(try EntityWithRequiredProperty(dictionary: dictionary, context: context))
case EntityWithSimpleProperties.type:
self = .entityWithSimpleProperties(try EntityWithSimpleProperties(dictionary: dictionary))
self = .entityWithSimpleProperties(try EntityWithSimpleProperties(dictionary: dictionary, context: context))
case EntityWithStringArrayProperty.type:
self = .entityWithStringArrayProperty(try EntityWithStringArrayProperty(dictionary: dictionary))
self = .entityWithStringArrayProperty(try EntityWithStringArrayProperty(dictionary: dictionary, context: context))
case EntityWithStringEnumProperty.type:
self = .entityWithStringEnumProperty(try EntityWithStringEnumProperty(dictionary: dictionary))
self = .entityWithStringEnumProperty(try EntityWithStringEnumProperty(dictionary: dictionary, context: context))
case EntityWithStringEnumPropertyWithDefaultValue.type:
self = .entityWithStringEnumPropertyWithDefaultValue(try EntityWithStringEnumPropertyWithDefaultValue(dictionary: dictionary))
self = .entityWithStringEnumPropertyWithDefaultValue(try EntityWithStringEnumPropertyWithDefaultValue(dictionary: dictionary, context: context))
case EntityWithoutProperties.type:
self = .entityWithoutProperties(try EntityWithoutProperties(dictionary: dictionary))
self = .entityWithoutProperties(try EntityWithoutProperties(dictionary: dictionary, context: context))
default:
throw DeserializationError.invalidFieldRepresentation(field: "entity", representation: dictionary)
}
@@ -11,6 +11,12 @@ public final class EntityWithArray: Sendable {
static let arrayValidator: AnyArrayValueValidator<Entity> =
makeArrayValidator(minItems: 1)
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
array: try dictionary.getArray("array", transform: { (dict: [String: Any]) in try? Entity(dictionary: dict, context: context) }, validator: Self.arrayValidator)
)
}
init(
array: [Entity]
) {
@@ -17,6 +17,12 @@ public final class EntityWithArrayOfEnums: Sendable {
static let itemsValidator: AnyArrayValueValidator<EntityWithArrayOfEnums.Item> =
makeArrayValidator(minItems: 1)
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
items: try dictionary.getArray("items", validator: Self.itemsValidator, context: context)
)
}
init(
items: [Item]
) {
@@ -15,6 +15,12 @@ public final class EntityWithArrayOfExpressions: Sendable {
static let itemsValidator: AnyArrayValueValidator<Expression<String>> =
makeArrayValidator(minItems: 1)
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
items: try dictionary.getExpressionArray("items", validator: Self.itemsValidator, context: context)
)
}
init(
items: [Expression<String>]
) {
@@ -13,6 +13,13 @@ public final class EntityWithArrayOfNestedItems: Sendable {
resolver.resolveString(property)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
entity: try dictionary.getField("entity", transform: { (dict: [String: Any]) in try Entity(dictionary: dict, context: context) }),
property: try dictionary.getExpressionField("property", context: context)
)
}
init(
entity: Entity,
property: Expression<String>
@@ -28,6 +35,12 @@ public final class EntityWithArrayOfNestedItems: Sendable {
static let itemsValidator: AnyArrayValueValidator<EntityWithArrayOfNestedItems.Item> =
makeArrayValidator(minItems: 1)
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
items: try dictionary.getArray("items", transform: { (dict: [String: Any]) in try? EntityWithArrayOfNestedItems.Item(dictionary: dict, context: context) }, validator: Self.itemsValidator)
)
}
init(
items: [Item]
) {
@@ -15,6 +15,12 @@ public final class EntityWithArrayWithTransform: Sendable {
static let arrayValidator: AnyArrayValueValidator<Expression<Color>> =
makeArrayValidator(minItems: 1)
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
array: try dictionary.getExpressionArray("array", transform: Color.color(withHexString:), validator: Self.arrayValidator, context: context)
)
}
init(
array: [Expression<Color>]
) {
@@ -12,6 +12,12 @@ public final class EntityWithComplexProperty: Sendable {
resolver.resolveUrl(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
value: try dictionary.getExpressionField("value", transform: URL.makeFromNonEncodedString, context: context)
)
}
init(
value: Expression<URL>
) {
@@ -22,6 +28,12 @@ public final class EntityWithComplexProperty: Sendable {
public static let type: String = "entity_with_complex_property"
public let property: Property
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
property: try dictionary.getField("property", transform: { (dict: [String: Any]) in try EntityWithComplexProperty.Property(dictionary: dict, context: context) })
)
}
init(
property: Property
) {
@@ -12,6 +12,12 @@ public final class EntityWithComplexPropertyWithDefaultValue: Sendable {
resolver.resolveString(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
value: try dictionary.getExpressionField("value", context: context)
)
}
init(
value: Expression<String>
) {
@@ -22,6 +28,12 @@ public final class EntityWithComplexPropertyWithDefaultValue: Sendable {
public static let type: String = "entity_with_complex_property_with_default_value"
public let property: Property // default value: EntityWithComplexPropertyWithDefaultValue.Property(value: .value("Default text"))
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
property: try dictionary.getOptionalField("property", transform: { (dict: [String: Any]) in try EntityWithComplexPropertyWithDefaultValue.Property(dictionary: dict, context: context) })
)
}
init(
property: Property? = nil
) {
@@ -8,6 +8,12 @@ public final class EntityWithEntityProperty: Sendable {
public static let type: String = "entity_with_entity_property"
public let entity: Entity // default value: .entityWithStringEnumProperty(EntityWithStringEnumProperty(property: .value(.second)))
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
entity: try dictionary.getOptionalField("entity", transform: { (dict: [String: Any]) in try Entity(dictionary: dict, context: context) })
)
}
init(
entity: Entity? = nil
) {
@@ -8,6 +8,12 @@ public final class EntityWithJsonProperty: @unchecked Sendable {
public static let type: String = "entity_with_json_property"
public let jsonProperty: [String: Any] // default value: { "key": "value", "items": [ "value" ] }
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
jsonProperty: try dictionary.getOptionalField("json_property", context: context)
)
}
init(
jsonProperty: [String: Any]? = nil
) {
@@ -12,6 +12,12 @@ public final class EntityWithOptionalComplexProperty: Sendable {
resolver.resolveUrl(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
value: try dictionary.getExpressionField("value", transform: URL.makeFromNonEncodedString, context: context)
)
}
init(
value: Expression<URL>
) {
@@ -22,6 +28,12 @@ public final class EntityWithOptionalComplexProperty: Sendable {
public static let type: String = "entity_with_optional_complex_property"
public let property: Property?
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
property: try dictionary.getOptionalField("property", transform: { (dict: [String: Any]) in try EntityWithOptionalComplexProperty.Property(dictionary: dict, context: context) })
)
}
init(
property: Property? = nil
) {
@@ -12,6 +12,12 @@ public final class EntityWithOptionalProperty: Sendable {
resolver.resolveString(property)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
property: try dictionary.getOptionalExpressionField("property", context: context)
)
}
init(
property: Expression<String>? = nil
) {
@@ -18,6 +18,12 @@ public final class EntityWithOptionalStringEnumProperty: Sendable {
resolver.resolveEnum(property)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
property: try dictionary.getOptionalExpressionField("property", context: context)
)
}
init(
property: Expression<Property>? = nil
) {
@@ -28,6 +28,14 @@ public final class EntityWithPropertyWithDefaultValue: Sendable {
static let urlValidator: AnyValueValidator<URL> =
makeURLValidator(schemes: ["https"])
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
int: try dictionary.getOptionalExpressionField("int", validator: Self.intValidator, context: context),
nonOptional: try dictionary.getExpressionField("non_optional", context: context),
url: try dictionary.getOptionalExpressionField("url", transform: URL.makeFromNonEncodedString, validator: Self.urlValidator, context: context)
)
}
init(
int: Expression<Int>? = nil,
nonOptional: Expression<String>,
@@ -58,6 +66,14 @@ public final class EntityWithPropertyWithDefaultValue: Sendable {
static let urlValidator: AnyValueValidator<URL> =
makeURLValidator(schemes: ["https"])
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
int: try dictionary.getOptionalExpressionField("int", validator: Self.intValidator, context: context),
nested: try dictionary.getOptionalField("nested", transform: { (dict: [String: Any]) in try EntityWithPropertyWithDefaultValue.Nested(dictionary: dict, context: context) }),
url: try dictionary.getOptionalExpressionField("url", transform: URL.makeFromNonEncodedString, validator: Self.urlValidator, context: context)
)
}
init(
int: Expression<Int>? = nil,
nested: Nested? = nil,
@@ -12,6 +12,12 @@ public final class EntityWithRawArray: @unchecked Sendable {
resolver.resolveArray(array)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
array: try dictionary.getExpressionField("array", context: context)
)
}
init(
array: Expression<[Any]>
) {
@@ -15,6 +15,12 @@ public final class EntityWithRequiredProperty: Sendable {
static let propertyValidator: AnyValueValidator<String> =
makeStringValidator(minLength: 1)
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
property: try dictionary.getExpressionField("property", validator: Self.propertyValidator, context: context)
)
}
init(
property: Expression<String>
) {
@@ -51,6 +51,20 @@ public final class EntityWithSimpleProperties: EntityProtocol, Sendable {
static let positiveIntegerValidator: AnyValueValidator<Int> =
makeValueValidator(valueValidator: { $0 > 0 })
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
boolean: try dictionary.getOptionalExpressionField("boolean", context: context),
booleanInt: try dictionary.getOptionalExpressionField("boolean_int", context: context),
color: try dictionary.getOptionalExpressionField("color", transform: Color.color(withHexString:), context: context),
double: try dictionary.getOptionalExpressionField("double", context: context),
id: try dictionary.getOptionalField("id", context: context),
integer: try dictionary.getOptionalExpressionField("integer", context: context),
positiveInteger: try dictionary.getOptionalExpressionField("positive_integer", validator: Self.positiveIntegerValidator, context: context),
string: try dictionary.getOptionalExpressionField("string", context: context),
url: try dictionary.getOptionalExpressionField("url", transform: URL.makeFromNonEncodedString, context: context)
)
}
init(
boolean: Expression<Bool>? = nil,
booleanInt: Expression<Bool>? = nil,
@@ -15,6 +15,12 @@ public final class EntityWithStringArrayProperty: Sendable {
static let arrayValidator: AnyArrayValueValidator<Expression<String>> =
makeArrayValidator(minItems: 1)
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
array: try dictionary.getExpressionArray("array", validator: Self.arrayValidator, context: context)
)
}
init(
array: [Expression<String>]
) {
@@ -18,6 +18,12 @@ public final class EntityWithStringEnumProperty: Sendable {
resolver.resolveEnum(property)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
property: try dictionary.getExpressionField("property", context: context)
)
}
init(
property: Expression<Property>
) {
@@ -19,6 +19,12 @@ public final class EntityWithStringEnumPropertyWithDefaultValue: Sendable {
resolver.resolveEnum(value) ?? Value.second
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
value: try dictionary.getOptionalExpressionField("value", context: context)
)
}
init(
value: Expression<Value>? = nil
) {
@@ -7,6 +7,8 @@ import Serialization
public final class EntityWithoutProperties: Sendable {
public static let type: String = "entity_without_properties"
public init(dictionary: [String: Any], context: ParsingContext) throws {}
init() {}
}
@@ -20,13 +20,13 @@ public enum EnumWithDefaultType: Sendable {
}
extension EnumWithDefaultType {
public init(dictionary: [String: Any]) throws {
public init(dictionary: [String: Any], context: ParsingContext) throws {
let blockType = try dictionary.getField("type") as String
switch blockType {
case WithDefault.type:
self = .withDefault(try WithDefault(dictionary: dictionary))
self = .withDefault(try WithDefault(dictionary: dictionary, context: context))
case WithoutDefault.type:
self = .withoutDefault(try WithoutDefault(dictionary: dictionary))
self = .withoutDefault(try WithoutDefault(dictionary: dictionary, context: context))
default:
throw DeserializationError.invalidFieldRepresentation(field: "enum_with_default_type", representation: dictionary)
}
@@ -7,7 +7,7 @@ import Serialization
public final class WithDefault: Sendable {
public static let type: String = "default"
public init(dictionary: [String: Any]) throws {}
public init(dictionary: [String: Any], context: ParsingContext) throws {}
init() {}
}
@@ -7,7 +7,7 @@ import Serialization
public final class WithoutDefault: Sendable {
public static let type: String = "non_default"
public init(dictionary: [String: Any]) throws {}
public init(dictionary: [String: Any], context: ParsingContext) throws {}
init() {}
}
@@ -70,6 +70,54 @@ public enum Entity: Sendable {
}
}
extension Entity {
public init(dictionary: [String: Any], context: ParsingContext) throws {
let blockType = try dictionary.getField("type") as String
switch blockType {
case EntityWithArray.type:
self = .entityWithArray(try EntityWithArray(dictionary: dictionary, context: context))
case EntityWithArrayOfEnums.type:
self = .entityWithArrayOfEnums(try EntityWithArrayOfEnums(dictionary: dictionary, context: context))
case EntityWithArrayOfExpressions.type:
self = .entityWithArrayOfExpressions(try EntityWithArrayOfExpressions(dictionary: dictionary, context: context))
case EntityWithArrayOfNestedItems.type:
self = .entityWithArrayOfNestedItems(try EntityWithArrayOfNestedItems(dictionary: dictionary, context: context))
case EntityWithArrayWithTransform.type:
self = .entityWithArrayWithTransform(try EntityWithArrayWithTransform(dictionary: dictionary, context: context))
case EntityWithComplexProperty.type:
self = .entityWithComplexProperty(try EntityWithComplexProperty(dictionary: dictionary, context: context))
case EntityWithComplexPropertyWithDefaultValue.type:
self = .entityWithComplexPropertyWithDefaultValue(try EntityWithComplexPropertyWithDefaultValue(dictionary: dictionary, context: context))
case EntityWithEntityProperty.type:
self = .entityWithEntityProperty(try EntityWithEntityProperty(dictionary: dictionary, context: context))
case EntityWithOptionalComplexProperty.type:
self = .entityWithOptionalComplexProperty(try EntityWithOptionalComplexProperty(dictionary: dictionary, context: context))
case EntityWithOptionalProperty.type:
self = .entityWithOptionalProperty(try EntityWithOptionalProperty(dictionary: dictionary, context: context))
case EntityWithOptionalStringEnumProperty.type:
self = .entityWithOptionalStringEnumProperty(try EntityWithOptionalStringEnumProperty(dictionary: dictionary, context: context))
case EntityWithPropertyWithDefaultValue.type:
self = .entityWithPropertyWithDefaultValue(try EntityWithPropertyWithDefaultValue(dictionary: dictionary, context: context))
case EntityWithRawArray.type:
self = .entityWithRawArray(try EntityWithRawArray(dictionary: dictionary, context: context))
case EntityWithRequiredProperty.type:
self = .entityWithRequiredProperty(try EntityWithRequiredProperty(dictionary: dictionary, context: context))
case EntityWithSimpleProperties.type:
self = .entityWithSimpleProperties(try EntityWithSimpleProperties(dictionary: dictionary, context: context))
case EntityWithStringArrayProperty.type:
self = .entityWithStringArrayProperty(try EntityWithStringArrayProperty(dictionary: dictionary, context: context))
case EntityWithStringEnumProperty.type:
self = .entityWithStringEnumProperty(try EntityWithStringEnumProperty(dictionary: dictionary, context: context))
case EntityWithStringEnumPropertyWithDefaultValue.type:
self = .entityWithStringEnumPropertyWithDefaultValue(try EntityWithStringEnumPropertyWithDefaultValue(dictionary: dictionary, context: context))
case EntityWithoutProperties.type:
self = .entityWithoutProperties(try EntityWithoutProperties(dictionary: dictionary, context: context))
default:
throw DeserializationError.invalidFieldRepresentation(field: "entity", representation: dictionary)
}
}
}
#if DEBUG
extension Entity: Equatable {
public static func ==(lhs: Entity, rhs: Entity) -> Bool {
@@ -11,6 +11,12 @@ public final class EntityWithArray: Sendable {
static let arrayValidator: AnyArrayValueValidator<Entity> =
makeArrayValidator(minItems: 1)
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
array: try dictionary.getArray("array", transform: { (dict: [String: Any]) in try? Entity(dictionary: dict, context: context) }, validator: Self.arrayValidator)
)
}
init(
array: [Entity]
) {
@@ -17,6 +17,12 @@ public final class EntityWithArrayOfEnums: Sendable {
static let itemsValidator: AnyArrayValueValidator<EntityWithArrayOfEnums.Item> =
makeArrayValidator(minItems: 1)
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
items: try dictionary.getArray("items", validator: Self.itemsValidator, context: context)
)
}
init(
items: [Item]
) {
@@ -15,6 +15,12 @@ public final class EntityWithArrayOfExpressions: Sendable {
static let itemsValidator: AnyArrayValueValidator<Expression<String>> =
makeArrayValidator(minItems: 1)
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
items: try dictionary.getExpressionArray("items", validator: Self.itemsValidator, context: context)
)
}
init(
items: [Expression<String>]
) {
@@ -13,6 +13,13 @@ public final class EntityWithArrayOfNestedItems: Sendable {
resolver.resolveString(property)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
entity: try dictionary.getField("entity", transform: { (dict: [String: Any]) in try Entity(dictionary: dict, context: context) }),
property: try dictionary.getExpressionField("property", context: context)
)
}
init(
entity: Entity,
property: Expression<String>
@@ -28,6 +35,12 @@ public final class EntityWithArrayOfNestedItems: Sendable {
static let itemsValidator: AnyArrayValueValidator<EntityWithArrayOfNestedItems.Item> =
makeArrayValidator(minItems: 1)
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
items: try dictionary.getArray("items", transform: { (dict: [String: Any]) in try? EntityWithArrayOfNestedItems.Item(dictionary: dict, context: context) }, validator: Self.itemsValidator)
)
}
init(
items: [Item]
) {
@@ -15,6 +15,12 @@ public final class EntityWithArrayWithTransform: Sendable {
static let arrayValidator: AnyArrayValueValidator<Expression<Color>> =
makeArrayValidator(minItems: 1)
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
array: try dictionary.getExpressionArray("array", transform: Color.color(withHexString:), validator: Self.arrayValidator, context: context)
)
}
init(
array: [Expression<Color>]
) {
@@ -12,6 +12,12 @@ public final class EntityWithComplexProperty: Sendable {
resolver.resolveUrl(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
value: try dictionary.getExpressionField("value", transform: URL.makeFromNonEncodedString, context: context)
)
}
init(
value: Expression<URL>
) {
@@ -22,6 +28,12 @@ public final class EntityWithComplexProperty: Sendable {
public static let type: String = "entity_with_complex_property"
public let property: Property
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
property: try dictionary.getField("property", transform: { (dict: [String: Any]) in try EntityWithComplexProperty.Property(dictionary: dict, context: context) })
)
}
init(
property: Property
) {
@@ -12,6 +12,12 @@ public final class EntityWithComplexPropertyWithDefaultValue: Sendable {
resolver.resolveString(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
value: try dictionary.getExpressionField("value", context: context)
)
}
init(
value: Expression<String>
) {
@@ -22,6 +28,12 @@ public final class EntityWithComplexPropertyWithDefaultValue: Sendable {
public static let type: String = "entity_with_complex_property_with_default_value"
public let property: Property // default value: EntityWithComplexPropertyWithDefaultValue.Property(value: .value("Default text"))
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
property: try dictionary.getOptionalField("property", transform: { (dict: [String: Any]) in try EntityWithComplexPropertyWithDefaultValue.Property(dictionary: dict, context: context) })
)
}
init(
property: Property? = nil
) {
@@ -8,6 +8,12 @@ public final class EntityWithEntityProperty: Sendable {
public static let type: String = "entity_with_entity_property"
public let entity: Entity // default value: .entityWithStringEnumProperty(EntityWithStringEnumProperty(property: .value(.second)))
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
entity: try dictionary.getOptionalField("entity", transform: { (dict: [String: Any]) in try Entity(dictionary: dict, context: context) })
)
}
init(
entity: Entity? = nil
) {
@@ -8,6 +8,12 @@ public final class EntityWithJsonProperty: @unchecked Sendable {
public static let type: String = "entity_with_json_property"
public let jsonProperty: [String: Any] // default value: { "key": "value", "items": [ "value" ] }
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
jsonProperty: try dictionary.getOptionalField("json_property", context: context)
)
}
init(
jsonProperty: [String: Any]? = nil
) {
@@ -12,6 +12,12 @@ public final class EntityWithOptionalComplexProperty: Sendable {
resolver.resolveUrl(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
value: try dictionary.getExpressionField("value", transform: URL.makeFromNonEncodedString, context: context)
)
}
init(
value: Expression<URL>
) {
@@ -22,6 +28,12 @@ public final class EntityWithOptionalComplexProperty: Sendable {
public static let type: String = "entity_with_optional_complex_property"
public let property: Property?
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
property: try dictionary.getOptionalField("property", transform: { (dict: [String: Any]) in try EntityWithOptionalComplexProperty.Property(dictionary: dict, context: context) })
)
}
init(
property: Property? = nil
) {
@@ -12,6 +12,12 @@ public final class EntityWithOptionalProperty: Sendable {
resolver.resolveString(property)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
property: try dictionary.getOptionalExpressionField("property", context: context)
)
}
init(
property: Expression<String>? = nil
) {
@@ -18,6 +18,12 @@ public final class EntityWithOptionalStringEnumProperty: Sendable {
resolver.resolveEnum(property)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
property: try dictionary.getOptionalExpressionField("property", context: context)
)
}
init(
property: Expression<Property>? = nil
) {
@@ -28,6 +28,14 @@ public final class EntityWithPropertyWithDefaultValue: Sendable {
static let urlValidator: AnyValueValidator<URL> =
makeURLValidator(schemes: ["https"])
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
int: try dictionary.getOptionalExpressionField("int", validator: Self.intValidator, context: context),
nonOptional: try dictionary.getExpressionField("non_optional", context: context),
url: try dictionary.getOptionalExpressionField("url", transform: URL.makeFromNonEncodedString, validator: Self.urlValidator, context: context)
)
}
init(
int: Expression<Int>? = nil,
nonOptional: Expression<String>,
@@ -58,6 +66,14 @@ public final class EntityWithPropertyWithDefaultValue: Sendable {
static let urlValidator: AnyValueValidator<URL> =
makeURLValidator(schemes: ["https"])
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
int: try dictionary.getOptionalExpressionField("int", validator: Self.intValidator, context: context),
nested: try dictionary.getOptionalField("nested", transform: { (dict: [String: Any]) in try EntityWithPropertyWithDefaultValue.Nested(dictionary: dict, context: context) }),
url: try dictionary.getOptionalExpressionField("url", transform: URL.makeFromNonEncodedString, validator: Self.urlValidator, context: context)
)
}
init(
int: Expression<Int>? = nil,
nested: Nested? = nil,
@@ -12,6 +12,12 @@ public final class EntityWithRawArray: @unchecked Sendable {
resolver.resolveArray(array)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
array: try dictionary.getExpressionField("array", context: context)
)
}
init(
array: Expression<[Any]>
) {
@@ -15,6 +15,12 @@ public final class EntityWithRequiredProperty: Sendable {
static let propertyValidator: AnyValueValidator<String> =
makeStringValidator(minLength: 1)
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
property: try dictionary.getExpressionField("property", validator: Self.propertyValidator, context: context)
)
}
init(
property: Expression<String>
) {
@@ -51,6 +51,20 @@ public final class EntityWithSimpleProperties: EntityProtocol, Sendable {
static let positiveIntegerValidator: AnyValueValidator<Int> =
makeValueValidator(valueValidator: { $0 > 0 })
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
boolean: try dictionary.getOptionalExpressionField("boolean", context: context),
booleanInt: try dictionary.getOptionalExpressionField("boolean_int", context: context),
color: try dictionary.getOptionalExpressionField("color", transform: Color.color(withHexString:), context: context),
double: try dictionary.getOptionalExpressionField("double", context: context),
id: try dictionary.getOptionalField("id", context: context),
integer: try dictionary.getOptionalExpressionField("integer", context: context),
positiveInteger: try dictionary.getOptionalExpressionField("positive_integer", validator: Self.positiveIntegerValidator, context: context),
string: try dictionary.getOptionalExpressionField("string", context: context),
url: try dictionary.getOptionalExpressionField("url", transform: URL.makeFromNonEncodedString, context: context)
)
}
init(
boolean: Expression<Bool>? = nil,
booleanInt: Expression<Bool>? = nil,
@@ -15,6 +15,12 @@ public final class EntityWithStringArrayProperty: Sendable {
static let arrayValidator: AnyArrayValueValidator<Expression<String>> =
makeArrayValidator(minItems: 1)
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
array: try dictionary.getExpressionArray("array", validator: Self.arrayValidator, context: context)
)
}
init(
array: [Expression<String>]
) {
@@ -18,6 +18,12 @@ public final class EntityWithStringEnumProperty: Sendable {
resolver.resolveEnum(property)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
property: try dictionary.getExpressionField("property", context: context)
)
}
init(
property: Expression<Property>
) {
@@ -19,6 +19,12 @@ public final class EntityWithStringEnumPropertyWithDefaultValue: Sendable {
resolver.resolveEnum(value) ?? Value.second
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
value: try dictionary.getOptionalExpressionField("value", context: context)
)
}
init(
value: Expression<Value>? = nil
) {
@@ -7,6 +7,8 @@ import Serialization
public final class EntityWithoutProperties: Sendable {
public static let type: String = "entity_without_properties"
public init(dictionary: [String: Any], context: ParsingContext) throws {}
init() {}
}
@@ -19,6 +19,20 @@ public enum EnumWithDefaultType: Sendable {
}
}
extension EnumWithDefaultType {
public init(dictionary: [String: Any], context: ParsingContext) throws {
let blockType = try dictionary.getField("type") as String
switch blockType {
case WithDefault.type:
self = .withDefault(try WithDefault(dictionary: dictionary, context: context))
case WithoutDefault.type:
self = .withoutDefault(try WithoutDefault(dictionary: dictionary, context: context))
default:
throw DeserializationError.invalidFieldRepresentation(field: "enum_with_default_type", representation: dictionary)
}
}
}
#if DEBUG
extension EnumWithDefaultType: Equatable {
public static func ==(lhs: EnumWithDefaultType, rhs: EnumWithDefaultType) -> Bool {
@@ -7,6 +7,8 @@ import Serialization
public final class WithDefault: Sendable {
public static let type: String = "default"
public init(dictionary: [String: Any], context: ParsingContext) throws {}
init() {}
}
@@ -7,6 +7,8 @@ import Serialization
public final class WithoutDefault: Sendable {
public static let type: String = "non_default"
public init(dictionary: [String: Any], context: ParsingContext) throws {}
init() {}
}
+18 -2
View File
@@ -5,7 +5,7 @@
/// performance issues, crashes, or other problems. By default, only features that have testing are
/// included in the framework.
/// You can access the default `DivFlagsInfo` instance using the static property `default`.
public struct DivFlagsInfo: Encodable, Equatable {
public struct DivFlagsInfo: Encodable, Equatable, Sendable {
/// The default instance of `DivFlagsInfo`.
public static let `default` = DivFlagsInfo()
@@ -53,6 +53,14 @@ public struct DivFlagsInfo: Encodable, Equatable {
/// `font_weight_value` is specified.
public let variationFontWeightOverrideEnabled: Bool
/// Enables untyped template resolving pipeline for card parsing.
///
/// `false` - typed template resolving is used (`*Template.swift` entities).
///
/// `true` - templates are resolved by untyped resolver and then parsed with generated
/// non-template deserializers.
public let useUntypedTemplateResolver: Bool
/// Creates an instance of `DivFlagsInfo`.
public init(
useUrlHandlerForVisibilityActions: Bool = false,
@@ -62,7 +70,8 @@ public struct DivFlagsInfo: Encodable, Equatable {
initializeTriggerOnSet: Bool = true,
defaultTextAutoEllipsize: Bool = true,
fontCacheEnabled: Bool = true,
variationFontWeightOverrideEnabled: Bool = true
variationFontWeightOverrideEnabled: Bool = true,
useUntypedTemplateResolver: Bool = false
) {
self.useUrlHandlerForVisibilityActions = useUrlHandlerForVisibilityActions
self.imageBlurPreferMetal = imageBlurPreferMetal
@@ -72,6 +81,7 @@ public struct DivFlagsInfo: Encodable, Equatable {
self.defaultTextAutoEllipsize = defaultTextAutoEllipsize
self.fontCacheEnabled = fontCacheEnabled
self.variationFontWeightOverrideEnabled = variationFontWeightOverrideEnabled
self.useUntypedTemplateResolver = useUntypedTemplateResolver
}
}
@@ -85,6 +95,7 @@ extension DivFlagsInfo: Decodable {
case defaultTextAutoEllipsize
case fontCacheEnabled
case variationFontWeightOverrideEnabled
case useUntypedTemplateResolver
}
public init(from decoder: Decoder) throws {
@@ -129,5 +140,10 @@ extension DivFlagsInfo: Decodable {
Bool.self,
forKey: .variationFontWeightOverrideEnabled
) ?? Self.default.variationFontWeightOverrideEnabled
self.useUntypedTemplateResolver = try container.decodeIfPresent(
Bool.self,
forKey: .useUntypedTemplateResolver
) ?? Self.default.useUntypedTemplateResolver
}
}
+2 -1
View File
@@ -265,7 +265,8 @@ public final class DivKitComponents {
let rawDivData = try RawDivData(dictionary: jsonDict)
let result = DivData.resolve(
card: rawDivData.card,
templates: rawDivData.templates
templates: rawDivData.templates,
flagsInfo: flagsInfo
).asCardResult(cardId: cardId)
if let divData = result.value {
setCardData(divData: divData, cardId: cardId)
@@ -43,3 +43,245 @@ extension [String: Any] {
)
}
}
extension [String: Any] {
func getExpressionField<T: RawRepresentable>(
_ key: String,
validator: AnyValueValidator<T>? = nil
) throws -> Expression<T> where T.RawValue == String {
try getExpressionField(
key,
transform: T.init(rawValue:),
validator: validator
)
}
func getExpressionField<T: ValidSerializationValue>(
_ key: String,
validator: AnyValueValidator<T>? = nil
) throws -> Expression<T> {
try getExpressionField(
key,
transform: { $0 as T },
validator: validator
)
}
func getExpressionField<T, U>(
_ key: String,
transform: (U) -> T?,
validator: AnyValueValidator<T>? = nil
) throws -> Expression<T> {
try getField(
key,
transform: { expressionTransform($0, transform: transform, validator: validator) }
)
}
func getOptionalExpressionField<T: RawRepresentable>(
_ key: String,
validator: AnyValueValidator<T>? = nil
) throws -> Expression<T>? where T.RawValue == String {
try getOptionalExpressionField(
key,
transform: T.init(rawValue:),
validator: validator
)
}
func getOptionalExpressionField<T: ValidSerializationValue>(
_ key: String,
validator: AnyValueValidator<T>? = nil
) throws -> Expression<T>? {
try getOptionalExpressionField(
key,
transform: { $0 as T },
validator: validator
)
}
func getOptionalExpressionField<T, U>(
_ key: String,
transform: (U) -> T?,
validator: AnyValueValidator<T>? = nil
) throws -> Expression<T>? {
try getOptionalField(
key,
transform: { expressionTransform($0, transform: transform, validator: validator) }
)
}
func getExpressionArray<T: ValidSerializationValue>(
_ key: String,
validator: AnyArrayValueValidator<Expression<T>>? = nil
) throws -> [Expression<T>] {
try getExpressionArray(
key,
transform: { $0 as T },
validator: validator
)
}
func getExpressionArray<T, U: ValidSerializationValue>(
_ key: String,
transform: (U) -> T?,
validator: AnyArrayValueValidator<Expression<T>>? = nil
) throws -> [Expression<T>] {
try getArray(
key,
transform: { (value: U) in
expressionTransform(value, transform: transform)
},
validator: validator
)
}
func getOptionalExpressionArray<T: ValidSerializationValue>(
_ key: String,
validator: AnyArrayValueValidator<Expression<T>>? = nil
) throws -> [Expression<T>]? {
try getOptionalExpressionArray(
key,
transform: { $0 as T },
validator: validator
)
}
func getOptionalExpressionArray<T, U: ValidSerializationValue>(
_ key: String,
transform: (U) -> T?,
validator: AnyArrayValueValidator<Expression<T>>? = nil
) throws -> [Expression<T>]? {
try getOptionalArray(
key,
transform: { (value: U) in
expressionTransform(value, transform: transform)
},
validator: validator
)
}
}
extension [String: Any] {
func getExpressionField<T: RawRepresentable>(
_ key: String,
validator: AnyValueValidator<T>? = nil,
context: ParsingContext
) throws -> Expression<T> where T.RawValue == String {
try getExpressionField(
key,
transform: T.init(rawValue:),
validator: validator,
context: context
)
}
func getExpressionField<T: ValidSerializationValue>(
_ key: String,
validator: AnyValueValidator<T>? = nil,
context: ParsingContext
) throws -> Expression<T> {
try getExpressionField(key, transform: { $0 as T }, validator: validator, context: context)
}
func getExpressionField<T, U>(
_ key: String,
transform: (U) -> T?,
validator: AnyValueValidator<T>? = nil,
context: ParsingContext
) throws -> Expression<T> {
try getField(
key,
transform: { expressionTransform($0, transform: transform, validator: validator) },
context: context
)
}
func getOptionalExpressionField<T: RawRepresentable>(
_ key: String,
validator: AnyValueValidator<T>? = nil,
context: ParsingContext
) throws -> Expression<T>? where T.RawValue == String {
try getOptionalExpressionField(
key,
transform: T.init(rawValue:),
validator: validator,
context: context
)
}
func getOptionalExpressionField<T: ValidSerializationValue>(
_ key: String,
validator: AnyValueValidator<T>? = nil,
context: ParsingContext
) throws -> Expression<T>? {
try getOptionalExpressionField(
key,
transform: { $0 as T },
validator: validator,
context: context
)
}
func getOptionalExpressionField<T, U>(
_ key: String,
transform: (U) -> T?,
validator: AnyValueValidator<T>? = nil,
context: ParsingContext
) throws -> Expression<T>? {
try getOptionalField(
key,
transform: { expressionTransform($0, transform: transform, validator: validator) },
context: context
)
}
func getExpressionArray<T: ValidSerializationValue>(
_ key: String,
validator: AnyArrayValueValidator<Expression<T>>? = nil,
context: ParsingContext
) throws -> [Expression<T>] {
try getExpressionArray(key, transform: { $0 as T }, validator: validator, context: context)
}
func getExpressionArray<T, U: ValidSerializationValue>(
_ key: String,
transform: (U) -> T?,
validator: AnyArrayValueValidator<Expression<T>>? = nil,
context: ParsingContext
) throws -> [Expression<T>] {
try getArray(
key,
transform: { (value: U) in expressionTransform(value, transform: transform) },
validator: validator,
context: context
)
}
func getOptionalExpressionArray<T: ValidSerializationValue>(
_ key: String,
validator: AnyArrayValueValidator<Expression<T>>? = nil,
context: ParsingContext
) throws -> [Expression<T>]? {
try getOptionalExpressionArray(
key,
transform: { $0 as T },
validator: validator,
context: context
)
}
func getOptionalExpressionArray<T, U: ValidSerializationValue>(
_ key: String,
transform: (U) -> T?,
validator: AnyArrayValueValidator<Expression<T>>? = nil,
context: ParsingContext
) throws -> [Expression<T>]? {
try getOptionalArray(
key,
transform: { (value: U) in expressionTransform(value, transform: transform) },
validator: validator,
context: context
)
}
}
@@ -69,8 +69,11 @@ extension Field {
case let .link(link):
return context.templateData.getArray(
link,
transform: { (value: U) in
expressionTransform(value, transform: transform)
transform: { (value: U) -> DeserializationResult<Expression<E>> in
guard let transformed = expressionTransform(value, transform: transform) else {
return .noValue
}
return .success(transformed)
},
validator: validator
)
@@ -95,8 +95,59 @@ extension DivData: DivBlockModeling {
extension DivData {
public static func resolve(
card cardDict: [String: Any],
templates templatesDict: [String: Any]?
templates templatesDict: [String: Any]?,
flagsInfo: DivFlagsInfo = .default
) -> DeserializationResult<DivData> {
if flagsInfo.useUntypedTemplateResolver {
var resolver = UntypedDivTemplateResolver(templates: templatesDict)
let resolvedCardResult = resolver.resolve(card: cardDict)
let parsingContext = ParsingContext()
guard let resolvedCard = resolvedCardResult.value else {
return .failure(resolvedCardResult.errorsOrWarnings ?? NonEmptyArray(.generic))
}
let divDataResult: DeserializationResult<DivData>
do {
divDataResult = try .success(DivData(dictionary: resolvedCard, context: parsingContext))
} catch let error as DeserializationError {
divDataResult = .failure(NonEmptyArray(error))
} catch {
divDataResult =
.failure(NonEmptyArray(.unexpectedError(message: String(describing: error))))
}
let contextWarnings: NonEmptyArray<DeserializationError>? = NonEmptyArray(parsingContext
.warnings
)
let contextErrors: NonEmptyArray<DeserializationError>? = NonEmptyArray(parsingContext.errors)
let resolverWarnings = resolvedCardResult.warnings
let allIssues: NonEmptyArray<DeserializationError>? = NonEmptyArray(
mergeErrors(contextWarnings, contextErrors, resolverWarnings)
)
switch divDataResult {
case let .success(value):
if let warnings = allIssues {
return .partialSuccess(value, warnings: warnings)
}
return .success(value)
case let .partialSuccess(value, warnings):
if let mergedWarnings = NonEmptyArray(mergeErrors(warnings, allIssues)) {
return .partialSuccess(value, warnings: mergedWarnings)
}
return .success(value)
case let .failure(errors):
return .failure(NonEmptyArray(mergeErrors(errors, allIssues))!)
case .noValue:
if let warnings = allIssues {
return .failure(warnings)
}
return .failure(NonEmptyArray(.generic))
}
}
let divTemplates = templatesDict.map(DivTemplates.init) ?? .empty
return divTemplates.parseValue(type: DivDataTemplate.self, from: cardDict)
}
+2 -2
View File
@@ -9,7 +9,7 @@ public struct RawDivData: Deserializable, @unchecked Sendable {
templates = try dictionary.getOptionalField("templates") ?? [:]
}
public func resolve() -> DeserializationResult<DivData> {
DivData.resolve(card: card, templates: templates)
public func resolve(flagsInfo: DivFlagsInfo = .default) -> DeserializationResult<DivData> {
DivData.resolve(card: card, templates: templates, flagsInfo: flagsInfo)
}
}
@@ -0,0 +1,219 @@
import Foundation
import Serialization
import VGSL
struct UntypedDivTemplateResolver {
private enum Origin {
case instance
case template
}
private struct OriginValue {
let value: Any
let origin: Origin
}
private let templates: [TemplateName: Any]
private let templateToType: [TemplateName: String]
private var resolvedTemplateCache: [TemplateName: [String: Any]] = [:]
private var currentlyResolvingTemplates = Set<TemplateName>()
init(templates: [String: Any]?) {
let templates = templates ?? [:]
self.templates = templates
templateToType = calculateTemplateToType(in: templates)
}
mutating func resolve(card: [String: Any]) -> DeserializationResult<[String: Any]> {
resolveDictionary(
card,
linkSource: card,
origin: .instance
)
}
private mutating func resolveDictionary(
_ dictionary: [String: Any],
linkSource: [String: Any],
origin: Origin
) -> DeserializationResult<[String: Any]> {
var values: [String: OriginValue] = [:]
if let templateName = dictionary["type"] as? String, templates[templateName] != nil {
let resolvedTemplate = resolveTemplate(named: templateName)
guard let templateValue = resolvedTemplate.value else {
return .failure(normalizedErrors(from: resolvedTemplate.errorsOrWarnings))
}
values = templateValue.mapValues { OriginValue(value: $0, origin: .template) }
for (key, value) in dictionary {
values[key] = OriginValue(value: value, origin: .instance)
}
values["type"] = OriginValue(
value: templateToType[templateName] ?? templateName,
origin: .template
)
} else {
values = dictionary.mapValues { OriginValue(value: $0, origin: .instance) }
}
let currentLinkSource = origin == .instance ? dictionary : linkSource
substituteLinks(values: &values, linkSource: currentLinkSource)
var resolved: [String: Any] = [:]
var errors: [DeserializationError] = []
for (key, value) in values {
guard !key.hasPrefix("$") else { continue }
let childResult = resolveAny(
value.value,
linkSource: currentLinkSource,
origin: value.origin
)
if let resolvedChild = childResult.value {
resolved[key] = resolvedChild
}
if let childErrors = childResult.errorsOrWarnings {
errors.append(contentsOf: childErrors.map { .nestedObjectError(field: key, error: $0) })
}
}
if let warnings = NonEmptyArray(errors) {
return .partialSuccess(resolved, warnings: warnings)
}
return .success(resolved)
}
private mutating func resolveArray(
_ array: [Any],
linkSource: [String: Any],
origin: Origin
) -> DeserializationResult<[Any]> {
var result: [Any] = []
var errors: [DeserializationError] = []
result.reserveCapacity(array.count)
for index in array.indices {
let child = array[index]
let childResult: DeserializationResult<Any>
if let dictionary = child as? [String: Any] {
let childLinkSource = origin == .instance ? dictionary : linkSource
childResult = resolveDictionary(
dictionary,
linkSource: childLinkSource,
origin: origin == .instance ? .instance : .template
).map { $0 as Any }
} else if let nestedArray = child as? [Any] {
childResult = resolveArray(
nestedArray,
linkSource: linkSource,
origin: origin
).map { $0 as Any }
} else {
childResult = .success(child)
}
if let value = childResult.value {
result.append(value)
}
if let childErrors = childResult.errorsOrWarnings {
errors
.append(contentsOf: childErrors.map { .nestedObjectError(field: "\(index)", error: $0) })
}
}
if let warnings = NonEmptyArray(errors) {
return .partialSuccess(result, warnings: warnings)
}
return .success(result)
}
private mutating func resolveAny(
_ value: Any,
linkSource: [String: Any],
origin: Origin
) -> DeserializationResult<Any> {
if let dictionary = value as? [String: Any] {
let childLinkSource = origin == .instance ? dictionary : linkSource
return resolveDictionary(
dictionary,
linkSource: childLinkSource,
origin: origin == .instance ? .instance : .template
).map { $0 as Any }
}
if let array = value as? [Any] {
return resolveArray(array, linkSource: linkSource, origin: origin).map { $0 as Any }
}
return .success(value)
}
private mutating func resolveTemplate(named templateName: TemplateName)
-> DeserializationResult<[String: Any]> {
if let cached = resolvedTemplateCache[templateName] {
return .success(cached)
}
guard !currentlyResolvingTemplates.contains(templateName) else {
return .failure(NonEmptyArray(.unknownType(type: templateName)))
}
guard let templateDict = templates[templateName] as? [String: Any] else {
return .failure(NonEmptyArray(.unknownType(type: templateName)))
}
currentlyResolvingTemplates.insert(templateName)
defer { currentlyResolvingTemplates.remove(templateName) }
var result: [String: Any] = [:]
if let parentName = templateDict["type"] as? String, templates[parentName] != nil {
let parentResult = resolveTemplate(named: parentName)
guard let parentTemplate = parentResult.value else {
return .failure(normalizedErrors(from: parentResult.errorsOrWarnings))
}
result = parentTemplate
}
for (key, value) in templateDict {
result[key] = value
}
result["type"] = templateToType[templateName] ??
(templateDict["type"] as? String ?? templateName)
resolvedTemplateCache[templateName] = result
return .success(result)
}
private func substituteLinks(values: inout [String: OriginValue], linkSource: [String: Any]) {
let linkValues = values.filter { $0.key.hasPrefix("$") }
for (linkKey, linkValue) in linkValues {
let key = String(linkKey.dropFirst())
guard values[key] == nil else { continue }
guard let linkName = linkValue.value as? String else { continue }
guard let value = linkSource[linkName] else { continue }
values[key] = OriginValue(value: value, origin: .instance)
}
}
}
private func normalizedErrors(
from errors: NonEmptyArray<DeserializationError>?
) -> NonEmptyArray<DeserializationError> {
errors ?? NonEmptyArray(.generic)
}
extension DeserializationResult {
fileprivate func map<U>(_ transform: (T) -> U) -> DeserializationResult<U> {
switch self {
case let .success(value):
.success(transform(value))
case let .partialSuccess(value, warnings):
.partialSuccess(transform(value), warnings: warnings)
case let .failure(errors):
.failure(errors)
case .noValue:
.noValue
}
}
}
+13 -12
View File
@@ -305,13 +305,13 @@ final class DivBlockProvider {
cardId: DivCardID
) throws -> DeserializationResult<DivData> {
let rawDivData = try RawDivData(dictionary: jsonDict)
let templates = try measurements.templateParsingTime.updateMeasure {
DivTemplates(dictionary: rawDivData.templates)
}
return try measurements.divDataParsingTime.updateMeasure {
templates
.parseValue(type: DivDataTemplate.self, from: rawDivData.card)
.asCardResult(cardId: cardId)
DivData.resolve(
card: rawDivData.card,
templates: rawDivData.templates,
flagsInfo: divKitComponents.flagsInfo
)
.asCardResult(cardId: cardId)
}
}
@@ -321,16 +321,17 @@ final class DivBlockProvider {
) async throws -> DeserializationResult<DivData> {
let rawDivData = try RawDivData(dictionary: jsonDict)
let measurements = measurements
let templates = try measurements.templateParsingTime.updateMeasure {
DivTemplates(dictionary: rawDivData.templates)
}
let flagsInfo = divKitComponents.flagsInfo
let result = try await withCheckedThrowingContinuation { continuation in
DispatchQueue.global().async { [measurements] in
do {
let result = try measurements.divDataParsingTime.updateMeasure {
templates
.parseValue(type: DivDataTemplate.self, from: rawDivData.card)
.asCardResult(cardId: cardId)
DivData.resolve(
card: rawDivData.card,
templates: rawDivData.templates,
flagsInfo: flagsInfo
)
.asCardResult(cardId: cardId)
}
continuation.resume(returning: result)
} catch {
@@ -12,6 +12,12 @@ public final class ArrayValue: @unchecked Sendable {
resolver.resolveArray(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
value: try dictionary.getExpressionField("value", context: context)
)
}
init(
value: Expression<[Any]>
) {
@@ -13,6 +13,13 @@ public final class ArrayVariable: @unchecked Sendable {
resolver.resolveArray(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
name: try dictionary.getField("name", context: context),
value: try dictionary.getExpressionField("value", context: context)
)
}
init(
name: String,
value: Expression<[Any]>
@@ -12,6 +12,12 @@ public final class BooleanValue: Sendable {
resolver.resolveNumeric(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
value: try dictionary.getExpressionField("value", context: context)
)
}
init(
value: Expression<Bool>
) {
@@ -13,6 +13,13 @@ public final class BooleanVariable: Sendable {
resolver.resolveNumeric(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
name: try dictionary.getField("name", context: context),
value: try dictionary.getExpressionField("value", context: context)
)
}
init(
name: String,
value: Expression<Bool>
@@ -12,6 +12,12 @@ public final class ColorValue: Sendable {
resolver.resolveColor(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
value: try dictionary.getExpressionField("value", transform: Color.color(withHexString:), context: context)
)
}
init(
value: Expression<Color>
) {
@@ -13,6 +13,13 @@ public final class ColorVariable: Sendable {
resolver.resolveColor(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
name: try dictionary.getField("name", context: context),
value: try dictionary.getExpressionField("value", transform: Color.color(withHexString:), context: context)
)
}
init(
name: String,
value: Expression<Color>
@@ -12,6 +12,12 @@ public final class ContentText: Sendable {
resolver.resolveString(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
value: try dictionary.getExpressionField("value", context: context)
)
}
init(
value: Expression<String>
) {
@@ -12,6 +12,12 @@ public final class ContentUrl: Sendable {
resolver.resolveUrl(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
value: try dictionary.getExpressionField("value", transform: URL.makeFromNonEncodedString, context: context)
)
}
init(
value: Expression<URL>
) {
@@ -12,6 +12,12 @@ public final class DictValue: @unchecked Sendable {
resolver.resolveDict(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
value: try dictionary.getExpressionField("value", context: context)
)
}
init(
value: Expression<[String: Any]>
) {
@@ -13,6 +13,13 @@ public final class DictVariable: @unchecked Sendable {
resolver.resolveDict(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
name: try dictionary.getField("name", context: context),
value: try dictionary.getExpressionField("value", context: context)
)
}
init(
name: String,
value: Expression<[String: Any]>
@@ -103,6 +103,50 @@ public enum Div: Sendable {
}
}
extension Div {
public init(dictionary: [String: Any], context: ParsingContext) throws {
let blockType = try dictionary.getField("type") as String
switch blockType {
case DivImage.type:
self = .divImage(try DivImage(dictionary: dictionary, context: context))
case DivGifImage.type:
self = .divGifImage(try DivGifImage(dictionary: dictionary, context: context))
case DivText.type:
self = .divText(try DivText(dictionary: dictionary, context: context))
case DivSeparator.type:
self = .divSeparator(try DivSeparator(dictionary: dictionary, context: context))
case DivContainer.type:
self = .divContainer(try DivContainer(dictionary: dictionary, context: context))
case DivGrid.type:
self = .divGrid(try DivGrid(dictionary: dictionary, context: context))
case DivGallery.type:
self = .divGallery(try DivGallery(dictionary: dictionary, context: context))
case DivPager.type:
self = .divPager(try DivPager(dictionary: dictionary, context: context))
case DivTabs.type:
self = .divTabs(try DivTabs(dictionary: dictionary, context: context))
case DivState.type:
self = .divState(try DivState(dictionary: dictionary, context: context))
case DivCustom.type:
self = .divCustom(try DivCustom(dictionary: dictionary, context: context))
case DivIndicator.type:
self = .divIndicator(try DivIndicator(dictionary: dictionary, context: context))
case DivSlider.type:
self = .divSlider(try DivSlider(dictionary: dictionary, context: context))
case DivSwitch.type:
self = .divSwitch(try DivSwitch(dictionary: dictionary, context: context))
case DivInput.type:
self = .divInput(try DivInput(dictionary: dictionary, context: context))
case DivSelect.type:
self = .divSelect(try DivSelect(dictionary: dictionary, context: context))
case DivVideo.type:
self = .divVideo(try DivVideo(dictionary: dictionary, context: context))
default:
throw DeserializationError.invalidFieldRepresentation(field: "div", representation: dictionary)
}
}
}
#if DEBUG
extension Div: Equatable {
public static func ==(lhs: Div, rhs: Div) -> Bool {
@@ -38,6 +38,15 @@ public final class DivAbsoluteEdgeInsets: Sendable {
static let topValidator: AnyValueValidator<Int> =
makeValueValidator(valueValidator: { $0 >= 0 })
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
bottom: try dictionary.getOptionalExpressionField("bottom", validator: Self.bottomValidator, context: context),
left: try dictionary.getOptionalExpressionField("left", validator: Self.leftValidator, context: context),
right: try dictionary.getOptionalExpressionField("right", validator: Self.rightValidator, context: context),
top: try dictionary.getOptionalExpressionField("top", validator: Self.topValidator, context: context)
)
}
init(
bottom: Expression<Int>? = nil,
left: Expression<Int>? = nil,
@@ -60,6 +60,18 @@ public final class DivAccessibility: Sendable {
resolver.resolveString(stateDescription)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
description: try dictionary.getOptionalExpressionField("description", context: context),
hint: try dictionary.getOptionalExpressionField("hint", context: context),
isChecked: try dictionary.getOptionalExpressionField("is_checked", context: context),
mode: try dictionary.getOptionalExpressionField("mode", context: context),
muteAfterAction: try dictionary.getOptionalExpressionField("mute_after_action", context: context),
stateDescription: try dictionary.getOptionalExpressionField("state_description", context: context),
type: try dictionary.getOptionalField("type", context: context)
)
}
init(
description: Expression<String>? = nil,
hint: Expression<String>? = nil,
@@ -14,6 +14,14 @@ public final class DivAction: @unchecked Sendable {
resolver.resolveString(text)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
action: try dictionary.getOptionalField("action", transform: { (dict: [String: Any]) in try DivAction(dictionary: dict, context: context) }),
actions: try dictionary.getOptionalArray("actions", transform: { (dict: [String: Any]) in try? DivAction(dictionary: dict, context: context) }),
text: try dictionary.getExpressionField("text", context: context)
)
}
init(
action: DivAction? = nil,
actions: [DivAction]? = nil,
@@ -56,6 +64,21 @@ public final class DivAction: @unchecked Sendable {
resolver.resolveUrl(url)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
downloadCallbacks: try dictionary.getOptionalField("download_callbacks", transform: { (dict: [String: Any]) in try DivDownloadCallbacks(dictionary: dict, context: context) }),
isEnabled: try dictionary.getOptionalExpressionField("is_enabled", context: context),
logId: try dictionary.getExpressionField("log_id", context: context),
logUrl: try dictionary.getOptionalExpressionField("log_url", transform: URL.makeFromNonEncodedString, context: context),
menuItems: try dictionary.getOptionalArray("menu_items", transform: { (dict: [String: Any]) in try? DivAction.MenuItem(dictionary: dict, context: context) }),
payload: try dictionary.getOptionalField("payload", context: context),
referer: try dictionary.getOptionalExpressionField("referer", transform: URL.makeFromNonEncodedString, context: context),
scopeId: try dictionary.getOptionalField("scope_id", context: context),
typed: try dictionary.getOptionalField("typed", transform: { (dict: [String: Any]) in try DivActionTyped(dictionary: dict, context: context) }),
url: try dictionary.getOptionalExpressionField("url", transform: URL.makeFromNonEncodedString, context: context)
)
}
init(
downloadCallbacks: DivDownloadCallbacks? = nil,
isEnabled: Expression<Bool>? = nil,
@@ -37,6 +37,19 @@ public final class DivActionAnimatorStart: Sendable {
static let startDelayValidator: AnyValueValidator<Int> =
makeValueValidator(valueValidator: { $0 >= 0 })
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
animatorId: try dictionary.getField("animator_id", context: context),
direction: try dictionary.getOptionalExpressionField("direction", context: context),
duration: try dictionary.getOptionalExpressionField("duration", validator: Self.durationValidator, context: context),
endValue: try dictionary.getOptionalField("end_value", transform: { (dict: [String: Any]) in try DivTypedValue(dictionary: dict, context: context) }),
interpolator: try dictionary.getOptionalExpressionField("interpolator", context: context),
repeatCount: try dictionary.getOptionalField("repeat_count", transform: { (dict: [String: Any]) in try DivCount(dictionary: dict, context: context) }),
startDelay: try dictionary.getOptionalExpressionField("start_delay", validator: Self.startDelayValidator, context: context),
startValue: try dictionary.getOptionalField("start_value", transform: { (dict: [String: Any]) in try DivTypedValue(dictionary: dict, context: context) })
)
}
init(
animatorId: String,
direction: Expression<DivAnimationDirection>? = nil,
@@ -8,6 +8,12 @@ public final class DivActionAnimatorStop: Sendable {
public static let type: String = "animator_stop"
public let animatorId: String
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
animatorId: try dictionary.getField("animator_id", context: context)
)
}
init(
animatorId: String
) {
@@ -18,6 +18,14 @@ public final class DivActionArrayInsertValue: Sendable {
resolver.resolveString(variableName)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
index: try dictionary.getOptionalExpressionField("index", context: context),
value: try dictionary.getField("value", transform: { (dict: [String: Any]) in try DivTypedValue(dictionary: dict, context: context) }),
variableName: try dictionary.getExpressionField("variable_name", context: context)
)
}
init(
index: Expression<Int>? = nil,
value: DivTypedValue,
@@ -17,6 +17,13 @@ public final class DivActionArrayRemoveValue: Sendable {
resolver.resolveString(variableName)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
index: try dictionary.getExpressionField("index", context: context),
variableName: try dictionary.getExpressionField("variable_name", context: context)
)
}
init(
index: Expression<Int>,
variableName: Expression<String>
@@ -18,6 +18,14 @@ public final class DivActionArraySetValue: Sendable {
resolver.resolveString(variableName)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
index: try dictionary.getExpressionField("index", context: context),
value: try dictionary.getField("value", transform: { (dict: [String: Any]) in try DivTypedValue(dictionary: dict, context: context) }),
variableName: try dictionary.getExpressionField("variable_name", context: context)
)
}
init(
index: Expression<Int>,
value: DivTypedValue,
@@ -7,6 +7,8 @@ import VGSL
public final class DivActionClearFocus: Sendable {
public static let type: String = "clear_focus"
public init(dictionary: [String: Any], context: ParsingContext) throws {}
init() {}
}
@@ -8,6 +8,12 @@ public final class DivActionCopyToClipboard: Sendable {
public static let type: String = "copy_to_clipboard"
public let content: DivActionCopyToClipboardContent
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
content: try dictionary.getField("content", transform: { (dict: [String: Any]) in try DivActionCopyToClipboardContent(dictionary: dict, context: context) })
)
}
init(
content: DivActionCopyToClipboardContent
) {
@@ -19,6 +19,20 @@ public enum DivActionCopyToClipboardContent: Sendable {
}
}
extension DivActionCopyToClipboardContent {
public init(dictionary: [String: Any], context: ParsingContext) throws {
let blockType = try dictionary.getField("type") as String
switch blockType {
case ContentText.type:
self = .contentText(try ContentText(dictionary: dictionary, context: context))
case ContentUrl.type:
self = .contentUrl(try ContentUrl(dictionary: dictionary, context: context))
default:
throw DeserializationError.invalidFieldRepresentation(field: "div-action-copy-to-clipboard-content", representation: dictionary)
}
}
}
#if DEBUG
extension DivActionCopyToClipboardContent: Equatable {
public static func ==(lhs: DivActionCopyToClipboardContent, rhs: DivActionCopyToClipboardContent) -> Bool {
@@ -7,6 +7,8 @@ import VGSL
public final class DivActionCustom: Sendable {
public static let type: String = "custom"
public init(dictionary: [String: Any], context: ParsingContext) throws {}
init() {}
}
@@ -18,6 +18,14 @@ public final class DivActionDictSetValue: Sendable {
resolver.resolveString(variableName)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
key: try dictionary.getExpressionField("key", context: context),
value: try dictionary.getOptionalField("value", transform: { (dict: [String: Any]) in try DivTypedValue(dictionary: dict, context: context) }),
variableName: try dictionary.getExpressionField("variable_name", context: context)
)
}
init(
key: Expression<String>,
value: DivTypedValue? = nil,
@@ -14,6 +14,14 @@ public final class DivActionDownload: Sendable {
resolver.resolveUrl(url)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
onFailActions: try dictionary.getOptionalArray("on_fail_actions", transform: { (dict: [String: Any]) in try? DivAction(dictionary: dict, context: context) }),
onSuccessActions: try dictionary.getOptionalArray("on_success_actions", transform: { (dict: [String: Any]) in try? DivAction(dictionary: dict, context: context) }),
url: try dictionary.getExpressionField("url", transform: URL.makeFromNonEncodedString, context: context)
)
}
init(
onFailActions: [DivAction]? = nil,
onSuccessActions: [DivAction]? = nil,
@@ -12,6 +12,12 @@ public final class DivActionFocusElement: Sendable {
resolver.resolveString(elementId)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
elementId: try dictionary.getExpressionField("element_id", context: context)
)
}
init(
elementId: Expression<String>
) {
@@ -12,6 +12,12 @@ public final class DivActionHideTooltip: Sendable {
resolver.resolveString(id)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
id: try dictionary.getExpressionField("id", context: context)
)
}
init(
id: Expression<String>
) {
@@ -38,6 +38,16 @@ public final class DivActionScrollBy: Sendable {
resolver.resolveEnum(overflow) ?? Overflow.clamp
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
animated: try dictionary.getOptionalExpressionField("animated", context: context),
id: try dictionary.getExpressionField("id", context: context),
itemCount: try dictionary.getOptionalExpressionField("item_count", context: context),
offset: try dictionary.getOptionalExpressionField("offset", context: context),
overflow: try dictionary.getOptionalExpressionField("overflow", context: context)
)
}
init(
animated: Expression<Bool>? = nil,
id: Expression<String>,
@@ -25,6 +25,24 @@ public enum DivActionScrollDestination: Sendable {
}
}
extension DivActionScrollDestination {
public init(dictionary: [String: Any], context: ParsingContext) throws {
let blockType = try dictionary.getField("type") as String
switch blockType {
case OffsetDestination.type:
self = .offsetDestination(try OffsetDestination(dictionary: dictionary, context: context))
case IndexDestination.type:
self = .indexDestination(try IndexDestination(dictionary: dictionary, context: context))
case StartDestination.type:
self = .startDestination(try StartDestination(dictionary: dictionary, context: context))
case EndDestination.type:
self = .endDestination(try EndDestination(dictionary: dictionary, context: context))
default:
throw DeserializationError.invalidFieldRepresentation(field: "div-action-scroll-destination", representation: dictionary)
}
}
}
#if DEBUG
extension DivActionScrollDestination: Equatable {
public static func ==(lhs: DivActionScrollDestination, rhs: DivActionScrollDestination) -> Bool {
@@ -18,6 +18,14 @@ public final class DivActionScrollTo: Sendable {
resolver.resolveString(id)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
animated: try dictionary.getOptionalExpressionField("animated", context: context),
destination: try dictionary.getField("destination", transform: { (dict: [String: Any]) in try DivActionScrollDestination(dictionary: dict, context: context) }),
id: try dictionary.getExpressionField("id", context: context)
)
}
init(
animated: Expression<Bool>? = nil,
destination: DivActionScrollDestination,
@@ -17,6 +17,13 @@ public final class DivActionSetState: Sendable {
resolver.resolveNumeric(temporary) ?? true
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
stateId: try dictionary.getExpressionField("state_id", context: context),
temporary: try dictionary.getOptionalExpressionField("temporary", context: context)
)
}
init(
stateId: Expression<String>,
temporary: Expression<Bool>? = nil
@@ -18,6 +18,14 @@ public final class DivActionSetStoredValue: Sendable {
resolver.resolveString(name)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
lifetime: try dictionary.getExpressionField("lifetime", context: context),
name: try dictionary.getExpressionField("name", context: context),
value: try dictionary.getField("value", transform: { (dict: [String: Any]) in try DivTypedValue(dictionary: dict, context: context) })
)
}
init(
lifetime: Expression<Int>,
name: Expression<String>,
@@ -13,6 +13,13 @@ public final class DivActionSetVariable: Sendable {
resolver.resolveString(variableName)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
value: try dictionary.getField("value", transform: { (dict: [String: Any]) in try DivTypedValue(dictionary: dict, context: context) }),
variableName: try dictionary.getExpressionField("variable_name", context: context)
)
}
init(
value: DivTypedValue,
variableName: Expression<String>
@@ -17,6 +17,13 @@ public final class DivActionShowTooltip: Sendable {
resolver.resolveNumeric(multiple)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
id: try dictionary.getExpressionField("id", context: context),
multiple: try dictionary.getOptionalExpressionField("multiple", context: context)
)
}
init(
id: Expression<String>,
multiple: Expression<Bool>? = nil
@@ -29,6 +29,13 @@ public final class DivActionSubmit: Sendable {
resolver.resolveString(value)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
name: try dictionary.getExpressionField("name", context: context),
value: try dictionary.getExpressionField("value", context: context)
)
}
init(
name: Expression<String>,
value: Expression<String>
@@ -50,6 +57,14 @@ public final class DivActionSubmit: Sendable {
resolver.resolveUrl(url)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
headers: try dictionary.getOptionalArray("headers", transform: { (dict: [String: Any]) in try? DivActionSubmit.Request.Header(dictionary: dict, context: context) }),
method: try dictionary.getOptionalExpressionField("method", context: context),
url: try dictionary.getExpressionField("url", transform: URL.makeFromNonEncodedString, context: context)
)
}
init(
headers: [Header]? = nil,
method: Expression<Method>? = nil,
@@ -71,6 +86,15 @@ public final class DivActionSubmit: Sendable {
resolver.resolveString(containerId)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
containerId: try dictionary.getExpressionField("container_id", context: context),
onFailActions: try dictionary.getOptionalArray("on_fail_actions", transform: { (dict: [String: Any]) in try? DivAction(dictionary: dict, context: context) }),
onSuccessActions: try dictionary.getOptionalArray("on_success_actions", transform: { (dict: [String: Any]) in try? DivAction(dictionary: dict, context: context) }),
request: try dictionary.getField("request", transform: { (dict: [String: Any]) in try DivActionSubmit.Request(dictionary: dict, context: context) })
)
}
init(
containerId: Expression<String>,
onFailActions: [DivAction]? = nil,
@@ -27,6 +27,13 @@ public final class DivActionTimer: Sendable {
resolver.resolveString(id)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
action: try dictionary.getExpressionField("action", context: context),
id: try dictionary.getExpressionField("id", context: context)
)
}
init(
action: Expression<Action>,
id: Expression<String>
@@ -79,6 +79,60 @@ public enum DivActionTyped: Sendable {
}
}
extension DivActionTyped {
public init(dictionary: [String: Any], context: ParsingContext) throws {
let blockType = try dictionary.getField("type") as String
switch blockType {
case DivActionAnimatorStart.type:
self = .divActionAnimatorStart(try DivActionAnimatorStart(dictionary: dictionary, context: context))
case DivActionAnimatorStop.type:
self = .divActionAnimatorStop(try DivActionAnimatorStop(dictionary: dictionary, context: context))
case DivActionArrayInsertValue.type:
self = .divActionArrayInsertValue(try DivActionArrayInsertValue(dictionary: dictionary, context: context))
case DivActionArrayRemoveValue.type:
self = .divActionArrayRemoveValue(try DivActionArrayRemoveValue(dictionary: dictionary, context: context))
case DivActionArraySetValue.type:
self = .divActionArraySetValue(try DivActionArraySetValue(dictionary: dictionary, context: context))
case DivActionClearFocus.type:
self = .divActionClearFocus(try DivActionClearFocus(dictionary: dictionary, context: context))
case DivActionCopyToClipboard.type:
self = .divActionCopyToClipboard(try DivActionCopyToClipboard(dictionary: dictionary, context: context))
case DivActionDictSetValue.type:
self = .divActionDictSetValue(try DivActionDictSetValue(dictionary: dictionary, context: context))
case DivActionDownload.type:
self = .divActionDownload(try DivActionDownload(dictionary: dictionary, context: context))
case DivActionFocusElement.type:
self = .divActionFocusElement(try DivActionFocusElement(dictionary: dictionary, context: context))
case DivActionHideTooltip.type:
self = .divActionHideTooltip(try DivActionHideTooltip(dictionary: dictionary, context: context))
case DivActionScrollBy.type:
self = .divActionScrollBy(try DivActionScrollBy(dictionary: dictionary, context: context))
case DivActionScrollTo.type:
self = .divActionScrollTo(try DivActionScrollTo(dictionary: dictionary, context: context))
case DivActionSetState.type:
self = .divActionSetState(try DivActionSetState(dictionary: dictionary, context: context))
case DivActionSetStoredValue.type:
self = .divActionSetStoredValue(try DivActionSetStoredValue(dictionary: dictionary, context: context))
case DivActionSetVariable.type:
self = .divActionSetVariable(try DivActionSetVariable(dictionary: dictionary, context: context))
case DivActionShowTooltip.type:
self = .divActionShowTooltip(try DivActionShowTooltip(dictionary: dictionary, context: context))
case DivActionSubmit.type:
self = .divActionSubmit(try DivActionSubmit(dictionary: dictionary, context: context))
case DivActionTimer.type:
self = .divActionTimer(try DivActionTimer(dictionary: dictionary, context: context))
case DivActionUpdateStructure.type:
self = .divActionUpdateStructure(try DivActionUpdateStructure(dictionary: dictionary, context: context))
case DivActionVideo.type:
self = .divActionVideo(try DivActionVideo(dictionary: dictionary, context: context))
case DivActionCustom.type:
self = .divActionCustom(try DivActionCustom(dictionary: dictionary, context: context))
default:
throw DeserializationError.invalidFieldRepresentation(field: "div-action-typed", representation: dictionary)
}
}
}
#if DEBUG
extension DivActionTyped: Equatable {
public static func ==(lhs: DivActionTyped, rhs: DivActionTyped) -> Bool {
@@ -21,6 +21,14 @@ public final class DivActionUpdateStructure: Sendable {
static let pathValidator: AnyValueValidator<String> =
makeStringValidator(regex: "^(?!/)(.+)(?<!/)$")
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
path: try dictionary.getExpressionField("path", validator: Self.pathValidator, context: context),
value: try dictionary.getField("value", transform: { (dict: [String: Any]) in try DivTypedValue(dictionary: dict, context: context) }),
variableName: try dictionary.getExpressionField("variable_name", context: context)
)
}
init(
path: Expression<String>,
value: DivTypedValue,
@@ -23,6 +23,13 @@ public final class DivActionVideo: Sendable {
resolver.resolveString(id)
}
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
action: try dictionary.getExpressionField("action", context: context),
id: try dictionary.getExpressionField("id", context: context)
)
}
init(
action: Expression<Action>,
id: Expression<String>
@@ -54,6 +54,19 @@ public final class DivAnimation: Sendable {
static let startDelayValidator: AnyValueValidator<Int> =
makeValueValidator(valueValidator: { $0 >= 0 })
public convenience init(dictionary: [String: Any], context: ParsingContext) throws {
self.init(
duration: try dictionary.getOptionalExpressionField("duration", validator: Self.durationValidator, context: context),
endValue: try dictionary.getOptionalExpressionField("end_value", context: context),
interpolator: try dictionary.getOptionalExpressionField("interpolator", context: context),
items: try dictionary.getOptionalArray("items", transform: { (dict: [String: Any]) in try? DivAnimation(dictionary: dict, context: context) }),
name: try dictionary.getExpressionField("name", context: context),
repeatCount: try dictionary.getOptionalField("repeat", transform: { (dict: [String: Any]) in try DivCount(dictionary: dict, context: context) }),
startDelay: try dictionary.getOptionalExpressionField("start_delay", validator: Self.startDelayValidator, context: context),
startValue: try dictionary.getOptionalExpressionField("start_value", context: context)
)
}
init(
duration: Expression<Int>? = nil,
endValue: Expression<Double>? = nil,
@@ -28,6 +28,20 @@ public enum DivAnimator: Sendable {
}
}
extension DivAnimator {
public init(dictionary: [String: Any], context: ParsingContext) throws {
let blockType = try dictionary.getField("type") as String
switch blockType {
case DivColorAnimator.type:
self = .divColorAnimator(try DivColorAnimator(dictionary: dictionary, context: context))
case DivNumberAnimator.type:
self = .divNumberAnimator(try DivNumberAnimator(dictionary: dictionary, context: context))
default:
throw DeserializationError.invalidFieldRepresentation(field: "div-animator", representation: dictionary)
}
}
}
#if DEBUG
extension DivAnimator: Equatable {
public static func ==(lhs: DivAnimator, rhs: DivAnimator) -> Bool {

Some files were not shown because too many files have changed in this diff Show More