import CoreFoundation import Foundation import VGSL // MARK: Utils @usableFromInline func invalidFieldErrorForKey( _ key: [some Any], element: Int? = nil, representation: (some Any)? ) -> DeserializationError { let keyStrings: [String] = key.map { "\($0)" } return .invalidFieldRepresentation( field: "\(keyStrings.joined(separator: "."))" + (element.map { "[\($0)]" } ?? ""), representation: representation ) } @usableFromInline func getResult(_ block: () throws -> U) -> DeserializationResult { do { return try .success(block()) } catch let error as DeserializationError { return .failure(NonEmptyArray(error)) } catch { assertionFailure("Closure should throw only DeserializationError") return .failure(NonEmptyArray(.unexpectedError(message: error.localizedDescription))) } } @usableFromInline func unwrapOptionalTransformedValue( _ value: T, key: [some Any], transform: (T) -> U? ) throws -> U { guard let transformed = transform(value) else { throw invalidFieldErrorForKey(key, representation: value) } return transformed } @usableFromInline func rawRepresentableValue( _ raw: T.RawValue, key: [some Any] ) throws -> T { guard let value = T(rawValue: raw) else { throw invalidFieldErrorForKey(key, representation: raw) } return value } @usableFromInline func castValidSerializationValue( _ value: Any, key _: [some Any] ) throws -> T { guard let transformed = value as? T else { throw DeserializationError.typeMismatch( expected: String(describing: T.self), representation: value ) } return transformed } extension Dictionary where Key == String { @usableFromInline func enclosedDictForKeySequence(_ keys: [Key]) throws -> Self { guard !keys.isEmpty else { throw DeserializationError.noData } guard keys.count > 1 else { return self } var dict = self for key in keys.dropLast() { dict = try dict.getField(key) as Self } return dict } } // MARK: Required values (private interface) extension Dictionary where Key == String { @usableFromInline func getFieldPreservingError( _ key: [Key], transform: (T) throws -> U, validator: AnyValueValidator? ) throws -> U { let dict = try enclosedDictForKeySequence(key) guard let valueBeforeConversion = dict[key.last!] else { throw DeserializationError.noData } guard let typedValue = valueBeforeConversion as? T else { throw DeserializationError.typeMismatch( expected: String(describing: T.self), representation: valueBeforeConversion ) } let transformed = try transform(typedValue) guard validator?.isValid(transformed) != false else { throw DeserializationError.invalidValue(result: transformed, value: valueBeforeConversion) } return transformed } @usableFromInline func getField( _ key: [Key], transform: (T) throws -> U, validator: AnyValueValidator? ) throws -> U { let dict = try enclosedDictForKeySequence(key) guard let valueBeforeConversion = dict[key.last!] else { throw DeserializationError.noData } guard let transformed = try? (valueBeforeConversion as? T).map(transform), validator?.isValid(transformed) != false else { throw invalidFieldErrorForKey(key, representation: valueBeforeConversion) } return transformed } @usableFromInline func getArray( _ key: [Key], transform: (T) throws -> U, validator: AnyArrayValueValidator? ) throws -> [U] { try getArray(key, transform: transform, validator: validator).unwrap() } @usableFromInline func getArray( _ key: [Key], transform: (T) throws -> U, validator: AnyArrayValueValidator? ) -> DeserializationResult<[U]> { getArray(key, transform: { value in getResult { try transform(value) } }, validator: validator) } @usableFromInline func getArray( _ key: [Key], transform: (T) -> DeserializationResult, validator: AnyArrayValueValidator? ) -> DeserializationResult<[U]> { var errors: [DeserializationError] = [] let dictResult = getResult { try enclosedDictForKeySequence(key) } if case let .failure(dictErrors) = dictResult { return .failure(dictErrors) } errors.append(contentsOf: dictResult.errorsOrWarnings?.asArray() ?? []) guard let dict = dictResult.value, let valueBeforeConversion = dict[key.last!] else { return .failure(NonEmptyArray(.noData, errors)) } guard let array = valueBeforeConversion as? NSArray else { return .failure( NonEmptyArray( invalidFieldErrorForKey(key, representation: valueBeforeConversion), errors ) ) } var result: [U] = [] result.reserveCapacity(array.count) for index in 0..( _ key: [Key], transform: (T) throws -> U, validator: AnyValueValidator?, invalidValueHandler: ((Any) -> Void)? ) throws -> U? { guard let dict = try? enclosedDictForKeySequence(key), let value = dict[key.last!] else { return nil } guard let valueBeforeConversion = value as? T else { invalidValueHandler?(value) return nil } guard let result = try? transform(valueBeforeConversion), validator?.isValid(result) != false else { invalidValueHandler?(valueBeforeConversion) return nil } return result } @usableFromInline func getOptionalField( _ key: [Key], transform: (T) throws -> U, validator: AnyValueValidator? ) throws -> U? { try getOptionalFieldCore( key, transform: transform, validator: validator, invalidValueHandler: nil ) } @usableFromInline func getOptionalArrayCore( _ key: [Key], transform: (T) throws -> U, validator: AnyArrayValueValidator?, invalidContainerHandler: ((Any) -> Void)?, invalidElementHandler: ((Int, Any) throws -> Void)?, invalidTransformedElementHandler: ((Int, Any) -> Void)? ) throws -> [U]? { guard let dict = try? enclosedDictForKeySequence(key), let value = dict[key.last!] else { return nil } guard let valueBeforeConversion = value as? NSArray else { invalidContainerHandler?(value) return nil } var result: [U] = [] result.reserveCapacity(valueBeforeConversion.count) for (index, element) in valueBeforeConversion.enumerated() { guard let typedElement = element as? T else { try invalidElementHandler?(index, element) continue } guard let transformed = try? transform(typedElement) else { invalidTransformedElementHandler?(index, element) continue } result.append(transformed) } if validator?.isValid(result) == false { invalidContainerHandler?(valueBeforeConversion) return nil } return result } @usableFromInline func getOptionalArray( _ key: [Key], transform: (T) throws -> U, validator: AnyArrayValueValidator? ) throws -> [U]? { try getOptionalArrayCore( key, transform: transform, validator: validator, invalidContainerHandler: nil, invalidElementHandler: { index, element in throw invalidFieldErrorForKey(key, element: index, representation: element) }, invalidTransformedElementHandler: nil ) } } // MARK: Adapters for transform extension Dictionary where Key == String { @usableFromInline func getField( _ key: [Key], transform: (T) -> U?, validator: AnyValueValidator? ) throws -> U { try getField( key, transform: { value in try unwrapOptionalTransformedValue(value, key: key, transform: transform) }, validator: validator ) } @usableFromInline func getArray( _ key: [Key], transform: (T) -> U?, validator: AnyArrayValueValidator? ) throws -> [U] { try getArray(key, transform: transform, validator: validator).unwrap() } @usableFromInline func getArray( _ key: [Key], transform: (T) -> U?, validator: AnyArrayValueValidator? ) -> DeserializationResult<[U]> { getArray( key, transform: { guard let result = transform($0) else { return .noValue } return .success(result) }, validator: validator ) } @usableFromInline func getOptionalField( _ key: [Key], transform: (T) -> U?, validator: AnyValueValidator? ) throws -> U? { try getOptionalField( key, transform: { value in try unwrapOptionalTransformedValue(value, key: key, transform: transform) }, validator: validator ) } @usableFromInline func getOptionalArray( _ key: [Key], transform: (T) -> U?, validator: AnyArrayValueValidator? ) throws -> [U]? { try getOptionalArray( key, transform: { value in try unwrapOptionalTransformedValue(value, key: key, transform: transform) }, validator: validator ) } } // MARK: Required values (public interface) extension Dictionary where Key == String { @usableFromInline func getFieldWithContext( _ key: [Key], transform: (T) throws -> U, validator: AnyValueValidator? = nil, context: ParsingContext ) throws -> U { let fieldName = key.last! do { return try getFieldPreservingError(key, transform: transform, validator: validator) } catch let error as DeserializationError { let wrappedError: DeserializationError = switch error { case .noData: .requiredFieldIsMissing(field: fieldName) case .typeMismatch, .invalidFieldRepresentation: .nestedObjectError(field: fieldName, error: error) default: .nestedObjectError( field: fieldName, error: .composite( error: .invalidValue(result: nil, from: nil), causes: NonEmptyArray(error) ) ) } context.appendError(wrappedError) throw wrappedError } } @usableFromInline func getArrayWithContext( _ key: [Key], transform: (T) throws -> U, validator: AnyArrayValueValidator? = nil, context: ParsingContext ) throws -> [U] { let fieldName = key.last! let dict: [String: Any] do { dict = try enclosedDictForKeySequence(key) } catch { let err = DeserializationError.requiredFieldIsMissing(field: fieldName) context.appendError(err) throw err } guard let value = dict[fieldName] else { let err = DeserializationError.requiredFieldIsMissing(field: fieldName) context.appendError(err) throw err } guard let array = value as? NSArray else { let err = DeserializationError.nestedObjectError( field: fieldName, error: .typeMismatch(expected: "NSArray", representation: value) ) context.appendError(err) throw err } var result: [U] = [] var elementCauses: [DeserializationError] = [] result.reserveCapacity(array.count) for index in 0..( _ key: [Key], transform: (T) throws -> U, validator: AnyValueValidator? = nil, context: ParsingContext ) throws -> U? { try getOptionalFieldCore( key, transform: transform, validator: validator, invalidValueHandler: { value in context.appendWarning(.nestedObjectError( field: key.last!, error: .typeMismatch(expected: String(describing: U.self), representation: value) )) } ) } @usableFromInline func getOptionalArrayWithContext( _ key: [Key], transform: (T) throws -> U, validator: AnyArrayValueValidator? = nil, context: ParsingContext ) throws -> [U]? { try getOptionalArrayCore( key, transform: transform, validator: validator, invalidContainerHandler: { value in context.appendWarning(.nestedObjectError( field: key.last!, error: .typeMismatch(expected: "NSArray", representation: value) )) }, invalidElementHandler: { index, element in context.appendWarning(.nestedObjectError( field: key.last!, error: .nestedObjectError( field: "\(index)", error: .typeMismatch(expected: String(describing: T.self), representation: element) ) )) }, invalidTransformedElementHandler: { index, element in context.appendWarning(.nestedObjectError( field: key.last!, error: .nestedObjectError( field: "\(index)", error: .typeMismatch(expected: String(describing: U.self), representation: element) ) )) } ) } @inlinable public func getField( _ key: Key..., validator: AnyValueValidator? = nil ) throws -> T { try getField(key, transform: { $0 as T }, validator: validator) } @inlinable public func getField( _ key: Key..., validator: AnyValueValidator? = nil ) throws -> T { try getField( key, transform: { raw in try rawRepresentableValue(raw, key: key) }, validator: validator ) } @inlinable public func getField( _ key: Key..., validator: AnyValueValidator? = nil ) throws -> T { try getField( key, transform: { (dict: Self) in try T(dictionary: dict) }, validator: validator ) } @inlinable public func getArray( _ key: Key..., validator: AnyArrayValueValidator? = nil ) throws -> [Any] { try getArray(key, transform: { (value: Any) throws -> Any in value }, validator: validator) } @inlinable public func getArray( _ key: Key..., transform: (T) -> DeserializationResult, validator: AnyArrayValueValidator? = nil ) -> DeserializationResult<[U]> { getArray(key, transform: transform, validator: validator) } @inlinable public func getArray( _ key: Key..., transform: (T) -> U?, validator: AnyArrayValueValidator? = nil ) throws -> [U] { try getArray(key, transform: transform, validator: validator) } @inlinable public func getArray( _ key: Key..., validator: AnyArrayValueValidator? = nil ) throws -> [T] { try getArray( key, transform: { (obj: Any) -> T? in try? castValidSerializationValue(obj, key: key) }, validator: validator ) } @inlinable public func getArray( _ key: Key..., validator: AnyArrayValueValidator? = nil ) throws -> [T] { try getArray( key, transform: { (dict: Self) in try T(dictionary: dict) }, validator: validator ) } @inlinable public func getField( _ key: Key..., transform: (T) throws -> U, validator: AnyValueValidator? = nil ) throws -> U { try getField(key, transform: transform, validator: validator) } @inlinable public func getField( _ key: Key..., transform: (T) -> U?, validator: AnyValueValidator? = nil ) throws -> U { try getField(key, transform: transform, validator: validator) } public func getURL(_ key: Key..., validator: AnyValueValidator? = nil) throws -> URL { try getField(key, transform: URL.init(string:), validator: validator) } public func getObjCCompatibleBool(_ key: Key...) -> Bool? { (try? self.getField(key, transform: { ($0 as NSString).boolValue }, validator: nil)) ?? (try? self.getField(key, transform: { $0 as Bool }, validator: nil) as Bool) } } // MARK: Optional values (public interface) extension Dictionary where Key == String { public func getOptionalField( _ key: Key..., validator: AnyValueValidator? = nil ) throws -> CFString? { try getOptionalField(key, transform: safeCFCast, validator: validator) } @inlinable public func getOptionalField( _ key: Key..., validator: AnyValueValidator? = nil ) throws -> T? { try getOptionalField(key, transform: { $0 as T }, validator: validator) } @inlinable public func getOptionalField( _ key: Key..., validator: AnyValueValidator? = nil ) throws -> T? { try getOptionalField( key, transform: { raw in try rawRepresentableValue(raw, key: key) }, validator: validator ) } @inlinable public func getOptionalField( _ key: Key..., transform: (T) -> U?, validator: AnyValueValidator? = nil ) throws -> U? { try getOptionalField(key, transform: transform, validator: validator) } @inlinable public func getOptionalField( _ key: Key..., transform: (T) throws -> U, validator: AnyValueValidator? = nil ) throws -> U? { try getOptionalField(key, transform: transform, validator: validator) } @inlinable public func getOptionalField( _ key: Key..., validator: AnyValueValidator? = nil ) throws -> T? { try getOptionalField( key, transform: { (dict: Self) in try T(dictionary: dict) }, validator: validator ) } @inlinable public func getOptionalArray( _ key: Key..., validator: AnyArrayValueValidator? = nil ) throws -> [T]? { try getOptionalArray( key, transform: { (obj: Any) -> T? in obj as? T }, validator: validator ) } @inlinable public func getOptionalArray( _ key: Key..., validator: AnyArrayValueValidator? = nil ) throws -> [T]? { try getOptionalArray( key, transform: { (obj: Any) -> T? in (obj as? T.RawValue).flatMap { try? rawRepresentableValue($0, key: key) } }, validator: validator ) } @inlinable public func getOptionalArray( _ key: Key..., transform: (T) throws -> U, validator: AnyArrayValueValidator? = nil ) throws -> [U]? { try getOptionalArray(key, transform: transform, validator: validator) } @inlinable public func getOptionalArray( _ key: Key..., transform: (T) -> U?, validator: AnyArrayValueValidator? = nil ) throws -> [U]? { try getOptionalArray(key, transform: transform, validator: validator) } @inlinable public func getOptionalArray( _ key: Key..., validator: AnyArrayValueValidator? = nil ) throws -> [T]? { try getOptionalArray( key, transform: { (dict: Self) in try T(dictionary: dict) }, validator: validator ) } } // MARK: Context-aware values (public interface) extension Dictionary where Key == String { @inlinable public func getField( _ key: Key..., validator: AnyValueValidator? = nil, context: ParsingContext ) throws -> T { try getFieldWithContext( key, transform: { value in try castValidSerializationValue(value, key: key) }, validator: validator, context: context ) } @inlinable public func getField( _ key: Key..., validator: AnyValueValidator? = nil, context: ParsingContext ) throws -> T { try getFieldWithContext( key, transform: { raw in try rawRepresentableValue(raw, key: key) }, validator: validator, context: context ) } @inlinable public func getField( _ key: Key..., transform: (T) throws -> U, validator: AnyValueValidator? = nil, context: ParsingContext ) throws -> U { try getFieldWithContext(key, transform: transform, validator: validator, context: context) } @inlinable public func getField( _ key: Key..., transform: (T) -> U?, validator: AnyValueValidator? = nil, context: ParsingContext ) throws -> U { try getFieldWithContext( key, transform: { value in try unwrapOptionalTransformedValue(value, key: key, transform: transform) }, validator: validator, context: context ) } @inlinable public func getArray( _ key: Key..., validator: AnyArrayValueValidator? = nil, context: ParsingContext ) throws -> [T] { try getArrayWithContext( key, transform: { value in try castValidSerializationValue(value, key: key) }, validator: validator, context: context ) } @inlinable public func getArray( _ key: Key..., validator: AnyArrayValueValidator? = nil, context: ParsingContext ) throws -> [T] { try getArrayWithContext( key, transform: { (obj: Any) throws -> T in guard let rawValue = obj as? T.RawValue else { throw invalidFieldErrorForKey(key, representation: obj) } return try rawRepresentableValue(rawValue, key: key) }, validator: validator, context: context ) } @inlinable public func getArray( _ key: Key..., transform: (T) throws -> U, validator: AnyArrayValueValidator? = nil, context: ParsingContext ) throws -> [U] { try getArrayWithContext(key, transform: transform, validator: validator, context: context) } @inlinable public func getArray( _ key: Key..., transform: (T) -> U?, validator: AnyArrayValueValidator? = nil, context: ParsingContext ) throws -> [U] { try getArrayWithContext( key, transform: { value in try unwrapOptionalTransformedValue(value, key: key, transform: transform) }, validator: validator, context: context ) } @inlinable public func getOptionalField( _ key: Key..., validator: AnyValueValidator? = nil, context: ParsingContext ) throws -> T? { try getOptionalFieldWithContext( key, transform: { value in try castValidSerializationValue(value, key: key) }, validator: validator, context: context ) } @inlinable public func getOptionalField( _ key: Key..., validator: AnyValueValidator? = nil, context: ParsingContext ) throws -> T? { try getOptionalFieldWithContext( key, transform: { raw in try rawRepresentableValue(raw, key: key) }, validator: validator, context: context ) } @inlinable public func getOptionalField( _ key: Key..., transform: (T) throws -> U, validator: AnyValueValidator? = nil, context: ParsingContext ) throws -> U? { try getOptionalFieldWithContext( key, transform: transform, validator: validator, context: context ) } @inlinable public func getOptionalField( _ key: Key..., transform: (T) -> U?, validator: AnyValueValidator? = nil, context: ParsingContext ) throws -> U? { try getOptionalFieldWithContext( key, transform: { value in try unwrapOptionalTransformedValue(value, key: key, transform: transform) }, validator: validator, context: context ) } @inlinable public func getOptionalArray( _ key: Key..., validator: AnyArrayValueValidator? = nil, context: ParsingContext ) throws -> [T]? { try getOptionalArrayWithContext( key, transform: { value in try castValidSerializationValue(value, key: key) }, validator: validator, context: context ) } @inlinable public func getOptionalArray( _ key: Key..., validator: AnyArrayValueValidator? = nil, context: ParsingContext ) throws -> [T]? { try getOptionalArrayWithContext( key, transform: { (obj: Any) throws -> T in guard let rawValue = obj as? T.RawValue else { throw invalidFieldErrorForKey(key, representation: obj) } return try rawRepresentableValue(rawValue, key: key) }, validator: validator, context: context ) } @inlinable public func getOptionalArray( _ key: Key..., transform: (T) throws -> U, validator: AnyArrayValueValidator? = nil, context: ParsingContext ) throws -> [U]? { try getOptionalArrayWithContext( key, transform: transform, validator: validator, context: context ) } @inlinable public func getOptionalArray( _ key: Key..., transform: (T) -> U?, validator: AnyArrayValueValidator? = nil, context: ParsingContext ) throws -> [U]? { try getOptionalArrayWithContext( key, transform: { value in try unwrapOptionalTransformedValue(value, key: key, transform: transform) }, validator: validator, context: context ) } }