public extension PureYAML.Decoding { /// Typed decoder backed by a ``PureYAML/Model/Value``. struct Decoder: Swift.Decoder { public let value: PureYAML.Model.Value public let codingPath: [any CodingKey] public let userInfo: [CodingUserInfoKey: Any] let validatesInput: Bool public init( value: PureYAML.Model.Value, codingPath: [any CodingKey] = [], userInfo: [CodingUserInfoKey: Any] = [:], validatesInput: Bool = true, ) { self.value = value self.codingPath = codingPath self.userInfo = userInfo self.validatesInput = validatesInput } public func container( keyedBy _: Key.Type, ) throws -> KeyedDecodingContainer { try validateIfNeeded() guard case let .mapping(mapping) = value else { throw Error.typeMismatch( expected: "mapping", actual: value.kindDescription, path: PureYAML.Validation.Path(codingPath: codingPath), ) } return KeyedDecodingContainer(KeyedDecodingContainerImpl( mapping: mapping, codingPath: codingPath, userInfo: userInfo, validatesInput: validatesInput, )) } public func unkeyedContainer() throws -> any UnkeyedDecodingContainer { try validateIfNeeded() guard case let .sequence(values) = value else { throw Error.typeMismatch( expected: "sequence", actual: value.kindDescription, path: PureYAML.Validation.Path(codingPath: codingPath), ) } return UnkeyedDecodingContainerImpl( values: values, codingPath: codingPath, userInfo: userInfo, validatesInput: validatesInput, ) } public func singleValueContainer() throws -> any SingleValueDecodingContainer { try validateIfNeeded() return SingleValueContainer( value: value, codingPath: codingPath, userInfo: userInfo, validatesInput: validatesInput, ) } private func validateIfNeeded() throws { if validatesInput { try PureYAML.Decoding.validate(value, codingPath: codingPath) } } } } extension PureYAML.Decoding { static func validate( _ value: PureYAML.Model.Value, codingPath: [any CodingKey], ) throws { let result = PureYAML.Validation.Validator().collect(value) guard !result.values.isEmpty else { return } let pathPrefix = PureYAML.Validation.Path(codingPath: codingPath) let errors = result.values.map { error in PureYAML.Validation.ValidationError( severity: error.severity, reason: error.reason, path: .init(pathPrefix.components + error.path.components), ) } throw PureYAML.Validation.ValidationErrorCollection(errors) } } private struct SingleValueContainer: SingleValueDecodingContainer { let value: PureYAML.Model.Value let codingPath: [any CodingKey] let userInfo: [CodingUserInfoKey: Any] let validatesInput: Bool func decodeNil() -> Bool { value == .null } func decode(_: Bool.Type) throws -> Bool { guard case let .bool(value) = value else { throw mismatch("Bool") } return value } func decode(_: String.Type) throws -> String { guard case let .string(value) = value else { throw mismatch("String") } return value } func decode(_: Double.Type) throws -> Double { switch value { case let .double(value): return value case let .int(value): return Double(value) case .null, .bool, .string, .sequence, .mapping: throw mismatch("Double") } } func decode(_: Float.Type) throws -> Float { try Float(decode(Double.self)) } func decode(_: Int.Type) throws -> Int { guard case let .int(value) = value else { throw mismatch("Int") } return value } func decode(_ type: Int8.Type) throws -> Int8 { try decodeInteger(type) } func decode(_ type: Int16.Type) throws -> Int16 { try decodeInteger(type) } func decode(_ type: Int32.Type) throws -> Int32 { try decodeInteger(type) } func decode(_ type: Int64.Type) throws -> Int64 { try decodeInteger(type) } func decode(_ type: UInt.Type) throws -> UInt { try decodeUnsignedInteger(type) } func decode(_ type: UInt8.Type) throws -> UInt8 { try decodeUnsignedInteger(type) } func decode(_ type: UInt16.Type) throws -> UInt16 { try decodeUnsignedInteger(type) } func decode(_ type: UInt32.Type) throws -> UInt32 { try decodeUnsignedInteger(type) } func decode(_ type: UInt64.Type) throws -> UInt64 { try decodeUnsignedInteger(type) } func decode(_: Value.Type) throws -> Value { try Value(from: PureYAML.Decoding.Decoder( value: value, codingPath: codingPath, userInfo: userInfo, validatesInput: validatesInput, )) } private func decodeInteger( _: Integer.Type, ) throws -> Integer { let value = try decode(Int.self) guard let converted = Integer(exactly: value) else { throw integerOutOfRange(String(describing: Integer.self)) } return converted } private func decodeUnsignedInteger( _: Integer.Type, ) throws -> Integer { let value = try decode(Int.self) guard let converted = Integer(exactly: value) else { throw integerOutOfRange(String(describing: Integer.self)) } return converted } private func mismatch(_ expected: String) -> PureYAML.Decoding.Error { .typeMismatch( expected: expected, actual: value.kindDescription, path: PureYAML.Validation.Path(codingPath: codingPath), ) } private func integerOutOfRange(_ type: String) -> PureYAML.Decoding.Error { .integerOutOfRange( type: type, path: PureYAML.Validation.Path(codingPath: codingPath), ) } }