From 3a91aacc5cc85f56e2b02c8d04750d8a209186a3 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Mon, 23 Dec 2024 18:22:51 -0600 Subject: [PATCH 01/16] WIP, schemas compile --- Sources/Smithy/Document/ShapeType.swift | 21 ++ .../Reader/Reader+ShapeDeserializer.swift | 108 ++++++++ Sources/SmithyJSON/Reader/Reader.swift | 2 +- .../SmithyReadWrite/DeserializableShape.swift | 11 + Sources/SmithyReadWrite/ReadingClosure.swift | 4 +- Sources/SmithyReadWrite/Schema/Prelude.swift | 57 ++++ Sources/SmithyReadWrite/Schema/Schema.swift | 232 ++++++++++++++++ .../SmithyReadWrite/ShapeDeserializer.swift | 167 +++++++++++ Sources/SmithyReadWrite/SmithyReader.swift | 9 +- Sources/SmithyReadWrite/SmithyWriter.swift | 2 +- Sources/SmithyReadWrite/WritingClosure.swift | 2 +- Sources/SmithyXML/Reader/Reader.swift | 4 +- .../swift/codegen/DirectedSwiftCodegen.kt | 1 + .../swift/codegen/StructureGenerator.kt | 32 ++- .../swift/codegen/SwiftSymbolProvider.kt | 2 +- .../smithy/swift/codegen/SwiftWriter.kt | 2 +- .../smithy/swift/codegen/UnionGenerator.kt | 5 +- .../endpoints/EndpointParamsGenerator.kt | 2 +- .../HTTPBindingProtocolGenerator.kt | 98 +++++++ .../codegen/integration/ProtocolGenerator.kt | 2 + .../serde/schema/SchemaGenerator.kt | 259 ++++++++++++++++++ .../swiftmodules/ClientRuntimeTypes.kt | 6 + .../swiftmodules/SmithyReadWriteTypes.kt | 14 + .../swift/codegen/swiftmodules/SmithyTypes.kt | 14 +- .../swift/codegen/swiftmodules/SwiftTypes.kt | 1 + 25 files changed, 1031 insertions(+), 26 deletions(-) create mode 100644 Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift create mode 100644 Sources/SmithyReadWrite/DeserializableShape.swift create mode 100644 Sources/SmithyReadWrite/Schema/Prelude.swift create mode 100644 Sources/SmithyReadWrite/Schema/Schema.swift create mode 100644 Sources/SmithyReadWrite/ShapeDeserializer.swift create mode 100644 smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt diff --git a/Sources/Smithy/Document/ShapeType.swift b/Sources/Smithy/Document/ShapeType.swift index 5b3de70a1..f86f32a28 100644 --- a/Sources/Smithy/Document/ShapeType.swift +++ b/Sources/Smithy/Document/ShapeType.swift @@ -8,6 +8,14 @@ /// Reproduces the cases in Smithy `ShapeType`. /// https://github.com/smithy-lang/smithy/blob/main/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ShapeType.java public enum ShapeType { + + public enum Category { + case simple + case aggregate + case service + case member + } + case blob case boolean case string @@ -32,4 +40,17 @@ public enum ShapeType { case service case resource case operation + + public var category: Category { + switch self { + case .blob, .boolean, .string, .timestamp, .byte, .short, .integer, .long, .float, .document, .double, .bigDecimal, .bigInteger, .enum, .intEnum: + return .simple + case .list, .set, .map, .structure, .union: + return .aggregate + case .service, .resource, .operation: + return .service + case .member: + return .member + } + } } diff --git a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift new file mode 100644 index 000000000..778b546a7 --- /dev/null +++ b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift @@ -0,0 +1,108 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Foundation.Data +import struct Foundation.Date +import protocol Smithy.SmithyDocument +import enum SmithyReadWrite.ReaderError +@_spi(SchemaBasedSerde) import protocol SmithyReadWrite.ShapeDeserializer +@_spi(SchemaBasedSerde) import protocol SmithyReadWrite.SchemaProtocol +@_spi(SchemaBasedSerde) import protocol SmithyReadWrite.DeserializableShape +@_spi(SchemaBasedSerde) import struct SmithyReadWrite.StructureSchema +@_spi(SchemaBasedSerde) import struct SmithyReadWrite.ListSchema +@_spi(SchemaBasedSerde) import struct SmithyReadWrite.MapSchema +@_spi(SchemaBasedSerde) import struct SmithyReadWrite.Member +@_spi(SmithyTimestamps) import enum SmithyTimestamps.TimestampFormat + +extension Reader: SmithyReadWrite.ShapeDeserializer { + + public func readStruct( + schema: SmithyReadWrite.StructureSchema + ) throws -> Base? { + var value = Base() + try schema.members.forEach { member in + try fillMember(value: &value, member: member) + } + return value + } + + private func fillMember(value: inout T, member: SmithyReadWrite.Member) throws { + guard let resolvedName = member.memberSchema.jsonName ?? member.memberSchema.memberName else { + throw ReaderError.invalidSchema("Could not resolve member name") + } + try member.setter(&value, self[NodeInfo(resolvedName)]) + } + + public func readString(schema: SmithyReadWrite.SchemaProtocol) throws -> String? { + try readIfPresent() + } + + public func readList(schema: SmithyReadWrite.ListSchema) throws -> [T]? { + // TODO: Implement me + [] + } + + public func readMap(schema: SmithyReadWrite.MapSchema) throws -> [String : T]? { + // TODO: Implement me + [:] + } + + public func readBoolean(schema: any SmithyReadWrite.SchemaProtocol) throws -> Bool? { + try readIfPresent() + } + + public func readByte(schema: any SmithyReadWrite.SchemaProtocol) throws -> Int8? { + try readIfPresent() + } + + public func readShort(schema: any SmithyReadWrite.SchemaProtocol) throws -> Int16? { + try readIfPresent() + } + + public func readInteger(schema: any SmithyReadWrite.SchemaProtocol) throws -> Int? { + try readIfPresent() + } + + public func readLong(schema: any SmithyReadWrite.SchemaProtocol) throws -> Int? { + try readIfPresent() + } + + public func readFloat(schema: any SmithyReadWrite.SchemaProtocol) throws -> Float? { + try readIfPresent() + } + + public func readDouble(schema: any SmithyReadWrite.SchemaProtocol) throws -> Double? { + try readIfPresent() + } + + public func readBigInteger(schema: any SmithyReadWrite.SchemaProtocol) throws -> Int? { + try readIfPresent() + } + + public func readBigDecimal(schema: any SmithyReadWrite.SchemaProtocol) throws -> Float? { + try readIfPresent() + } + + public func readBlob(schema: any SmithyReadWrite.SchemaProtocol) throws -> Data? { + try readIfPresent() + } + + public func readTimestamp(schema: any SmithyReadWrite.SchemaProtocol) throws -> Date? { + // TODO: Implement me + try readTimestampIfPresent(format: schema.timestampFormat ?? .epochSeconds) + } + + public func readDocument(schema: any SmithyReadWrite.SchemaProtocol) throws -> (any Smithy.SmithyDocument)? { + // TODO: Implement me + nil + } + + public func readNull(schema: any SmithyReadWrite.SchemaProtocol) throws -> Bool? { + // TODO: Implement me + return false + } +} diff --git a/Sources/SmithyJSON/Reader/Reader.swift b/Sources/SmithyJSON/Reader/Reader.swift index f70752696..9436bcd79 100644 --- a/Sources/SmithyJSON/Reader/Reader.swift +++ b/Sources/SmithyJSON/Reader/Reader.swift @@ -152,7 +152,7 @@ public extension Reader { } } - func readIfPresent() throws -> Document? { + func readIfPresent() throws -> (any SmithyDocument)? { guard let jsonObject = self.jsonObject else { return nil } return try Document.make(from: jsonObject) } diff --git a/Sources/SmithyReadWrite/DeserializableShape.swift b/Sources/SmithyReadWrite/DeserializableShape.swift new file mode 100644 index 000000000..566058124 --- /dev/null +++ b/Sources/SmithyReadWrite/DeserializableShape.swift @@ -0,0 +1,11 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +public protocol DeserializableShape { + + init() +} diff --git a/Sources/SmithyReadWrite/ReadingClosure.swift b/Sources/SmithyReadWrite/ReadingClosure.swift index f09498a75..bc8f4999f 100644 --- a/Sources/SmithyReadWrite/ReadingClosure.swift +++ b/Sources/SmithyReadWrite/ReadingClosure.swift @@ -181,11 +181,11 @@ public enum ReadingClosures { try reader.readIfPresent() } - public static func readDocument(from reader: Reader) throws -> Document { + public static func readSmithyDocument(from reader: Reader) throws -> SmithyDocument { Document(try reader.read()) } - public static func readDocument(from reader: Reader) throws -> Document? { + public static func readSmithyDocument(from reader: Reader) throws -> SmithyDocument? { (try reader.readIfPresent()).map { Document($0) } } } diff --git a/Sources/SmithyReadWrite/Schema/Prelude.swift b/Sources/SmithyReadWrite/Schema/Prelude.swift new file mode 100644 index 000000000..8a125a005 --- /dev/null +++ b/Sources/SmithyReadWrite/Schema/Prelude.swift @@ -0,0 +1,57 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import protocol Smithy.SmithyDocument + +@_spi(SchemaBasedSerde) +public let unitSchema = SimpleSchema( + namespace: "smithy.api", + name: "Unit", + type: .structure +) + +@_spi(SchemaBasedSerde) +public let booleanSchema = SimpleSchema( + namespace: "smithy.api", + name: "Boolean", + type: .boolean +) + +@_spi(SchemaBasedSerde) +public let integerSchema = SimpleSchema( + namespace: "smithy.api", + name: "Integer", + type: .integer +) + +@_spi(SchemaBasedSerde) +public let floatSchema = SimpleSchema( + namespace: "smithy.api", + name: "Float", + type: .float +) + +@_spi(SchemaBasedSerde) +public let doubleSchema = SimpleSchema( + namespace: "smithy.api", + name: "Double", + type: .double +) + +@_spi(SchemaBasedSerde) +public let stringSchema = SimpleSchema( + namespace: "smithy.api", + name: "String", + type: .string +) + +@_spi(SchemaBasedSerde) +public let documentSchema = SimpleSchema( + namespace: "smithy.api", + name: "Document", + type: .document +) diff --git a/Sources/SmithyReadWrite/Schema/Schema.swift b/Sources/SmithyReadWrite/Schema/Schema.swift new file mode 100644 index 000000000..47744086a --- /dev/null +++ b/Sources/SmithyReadWrite/Schema/Schema.swift @@ -0,0 +1,232 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Smithy +@_spi(SmithyTimestamps) import SmithyTimestamps + +@_spi(SchemaBasedSerde) +public protocol SchemaProtocol { +// var namespace: String { get } +// var name: String { get } + var type: ShapeType { get } + + func read(reader: any SmithyReader) throws + func write(writer: any SmithyWriter) throws +} + +//extension SchemaProtocol { +// +// public var id: String { ["\(namespace)#\(name)", memberName].compactMap { $0 }.joined(separator: "$") } +//} + +@_spi(SchemaBasedSerde) +public struct StructureSchema: SchemaProtocol { + + public struct Member { + public let memberSchema: SchemaProtocol + public let targetSchema: SchemaProtocol + public let readBlock: (inout Base, any ShapeDeserializer) throws -> Void + public let writeBlock: (Base, any SmithyWriter) throws -> Void + + public init( + memberSchema: SchemaProtocol, + targetSchema: SchemaProtocol, + readBlock: @escaping (inout Base, any ShapeDeserializer) throws -> Void, + writeBlock: @escaping (Base, any SmithyWriter) throws -> Void + ) { + self.memberSchema = memberSchema + self.targetSchema = targetSchema + self.readBlock = readBlock + self.writeBlock = writeBlock + } + } + +// public let namespace: String +// public let name: String + public let type: ShapeType + public let members: [Member] + public let memberName: String? + + public init( + namespace: String = "", + name: String = "", + type: ShapeType, + members: [Member] = [], + memberName: String? = nil + ) { +// self.namespace = namespace +// self.name = name + self.type = type + self.members = members + self.memberName = memberName + } + + public func read(reader: any SmithyReader) throws { + // TODO: implement + } + + public func write(writer: any SmithyWriter) throws { + // TODO: implement + } +} + +@_spi(SchemaBasedSerde) +public struct ListSchema: SchemaProtocol { +// public let namespace: String +// public let name: String + public let type: ShapeType + public let memberSchema: SchemaProtocol + public let targetSchema: SchemaProtocol + public let readBlock: (any ShapeDeserializer) throws -> Element + public let writeBlock: (any SmithyWriter, Element) throws -> Void + + public init( + namespace: String = "", + name: String = "", + type: ShapeType, + memberSchema: SchemaProtocol, + targetSchema: SchemaProtocol, + readBlock: @escaping (any ShapeDeserializer) throws -> Element, + writeBlock: @escaping (any SmithyWriter, Element) throws -> Void + ) { +// self.namespace = namespace +// self.name = name + self.type = type + self.memberSchema = memberSchema + self.targetSchema = targetSchema + self.readBlock = readBlock + self.writeBlock = writeBlock + } + + public func read(reader: any SmithyReader) throws { + // TODO: implement + } + + public func write(writer: any SmithyWriter) throws { + // TODO: implement + } +} + +@_spi(SchemaBasedSerde) +public struct MapSchema: SchemaProtocol { +// public let namespace: String +// public let name: String + public let type: ShapeType + public let keyMemberSchema: SchemaProtocol + public let keyTargetSchema: SchemaProtocol + public let valueMemberSchema: SchemaProtocol + public let valueTargetSchema: SchemaProtocol + public let readBlock: (any ShapeDeserializer) throws -> Value + public let writeBlock: (any SmithyWriter, Value) throws -> Void + + public init( + namespace: String = "", + name: String = "", + type: ShapeType, + keyMemberSchema: SchemaProtocol, + keyTargetSchema: SchemaProtocol, + valueMemberSchema: SchemaProtocol, + valueTargetSchema: SchemaProtocol, + readBlock: @escaping (any ShapeDeserializer) throws -> Value, + writeBlock: @escaping (any SmithyWriter, Value) throws -> Void + ) { +// self.namespace = namespace +// self.name = name + self.type = type + self.keyMemberSchema = keyMemberSchema + self.keyTargetSchema = keyTargetSchema + self.valueMemberSchema = valueMemberSchema + self.valueTargetSchema = valueTargetSchema + self.readBlock = readBlock + self.writeBlock = writeBlock + } + + public func read(reader: any SmithyReader) throws { + // TODO: implement + } + + public func write(writer: any SmithyWriter) throws { + // TODO: implement + } +} + +@_spi(SchemaBasedSerde) +public struct MemberSchema: SchemaProtocol { +// public let namespace: String +// public let name: String + public let type: ShapeType + public let memberName: String? + public let jsonName: String? + public let xmlName: String? + public let isRequired: Bool + public let defaultValue: (any SmithyDocument)? + + public init( + namespace: String = "", + name: String = "", + type: ShapeType, + memberName: String? = nil, + jsonName: String? = nil, + xmlName: String? = nil, + isRequired: Bool = false, + defaultValue: SmithyDocument? = nil + ) { +// self.namespace = namespace +// self.name = name + self.type = type + self.memberName = memberName + self.jsonName = jsonName + self.xmlName = xmlName + self.isRequired = isRequired + self.defaultValue = defaultValue + } + + public func read(reader: any SmithyReader) throws { + // TODO: implement + } + + public func write(writer: any SmithyWriter) throws { + // TODO: implement + } +} + +@_spi(SchemaBasedSerde) +public struct SimpleSchema: SchemaProtocol { +// public let namespace: String +// public let name: String + public let type: ShapeType + public let memberName: String? + public let isRequired: Bool + public let timestampFormat: TimestampFormat? + public let defaultValue: (any SmithyDocument)? + + public init( + namespace: String = "", + name: String = "", + type: ShapeType, + memberName: String? = nil, + isRequired: Bool = false, + timestampFormat: TimestampFormat? = nil, + defaultValue: SmithyDocument? = nil + ) { +// self.namespace = namespace +// self.name = name + self.type = type + self.memberName = memberName + self.isRequired = isRequired + self.timestampFormat = timestampFormat + self.defaultValue = defaultValue + } + + public func read(reader: any SmithyReader) throws { + // TODO: implement + } + + public func write(writer: any SmithyWriter) throws { + // TODO: implement + } +} diff --git a/Sources/SmithyReadWrite/ShapeDeserializer.swift b/Sources/SmithyReadWrite/ShapeDeserializer.swift new file mode 100644 index 000000000..444b223c5 --- /dev/null +++ b/Sources/SmithyReadWrite/ShapeDeserializer.swift @@ -0,0 +1,167 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import protocol Smithy.SmithyDocument +import struct Foundation.Data +import struct Foundation.Date + +@_spi(SchemaBasedSerde) +public protocol ShapeDeserializer { + func readStruct(schema: StructureSchema) throws -> T? + func readList(schema: ListSchema) throws -> [T]? + func readMap(schema: MapSchema) throws -> [String: T]? + func readBoolean(schema: SchemaProtocol) throws -> Bool? + func readByte(schema: SchemaProtocol) throws -> Int8? + func readShort(schema: SchemaProtocol) throws -> Int16? + func readInteger(schema: SchemaProtocol) throws -> Int? + func readLong(schema: SchemaProtocol) throws -> Int? + func readFloat(schema: SchemaProtocol) throws -> Float? + func readDouble(schema: SchemaProtocol) throws -> Double? + func readBigInteger(schema: SchemaProtocol) throws -> Int? + func readBigDecimal(schema: SchemaProtocol) throws -> Float? + func readString(schema: SchemaProtocol) throws -> String? + func readBlob(schema: SchemaProtocol) throws -> Data? + func readTimestamp(schema: SchemaProtocol) throws -> Date? + func readDocument(schema: SchemaProtocol) throws -> SmithyDocument? + func readNull(schema: SchemaProtocol) throws -> Bool? +} + +@_spi(SchemaBasedSerde) +public extension ShapeDeserializer { + + func readEnum(schema: SchemaProtocol) throws -> T? where T.RawValue == String { + guard let rawValue = try readString(schema: schema) else { return nil } + return T(rawValue: rawValue) + } + + func readIntEnum(schema: SchemaProtocol) throws -> T? where T.RawValue == Int { + guard let rawValue = try readInteger(schema: schema) else { return nil } + return T(rawValue: rawValue) + } +} + +@_spi(SchemaBasedSerde) +public extension ShapeDeserializer { + + func readStructNonNull(schema: StructureSchema) throws -> T { + guard let value = try readStruct(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + func readListNonNull(schema: ListSchema) throws -> [T] { + guard let value = try readList(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readMapNonNull(schema: MapSchema) throws -> [String: T] { + guard let value = try readMap(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readBooleanNonNull(schema: SimpleSchema) throws -> Bool { + guard let value = try readBoolean(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readByteNonNull(schema: SimpleSchema) throws -> Int8 { + guard let value = try readByte(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readShortNonNull(schema: SimpleSchema) throws -> Int16 { + guard let value = try readShort(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readIntegerNonNull(schema: SimpleSchema) throws -> Int { + guard let value = try readInteger(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readLongNonNull(schema: SimpleSchema) throws -> Int { + guard let value = try readLong(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readFloatNonNull(schema: SimpleSchema) throws -> Float { + guard let value = try readFloat(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readDoubleNonNull(schema: SimpleSchema) throws -> Double { + guard let value = try readDouble(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readBigIntegerNonNull(schema: SimpleSchema) throws -> Int { + guard let value = try readBigInteger(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readBigDecimalNonNull(schema: SimpleSchema) throws -> Float { + guard let value = try readBigDecimal(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readStringNonNull(schema: SimpleSchema) throws -> String { + guard let value = try readString(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readBlobNonNull(schema: SimpleSchema) throws -> Data { + guard let value = try readBlob(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readTimestampNonNull(schema: SimpleSchema) throws -> Date { + guard let value = try readTimestamp(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readDocumentNonNull(schema: SimpleSchema) throws -> any SmithyDocument { + guard let value = try readDocument(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readNullNonNull(schema: SimpleSchema) throws -> Bool { + guard let value = try readNull(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } +} diff --git a/Sources/SmithyReadWrite/SmithyReader.swift b/Sources/SmithyReadWrite/SmithyReader.swift index 7904a12c1..23c177b56 100644 --- a/Sources/SmithyReadWrite/SmithyReader.swift +++ b/Sources/SmithyReadWrite/SmithyReader.swift @@ -8,7 +8,7 @@ import struct Foundation.Data import struct Foundation.Date @_spi(SmithyTimestamps) import enum SmithyTimestamps.TimestampFormat -import struct Smithy.Document +import protocol Smithy.SmithyDocument @_spi(SmithyReadWrite) public protocol SmithyReader: AnyObject { @@ -27,7 +27,7 @@ public protocol SmithyReader: AnyObject { func readIfPresent() throws -> Double? func readIfPresent() throws -> Bool? func readIfPresent() throws -> Data? - func readIfPresent() throws -> Document? + func readIfPresent() throws -> (any SmithyDocument)? func readIfPresent() throws -> T? where T.RawValue == Int func readIfPresent() throws -> T? where T.RawValue == String func readTimestampIfPresent(format: TimestampFormat) throws -> Date? @@ -131,8 +131,8 @@ public extension SmithyReader { } } - func read() throws -> Document { - if let value: Document = try readIfPresent() { + func read() throws -> any SmithyDocument { + if let value: any SmithyDocument = try readIfPresent() { return value } else { throw ReaderError.requiredValueNotPresent @@ -208,4 +208,5 @@ public extension SmithyReader { public enum ReaderError: Error { case requiredValueNotPresent + case invalidSchema(String) } diff --git a/Sources/SmithyReadWrite/SmithyWriter.swift b/Sources/SmithyReadWrite/SmithyWriter.swift index 62f992122..7437b098c 100644 --- a/Sources/SmithyReadWrite/SmithyWriter.swift +++ b/Sources/SmithyReadWrite/SmithyWriter.swift @@ -28,7 +28,7 @@ public protocol SmithyWriter: AnyObject { func write(_ value: Int16?) throws func write(_ value: UInt8?) throws func write(_ value: Data?) throws - func write(_ value: SmithyDocument?) throws + func write(_ value: (any SmithyDocument)?) throws func writeTimestamp(_ value: Date?, format: TimestampFormat) throws func write(_ value: T?) throws where T.RawValue == Int func write(_ value: T?) throws where T.RawValue == String diff --git a/Sources/SmithyReadWrite/WritingClosure.swift b/Sources/SmithyReadWrite/WritingClosure.swift index fcc3fe20a..ac1074b99 100644 --- a/Sources/SmithyReadWrite/WritingClosure.swift +++ b/Sources/SmithyReadWrite/WritingClosure.swift @@ -103,7 +103,7 @@ public enum WritingClosures { try writer.write(value) } - public static func writeDocument(value: SmithyDocument?, to writer: Writer) throws { + public static func writeSmithyDocument(value: (any SmithyDocument)?, to writer: Writer) throws { try writer.write(value) } } diff --git a/Sources/SmithyXML/Reader/Reader.swift b/Sources/SmithyXML/Reader/Reader.swift index 85d3f3f73..7cb4a2ff7 100644 --- a/Sources/SmithyXML/Reader/Reader.swift +++ b/Sources/SmithyXML/Reader/Reader.swift @@ -6,7 +6,7 @@ // @_spi(SmithyReadWrite) import protocol SmithyReadWrite.SmithyReader -import struct Smithy.Document +import protocol Smithy.SmithyDocument @_spi(SmithyReadWrite) import typealias SmithyReadWrite.ReadingClosure import struct Foundation.Date import struct Foundation.Data @@ -114,7 +114,7 @@ public final class Reader: SmithyReader { return Data(base64Encoded: Data(content.utf8)) } - public func readIfPresent() throws -> Document? { + public func readIfPresent() throws -> (any SmithyDocument)? { // No operation. Smithy document not supported in XML return nil } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/DirectedSwiftCodegen.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/DirectedSwiftCodegen.kt index a68c9c159..37c459ef7 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/DirectedSwiftCodegen.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/DirectedSwiftCodegen.kt @@ -80,6 +80,7 @@ class DirectedSwiftCodegen(val context: PluginContext) : generateMessageMarshallable(ctx) generateMessageUnmarshallable(ctx) generateCodableConformanceForNestedTypes(ctx) + generateSchemas(ctx) initializeMiddleware(ctx) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt index 88dcd287b..8f0918043 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt @@ -26,6 +26,7 @@ import software.amazon.smithy.swift.codegen.model.nestedNamespaceType import software.amazon.smithy.swift.codegen.model.toLowerCamelCase import software.amazon.smithy.swift.codegen.swiftmodules.ClientRuntimeTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SwiftTypes import software.amazon.smithy.swift.codegen.utils.errorShapeName import software.amazon.smithy.swift.codegen.utils.toUpperCamelCase @@ -117,11 +118,15 @@ class StructureGenerator( writer.writeShapeDocs(shape) writer.writeAvailableAttribute(model, shape) val equatableConformance = writer.format(", \$N", SwiftTypes.Protocols.Equatable).takeIf { shape.hasTrait() } ?: "" - writer.openBlock("public struct \$struct.name:L: \$N$equatableConformance {", SwiftTypes.Protocols.Sendable) - .call { generateStructMembers() } - .write("") - .call { generateInitializerForStructure(false) } - .closeBlock("}") + writer.openBlock( + "public struct \$struct.name:L: \$N, \$N$equatableConformance {", "}", + SmithyReadWriteTypes.DeserializableShape, + SwiftTypes.Protocols.Sendable, + ) { + generateStructMembers() + writer.write("") + generateInitializerForStructure(false) + } } private fun generateStructMembers() { @@ -148,7 +153,7 @@ class StructureGenerator( val (memberName, memberSymbol) = memberShapeDataContainer.getOrElse(member) { Pair(null, null) } if (memberName == null || memberSymbol == null) continue val terminator = if (index == membersSortedByName.size - 1) "" else "," - writer.write("\$L: \$D$terminator", memberName, memberSymbol) + writer.write("\$L: \$T = \$D\$L", memberName, memberSymbol, memberSymbol, terminator) } } writer.openBlock("{", "}") { @@ -158,6 +163,19 @@ class StructureGenerator( writer.write("self.$path\$L = \$L", memberName, memberName) } } + writer.write("") + if (error) { + writer.write("public init() {}") + } else { + writer.openBlock("public init() {", "}") { + membersSortedByName.forEach { member -> + val (memberName, memberSymbol) = memberShapeDataContainer.getOrElse(member) { Pair(null, null) } + if (memberName != null && memberSymbol != null) { + writer.write("self.\$L = \$D", memberName, memberSymbol) + } + } + } + } } else { writer.write("public init() { }") } @@ -242,7 +260,7 @@ class StructureGenerator( writer.writeAvailableAttribute(model, it) val targetShape = model.expectShape(it.target) val boxedOrNot = "@Boxed ".takeIf { targetShape.hasTrait() } - writer.write("\$Lpublic internal(set) var \$L: \$D", boxedOrNot, memberName, memberSymbol) + writer.write("\$Lpublic internal(set) var \$L: \$T = \$D", boxedOrNot, memberName, memberSymbol, memberSymbol) } } writer.write("") diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt index 4942e639b..d6dafff51 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt @@ -242,7 +242,7 @@ class SwiftSymbolProvider(private val model: Model, val swiftSettings: SwiftSett } override fun documentShape(shape: DocumentShape): Symbol { - return createSymbolBuilder(shape, "Document", "Smithy", SwiftDeclaration.STRUCT, true) + return createSymbolBuilder(shape, "SmithyDocument", "Smithy", SwiftDeclaration.PROTOCOL, true) .addDependency(SwiftDependency.SMITHY_READ_WRITE) .build() } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftWriter.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftWriter.kt index ed8bc8e06..d052c7517 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftWriter.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftWriter.kt @@ -193,7 +193,7 @@ class SwiftWriter( if (shouldSetDefault) { getDefaultValue(type)?.let { - formatted += " = $it" + formatted = "$it" } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/UnionGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/UnionGenerator.kt index 1cd21c3b2..9af1d0ad9 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/UnionGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/UnionGenerator.kt @@ -18,6 +18,7 @@ import software.amazon.smithy.swift.codegen.model.eventStreamEvents import software.amazon.smithy.swift.codegen.model.expectShape import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.model.nestedNamespaceType +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SwiftTypes /** @@ -78,7 +79,7 @@ class UnionGenerator( writer.writeAvailableAttribute(model, shape) val indirectOrNot = "indirect ".takeIf { shape.hasTrait() } ?: "" val equatableConformance = writer.format(", \$N", SwiftTypes.Protocols.Equatable).takeIf { shape.hasTrait() } ?: "" - writer.openBlock("public ${indirectOrNot}enum \$union.name:L: \$N$equatableConformance {", "}", SwiftTypes.Protocols.Sendable) { + writer.openBlock("public ${indirectOrNot}enum \$union.name:L: \$N, \$N$equatableConformance {", "}", SwiftTypes.Protocols.Sendable, SmithyReadWriteTypes.DeserializableShape) { // event streams (@streaming union) MAY have variants that target errors. // These errors if encountered on the stream will be thrown as an exception rather // than showing up as one of the possible events the consumer will see on the stream (AsyncThrowingStream). @@ -92,6 +93,8 @@ class UnionGenerator( } // add the sdkUnknown case which will always be last writer.write("case sdkUnknown(\$N)", SwiftTypes.String) + writer.write("") + writer.write("public init() { self = .sdkUnknown(\"\") }") } } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt index ed0ede05a..cb2228e11 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt @@ -57,7 +57,7 @@ class EndpointParamsGenerator( val memberName = param.name.toString().toLowerCamelCase() val memberSymbol = param.toSymbol() val terminator = if (index != parameters.lastIndex) "," else "" - writer.write("$memberName: \$D$terminator", memberSymbol) + writer.write("$memberName: \$T = \$D$terminator", memberSymbol, memberSymbol) } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt index 0346bc967..86c44c746 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt @@ -18,6 +18,7 @@ import software.amazon.smithy.model.shapes.MemberShape import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.shapes.ShapeType import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.TimestampShape @@ -57,6 +58,9 @@ import software.amazon.smithy.swift.codegen.integration.middlewares.SignerMiddle import software.amazon.smithy.swift.codegen.integration.middlewares.providers.HttpHeaderProvider import software.amazon.smithy.swift.codegen.integration.middlewares.providers.HttpQueryItemProvider import software.amazon.smithy.swift.codegen.integration.middlewares.providers.HttpUrlPathProvider +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.WireProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.responseWireProtocol +import software.amazon.smithy.swift.codegen.integration.serde.schema.SchemaGenerator import software.amazon.smithy.swift.codegen.integration.serde.struct.StructDecodeGenerator import software.amazon.smithy.swift.codegen.integration.serde.struct.StructEncodeGenerator import software.amazon.smithy.swift.codegen.integration.serde.union.UnionDecodeGenerator @@ -207,6 +211,28 @@ abstract class HTTPBindingProtocolGenerator( } } + override fun generateSchemas(ctx: ProtocolGenerator.GenerationContext) { + if (ctx.service.responseWireProtocol != WireProtocol.JSON) { return } + val nestedShapes = resolveShapesNeedingSchema(ctx) + .filter { !it.isStreaming } + for (shape in nestedShapes) { + renderSchemas(ctx, shape) + } + } + + private fun renderSchemas(ctx: ProtocolGenerator.GenerationContext, shape: Shape) { + val symbol: Symbol = ctx.symbolProvider.toSymbol(shape) + val symbolName = symbol.name + val filename = ModelFileUtils.filename(ctx.settings, "$symbolName+Schema") + val encodeSymbol = Symbol.builder() + .definitionFile(filename) + .name(symbolName) + .build() + ctx.delegator.useShapeWriter(encodeSymbol) { writer -> + SchemaGenerator(ctx, writer).renderSchemas(shape) + } + } + fun renderCodableExtension( ctx: ProtocolGenerator.GenerationContext, shape: Shape, @@ -322,6 +348,46 @@ abstract class HTTPBindingProtocolGenerator( return nestedTypes } + private fun resolveShapesNeedingSchema(ctx: ProtocolGenerator.GenerationContext): Set { + val topLevelOutputMembers = getHttpBindingOperations(ctx).flatMap { + val outputShape = ctx.model.expectShape(it.output.get()) + outputShape.members() + } + .map { ctx.model.expectShape(it.target) } +// .filter { it.isStructureShape || it.isUnionShape || it is CollectionShape || it.isMapShape } + .toSet() + +// val topLevelErrorMembers = getHttpBindingOperations(ctx) +// .flatMap { it.errors } +// .flatMap { ctx.model.expectShape(it).members() } +// .map { ctx.model.expectShape(it.target) } +// .filter { it.isStructureShape || it.isUnionShape || it is CollectionShape || it.isMapShape } +// .toSet() + +// val topLevelServiceErrorMembers = ctx.service.errors +// .flatMap { ctx.model.expectShape(it).members() } +// .map { ctx.model.expectShape(it.target) } +// .filter { it.isStructureShape || it.isUnionShape || it is CollectionShape || it.isMapShape } +// .toSet() + +// val topLevelInputMembers = getHttpBindingOperations(ctx).flatMap { +// val inputShape = ctx.model.expectShape(it.input.get()) +// inputShape.members() +// } +// .map { ctx.model.expectShape(it.target) } +// .filter { it.isStructureShape || it.isUnionShape || it is CollectionShape || it.isMapShape } +// .toSet() + + val allTopLevelMembers = + topLevelOutputMembers +// .union(topLevelErrorMembers) +// .union(topLevelServiceErrorMembers) +// .union(topLevelInputMembers) + + val nestedTypes = walkNestedShapesRequiringSchema(ctx, allTopLevelMembers) + return nestedTypes + } + private fun walkNestedShapesRequiringSerde(ctx: ProtocolGenerator.GenerationContext, shapes: Set): Set { val resolved = mutableSetOf() val walker = Walker(ctx.model) @@ -350,6 +416,38 @@ abstract class HTTPBindingProtocolGenerator( return resolved } + private fun walkNestedShapesRequiringSchema(ctx: ProtocolGenerator.GenerationContext, shapes: Set): Set { + val resolved = mutableSetOf() + val walker = Walker(ctx.model) + + // walk all the shapes in the set and find all other + // structs/unions (or collections thereof) in the graph from that shape + shapes.forEach { shape -> + walker.iterateShapes(shape) { relationship -> + when (relationship.relationshipType) { + RelationshipType.MEMBER_TARGET, + RelationshipType.STRUCTURE_MEMBER, + RelationshipType.LIST_MEMBER, + RelationshipType.SET_MEMBER, + RelationshipType.MAP_KEY, + RelationshipType.MAP_VALUE, + RelationshipType.UNION_MEMBER, + -> true + else -> false + } + }.forEach { + if (it.type != ShapeType.MEMBER && it.id.namespace != "smithy.api" && !it.hasTrait()) { + resolved.add(it) + } +// when (it) { +// is UnionShape -> resolved.add(it) +// is StructureShape -> resolved.add(it) +// } + } + } + return resolved + } + // Checks for @requiresLength trait // Returns true if the operation: // - has a streaming member with @httpPayload trait diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/ProtocolGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/ProtocolGenerator.kt index eecd0c585..0527c8797 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/ProtocolGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/ProtocolGenerator.kt @@ -117,6 +117,8 @@ interface ProtocolGenerator { */ fun generateCodableConformanceForNestedTypes(ctx: GenerationContext) + fun generateSchemas(ctx: GenerationContext) + /** * * Generate unit tests for the protocol diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt new file mode 100644 index 000000000..d0a9452f6 --- /dev/null +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt @@ -0,0 +1,259 @@ +package software.amazon.smithy.swift.codegen.integration.serde.schema + +import software.amazon.smithy.codegen.core.Symbol +import software.amazon.smithy.model.shapes.MemberShape +import software.amazon.smithy.model.shapes.ServiceShape +import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.shapes.ShapeType +import software.amazon.smithy.model.traits.DefaultTrait +import software.amazon.smithy.model.traits.JsonNameTrait +import software.amazon.smithy.model.traits.RequiredTrait +import software.amazon.smithy.model.traits.SparseTrait +import software.amazon.smithy.model.traits.TimestampFormatTrait +import software.amazon.smithy.model.traits.XmlNameTrait +import software.amazon.smithy.swift.codegen.SwiftWriter +import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator +import software.amazon.smithy.swift.codegen.model.getTrait +import software.amazon.smithy.swift.codegen.model.hasTrait +import software.amazon.smithy.swift.codegen.model.nestedNamespaceType +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTimestampsTypes +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTypes +import software.amazon.smithy.swift.codegen.swiftmodules.SwiftTypes +import kotlin.jvm.optionals.getOrNull + +class SchemaGenerator( + val ctx: ProtocolGenerator.GenerationContext, + val writer: SwiftWriter, +) { + + fun renderSchemas(shape: Shape) { + writer.writeInline("let \$L = ", schemaVar(writer, shape.id)) + renderSingleSchema(shape) + writer.unwrite(",\n") + writer.write("") + writer.write("") + } + + private fun renderSingleSchema(shape: Shape) { + val swiftType = swiftSymbolFor(ctx, shape) + writer.openBlock( + "\$N<\$N>(", + "),", + schemaType(shape), + swiftType, + ) { +// writer.write("namespace: \$S,", shape.id.namespace) +// writer.write("name: \$S,", shape.id.name) + writer.write("type: .\$L,", shape.type) + when (shape.type) { + ShapeType.STRUCTURE -> { + if (shape.members().isNotEmpty()) { + writer.openBlock("members: [", "],") { + shape.members().forEach { member -> + writer.openBlock(".init(", "),") { + writer.writeInline("memberSchema: ") + renderSingleSchema(member) + writer.write("targetSchema: \$L,", schemaVar(writer, member.target)) + writeSetterGetter(ctx, writer, shape, member) + } + } + writer.unwrite(",\n") + writer.write("") + } + } + } + ShapeType.LIST, ShapeType.SET -> { + val member = shape.members().first { it.memberName == "member" } + val target = ctx.model.expectShape(member.target) + writer.writeInline("memberSchema: ") + renderSingleSchema(member) + writer.write("targetSchema: \$L,", schemaVar(writer, target.id)) + writeSetterGetter(ctx, writer, shape, member) + } + ShapeType.MAP -> { + val keyMember = shape.members().first { it.memberName == "key" } + val keyTarget = keyMember.let { ctx.model.expectShape(it.target) } + val valueMember = shape.members().first { it.memberName == "value" } + val valueTarget = valueMember.let { ctx.model.expectShape(it.target) } + writer.writeInline("keyMemberSchema: ") + renderSingleSchema(keyMember) + writer.write("keyTargetSchema: \$L,", schemaVar(writer, keyTarget.id)) + writer.writeInline("valueMemberSchema: ") + renderSingleSchema(valueMember) + writer.write("valueTargetSchema: \$L,", schemaVar(writer, valueTarget.id)) + writeSetterGetter(ctx, writer, shape, valueMember) + } + ShapeType.MEMBER -> { + shape.id.member.getOrNull()?.let { writer.write("memberName: \$S,", it) } + jsonName(shape)?.let { writer.write("jsonName: \$S,", it) } + xmlName(shape)?.let { writer.write("xmlName: \$S,", it) } + if (isRequired(shape)) { + writer.write("isRequired: true,") + } + defaultValue(shape)?.let { writer.write("defaultValue: \$N(value: \$S),", SmithyTypes.StringDocument, it) } + } + else -> { + timestampFormat(shape)?.let { + writer.addImport(SmithyTimestampsTypes.TimestampFormat) + writer.write("timestampFormat: .\$L", it.swiftEnumCase) + } + defaultValue(shape)?.let { writer.write("defaultValue: \$N(value: \$S),", SmithyTypes.StringDocument, it) } + } + } + writer.unwrite(",\n") + writer.write("") + } + } + + private fun schemaType(shape: Shape): Symbol { + return when (shape.type) { + ShapeType.STRUCTURE, ShapeType.UNION -> SmithyReadWriteTypes.StructureSchema + ShapeType.LIST, ShapeType.SET -> SmithyReadWriteTypes.ListSchema + ShapeType.MAP -> SmithyReadWriteTypes.MapSchema + ShapeType.MEMBER -> SmithyReadWriteTypes.MemberSchema + else -> SmithyReadWriteTypes.SimpleSchema + } + } + + private fun swiftSymbolFor(ctx: ProtocolGenerator.GenerationContext, shape: Shape): Symbol { + return swiftSymbolWithOptionalRecursion(ctx, shape, true) + } + + private fun swiftSymbolWithOptionalRecursion(ctx: ProtocolGenerator.GenerationContext, shape: Shape, recurse: Boolean): Symbol { + val service = ctx.model.getShape(ctx.settings.service).get() as ServiceShape + return when (shape.type) { + ShapeType.STRUCTURE, ShapeType.UNION -> { + if (shape.id.namespace == "smithy.api" && shape.id.name == "Unit") { + return SwiftTypes.Void + } else { + val symbol = ctx.symbolProvider.toSymbol(shape) + return symbol.toBuilder().namespace(service.nestedNamespaceType(ctx.symbolProvider).name, ".") + .build() + } + } + ShapeType.LIST, ShapeType.SET -> { + val target = shape.members() + .first { it.memberName == "member" } + ?.let { ctx.model.expectShape(it.target) } + ?: run { throw Exception("List / set does not have target") } + val innerSymbol = swiftSymbolWithOptionalRecursion(ctx, target, false) + if (recurse) { + return innerSymbol + } else { + return Symbol.builder().namespace("Swift", ".").name("Array<${innerSymbol}>").build() + } + } + ShapeType.MAP -> { + val target = shape.members() + .first { it.memberName == "value" } + ?.let { ctx.model.expectShape(it.target) } + ?: run { throw Exception("Map does not have target") } + val innerSymbol = swiftSymbolWithOptionalRecursion(ctx, target, false) + if (recurse) { + return innerSymbol + } else { + return Symbol.builder().namespace("Swift", ".").name("Dictionary").build() + } + } + ShapeType.MEMBER -> { + val memberShape = shape as MemberShape + val target = ctx.model.expectShape(memberShape.target) + return swiftSymbolWithOptionalRecursion(ctx, target, false) + } + else -> { + return ctx.symbolProvider.toSymbol(shape) + } + } + } + + private fun writeSetterGetter(ctx: ProtocolGenerator.GenerationContext, writer: SwiftWriter, shape: Shape, member: MemberShape) { + val target = ctx.model.expectShape(member.target) + val readMethodName = when (target.type) { + ShapeType.BLOB -> "readBlob" + ShapeType.BOOLEAN -> "readBoolean" + ShapeType.STRING -> "readString" + ShapeType.ENUM -> "readEnum" + ShapeType.TIMESTAMP -> "readTimestamp" + ShapeType.BYTE -> "readByte" + ShapeType.SHORT -> "readShort" + ShapeType.INTEGER -> "readInteger" + ShapeType.INT_ENUM -> "readIntEnum" + ShapeType.LONG -> "readLong" + ShapeType.FLOAT -> "readFloat" + ShapeType.DOCUMENT -> "readDocument" + ShapeType.DOUBLE -> "readDouble" + ShapeType.BIG_DECIMAL -> "readBigDecimal" + ShapeType.BIG_INTEGER -> "readBigInteger" + ShapeType.LIST, ShapeType.SET -> "readList" + ShapeType.MAP -> "readMap" + ShapeType.STRUCTURE, ShapeType.UNION -> "readStruct" + ShapeType.MEMBER, ShapeType.SERVICE, ShapeType.RESOURCE, ShapeType.OPERATION, null -> + throw Exception("Unsupported member target type: ${target.type}") + } + when (shape.type) { + ShapeType.STRUCTURE -> { + writer.write("readBlock: { \$\$0.\$L = try \$\$1.\$L(schema: \$L) },", ctx.symbolProvider.toMemberName(member), readMethodName, schemaVar(writer, target.id)) + writer.write("writeBlock: { _, _ in }") + } + ShapeType.UNION -> { + writer.write("readBlock: { \$\$0 = .\$L(try \$\$1.\$LNonNull(schema: \$L)) },", ctx.symbolProvider.toMemberName(member), readMethodName, schemaVar(writer, target.id)) + writer.write("writeBlock: { _, _ in }") + } + ShapeType.LIST, ShapeType.SET, ShapeType.MAP -> { + val nonNullModifier = "NonNull".takeIf { !shape.hasTrait() } ?: "" + writer.write("readBlock: { try \$\$0.\$L\$L(schema: \$L) },", readMethodName, nonNullModifier, schemaVar(writer, target.id)) + writer.write("writeBlock: { _, _ in }") + } + else -> {} + } + } + + private fun jsonName(shape: Shape): String? { + return shape.getTrait()?.value + } + + private fun xmlName(shape: Shape): String? { + return shape.getTrait()?.value + } + + private fun isRequired(shape: Shape): Boolean { + return shape.hasTrait() + } + + private fun defaultValue(shape: Shape): String? { + return shape.getTrait()?.let { it.toNode().asStringNode().getOrNull()?.toString() } + } + + private fun timestampFormat(shape: Shape): TimestampFormatTrait.Format? { + return shape.getTrait()?.format + } + + fun schemaVar(writer: SwiftWriter, shapeId: ShapeId): String { + return when (Pair(shapeId.namespace, shapeId.name)) { + Pair("smithy.api", "Unit") -> writer.format("\$N", SmithyReadWriteTypes.unitSchema) + Pair("smithy.api", "Boolean") -> writer.format("\$N", SmithyReadWriteTypes.booleanSchema) + Pair("smithy.api", "Integer") -> writer.format("\$N", SmithyReadWriteTypes.integerSchema) + Pair("smithy.api", "Float") -> writer.format("\$N", SmithyReadWriteTypes.floatSchema) + Pair("smithy.api", "Double") -> writer.format("\$N", SmithyReadWriteTypes.doubleSchema) + Pair("smithy.api", "String") -> writer.format("\$N", SmithyReadWriteTypes.stringSchema) + Pair("smithy.api", "Document") -> writer.format("\$N", SmithyReadWriteTypes.documentSchema) + else -> { + val namespacePortion = shapeId.namespace.replace(".", "_") + val memberPortion = shapeId.member.getOrNull()?.let { "_member_$it" } ?: "" + "${namespacePortion}__${shapeId.name}_schema${memberPortion}" + } + } + } + + private val TimestampFormatTrait.Format.swiftEnumCase: String + get() { + return when (this) { + TimestampFormatTrait.Format.EPOCH_SECONDS -> "epochSeconds" + TimestampFormatTrait.Format.DATE_TIME -> "dateTime" + TimestampFormatTrait.Format.HTTP_DATE -> "httpDate" + else -> throw Exception("Unknown TimestampFormat") + } + } +} \ No newline at end of file diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt index d7ab434df..2be8e927b 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt @@ -98,6 +98,12 @@ object ClientRuntimeTypes { val InterceptorProviders = runtimeSymbol("[ClientRuntime.InterceptorProvider]", null, listOf(InterceptorProvider)) val HttpInterceptorProviders = runtimeSymbol("[ClientRuntime.HttpInterceptorProvider]", null, listOf(HttpInterceptorProvider)) } + + object Model { + val StructureMember = runtimeSymbol("StructureMember", SwiftDeclaration.STRUCT) + val UnionMember = runtimeSymbol("UnionMember", SwiftDeclaration.STRUCT) + val EnumMember = runtimeSymbol("EnumMember", SwiftDeclaration.STRUCT) + } } private fun runtimeSymbol( diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt index 939892200..38d747e57 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt @@ -26,6 +26,20 @@ object SmithyReadWriteTypes { val WritingClosures = runtimeSymbol("WritingClosures", SwiftDeclaration.ENUM) val ReadingClosureBox = runtimeSymbol("ReadingClosureBox", SwiftDeclaration.STRUCT) val WritingClosureBox = runtimeSymbol("WritingClosureBox", SwiftDeclaration.STRUCT) + val StructureSchema = runtimeSymbol("StructureSchema", SwiftDeclaration.STRUCT, listOf("SchemaBasedSerde")) + val ListSchema = runtimeSymbol("ListSchema", SwiftDeclaration.STRUCT, listOf("SchemaBasedSerde")) + val MapSchema = runtimeSymbol("MapSchema", SwiftDeclaration.STRUCT, listOf("SchemaBasedSerde")) + val SimpleSchema = runtimeSymbol("SimpleSchema", SwiftDeclaration.STRUCT, listOf("SchemaBasedSerde")) + val MemberSchema = runtimeSymbol("MemberSchema", SwiftDeclaration.STRUCT, listOf("SchemaBasedSerde")) + val Member = runtimeSymbol("Member", SwiftDeclaration.STRUCT, listOf("SchemaBasedSerde")) + val DeserializableShape = runtimeSymbol("DeserializableShape", SwiftDeclaration.PROTOCOL) + val unitSchema = runtimeSymbol("unitSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) + val booleanSchema = runtimeSymbol("booleanSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) + val integerSchema = runtimeSymbol("integerSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) + val floatSchema = runtimeSymbol("floatSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) + val doubleSchema = runtimeSymbol("doubleSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) + val stringSchema = runtimeSymbol("stringSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) + val documentSchema = runtimeSymbol("documentSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) } private fun runtimeSymbol( diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyTypes.kt index 713ac236d..33f8f262c 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyTypes.kt @@ -21,12 +21,18 @@ object SmithyTypes { val LogAgent = runtimeSymbol("LogAgent", SwiftDeclaration.PROTOCOL) val RequestMessageSerializer = runtimeSymbol("RequestMessageSerializer", SwiftDeclaration.PROTOCOL) val URIQueryItem = runtimeSymbol("URIQueryItem", SwiftDeclaration.STRUCT) + val StringDocument = runtimeSymbol("StringDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) } -private fun runtimeSymbol(name: String, declaration: SwiftDeclaration? = null): Symbol = SwiftSymbol.make( +private fun runtimeSymbol( + name: String, + declaration: SwiftDeclaration?, + additionalImports: List = emptyList(), + spiName: List = emptyList(), +): Symbol = SwiftSymbol.make( name, declaration, - SwiftDependency.SMITHY, - emptyList(), - emptyList(), + SwiftDependency.SMITHY.takeIf { additionalImports.isEmpty() }, + additionalImports, + spiName, ) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SwiftTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SwiftTypes.kt index 9045179fd..7df4a5ce7 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SwiftTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SwiftTypes.kt @@ -10,6 +10,7 @@ import software.amazon.smithy.swift.codegen.SwiftDeclaration import software.amazon.smithy.swift.codegen.SwiftDependency object SwiftTypes { + val Void = builtInSymbol("Void", SwiftDeclaration.STRUCT) val String = builtInSymbol("String", SwiftDeclaration.STRUCT) val Int = builtInSymbol("Int", SwiftDeclaration.STRUCT) val Int8 = builtInSymbol("Int8", SwiftDeclaration.STRUCT) From c7545456bcb1471fc6c6322a50c3741b62946f9d Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Tue, 24 Dec 2024 14:12:51 -0600 Subject: [PATCH 02/16] Protocol tests compile --- .../Reader/Reader+ShapeDeserializer.swift | 31 ++-- Sources/SmithyJSON/Reader/Reader.swift | 3 +- Sources/SmithyReadWrite/Schema/Prelude.swift | 37 ++++ Sources/SmithyReadWrite/Schema/Schema.swift | 44 ++--- .../SmithyReadWrite/ShapeDeserializer.swift | 28 ++- Sources/SmithyReadWrite/SmithyReader.swift | 8 +- Sources/SmithyReadWrite/WritingClosure.swift | 4 +- Sources/SmithyXML/Reader/Reader.swift | 4 +- .../swift/codegen/StructureGenerator.kt | 3 +- .../swift/codegen/SwiftSymbolProvider.kt | 4 +- .../events/MessageMarshallableGenerator.kt | 2 +- .../events/MessageUnmarshallableGenerator.kt | 14 +- .../HTTPBindingProtocolGenerator.kt | 27 +-- .../member/MemberShapeDecodeGenerator.kt | 45 +++++ .../serde/schema/SchemaGenerator.kt | 170 ++++++++++-------- .../codegen/swiftmodules/SmithyJSONTypes.kt | 4 +- .../swiftmodules/SmithyReadWriteTypes.kt | 17 +- .../swift/codegen/swiftmodules/SmithyTypes.kt | 14 ++ 18 files changed, 297 insertions(+), 162 deletions(-) diff --git a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift index 778b546a7..85574a13f 100644 --- a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift +++ b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift @@ -7,34 +7,25 @@ import struct Foundation.Data import struct Foundation.Date -import protocol Smithy.SmithyDocument +import struct Smithy.Document import enum SmithyReadWrite.ReaderError @_spi(SchemaBasedSerde) import protocol SmithyReadWrite.ShapeDeserializer @_spi(SchemaBasedSerde) import protocol SmithyReadWrite.SchemaProtocol @_spi(SchemaBasedSerde) import protocol SmithyReadWrite.DeserializableShape -@_spi(SchemaBasedSerde) import struct SmithyReadWrite.StructureSchema -@_spi(SchemaBasedSerde) import struct SmithyReadWrite.ListSchema -@_spi(SchemaBasedSerde) import struct SmithyReadWrite.MapSchema -@_spi(SchemaBasedSerde) import struct SmithyReadWrite.Member +@_spi(SchemaBasedSerde) import class SmithyReadWrite.StructureSchema +@_spi(SchemaBasedSerde) import class SmithyReadWrite.ListSchema +@_spi(SchemaBasedSerde) import class SmithyReadWrite.MapSchema +@_spi(SchemaBasedSerde) import class SmithyReadWrite.SimpleSchema @_spi(SmithyTimestamps) import enum SmithyTimestamps.TimestampFormat +@_spi(SchemaBasedSerde) extension Reader: SmithyReadWrite.ShapeDeserializer { - public func readStruct( + public func readStructure( schema: SmithyReadWrite.StructureSchema ) throws -> Base? { - var value = Base() - try schema.members.forEach { member in - try fillMember(value: &value, member: member) - } - return value - } - - private func fillMember(value: inout T, member: SmithyReadWrite.Member) throws { - guard let resolvedName = member.memberSchema.jsonName ?? member.memberSchema.memberName else { - throw ReaderError.invalidSchema("Could not resolve member name") - } - try member.setter(&value, self[NodeInfo(resolvedName)]) + // TODO: Implement me + return Base() } public func readString(schema: SmithyReadWrite.SchemaProtocol) throws -> String? { @@ -91,12 +82,12 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { try readIfPresent() } - public func readTimestamp(schema: any SmithyReadWrite.SchemaProtocol) throws -> Date? { + public func readTimestamp(schema: SmithyReadWrite.SimpleSchema) throws -> Date? { // TODO: Implement me try readTimestampIfPresent(format: schema.timestampFormat ?? .epochSeconds) } - public func readDocument(schema: any SmithyReadWrite.SchemaProtocol) throws -> (any Smithy.SmithyDocument)? { + public func readDocument(schema: any SmithyReadWrite.SchemaProtocol) throws -> Document? { // TODO: Implement me nil } diff --git a/Sources/SmithyJSON/Reader/Reader.swift b/Sources/SmithyJSON/Reader/Reader.swift index 9436bcd79..f84e06c6b 100644 --- a/Sources/SmithyJSON/Reader/Reader.swift +++ b/Sources/SmithyJSON/Reader/Reader.swift @@ -6,7 +6,6 @@ // @_spi(SmithyReadWrite) import protocol SmithyReadWrite.SmithyReader -import protocol Smithy.SmithyDocument import struct Smithy.Document import typealias SmithyReadWrite.ReadingClosure import enum SmithyReadWrite.ReaderError @@ -152,7 +151,7 @@ public extension Reader { } } - func readIfPresent() throws -> (any SmithyDocument)? { + func readIfPresent() throws -> Document? { guard let jsonObject = self.jsonObject else { return nil } return try Document.make(from: jsonObject) } diff --git a/Sources/SmithyReadWrite/Schema/Prelude.swift b/Sources/SmithyReadWrite/Schema/Prelude.swift index 8a125a005..febe05858 100644 --- a/Sources/SmithyReadWrite/Schema/Prelude.swift +++ b/Sources/SmithyReadWrite/Schema/Prelude.swift @@ -5,6 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // +import struct Foundation.Data +import struct Foundation.Date import protocol Smithy.SmithyDocument @_spi(SchemaBasedSerde) @@ -21,6 +23,20 @@ public let booleanSchema = SimpleSchema( type: .boolean ) +@_spi(SchemaBasedSerde) +public let byteSchema = SimpleSchema( + namespace: "smithy.api", + name: "Byte", + type: .byte +) + +@_spi(SchemaBasedSerde) +public let shortSchema = SimpleSchema( + namespace: "smithy.api", + name: "Short", + type: .short +) + @_spi(SchemaBasedSerde) public let integerSchema = SimpleSchema( namespace: "smithy.api", @@ -28,6 +44,13 @@ public let integerSchema = SimpleSchema( type: .integer ) +@_spi(SchemaBasedSerde) +public let longSchema = SimpleSchema( + namespace: "smithy.api", + name: "Long", + type: .long +) + @_spi(SchemaBasedSerde) public let floatSchema = SimpleSchema( namespace: "smithy.api", @@ -55,3 +78,17 @@ public let documentSchema = SimpleSchema( name: "Document", type: .document ) + +@_spi(SchemaBasedSerde) +public let blobSchema = SimpleSchema( + namespace: "smithy.api", + name: "Blob", + type: .blob +) + +@_spi(SchemaBasedSerde) +public let timestampSchema = SimpleSchema( + namespace: "smithy.api", + name: "Timestamp", + type: .timestamp +) diff --git a/Sources/SmithyReadWrite/Schema/Schema.swift b/Sources/SmithyReadWrite/Schema/Schema.swift index 47744086a..b122cee8c 100644 --- a/Sources/SmithyReadWrite/Schema/Schema.swift +++ b/Sources/SmithyReadWrite/Schema/Schema.swift @@ -9,7 +9,7 @@ import Smithy @_spi(SmithyTimestamps) import SmithyTimestamps @_spi(SchemaBasedSerde) -public protocol SchemaProtocol { +public protocol SchemaProtocol: AnyObject { // var namespace: String { get } // var name: String { get } var type: ShapeType { get } @@ -24,17 +24,17 @@ public protocol SchemaProtocol { //} @_spi(SchemaBasedSerde) -public struct StructureSchema: SchemaProtocol { +public class StructureSchema: SchemaProtocol { public struct Member { - public let memberSchema: SchemaProtocol - public let targetSchema: SchemaProtocol + public let memberSchema: () -> SchemaProtocol + public let targetSchema: () -> SchemaProtocol public let readBlock: (inout Base, any ShapeDeserializer) throws -> Void public let writeBlock: (Base, any SmithyWriter) throws -> Void public init( - memberSchema: SchemaProtocol, - targetSchema: SchemaProtocol, + memberSchema: @escaping () -> SchemaProtocol, + targetSchema: @escaping () -> SchemaProtocol, readBlock: @escaping (inout Base, any ShapeDeserializer) throws -> Void, writeBlock: @escaping (Base, any SmithyWriter) throws -> Void ) { @@ -75,12 +75,12 @@ public struct StructureSchema: SchemaProtocol { } @_spi(SchemaBasedSerde) -public struct ListSchema: SchemaProtocol { +public class ListSchema: SchemaProtocol { // public let namespace: String // public let name: String public let type: ShapeType - public let memberSchema: SchemaProtocol - public let targetSchema: SchemaProtocol + public let memberSchema: () -> SchemaProtocol + public let targetSchema: () -> SchemaProtocol public let readBlock: (any ShapeDeserializer) throws -> Element public let writeBlock: (any SmithyWriter, Element) throws -> Void @@ -88,8 +88,8 @@ public struct ListSchema: SchemaProtocol { namespace: String = "", name: String = "", type: ShapeType, - memberSchema: SchemaProtocol, - targetSchema: SchemaProtocol, + memberSchema: @escaping () -> SchemaProtocol, + targetSchema: @escaping () -> SchemaProtocol, readBlock: @escaping (any ShapeDeserializer) throws -> Element, writeBlock: @escaping (any SmithyWriter, Element) throws -> Void ) { @@ -112,14 +112,14 @@ public struct ListSchema: SchemaProtocol { } @_spi(SchemaBasedSerde) -public struct MapSchema: SchemaProtocol { +public class MapSchema: SchemaProtocol { // public let namespace: String // public let name: String public let type: ShapeType - public let keyMemberSchema: SchemaProtocol - public let keyTargetSchema: SchemaProtocol - public let valueMemberSchema: SchemaProtocol - public let valueTargetSchema: SchemaProtocol + public let keyMemberSchema: () -> SchemaProtocol + public let keyTargetSchema: () -> SchemaProtocol + public let valueMemberSchema: () -> SchemaProtocol + public let valueTargetSchema: () -> SchemaProtocol public let readBlock: (any ShapeDeserializer) throws -> Value public let writeBlock: (any SmithyWriter, Value) throws -> Void @@ -127,10 +127,10 @@ public struct MapSchema: SchemaProtocol { namespace: String = "", name: String = "", type: ShapeType, - keyMemberSchema: SchemaProtocol, - keyTargetSchema: SchemaProtocol, - valueMemberSchema: SchemaProtocol, - valueTargetSchema: SchemaProtocol, + keyMemberSchema: @escaping () -> SchemaProtocol, + keyTargetSchema: @escaping () -> SchemaProtocol, + valueMemberSchema: @escaping () -> SchemaProtocol, + valueTargetSchema: @escaping () -> SchemaProtocol, readBlock: @escaping (any ShapeDeserializer) throws -> Value, writeBlock: @escaping (any SmithyWriter, Value) throws -> Void ) { @@ -155,7 +155,7 @@ public struct MapSchema: SchemaProtocol { } @_spi(SchemaBasedSerde) -public struct MemberSchema: SchemaProtocol { +public class MemberSchema: SchemaProtocol { // public let namespace: String // public let name: String public let type: ShapeType @@ -195,7 +195,7 @@ public struct MemberSchema: SchemaProtocol { } @_spi(SchemaBasedSerde) -public struct SimpleSchema: SchemaProtocol { +public class SimpleSchema: SchemaProtocol { // public let namespace: String // public let name: String public let type: ShapeType diff --git a/Sources/SmithyReadWrite/ShapeDeserializer.swift b/Sources/SmithyReadWrite/ShapeDeserializer.swift index 444b223c5..9e79d7a74 100644 --- a/Sources/SmithyReadWrite/ShapeDeserializer.swift +++ b/Sources/SmithyReadWrite/ShapeDeserializer.swift @@ -5,13 +5,13 @@ // SPDX-License-Identifier: Apache-2.0 // -import protocol Smithy.SmithyDocument +import struct Smithy.Document import struct Foundation.Data import struct Foundation.Date @_spi(SchemaBasedSerde) public protocol ShapeDeserializer { - func readStruct(schema: StructureSchema) throws -> T? + func readStructure(schema: StructureSchema) throws -> T? func readList(schema: ListSchema) throws -> [T]? func readMap(schema: MapSchema) throws -> [String: T]? func readBoolean(schema: SchemaProtocol) throws -> Bool? @@ -25,8 +25,8 @@ public protocol ShapeDeserializer { func readBigDecimal(schema: SchemaProtocol) throws -> Float? func readString(schema: SchemaProtocol) throws -> String? func readBlob(schema: SchemaProtocol) throws -> Data? - func readTimestamp(schema: SchemaProtocol) throws -> Date? - func readDocument(schema: SchemaProtocol) throws -> SmithyDocument? + func readTimestamp(schema: SimpleSchema) throws -> Date? + func readDocument(schema: SchemaProtocol) throws -> Document? func readNull(schema: SchemaProtocol) throws -> Bool? } @@ -42,13 +42,27 @@ public extension ShapeDeserializer { guard let rawValue = try readInteger(schema: schema) else { return nil } return T(rawValue: rawValue) } + + func readEnumNonNull(schema: SchemaProtocol) throws -> T where T.RawValue == String { + guard let value: T = try readEnum(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readIntEnumNonNull(schema: SchemaProtocol) throws -> T where T.RawValue == Int { + guard let value: T = try readIntEnum(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } } @_spi(SchemaBasedSerde) public extension ShapeDeserializer { - func readStructNonNull(schema: StructureSchema) throws -> T { - guard let value = try readStruct(schema: schema) else { + func readStructureNonNull(schema: StructureSchema) throws -> T { + guard let value = try readStructure(schema: schema) else { throw ReaderError.requiredValueNotPresent } return value @@ -151,7 +165,7 @@ public extension ShapeDeserializer { return value } - func readDocumentNonNull(schema: SimpleSchema) throws -> any SmithyDocument { + func readDocumentNonNull(schema: SimpleSchema) throws -> Document { guard let value = try readDocument(schema: schema) else { throw ReaderError.requiredValueNotPresent } diff --git a/Sources/SmithyReadWrite/SmithyReader.swift b/Sources/SmithyReadWrite/SmithyReader.swift index 23c177b56..6ef6793e1 100644 --- a/Sources/SmithyReadWrite/SmithyReader.swift +++ b/Sources/SmithyReadWrite/SmithyReader.swift @@ -8,7 +8,7 @@ import struct Foundation.Data import struct Foundation.Date @_spi(SmithyTimestamps) import enum SmithyTimestamps.TimestampFormat -import protocol Smithy.SmithyDocument +import struct Smithy.Document @_spi(SmithyReadWrite) public protocol SmithyReader: AnyObject { @@ -27,7 +27,7 @@ public protocol SmithyReader: AnyObject { func readIfPresent() throws -> Double? func readIfPresent() throws -> Bool? func readIfPresent() throws -> Data? - func readIfPresent() throws -> (any SmithyDocument)? + func readIfPresent() throws -> Document? func readIfPresent() throws -> T? where T.RawValue == Int func readIfPresent() throws -> T? where T.RawValue == String func readTimestampIfPresent(format: TimestampFormat) throws -> Date? @@ -131,8 +131,8 @@ public extension SmithyReader { } } - func read() throws -> any SmithyDocument { - if let value: any SmithyDocument = try readIfPresent() { + func read() throws -> Document { + if let value: Document = try readIfPresent() { return value } else { throw ReaderError.requiredValueNotPresent diff --git a/Sources/SmithyReadWrite/WritingClosure.swift b/Sources/SmithyReadWrite/WritingClosure.swift index ac1074b99..9a7a95100 100644 --- a/Sources/SmithyReadWrite/WritingClosure.swift +++ b/Sources/SmithyReadWrite/WritingClosure.swift @@ -8,7 +8,7 @@ import struct Foundation.Data import struct Foundation.Date @_spi(SmithyTimestamps) import enum SmithyTimestamps.TimestampFormat -import protocol Smithy.SmithyDocument +import struct Smithy.Document @_spi(SmithyReadWrite) public typealias WritingClosure = (T, Writer) throws -> Void @@ -103,7 +103,7 @@ public enum WritingClosures { try writer.write(value) } - public static func writeSmithyDocument(value: (any SmithyDocument)?, to writer: Writer) throws { + public static func writeDocument(value: Document?, to writer: Writer) throws { try writer.write(value) } } diff --git a/Sources/SmithyXML/Reader/Reader.swift b/Sources/SmithyXML/Reader/Reader.swift index 7cb4a2ff7..85d3f3f73 100644 --- a/Sources/SmithyXML/Reader/Reader.swift +++ b/Sources/SmithyXML/Reader/Reader.swift @@ -6,7 +6,7 @@ // @_spi(SmithyReadWrite) import protocol SmithyReadWrite.SmithyReader -import protocol Smithy.SmithyDocument +import struct Smithy.Document @_spi(SmithyReadWrite) import typealias SmithyReadWrite.ReadingClosure import struct Foundation.Date import struct Foundation.Data @@ -114,7 +114,7 @@ public final class Reader: SmithyReader { return Data(base64Encoded: Data(content.utf8)) } - public func readIfPresent() throws -> (any SmithyDocument)? { + public func readIfPresent() throws -> Document? { // No operation. Smithy document not supported in XML return nil } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt index 8f0918043..7ed11eb9f 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt @@ -233,11 +233,12 @@ class StructureGenerator( writer.writeAvailableAttribute(model, shape) writer.openBlock( - "public struct \$struct.name:L: \$N, \$error.protocol:N, \$N, \$N, \$N {", + "public struct \$struct.name:L: \$N, \$error.protocol:N, \$N, \$N, \$N, \$N {", ClientRuntimeTypes.Core.ModeledError, ClientRuntimeTypes.Http.HttpError, SwiftTypes.Error, SwiftTypes.Protocols.Sendable, + SmithyReadWriteTypes.DeserializableShape, ) .call { generateErrorStructMembers() } .write("") diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt index d6dafff51..95a5dbe58 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt @@ -242,8 +242,8 @@ class SwiftSymbolProvider(private val model: Model, val swiftSettings: SwiftSett } override fun documentShape(shape: DocumentShape): Symbol { - return createSymbolBuilder(shape, "SmithyDocument", "Smithy", SwiftDeclaration.PROTOCOL, true) - .addDependency(SwiftDependency.SMITHY_READ_WRITE) + return createSymbolBuilder(shape, "Document", "Smithy", SwiftDeclaration.STRUCT, true) + .addDependency(SwiftDependency.SMITHY) .build() } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageMarshallableGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageMarshallableGenerator.kt index 0cc35b848..f435a58b9 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageMarshallableGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageMarshallableGenerator.kt @@ -45,7 +45,7 @@ class MessageMarshallableGenerator( "var headers: [\$N] = [.init(name: \":message-type\", value: .string(\"event\"))]", SmithyEventStreamsAPITypes.Header ) - write("var payload: \$D", FoundationTypes.Data) + write("var payload: \$1T = \$1D", FoundationTypes.Data) write("switch self {") streamShape.eventStreamEvents(ctx.model).forEach { member -> val memberName = ctx.symbolProvider.toMemberName(member) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt index 81bcb64e5..4f4c95097 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt @@ -11,6 +11,8 @@ import software.amazon.smithy.swift.codegen.SwiftWriter import software.amazon.smithy.swift.codegen.integration.HTTPProtocolCustomizable import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ReadingClosureUtils +import software.amazon.smithy.swift.codegen.integration.serde.schema.readMethodName +import software.amazon.smithy.swift.codegen.integration.serde.schema.schemaVar import software.amazon.smithy.swift.codegen.integration.serde.struct.readerSymbol import software.amazon.smithy.swift.codegen.model.eventStreamErrors import software.amazon.smithy.swift.codegen.model.eventStreamEvents @@ -18,6 +20,7 @@ import software.amazon.smithy.swift.codegen.model.expectShape import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.swiftmodules.SmithyEventStreamsAPITypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTypes import software.amazon.smithy.swift.codegen.swiftmodules.SwiftTypes import software.amazon.smithy.swift.codegen.utils.ModelFileUtils @@ -95,8 +98,7 @@ class MessageUnmarshallableGenerator( writer.write("}") } writer.write("}") - writer.write("let error = try makeError(message, params)") - writer.write("throw error") + writer.write("throw try makeError(message, params)") } writer.write("case .error(let params):") writer.indent { @@ -205,11 +207,13 @@ class MessageUnmarshallableGenerator( } private fun renderReadToValue(writer: SwiftWriter, memberShape: MemberShape) { - val readingClosure = ReadingClosureUtils(ctx, writer).readingClosure(memberShape) +// writer.addImport(SmithyReadWriteTypes.ShapeDeserializer) + val target = ctx.model.expectShape(memberShape.target) writer.write( - "let value = try \$N.readFrom(message.payload, with: \$L)", + "let value = try \$N.from(data: message.payload).\$LNonNull(schema: \$L)", ctx.service.readerSymbol, - readingClosure, + target.type.readMethodName, + target.id.schemaVar(writer), ) } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt index 86c44c746..5ab1eebd1 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt @@ -38,6 +38,7 @@ import software.amazon.smithy.model.traits.TimestampFormatTrait import software.amazon.smithy.swift.codegen.SwiftWriter import software.amazon.smithy.swift.codegen.customtraits.NeedsReaderTrait import software.amazon.smithy.swift.codegen.customtraits.NeedsWriterTrait +import software.amazon.smithy.swift.codegen.customtraits.NestedTrait import software.amazon.smithy.swift.codegen.events.MessageMarshallableGenerator import software.amazon.smithy.swift.codegen.events.MessageUnmarshallableGenerator import software.amazon.smithy.swift.codegen.integration.httpResponse.HTTPResponseGenerator @@ -257,7 +258,7 @@ abstract class HTTPBindingProtocolGenerator( writer.write("") renderStructEncode(ctx, shape, mapOf(), httpBodyMembers, writer) } - if (shape.hasTrait()) { + if (shape.hasTrait() && (ctx.service.responseWireProtocol != WireProtocol.JSON || !shape.hasTrait())) { writer.write("") renderStructDecode(ctx, shape, mapOf(), httpBodyMembers, writer) } @@ -357,18 +358,18 @@ abstract class HTTPBindingProtocolGenerator( // .filter { it.isStructureShape || it.isUnionShape || it is CollectionShape || it.isMapShape } .toSet() -// val topLevelErrorMembers = getHttpBindingOperations(ctx) -// .flatMap { it.errors } -// .flatMap { ctx.model.expectShape(it).members() } -// .map { ctx.model.expectShape(it.target) } + val topLevelErrorMembers = getHttpBindingOperations(ctx) + .flatMap { it.errors } + .flatMap { ctx.model.expectShape(it).members() } + .map { ctx.model.expectShape(it.target) } // .filter { it.isStructureShape || it.isUnionShape || it is CollectionShape || it.isMapShape } -// .toSet() + .toSet() -// val topLevelServiceErrorMembers = ctx.service.errors -// .flatMap { ctx.model.expectShape(it).members() } -// .map { ctx.model.expectShape(it.target) } + val topLevelServiceErrorMembers = ctx.service.errors + .flatMap { ctx.model.expectShape(it).members() } + .map { ctx.model.expectShape(it.target) } // .filter { it.isStructureShape || it.isUnionShape || it is CollectionShape || it.isMapShape } -// .toSet() + .toSet() // val topLevelInputMembers = getHttpBindingOperations(ctx).flatMap { // val inputShape = ctx.model.expectShape(it.input.get()) @@ -380,8 +381,8 @@ abstract class HTTPBindingProtocolGenerator( val allTopLevelMembers = topLevelOutputMembers -// .union(topLevelErrorMembers) -// .union(topLevelServiceErrorMembers) + .union(topLevelErrorMembers) + .union(topLevelServiceErrorMembers) // .union(topLevelInputMembers) val nestedTypes = walkNestedShapesRequiringSchema(ctx, allTopLevelMembers) @@ -436,7 +437,7 @@ abstract class HTTPBindingProtocolGenerator( else -> false } }.forEach { - if (it.type != ShapeType.MEMBER && it.id.namespace != "smithy.api" && !it.hasTrait()) { + if (it.type != ShapeType.MEMBER) { resolved.add(it) } // when (it) { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt index e0bbde818..3643f0099 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt @@ -44,7 +44,10 @@ import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator import software.amazon.smithy.swift.codegen.integration.serde.json.TimestampUtils import software.amazon.smithy.swift.codegen.integration.serde.readwrite.NodeInfoUtils import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ReadingClosureUtils +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.WireProtocol import software.amazon.smithy.swift.codegen.integration.serde.readwrite.responseWireProtocol +import software.amazon.smithy.swift.codegen.integration.serde.schema.readMethodName +import software.amazon.smithy.swift.codegen.integration.serde.schema.schemaVar import software.amazon.smithy.swift.codegen.model.expectTrait import software.amazon.smithy.swift.codegen.model.getTrait import software.amazon.smithy.swift.codegen.model.hasTrait @@ -82,6 +85,15 @@ open class MemberShapeDecodeGenerator( } private fun renderStructOrUnionExp(memberShape: MemberShape, isPayload: Boolean): String { + if (useSBS) { + val target = ctx.model.expectShape(memberShape.target) + return writer.format( + "try \$L.readStructure\$L(schema: \$L)", + reader(memberShape, isPayload), + "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", + memberShape.target.schemaVar(writer), + ) + } val readingClosure = readingClosureUtils.readingClosure(memberShape) return writer.format( "try \$L.\$L(with: \$L)", @@ -92,6 +104,15 @@ open class MemberShapeDecodeGenerator( } private fun renderListExp(memberShape: MemberShape, listShape: ListShape): String { + if (useSBS) { + val target = ctx.model.expectShape(memberShape.target) + return writer.format( + "try \$L.readList\$L(schema: \$L)", + reader(memberShape, false), + "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", + memberShape.target.schemaVar(writer), + ) + } val isSparse = listShape.hasTrait() val memberReadingClosure = readingClosureUtils.readingClosure(listShape.member, isSparse) val memberNodeInfo = nodeInfoUtils.nodeInfo(listShape.member) @@ -108,6 +129,15 @@ open class MemberShapeDecodeGenerator( } private fun renderMapExp(memberShape: MemberShape, mapShape: MapShape): String { + if (useSBS) { + val target = ctx.model.expectShape(memberShape.target) + return writer.format( + "try \$L.readMap\$L(schema: \$L)", + reader(memberShape, false), + "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", + memberShape.target.schemaVar(writer), + ) + } val isSparse = mapShape.hasTrait() val valueReadingClosure = ReadingClosureUtils(ctx, writer).readingClosure(mapShape.value, isSparse) val keyNodeInfo = nodeInfoUtils.nodeInfo(mapShape.key) @@ -126,6 +156,9 @@ open class MemberShapeDecodeGenerator( } private fun renderTimestampExp(memberShape: MemberShape, timestampShape: TimestampShape): String { + if (useSBS) { + return renderMemberExp(memberShape, isPayload = false) + } val memberTimestampFormatTrait = memberShape.getTrait() val swiftTimestampFormatCase = TimestampUtils.timestampFormat(ctx, memberTimestampFormatTrait, timestampShape) return writer.format( @@ -139,6 +172,16 @@ open class MemberShapeDecodeGenerator( } private fun renderMemberExp(memberShape: MemberShape, isPayload: Boolean): String { + if (useSBS) { + val target = ctx.model.expectShape(memberShape.target) + return writer.format( + "try \$L.\$L\$L(schema: \$L)", + reader(memberShape, isPayload), + target.type.readMethodName, + "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", + memberShape.target.schemaVar(writer), + ) + } return writer.format( "try \$L.\$L()\$L", reader(memberShape, isPayload), @@ -297,4 +340,6 @@ open class MemberShapeDecodeGenerator( } private var decodingUnion: Boolean = shapeContainingMembers.isUnionShape + + private val useSBS: Boolean = ctx.service.responseWireProtocol == WireProtocol.JSON } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt index d0a9452f6..162334ef9 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt @@ -13,14 +13,15 @@ import software.amazon.smithy.model.traits.SparseTrait import software.amazon.smithy.model.traits.TimestampFormatTrait import software.amazon.smithy.model.traits.XmlNameTrait import software.amazon.smithy.swift.codegen.SwiftWriter +import software.amazon.smithy.swift.codegen.customtraits.NestedTrait import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator import software.amazon.smithy.swift.codegen.model.getTrait import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.model.nestedNamespaceType +import software.amazon.smithy.swift.codegen.model.toOptional import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTimestampsTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTypes -import software.amazon.smithy.swift.codegen.swiftmodules.SwiftTypes import kotlin.jvm.optionals.getOrNull class SchemaGenerator( @@ -29,20 +30,19 @@ class SchemaGenerator( ) { fun renderSchemas(shape: Shape) { - writer.writeInline("let \$L = ", schemaVar(writer, shape.id)) + writer.writeInline("let \$L: \$N<\$N> = ", shape.id.schemaVar(writer), schemaType(shape), swiftSymbolFor(ctx, shape)) renderSingleSchema(shape) writer.unwrite(",\n") - writer.write("") + writer.write("()") writer.write("") } - private fun renderSingleSchema(shape: Shape) { - val swiftType = swiftSymbolFor(ctx, shape) + private fun renderSingleSchema(shape: Shape, optionalize: Boolean = false) { writer.openBlock( - "\$N<\$N>(", - "),", + "{ \$N<\$N>(", + ") },", schemaType(shape), - swiftType, + swiftSymbolFor(ctx, shape).optionalIf(optionalize), ) { // writer.write("namespace: \$S,", shape.id.namespace) // writer.write("name: \$S,", shape.id.name) @@ -55,7 +55,7 @@ class SchemaGenerator( writer.openBlock(".init(", "),") { writer.writeInline("memberSchema: ") renderSingleSchema(member) - writer.write("targetSchema: \$L,", schemaVar(writer, member.target)) + writer.write("targetSchema: { \$L },", member.target.schemaVar(writer)) writeSetterGetter(ctx, writer, shape, member) } } @@ -68,8 +68,8 @@ class SchemaGenerator( val member = shape.members().first { it.memberName == "member" } val target = ctx.model.expectShape(member.target) writer.writeInline("memberSchema: ") - renderSingleSchema(member) - writer.write("targetSchema: \$L,", schemaVar(writer, target.id)) + renderSingleSchema(member, shape.hasTrait()) + writer.write("targetSchema: { \$L },", target.id.schemaVar(writer)) writeSetterGetter(ctx, writer, shape, member) } ShapeType.MAP -> { @@ -79,10 +79,10 @@ class SchemaGenerator( val valueTarget = valueMember.let { ctx.model.expectShape(it.target) } writer.writeInline("keyMemberSchema: ") renderSingleSchema(keyMember) - writer.write("keyTargetSchema: \$L,", schemaVar(writer, keyTarget.id)) + writer.write("keyTargetSchema: { \$L },", keyTarget.id.schemaVar(writer)) writer.writeInline("valueMemberSchema: ") - renderSingleSchema(valueMember) - writer.write("valueTargetSchema: \$L,", schemaVar(writer, valueTarget.id)) + renderSingleSchema(valueMember, shape.hasTrait()) + writer.write("valueTargetSchema: { \$L },", valueTarget.id.schemaVar(writer)) writeSetterGetter(ctx, writer, shape, valueMember) } ShapeType.MEMBER -> { @@ -125,24 +125,30 @@ class SchemaGenerator( val service = ctx.model.getShape(ctx.settings.service).get() as ServiceShape return when (shape.type) { ShapeType.STRUCTURE, ShapeType.UNION -> { - if (shape.id.namespace == "smithy.api" && shape.id.name == "Unit") { - return SwiftTypes.Void - } else { +// if (shape.id.namespace == "smithy.api" && shape.id.name == "Unit") { +// SwiftTypes.Void +// } else { val symbol = ctx.symbolProvider.toSymbol(shape) - return symbol.toBuilder().namespace(service.nestedNamespaceType(ctx.symbolProvider).name, ".") - .build() - } + if (shape.hasTrait()) { + return symbol.toBuilder().namespace(service.nestedNamespaceType(ctx.symbolProvider).name, ".").build() + } else { + return symbol + } + +// } } ShapeType.LIST, ShapeType.SET -> { val target = shape.members() .first { it.memberName == "member" } ?.let { ctx.model.expectShape(it.target) } ?: run { throw Exception("List / set does not have target") } - val innerSymbol = swiftSymbolWithOptionalRecursion(ctx, target, false) - if (recurse) { - return innerSymbol + val innerSymbol = swiftSymbolWithOptionalRecursion(ctx, target, false).optionalIf(shape.hasTrait()) + return if (recurse) { + innerSymbol } else { - return Symbol.builder().namespace("Swift", ".").name("Array<${innerSymbol}>").build() + Symbol.builder() + .name(writer.format("[ \$N ]", innerSymbol)) + .build() } } ShapeType.MAP -> { @@ -150,60 +156,49 @@ class SchemaGenerator( .first { it.memberName == "value" } ?.let { ctx.model.expectShape(it.target) } ?: run { throw Exception("Map does not have target") } - val innerSymbol = swiftSymbolWithOptionalRecursion(ctx, target, false) - if (recurse) { - return innerSymbol + val innerSymbol = swiftSymbolWithOptionalRecursion(ctx, target, false).optionalIf(shape.hasTrait()) + return if (recurse) { + innerSymbol } else { - return Symbol.builder().namespace("Swift", ".").name("Dictionary").build() + Symbol.builder() + .name(writer.format("[Swift.String: \$N]", innerSymbol)) + .build() } } ShapeType.MEMBER -> { val memberShape = shape as MemberShape val target = ctx.model.expectShape(memberShape.target) - return swiftSymbolWithOptionalRecursion(ctx, target, false) + swiftSymbolWithOptionalRecursion(ctx, target, false) } else -> { - return ctx.symbolProvider.toSymbol(shape) + ctx.symbolProvider.toSymbol(shape) } } } + fun Symbol.optionalIf(isOptional: Boolean): Symbol { + if (isOptional) { + return this.toOptional() + } else { + return this + } + } + private fun writeSetterGetter(ctx: ProtocolGenerator.GenerationContext, writer: SwiftWriter, shape: Shape, member: MemberShape) { val target = ctx.model.expectShape(member.target) - val readMethodName = when (target.type) { - ShapeType.BLOB -> "readBlob" - ShapeType.BOOLEAN -> "readBoolean" - ShapeType.STRING -> "readString" - ShapeType.ENUM -> "readEnum" - ShapeType.TIMESTAMP -> "readTimestamp" - ShapeType.BYTE -> "readByte" - ShapeType.SHORT -> "readShort" - ShapeType.INTEGER -> "readInteger" - ShapeType.INT_ENUM -> "readIntEnum" - ShapeType.LONG -> "readLong" - ShapeType.FLOAT -> "readFloat" - ShapeType.DOCUMENT -> "readDocument" - ShapeType.DOUBLE -> "readDouble" - ShapeType.BIG_DECIMAL -> "readBigDecimal" - ShapeType.BIG_INTEGER -> "readBigInteger" - ShapeType.LIST, ShapeType.SET -> "readList" - ShapeType.MAP -> "readMap" - ShapeType.STRUCTURE, ShapeType.UNION -> "readStruct" - ShapeType.MEMBER, ShapeType.SERVICE, ShapeType.RESOURCE, ShapeType.OPERATION, null -> - throw Exception("Unsupported member target type: ${target.type}") - } + val readMethodName = target.type.readMethodName when (shape.type) { ShapeType.STRUCTURE -> { - writer.write("readBlock: { \$\$0.\$L = try \$\$1.\$L(schema: \$L) },", ctx.symbolProvider.toMemberName(member), readMethodName, schemaVar(writer, target.id)) + writer.write("readBlock: { \$\$0.\$L = try \$\$1.\$L(schema: \$L) },", ctx.symbolProvider.toMemberName(member), readMethodName, target.id.schemaVar(writer)) writer.write("writeBlock: { _, _ in }") } ShapeType.UNION -> { - writer.write("readBlock: { \$\$0 = .\$L(try \$\$1.\$LNonNull(schema: \$L)) },", ctx.symbolProvider.toMemberName(member), readMethodName, schemaVar(writer, target.id)) + writer.write("readBlock: { \$\$0 = .\$L(try \$\$1.\$LNonNull(schema: \$L)) },", ctx.symbolProvider.toMemberName(member), readMethodName, target.id.schemaVar(writer)) writer.write("writeBlock: { _, _ in }") } ShapeType.LIST, ShapeType.SET, ShapeType.MAP -> { val nonNullModifier = "NonNull".takeIf { !shape.hasTrait() } ?: "" - writer.write("readBlock: { try \$\$0.\$L\$L(schema: \$L) },", readMethodName, nonNullModifier, schemaVar(writer, target.id)) + writer.write("readBlock: { try \$\$0.\$L\$L(schema: \$L) },", readMethodName, nonNullModifier, target.id.schemaVar(writer)) writer.write("writeBlock: { _, _ in }") } else -> {} @@ -230,23 +225,6 @@ class SchemaGenerator( return shape.getTrait()?.format } - fun schemaVar(writer: SwiftWriter, shapeId: ShapeId): String { - return when (Pair(shapeId.namespace, shapeId.name)) { - Pair("smithy.api", "Unit") -> writer.format("\$N", SmithyReadWriteTypes.unitSchema) - Pair("smithy.api", "Boolean") -> writer.format("\$N", SmithyReadWriteTypes.booleanSchema) - Pair("smithy.api", "Integer") -> writer.format("\$N", SmithyReadWriteTypes.integerSchema) - Pair("smithy.api", "Float") -> writer.format("\$N", SmithyReadWriteTypes.floatSchema) - Pair("smithy.api", "Double") -> writer.format("\$N", SmithyReadWriteTypes.doubleSchema) - Pair("smithy.api", "String") -> writer.format("\$N", SmithyReadWriteTypes.stringSchema) - Pair("smithy.api", "Document") -> writer.format("\$N", SmithyReadWriteTypes.documentSchema) - else -> { - val namespacePortion = shapeId.namespace.replace(".", "_") - val memberPortion = shapeId.member.getOrNull()?.let { "_member_$it" } ?: "" - "${namespacePortion}__${shapeId.name}_schema${memberPortion}" - } - } - } - private val TimestampFormatTrait.Format.swiftEnumCase: String get() { return when (this) { @@ -256,4 +234,50 @@ class SchemaGenerator( else -> throw Exception("Unknown TimestampFormat") } } -} \ No newline at end of file +} + +fun ShapeId.schemaVar(writer: SwiftWriter): String { + return when (Pair(this.namespace, this.name)) { +// Pair("smithy.api", "Unit") -> writer.format("\$N", SmithyReadWriteTypes.unitSchema) +// Pair("smithy.api", "Boolean") -> writer.format("\$N", SmithyReadWriteTypes.booleanSchema) +// Pair("smithy.api", "Byte") -> writer.format("\$N", SmithyReadWriteTypes.byteSchema) +// Pair("smithy.api", "Short") -> writer.format("\$N", SmithyReadWriteTypes.shortSchema) +// Pair("smithy.api", "Integer") -> writer.format("\$N", SmithyReadWriteTypes.integerSchema) +// Pair("smithy.api", "Long") -> writer.format("\$N", SmithyReadWriteTypes.longSchema) +// Pair("smithy.api", "Float") -> writer.format("\$N", SmithyReadWriteTypes.floatSchema) +// Pair("smithy.api", "Double") -> writer.format("\$N", SmithyReadWriteTypes.doubleSchema) +// Pair("smithy.api", "String") -> writer.format("\$N", SmithyReadWriteTypes.stringSchema) +// Pair("smithy.api", "Document") -> writer.format("\$N", SmithyReadWriteTypes.documentSchema) +// Pair("smithy.api", "Blob") -> writer.format("\$N", SmithyReadWriteTypes.blobSchema) +// Pair("smithy.api", "Timestamp") -> writer.format("\$N", SmithyReadWriteTypes.timestampSchema) + else -> { + val namespacePortion = this.namespace.replace(".", "_") + val memberPortion = this.member.getOrNull()?.let { "_member_$it" } ?: "" + "${namespacePortion}__${this.name}_schema${memberPortion}" + } + } +} + +val ShapeType.readMethodName: String + get() = when (this) { + ShapeType.BLOB -> "readBlob" + ShapeType.BOOLEAN -> "readBoolean" + ShapeType.STRING -> "readString" + ShapeType.ENUM -> "readEnum" + ShapeType.TIMESTAMP -> "readTimestamp" + ShapeType.BYTE -> "readByte" + ShapeType.SHORT -> "readShort" + ShapeType.INTEGER -> "readInteger" + ShapeType.INT_ENUM -> "readIntEnum" + ShapeType.LONG -> "readLong" + ShapeType.FLOAT -> "readFloat" + ShapeType.DOCUMENT -> "readDocument" + ShapeType.DOUBLE -> "readDouble" + ShapeType.BIG_DECIMAL -> "readBigDecimal" + ShapeType.BIG_INTEGER -> "readBigInteger" + ShapeType.LIST, ShapeType.SET -> "readList" + ShapeType.MAP -> "readMap" + ShapeType.STRUCTURE, ShapeType.UNION -> "readStructure" + ShapeType.MEMBER, ShapeType.SERVICE, ShapeType.RESOURCE, ShapeType.OPERATION -> + throw Exception("Unsupported member target type: ${this}") + } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyJSONTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyJSONTypes.kt index 3d4a743eb..767b91651 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyJSONTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyJSONTypes.kt @@ -10,7 +10,7 @@ import software.amazon.smithy.swift.codegen.SwiftDependency object SmithyJSONTypes { val Writer = runtimeSymbol("Writer", SwiftDeclaration.CLASS, listOf(SmithyReadWriteTypes.SmithyWriter)) - val Reader = runtimeSymbol("Reader", SwiftDeclaration.CLASS, listOf(SmithyReadWriteTypes.SmithyReader)) + val Reader = runtimeSymbol("Reader", SwiftDeclaration.CLASS, listOf(SmithyReadWriteTypes.SmithyReader, SmithyReadWriteTypes.ShapeDeserializer)) } private fun runtimeSymbol( @@ -22,5 +22,5 @@ private fun runtimeSymbol( declaration, SwiftDependency.SMITHY_JSON, additionalImports, - listOf("SmithyReadWrite"), + listOf("SmithyReadWrite", "SchemaBasedSerde"), ) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt index 38d747e57..66cd696d9 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt @@ -26,20 +26,25 @@ object SmithyReadWriteTypes { val WritingClosures = runtimeSymbol("WritingClosures", SwiftDeclaration.ENUM) val ReadingClosureBox = runtimeSymbol("ReadingClosureBox", SwiftDeclaration.STRUCT) val WritingClosureBox = runtimeSymbol("WritingClosureBox", SwiftDeclaration.STRUCT) - val StructureSchema = runtimeSymbol("StructureSchema", SwiftDeclaration.STRUCT, listOf("SchemaBasedSerde")) - val ListSchema = runtimeSymbol("ListSchema", SwiftDeclaration.STRUCT, listOf("SchemaBasedSerde")) - val MapSchema = runtimeSymbol("MapSchema", SwiftDeclaration.STRUCT, listOf("SchemaBasedSerde")) - val SimpleSchema = runtimeSymbol("SimpleSchema", SwiftDeclaration.STRUCT, listOf("SchemaBasedSerde")) - val MemberSchema = runtimeSymbol("MemberSchema", SwiftDeclaration.STRUCT, listOf("SchemaBasedSerde")) - val Member = runtimeSymbol("Member", SwiftDeclaration.STRUCT, listOf("SchemaBasedSerde")) + val StructureSchema = runtimeSymbol("StructureSchema", SwiftDeclaration.CLASS, listOf("SchemaBasedSerde")) + val ListSchema = runtimeSymbol("ListSchema", SwiftDeclaration.CLASS, listOf("SchemaBasedSerde")) + val MapSchema = runtimeSymbol("MapSchema", SwiftDeclaration.CLASS, listOf("SchemaBasedSerde")) + val SimpleSchema = runtimeSymbol("SimpleSchema", SwiftDeclaration.CLASS, listOf("SchemaBasedSerde")) + val MemberSchema = runtimeSymbol("MemberSchema", SwiftDeclaration.CLASS, listOf("SchemaBasedSerde")) val DeserializableShape = runtimeSymbol("DeserializableShape", SwiftDeclaration.PROTOCOL) + val ShapeDeserializer = runtimeSymbol("ShapeDeserializer", SwiftDeclaration.PROTOCOL, listOf("SchemaBasedSerde")) val unitSchema = runtimeSymbol("unitSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) val booleanSchema = runtimeSymbol("booleanSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) + val byteSchema = runtimeSymbol("byteSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) + val shortSchema = runtimeSymbol("shortSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) val integerSchema = runtimeSymbol("integerSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) + val longSchema = runtimeSymbol("longSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) val floatSchema = runtimeSymbol("floatSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) val doubleSchema = runtimeSymbol("doubleSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) val stringSchema = runtimeSymbol("stringSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) val documentSchema = runtimeSymbol("documentSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) + val blobSchema = runtimeSymbol("blobSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) + val timestampSchema = runtimeSymbol("timestampSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) } private fun runtimeSymbol( diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyTypes.kt index 33f8f262c..eae9f4ab5 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyTypes.kt @@ -21,7 +21,21 @@ object SmithyTypes { val LogAgent = runtimeSymbol("LogAgent", SwiftDeclaration.PROTOCOL) val RequestMessageSerializer = runtimeSymbol("RequestMessageSerializer", SwiftDeclaration.PROTOCOL) val URIQueryItem = runtimeSymbol("URIQueryItem", SwiftDeclaration.STRUCT) + val BigDecimalDocument = runtimeSymbol("BigDecimalDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) + val BigIntegerDocument = runtimeSymbol("BigIntegerDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) + val BlobDocument = runtimeSymbol("BlobDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) + val BooleanDocument = runtimeSymbol("BooleanDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) + val ByteDocument = runtimeSymbol("ByteDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) + val DoubleDocument = runtimeSymbol("DoubleDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) + val FloatDocument = runtimeSymbol("FloatDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) + val IntegerDocument = runtimeSymbol("IntegerDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) + val ListDocument = runtimeSymbol("ListDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) + val LongDocument = runtimeSymbol("LongDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) + val NullDocument = runtimeSymbol("NullDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) + val ShortDocument = runtimeSymbol("ShortDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) val StringDocument = runtimeSymbol("StringDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) + val StringMapDocument = runtimeSymbol("StringMapDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) + val TimestampDocument = runtimeSymbol("TimestampDocument", SwiftDeclaration.STRUCT, listOf(), listOf("SmithyDocumentImpl")) } private fun runtimeSymbol( From 10636987c2a3cba2c5b86aa94fe69755ada1dcd7 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Thu, 26 Dec 2024 11:22:49 -0600 Subject: [PATCH 03/16] Full SDK compiled --- .../endpoints/EndpointParamsGenerator.kt | 8 +++++++ .../events/MessageUnmarshallableGenerator.kt | 24 ++++++++++++++++--- .../HTTPBindingProtocolGenerator.kt | 4 ++-- .../member/MemberShapeDecodeGenerator.kt | 2 +- .../serde/schema/SchemaGenerator.kt | 20 +++++++++------- 5 files changed, 44 insertions(+), 14 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt index cb2228e11..c2b1ddc98 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt @@ -147,5 +147,13 @@ fun Parameter.toSymbol(): Symbol { } } + if (isRequired && !default.isPresent) { + when (type) { + ParameterType.STRING -> builder.defaultValue("\"\"") + ParameterType.BOOLEAN -> builder.defaultValue("false") + ParameterType.STRING_ARRAY -> builder.defaultValue("[]") + } + } + return builder.build() } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt index 4f4c95097..71e6c6803 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt @@ -11,6 +11,8 @@ import software.amazon.smithy.swift.codegen.SwiftWriter import software.amazon.smithy.swift.codegen.integration.HTTPProtocolCustomizable import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ReadingClosureUtils +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.WireProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.responseWireProtocol import software.amazon.smithy.swift.codegen.integration.serde.schema.readMethodName import software.amazon.smithy.swift.codegen.integration.serde.schema.schemaVar import software.amazon.smithy.swift.codegen.integration.serde.struct.readerSymbol @@ -206,14 +208,30 @@ class MessageUnmarshallableGenerator( } } - private fun renderReadToValue(writer: SwiftWriter, memberShape: MemberShape) { -// writer.addImport(SmithyReadWriteTypes.ShapeDeserializer) + private fun renderReadToValue(writer: SwiftWriter, member: MemberShape) { + if (ctx.service.responseWireProtocol == WireProtocol.JSON) { + renderReadWithSchemaToValue(writer, member) + } else { + renderReadWithSerdeToValue(writer, member) + } + } + + private fun renderReadWithSchemaToValue(writer: SwiftWriter, memberShape: MemberShape) { val target = ctx.model.expectShape(memberShape.target) writer.write( "let value = try \$N.from(data: message.payload).\$LNonNull(schema: \$L)", ctx.service.readerSymbol, - target.type.readMethodName, + target.readMethodName, target.id.schemaVar(writer), ) } + + private fun renderReadWithSerdeToValue(writer: SwiftWriter, memberShape: MemberShape) { + val readingClosure = ReadingClosureUtils(ctx, writer).readingClosure(memberShape) + writer.write( + "let value = try \$N.readFrom(message.payload, with: \$L)", + ctx.service.readerSymbol, + readingClosure, + ) + } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt index 5ab1eebd1..28a69465c 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt @@ -385,8 +385,8 @@ abstract class HTTPBindingProtocolGenerator( .union(topLevelServiceErrorMembers) // .union(topLevelInputMembers) - val nestedTypes = walkNestedShapesRequiringSchema(ctx, allTopLevelMembers) - return nestedTypes + return walkNestedShapesRequiringSchema(ctx, allTopLevelMembers) +// return nestedTypes } private fun walkNestedShapesRequiringSerde(ctx: ProtocolGenerator.GenerationContext, shapes: Set): Set { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt index 3643f0099..2d4255458 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt @@ -177,7 +177,7 @@ open class MemberShapeDecodeGenerator( return writer.format( "try \$L.\$L\$L(schema: \$L)", reader(memberShape, isPayload), - target.type.readMethodName, + target.readMethodName, "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", memberShape.target.schemaVar(writer), ) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt index 162334ef9..085b4db33 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt @@ -7,6 +7,8 @@ import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.model.shapes.ShapeType import software.amazon.smithy.model.traits.DefaultTrait +import software.amazon.smithy.model.traits.EnumTrait +import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.model.traits.JsonNameTrait import software.amazon.smithy.model.traits.RequiredTrait import software.amazon.smithy.model.traits.SparseTrait @@ -129,7 +131,7 @@ class SchemaGenerator( // SwiftTypes.Void // } else { val symbol = ctx.symbolProvider.toSymbol(shape) - if (shape.hasTrait()) { + if (shape.hasTrait() && !shape.hasTrait()) { return symbol.toBuilder().namespace(service.nestedNamespaceType(ctx.symbolProvider).name, ".").build() } else { return symbol @@ -186,10 +188,12 @@ class SchemaGenerator( private fun writeSetterGetter(ctx: ProtocolGenerator.GenerationContext, writer: SwiftWriter, shape: Shape, member: MemberShape) { val target = ctx.model.expectShape(member.target) - val readMethodName = target.type.readMethodName + val readMethodName = target.readMethodName + val mod = "NonNull".takeIf { member.isRequired || (member.hasTrait() || target.hasTrait()) } ?: "" when (shape.type) { ShapeType.STRUCTURE -> { - writer.write("readBlock: { \$\$0.\$L = try \$\$1.\$L(schema: \$L) },", ctx.symbolProvider.toMemberName(member), readMethodName, target.id.schemaVar(writer)) + val path = "properties.".takeIf { shape.hasTrait() } ?: "" + writer.write("readBlock: { \$\$0.\$L\$L = try \$\$1.\$L\$L(schema: \$L) },", path, ctx.symbolProvider.toMemberName(member), readMethodName, mod, target.id.schemaVar(writer)) writer.write("writeBlock: { _, _ in }") } ShapeType.UNION -> { @@ -258,11 +262,11 @@ fun ShapeId.schemaVar(writer: SwiftWriter): String { } } -val ShapeType.readMethodName: String - get() = when (this) { +val Shape.readMethodName: String + get() = when (type) { ShapeType.BLOB -> "readBlob" ShapeType.BOOLEAN -> "readBoolean" - ShapeType.STRING -> "readString" + ShapeType.STRING -> "readEnum".takeIf { hasTrait() } ?: "readString" ShapeType.ENUM -> "readEnum" ShapeType.TIMESTAMP -> "readTimestamp" ShapeType.BYTE -> "readByte" @@ -278,6 +282,6 @@ val ShapeType.readMethodName: String ShapeType.LIST, ShapeType.SET -> "readList" ShapeType.MAP -> "readMap" ShapeType.STRUCTURE, ShapeType.UNION -> "readStructure" - ShapeType.MEMBER, ShapeType.SERVICE, ShapeType.RESOURCE, ShapeType.OPERATION -> - throw Exception("Unsupported member target type: ${this}") + ShapeType.MEMBER, ShapeType.SERVICE, ShapeType.RESOURCE, ShapeType.OPERATION, null -> + throw Exception("Unsupported member target type: ${type}") } From e2df562b8e81606de554b1e275829579c16ba668 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Thu, 26 Dec 2024 23:55:22 -0600 Subject: [PATCH 04/16] All but 9 protocol tests pass --- .../Reader/Reader+ShapeDeserializer.swift | 52 +++- Sources/SmithyJSON/Reader/Reader.swift | 2 + .../SmithyReadWrite/DeserializableShape.swift | 5 + .../Schema/DefaultValueTransformers.swift | 152 +++++++++ Sources/SmithyReadWrite/Schema/Require.swift | 24 ++ Sources/SmithyReadWrite/Schema/Schema.swift | 54 +++- .../SmithyReadWrite/ShapeDeserializer.swift | 288 ++++++++++-------- .../smithy/swift/codegen/EnumGenerator.kt | 17 +- .../smithy/swift/codegen/IntEnumGenerator.kt | 17 +- .../events/MessageUnmarshallableGenerator.kt | 3 +- .../HTTPBindingProtocolGenerator.kt | 2 +- .../HTTPResponseBindingErrorInitGenerator.kt | 5 + .../HTTPResponseBindingOutputGenerator.kt | 3 + .../member/MemberShapeDecodeGenerator.kt | 21 +- .../serde/schema/SchemaGenerator.kt | 66 ++-- .../swiftmodules/SmithyReadWriteTypes.kt | 1 + 16 files changed, 524 insertions(+), 188 deletions(-) create mode 100644 Sources/SmithyReadWrite/Schema/DefaultValueTransformers.swift create mode 100644 Sources/SmithyReadWrite/Schema/Require.swift diff --git a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift index 85574a13f..6363e4827 100644 --- a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift +++ b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift @@ -8,10 +8,12 @@ import struct Foundation.Data import struct Foundation.Date import struct Smithy.Document +import protocol Smithy.SmithyDocument import enum SmithyReadWrite.ReaderError @_spi(SchemaBasedSerde) import protocol SmithyReadWrite.ShapeDeserializer @_spi(SchemaBasedSerde) import protocol SmithyReadWrite.SchemaProtocol @_spi(SchemaBasedSerde) import protocol SmithyReadWrite.DeserializableShape +@_spi(SchemaBasedSerde) import protocol SmithyReadWrite.MemberSchemaProtocol @_spi(SchemaBasedSerde) import class SmithyReadWrite.StructureSchema @_spi(SchemaBasedSerde) import class SmithyReadWrite.ListSchema @_spi(SchemaBasedSerde) import class SmithyReadWrite.MapSchema @@ -25,7 +27,36 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { schema: SmithyReadWrite.StructureSchema ) throws -> Base? { // TODO: Implement me - return Base() + guard self.hasContent else { return nil } + var value = Base() + try schema.members.forEach { member in + let resolvedName = try resolvedName(memberSchema: member.memberSchema()) + guard let resolvedName = member.memberSchema().jsonName ?? member.memberSchema().memberName else { + throw ReaderError.requiredValueNotPresent + } + let resolvedReader = self[NodeInfo(resolvedName)] + guard resolvedReader.hasContent else { return } + var resolvedDefault = member.targetSchema().defaultValue ?? member.memberSchema().defaultValue + if member.memberSchema().isRequired { + resolvedDefault = try resolvedDefault ?? member.targetSchema().lastResortDefaultValue + } + try member.readBlock(&value, resolvedReader, resolvedDefault) + } + return value + } + + private func resolvedName(memberSchema: SmithyReadWrite.MemberSchemaProtocol) throws -> String { + if respectsJSONName { + guard let resolvedName = memberSchema.jsonName ?? memberSchema.memberName else { + throw ReaderError.requiredValueNotPresent + } + return resolvedName + } else { + guard let resolvedName = memberSchema.memberName else { + throw ReaderError.requiredValueNotPresent + } + return resolvedName + } } public func readString(schema: SmithyReadWrite.SchemaProtocol) throws -> String? { @@ -33,13 +64,19 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { } public func readList(schema: SmithyReadWrite.ListSchema) throws -> [T]? { - // TODO: Implement me - [] + guard hasContent, jsonNode == .array else { return nil } + return try children.map { reader in + try schema.readBlock(reader) + } } public func readMap(schema: SmithyReadWrite.MapSchema) throws -> [String : T]? { // TODO: Implement me - [:] + guard hasContent, jsonNode == .object else { return nil } + let pairs = try children.map { reader in + return (reader.nodeInfo.name, try schema.readBlock(reader)) + } + return Dictionary(uniqueKeysWithValues: pairs) } public func readBoolean(schema: any SmithyReadWrite.SchemaProtocol) throws -> Bool? { @@ -83,17 +120,14 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { } public func readTimestamp(schema: SmithyReadWrite.SimpleSchema) throws -> Date? { - // TODO: Implement me try readTimestampIfPresent(format: schema.timestampFormat ?? .epochSeconds) } public func readDocument(schema: any SmithyReadWrite.SchemaProtocol) throws -> Document? { - // TODO: Implement me - nil + try readIfPresent() } public func readNull(schema: any SmithyReadWrite.SchemaProtocol) throws -> Bool? { - // TODO: Implement me - return false + try readNullIfPresent() } } diff --git a/Sources/SmithyJSON/Reader/Reader.swift b/Sources/SmithyJSON/Reader/Reader.swift index f84e06c6b..6232c817e 100644 --- a/Sources/SmithyJSON/Reader/Reader.swift +++ b/Sources/SmithyJSON/Reader/Reader.swift @@ -24,6 +24,7 @@ public final class Reader: SmithyReader { public let nodeInfo: NodeInfo let jsonNode: JSONNode? + public var respectsJSONName = false public internal(set) var children = [Reader]() public internal(set) weak var parent: Reader? public var hasContent: Bool { jsonNode != nil && jsonNode != .null } @@ -38,6 +39,7 @@ public final class Reader: SmithyReader { init(nodeInfo: NodeInfo, parent: Reader?) { self.nodeInfo = nodeInfo self.jsonNode = nil + self.respectsJSONName = parent?.respectsJSONName ?? false self.parent = parent } diff --git a/Sources/SmithyReadWrite/DeserializableShape.swift b/Sources/SmithyReadWrite/DeserializableShape.swift index 566058124..2925b23d3 100644 --- a/Sources/SmithyReadWrite/DeserializableShape.swift +++ b/Sources/SmithyReadWrite/DeserializableShape.swift @@ -9,3 +9,8 @@ public protocol DeserializableShape { init() } + +public protocol DeserializableEnum { + + init() +} diff --git a/Sources/SmithyReadWrite/Schema/DefaultValueTransformers.swift b/Sources/SmithyReadWrite/Schema/DefaultValueTransformers.swift new file mode 100644 index 000000000..bde47f0da --- /dev/null +++ b/Sources/SmithyReadWrite/Schema/DefaultValueTransformers.swift @@ -0,0 +1,152 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Foundation.Data +import struct Foundation.Date +import protocol Smithy.SmithyDocument +import struct Smithy.Document + +@_spi(SchemaBasedSerde) +public enum DefaultValueTransformer { + + public static func optional(_ document: (any SmithyDocument)?) throws -> Base? { + return document != nil ? Base() : nil + } + + public static func required(_ document: (any SmithyDocument)?) throws -> Base { + guard let value: Base = try optional(document) else { throw ReaderError.requiredValueNotPresent } + return value + } + + public static func optional(_ document: (any SmithyDocument)?) throws -> [Element]? { + return document != nil ? [] : nil + } + + public static func required(_ document: (any SmithyDocument)?) throws -> [Element] { + guard let value: [Element] = try optional(document) else { throw ReaderError.requiredValueNotPresent } + return value + } + + public static func optional(_ document: (any SmithyDocument)?) throws -> [String: Element]? { + return document != nil ? [:] : nil + } + + public static func required(_ document: (any SmithyDocument)?) throws -> [String: Element] { + guard let value: [String: Element] = try optional(document) else { throw ReaderError.requiredValueNotPresent } + return value + } + + public static func optional(_ document: (any SmithyDocument)?) throws -> Enum? where Enum.RawValue == String { + guard let rawValue = try document?.asString() else { return nil } + return Enum(rawValue: rawValue) + } + + public static func required(_ document: (any SmithyDocument)?) throws -> Enum where Enum.RawValue == String { + guard let value: Enum = try optional(document) else { throw ReaderError.requiredValueNotPresent } + return value + } + + public static func optional(_ document: (any SmithyDocument)?) throws -> IntEnum? where IntEnum.RawValue == Int { + guard let rawValue = try document?.asInteger() else { return nil } + return IntEnum(rawValue: rawValue) + } + + public static func required(_ document: (any SmithyDocument)?) throws -> IntEnum where IntEnum.RawValue == Int { + guard let value: IntEnum = try optional(document) else { throw ReaderError.requiredValueNotPresent } + return value + } + + public static func optional(_ document: (any SmithyDocument)?) throws -> Bool? { + return try document?.asBoolean() + } + + public static func required(_ document: (any SmithyDocument)?) throws -> Bool { + guard let value: Bool = try optional(document) else { throw ReaderError.requiredValueNotPresent } + return value + } + + public static func optional(_ document: (any SmithyDocument)?) throws -> Data? { + return try document?.asBlob() + } + + public static func required(_ document: (any SmithyDocument)?) throws -> Data { + guard let value: Data = try optional(document) else { throw ReaderError.requiredValueNotPresent } + return value + } + + public static func optional(_ document: (any SmithyDocument)?) throws -> Date? { + return try document?.asTimestamp() + } + + public static func required(_ document: (any SmithyDocument)?) throws -> Date { + guard let value: Date = try optional(document) else { throw ReaderError.requiredValueNotPresent } + return value + } + + public static func optional(_ document: (any SmithyDocument)?) throws -> String? { + return try document?.asString() + } + + public static func required(_ document: (any SmithyDocument)?) throws -> String { + guard let value: String = try optional(document) else { throw ReaderError.requiredValueNotPresent } + return value + } + + public static func optional(_ document: (any SmithyDocument)?) throws -> Int8? { + return try document?.asByte() + } + + public static func required(_ document: (any SmithyDocument)?) throws -> Int8 { + guard let value: Int8 = try optional(document) else { throw ReaderError.requiredValueNotPresent } + return value + } + + public static func optional(_ document: (any SmithyDocument)?) throws -> Int16? { + return try document?.asShort() + } + + public static func required(_ document: (any SmithyDocument)?) throws -> Int16 { + guard let value: Int16 = try optional(document) else { throw ReaderError.requiredValueNotPresent } + return value + } + + public static func optional(_ document: (any SmithyDocument)?) throws -> Int? { + return try document?.asInteger() + } + + public static func required(_ document: (any SmithyDocument)?) throws -> Int { + guard let value: Int = try optional(document) else { throw ReaderError.requiredValueNotPresent } + return value + } + + public static func optional(_ document: (any SmithyDocument)?) throws -> Float? { + return try document?.asFloat() + } + + public static func required(_ document: (any SmithyDocument)?) throws -> Float { + guard let value: Float = try optional(document) else { throw ReaderError.requiredValueNotPresent } + return value + } + + public static func optional(_ document: (any SmithyDocument)?) throws -> Double? { + return try document?.asDouble() + } + + public static func required(_ document: (any SmithyDocument)?) throws -> Double { + guard let value: Double = try optional(document) else { throw ReaderError.requiredValueNotPresent } + return value + } + + public static func optional(_ document: (any SmithyDocument)?) throws -> Document? { + return try document.map { Document($0) } + } + + public static func required(_ document: (any SmithyDocument)?) throws -> Document { + guard let value: Document = try optional(document) else { throw ReaderError.requiredValueNotPresent } + return value + } +} diff --git a/Sources/SmithyReadWrite/Schema/Require.swift b/Sources/SmithyReadWrite/Schema/Require.swift new file mode 100644 index 000000000..5e564b9ce --- /dev/null +++ b/Sources/SmithyReadWrite/Schema/Require.swift @@ -0,0 +1,24 @@ +// +// File.swift +// smithy-swift +// +// Created by Elkins, Josh on 12/26/24. +// + +//import Foundation +// +//@_spi(SchemaBasedSerdeImpl) +//extension Optional { +// +// func require() throws -> DeserializableShape { +// guard self else { throw ReaderError.requiredValueNotPresent } +// return self +// } +// +// func require() throws -> Array { +// guard self else { throw ReaderError.requiredValueNotPresent } +// return self +// } +// +// func require +//} diff --git a/Sources/SmithyReadWrite/Schema/Schema.swift b/Sources/SmithyReadWrite/Schema/Schema.swift index b122cee8c..b07111b79 100644 --- a/Sources/SmithyReadWrite/Schema/Schema.swift +++ b/Sources/SmithyReadWrite/Schema/Schema.swift @@ -5,7 +5,9 @@ // SPDX-License-Identifier: Apache-2.0 // -import Smithy +import struct Foundation.Data +import struct Foundation.Date +@_spi(SmithyDocumentImpl) import Smithy @_spi(SmithyTimestamps) import SmithyTimestamps @_spi(SchemaBasedSerde) @@ -18,6 +20,44 @@ public protocol SchemaProtocol: AnyObject { func write(writer: any SmithyWriter) throws } +public extension SchemaProtocol { + + var defaultValue: (any SmithyDocument)? { nil } + + var lastResortDefaultValue: any SmithyDocument { + get throws { + switch type { + case .structure, .union, .map: return StringMapDocument(value: [:]) + case .string, .enum: return StringDocument(value: "") + case .integer, .intEnum: return IntegerDocument(value: 0) + case .boolean: return BooleanDocument(value: false) + case .blob: return BlobDocument(value: Data()) + case .timestamp: return TimestampDocument(value: Date(timeIntervalSince1970: 0.0)) + case .bigDecimal: return BigDecimalDocument(value: 0.0) + case .bigInteger: return BigIntegerDocument(value: 0) + case .byte: return ByteDocument(value: 0) + case .document: return NullDocument() + case .list, .set: return ListDocument(value: []) + case .short: return ShortDocument(value: 0) + case .long: return LongDocument(value: 0) + case .float: return FloatDocument(value: 0.0) + case .double: return DoubleDocument(value: 0.0) + case .member, .service, .resource, .operation: + throw ReaderError.invalidSchema("Last resort not defined for type \(type)") + } + } + } +} + +@_spi(SchemaBasedSerde) +public protocol MemberSchemaProtocol: SchemaProtocol { + var memberName: String? { get } + var jsonName: String? { get } + var xmlName: String? { get } + var isRequired: Bool { get } + var defaultValue: (any SmithyDocument)? { get } +} + //extension SchemaProtocol { // // public var id: String { ["\(namespace)#\(name)", memberName].compactMap { $0 }.joined(separator: "$") } @@ -27,15 +67,15 @@ public protocol SchemaProtocol: AnyObject { public class StructureSchema: SchemaProtocol { public struct Member { - public let memberSchema: () -> SchemaProtocol + public let memberSchema: () -> MemberSchemaProtocol public let targetSchema: () -> SchemaProtocol - public let readBlock: (inout Base, any ShapeDeserializer) throws -> Void + public let readBlock: (inout Base, any ShapeDeserializer, (any SmithyDocument)?) throws -> Void public let writeBlock: (Base, any SmithyWriter) throws -> Void public init( - memberSchema: @escaping () -> SchemaProtocol, + memberSchema: @escaping () -> MemberSchemaProtocol, targetSchema: @escaping () -> SchemaProtocol, - readBlock: @escaping (inout Base, any ShapeDeserializer) throws -> Void, + readBlock: @escaping (inout Base, any ShapeDeserializer, (any SmithyDocument)?) throws -> Void, writeBlock: @escaping (Base, any SmithyWriter) throws -> Void ) { self.memberSchema = memberSchema @@ -47,7 +87,7 @@ public class StructureSchema: SchemaProtocol { // public let namespace: String // public let name: String - public let type: ShapeType + public var type: ShapeType public let members: [Member] public let memberName: String? @@ -155,7 +195,7 @@ public class MapSchema: SchemaProtocol { } @_spi(SchemaBasedSerde) -public class MemberSchema: SchemaProtocol { +public class MemberSchema: MemberSchemaProtocol { // public let namespace: String // public let name: String public let type: ShapeType diff --git a/Sources/SmithyReadWrite/ShapeDeserializer.swift b/Sources/SmithyReadWrite/ShapeDeserializer.swift index 9e79d7a74..4fdf5096c 100644 --- a/Sources/SmithyReadWrite/ShapeDeserializer.swift +++ b/Sources/SmithyReadWrite/ShapeDeserializer.swift @@ -6,6 +6,7 @@ // import struct Smithy.Document +@_spi(SmithyDocumentImpl) import struct Smithy.NullDocument import struct Foundation.Data import struct Foundation.Date @@ -43,139 +44,158 @@ public extension ShapeDeserializer { return T(rawValue: rawValue) } - func readEnumNonNull(schema: SchemaProtocol) throws -> T where T.RawValue == String { - guard let value: T = try readEnum(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readIntEnumNonNull(schema: SchemaProtocol) throws -> T where T.RawValue == Int { - guard let value: T = try readIntEnum(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } +// func readEnumNonNull(schema: SchemaProtocol) throws -> T where T.RawValue == String { +// guard let value: T = try readEnum(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +// return T() +// } +// return value +// } +// +// func readIntEnumNonNull(schema: SchemaProtocol) throws -> T where T.RawValue == Int { +// guard let value: T = try readIntEnum(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +// return T() +// } +// return value +// } } -@_spi(SchemaBasedSerde) -public extension ShapeDeserializer { - - func readStructureNonNull(schema: StructureSchema) throws -> T { - guard let value = try readStructure(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - func readListNonNull(schema: ListSchema) throws -> [T] { - guard let value = try readList(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readMapNonNull(schema: MapSchema) throws -> [String: T] { - guard let value = try readMap(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readBooleanNonNull(schema: SimpleSchema) throws -> Bool { - guard let value = try readBoolean(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readByteNonNull(schema: SimpleSchema) throws -> Int8 { - guard let value = try readByte(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readShortNonNull(schema: SimpleSchema) throws -> Int16 { - guard let value = try readShort(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readIntegerNonNull(schema: SimpleSchema) throws -> Int { - guard let value = try readInteger(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readLongNonNull(schema: SimpleSchema) throws -> Int { - guard let value = try readLong(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readFloatNonNull(schema: SimpleSchema) throws -> Float { - guard let value = try readFloat(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readDoubleNonNull(schema: SimpleSchema) throws -> Double { - guard let value = try readDouble(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readBigIntegerNonNull(schema: SimpleSchema) throws -> Int { - guard let value = try readBigInteger(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readBigDecimalNonNull(schema: SimpleSchema) throws -> Float { - guard let value = try readBigDecimal(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readStringNonNull(schema: SimpleSchema) throws -> String { - guard let value = try readString(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readBlobNonNull(schema: SimpleSchema) throws -> Data { - guard let value = try readBlob(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readTimestampNonNull(schema: SimpleSchema) throws -> Date { - guard let value = try readTimestamp(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readDocumentNonNull(schema: SimpleSchema) throws -> Document { - guard let value = try readDocument(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } - - func readNullNonNull(schema: SimpleSchema) throws -> Bool { - guard let value = try readNull(schema: schema) else { - throw ReaderError.requiredValueNotPresent - } - return value - } -} +//@_spi(SchemaBasedSerde) +//public extension ShapeDeserializer { +// +// func readStructureNonNull(schema: StructureSchema) throws -> T { +//// guard let value = try readStructure(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readStructure(schema: schema) ?? T() +// } +// func readListNonNull(schema: ListSchema) throws -> [T] { +//// guard let value = try readList(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readList(schema: schema) ?? [] +// } +// +// func readMapNonNull(schema: MapSchema) throws -> [String: T] { +//// guard let value = try readMap(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readMap(schema: schema) ?? [:] +// } +// +// func readBooleanNonNull(schema: SimpleSchema) throws -> Bool { +//// guard let value = try readBoolean(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readBoolean(schema: schema) ?? false +// } +// +// func readByteNonNull(schema: SimpleSchema) throws -> Int8 { +//// guard let value = try readByte(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readByte(schema: schema) ?? 0 +// } +// +// func readShortNonNull(schema: SimpleSchema) throws -> Int16 { +//// guard let value = try readShort(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readShort(schema: schema) ?? 0 +// } +// +// func readIntegerNonNull(schema: SimpleSchema) throws -> Int { +//// guard let value = try readInteger(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readInteger(schema: schema) ?? 0 +// } +// +// func readLongNonNull(schema: SimpleSchema) throws -> Int { +//// guard let value = try readLong(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readLong(schema: schema) ?? 0 +// } +// +// func readFloatNonNull(schema: SimpleSchema) throws -> Float { +//// guard let value = try readFloat(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readFloat(schema: schema) ?? 0.0 +// } +// +// func readDoubleNonNull(schema: SimpleSchema) throws -> Double { +//// guard let value = try readDouble(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readDouble(schema: schema) ?? 0.0 +// } +// +// func readBigIntegerNonNull(schema: SimpleSchema) throws -> Int { +//// guard let value = try readBigInteger(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readBigInteger(schema: schema) ?? 0 +// } +// +// func readBigDecimalNonNull(schema: SimpleSchema) throws -> Float { +//// guard let value = try readBigDecimal(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readBigDecimal(schema: schema) ?? 0.0 +// } +// +// func readStringNonNull(schema: SimpleSchema) throws -> String { +//// guard let value = try readString(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readString(schema: schema) ?? "" +// } +// +// func readBlobNonNull(schema: SimpleSchema) throws -> Data { +//// guard let value = try readBlob(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readBlob(schema: schema) ?? Data() +// } +// +// func readTimestampNonNull(schema: SimpleSchema) throws -> Date { +//// guard let value = try readTimestamp(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readTimestamp(schema: schema) ?? Date(timeIntervalSince1970: 0.0) +// } +// +// func readDocumentNonNull(schema: SimpleSchema) throws -> Document { +//// guard let value = try readDocument(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readDocument(schema: schema) ?? Document(NullDocument()) +// } +// +// func readNullNonNull(schema: SimpleSchema) throws -> Bool { +//// guard let value = try readNull(schema: schema) else { +//// throw ReaderError.requiredValueNotPresent +//// } +//// return value +// return try readNull(schema: schema) ?? false +// } +//} diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EnumGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EnumGenerator.kt index ce41d4878..ee2b2dfb0 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EnumGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EnumGenerator.kt @@ -15,6 +15,7 @@ import software.amazon.smithy.swift.codegen.customtraits.NestedTrait import software.amazon.smithy.swift.codegen.model.expectShape import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.model.nestedNamespaceType +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SwiftTypes /** @@ -142,14 +143,19 @@ class EnumGenerator( private fun renderEnum() { writer.writeShapeDocs(shape) writer.writeAvailableAttribute(null, shape) - writer.openBlock( - "public enum \$enum.name:L: \$N, \$N, \$N, \$N, \$N {", - "}", + val conformances = writer.format( + "\$N, \$N, \$N, \$N, \$N", SwiftTypes.Protocols.Sendable, SwiftTypes.Protocols.Equatable, SwiftTypes.Protocols.RawRepresentable, SwiftTypes.Protocols.CaseIterable, - SwiftTypes.Protocols.Hashable + SwiftTypes.Protocols.Hashable, + ) + writer.openBlock( + "public enum \$enum.name:L: \$L {", + "}", + conformances, +// SmithyReadWriteTypes.DeserializableShape, ) { createEnumWriterContexts() // add the sdkUnknown case which will always be last @@ -166,6 +172,9 @@ class EnumGenerator( // Generate rawValue internal enum generateRawValueEnumBlock() + + writer.write("") + writer.write("public init() { self = .sdkUnknown(\"\") }") } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/IntEnumGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/IntEnumGenerator.kt index 45b27b3d7..94a885dbd 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/IntEnumGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/IntEnumGenerator.kt @@ -10,6 +10,7 @@ import software.amazon.smithy.swift.codegen.customtraits.NestedTrait import software.amazon.smithy.swift.codegen.model.expectShape import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.model.nestedNamespaceType +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SwiftTypes class IntEnumGenerator( @@ -41,14 +42,19 @@ class IntEnumGenerator( private fun renderEnum() { writer.writeShapeDocs(shape) writer.writeAvailableAttribute(null, shape) - writer.openBlock( - "public enum \$enum.name:L: \$N, \$N, \$N, \$N, \$N {", - "}", + val conformances = writer.format( + "\$N, \$N, \$N, \$N, \$N", SwiftTypes.Protocols.Sendable, SwiftTypes.Protocols.Equatable, SwiftTypes.Protocols.RawRepresentable, SwiftTypes.Protocols.CaseIterable, - SwiftTypes.Protocols.Hashable + SwiftTypes.Protocols.Hashable, + ) + writer.openBlock( + "public enum \$enum.name:L: \$L {", + "}", + conformances, +// SmithyReadWriteTypes.DeserializableShape, ) { createEnumWriterContexts() // add the sdkUnknown case which will always be last @@ -65,6 +71,9 @@ class IntEnumGenerator( // Generate rawValue internal enum generateRawValueEnumBlock() + + writer.write("") + writer.write("public init() { self = .sdkUnknown(0) }") } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt index 71e6c6803..b55af9653 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt @@ -219,10 +219,11 @@ class MessageUnmarshallableGenerator( private fun renderReadWithSchemaToValue(writer: SwiftWriter, memberShape: MemberShape) { val target = ctx.model.expectShape(memberShape.target) writer.write( - "let value = try \$N.from(data: message.payload).\$LNonNull(schema: \$L)", + "let value = try \$N.from(data: message.payload).\$L(schema: \$L) ?? \$N.required(nil)", ctx.service.readerSymbol, target.readMethodName, target.id.schemaVar(writer), + SmithyReadWriteTypes.DefaultValueTransformer, ) } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt index 28a69465c..c70c0b26c 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt @@ -224,7 +224,7 @@ abstract class HTTPBindingProtocolGenerator( private fun renderSchemas(ctx: ProtocolGenerator.GenerationContext, shape: Shape) { val symbol: Symbol = ctx.symbolProvider.toSymbol(shape) val symbolName = symbol.name - val filename = ModelFileUtils.filename(ctx.settings, "$symbolName+Schema") + val filename = ModelFileUtils.filename(ctx.settings, "${symbol.name}+Schema") val encodeSymbol = Symbol.builder() .definitionFile(filename) .name(symbolName) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingErrorInitGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingErrorInitGenerator.kt index 15a1ceba7..62e7fd3d1 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingErrorInitGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingErrorInitGenerator.kt @@ -13,6 +13,8 @@ import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator import software.amazon.smithy.swift.codegen.integration.SectionId import software.amazon.smithy.swift.codegen.integration.httpResponse.bindingTraits.HTTPResponseTraitPayload import software.amazon.smithy.swift.codegen.integration.httpResponse.bindingTraits.HTTPResponseTraitResponseCode +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.AWSProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.awsProtocol import software.amazon.smithy.swift.codegen.integration.serde.struct.readerSymbol import software.amazon.smithy.swift.codegen.utils.ModelFileUtils @@ -53,6 +55,9 @@ class HTTPResponseBindingErrorInitGenerator( if (needsReader) { writer.addImport(ctx.service.readerSymbol) writer.write("let reader = baseError.errorBodyReader") + if (ctx.service.awsProtocol == AWSProtocol.REST_JSON_1) { + writer.write("reader.respectsJSONName = true") + } } if (needsResponse) { writer.write("let httpResponse = baseError.httpResponse") diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingOutputGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingOutputGenerator.kt index d680f39b0..c3819eb21 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingOutputGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingOutputGenerator.kt @@ -66,6 +66,9 @@ class HTTPResponseBindingOutputGenerator( writer.write("let data = try await httpResponse.data()") writer.write("let responseReader = try \$N.from(data: data)", ctx.service.readerSymbol) writer.write("let reader = \$L", reader(ctx, op, writer)) + if (ctx.service.awsProtocol == AWSProtocol.REST_JSON_1) { + writer.write("reader.respectsJSONName = true") + } } writer.write("var value = \$N()", outputSymbol) HTTPResponseHeaders(ctx, false, headerBindings, defaultTimestampFormat, writer).render() diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt index 2d4255458..4175b6d21 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt @@ -54,6 +54,7 @@ import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.model.isError import software.amazon.smithy.swift.codegen.swiftEnumCaseName import software.amazon.smithy.swift.codegen.swiftmodules.FoundationTypes +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTimestampsTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTypes @@ -88,10 +89,11 @@ open class MemberShapeDecodeGenerator( if (useSBS) { val target = ctx.model.expectShape(memberShape.target) return writer.format( - "try \$L.readStructure\$L(schema: \$L)", + "try \$L.readStructure(schema: \$L) ?? \$N.\$L(nil)", reader(memberShape, isPayload), - "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", memberShape.target.schemaVar(writer), + SmithyReadWriteTypes.DefaultValueTransformer, + "required".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "optional", ) } val readingClosure = readingClosureUtils.readingClosure(memberShape) @@ -107,10 +109,11 @@ open class MemberShapeDecodeGenerator( if (useSBS) { val target = ctx.model.expectShape(memberShape.target) return writer.format( - "try \$L.readList\$L(schema: \$L)", + "try \$L.readList(schema: \$L) ?? \$N.\$L(nil)", reader(memberShape, false), - "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", memberShape.target.schemaVar(writer), + SmithyReadWriteTypes.DefaultValueTransformer, + "required".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "optional", ) } val isSparse = listShape.hasTrait() @@ -132,10 +135,11 @@ open class MemberShapeDecodeGenerator( if (useSBS) { val target = ctx.model.expectShape(memberShape.target) return writer.format( - "try \$L.readMap\$L(schema: \$L)", + "try \$L.readMap(schema: \$L) ?? \$N.\$L(nil)", reader(memberShape, false), - "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", memberShape.target.schemaVar(writer), + SmithyReadWriteTypes.DefaultValueTransformer, + "required".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "optional", ) } val isSparse = mapShape.hasTrait() @@ -175,11 +179,12 @@ open class MemberShapeDecodeGenerator( if (useSBS) { val target = ctx.model.expectShape(memberShape.target) return writer.format( - "try \$L.\$L\$L(schema: \$L)", + "try \$L.\$L(schema: \$L) ?? \$N.\$L(nil)", reader(memberShape, isPayload), target.readMethodName, - "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", memberShape.target.schemaVar(writer), + SmithyReadWriteTypes.DefaultValueTransformer, + "required".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "optional", ) } return writer.format( diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt index 085b4db33..170fdada5 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt @@ -1,11 +1,14 @@ package software.amazon.smithy.swift.codegen.integration.serde.schema import software.amazon.smithy.codegen.core.Symbol +import software.amazon.smithy.model.node.NodeType import software.amazon.smithy.model.shapes.MemberShape import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.model.shapes.ShapeType +import software.amazon.smithy.model.shapes.StructureShape +import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.model.traits.DefaultTrait import software.amazon.smithy.model.traits.EnumTrait import software.amazon.smithy.model.traits.ErrorTrait @@ -50,7 +53,7 @@ class SchemaGenerator( // writer.write("name: \$S,", shape.id.name) writer.write("type: .\$L,", shape.type) when (shape.type) { - ShapeType.STRUCTURE -> { + ShapeType.STRUCTURE, ShapeType.UNION -> { if (shape.members().isNotEmpty()) { writer.openBlock("members: [", "],") { shape.members().forEach { member -> @@ -94,14 +97,14 @@ class SchemaGenerator( if (isRequired(shape)) { writer.write("isRequired: true,") } - defaultValue(shape)?.let { writer.write("defaultValue: \$N(value: \$S),", SmithyTypes.StringDocument, it) } + defaultValue(shape)?.let { writer.write("defaultValue: \$L,", it) } } else -> { timestampFormat(shape)?.let { writer.addImport(SmithyTimestampsTypes.TimestampFormat) writer.write("timestampFormat: .\$L", it.swiftEnumCase) } - defaultValue(shape)?.let { writer.write("defaultValue: \$N(value: \$S),", SmithyTypes.StringDocument, it) } + defaultValue(shape)?.let { writer.write("defaultValue: \$L,", it) } } } writer.unwrite(",\n") @@ -127,17 +130,12 @@ class SchemaGenerator( val service = ctx.model.getShape(ctx.settings.service).get() as ServiceShape return when (shape.type) { ShapeType.STRUCTURE, ShapeType.UNION -> { -// if (shape.id.namespace == "smithy.api" && shape.id.name == "Unit") { -// SwiftTypes.Void -// } else { - val symbol = ctx.symbolProvider.toSymbol(shape) - if (shape.hasTrait() && !shape.hasTrait()) { - return symbol.toBuilder().namespace(service.nestedNamespaceType(ctx.symbolProvider).name, ".").build() - } else { - return symbol - } - -// } + val symbol = ctx.symbolProvider.toSymbol(shape) + if (shape.hasTrait() && !shape.hasTrait()) { + return symbol.toBuilder().namespace(service.nestedNamespaceType(ctx.symbolProvider).name, ".").build() + } else { + return symbol + } } ShapeType.LIST, ShapeType.SET -> { val target = shape.members() @@ -189,20 +187,38 @@ class SchemaGenerator( private fun writeSetterGetter(ctx: ProtocolGenerator.GenerationContext, writer: SwiftWriter, shape: Shape, member: MemberShape) { val target = ctx.model.expectShape(member.target) val readMethodName = target.readMethodName - val mod = "NonNull".takeIf { member.isRequired || (member.hasTrait() || target.hasTrait()) } ?: "" + val memberIsRequired = member.isRequired + || (member.hasTrait() || target.hasTrait()) + || (shape.isMapShape && member.memberName == "key") + || (shape.isMapShape && member.memberName == "value" && !shape.hasTrait()) + || (shape.isListShape && !shape.hasTrait()) + val transformMethodName = "required".takeIf { memberIsRequired || shape.isUnionShape } ?: "optional" when (shape.type) { ShapeType.STRUCTURE -> { val path = "properties.".takeIf { shape.hasTrait() } ?: "" - writer.write("readBlock: { \$\$0.\$L\$L = try \$\$1.\$L\$L(schema: \$L) },", path, ctx.symbolProvider.toMemberName(member), readMethodName, mod, target.id.schemaVar(writer)) + writer.write( + "readBlock: { \$\$0.\$L\$L = try \$\$1.\$L(schema: \$L) ?? \$N.\$L(\$\$2) },", + path, + ctx.symbolProvider.toMemberName(member), + readMethodName, + target.id.schemaVar(writer), + SmithyReadWriteTypes.DefaultValueTransformer, + transformMethodName, + ) writer.write("writeBlock: { _, _ in }") } ShapeType.UNION -> { - writer.write("readBlock: { \$\$0 = .\$L(try \$\$1.\$LNonNull(schema: \$L)) },", ctx.symbolProvider.toMemberName(member), readMethodName, target.id.schemaVar(writer)) + writer.write("readBlock: { \$\$0 = .\$L(try \$\$1.\$L(schema: \$L) ?? \$N.\$L(\$\$2)) },", ctx.symbolProvider.toMemberName(member), readMethodName, target.id.schemaVar(writer), + SmithyReadWriteTypes.DefaultValueTransformer, + transformMethodName, + ) writer.write("writeBlock: { _, _ in }") } ShapeType.LIST, ShapeType.SET, ShapeType.MAP -> { - val nonNullModifier = "NonNull".takeIf { !shape.hasTrait() } ?: "" - writer.write("readBlock: { try \$\$0.\$L\$L(schema: \$L) },", readMethodName, nonNullModifier, target.id.schemaVar(writer)) + writer.write("readBlock: { try \$\$0.\$L(schema: \$L) ?? \$N.\$L(nil) },", readMethodName, target.id.schemaVar(writer), + SmithyReadWriteTypes.DefaultValueTransformer, + transformMethodName, + ) writer.write("writeBlock: { _, _ in }") } else -> {} @@ -222,7 +238,17 @@ class SchemaGenerator( } private fun defaultValue(shape: Shape): String? { - return shape.getTrait()?.let { it.toNode().asStringNode().getOrNull()?.toString() } + return shape.getTrait()?.let { + val node = it.toNode() + when (node.type) { + NodeType.STRING -> writer.format("\$N(value: \$S)", SmithyTypes.StringDocument, node.toString()) + NodeType.BOOLEAN -> writer.format("\$N(value: \$L)", SmithyTypes.BooleanDocument, node.toString()) + NodeType.NUMBER -> writer.format("\$N(value: \$L)", SmithyTypes.DoubleDocument, node.toString()) + NodeType.ARRAY -> writer.format("\$N(value: [])", SmithyTypes.ListDocument) + NodeType.OBJECT -> writer.format("\$N(value: [:])", SmithyTypes.StringMapDocument) + NodeType.NULL -> writer.format("\$N()", SmithyTypes.NullDocument) + } + } } private fun timestampFormat(shape: Shape): TimestampFormatTrait.Format? { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt index 66cd696d9..5cbb715dc 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt @@ -45,6 +45,7 @@ object SmithyReadWriteTypes { val documentSchema = runtimeSymbol("documentSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) val blobSchema = runtimeSymbol("blobSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) val timestampSchema = runtimeSymbol("timestampSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) + val DefaultValueTransformer = runtimeSymbol("DefaultValueTransformer", SwiftDeclaration.ENUM, listOf("SchemaBasedSerde")) } private fun runtimeSymbol( From b5708e9059121331a38ff12df23e64ea9e15f5b4 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Fri, 27 Dec 2024 16:50:52 -0600 Subject: [PATCH 05/16] Schema simplified, some tests failing --- .../Reader/Reader+ShapeDeserializer.swift | 89 +++-- Sources/SmithyReadWrite/ReadingClosure.swift | 4 +- .../Schema/DefaultValueTransformers.swift | 272 +++++++------- Sources/SmithyReadWrite/Schema/Prelude.swift | 94 ----- Sources/SmithyReadWrite/Schema/Schema.swift | 245 ++++--------- .../SmithyReadWrite/ShapeDeserializer.swift | 340 +++++++++--------- .../swift/codegen/SwiftSymbolProvider.kt | 5 + .../events/MessageUnmarshallableGenerator.kt | 5 +- .../HTTPBindingProtocolGenerator.kt | 30 +- .../member/MemberShapeDecodeGenerator.kt | 28 +- .../serde/schema/SchemaGenerator.kt | 278 ++++---------- .../serde/schema/SchemaShapeUtils.kt | 46 +++ .../SmithyEnumUnitIntegration.kt | 52 +++ .../swiftmodules/SmithyReadWriteTypes.kt | 19 +- ...swift.codegen.integration.SwiftIntegration | 3 +- 15 files changed, 630 insertions(+), 880 deletions(-) delete mode 100644 Sources/SmithyReadWrite/Schema/Prelude.swift create mode 100644 smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaShapeUtils.kt create mode 100644 smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftintegrations/SmithyEnumUnitIntegration.kt diff --git a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift index 6363e4827..d377b837d 100644 --- a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift +++ b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift @@ -13,39 +13,39 @@ import enum SmithyReadWrite.ReaderError @_spi(SchemaBasedSerde) import protocol SmithyReadWrite.ShapeDeserializer @_spi(SchemaBasedSerde) import protocol SmithyReadWrite.SchemaProtocol @_spi(SchemaBasedSerde) import protocol SmithyReadWrite.DeserializableShape -@_spi(SchemaBasedSerde) import protocol SmithyReadWrite.MemberSchemaProtocol -@_spi(SchemaBasedSerde) import class SmithyReadWrite.StructureSchema -@_spi(SchemaBasedSerde) import class SmithyReadWrite.ListSchema -@_spi(SchemaBasedSerde) import class SmithyReadWrite.MapSchema -@_spi(SchemaBasedSerde) import class SmithyReadWrite.SimpleSchema +@_spi(SchemaBasedSerde) import class SmithyReadWrite.Schema @_spi(SmithyTimestamps) import enum SmithyTimestamps.TimestampFormat @_spi(SchemaBasedSerde) extension Reader: SmithyReadWrite.ShapeDeserializer { - public func readStructure( - schema: SmithyReadWrite.StructureSchema - ) throws -> Base? { + public func readStructure( + schema: SmithyReadWrite.Schema + ) throws -> Target? { // TODO: Implement me guard self.hasContent else { return nil } - var value = Base() - try schema.members.forEach { member in - let resolvedName = try resolvedName(memberSchema: member.memberSchema()) - guard let resolvedName = member.memberSchema().jsonName ?? member.memberSchema().memberName else { + var value = Target() + try schema.members.forEach { memberContainer in + let memberSchema = memberContainer.member.memberSchema + guard let targetSchema = memberSchema.targetSchema() else { + throw ReaderError.requiredValueNotPresent + } + let resolvedName = try resolvedName(memberSchema: memberSchema) + guard let resolvedName = memberSchema.jsonName ?? memberSchema.memberName else { throw ReaderError.requiredValueNotPresent } let resolvedReader = self[NodeInfo(resolvedName)] guard resolvedReader.hasContent else { return } - var resolvedDefault = member.targetSchema().defaultValue ?? member.memberSchema().defaultValue - if member.memberSchema().isRequired { - resolvedDefault = try resolvedDefault ?? member.targetSchema().lastResortDefaultValue + var resolvedDefault = targetSchema.defaultValue ?? memberSchema.defaultValue + if memberSchema.isRequired { + resolvedDefault = try resolvedDefault ?? targetSchema.lastResortDefaultValue } - try member.readBlock(&value, resolvedReader, resolvedDefault) + try memberContainer.performRead(base: &value, reader: resolvedReader) } return value } - private func resolvedName(memberSchema: SmithyReadWrite.MemberSchemaProtocol) throws -> String { + private func resolvedName(memberSchema: SmithyReadWrite.SchemaProtocol) throws -> String { if respectsJSONName { guard let resolvedName = memberSchema.jsonName ?? memberSchema.memberName else { throw ReaderError.requiredValueNotPresent @@ -59,71 +59,84 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { } } - public func readString(schema: SmithyReadWrite.SchemaProtocol) throws -> String? { + public func readString(schema: SmithyReadWrite.Schema) throws -> String? { try readIfPresent() } - public func readList(schema: SmithyReadWrite.ListSchema) throws -> [T]? { + public func readList(schema: SmithyReadWrite.Schema<[T]>) throws -> [T]? { guard hasContent, jsonNode == .array else { return nil } - return try children.map { reader in - try schema.readBlock(reader) + guard let memberContainer = schema.members.first(where: { $0.member.memberSchema.memberName == "member" }) else { + throw ReaderError.requiredValueNotPresent } + var value = [T]() + try children.forEach { reader in + try memberContainer.performRead(base: &value, reader: reader) + } + return value } - public func readMap(schema: SmithyReadWrite.MapSchema) throws -> [String : T]? { - // TODO: Implement me + public func readMap(schema: SmithyReadWrite.Schema<[String: T]>) throws -> [String : T]? { guard hasContent, jsonNode == .object else { return nil } - let pairs = try children.map { reader in - return (reader.nodeInfo.name, try schema.readBlock(reader)) + guard let keyContainer = schema.members.first(where: { $0.member.memberSchema.memberName == "key" }) else { + throw ReaderError.requiredValueNotPresent + } + guard let valueContainer = schema.members.first(where: { $0.member.memberSchema.memberName == "value" }) else { + throw ReaderError.requiredValueNotPresent } - return Dictionary(uniqueKeysWithValues: pairs) + var value = [String: T]() + try children.forEach { reader in + var temp = [String: T]() + try valueContainer.performRead(base: &temp, reader: reader) + value[reader.nodeInfo.name] = temp["value"] + } + return value } - public func readBoolean(schema: any SmithyReadWrite.SchemaProtocol) throws -> Bool? { + public func readBoolean(schema: SmithyReadWrite.Schema) throws -> Bool? { try readIfPresent() } - public func readByte(schema: any SmithyReadWrite.SchemaProtocol) throws -> Int8? { + public func readByte(schema: SmithyReadWrite.Schema) throws -> Int8? { try readIfPresent() } - public func readShort(schema: any SmithyReadWrite.SchemaProtocol) throws -> Int16? { + public func readShort(schema: SmithyReadWrite.Schema) throws -> Int16? { try readIfPresent() } - public func readInteger(schema: any SmithyReadWrite.SchemaProtocol) throws -> Int? { + public func readInteger(schema: SmithyReadWrite.Schema) throws -> Int? { try readIfPresent() } - public func readLong(schema: any SmithyReadWrite.SchemaProtocol) throws -> Int? { + public func readLong(schema: SmithyReadWrite.Schema) throws -> Int? { try readIfPresent() } - public func readFloat(schema: any SmithyReadWrite.SchemaProtocol) throws -> Float? { + public func readFloat(schema: SmithyReadWrite.Schema) throws -> Float? { try readIfPresent() } - public func readDouble(schema: any SmithyReadWrite.SchemaProtocol) throws -> Double? { + public func readDouble(schema: SmithyReadWrite.Schema) throws -> Double? { try readIfPresent() } - public func readBigInteger(schema: any SmithyReadWrite.SchemaProtocol) throws -> Int? { + public func readBigInteger(schema: SmithyReadWrite.Schema) throws -> Int? { try readIfPresent() } - public func readBigDecimal(schema: any SmithyReadWrite.SchemaProtocol) throws -> Float? { + public func readBigDecimal(schema: SmithyReadWrite.Schema) throws -> Double? { try readIfPresent() } - public func readBlob(schema: any SmithyReadWrite.SchemaProtocol) throws -> Data? { + public func readBlob(schema: SmithyReadWrite.Schema) throws -> Data? { try readIfPresent() } - public func readTimestamp(schema: SmithyReadWrite.SimpleSchema) throws -> Date? { + public func readTimestamp(schema: SmithyReadWrite.Schema) throws -> Date? { try readTimestampIfPresent(format: schema.timestampFormat ?? .epochSeconds) } - public func readDocument(schema: any SmithyReadWrite.SchemaProtocol) throws -> Document? { + public func readDocument(schema: SmithyReadWrite.Schema) throws -> Document? { try readIfPresent() } diff --git a/Sources/SmithyReadWrite/ReadingClosure.swift b/Sources/SmithyReadWrite/ReadingClosure.swift index bc8f4999f..f09498a75 100644 --- a/Sources/SmithyReadWrite/ReadingClosure.swift +++ b/Sources/SmithyReadWrite/ReadingClosure.swift @@ -181,11 +181,11 @@ public enum ReadingClosures { try reader.readIfPresent() } - public static func readSmithyDocument(from reader: Reader) throws -> SmithyDocument { + public static func readDocument(from reader: Reader) throws -> Document { Document(try reader.read()) } - public static func readSmithyDocument(from reader: Reader) throws -> SmithyDocument? { + public static func readDocument(from reader: Reader) throws -> Document? { (try reader.readIfPresent()).map { Document($0) } } } diff --git a/Sources/SmithyReadWrite/Schema/DefaultValueTransformers.swift b/Sources/SmithyReadWrite/Schema/DefaultValueTransformers.swift index bde47f0da..e808e9130 100644 --- a/Sources/SmithyReadWrite/Schema/DefaultValueTransformers.swift +++ b/Sources/SmithyReadWrite/Schema/DefaultValueTransformers.swift @@ -13,140 +13,140 @@ import struct Smithy.Document @_spi(SchemaBasedSerde) public enum DefaultValueTransformer { - public static func optional(_ document: (any SmithyDocument)?) throws -> Base? { - return document != nil ? Base() : nil - } - - public static func required(_ document: (any SmithyDocument)?) throws -> Base { - guard let value: Base = try optional(document) else { throw ReaderError.requiredValueNotPresent } - return value - } - - public static func optional(_ document: (any SmithyDocument)?) throws -> [Element]? { - return document != nil ? [] : nil - } - - public static func required(_ document: (any SmithyDocument)?) throws -> [Element] { - guard let value: [Element] = try optional(document) else { throw ReaderError.requiredValueNotPresent } - return value - } - - public static func optional(_ document: (any SmithyDocument)?) throws -> [String: Element]? { - return document != nil ? [:] : nil - } - - public static func required(_ document: (any SmithyDocument)?) throws -> [String: Element] { - guard let value: [String: Element] = try optional(document) else { throw ReaderError.requiredValueNotPresent } - return value - } - - public static func optional(_ document: (any SmithyDocument)?) throws -> Enum? where Enum.RawValue == String { - guard let rawValue = try document?.asString() else { return nil } - return Enum(rawValue: rawValue) - } - - public static func required(_ document: (any SmithyDocument)?) throws -> Enum where Enum.RawValue == String { - guard let value: Enum = try optional(document) else { throw ReaderError.requiredValueNotPresent } - return value - } - - public static func optional(_ document: (any SmithyDocument)?) throws -> IntEnum? where IntEnum.RawValue == Int { - guard let rawValue = try document?.asInteger() else { return nil } - return IntEnum(rawValue: rawValue) - } - - public static func required(_ document: (any SmithyDocument)?) throws -> IntEnum where IntEnum.RawValue == Int { - guard let value: IntEnum = try optional(document) else { throw ReaderError.requiredValueNotPresent } - return value - } - - public static func optional(_ document: (any SmithyDocument)?) throws -> Bool? { - return try document?.asBoolean() - } - - public static func required(_ document: (any SmithyDocument)?) throws -> Bool { - guard let value: Bool = try optional(document) else { throw ReaderError.requiredValueNotPresent } - return value - } - - public static func optional(_ document: (any SmithyDocument)?) throws -> Data? { - return try document?.asBlob() - } - - public static func required(_ document: (any SmithyDocument)?) throws -> Data { - guard let value: Data = try optional(document) else { throw ReaderError.requiredValueNotPresent } - return value - } - - public static func optional(_ document: (any SmithyDocument)?) throws -> Date? { - return try document?.asTimestamp() - } - - public static func required(_ document: (any SmithyDocument)?) throws -> Date { - guard let value: Date = try optional(document) else { throw ReaderError.requiredValueNotPresent } - return value - } - - public static func optional(_ document: (any SmithyDocument)?) throws -> String? { - return try document?.asString() - } - - public static func required(_ document: (any SmithyDocument)?) throws -> String { - guard let value: String = try optional(document) else { throw ReaderError.requiredValueNotPresent } - return value - } - - public static func optional(_ document: (any SmithyDocument)?) throws -> Int8? { - return try document?.asByte() - } - - public static func required(_ document: (any SmithyDocument)?) throws -> Int8 { - guard let value: Int8 = try optional(document) else { throw ReaderError.requiredValueNotPresent } - return value - } - - public static func optional(_ document: (any SmithyDocument)?) throws -> Int16? { - return try document?.asShort() - } - - public static func required(_ document: (any SmithyDocument)?) throws -> Int16 { - guard let value: Int16 = try optional(document) else { throw ReaderError.requiredValueNotPresent } - return value - } - - public static func optional(_ document: (any SmithyDocument)?) throws -> Int? { - return try document?.asInteger() - } - - public static func required(_ document: (any SmithyDocument)?) throws -> Int { - guard let value: Int = try optional(document) else { throw ReaderError.requiredValueNotPresent } - return value - } - - public static func optional(_ document: (any SmithyDocument)?) throws -> Float? { - return try document?.asFloat() - } - - public static func required(_ document: (any SmithyDocument)?) throws -> Float { - guard let value: Float = try optional(document) else { throw ReaderError.requiredValueNotPresent } - return value - } - - public static func optional(_ document: (any SmithyDocument)?) throws -> Double? { - return try document?.asDouble() - } - - public static func required(_ document: (any SmithyDocument)?) throws -> Double { - guard let value: Double = try optional(document) else { throw ReaderError.requiredValueNotPresent } - return value - } - - public static func optional(_ document: (any SmithyDocument)?) throws -> Document? { - return try document.map { Document($0) } - } - - public static func required(_ document: (any SmithyDocument)?) throws -> Document { - guard let value: Document = try optional(document) else { throw ReaderError.requiredValueNotPresent } - return value - } +// public static func optional(_ document: (any SmithyDocument)?) throws -> Base? { +// return document != nil ? Base() : nil +// } +// +// public static func required(_ document: (any SmithyDocument)?) throws -> Base { +// guard let value: Base = try optional(document) else { throw ReaderError.requiredValueNotPresent } +// return value +// } +// +// public static func optional(_ document: (any SmithyDocument)?) throws -> [Element]? { +// return document != nil ? [] : nil +// } +// +// public static func required(_ document: (any SmithyDocument)?) throws -> [Element] { +// guard let value: [Element] = try optional(document) else { throw ReaderError.requiredValueNotPresent } +// return value +// } +// +// public static func optional(_ document: (any SmithyDocument)?) throws -> [String: Element]? { +// return document != nil ? [:] : nil +// } +// +// public static func required(_ document: (any SmithyDocument)?) throws -> [String: Element] { +// guard let value: [String: Element] = try optional(document) else { throw ReaderError.requiredValueNotPresent } +// return value +// } +// +// public static func optional(_ document: (any SmithyDocument)?) throws -> Enum? where Enum.RawValue == String { +// guard let rawValue = try document?.asString() else { return nil } +// return Enum(rawValue: rawValue) +// } +// +// public static func required(_ document: (any SmithyDocument)?) throws -> Enum where Enum.RawValue == String { +// guard let value: Enum = try optional(document) else { throw ReaderError.requiredValueNotPresent } +// return value +// } +// +// public static func optional(_ document: (any SmithyDocument)?) throws -> IntEnum? where IntEnum.RawValue == Int { +// guard let rawValue = try document?.asInteger() else { return nil } +// return IntEnum(rawValue: rawValue) +// } +// +// public static func required(_ document: (any SmithyDocument)?) throws -> IntEnum where IntEnum.RawValue == Int { +// guard let value: IntEnum = try optional(document) else { throw ReaderError.requiredValueNotPresent } +// return value +// } +// +// public static func optional(_ document: (any SmithyDocument)?) throws -> Bool? { +// return try document?.asBoolean() +// } +// +// public static func required(_ document: (any SmithyDocument)?) throws -> Bool { +// guard let value: Bool = try optional(document) else { throw ReaderError.requiredValueNotPresent } +// return value +// } +// +// public static func optional(_ document: (any SmithyDocument)?) throws -> Data? { +// return try document?.asBlob() +// } +// +// public static func required(_ document: (any SmithyDocument)?) throws -> Data { +// guard let value: Data = try optional(document) else { throw ReaderError.requiredValueNotPresent } +// return value +// } +// +// public static func optional(_ document: (any SmithyDocument)?) throws -> Date? { +// return try document?.asTimestamp() +// } +// +// public static func required(_ document: (any SmithyDocument)?) throws -> Date { +// guard let value: Date = try optional(document) else { throw ReaderError.requiredValueNotPresent } +// return value +// } +// +// public static func optional(_ document: (any SmithyDocument)?) throws -> String? { +// return try document?.asString() +// } +// +// public static func required(_ document: (any SmithyDocument)?) throws -> String { +// guard let value: String = try optional(document) else { throw ReaderError.requiredValueNotPresent } +// return value +// } +// +// public static func optional(_ document: (any SmithyDocument)?) throws -> Int8? { +// return try document?.asByte() +// } +// +// public static func required(_ document: (any SmithyDocument)?) throws -> Int8 { +// guard let value: Int8 = try optional(document) else { throw ReaderError.requiredValueNotPresent } +// return value +// } +// +// public static func optional(_ document: (any SmithyDocument)?) throws -> Int16? { +// return try document?.asShort() +// } +// +// public static func required(_ document: (any SmithyDocument)?) throws -> Int16 { +// guard let value: Int16 = try optional(document) else { throw ReaderError.requiredValueNotPresent } +// return value +// } +// +// public static func optional(_ document: (any SmithyDocument)?) throws -> Int? { +// return try document?.asInteger() +// } +// +// public static func required(_ document: (any SmithyDocument)?) throws -> Int { +// guard let value: Int = try optional(document) else { throw ReaderError.requiredValueNotPresent } +// return value +// } +// +// public static func optional(_ document: (any SmithyDocument)?) throws -> Float? { +// return try document?.asFloat() +// } +// +// public static func required(_ document: (any SmithyDocument)?) throws -> Float { +// guard let value: Float = try optional(document) else { throw ReaderError.requiredValueNotPresent } +// return value +// } +// +// public static func optional(_ document: (any SmithyDocument)?) throws -> Double? { +// return try document?.asDouble() +// } +// +// public static func required(_ document: (any SmithyDocument)?) throws -> Double { +// guard let value: Double = try optional(document) else { throw ReaderError.requiredValueNotPresent } +// return value +// } +// +// public static func optional(_ document: (any SmithyDocument)?) throws -> Document? { +// return try document.map { Document($0) } +// } +// +// public static func required(_ document: (any SmithyDocument)?) throws -> Document { +// guard let value: Document = try optional(document) else { throw ReaderError.requiredValueNotPresent } +// return value +// } } diff --git a/Sources/SmithyReadWrite/Schema/Prelude.swift b/Sources/SmithyReadWrite/Schema/Prelude.swift deleted file mode 100644 index febe05858..000000000 --- a/Sources/SmithyReadWrite/Schema/Prelude.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import struct Foundation.Data -import struct Foundation.Date -import protocol Smithy.SmithyDocument - -@_spi(SchemaBasedSerde) -public let unitSchema = SimpleSchema( - namespace: "smithy.api", - name: "Unit", - type: .structure -) - -@_spi(SchemaBasedSerde) -public let booleanSchema = SimpleSchema( - namespace: "smithy.api", - name: "Boolean", - type: .boolean -) - -@_spi(SchemaBasedSerde) -public let byteSchema = SimpleSchema( - namespace: "smithy.api", - name: "Byte", - type: .byte -) - -@_spi(SchemaBasedSerde) -public let shortSchema = SimpleSchema( - namespace: "smithy.api", - name: "Short", - type: .short -) - -@_spi(SchemaBasedSerde) -public let integerSchema = SimpleSchema( - namespace: "smithy.api", - name: "Integer", - type: .integer -) - -@_spi(SchemaBasedSerde) -public let longSchema = SimpleSchema( - namespace: "smithy.api", - name: "Long", - type: .long -) - -@_spi(SchemaBasedSerde) -public let floatSchema = SimpleSchema( - namespace: "smithy.api", - name: "Float", - type: .float -) - -@_spi(SchemaBasedSerde) -public let doubleSchema = SimpleSchema( - namespace: "smithy.api", - name: "Double", - type: .double -) - -@_spi(SchemaBasedSerde) -public let stringSchema = SimpleSchema( - namespace: "smithy.api", - name: "String", - type: .string -) - -@_spi(SchemaBasedSerde) -public let documentSchema = SimpleSchema( - namespace: "smithy.api", - name: "Document", - type: .document -) - -@_spi(SchemaBasedSerde) -public let blobSchema = SimpleSchema( - namespace: "smithy.api", - name: "Blob", - type: .blob -) - -@_spi(SchemaBasedSerde) -public let timestampSchema = SimpleSchema( - namespace: "smithy.api", - name: "Timestamp", - type: .timestamp -) diff --git a/Sources/SmithyReadWrite/Schema/Schema.swift b/Sources/SmithyReadWrite/Schema/Schema.swift index b07111b79..66426047d 100644 --- a/Sources/SmithyReadWrite/Schema/Schema.swift +++ b/Sources/SmithyReadWrite/Schema/Schema.swift @@ -12,18 +12,20 @@ import struct Foundation.Date @_spi(SchemaBasedSerde) public protocol SchemaProtocol: AnyObject { -// var namespace: String { get } -// var name: String { get } var type: ShapeType { get } + var targetSchema: () -> SchemaProtocol? { get } + var defaultValue: (any SmithyDocument)? { get } + var jsonName: String? { get } + var enumValue: (any SmithyDocument)? { get } + var memberName: String? { get } + var isRequired: Bool { get } - func read(reader: any SmithyReader) throws + func read(reader: any ShapeDeserializer) throws func write(writer: any SmithyWriter) throws } public extension SchemaProtocol { - var defaultValue: (any SmithyDocument)? { nil } - var lastResortDefaultValue: any SmithyDocument { get throws { switch type { @@ -50,219 +52,92 @@ public extension SchemaProtocol { } @_spi(SchemaBasedSerde) -public protocol MemberSchemaProtocol: SchemaProtocol { - var memberName: String? { get } - var jsonName: String? { get } - var xmlName: String? { get } - var isRequired: Bool { get } - var defaultValue: (any SmithyDocument)? { get } +public protocol MemberProtocol { + associatedtype Base + var memberSchema: SchemaProtocol { get } + func performRead(base: inout Base, reader: any ShapeDeserializer) throws + func performWrite(base: Base, writer: any SmithyWriter) throws } -//extension SchemaProtocol { -// -// public var id: String { ["\(namespace)#\(name)", memberName].compactMap { $0 }.joined(separator: "$") } -//} - @_spi(SchemaBasedSerde) -public class StructureSchema: SchemaProtocol { +public class Schema: SchemaProtocol { - public struct Member { - public let memberSchema: () -> MemberSchemaProtocol - public let targetSchema: () -> SchemaProtocol - public let readBlock: (inout Base, any ShapeDeserializer, (any SmithyDocument)?) throws -> Void - public let writeBlock: (Base, any SmithyWriter) throws -> Void + public struct MemberContainer { + public let member: any MemberProtocol - public init( - memberSchema: @escaping () -> MemberSchemaProtocol, - targetSchema: @escaping () -> SchemaProtocol, - readBlock: @escaping (inout Base, any ShapeDeserializer, (any SmithyDocument)?) throws -> Void, - writeBlock: @escaping (Base, any SmithyWriter) throws -> Void - ) { - self.memberSchema = memberSchema - self.targetSchema = targetSchema - self.readBlock = readBlock - self.writeBlock = writeBlock + public init(member: any MemberProtocol) { + self.member = member } - } -// public let namespace: String -// public let name: String - public var type: ShapeType - public let members: [Member] - public let memberName: String? - - public init( - namespace: String = "", - name: String = "", - type: ShapeType, - members: [Member] = [], - memberName: String? = nil - ) { -// self.namespace = namespace -// self.name = name - self.type = type - self.members = members - self.memberName = memberName - } - - public func read(reader: any SmithyReader) throws { - // TODO: implement - } - - public func write(writer: any SmithyWriter) throws { - // TODO: implement - } -} - -@_spi(SchemaBasedSerde) -public class ListSchema: SchemaProtocol { -// public let namespace: String -// public let name: String - public let type: ShapeType - public let memberSchema: () -> SchemaProtocol - public let targetSchema: () -> SchemaProtocol - public let readBlock: (any ShapeDeserializer) throws -> Element - public let writeBlock: (any SmithyWriter, Element) throws -> Void - - public init( - namespace: String = "", - name: String = "", - type: ShapeType, - memberSchema: @escaping () -> SchemaProtocol, - targetSchema: @escaping () -> SchemaProtocol, - readBlock: @escaping (any ShapeDeserializer) throws -> Element, - writeBlock: @escaping (any SmithyWriter, Element) throws -> Void - ) { -// self.namespace = namespace -// self.name = name - self.type = type - self.memberSchema = memberSchema - self.targetSchema = targetSchema - self.readBlock = readBlock - self.writeBlock = writeBlock - } + public func performRead(base: inout Base, reader: any ShapeDeserializer) throws { + try member.performRead(base: &base, reader: reader) + } - public func read(reader: any SmithyReader) throws { - // TODO: implement + public func performWrite(base: Base, writer: any SmithyWriter) throws { + try member.performWrite(base: base, writer: writer) + } } - public func write(writer: any SmithyWriter) throws { - // TODO: implement - } -} + public struct Member: MemberProtocol { + public let memberSchema: any SchemaProtocol + let readBlock: (inout Base, any ShapeDeserializer) throws -> Void + let writeBlock: (Base, any SmithyWriter) throws -> Void -@_spi(SchemaBasedSerde) -public class MapSchema: SchemaProtocol { -// public let namespace: String -// public let name: String - public let type: ShapeType - public let keyMemberSchema: () -> SchemaProtocol - public let keyTargetSchema: () -> SchemaProtocol - public let valueMemberSchema: () -> SchemaProtocol - public let valueTargetSchema: () -> SchemaProtocol - public let readBlock: (any ShapeDeserializer) throws -> Value - public let writeBlock: (any SmithyWriter, Value) throws -> Void - - public init( - namespace: String = "", - name: String = "", - type: ShapeType, - keyMemberSchema: @escaping () -> SchemaProtocol, - keyTargetSchema: @escaping () -> SchemaProtocol, - valueMemberSchema: @escaping () -> SchemaProtocol, - valueTargetSchema: @escaping () -> SchemaProtocol, - readBlock: @escaping (any ShapeDeserializer) throws -> Value, - writeBlock: @escaping (any SmithyWriter, Value) throws -> Void - ) { -// self.namespace = namespace -// self.name = name - self.type = type - self.keyMemberSchema = keyMemberSchema - self.keyTargetSchema = keyTargetSchema - self.valueMemberSchema = valueMemberSchema - self.valueTargetSchema = valueTargetSchema - self.readBlock = readBlock - self.writeBlock = writeBlock - } + public init( + memberSchema: any SchemaProtocol, + readBlock: @escaping (inout Base, any ShapeDeserializer) throws -> Void = { _, _ in }, + writeBlock: @escaping (Base, any SmithyWriter) throws -> Void = { _, _ in } + ) { + self.memberSchema = memberSchema + self.readBlock = readBlock + self.writeBlock = writeBlock + } - public func read(reader: any SmithyReader) throws { - // TODO: implement - } + public func performRead(base: inout Base, reader: any ShapeDeserializer) throws { + try readBlock(&base, reader) + } - public func write(writer: any SmithyWriter) throws { - // TODO: implement + public func performWrite(base: Base, writer: any SmithyWriter) throws { + try writeBlock(base, writer) + } } -} -@_spi(SchemaBasedSerde) -public class MemberSchema: MemberSchemaProtocol { -// public let namespace: String -// public let name: String public let type: ShapeType + public let members: [MemberContainer] + public let targetSchemaSpecific: () -> Schema? public let memberName: String? public let jsonName: String? - public let xmlName: String? + public let enumValue: (any SmithyDocument)? + public let timestampFormat: SmithyTimestamps.TimestampFormat? public let isRequired: Bool - public let defaultValue: (any SmithyDocument)? - - public init( - namespace: String = "", - name: String = "", - type: ShapeType, - memberName: String? = nil, - jsonName: String? = nil, - xmlName: String? = nil, - isRequired: Bool = false, - defaultValue: SmithyDocument? = nil - ) { -// self.namespace = namespace -// self.name = name - self.type = type - self.memberName = memberName - self.jsonName = jsonName - self.xmlName = xmlName - self.isRequired = isRequired - self.defaultValue = defaultValue - } + public let defaultValue: (any Smithy.SmithyDocument)? - public func read(reader: any SmithyReader) throws { - // TODO: implement - } - public func write(writer: any SmithyWriter) throws { - // TODO: implement - } -} - -@_spi(SchemaBasedSerde) -public class SimpleSchema: SchemaProtocol { -// public let namespace: String -// public let name: String - public let type: ShapeType - public let memberName: String? - public let isRequired: Bool - public let timestampFormat: TimestampFormat? - public let defaultValue: (any SmithyDocument)? + public var targetSchema: () -> (any SchemaProtocol)? { targetSchemaSpecific } public init( - namespace: String = "", - name: String = "", type: ShapeType, + members: [MemberContainer] = [], + targetSchema: @escaping () -> Schema? = { nil }, memberName: String? = nil, + jsonName: String? = nil, + enumValue: (any SmithyDocument)? = nil, + timestampFormat: SmithyTimestamps.TimestampFormat? = nil, isRequired: Bool = false, - timestampFormat: TimestampFormat? = nil, - defaultValue: SmithyDocument? = nil + defaultValue: (any Smithy.SmithyDocument)? = nil ) { -// self.namespace = namespace -// self.name = name self.type = type + self.members = members + self.targetSchemaSpecific = targetSchema self.memberName = memberName - self.isRequired = isRequired + self.jsonName = jsonName + self.enumValue = enumValue self.timestampFormat = timestampFormat + self.isRequired = isRequired self.defaultValue = defaultValue } - public func read(reader: any SmithyReader) throws { + public func read(reader: any ShapeDeserializer) throws { // TODO: implement } diff --git a/Sources/SmithyReadWrite/ShapeDeserializer.swift b/Sources/SmithyReadWrite/ShapeDeserializer.swift index 4fdf5096c..757bd0952 100644 --- a/Sources/SmithyReadWrite/ShapeDeserializer.swift +++ b/Sources/SmithyReadWrite/ShapeDeserializer.swift @@ -11,191 +11,211 @@ import struct Foundation.Data import struct Foundation.Date @_spi(SchemaBasedSerde) -public protocol ShapeDeserializer { - func readStructure(schema: StructureSchema) throws -> T? - func readList(schema: ListSchema) throws -> [T]? - func readMap(schema: MapSchema) throws -> [String: T]? - func readBoolean(schema: SchemaProtocol) throws -> Bool? - func readByte(schema: SchemaProtocol) throws -> Int8? - func readShort(schema: SchemaProtocol) throws -> Int16? - func readInteger(schema: SchemaProtocol) throws -> Int? - func readLong(schema: SchemaProtocol) throws -> Int? - func readFloat(schema: SchemaProtocol) throws -> Float? - func readDouble(schema: SchemaProtocol) throws -> Double? - func readBigInteger(schema: SchemaProtocol) throws -> Int? - func readBigDecimal(schema: SchemaProtocol) throws -> Float? - func readString(schema: SchemaProtocol) throws -> String? - func readBlob(schema: SchemaProtocol) throws -> Data? - func readTimestamp(schema: SimpleSchema) throws -> Date? - func readDocument(schema: SchemaProtocol) throws -> Document? +public protocol ShapeDeserializer: SmithyReader { + func readStructure(schema: Schema) throws -> T? + func readList(schema: Schema<[T]>) throws -> [T]? + func readMap(schema: Schema<[String: T]>) throws -> [String: T]? + func readBoolean(schema: Schema) throws -> Bool? + func readByte(schema: Schema) throws -> Int8? + func readShort(schema: Schema) throws -> Int16? + func readInteger(schema: Schema) throws -> Int? + func readLong(schema: Schema) throws -> Int? + func readFloat(schema: Schema) throws -> Float? + func readDouble(schema: Schema) throws -> Double? + func readBigInteger(schema: Schema) throws -> Int? + func readBigDecimal(schema: Schema) throws -> Double? + func readString(schema: Schema) throws -> String? + func readBlob(schema: Schema) throws -> Data? + func readTimestamp(schema: Schema) throws -> Date? + func readDocument(schema: Schema) throws -> Document? func readNull(schema: SchemaProtocol) throws -> Bool? + func readEnum(schema: Schema) throws -> T? where T.RawValue == String + func readIntEnum(schema: Schema) throws -> T? where T.RawValue == Int } @_spi(SchemaBasedSerde) public extension ShapeDeserializer { - func readEnum(schema: SchemaProtocol) throws -> T? where T.RawValue == String { - guard let rawValue = try readString(schema: schema) else { return nil } - return T(rawValue: rawValue) + func readEnum(schema: Schema) throws -> T? where T.RawValue == String { + guard hasContent else { return nil } + guard let rawValue: String = try readIfPresent() else { throw ReaderError.requiredValueNotPresent } + for memberContainer in schema.members { + guard let resolvedEnumValue = try memberContainer.member.memberSchema.enumValue?.asString() ?? memberContainer.member.memberSchema.memberName else { + throw ReaderError.requiredValueNotPresent + } + if rawValue == resolvedEnumValue { + return T(rawValue: rawValue) + } + } + return T(rawValue: "") } - func readIntEnum(schema: SchemaProtocol) throws -> T? where T.RawValue == Int { - guard let rawValue = try readInteger(schema: schema) else { return nil } - return T(rawValue: rawValue) + func readIntEnum(schema: Schema) throws -> T? where T.RawValue == Int { + guard hasContent else { return nil } + guard let rawValue: Int = try readIfPresent() else { throw ReaderError.requiredValueNotPresent } + for memberContainer in schema.members { + guard let resolvedEnumValue = try memberContainer.member.memberSchema.enumValue?.asInteger() else { + throw ReaderError.requiredValueNotPresent + } + if rawValue == resolvedEnumValue { + return T(rawValue: rawValue) + } + } + return T(rawValue: Int.min) } -// func readEnumNonNull(schema: SchemaProtocol) throws -> T where T.RawValue == String { -// guard let value: T = try readEnum(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -// return T() -// } -// return value -// } -// -// func readIntEnumNonNull(schema: SchemaProtocol) throws -> T where T.RawValue == Int { -// guard let value: T = try readIntEnum(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -// return T() -// } -// return value -// } + func readEnumNonNull(schema: Schema) throws -> T where T.RawValue == String { + guard let value: T = try readEnum(schema: schema) else { + throw ReaderError.requiredValueNotPresent + return T(rawValue: "")! + } + return value + } + + func readIntEnumNonNull(schema: Schema) throws -> T where T.RawValue == Int { + guard let value: T = try readIntEnum(schema: schema) else { + throw ReaderError.requiredValueNotPresent + return T(rawValue: Int.min)! + } + return value + } } -//@_spi(SchemaBasedSerde) -//public extension ShapeDeserializer { -// -// func readStructureNonNull(schema: StructureSchema) throws -> T { -//// guard let value = try readStructure(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value -// return try readStructure(schema: schema) ?? T() -// } -// func readListNonNull(schema: ListSchema) throws -> [T] { -//// guard let value = try readList(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value +@_spi(SchemaBasedSerde) +public extension ShapeDeserializer { + + func readStructureNonNull(schema: Schema) throws -> T { + guard let value = try readStructure(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value + } + + func readListNonNull(schema: Schema<[T]>) throws -> [T] { + guard let value = try readList(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readList(schema: schema) ?? [] -// } -// -// func readMapNonNull(schema: MapSchema) throws -> [String: T] { -//// guard let value = try readMap(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value + } + + func readMapNonNull(schema: Schema<[String: T]>) throws -> [String: T] { + guard let value = try readMap(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readMap(schema: schema) ?? [:] -// } -// -// func readBooleanNonNull(schema: SimpleSchema) throws -> Bool { -//// guard let value = try readBoolean(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value + } + + func readBooleanNonNull(schema: Schema) throws -> Bool { + guard let value = try readBoolean(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readBoolean(schema: schema) ?? false -// } -// -// func readByteNonNull(schema: SimpleSchema) throws -> Int8 { -//// guard let value = try readByte(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value + } + + func readByteNonNull(schema: Schema) throws -> Int8 { + guard let value = try readByte(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readByte(schema: schema) ?? 0 -// } -// -// func readShortNonNull(schema: SimpleSchema) throws -> Int16 { -//// guard let value = try readShort(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value + } + + func readShortNonNull(schema: Schema) throws -> Int16 { + guard let value = try readShort(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readShort(schema: schema) ?? 0 -// } -// -// func readIntegerNonNull(schema: SimpleSchema) throws -> Int { -//// guard let value = try readInteger(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value + } + + func readIntegerNonNull(schema: Schema) throws -> Int { + guard let value = try readInteger(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readInteger(schema: schema) ?? 0 -// } -// -// func readLongNonNull(schema: SimpleSchema) throws -> Int { -//// guard let value = try readLong(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value + } + + func readLongNonNull(schema: Schema) throws -> Int { + guard let value = try readLong(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readLong(schema: schema) ?? 0 -// } -// -// func readFloatNonNull(schema: SimpleSchema) throws -> Float { -//// guard let value = try readFloat(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value + } + + func readFloatNonNull(schema: Schema) throws -> Float { + guard let value = try readFloat(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readFloat(schema: schema) ?? 0.0 -// } -// -// func readDoubleNonNull(schema: SimpleSchema) throws -> Double { -//// guard let value = try readDouble(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value + } + + func readDoubleNonNull(schema: Schema) throws -> Double { + guard let value = try readDouble(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readDouble(schema: schema) ?? 0.0 -// } -// -// func readBigIntegerNonNull(schema: SimpleSchema) throws -> Int { -//// guard let value = try readBigInteger(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value + } + + func readBigIntegerNonNull(schema: Schema) throws -> Int { + guard let value = try readBigInteger(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readBigInteger(schema: schema) ?? 0 -// } -// -// func readBigDecimalNonNull(schema: SimpleSchema) throws -> Float { -//// guard let value = try readBigDecimal(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value + } + + func readBigDecimalNonNull(schema: Schema) throws -> Double { + guard let value = try readBigDecimal(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readBigDecimal(schema: schema) ?? 0.0 -// } -// -// func readStringNonNull(schema: SimpleSchema) throws -> String { -//// guard let value = try readString(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value + } + + func readStringNonNull(schema: Schema) throws -> String { + guard let value = try readString(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readString(schema: schema) ?? "" -// } -// -// func readBlobNonNull(schema: SimpleSchema) throws -> Data { -//// guard let value = try readBlob(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value + } + + func readBlobNonNull(schema: Schema) throws -> Data { + guard let value = try readBlob(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readBlob(schema: schema) ?? Data() -// } -// -// func readTimestampNonNull(schema: SimpleSchema) throws -> Date { -//// guard let value = try readTimestamp(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value + } + + func readTimestampNonNull(schema: Schema) throws -> Date { + guard let value = try readTimestamp(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readTimestamp(schema: schema) ?? Date(timeIntervalSince1970: 0.0) -// } -// -// func readDocumentNonNull(schema: SimpleSchema) throws -> Document { -//// guard let value = try readDocument(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value + } + + func readDocumentNonNull(schema: Schema) throws -> Document { + guard let value = try readDocument(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readDocument(schema: schema) ?? Document(NullDocument()) -// } -// -// func readNullNonNull(schema: SimpleSchema) throws -> Bool { -//// guard let value = try readNull(schema: schema) else { -//// throw ReaderError.requiredValueNotPresent -//// } -//// return value + } + + func readNullNonNull(schema: SchemaProtocol) throws -> Bool { + guard let value = try readNull(schema: schema) else { + throw ReaderError.requiredValueNotPresent + } + return value // return try readNull(schema: schema) ?? false -// } -//} + } +} diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt index 95a5dbe58..6daf94311 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt @@ -48,6 +48,7 @@ import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.model.traits.InputTrait import software.amazon.smithy.model.traits.SparseTrait import software.amazon.smithy.model.traits.StreamingTrait +import software.amazon.smithy.model.traits.UnitTypeTrait import software.amazon.smithy.swift.codegen.customtraits.NestedTrait import software.amazon.smithy.swift.codegen.lang.swiftReservedWords import software.amazon.smithy.swift.codegen.model.SymbolProperty @@ -174,6 +175,10 @@ class SwiftSymbolProvider(private val model: Model, val swiftSettings: SwiftSett if (shape.hasTrait() && service != null && !shape.hasTrait()) { builder.namespace(service.nestedNamespaceType(this).name, ".") } + if (shape.id.toString() == "smithy.api#Unit" && !shape.hasTrait() && service != null) { + builder.name("EnumUnit") + builder.namespace(service.nestedNamespaceType(this).name, ".") + } return builder.build() } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt index b55af9653..3fbc30d6b 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt @@ -219,11 +219,10 @@ class MessageUnmarshallableGenerator( private fun renderReadWithSchemaToValue(writer: SwiftWriter, memberShape: MemberShape) { val target = ctx.model.expectShape(memberShape.target) writer.write( - "let value = try \$N.from(data: message.payload).\$L(schema: \$L) ?? \$N.required(nil)", + "let value = try \$N.from(data: message.payload).\$LNonNull(schema: \$L)", ctx.service.readerSymbol, target.readMethodName, - target.id.schemaVar(writer), - SmithyReadWriteTypes.DefaultValueTransformer, + target.schemaVar, ) } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt index c70c0b26c..add904781 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt @@ -24,6 +24,7 @@ import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.TimestampShape import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.model.traits.EnumTrait +import software.amazon.smithy.model.traits.EnumValueTrait import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.model.traits.HttpHeaderTrait import software.amazon.smithy.model.traits.HttpLabelTrait @@ -35,6 +36,7 @@ import software.amazon.smithy.model.traits.MediaTypeTrait import software.amazon.smithy.model.traits.RequiresLengthTrait import software.amazon.smithy.model.traits.StreamingTrait import software.amazon.smithy.model.traits.TimestampFormatTrait +import software.amazon.smithy.model.traits.UnitTypeTrait import software.amazon.smithy.swift.codegen.SwiftWriter import software.amazon.smithy.swift.codegen.customtraits.NeedsReaderTrait import software.amazon.smithy.swift.codegen.customtraits.NeedsWriterTrait @@ -71,6 +73,7 @@ import software.amazon.smithy.swift.codegen.model.ShapeMetadata import software.amazon.smithy.swift.codegen.model.findStreamingMember import software.amazon.smithy.swift.codegen.model.hasEventStreamMember import software.amazon.smithy.swift.codegen.model.hasTrait +import software.amazon.smithy.swift.codegen.model.isEnum import software.amazon.smithy.swift.codegen.model.isInputEventStream import software.amazon.smithy.swift.codegen.model.isOutputEventStream import software.amazon.smithy.swift.codegen.model.targetOrSelf @@ -224,13 +227,13 @@ abstract class HTTPBindingProtocolGenerator( private fun renderSchemas(ctx: ProtocolGenerator.GenerationContext, shape: Shape) { val symbol: Symbol = ctx.symbolProvider.toSymbol(shape) val symbolName = symbol.name - val filename = ModelFileUtils.filename(ctx.settings, "${symbol.name}+Schema") + val filename = ModelFileUtils.filename(ctx.settings, "${shape.id.name}+Schema") val encodeSymbol = Symbol.builder() .definitionFile(filename) .name(symbolName) .build() ctx.delegator.useShapeWriter(encodeSymbol) { writer -> - SchemaGenerator(ctx, writer).renderSchemas(shape) + SchemaGenerator(ctx, writer).renderSchema(shape) } } @@ -238,7 +241,9 @@ abstract class HTTPBindingProtocolGenerator( ctx: ProtocolGenerator.GenerationContext, shape: Shape, ) { - if (!shape.hasTrait() && !shape.hasTrait()) { return } + val shapeUsesSchemaBasedRead = ctx.service.responseWireProtocol == WireProtocol.JSON && shape.hasTrait() + if (shapeUsesSchemaBasedRead && !shape.hasTrait()) { return } + if (ctx.service.responseWireProtocol == WireProtocol.JSON && !shape.hasTrait()) { return } val symbol: Symbol = ctx.symbolProvider.toSymbol(shape) val symbolName = symbol.name val filename = ModelFileUtils.filename(ctx.settings, "$symbolName+ReadWrite") @@ -258,7 +263,7 @@ abstract class HTTPBindingProtocolGenerator( writer.write("") renderStructEncode(ctx, shape, mapOf(), httpBodyMembers, writer) } - if (shape.hasTrait() && (ctx.service.responseWireProtocol != WireProtocol.JSON || !shape.hasTrait())) { + if (shape.hasTrait() && !shapeUsesSchemaBasedRead) { writer.write("") renderStructDecode(ctx, shape, mapOf(), httpBodyMembers, writer) } @@ -355,20 +360,17 @@ abstract class HTTPBindingProtocolGenerator( outputShape.members() } .map { ctx.model.expectShape(it.target) } -// .filter { it.isStructureShape || it.isUnionShape || it is CollectionShape || it.isMapShape } .toSet() val topLevelErrorMembers = getHttpBindingOperations(ctx) .flatMap { it.errors } .flatMap { ctx.model.expectShape(it).members() } .map { ctx.model.expectShape(it.target) } -// .filter { it.isStructureShape || it.isUnionShape || it is CollectionShape || it.isMapShape } .toSet() val topLevelServiceErrorMembers = ctx.service.errors .flatMap { ctx.model.expectShape(it).members() } .map { ctx.model.expectShape(it.target) } -// .filter { it.isStructureShape || it.isUnionShape || it is CollectionShape || it.isMapShape } .toSet() // val topLevelInputMembers = getHttpBindingOperations(ctx).flatMap { @@ -376,7 +378,6 @@ abstract class HTTPBindingProtocolGenerator( // inputShape.members() // } // .map { ctx.model.expectShape(it.target) } -// .filter { it.isStructureShape || it.isUnionShape || it is CollectionShape || it.isMapShape } // .toSet() val allTopLevelMembers = @@ -386,7 +387,6 @@ abstract class HTTPBindingProtocolGenerator( // .union(topLevelInputMembers) return walkNestedShapesRequiringSchema(ctx, allTopLevelMembers) -// return nestedTypes } private fun walkNestedShapesRequiringSerde(ctx: ProtocolGenerator.GenerationContext, shapes: Set): Set { @@ -433,18 +433,12 @@ abstract class HTTPBindingProtocolGenerator( RelationshipType.MAP_KEY, RelationshipType.MAP_VALUE, RelationshipType.UNION_MEMBER, + RelationshipType.ENUM_MEMBER, + RelationshipType.INT_ENUM_MEMBER, -> true else -> false } - }.forEach { - if (it.type != ShapeType.MEMBER) { - resolved.add(it) - } -// when (it) { -// is UnionShape -> resolved.add(it) -// is StructureShape -> resolved.add(it) -// } - } + }.forEach { resolved.add(it) } } return resolved } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt index 4175b6d21..aebcef0bb 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt @@ -89,11 +89,10 @@ open class MemberShapeDecodeGenerator( if (useSBS) { val target = ctx.model.expectShape(memberShape.target) return writer.format( - "try \$L.readStructure(schema: \$L) ?? \$N.\$L(nil)", + "try \$L.readStructure\$L(schema: \$L)", reader(memberShape, isPayload), - memberShape.target.schemaVar(writer), - SmithyReadWriteTypes.DefaultValueTransformer, - "required".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "optional", + "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", + target.schemaVar, ) } val readingClosure = readingClosureUtils.readingClosure(memberShape) @@ -109,11 +108,10 @@ open class MemberShapeDecodeGenerator( if (useSBS) { val target = ctx.model.expectShape(memberShape.target) return writer.format( - "try \$L.readList(schema: \$L) ?? \$N.\$L(nil)", + "try \$L.readList\$L(schema: \$L)", reader(memberShape, false), - memberShape.target.schemaVar(writer), - SmithyReadWriteTypes.DefaultValueTransformer, - "required".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "optional", + "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", + target.schemaVar, ) } val isSparse = listShape.hasTrait() @@ -135,11 +133,10 @@ open class MemberShapeDecodeGenerator( if (useSBS) { val target = ctx.model.expectShape(memberShape.target) return writer.format( - "try \$L.readMap(schema: \$L) ?? \$N.\$L(nil)", + "try \$L.readMap\$L(schema: \$L)", reader(memberShape, false), - memberShape.target.schemaVar(writer), - SmithyReadWriteTypes.DefaultValueTransformer, - "required".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "optional", + "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", + target.schemaVar, ) } val isSparse = mapShape.hasTrait() @@ -179,12 +176,11 @@ open class MemberShapeDecodeGenerator( if (useSBS) { val target = ctx.model.expectShape(memberShape.target) return writer.format( - "try \$L.\$L(schema: \$L) ?? \$N.\$L(nil)", + "try \$L.\$L\$L(schema: \$L)", reader(memberShape, isPayload), target.readMethodName, - memberShape.target.schemaVar(writer), - SmithyReadWriteTypes.DefaultValueTransformer, - "required".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "optional", + "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", + target.schemaVar, ) } return writer.format( diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt index 170fdada5..fbc6c0ea8 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt @@ -1,29 +1,21 @@ package software.amazon.smithy.swift.codegen.integration.serde.schema -import software.amazon.smithy.codegen.core.Symbol +import software.amazon.smithy.model.node.Node import software.amazon.smithy.model.node.NodeType import software.amazon.smithy.model.shapes.MemberShape -import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.shapes.Shape -import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.model.shapes.ShapeType -import software.amazon.smithy.model.shapes.StructureShape -import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.model.traits.DefaultTrait -import software.amazon.smithy.model.traits.EnumTrait +import software.amazon.smithy.model.traits.EnumValueTrait import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.model.traits.JsonNameTrait import software.amazon.smithy.model.traits.RequiredTrait import software.amazon.smithy.model.traits.SparseTrait import software.amazon.smithy.model.traits.TimestampFormatTrait -import software.amazon.smithy.model.traits.XmlNameTrait import software.amazon.smithy.swift.codegen.SwiftWriter -import software.amazon.smithy.swift.codegen.customtraits.NestedTrait import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator import software.amazon.smithy.swift.codegen.model.getTrait import software.amazon.smithy.swift.codegen.model.hasTrait -import software.amazon.smithy.swift.codegen.model.nestedNamespaceType -import software.amazon.smithy.swift.codegen.model.toOptional import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTimestampsTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTypes @@ -34,157 +26,58 @@ class SchemaGenerator( val writer: SwiftWriter, ) { - fun renderSchemas(shape: Shape) { - writer.writeInline("let \$L: \$N<\$N> = ", shape.id.schemaVar(writer), schemaType(shape), swiftSymbolFor(ctx, shape)) - renderSingleSchema(shape) - writer.unwrite(",\n") - writer.write("()") - writer.write("") - } - - private fun renderSingleSchema(shape: Shape, optionalize: Boolean = false) { + fun renderSchema(shape: Shape) { + writer.writeInline("let \$L: \$N<\$N> = ", shape.schemaVar, SmithyReadWriteTypes.Schema, ctx.symbolProvider.toSymbol(shape)) writer.openBlock( "{ \$N<\$N>(", ") },", - schemaType(shape), - swiftSymbolFor(ctx, shape).optionalIf(optionalize), + SmithyReadWriteTypes.Schema, + ctx.symbolProvider.toSymbol(shape), ) { -// writer.write("namespace: \$S,", shape.id.namespace) -// writer.write("name: \$S,", shape.id.name) writer.write("type: .\$L,", shape.type) - when (shape.type) { - ShapeType.STRUCTURE, ShapeType.UNION -> { - if (shape.members().isNotEmpty()) { - writer.openBlock("members: [", "],") { - shape.members().forEach { member -> - writer.openBlock(".init(", "),") { - writer.writeInline("memberSchema: ") - renderSingleSchema(member) - writer.write("targetSchema: { \$L },", member.target.schemaVar(writer)) - writeSetterGetter(ctx, writer, shape, member) - } + if (shape.members().isNotEmpty()) { + writer.openBlock("members: [", "],") { + shape.members().forEach { member -> + writer.openBlock(".init(member:", "),") { + writer.openBlock("\$N<\$N>.Member<\$N>(", ")", SmithyReadWriteTypes.Schema, ctx.symbolProvider.toSymbol(shape), ctx.symbolProvider.toSymbol(shape)) { + writer.write("memberSchema: \$L,", member.schemaVar) + writeSetterGetter(writer, shape, member) + writer.unwrite(",\n") + writer.write("") } - writer.unwrite(",\n") - writer.write("") } } - } - ShapeType.LIST, ShapeType.SET -> { - val member = shape.members().first { it.memberName == "member" } - val target = ctx.model.expectShape(member.target) - writer.writeInline("memberSchema: ") - renderSingleSchema(member, shape.hasTrait()) - writer.write("targetSchema: { \$L },", target.id.schemaVar(writer)) - writeSetterGetter(ctx, writer, shape, member) - } - ShapeType.MAP -> { - val keyMember = shape.members().first { it.memberName == "key" } - val keyTarget = keyMember.let { ctx.model.expectShape(it.target) } - val valueMember = shape.members().first { it.memberName == "value" } - val valueTarget = valueMember.let { ctx.model.expectShape(it.target) } - writer.writeInline("keyMemberSchema: ") - renderSingleSchema(keyMember) - writer.write("keyTargetSchema: { \$L },", keyTarget.id.schemaVar(writer)) - writer.writeInline("valueMemberSchema: ") - renderSingleSchema(valueMember, shape.hasTrait()) - writer.write("valueTargetSchema: { \$L },", valueTarget.id.schemaVar(writer)) - writeSetterGetter(ctx, writer, shape, valueMember) - } - ShapeType.MEMBER -> { - shape.id.member.getOrNull()?.let { writer.write("memberName: \$S,", it) } - jsonName(shape)?.let { writer.write("jsonName: \$S,", it) } - xmlName(shape)?.let { writer.write("xmlName: \$S,", it) } - if (isRequired(shape)) { - writer.write("isRequired: true,") - } - defaultValue(shape)?.let { writer.write("defaultValue: \$L,", it) } - } - else -> { - timestampFormat(shape)?.let { - writer.addImport(SmithyTimestampsTypes.TimestampFormat) - writer.write("timestampFormat: .\$L", it.swiftEnumCase) - } - defaultValue(shape)?.let { writer.write("defaultValue: \$L,", it) } + writer.unwrite(",\n") + writer.write("") } } - writer.unwrite(",\n") - writer.write("") - } - } - - private fun schemaType(shape: Shape): Symbol { - return when (shape.type) { - ShapeType.STRUCTURE, ShapeType.UNION -> SmithyReadWriteTypes.StructureSchema - ShapeType.LIST, ShapeType.SET -> SmithyReadWriteTypes.ListSchema - ShapeType.MAP -> SmithyReadWriteTypes.MapSchema - ShapeType.MEMBER -> SmithyReadWriteTypes.MemberSchema - else -> SmithyReadWriteTypes.SimpleSchema - } - } - - private fun swiftSymbolFor(ctx: ProtocolGenerator.GenerationContext, shape: Shape): Symbol { - return swiftSymbolWithOptionalRecursion(ctx, shape, true) - } - - private fun swiftSymbolWithOptionalRecursion(ctx: ProtocolGenerator.GenerationContext, shape: Shape, recurse: Boolean): Symbol { - val service = ctx.model.getShape(ctx.settings.service).get() as ServiceShape - return when (shape.type) { - ShapeType.STRUCTURE, ShapeType.UNION -> { - val symbol = ctx.symbolProvider.toSymbol(shape) - if (shape.hasTrait() && !shape.hasTrait()) { - return symbol.toBuilder().namespace(service.nestedNamespaceType(ctx.symbolProvider).name, ".").build() - } else { - return symbol + targetSchema(shape)?.let { writer.write("targetSchema: { \$L },", it.schemaVar) } + shape.id.member.getOrNull()?.let { writer.write("memberName: \$S,", it) } + jsonName(shape)?.let { writer.write("jsonName: \$S,", it) } + enumValue(shape)?.let { node -> + when (node.type) { + NodeType.STRING -> writer.write("enumValue: \$N(value: \$S)", SmithyTypes.StringDocument, node) + NodeType.NUMBER -> writer.write("enumValue: \$N(value: \$L)", SmithyTypes.IntegerDocument, node) + else -> throw Exception("Unsupported node type") } } - ShapeType.LIST, ShapeType.SET -> { - val target = shape.members() - .first { it.memberName == "member" } - ?.let { ctx.model.expectShape(it.target) } - ?: run { throw Exception("List / set does not have target") } - val innerSymbol = swiftSymbolWithOptionalRecursion(ctx, target, false).optionalIf(shape.hasTrait()) - return if (recurse) { - innerSymbol - } else { - Symbol.builder() - .name(writer.format("[ \$N ]", innerSymbol)) - .build() - } - } - ShapeType.MAP -> { - val target = shape.members() - .first { it.memberName == "value" } - ?.let { ctx.model.expectShape(it.target) } - ?: run { throw Exception("Map does not have target") } - val innerSymbol = swiftSymbolWithOptionalRecursion(ctx, target, false).optionalIf(shape.hasTrait()) - return if (recurse) { - innerSymbol - } else { - Symbol.builder() - .name(writer.format("[Swift.String: \$N]", innerSymbol)) - .build() - } - } - ShapeType.MEMBER -> { - val memberShape = shape as MemberShape - val target = ctx.model.expectShape(memberShape.target) - swiftSymbolWithOptionalRecursion(ctx, target, false) + timestampFormat(shape)?.let { + writer.addImport(SmithyTimestampsTypes.TimestampFormat) + writer.write("timestampFormat: .\$L,", it.swiftEnumCase) } - else -> { - ctx.symbolProvider.toSymbol(shape) + if (isRequired(shape)) { + writer.write("isRequired: true,") } + defaultValue(shape)?.let { writer.write("defaultValue: \$L,", it) } + writer.unwrite(",\n") + writer.write("") } + writer.unwrite(",\n") + writer.write("()") + writer.write("") } - fun Symbol.optionalIf(isOptional: Boolean): Symbol { - if (isOptional) { - return this.toOptional() - } else { - return this - } - } - - private fun writeSetterGetter(ctx: ProtocolGenerator.GenerationContext, writer: SwiftWriter, shape: Shape, member: MemberShape) { + private fun writeSetterGetter(writer: SwiftWriter, shape: Shape, member: MemberShape) { val target = ctx.model.expectShape(member.target) val readMethodName = target.readMethodName val memberIsRequired = member.isRequired @@ -192,45 +85,58 @@ class SchemaGenerator( || (shape.isMapShape && member.memberName == "key") || (shape.isMapShape && member.memberName == "value" && !shape.hasTrait()) || (shape.isListShape && !shape.hasTrait()) - val transformMethodName = "required".takeIf { memberIsRequired || shape.isUnionShape } ?: "optional" + val readMethodExtension = "NonNull".takeIf { memberIsRequired || shape.isUnionShape } ?: "" when (shape.type) { ShapeType.STRUCTURE -> { val path = "properties.".takeIf { shape.hasTrait() } ?: "" writer.write( - "readBlock: { \$\$0.\$L\$L = try \$\$1.\$L(schema: \$L) ?? \$N.\$L(\$\$2) },", + "readBlock: { \$\$0.\$L\$L = try \$\$1.\$L\$L(schema: \$L) },", path, ctx.symbolProvider.toMemberName(member), readMethodName, - target.id.schemaVar(writer), - SmithyReadWriteTypes.DefaultValueTransformer, - transformMethodName, + readMethodExtension, + member.schemaVar, ) - writer.write("writeBlock: { _, _ in }") } ShapeType.UNION -> { - writer.write("readBlock: { \$\$0 = .\$L(try \$\$1.\$L(schema: \$L) ?? \$N.\$L(\$\$2)) },", ctx.symbolProvider.toMemberName(member), readMethodName, target.id.schemaVar(writer), - SmithyReadWriteTypes.DefaultValueTransformer, - transformMethodName, + writer.write( + "readBlock: { try \$\$0 = .\$L(\$\$1.\$LNonNull(schema: \$L)) },", + ctx.symbolProvider.toMemberName(member), + readMethodName, + member.schemaVar, ) - writer.write("writeBlock: { _, _ in }") } - ShapeType.LIST, ShapeType.SET, ShapeType.MAP -> { - writer.write("readBlock: { try \$\$0.\$L(schema: \$L) ?? \$N.\$L(nil) },", readMethodName, target.id.schemaVar(writer), - SmithyReadWriteTypes.DefaultValueTransformer, - transformMethodName, + ShapeType.SET, ShapeType.LIST -> { + writer.write("readBlock: { try \$\$0.append(\$\$1.\$L\$L(schema: \$L)) },", + readMethodName, + readMethodExtension, + target.schemaVar, ) - writer.write("writeBlock: { _, _ in }") + } + ShapeType.MAP -> { + if (member.memberName != "key") { + writer.write( + "readBlock: { try \$\$0[\"value\"] = \$\$1.\$L\$L(schema: \$L) },", + readMethodName, + readMethodExtension, + target.schemaVar, + ) + } } else -> {} } } - private fun jsonName(shape: Shape): String? { - return shape.getTrait()?.value + private fun targetSchema(shape: Shape): Shape? { + return shape.asMemberShape().getOrNull()?.let { ctx.model.expectShape(it.target) } } - private fun xmlName(shape: Shape): String? { - return shape.getTrait()?.value + private fun enumValue(shape: Shape): Node? { + return shape.getTrait()?.toNode() + } + + private fun jsonName(shape: Shape): String? { + return shape.getTrait()?.value } private fun isRequired(shape: Shape): Boolean { @@ -265,49 +171,3 @@ class SchemaGenerator( } } } - -fun ShapeId.schemaVar(writer: SwiftWriter): String { - return when (Pair(this.namespace, this.name)) { -// Pair("smithy.api", "Unit") -> writer.format("\$N", SmithyReadWriteTypes.unitSchema) -// Pair("smithy.api", "Boolean") -> writer.format("\$N", SmithyReadWriteTypes.booleanSchema) -// Pair("smithy.api", "Byte") -> writer.format("\$N", SmithyReadWriteTypes.byteSchema) -// Pair("smithy.api", "Short") -> writer.format("\$N", SmithyReadWriteTypes.shortSchema) -// Pair("smithy.api", "Integer") -> writer.format("\$N", SmithyReadWriteTypes.integerSchema) -// Pair("smithy.api", "Long") -> writer.format("\$N", SmithyReadWriteTypes.longSchema) -// Pair("smithy.api", "Float") -> writer.format("\$N", SmithyReadWriteTypes.floatSchema) -// Pair("smithy.api", "Double") -> writer.format("\$N", SmithyReadWriteTypes.doubleSchema) -// Pair("smithy.api", "String") -> writer.format("\$N", SmithyReadWriteTypes.stringSchema) -// Pair("smithy.api", "Document") -> writer.format("\$N", SmithyReadWriteTypes.documentSchema) -// Pair("smithy.api", "Blob") -> writer.format("\$N", SmithyReadWriteTypes.blobSchema) -// Pair("smithy.api", "Timestamp") -> writer.format("\$N", SmithyReadWriteTypes.timestampSchema) - else -> { - val namespacePortion = this.namespace.replace(".", "_") - val memberPortion = this.member.getOrNull()?.let { "_member_$it" } ?: "" - "${namespacePortion}__${this.name}_schema${memberPortion}" - } - } -} - -val Shape.readMethodName: String - get() = when (type) { - ShapeType.BLOB -> "readBlob" - ShapeType.BOOLEAN -> "readBoolean" - ShapeType.STRING -> "readEnum".takeIf { hasTrait() } ?: "readString" - ShapeType.ENUM -> "readEnum" - ShapeType.TIMESTAMP -> "readTimestamp" - ShapeType.BYTE -> "readByte" - ShapeType.SHORT -> "readShort" - ShapeType.INTEGER -> "readInteger" - ShapeType.INT_ENUM -> "readIntEnum" - ShapeType.LONG -> "readLong" - ShapeType.FLOAT -> "readFloat" - ShapeType.DOCUMENT -> "readDocument" - ShapeType.DOUBLE -> "readDouble" - ShapeType.BIG_DECIMAL -> "readBigDecimal" - ShapeType.BIG_INTEGER -> "readBigInteger" - ShapeType.LIST, ShapeType.SET -> "readList" - ShapeType.MAP -> "readMap" - ShapeType.STRUCTURE, ShapeType.UNION -> "readStructure" - ShapeType.MEMBER, ShapeType.SERVICE, ShapeType.RESOURCE, ShapeType.OPERATION, null -> - throw Exception("Unsupported member target type: ${type}") - } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaShapeUtils.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaShapeUtils.kt new file mode 100644 index 000000000..2bc308dc9 --- /dev/null +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaShapeUtils.kt @@ -0,0 +1,46 @@ +package software.amazon.smithy.swift.codegen.integration.serde.schema + +import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.shapes.ShapeType +import software.amazon.smithy.model.traits.EnumTrait +import software.amazon.smithy.swift.codegen.customtraits.NestedTrait +import software.amazon.smithy.swift.codegen.model.hasTrait +import kotlin.jvm.optionals.getOrNull + +val Shape.schemaVar: String + get() = if (this.id.toString() == "smithy.api#Unit" && !this.hasTrait()) { + ShapeId.from("smithy.api#EnumUnit").schemaVarName() + } else { + this.id.schemaVarName() + } + +private fun ShapeId.schemaVarName(): String { + val namespacePortion = this.namespace.replace(".", "_") + val memberPortion = this.member.getOrNull()?.let { "_member_$it" } ?: "" + return "${namespacePortion}__${this.name}_schema${memberPortion}" +} + +val Shape.readMethodName: String + get() = when (type) { + ShapeType.BLOB -> "readBlob" + ShapeType.BOOLEAN -> "readBoolean" + ShapeType.STRING -> "readEnum".takeIf { hasTrait() } ?: "readString" + ShapeType.ENUM -> "readEnum" + ShapeType.TIMESTAMP -> "readTimestamp" + ShapeType.BYTE -> "readByte" + ShapeType.SHORT -> "readShort" + ShapeType.INTEGER -> "readInteger" + ShapeType.INT_ENUM -> "readIntEnum" + ShapeType.LONG -> "readLong" + ShapeType.FLOAT -> "readFloat" + ShapeType.DOCUMENT -> "readDocument" + ShapeType.DOUBLE -> "readDouble" + ShapeType.BIG_DECIMAL -> "readBigDecimal" + ShapeType.BIG_INTEGER -> "readBigInteger" + ShapeType.LIST, ShapeType.SET -> "readList" + ShapeType.MAP -> "readMap" + ShapeType.STRUCTURE, ShapeType.UNION -> "readStructure" + ShapeType.MEMBER, ShapeType.SERVICE, ShapeType.RESOURCE, ShapeType.OPERATION, null -> + throw Exception("Unsupported member target type: ${type}") + } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftintegrations/SmithyEnumUnitIntegration.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftintegrations/SmithyEnumUnitIntegration.kt new file mode 100644 index 000000000..a0684c98c --- /dev/null +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftintegrations/SmithyEnumUnitIntegration.kt @@ -0,0 +1,52 @@ +package software.amazon.smithy.swift.codegen.swiftintegrations + +import software.amazon.smithy.model.Model +import software.amazon.smithy.swift.codegen.SwiftDelegator +import software.amazon.smithy.swift.codegen.SwiftSettings +import software.amazon.smithy.swift.codegen.core.SwiftCodegenContext +import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator +import software.amazon.smithy.swift.codegen.integration.SwiftIntegration +import software.amazon.smithy.swift.codegen.model.nestedNamespaceType +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes +import software.amazon.smithy.swift.codegen.swiftmodules.SwiftTypes +import software.amazon.smithy.swift.codegen.utils.ModelFileUtils + +class SmithyEnumUnitIntegration : SwiftIntegration { + override fun enabledForService(model: Model, settings: SwiftSettings): Boolean { + return true + } + + override fun writeAdditionalFiles( + ctx: SwiftCodegenContext, + protoCtx: ProtocolGenerator.GenerationContext, + delegator: SwiftDelegator + ) { + val service = ctx.settings.getService(ctx.model) + val namespaceName = service.nestedNamespaceType(ctx.symbolProvider).name + val structFilename = ModelFileUtils.filename(ctx.settings, "EnumUnit") + delegator.useFileWriter(structFilename) { writer -> + writer.openBlock("public extension \$L {", "}", namespaceName) { + writer.write("") + writer.openBlock( + "struct EnumUnit: \$N {", + "}", + SwiftTypes.Protocols.Sendable, + ) { + writer.write("public init() {}") + } + } + } + + val schemaFilename = ModelFileUtils.filename(ctx.settings, "EnumUnit+Schema") + delegator.useFileWriter(schemaFilename) { writer -> + writer.openBlock( + "let smithy_api__EnumUnit_schema = { \$N<\$L.EnumUnit>(", + ") }()", + SmithyReadWriteTypes.Schema, + namespaceName, + ) { + writer.write("type: .structure") + } + } + } +} diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt index 5cbb715dc..d26fafd11 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt @@ -26,26 +26,9 @@ object SmithyReadWriteTypes { val WritingClosures = runtimeSymbol("WritingClosures", SwiftDeclaration.ENUM) val ReadingClosureBox = runtimeSymbol("ReadingClosureBox", SwiftDeclaration.STRUCT) val WritingClosureBox = runtimeSymbol("WritingClosureBox", SwiftDeclaration.STRUCT) - val StructureSchema = runtimeSymbol("StructureSchema", SwiftDeclaration.CLASS, listOf("SchemaBasedSerde")) - val ListSchema = runtimeSymbol("ListSchema", SwiftDeclaration.CLASS, listOf("SchemaBasedSerde")) - val MapSchema = runtimeSymbol("MapSchema", SwiftDeclaration.CLASS, listOf("SchemaBasedSerde")) - val SimpleSchema = runtimeSymbol("SimpleSchema", SwiftDeclaration.CLASS, listOf("SchemaBasedSerde")) - val MemberSchema = runtimeSymbol("MemberSchema", SwiftDeclaration.CLASS, listOf("SchemaBasedSerde")) + val Schema = runtimeSymbol("Schema", SwiftDeclaration.CLASS, listOf("SchemaBasedSerde")) val DeserializableShape = runtimeSymbol("DeserializableShape", SwiftDeclaration.PROTOCOL) val ShapeDeserializer = runtimeSymbol("ShapeDeserializer", SwiftDeclaration.PROTOCOL, listOf("SchemaBasedSerde")) - val unitSchema = runtimeSymbol("unitSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) - val booleanSchema = runtimeSymbol("booleanSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) - val byteSchema = runtimeSymbol("byteSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) - val shortSchema = runtimeSymbol("shortSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) - val integerSchema = runtimeSymbol("integerSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) - val longSchema = runtimeSymbol("longSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) - val floatSchema = runtimeSymbol("floatSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) - val doubleSchema = runtimeSymbol("doubleSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) - val stringSchema = runtimeSymbol("stringSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) - val documentSchema = runtimeSymbol("documentSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) - val blobSchema = runtimeSymbol("blobSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) - val timestampSchema = runtimeSymbol("timestampSchema", SwiftDeclaration.LET, listOf("SchemaBasedSerde")) - val DefaultValueTransformer = runtimeSymbol("DefaultValueTransformer", SwiftDeclaration.ENUM, listOf("SchemaBasedSerde")) } private fun runtimeSymbol( diff --git a/smithy-swift-codegen/src/main/resources/META-INF/services/software.amazon.smithy.swift.codegen.integration.SwiftIntegration b/smithy-swift-codegen/src/main/resources/META-INF/services/software.amazon.smithy.swift.codegen.integration.SwiftIntegration index 7b58c68d1..e224b8aef 100644 --- a/smithy-swift-codegen/src/main/resources/META-INF/services/software.amazon.smithy.swift.codegen.integration.SwiftIntegration +++ b/smithy-swift-codegen/src/main/resources/META-INF/services/software.amazon.smithy.swift.codegen.integration.SwiftIntegration @@ -3,4 +3,5 @@ software.amazon.smithy.swift.codegen.waiters.WaiterIntegration software.amazon.smithy.swift.codegen.ServiceNamespaceIntegration software.amazon.smithy.swift.codegen.DefaultClientConfigurationIntegration software.amazon.smithy.swift.codegen.integration.ExtractTelemetryLoggerConfig -software.amazon.smithy.swift.codegen.swiftintegrations.TestEquatableConformanceIntegration \ No newline at end of file +software.amazon.smithy.swift.codegen.swiftintegrations.TestEquatableConformanceIntegration +software.amazon.smithy.swift.codegen.swiftintegrations.SmithyEnumUnitIntegration \ No newline at end of file From 72efe11ff6c6e4fce3af383b6612508f2702909f Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Sat, 4 Jan 2025 17:27:16 -0600 Subject: [PATCH 06/16] 9 protocol test fails --- .../Implementations/BigDecimalDocument.swift | 4 +- .../Reader/Reader+ShapeDeserializer.swift | 226 +++++++++++++----- Sources/SmithyReadWrite/Schema/Schema.swift | 62 ++--- .../SmithyReadWrite/ShapeDeserializer.swift | 48 +--- .../HTTPBindingProtocolGenerator.kt | 19 +- .../member/MemberShapeDecodeGenerator.kt | 10 +- .../serde/schema/SchemaGenerator.kt | 52 ++-- 7 files changed, 248 insertions(+), 173 deletions(-) diff --git a/Sources/Smithy/Document/Implementations/BigDecimalDocument.swift b/Sources/Smithy/Document/Implementations/BigDecimalDocument.swift index 69a8b7803..b5381fbdf 100644 --- a/Sources/Smithy/Document/Implementations/BigDecimalDocument.swift +++ b/Sources/Smithy/Document/Implementations/BigDecimalDocument.swift @@ -35,8 +35,8 @@ public struct BigDecimalDocument: SmithyDocument { return int } - public func asLong() throws -> Int64 { - guard let long = Int64(exactly: value) else { + public func asLong() throws -> Int { + guard let long = Int(exactly: value) else { throw DocumentError.numberOverflow("BigDecimal \(value) overflows long") } return long diff --git a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift index d377b837d..cc77b8c1a 100644 --- a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift +++ b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift @@ -22,69 +22,43 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { public func readStructure( schema: SmithyReadWrite.Schema ) throws -> Target? { - // TODO: Implement me - guard self.hasContent else { return nil } + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent, resolvedReader.jsonNode == .object else { + return resolvedDefault(schema: schema) != nil ? Target() : nil + } var value = Target() - try schema.members.forEach { memberContainer in - let memberSchema = memberContainer.member.memberSchema - guard let targetSchema = memberSchema.targetSchema() else { - throw ReaderError.requiredValueNotPresent - } - let resolvedName = try resolvedName(memberSchema: memberSchema) - guard let resolvedName = memberSchema.jsonName ?? memberSchema.memberName else { - throw ReaderError.requiredValueNotPresent - } - let resolvedReader = self[NodeInfo(resolvedName)] - guard resolvedReader.hasContent else { return } - var resolvedDefault = targetSchema.defaultValue ?? memberSchema.defaultValue - if memberSchema.isRequired { - resolvedDefault = try resolvedDefault ?? targetSchema.lastResortDefaultValue - } + let structureSchema = resolvedTargetSchema(schema: schema) + try structureSchema.members.forEach { memberContainer in try memberContainer.performRead(base: &value, reader: resolvedReader) } return value } - private func resolvedName(memberSchema: SmithyReadWrite.SchemaProtocol) throws -> String { - if respectsJSONName { - guard let resolvedName = memberSchema.jsonName ?? memberSchema.memberName else { - throw ReaderError.requiredValueNotPresent - } - return resolvedName - } else { - guard let resolvedName = memberSchema.memberName else { - throw ReaderError.requiredValueNotPresent - } - return resolvedName + public func readList(schema: Schema<[T]>) throws -> [T]? { + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent, resolvedReader.jsonNode == .array else { + return resolvedDefault(schema: schema) != nil ? [] : nil } - } - - public func readString(schema: SmithyReadWrite.Schema) throws -> String? { - try readIfPresent() - } - - public func readList(schema: SmithyReadWrite.Schema<[T]>) throws -> [T]? { - guard hasContent, jsonNode == .array else { return nil } - guard let memberContainer = schema.members.first(where: { $0.member.memberSchema.memberName == "member" }) else { + guard let memberContainer = resolvedTargetSchema(schema: schema).members.first(where: { $0.member.memberSchema.memberName == "member" }) else { throw ReaderError.requiredValueNotPresent } var value = [T]() - try children.forEach { reader in - try memberContainer.performRead(base: &value, reader: reader) + for child in resolvedReader.children { + try memberContainer.performRead(base: &value, reader: child) } return value } - public func readMap(schema: SmithyReadWrite.Schema<[String: T]>) throws -> [String : T]? { - guard hasContent, jsonNode == .object else { return nil } - guard let keyContainer = schema.members.first(where: { $0.member.memberSchema.memberName == "key" }) else { - throw ReaderError.requiredValueNotPresent + public func readMap(schema: Schema<[String: T]>) throws -> [String : T]? { + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent, resolvedReader.jsonNode == .object else { + return resolvedDefault(schema: schema) != nil ? [:] : nil } - guard let valueContainer = schema.members.first(where: { $0.member.memberSchema.memberName == "value" }) else { + guard let valueContainer = resolvedTargetSchema(schema: schema).members.first(where: { $0.member.memberSchema.memberName == "value" }) else { throw ReaderError.requiredValueNotPresent } var value = [String: T]() - try children.forEach { reader in + try resolvedReader.children.forEach { reader in var temp = [String: T]() try valueContainer.performRead(base: &temp, reader: reader) value[reader.nodeInfo.name] = temp["value"] @@ -92,55 +66,189 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { return value } + public func readEnum(schema: Schema) throws -> T? where T.RawValue == String { + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent, case .string = resolvedReader.jsonNode else { + return try resolvedDefault(schema: schema).map { T(rawValue: try $0.asString())! } + } + guard let rawValue: String = try resolvedReader.readIfPresent() else { return nil } + for memberContainer in resolvedTargetSchema(schema: schema).members { + guard let resolvedEnumValue = try memberContainer.member.memberSchema.enumValue?.asString() ?? memberContainer.member.memberSchema.memberName else { + throw ReaderError.requiredValueNotPresent + } + if rawValue == resolvedEnumValue { + return T(rawValue: rawValue) + } + } + return T(rawValue: "") + } + + public func readIntEnum(schema: Schema) throws -> T? where T.RawValue == Int { + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent, case .number = resolvedReader.jsonNode else { + return try resolvedDefault(schema: schema).map { T(rawValue: try $0.asInteger())! } + } + guard let rawValue: Int = try resolvedReader.readIfPresent() else { return nil } + for memberContainer in resolvedTargetSchema(schema: schema).members { + guard let resolvedEnumValue = try memberContainer.member.memberSchema.enumValue?.asInteger() else { + throw ReaderError.requiredValueNotPresent + } + if rawValue == resolvedEnumValue { + return T(rawValue: rawValue) + } + } + return T(rawValue: Int.min) + } + + public func readString(schema: Schema) throws -> String? { + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent, case .string = resolvedReader.jsonNode else { + return try resolvedDefault(schema: schema)?.asString() + } + return try resolvedReader.readIfPresent() + } + public func readBoolean(schema: SmithyReadWrite.Schema) throws -> Bool? { - try readIfPresent() + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent, case .bool = resolvedReader.jsonNode else { + return try resolvedDefault(schema: schema)?.asBoolean() + } + return try resolvedReader.readIfPresent() } public func readByte(schema: SmithyReadWrite.Schema) throws -> Int8? { - try readIfPresent() + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent, case .number = resolvedReader.jsonNode else { + return try resolvedDefault(schema: schema)?.asByte() + } + return try resolvedReader.readIfPresent() } public func readShort(schema: SmithyReadWrite.Schema) throws -> Int16? { - try readIfPresent() + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent, case .number = resolvedReader.jsonNode else { + return try resolvedDefault(schema: schema)?.asShort() + } + return try resolvedReader.readIfPresent() } public func readInteger(schema: SmithyReadWrite.Schema) throws -> Int? { - try readIfPresent() + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent, case .number = resolvedReader.jsonNode else { + return try resolvedDefault(schema: schema)?.asInteger() + } + return try resolvedReader.readIfPresent() } public func readLong(schema: SmithyReadWrite.Schema) throws -> Int? { - try readIfPresent() + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent, case .number = resolvedReader.jsonNode else { + return try resolvedDefault(schema: schema)?.asInteger() + } + return try resolvedReader.readIfPresent() } public func readFloat(schema: SmithyReadWrite.Schema) throws -> Float? { - try readIfPresent() + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent else { + return try resolvedDefault(schema: schema)?.asFloat() + } + return try resolvedReader.readIfPresent() } public func readDouble(schema: SmithyReadWrite.Schema) throws -> Double? { - try readIfPresent() + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent else { + return try resolvedDefault(schema: schema)?.asDouble() + } + return try resolvedReader.readIfPresent() } - public func readBigInteger(schema: SmithyReadWrite.Schema) throws -> Int? { - try readIfPresent() + public func readBigInteger(schema: SmithyReadWrite.Schema) throws -> Int64? { + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent, case .number = resolvedReader.jsonNode else { + return try resolvedDefault(schema: schema)?.asBigInteger() + } + let int: Int? = try resolvedReader.readIfPresent() + return int.map { Int64($0) } } public func readBigDecimal(schema: SmithyReadWrite.Schema) throws -> Double? { - try readIfPresent() + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent else { + return try resolvedDefault(schema: schema)?.asDouble() + } + return try resolvedReader.readIfPresent() } public func readBlob(schema: SmithyReadWrite.Schema) throws -> Data? { - try readIfPresent() + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent, case .string = resolvedReader.jsonNode else { + guard let base64String = try resolvedDefault(schema: schema)?.asString() else { return nil } + return Data(base64Encoded: base64String) + } + return try resolvedReader.readIfPresent() } public func readTimestamp(schema: SmithyReadWrite.Schema) throws -> Date? { - try readTimestampIfPresent(format: schema.timestampFormat ?? .epochSeconds) + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent else { + return try resolvedDefault(schema: schema)?.asTimestamp() + } + let memberSchema = schema.type == .member ? schema : nil + let timestampSchema = schema.targetSchema() ?? schema + let resolvedTimestampFormat = memberSchema?.timestampFormat ?? timestampSchema.timestampFormat + return try resolvedReader.readTimestampIfPresent(format: resolvedTimestampFormat ?? .epochSeconds) } public func readDocument(schema: SmithyReadWrite.Schema) throws -> Document? { - try readIfPresent() + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent else { + return resolvedDefault(schema: schema).map { Document($0) } + } + return try resolvedReader.readIfPresent() } public func readNull(schema: any SmithyReadWrite.SchemaProtocol) throws -> Bool? { - try readNullIfPresent() + let resolvedReader = try resolvedReader(schema: schema) + guard resolvedReader.hasContent, case .null = resolvedReader.jsonNode else { + return false + } + return try resolvedReader.readIfPresent() + } + + private func resolvedReader(schema: any SchemaProtocol) throws -> Reader { + if schema.type == .member { + let resolvedName = try resolvedName(memberSchema: schema) + return self[NodeInfo(resolvedName)] + } else { + return self + } + } + + private func resolvedDefault(schema: Schema) -> (any SmithyDocument)? { + if schema.type == .member { + return schema.defaultValue ?? schema.targetSchema()?.defaultValue + } else { + return schema.defaultValue + } + } + + private func resolvedTargetSchema(schema: Schema) -> Schema { + schema.targetSchema() ?? schema + } + + private func resolvedName(memberSchema: any SchemaProtocol) throws -> String { + if respectsJSONName { + guard let resolvedName = memberSchema.jsonName ?? memberSchema.memberName else { + throw ReaderError.requiredValueNotPresent + } + return resolvedName + } else { + guard let resolvedName = memberSchema.memberName else { + throw ReaderError.requiredValueNotPresent + } + return resolvedName + } } } diff --git a/Sources/SmithyReadWrite/Schema/Schema.swift b/Sources/SmithyReadWrite/Schema/Schema.swift index 66426047d..1ec698b01 100644 --- a/Sources/SmithyReadWrite/Schema/Schema.swift +++ b/Sources/SmithyReadWrite/Schema/Schema.swift @@ -13,15 +13,11 @@ import struct Foundation.Date @_spi(SchemaBasedSerde) public protocol SchemaProtocol: AnyObject { var type: ShapeType { get } - var targetSchema: () -> SchemaProtocol? { get } var defaultValue: (any SmithyDocument)? { get } var jsonName: String? { get } var enumValue: (any SmithyDocument)? { get } var memberName: String? { get } var isRequired: Bool { get } - - func read(reader: any ShapeDeserializer) throws - func write(writer: any SmithyWriter) throws } public extension SchemaProtocol { @@ -60,35 +56,37 @@ public protocol MemberProtocol { } @_spi(SchemaBasedSerde) -public class Schema: SchemaProtocol { - - public struct MemberContainer { - public let member: any MemberProtocol +public struct MemberContainer { + public let member: any MemberProtocol - public init(member: any MemberProtocol) { - self.member = member - } + public init(member: any MemberProtocol) { + self.member = member + } - public func performRead(base: inout Base, reader: any ShapeDeserializer) throws { - try member.performRead(base: &base, reader: reader) - } + public func performRead(base: inout Base, reader: any ShapeDeserializer) throws { + try member.performRead(base: &base, reader: reader) + } - public func performWrite(base: Base, writer: any SmithyWriter) throws { - try member.performWrite(base: base, writer: writer) - } + public func performWrite(base: Base, writer: any SmithyWriter) throws { + try member.performWrite(base: base, writer: writer) } +} + +@_spi(SchemaBasedSerde) +public class Schema: SchemaProtocol { public struct Member: MemberProtocol { - public let memberSchema: any SchemaProtocol + public var memberSchema: any SchemaProtocol { memberSchemaSpecific } + public let memberSchemaSpecific: Schema let readBlock: (inout Base, any ShapeDeserializer) throws -> Void let writeBlock: (Base, any SmithyWriter) throws -> Void public init( - memberSchema: any SchemaProtocol, + memberSchema: Schema, readBlock: @escaping (inout Base, any ShapeDeserializer) throws -> Void = { _, _ in }, writeBlock: @escaping (Base, any SmithyWriter) throws -> Void = { _, _ in } ) { - self.memberSchema = memberSchema + self.memberSchemaSpecific = memberSchema self.readBlock = readBlock self.writeBlock = writeBlock } @@ -103,45 +101,37 @@ public class Schema: SchemaProtocol { } public let type: ShapeType - public let members: [MemberContainer] - public let targetSchemaSpecific: () -> Schema? + public let members: [MemberContainer] + public let targetSchema: () -> Schema? public let memberName: String? + public let containerType: ShapeType? public let jsonName: String? public let enumValue: (any SmithyDocument)? public let timestampFormat: SmithyTimestamps.TimestampFormat? public let isRequired: Bool public let defaultValue: (any Smithy.SmithyDocument)? - - public var targetSchema: () -> (any SchemaProtocol)? { targetSchemaSpecific } - public init( type: ShapeType, - members: [MemberContainer] = [], + members: [MemberContainer] = [], targetSchema: @escaping () -> Schema? = { nil }, memberName: String? = nil, + containerType: ShapeType? = nil, jsonName: String? = nil, enumValue: (any SmithyDocument)? = nil, timestampFormat: SmithyTimestamps.TimestampFormat? = nil, isRequired: Bool = false, - defaultValue: (any Smithy.SmithyDocument)? = nil + defaultValue: (any SmithyDocument)? = nil ) { self.type = type self.members = members - self.targetSchemaSpecific = targetSchema + self.targetSchema = targetSchema self.memberName = memberName + self.containerType = containerType self.jsonName = jsonName self.enumValue = enumValue self.timestampFormat = timestampFormat self.isRequired = isRequired self.defaultValue = defaultValue } - - public func read(reader: any ShapeDeserializer) throws { - // TODO: implement - } - - public func write(writer: any SmithyWriter) throws { - // TODO: implement - } } diff --git a/Sources/SmithyReadWrite/ShapeDeserializer.swift b/Sources/SmithyReadWrite/ShapeDeserializer.swift index 757bd0952..1b19dfac6 100644 --- a/Sources/SmithyReadWrite/ShapeDeserializer.swift +++ b/Sources/SmithyReadWrite/ShapeDeserializer.swift @@ -22,7 +22,7 @@ public protocol ShapeDeserializer: SmithyReader { func readLong(schema: Schema) throws -> Int? func readFloat(schema: Schema) throws -> Float? func readDouble(schema: Schema) throws -> Double? - func readBigInteger(schema: Schema) throws -> Int? + func readBigInteger(schema: Schema) throws -> Int64? func readBigDecimal(schema: Schema) throws -> Double? func readString(schema: Schema) throws -> String? func readBlob(schema: Schema) throws -> Data? @@ -36,34 +36,6 @@ public protocol ShapeDeserializer: SmithyReader { @_spi(SchemaBasedSerde) public extension ShapeDeserializer { - func readEnum(schema: Schema) throws -> T? where T.RawValue == String { - guard hasContent else { return nil } - guard let rawValue: String = try readIfPresent() else { throw ReaderError.requiredValueNotPresent } - for memberContainer in schema.members { - guard let resolvedEnumValue = try memberContainer.member.memberSchema.enumValue?.asString() ?? memberContainer.member.memberSchema.memberName else { - throw ReaderError.requiredValueNotPresent - } - if rawValue == resolvedEnumValue { - return T(rawValue: rawValue) - } - } - return T(rawValue: "") - } - - func readIntEnum(schema: Schema) throws -> T? where T.RawValue == Int { - guard hasContent else { return nil } - guard let rawValue: Int = try readIfPresent() else { throw ReaderError.requiredValueNotPresent } - for memberContainer in schema.members { - guard let resolvedEnumValue = try memberContainer.member.memberSchema.enumValue?.asInteger() else { - throw ReaderError.requiredValueNotPresent - } - if rawValue == resolvedEnumValue { - return T(rawValue: rawValue) - } - } - return T(rawValue: Int.min) - } - func readEnumNonNull(schema: Schema) throws -> T where T.RawValue == String { guard let value: T = try readEnum(schema: schema) else { throw ReaderError.requiredValueNotPresent @@ -96,7 +68,6 @@ public extension ShapeDeserializer { throw ReaderError.requiredValueNotPresent } return value -// return try readList(schema: schema) ?? [] } func readMapNonNull(schema: Schema<[String: T]>) throws -> [String: T] { @@ -104,7 +75,6 @@ public extension ShapeDeserializer { throw ReaderError.requiredValueNotPresent } return value -// return try readMap(schema: schema) ?? [:] } func readBooleanNonNull(schema: Schema) throws -> Bool { @@ -112,7 +82,6 @@ public extension ShapeDeserializer { throw ReaderError.requiredValueNotPresent } return value -// return try readBoolean(schema: schema) ?? false } func readByteNonNull(schema: Schema) throws -> Int8 { @@ -120,7 +89,6 @@ public extension ShapeDeserializer { throw ReaderError.requiredValueNotPresent } return value -// return try readByte(schema: schema) ?? 0 } func readShortNonNull(schema: Schema) throws -> Int16 { @@ -128,7 +96,6 @@ public extension ShapeDeserializer { throw ReaderError.requiredValueNotPresent } return value -// return try readShort(schema: schema) ?? 0 } func readIntegerNonNull(schema: Schema) throws -> Int { @@ -136,7 +103,6 @@ public extension ShapeDeserializer { throw ReaderError.requiredValueNotPresent } return value -// return try readInteger(schema: schema) ?? 0 } func readLongNonNull(schema: Schema) throws -> Int { @@ -144,7 +110,6 @@ public extension ShapeDeserializer { throw ReaderError.requiredValueNotPresent } return value -// return try readLong(schema: schema) ?? 0 } func readFloatNonNull(schema: Schema) throws -> Float { @@ -152,7 +117,6 @@ public extension ShapeDeserializer { throw ReaderError.requiredValueNotPresent } return value -// return try readFloat(schema: schema) ?? 0.0 } func readDoubleNonNull(schema: Schema) throws -> Double { @@ -160,15 +124,13 @@ public extension ShapeDeserializer { throw ReaderError.requiredValueNotPresent } return value -// return try readDouble(schema: schema) ?? 0.0 } - func readBigIntegerNonNull(schema: Schema) throws -> Int { + func readBigIntegerNonNull(schema: Schema) throws -> Int64 { guard let value = try readBigInteger(schema: schema) else { throw ReaderError.requiredValueNotPresent } return value -// return try readBigInteger(schema: schema) ?? 0 } func readBigDecimalNonNull(schema: Schema) throws -> Double { @@ -176,7 +138,6 @@ public extension ShapeDeserializer { throw ReaderError.requiredValueNotPresent } return value -// return try readBigDecimal(schema: schema) ?? 0.0 } func readStringNonNull(schema: Schema) throws -> String { @@ -184,7 +145,6 @@ public extension ShapeDeserializer { throw ReaderError.requiredValueNotPresent } return value -// return try readString(schema: schema) ?? "" } func readBlobNonNull(schema: Schema) throws -> Data { @@ -192,7 +152,6 @@ public extension ShapeDeserializer { throw ReaderError.requiredValueNotPresent } return value -// return try readBlob(schema: schema) ?? Data() } func readTimestampNonNull(schema: Schema) throws -> Date { @@ -200,7 +159,6 @@ public extension ShapeDeserializer { throw ReaderError.requiredValueNotPresent } return value -// return try readTimestamp(schema: schema) ?? Date(timeIntervalSince1970: 0.0) } func readDocumentNonNull(schema: Schema) throws -> Document { @@ -208,7 +166,6 @@ public extension ShapeDeserializer { throw ReaderError.requiredValueNotPresent } return value -// return try readDocument(schema: schema) ?? Document(NullDocument()) } func readNullNonNull(schema: SchemaProtocol) throws -> Bool { @@ -216,6 +173,5 @@ public extension ShapeDeserializer { throw ReaderError.requiredValueNotPresent } return value -// return try readNull(schema: schema) ?? false } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt index add904781..734a3f5da 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt @@ -83,6 +83,7 @@ import software.amazon.smithy.swift.codegen.utils.ModelFileUtils import software.amazon.smithy.utils.OptionalUtils import java.util.Optional import java.util.logging.Logger +import kotlin.jvm.optionals.getOrNull private val Shape.isStreaming: Boolean get() = hasTrait() && isUnionShape @@ -218,7 +219,10 @@ abstract class HTTPBindingProtocolGenerator( override fun generateSchemas(ctx: ProtocolGenerator.GenerationContext) { if (ctx.service.responseWireProtocol != WireProtocol.JSON) { return } val nestedShapes = resolveShapesNeedingSchema(ctx) - .filter { !it.isStreaming } + .filter { !it.hasTrait() } + .filter { !(it.asMemberShape().getOrNull()?.let { + ctx.model.expectShape(it.target).hasTrait() } ?: false) + } for (shape in nestedShapes) { renderSchemas(ctx, shape) } @@ -355,22 +359,17 @@ abstract class HTTPBindingProtocolGenerator( } private fun resolveShapesNeedingSchema(ctx: ProtocolGenerator.GenerationContext): Set { - val topLevelOutputMembers = getHttpBindingOperations(ctx).flatMap { - val outputShape = ctx.model.expectShape(it.output.get()) - outputShape.members() - } - .map { ctx.model.expectShape(it.target) } + val topLevelOutputMembers = getHttpBindingOperations(ctx) + .map { ctx.model.expectShape(it.output.get()) } .toSet() val topLevelErrorMembers = getHttpBindingOperations(ctx) .flatMap { it.errors } - .flatMap { ctx.model.expectShape(it).members() } - .map { ctx.model.expectShape(it.target) } + .map { ctx.model.expectShape(it) } .toSet() val topLevelServiceErrorMembers = ctx.service.errors - .flatMap { ctx.model.expectShape(it).members() } - .map { ctx.model.expectShape(it.target) } + .map { ctx.model.expectShape(it) } .toSet() // val topLevelInputMembers = getHttpBindingOperations(ctx).flatMap { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt index aebcef0bb..76f79c419 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt @@ -92,7 +92,7 @@ open class MemberShapeDecodeGenerator( "try \$L.readStructure\$L(schema: \$L)", reader(memberShape, isPayload), "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", - target.schemaVar, + memberShape.schemaVar, ) } val readingClosure = readingClosureUtils.readingClosure(memberShape) @@ -111,7 +111,7 @@ open class MemberShapeDecodeGenerator( "try \$L.readList\$L(schema: \$L)", reader(memberShape, false), "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", - target.schemaVar, + memberShape.schemaVar, ) } val isSparse = listShape.hasTrait() @@ -136,7 +136,7 @@ open class MemberShapeDecodeGenerator( "try \$L.readMap\$L(schema: \$L)", reader(memberShape, false), "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", - target.schemaVar, + memberShape.schemaVar, ) } val isSparse = mapShape.hasTrait() @@ -180,7 +180,7 @@ open class MemberShapeDecodeGenerator( reader(memberShape, isPayload), target.readMethodName, "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", - target.schemaVar, + memberShape.schemaVar, ) } return writer.format( @@ -198,7 +198,7 @@ open class MemberShapeDecodeGenerator( private fun reader(memberShape: MemberShape, isPayload: Boolean): String { val nodeInfo = nodeInfoUtils.nodeInfo(memberShape) - return "reader".takeIf { isPayload } ?: writer.format("reader[\$L]", nodeInfo) + return "reader".takeIf { isPayload || useSBS } ?: writer.format("reader[\$L]", nodeInfo) } private fun default(memberShape: MemberShape): String { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt index fbc6c0ea8..47c18273c 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt @@ -11,6 +11,7 @@ import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.model.traits.JsonNameTrait import software.amazon.smithy.model.traits.RequiredTrait import software.amazon.smithy.model.traits.SparseTrait +import software.amazon.smithy.model.traits.StreamingTrait import software.amazon.smithy.model.traits.TimestampFormatTrait import software.amazon.smithy.swift.codegen.SwiftWriter import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator @@ -27,19 +28,38 @@ class SchemaGenerator( ) { fun renderSchema(shape: Shape) { - writer.writeInline("let \$L: \$N<\$N> = ", shape.schemaVar, SmithyReadWriteTypes.Schema, ctx.symbolProvider.toSymbol(shape)) writer.openBlock( - "{ \$N<\$N>(", - ") },", + "var \$L: \$N<\$N> {", + "}", + shape.schemaVar, + SmithyReadWriteTypes.Schema, + ctx.symbolProvider.toSymbol(shape), + ) { + renderSchemaStruct(shape) + } + } + + private fun renderSchemaStruct(shape: Shape) { + writer.openBlock( + "\$N<\$N>(", + ")", SmithyReadWriteTypes.Schema, ctx.symbolProvider.toSymbol(shape), ) { writer.write("type: .\$L,", shape.type) if (shape.members().isNotEmpty()) { writer.openBlock("members: [", "],") { - shape.members().forEach { member -> + shape.members() + .filter { !ctx.model.expectShape(it.target).hasTrait() } + .forEach { member -> writer.openBlock(".init(member:", "),") { - writer.openBlock("\$N<\$N>.Member<\$N>(", ")", SmithyReadWriteTypes.Schema, ctx.symbolProvider.toSymbol(shape), ctx.symbolProvider.toSymbol(shape)) { + writer.openBlock( + "\$N<\$N>.Member<\$N>(", + ")", + SmithyReadWriteTypes.Schema, + ctx.symbolProvider.toSymbol(shape), + ctx.symbolProvider.toSymbol(ctx.model.expectShape(member.target)), + ) { writer.write("memberSchema: \$L,", member.schemaVar) writeSetterGetter(writer, shape, member) writer.unwrite(",\n") @@ -51,8 +71,9 @@ class SchemaGenerator( writer.write("") } } - targetSchema(shape)?.let { writer.write("targetSchema: { \$L },", it.schemaVar) } + targetShape(shape)?.let { writer.write("targetSchema: { \$L },", it.schemaVar) } shape.id.member.getOrNull()?.let { writer.write("memberName: \$S,", it) } + memberShape(shape)?.let { writer.write("containerType: \$L,", ctx.model.expectShape(it.container).type) } jsonName(shape)?.let { writer.write("jsonName: \$S,", it) } enumValue(shape)?.let { node -> when (node.type) { @@ -72,9 +93,6 @@ class SchemaGenerator( writer.unwrite(",\n") writer.write("") } - writer.unwrite(",\n") - writer.write("()") - writer.write("") } private fun writeSetterGetter(writer: SwiftWriter, shape: Shape, member: MemberShape) { @@ -82,10 +100,10 @@ class SchemaGenerator( val readMethodName = target.readMethodName val memberIsRequired = member.isRequired || (member.hasTrait() || target.hasTrait()) - || (shape.isMapShape && member.memberName == "key") || (shape.isMapShape && member.memberName == "value" && !shape.hasTrait()) || (shape.isListShape && !shape.hasTrait()) - val readMethodExtension = "NonNull".takeIf { memberIsRequired || shape.isUnionShape } ?: "" + || shape.isUnionShape + val readMethodExtension = "NonNull".takeIf { memberIsRequired } ?: "" when (shape.type) { ShapeType.STRUCTURE -> { val path = "properties.".takeIf { shape.hasTrait() } ?: "" @@ -100,10 +118,10 @@ class SchemaGenerator( } ShapeType.UNION -> { writer.write( - "readBlock: { try \$\$0 = .\$L(\$\$1.\$LNonNull(schema: \$L)) },", - ctx.symbolProvider.toMemberName(member), + "readBlock: { value, reader in try reader.\$L(schema: \$L).map { unwrapped in value = .\$L(unwrapped) } },", readMethodName, member.schemaVar, + ctx.symbolProvider.toMemberName(member), ) } ShapeType.SET, ShapeType.LIST -> { @@ -127,8 +145,12 @@ class SchemaGenerator( } } - private fun targetSchema(shape: Shape): Shape? { - return shape.asMemberShape().getOrNull()?.let { ctx.model.expectShape(it.target) } + private fun targetShape(shape: Shape): Shape? { + return memberShape(shape)?.let { ctx.model.expectShape(it.target) } + } + + private fun memberShape(shape: Shape): MemberShape? { + return shape.asMemberShape().getOrNull() } private fun enumValue(shape: Shape): Node? { From 37f013c74efe959406bc1f43a5dc8293e948ab88 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Sat, 4 Jan 2025 23:23:00 -0600 Subject: [PATCH 07/16] All protocol tests pass --- .../Reader/Reader+ShapeDeserializer.swift | 45 ++++-- Sources/SmithyJSON/Reader/Reader.swift | 2 + .../Schema/DefaultValueTransformers.swift | 152 ------------------ Sources/SmithyReadWrite/Schema/Require.swift | 24 --- Sources/SmithyReadWrite/Schema/Schema.swift | 20 ++- .../SmithyReadWrite/ShapeDeserializer.swift | 25 ++- .../swift/codegen/StructureGenerator.kt | 19 +-- .../HTTPResponseBindingErrorGenerator.kt | 5 + .../serde/schema/SchemaGenerator.kt | 13 +- .../codegen/swiftmodules/SmithyJSONTypes.kt | 2 +- .../swiftmodules/SmithyReadWriteTypes.kt | 4 +- 11 files changed, 90 insertions(+), 221 deletions(-) delete mode 100644 Sources/SmithyReadWrite/Schema/DefaultValueTransformers.swift delete mode 100644 Sources/SmithyReadWrite/Schema/Require.swift diff --git a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift index cc77b8c1a..fc6708c9e 100644 --- a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift +++ b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift @@ -7,16 +7,17 @@ import struct Foundation.Data import struct Foundation.Date +import struct Foundation.TimeInterval import struct Smithy.Document import protocol Smithy.SmithyDocument import enum SmithyReadWrite.ReaderError -@_spi(SchemaBasedSerde) import protocol SmithyReadWrite.ShapeDeserializer -@_spi(SchemaBasedSerde) import protocol SmithyReadWrite.SchemaProtocol -@_spi(SchemaBasedSerde) import protocol SmithyReadWrite.DeserializableShape -@_spi(SchemaBasedSerde) import class SmithyReadWrite.Schema +@_spi(SmithyReadWrite) import protocol SmithyReadWrite.ShapeDeserializer +@_spi(SmithyReadWrite) import protocol SmithyReadWrite.SchemaProtocol +@_spi(SmithyReadWrite) import protocol SmithyReadWrite.DeserializableShape +@_spi(SmithyReadWrite) import struct SmithyReadWrite.Schema @_spi(SmithyTimestamps) import enum SmithyTimestamps.TimestampFormat -@_spi(SchemaBasedSerde) +@_spi(SmithyReadWrite) extension Reader: SmithyReadWrite.ShapeDeserializer { public func readStructure( @@ -39,11 +40,13 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { guard resolvedReader.hasContent, resolvedReader.jsonNode == .array else { return resolvedDefault(schema: schema) != nil ? [] : nil } - guard let memberContainer = resolvedTargetSchema(schema: schema).members.first(where: { $0.member.memberSchema.memberName == "member" }) else { + let listSchema = resolvedTargetSchema(schema: schema) + guard let memberContainer = listSchema.members.first(where: { $0.member.memberSchema.memberName == "member" }) else { throw ReaderError.requiredValueNotPresent } var value = [T]() for child in resolvedReader.children { + child.respectsJSONName = respectsJSONName try memberContainer.performRead(base: &value, reader: child) } return value @@ -54,14 +57,17 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { guard resolvedReader.hasContent, resolvedReader.jsonNode == .object else { return resolvedDefault(schema: schema) != nil ? [:] : nil } - guard let valueContainer = resolvedTargetSchema(schema: schema).members.first(where: { $0.member.memberSchema.memberName == "value" }) else { + let mapSchema = resolvedTargetSchema(schema: schema) + guard let valueContainer = mapSchema.members.first(where: { $0.member.memberSchema.memberName == "value" }) else { throw ReaderError.requiredValueNotPresent } var value = [String: T]() - try resolvedReader.children.forEach { reader in + for child in resolvedReader.children { + child.respectsJSONName = respectsJSONName + if !mapSchema.isSparse && child.jsonNode == .null { continue } var temp = [String: T]() - try valueContainer.performRead(base: &temp, reader: reader) - value[reader.nodeInfo.name] = temp["value"] + try valueContainer.performRead(base: &temp, reader: child) + value[child.nodeInfo.name] = temp["value"] } return value } @@ -193,7 +199,18 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { public func readTimestamp(schema: SmithyReadWrite.Schema) throws -> Date? { let resolvedReader = try resolvedReader(schema: schema) guard resolvedReader.hasContent else { - return try resolvedDefault(schema: schema)?.asTimestamp() + guard let defaultValue = try resolvedDefault(schema: schema) else { return nil } + switch defaultValue.type { + case .float: + let interval = try TimeInterval(defaultValue.asFloat()) + return try Date(timeIntervalSince1970: interval) + case .double: + return try Date(timeIntervalSince1970: defaultValue.asDouble()) + case .timestamp: + return try defaultValue.asTimestamp() + default: + throw SmithyReadWrite.ReaderError.invalidSchema("Unsupported timestamp default type: \(defaultValue.type)") + } } let memberSchema = schema.type == .member ? schema : nil let timestampSchema = schema.targetSchema() ?? schema @@ -218,7 +235,11 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { } private func resolvedReader(schema: any SchemaProtocol) throws -> Reader { - if schema.type == .member { + if schema.httpPayload { + return self + } else if schema.containerType == .map || schema.containerType == .list || schema.containerType == .set { + return self + } else if schema.type == .member { let resolvedName = try resolvedName(memberSchema: schema) return self[NodeInfo(resolvedName)] } else { diff --git a/Sources/SmithyJSON/Reader/Reader.swift b/Sources/SmithyJSON/Reader/Reader.swift index 6232c817e..7c920ebd9 100644 --- a/Sources/SmithyJSON/Reader/Reader.swift +++ b/Sources/SmithyJSON/Reader/Reader.swift @@ -32,6 +32,7 @@ public final class Reader: SmithyReader { init(nodeInfo: NodeInfo, jsonObject: Any?, parent: Reader? = nil) throws { self.nodeInfo = nodeInfo self.jsonNode = try Self.jsonNode(for: jsonObject) + self.respectsJSONName = parent?.respectsJSONName ?? false self.parent = parent self.children = try Self.children(from: jsonObject, parent: self) } @@ -76,6 +77,7 @@ public extension Reader { subscript(nodeInfo: NodeInfo) -> Reader { if let match = children.first(where: { nodeInfo.name == $0.nodeInfo.name }) { + match.respectsJSONName = respectsJSONName return match } else { // The queried node doesn't exist. Return one that has nil content. diff --git a/Sources/SmithyReadWrite/Schema/DefaultValueTransformers.swift b/Sources/SmithyReadWrite/Schema/DefaultValueTransformers.swift deleted file mode 100644 index e808e9130..000000000 --- a/Sources/SmithyReadWrite/Schema/DefaultValueTransformers.swift +++ /dev/null @@ -1,152 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -import struct Foundation.Data -import struct Foundation.Date -import protocol Smithy.SmithyDocument -import struct Smithy.Document - -@_spi(SchemaBasedSerde) -public enum DefaultValueTransformer { - -// public static func optional(_ document: (any SmithyDocument)?) throws -> Base? { -// return document != nil ? Base() : nil -// } -// -// public static func required(_ document: (any SmithyDocument)?) throws -> Base { -// guard let value: Base = try optional(document) else { throw ReaderError.requiredValueNotPresent } -// return value -// } -// -// public static func optional(_ document: (any SmithyDocument)?) throws -> [Element]? { -// return document != nil ? [] : nil -// } -// -// public static func required(_ document: (any SmithyDocument)?) throws -> [Element] { -// guard let value: [Element] = try optional(document) else { throw ReaderError.requiredValueNotPresent } -// return value -// } -// -// public static func optional(_ document: (any SmithyDocument)?) throws -> [String: Element]? { -// return document != nil ? [:] : nil -// } -// -// public static func required(_ document: (any SmithyDocument)?) throws -> [String: Element] { -// guard let value: [String: Element] = try optional(document) else { throw ReaderError.requiredValueNotPresent } -// return value -// } -// -// public static func optional(_ document: (any SmithyDocument)?) throws -> Enum? where Enum.RawValue == String { -// guard let rawValue = try document?.asString() else { return nil } -// return Enum(rawValue: rawValue) -// } -// -// public static func required(_ document: (any SmithyDocument)?) throws -> Enum where Enum.RawValue == String { -// guard let value: Enum = try optional(document) else { throw ReaderError.requiredValueNotPresent } -// return value -// } -// -// public static func optional(_ document: (any SmithyDocument)?) throws -> IntEnum? where IntEnum.RawValue == Int { -// guard let rawValue = try document?.asInteger() else { return nil } -// return IntEnum(rawValue: rawValue) -// } -// -// public static func required(_ document: (any SmithyDocument)?) throws -> IntEnum where IntEnum.RawValue == Int { -// guard let value: IntEnum = try optional(document) else { throw ReaderError.requiredValueNotPresent } -// return value -// } -// -// public static func optional(_ document: (any SmithyDocument)?) throws -> Bool? { -// return try document?.asBoolean() -// } -// -// public static func required(_ document: (any SmithyDocument)?) throws -> Bool { -// guard let value: Bool = try optional(document) else { throw ReaderError.requiredValueNotPresent } -// return value -// } -// -// public static func optional(_ document: (any SmithyDocument)?) throws -> Data? { -// return try document?.asBlob() -// } -// -// public static func required(_ document: (any SmithyDocument)?) throws -> Data { -// guard let value: Data = try optional(document) else { throw ReaderError.requiredValueNotPresent } -// return value -// } -// -// public static func optional(_ document: (any SmithyDocument)?) throws -> Date? { -// return try document?.asTimestamp() -// } -// -// public static func required(_ document: (any SmithyDocument)?) throws -> Date { -// guard let value: Date = try optional(document) else { throw ReaderError.requiredValueNotPresent } -// return value -// } -// -// public static func optional(_ document: (any SmithyDocument)?) throws -> String? { -// return try document?.asString() -// } -// -// public static func required(_ document: (any SmithyDocument)?) throws -> String { -// guard let value: String = try optional(document) else { throw ReaderError.requiredValueNotPresent } -// return value -// } -// -// public static func optional(_ document: (any SmithyDocument)?) throws -> Int8? { -// return try document?.asByte() -// } -// -// public static func required(_ document: (any SmithyDocument)?) throws -> Int8 { -// guard let value: Int8 = try optional(document) else { throw ReaderError.requiredValueNotPresent } -// return value -// } -// -// public static func optional(_ document: (any SmithyDocument)?) throws -> Int16? { -// return try document?.asShort() -// } -// -// public static func required(_ document: (any SmithyDocument)?) throws -> Int16 { -// guard let value: Int16 = try optional(document) else { throw ReaderError.requiredValueNotPresent } -// return value -// } -// -// public static func optional(_ document: (any SmithyDocument)?) throws -> Int? { -// return try document?.asInteger() -// } -// -// public static func required(_ document: (any SmithyDocument)?) throws -> Int { -// guard let value: Int = try optional(document) else { throw ReaderError.requiredValueNotPresent } -// return value -// } -// -// public static func optional(_ document: (any SmithyDocument)?) throws -> Float? { -// return try document?.asFloat() -// } -// -// public static func required(_ document: (any SmithyDocument)?) throws -> Float { -// guard let value: Float = try optional(document) else { throw ReaderError.requiredValueNotPresent } -// return value -// } -// -// public static func optional(_ document: (any SmithyDocument)?) throws -> Double? { -// return try document?.asDouble() -// } -// -// public static func required(_ document: (any SmithyDocument)?) throws -> Double { -// guard let value: Double = try optional(document) else { throw ReaderError.requiredValueNotPresent } -// return value -// } -// -// public static func optional(_ document: (any SmithyDocument)?) throws -> Document? { -// return try document.map { Document($0) } -// } -// -// public static func required(_ document: (any SmithyDocument)?) throws -> Document { -// guard let value: Document = try optional(document) else { throw ReaderError.requiredValueNotPresent } -// return value -// } -} diff --git a/Sources/SmithyReadWrite/Schema/Require.swift b/Sources/SmithyReadWrite/Schema/Require.swift deleted file mode 100644 index 5e564b9ce..000000000 --- a/Sources/SmithyReadWrite/Schema/Require.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// File.swift -// smithy-swift -// -// Created by Elkins, Josh on 12/26/24. -// - -//import Foundation -// -//@_spi(SchemaBasedSerdeImpl) -//extension Optional { -// -// func require() throws -> DeserializableShape { -// guard self else { throw ReaderError.requiredValueNotPresent } -// return self -// } -// -// func require() throws -> Array { -// guard self else { throw ReaderError.requiredValueNotPresent } -// return self -// } -// -// func require -//} diff --git a/Sources/SmithyReadWrite/Schema/Schema.swift b/Sources/SmithyReadWrite/Schema/Schema.swift index 1ec698b01..2acfce5a6 100644 --- a/Sources/SmithyReadWrite/Schema/Schema.swift +++ b/Sources/SmithyReadWrite/Schema/Schema.swift @@ -10,13 +10,15 @@ import struct Foundation.Date @_spi(SmithyDocumentImpl) import Smithy @_spi(SmithyTimestamps) import SmithyTimestamps -@_spi(SchemaBasedSerde) -public protocol SchemaProtocol: AnyObject { +@_spi(SmithyReadWrite) +public protocol SchemaProtocol { var type: ShapeType { get } var defaultValue: (any SmithyDocument)? { get } var jsonName: String? { get } + var httpPayload: Bool { get } var enumValue: (any SmithyDocument)? { get } var memberName: String? { get } + var containerType: ShapeType? { get } var isRequired: Bool { get } } @@ -47,7 +49,7 @@ public extension SchemaProtocol { } } -@_spi(SchemaBasedSerde) +@_spi(SmithyReadWrite) public protocol MemberProtocol { associatedtype Base var memberSchema: SchemaProtocol { get } @@ -55,7 +57,7 @@ public protocol MemberProtocol { func performWrite(base: Base, writer: any SmithyWriter) throws } -@_spi(SchemaBasedSerde) +@_spi(SmithyReadWrite) public struct MemberContainer { public let member: any MemberProtocol @@ -72,8 +74,8 @@ public struct MemberContainer { } } -@_spi(SchemaBasedSerde) -public class Schema: SchemaProtocol { +@_spi(SmithyReadWrite) +public struct Schema: SchemaProtocol { public struct Member: MemberProtocol { public var memberSchema: any SchemaProtocol { memberSchemaSpecific } @@ -106,8 +108,10 @@ public class Schema: SchemaProtocol { public let memberName: String? public let containerType: ShapeType? public let jsonName: String? + public let httpPayload: Bool public let enumValue: (any SmithyDocument)? public let timestampFormat: SmithyTimestamps.TimestampFormat? + public let isSparse: Bool public let isRequired: Bool public let defaultValue: (any Smithy.SmithyDocument)? @@ -118,8 +122,10 @@ public class Schema: SchemaProtocol { memberName: String? = nil, containerType: ShapeType? = nil, jsonName: String? = nil, + httpPayload: Bool = false, enumValue: (any SmithyDocument)? = nil, timestampFormat: SmithyTimestamps.TimestampFormat? = nil, + isSparse: Bool = false, isRequired: Bool = false, defaultValue: (any SmithyDocument)? = nil ) { @@ -129,8 +135,10 @@ public class Schema: SchemaProtocol { self.memberName = memberName self.containerType = containerType self.jsonName = jsonName + self.httpPayload = httpPayload self.enumValue = enumValue self.timestampFormat = timestampFormat + self.isSparse = isSparse self.isRequired = isRequired self.defaultValue = defaultValue } diff --git a/Sources/SmithyReadWrite/ShapeDeserializer.swift b/Sources/SmithyReadWrite/ShapeDeserializer.swift index 1b19dfac6..d67924cbb 100644 --- a/Sources/SmithyReadWrite/ShapeDeserializer.swift +++ b/Sources/SmithyReadWrite/ShapeDeserializer.swift @@ -10,7 +10,7 @@ import struct Smithy.Document import struct Foundation.Data import struct Foundation.Date -@_spi(SchemaBasedSerde) +@_spi(SmithyReadWrite) public protocol ShapeDeserializer: SmithyReader { func readStructure(schema: Schema) throws -> T? func readList(schema: Schema<[T]>) throws -> [T]? @@ -33,13 +33,12 @@ public protocol ShapeDeserializer: SmithyReader { func readIntEnum(schema: Schema) throws -> T? where T.RawValue == Int } -@_spi(SchemaBasedSerde) +@_spi(SmithyReadWrite) public extension ShapeDeserializer { func readEnumNonNull(schema: Schema) throws -> T where T.RawValue == String { guard let value: T = try readEnum(schema: schema) else { throw ReaderError.requiredValueNotPresent - return T(rawValue: "")! } return value } @@ -47,17 +46,17 @@ public extension ShapeDeserializer { func readIntEnumNonNull(schema: Schema) throws -> T where T.RawValue == Int { guard let value: T = try readIntEnum(schema: schema) else { throw ReaderError.requiredValueNotPresent - return T(rawValue: Int.min)! } return value } } -@_spi(SchemaBasedSerde) +@_spi(SmithyReadWrite) public extension ShapeDeserializer { func readStructureNonNull(schema: Schema) throws -> T { guard let value = try readStructure(schema: schema) else { + if schema.isRequired { return T() } throw ReaderError.requiredValueNotPresent } return value @@ -65,6 +64,7 @@ public extension ShapeDeserializer { func readListNonNull(schema: Schema<[T]>) throws -> [T] { guard let value = try readList(schema: schema) else { + if schema.isRequired { return [] } throw ReaderError.requiredValueNotPresent } return value @@ -72,6 +72,7 @@ public extension ShapeDeserializer { func readMapNonNull(schema: Schema<[String: T]>) throws -> [String: T] { guard let value = try readMap(schema: schema) else { + if schema.isRequired { return [:] } throw ReaderError.requiredValueNotPresent } return value @@ -79,6 +80,7 @@ public extension ShapeDeserializer { func readBooleanNonNull(schema: Schema) throws -> Bool { guard let value = try readBoolean(schema: schema) else { + if schema.isRequired { return false } throw ReaderError.requiredValueNotPresent } return value @@ -86,6 +88,7 @@ public extension ShapeDeserializer { func readByteNonNull(schema: Schema) throws -> Int8 { guard let value = try readByte(schema: schema) else { + if schema.isRequired { return 0 } throw ReaderError.requiredValueNotPresent } return value @@ -93,6 +96,7 @@ public extension ShapeDeserializer { func readShortNonNull(schema: Schema) throws -> Int16 { guard let value = try readShort(schema: schema) else { + if schema.isRequired { return 0 } throw ReaderError.requiredValueNotPresent } return value @@ -100,6 +104,7 @@ public extension ShapeDeserializer { func readIntegerNonNull(schema: Schema) throws -> Int { guard let value = try readInteger(schema: schema) else { + if schema.isRequired { return 0 } throw ReaderError.requiredValueNotPresent } return value @@ -107,6 +112,7 @@ public extension ShapeDeserializer { func readLongNonNull(schema: Schema) throws -> Int { guard let value = try readLong(schema: schema) else { + if schema.isRequired { return 0 } throw ReaderError.requiredValueNotPresent } return value @@ -114,6 +120,7 @@ public extension ShapeDeserializer { func readFloatNonNull(schema: Schema) throws -> Float { guard let value = try readFloat(schema: schema) else { + if schema.isRequired { return 0.0 } throw ReaderError.requiredValueNotPresent } return value @@ -121,6 +128,7 @@ public extension ShapeDeserializer { func readDoubleNonNull(schema: Schema) throws -> Double { guard let value = try readDouble(schema: schema) else { + if schema.isRequired { return 0.0 } throw ReaderError.requiredValueNotPresent } return value @@ -128,6 +136,7 @@ public extension ShapeDeserializer { func readBigIntegerNonNull(schema: Schema) throws -> Int64 { guard let value = try readBigInteger(schema: schema) else { + if schema.isRequired { return 0 } throw ReaderError.requiredValueNotPresent } return value @@ -135,6 +144,7 @@ public extension ShapeDeserializer { func readBigDecimalNonNull(schema: Schema) throws -> Double { guard let value = try readBigDecimal(schema: schema) else { + if schema.isRequired { return 0.0 } throw ReaderError.requiredValueNotPresent } return value @@ -142,6 +152,7 @@ public extension ShapeDeserializer { func readStringNonNull(schema: Schema) throws -> String { guard let value = try readString(schema: schema) else { + if schema.isRequired { return "" } throw ReaderError.requiredValueNotPresent } return value @@ -149,6 +160,7 @@ public extension ShapeDeserializer { func readBlobNonNull(schema: Schema) throws -> Data { guard let value = try readBlob(schema: schema) else { + if schema.isRequired { return Data() } throw ReaderError.requiredValueNotPresent } return value @@ -156,6 +168,7 @@ public extension ShapeDeserializer { func readTimestampNonNull(schema: Schema) throws -> Date { guard let value = try readTimestamp(schema: schema) else { + if schema.isRequired { return Date(timeIntervalSince1970: 0.0) } throw ReaderError.requiredValueNotPresent } return value @@ -163,6 +176,7 @@ public extension ShapeDeserializer { func readDocumentNonNull(schema: Schema) throws -> Document { guard let value = try readDocument(schema: schema) else { + if schema.isRequired { return Document(NullDocument()) } throw ReaderError.requiredValueNotPresent } return value @@ -170,6 +184,7 @@ public extension ShapeDeserializer { func readNullNonNull(schema: SchemaProtocol) throws -> Bool { guard let value = try readNull(schema: schema) else { + if schema.isRequired { return false } throw ReaderError.requiredValueNotPresent } return value diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt index 7ed11eb9f..a734885b1 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt @@ -131,7 +131,7 @@ class StructureGenerator( private fun generateStructMembers() { membersSortedByName.forEach { - var (memberName, memberSymbol) = memberShapeDataContainer.getOrElse(it) { return@forEach } + val (memberName, memberSymbol) = memberShapeDataContainer.getOrElse(it) { return@forEach } writer.writeMemberDocs(model, it) val indirect = it.hasTrait() var indirectOrNot = "" @@ -140,7 +140,7 @@ class StructureGenerator( indirectOrNot = "@Indirect " } writer.writeAvailableAttribute(model, it) - writer.write("\$Lpublic var \$L: \$T", indirectOrNot, memberName, memberSymbol) + writer.write("\$Lpublic var \$L: \$T = \$D", indirectOrNot, memberName, memberSymbol, memberSymbol) } } @@ -164,21 +164,8 @@ class StructureGenerator( } } writer.write("") - if (error) { - writer.write("public init() {}") - } else { - writer.openBlock("public init() {", "}") { - membersSortedByName.forEach { member -> - val (memberName, memberSymbol) = memberShapeDataContainer.getOrElse(member) { Pair(null, null) } - if (memberName != null && memberSymbol != null) { - writer.write("self.\$L = \$D", memberName, memberSymbol) - } - } - } - } - } else { - writer.write("public init() { }") } + writer.write("public init() {}") } /** diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingErrorGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingErrorGenerator.kt index 8bccc6caf..0bf424690 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingErrorGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingErrorGenerator.kt @@ -13,6 +13,8 @@ import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.swift.codegen.SwiftDependency import software.amazon.smithy.swift.codegen.integration.HTTPProtocolCustomizable import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.AWSProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.awsProtocol import software.amazon.smithy.swift.codegen.integration.serde.struct.readerSymbol import software.amazon.smithy.swift.codegen.model.getTrait import software.amazon.smithy.swift.codegen.model.hasTrait @@ -90,6 +92,9 @@ class HTTPResponseBindingErrorGenerator( writer.addImport(SwiftSymbol.make("ClientRuntime", null, SwiftDependency.CLIENT_RUNTIME, emptyList(), listOf("SmithyReadWrite"))) writer.write("let data = try await httpResponse.data()") writer.write("let responseReader = try \$N.from(data: data)", ctx.service.readerSymbol) + if (ctx.service.awsProtocol == AWSProtocol.REST_JSON_1) { + writer.write("responseReader.respectsJSONName = true") + } val noErrorWrapping = ctx.service.getTrait()?.isNoErrorWrapping ?: false if (ctx.service.hasTrait()) { writer.write("let errorDetails = httpResponse.headers.value(for: \"x-amzn-query-error\")") diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt index 47c18273c..65ac4881a 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt @@ -8,6 +8,7 @@ import software.amazon.smithy.model.shapes.ShapeType import software.amazon.smithy.model.traits.DefaultTrait import software.amazon.smithy.model.traits.EnumValueTrait import software.amazon.smithy.model.traits.ErrorTrait +import software.amazon.smithy.model.traits.HttpPayloadTrait import software.amazon.smithy.model.traits.JsonNameTrait import software.amazon.smithy.model.traits.RequiredTrait import software.amazon.smithy.model.traits.SparseTrait @@ -15,6 +16,7 @@ import software.amazon.smithy.model.traits.StreamingTrait import software.amazon.smithy.model.traits.TimestampFormatTrait import software.amazon.smithy.swift.codegen.SwiftWriter import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator +import software.amazon.smithy.swift.codegen.integration.isInHttpBody import software.amazon.smithy.swift.codegen.model.getTrait import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes @@ -50,6 +52,7 @@ class SchemaGenerator( if (shape.members().isNotEmpty()) { writer.openBlock("members: [", "],") { shape.members() + .filter { it.isInHttpBody() } .filter { !ctx.model.expectShape(it.target).hasTrait() } .forEach { member -> writer.openBlock(".init(member:", "),") { @@ -73,8 +76,9 @@ class SchemaGenerator( } targetShape(shape)?.let { writer.write("targetSchema: { \$L },", it.schemaVar) } shape.id.member.getOrNull()?.let { writer.write("memberName: \$S,", it) } - memberShape(shape)?.let { writer.write("containerType: \$L,", ctx.model.expectShape(it.container).type) } + memberShape(shape)?.let { writer.write("containerType: .\$L,", ctx.model.expectShape(it.container).type) } jsonName(shape)?.let { writer.write("jsonName: \$S,", it) } + if (shape.hasTrait()) { writer.write("httpPayload: true,") } enumValue(shape)?.let { node -> when (node.type) { NodeType.STRING -> writer.write("enumValue: \$N(value: \$S)", SmithyTypes.StringDocument, node) @@ -86,6 +90,9 @@ class SchemaGenerator( writer.addImport(SmithyTimestampsTypes.TimestampFormat) writer.write("timestampFormat: .\$L,", it.swiftEnumCase) } + if (shape.hasTrait()) { + writer.write("isSparse: true,") + } if (isRequired(shape)) { writer.write("isRequired: true,") } @@ -128,7 +135,7 @@ class SchemaGenerator( writer.write("readBlock: { try \$\$0.append(\$\$1.\$L\$L(schema: \$L)) },", readMethodName, readMethodExtension, - target.schemaVar, + member.schemaVar, ) } ShapeType.MAP -> { @@ -137,7 +144,7 @@ class SchemaGenerator( "readBlock: { try \$\$0[\"value\"] = \$\$1.\$L\$L(schema: \$L) },", readMethodName, readMethodExtension, - target.schemaVar, + member.schemaVar, ) } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyJSONTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyJSONTypes.kt index 767b91651..c94ed2a28 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyJSONTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyJSONTypes.kt @@ -22,5 +22,5 @@ private fun runtimeSymbol( declaration, SwiftDependency.SMITHY_JSON, additionalImports, - listOf("SmithyReadWrite", "SchemaBasedSerde"), + listOf("SmithyReadWrite"), ) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt index d26fafd11..75b8ab52b 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt @@ -26,9 +26,9 @@ object SmithyReadWriteTypes { val WritingClosures = runtimeSymbol("WritingClosures", SwiftDeclaration.ENUM) val ReadingClosureBox = runtimeSymbol("ReadingClosureBox", SwiftDeclaration.STRUCT) val WritingClosureBox = runtimeSymbol("WritingClosureBox", SwiftDeclaration.STRUCT) - val Schema = runtimeSymbol("Schema", SwiftDeclaration.CLASS, listOf("SchemaBasedSerde")) + val Schema = runtimeSymbol("Schema", SwiftDeclaration.STRUCT) val DeserializableShape = runtimeSymbol("DeserializableShape", SwiftDeclaration.PROTOCOL) - val ShapeDeserializer = runtimeSymbol("ShapeDeserializer", SwiftDeclaration.PROTOCOL, listOf("SchemaBasedSerde")) + val ShapeDeserializer = runtimeSymbol("ShapeDeserializer", SwiftDeclaration.PROTOCOL) } private fun runtimeSymbol( From 2e9ef4e0c26b9f433ba4d7f0faa50b736b61e05e Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Sun, 5 Jan 2025 16:16:08 -0600 Subject: [PATCH 08/16] Remove Unit associated type from union members, provide prelude schemas --- .../Reader/Reader+ShapeDeserializer.swift | 23 ++-- Sources/SmithyReadWrite/Schema/Prelude.swift | 105 ++++++++++++++++++ Sources/SmithyReadWrite/Schema/Schema.swift | 26 +++-- Sources/SmithyReadWrite/Schema/Unit.swift | 14 +++ .../smithy/swift/codegen/EnumGenerator.kt | 1 - .../smithy/swift/codegen/IntEnumGenerator.kt | 1 - .../swift/codegen/ShapeValueGenerator.kt | 11 +- .../swift/codegen/SwiftSymbolProvider.kt | 14 ++- .../smithy/swift/codegen/UnionGenerator.kt | 6 +- .../events/MessageUnmarshallableGenerator.kt | 3 +- .../HTTPBindingProtocolGenerator.kt | 78 +++++++------ .../member/MemberShapeDecodeGenerator.kt | 13 ++- .../member/MemberShapeEncodeGenerator.kt | 15 +++ .../serde/schema/SchemaGenerator.kt | 75 +++++++------ .../serde/schema/SchemaShapeUtils.kt | 42 +++++-- .../serde/union/UnionEncodeGenerator.kt | 15 ++- .../model/EquatableConformanceTransformer.kt | 2 +- .../model/NeedsReaderWriterTransformer.kt | 4 +- .../codegen/model/NestedShapeTransformer.kt | 2 +- .../TestEquatableConformanceTransformer.kt | 2 +- .../SmithyEnumUnitIntegration.kt | 52 --------- .../TestEquatableConformanceIntegration.kt | 13 ++- .../swiftmodules/SmithyReadWriteTypes.kt | 20 ++++ ...swift.codegen.integration.SwiftIntegration | 3 +- 24 files changed, 359 insertions(+), 181 deletions(-) create mode 100644 Sources/SmithyReadWrite/Schema/Prelude.swift create mode 100644 Sources/SmithyReadWrite/Schema/Unit.swift delete mode 100644 smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftintegrations/SmithyEnumUnitIntegration.kt diff --git a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift index fc6708c9e..ff2f2e1df 100644 --- a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift +++ b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift @@ -30,7 +30,7 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { var value = Target() let structureSchema = resolvedTargetSchema(schema: schema) try structureSchema.members.forEach { memberContainer in - try memberContainer.performRead(base: &value, reader: resolvedReader) + try memberContainer.performRead(base: &value, key: "", reader: resolvedReader) } return value } @@ -41,13 +41,13 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { return resolvedDefault(schema: schema) != nil ? [] : nil } let listSchema = resolvedTargetSchema(schema: schema) - guard let memberContainer = listSchema.members.first(where: { $0.member.memberSchema.memberName == "member" }) else { + guard let memberContainer = listSchema.members.first(where: { $0.member.memberSchema().memberName == "member" }) else { throw ReaderError.requiredValueNotPresent } var value = [T]() for child in resolvedReader.children { child.respectsJSONName = respectsJSONName - try memberContainer.performRead(base: &value, reader: child) + try memberContainer.performRead(base: &value, key: "", reader: child) } return value } @@ -58,16 +58,14 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { return resolvedDefault(schema: schema) != nil ? [:] : nil } let mapSchema = resolvedTargetSchema(schema: schema) - guard let valueContainer = mapSchema.members.first(where: { $0.member.memberSchema.memberName == "value" }) else { + guard let valueContainer = mapSchema.members.first(where: { $0.member.memberSchema().memberName == "value" }) else { throw ReaderError.requiredValueNotPresent } var value = [String: T]() for child in resolvedReader.children { child.respectsJSONName = respectsJSONName if !mapSchema.isSparse && child.jsonNode == .null { continue } - var temp = [String: T]() - try valueContainer.performRead(base: &temp, reader: child) - value[child.nodeInfo.name] = temp["value"] + try valueContainer.performRead(base: &value, key: child.nodeInfo.name, reader: child) } return value } @@ -77,16 +75,17 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { guard resolvedReader.hasContent, case .string = resolvedReader.jsonNode else { return try resolvedDefault(schema: schema).map { T(rawValue: try $0.asString())! } } + let enumSchema = resolvedTargetSchema(schema: schema) guard let rawValue: String = try resolvedReader.readIfPresent() else { return nil } - for memberContainer in resolvedTargetSchema(schema: schema).members { - guard let resolvedEnumValue = try memberContainer.member.memberSchema.enumValue?.asString() ?? memberContainer.member.memberSchema.memberName else { + for memberContainer in enumSchema.members { + guard let resolvedEnumValue = try memberContainer.member.memberSchema().enumValue?.asString() ?? memberContainer.member.memberSchema().memberName else { throw ReaderError.requiredValueNotPresent } if rawValue == resolvedEnumValue { return T(rawValue: rawValue) } } - return T(rawValue: "") + return T(rawValue: rawValue) } public func readIntEnum(schema: Schema) throws -> T? where T.RawValue == Int { @@ -96,14 +95,14 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { } guard let rawValue: Int = try resolvedReader.readIfPresent() else { return nil } for memberContainer in resolvedTargetSchema(schema: schema).members { - guard let resolvedEnumValue = try memberContainer.member.memberSchema.enumValue?.asInteger() else { + guard let resolvedEnumValue = try memberContainer.member.memberSchema().enumValue?.asInteger() else { throw ReaderError.requiredValueNotPresent } if rawValue == resolvedEnumValue { return T(rawValue: rawValue) } } - return T(rawValue: Int.min) + return T(rawValue: rawValue) } public func readString(schema: Schema) throws -> String? { diff --git a/Sources/SmithyReadWrite/Schema/Prelude.swift b/Sources/SmithyReadWrite/Schema/Prelude.swift new file mode 100644 index 000000000..a47b9c5b1 --- /dev/null +++ b/Sources/SmithyReadWrite/Schema/Prelude.swift @@ -0,0 +1,105 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Foundation.Data +import struct Foundation.Date +@_spi(SmithyDocumentImpl) import Smithy + +@_spi(SmithyReadWrite) +public var unitSchema: Schema { + Schema(id: "smithy.api#Unit", type: .structure) +} + +@_spi(SmithyReadWrite) +public var booleanSchema: Schema { + Schema(id: "smithy.api#Boolean", type: .boolean) +} + +@_spi(SmithyReadWrite) +public var stringSchema: Schema { + Schema(id: "smithy.api#String", type: .string) +} + +@_spi(SmithyReadWrite) +public var integerSchema: Schema { + Schema(id: "smithy.api#Integer", type: .integer) +} + +@_spi(SmithyReadWrite) +public var blobSchema: Schema { + Schema(id: "smithy.api#Blob", type: .blob) +} + +@_spi(SmithyReadWrite) +public var timestampSchema: Schema { + Schema(id: "smithy.api#Timestamp", type: .timestamp) +} + +@_spi(SmithyReadWrite) +public var byteSchema: Schema { + Schema(id: "smithy.api#Byte", type: .byte) +} + +@_spi(SmithyReadWrite) +public var shortSchema: Schema { + Schema(id: "smithy.api#Short", type: .short) +} + +@_spi(SmithyReadWrite) +public var longSchema: Schema { + Schema(id: "smithy.api#Long", type: .long) +} + +@_spi(SmithyReadWrite) +public var floatSchema: Schema { + Schema(id: "smithy.api#Float", type: .float) +} + +@_spi(SmithyReadWrite) +public var doubleSchema: Schema { + Schema(id: "smithy.api#Double", type: .double) +} + +@_spi(SmithyReadWrite) +public var documentSchema: Schema { + Schema(id: "smithy.api#PrimitiveDocument", type: .document) +} + +@_spi(SmithyReadWrite) +public var primitiveBooleanSchema: Schema { + Schema(id: "smithy.api#PrimitiveBoolean", type: .boolean, defaultValue: BooleanDocument(value: false)) +} + +@_spi(SmithyReadWrite) +public var primitiveIntegerSchema: Schema { + Schema(id: "smithy.api#PrimitiveInteger", type: .integer, defaultValue: IntegerDocument(value: 0)) +} + +@_spi(SmithyReadWrite) +public var primitiveByteSchema: Schema { + Schema(id: "smithy.api#PrimitiveByte", type: .byte, defaultValue: ByteDocument(value: 0)) +} + +@_spi(SmithyReadWrite) +public var primitiveShortSchema: Schema { + Schema(id: "smithy.api#PrimitiveShort", type: .short, defaultValue: ShortDocument(value: 0)) +} + +@_spi(SmithyReadWrite) +public var primitiveLongSchema: Schema { + Schema(id: "smithy.api#PrimitiveLong", type: .long, defaultValue: LongDocument(value: 0)) +} + +@_spi(SmithyReadWrite) +public var primitiveFloatSchema: Schema { + Schema(id: "smithy.api#PrimitiveFloat", type: .float, defaultValue: FloatDocument(value: 0.0)) +} + +@_spi(SmithyReadWrite) +public var primitiveDoubleSchema: Schema { + Schema(id: "smithy.api#PrimitiveDouble", type: .double, defaultValue: DoubleDocument(value: 0.0)) +} diff --git a/Sources/SmithyReadWrite/Schema/Schema.swift b/Sources/SmithyReadWrite/Schema/Schema.swift index 2acfce5a6..1d8bb3cba 100644 --- a/Sources/SmithyReadWrite/Schema/Schema.swift +++ b/Sources/SmithyReadWrite/Schema/Schema.swift @@ -12,6 +12,7 @@ import struct Foundation.Date @_spi(SmithyReadWrite) public protocol SchemaProtocol { + var id: String { get } var type: ShapeType { get } var defaultValue: (any SmithyDocument)? { get } var jsonName: String? { get } @@ -52,8 +53,8 @@ public extension SchemaProtocol { @_spi(SmithyReadWrite) public protocol MemberProtocol { associatedtype Base - var memberSchema: SchemaProtocol { get } - func performRead(base: inout Base, reader: any ShapeDeserializer) throws + var memberSchema: () -> (any SchemaProtocol) { get } + func performRead(base: inout Base, key: String, reader: any ShapeDeserializer) throws func performWrite(base: Base, writer: any SmithyWriter) throws } @@ -65,8 +66,8 @@ public struct MemberContainer { self.member = member } - public func performRead(base: inout Base, reader: any ShapeDeserializer) throws { - try member.performRead(base: &base, reader: reader) + public func performRead(base: inout Base, key: String, reader: any ShapeDeserializer) throws { + try member.performRead(base: &base, key: key, reader: reader) } public func performWrite(base: Base, writer: any SmithyWriter) throws { @@ -78,14 +79,14 @@ public struct MemberContainer { public struct Schema: SchemaProtocol { public struct Member: MemberProtocol { - public var memberSchema: any SchemaProtocol { memberSchemaSpecific } - public let memberSchemaSpecific: Schema - let readBlock: (inout Base, any ShapeDeserializer) throws -> Void + public var memberSchema: () -> (any SchemaProtocol) { { memberSchemaSpecific() } } + public let memberSchemaSpecific: () -> Schema + let readBlock: (inout Base, String, any ShapeDeserializer) throws -> Void let writeBlock: (Base, any SmithyWriter) throws -> Void public init( - memberSchema: Schema, - readBlock: @escaping (inout Base, any ShapeDeserializer) throws -> Void = { _, _ in }, + memberSchema: @escaping () -> Schema, + readBlock: @escaping (inout Base, String, any ShapeDeserializer) throws -> Void = { _, _, _ in }, writeBlock: @escaping (Base, any SmithyWriter) throws -> Void = { _, _ in } ) { self.memberSchemaSpecific = memberSchema @@ -93,8 +94,8 @@ public struct Schema: SchemaProtocol { self.writeBlock = writeBlock } - public func performRead(base: inout Base, reader: any ShapeDeserializer) throws { - try readBlock(&base, reader) + public func performRead(base: inout Base, key: String, reader: any ShapeDeserializer) throws { + try readBlock(&base, key, reader) } public func performWrite(base: Base, writer: any SmithyWriter) throws { @@ -102,6 +103,7 @@ public struct Schema: SchemaProtocol { } } + public let id: String public let type: ShapeType public let members: [MemberContainer] public let targetSchema: () -> Schema? @@ -116,6 +118,7 @@ public struct Schema: SchemaProtocol { public let defaultValue: (any Smithy.SmithyDocument)? public init( + id: String, type: ShapeType, members: [MemberContainer] = [], targetSchema: @escaping () -> Schema? = { nil }, @@ -129,6 +132,7 @@ public struct Schema: SchemaProtocol { isRequired: Bool = false, defaultValue: (any SmithyDocument)? = nil ) { + self.id = id self.type = type self.members = members self.targetSchema = targetSchema diff --git a/Sources/SmithyReadWrite/Schema/Unit.swift b/Sources/SmithyReadWrite/Schema/Unit.swift new file mode 100644 index 000000000..6b537e7e3 --- /dev/null +++ b/Sources/SmithyReadWrite/Schema/Unit.swift @@ -0,0 +1,14 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +@_spi(SmithyDocumentImpl) import Smithy + +@_spi(SmithyReadWrite) +public struct Unit: Sendable, Equatable, DeserializableShape { + + public init() {} +} diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EnumGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EnumGenerator.kt index ee2b2dfb0..a3cb02a1c 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EnumGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EnumGenerator.kt @@ -15,7 +15,6 @@ import software.amazon.smithy.swift.codegen.customtraits.NestedTrait import software.amazon.smithy.swift.codegen.model.expectShape import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.model.nestedNamespaceType -import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SwiftTypes /** diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/IntEnumGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/IntEnumGenerator.kt index 94a885dbd..4896afb1b 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/IntEnumGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/IntEnumGenerator.kt @@ -10,7 +10,6 @@ import software.amazon.smithy.swift.codegen.customtraits.NestedTrait import software.amazon.smithy.swift.codegen.model.expectShape import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.model.nestedNamespaceType -import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SwiftTypes class IntEnumGenerator( diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/ShapeValueGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/ShapeValueGenerator.kt index e5b7ec7ed..fc4a72e64 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/ShapeValueGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/ShapeValueGenerator.kt @@ -87,7 +87,7 @@ class ShapeValueGenerator( private fun unionDecl(writer: SwiftWriter, shape: UnionShape, block: () -> Unit) { val symbol = symbolProvider.toSymbol(shape) - writer.writeInline("\$N.", symbol).call { block() }.write(")") + writer.writeInline("\$N.", symbol).call { block() } } private fun documentDecl(writer: SwiftWriter, node: Node) { @@ -231,8 +231,13 @@ class ShapeValueGenerator( CodegenException("unknown member ${currShape.id}.${keyNode.value}") } memberShape = generator.model.expectShape(member.target) - writer.writeInline("\$L(", lowerCase(keyNode.value)) - generator.writeShapeValueInline(writer, memberShape, valueNode) + if (member.target.toString() != "smithy.api#Unit") { + writer.writeInline("\$L(", lowerCase(keyNode.value)) + generator.writeShapeValueInline(writer, memberShape, valueNode) + writer.writeInline(")") + } else { + writer.writeInline("\$L", lowerCase(keyNode.value)) + } } else -> throw CodegenException("unexpected shape type " + currShape.type) } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt index 6daf94311..b2db06531 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt @@ -48,7 +48,6 @@ import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.model.traits.InputTrait import software.amazon.smithy.model.traits.SparseTrait import software.amazon.smithy.model.traits.StreamingTrait -import software.amazon.smithy.model.traits.UnitTypeTrait import software.amazon.smithy.swift.codegen.customtraits.NestedTrait import software.amazon.smithy.swift.codegen.lang.swiftReservedWords import software.amazon.smithy.swift.codegen.model.SymbolProperty @@ -175,9 +174,16 @@ class SwiftSymbolProvider(private val model: Model, val swiftSettings: SwiftSett if (shape.hasTrait() && service != null && !shape.hasTrait()) { builder.namespace(service.nestedNamespaceType(this).name, ".") } - if (shape.id.toString() == "smithy.api#Unit" && !shape.hasTrait() && service != null) { - builder.name("EnumUnit") - builder.namespace(service.nestedNamespaceType(this).name, ".") + if (shape.id.namespace == "smithy.api") { + when (shape.id.name) { + "Unit" -> { + builder.addDependency(SwiftDependency.SMITHY_READ_WRITE) + builder.namespace(SwiftDependency.SMITHY_READ_WRITE.target, ".") + builder.name("Unit") + builder.putProperty("spiNames", listOf("SmithyReadWrite")) + } + else -> throw Exception("Unhandled prelude type converted to Symbol") + } } return builder.build() } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/UnionGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/UnionGenerator.kt index 9af1d0ad9..6d67039f9 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/UnionGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/UnionGenerator.kt @@ -89,7 +89,11 @@ class UnionGenerator( writer.writeMemberDocs(model, member) val enumCaseName = symbolProvider.toMemberName(member) val enumCaseAssociatedType = symbolProvider.toSymbol(member) - writer.write("case \$L(\$L)", enumCaseName, enumCaseAssociatedType) + if (member.target.toString() != "smithy.api#Unit") { + writer.write("case \$L(\$N)", enumCaseName, enumCaseAssociatedType) + } else { + writer.write("case \$L", enumCaseName) + } } // add the sdkUnknown case which will always be last writer.write("case sdkUnknown(\$N)", SwiftTypes.String) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt index 3fbc30d6b..b694d1900 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt @@ -22,7 +22,6 @@ import software.amazon.smithy.swift.codegen.model.expectShape import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.swiftmodules.SmithyEventStreamsAPITypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes -import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTypes import software.amazon.smithy.swift.codegen.swiftmodules.SwiftTypes import software.amazon.smithy.swift.codegen.utils.ModelFileUtils @@ -222,7 +221,7 @@ class MessageUnmarshallableGenerator( "let value = try \$N.from(data: message.payload).\$LNonNull(schema: \$L)", ctx.service.readerSymbol, target.readMethodName, - target.schemaVar, + target.schemaVar(writer), ) } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt index 734a3f5da..77800bddb 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt @@ -18,13 +18,11 @@ import software.amazon.smithy.model.shapes.MemberShape import software.amazon.smithy.model.shapes.OperationShape import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.ShapeId -import software.amazon.smithy.model.shapes.ShapeType import software.amazon.smithy.model.shapes.StringShape import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.TimestampShape import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.model.traits.EnumTrait -import software.amazon.smithy.model.traits.EnumValueTrait import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.model.traits.HttpHeaderTrait import software.amazon.smithy.model.traits.HttpLabelTrait @@ -36,7 +34,6 @@ import software.amazon.smithy.model.traits.MediaTypeTrait import software.amazon.smithy.model.traits.RequiresLengthTrait import software.amazon.smithy.model.traits.StreamingTrait import software.amazon.smithy.model.traits.TimestampFormatTrait -import software.amazon.smithy.model.traits.UnitTypeTrait import software.amazon.smithy.swift.codegen.SwiftWriter import software.amazon.smithy.swift.codegen.customtraits.NeedsReaderTrait import software.amazon.smithy.swift.codegen.customtraits.NeedsWriterTrait @@ -73,7 +70,6 @@ import software.amazon.smithy.swift.codegen.model.ShapeMetadata import software.amazon.smithy.swift.codegen.model.findStreamingMember import software.amazon.smithy.swift.codegen.model.hasEventStreamMember import software.amazon.smithy.swift.codegen.model.hasTrait -import software.amazon.smithy.swift.codegen.model.isEnum import software.amazon.smithy.swift.codegen.model.isInputEventStream import software.amazon.smithy.swift.codegen.model.isOutputEventStream import software.amazon.smithy.swift.codegen.model.targetOrSelf @@ -220,8 +216,12 @@ abstract class HTTPBindingProtocolGenerator( if (ctx.service.responseWireProtocol != WireProtocol.JSON) { return } val nestedShapes = resolveShapesNeedingSchema(ctx) .filter { !it.hasTrait() } - .filter { !(it.asMemberShape().getOrNull()?.let { - ctx.model.expectShape(it.target).hasTrait() } ?: false) + .filter { + !( + it.asMemberShape().getOrNull()?.let { + ctx.model.expectShape(it.target).hasTrait() + } ?: false + ) } for (shape in nestedShapes) { renderSchemas(ctx, shape) @@ -358,6 +358,34 @@ abstract class HTTPBindingProtocolGenerator( return nestedTypes } + private fun walkNestedShapesRequiringSerde(ctx: ProtocolGenerator.GenerationContext, shapes: Set): Set { + val resolved = mutableSetOf() + val walker = Walker(ctx.model) + + // walk all the shapes in the set and find all other + // structs/unions (or collections thereof) in the graph from that shape + shapes.forEach { shape -> + walker.iterateShapes(shape) { relationship -> + when (relationship.relationshipType) { + RelationshipType.MEMBER_TARGET, + RelationshipType.STRUCTURE_MEMBER, + RelationshipType.LIST_MEMBER, + RelationshipType.SET_MEMBER, + RelationshipType.MAP_VALUE, + RelationshipType.UNION_MEMBER, + -> true + else -> false + } + }.forEach { + when (it) { + is UnionShape -> resolved.add(it) + is StructureShape -> resolved.add(it) + } + } + } + return resolved + } + private fun resolveShapesNeedingSchema(ctx: ProtocolGenerator.GenerationContext): Set { val topLevelOutputMembers = getHttpBindingOperations(ctx) .map { ctx.model.expectShape(it.output.get()) } @@ -372,6 +400,8 @@ abstract class HTTPBindingProtocolGenerator( .map { ctx.model.expectShape(it) } .toSet() + // Input members excluded from schema generation until schema-based deser is implemented + // val topLevelInputMembers = getHttpBindingOperations(ctx).flatMap { // val inputShape = ctx.model.expectShape(it.input.get()) // inputShape.members() @@ -388,34 +418,6 @@ abstract class HTTPBindingProtocolGenerator( return walkNestedShapesRequiringSchema(ctx, allTopLevelMembers) } - private fun walkNestedShapesRequiringSerde(ctx: ProtocolGenerator.GenerationContext, shapes: Set): Set { - val resolved = mutableSetOf() - val walker = Walker(ctx.model) - - // walk all the shapes in the set and find all other - // structs/unions (or collections thereof) in the graph from that shape - shapes.forEach { shape -> - walker.iterateShapes(shape) { relationship -> - when (relationship.relationshipType) { - RelationshipType.MEMBER_TARGET, - RelationshipType.STRUCTURE_MEMBER, - RelationshipType.LIST_MEMBER, - RelationshipType.SET_MEMBER, - RelationshipType.MAP_VALUE, - RelationshipType.UNION_MEMBER, - -> true - else -> false - } - }.forEach { - when (it) { - is UnionShape -> resolved.add(it) - is StructureShape -> resolved.add(it) - } - } - } - return resolved - } - private fun walkNestedShapesRequiringSchema(ctx: ProtocolGenerator.GenerationContext, shapes: Set): Set { val resolved = mutableSetOf() val walker = Walker(ctx.model) @@ -434,10 +436,14 @@ abstract class HTTPBindingProtocolGenerator( RelationshipType.UNION_MEMBER, RelationshipType.ENUM_MEMBER, RelationshipType.INT_ENUM_MEMBER, - -> true + -> true else -> false } - }.forEach { resolved.add(it) } + }.forEach { + // Don't generate schemas for Smithy built-in / "prelude" shapes. + // Those are included in runtime. + if (it.id.namespace != "smithy.api") { resolved.add(it) } + } } return resolved } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt index 76f79c419..8504df524 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt @@ -54,7 +54,6 @@ import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.model.isError import software.amazon.smithy.swift.codegen.swiftEnumCaseName import software.amazon.smithy.swift.codegen.swiftmodules.FoundationTypes -import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTimestampsTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTypes @@ -76,8 +75,10 @@ open class MemberShapeDecodeGenerator( else -> renderMemberExp(member, isPayload) } val memberName = ctx.symbolProvider.toMemberName(member) - if (decodingUnion) { + if (decodingUnion && member.target.toString() != "smithy.api#Unit") { writer.write("return .\$L(\$L)", memberName, readExp) + } else if (decodingUnion) { + writer.write("return .\$L", memberName) } else if (shapeContainingMembers.isError) { writer.write("value.properties.\$L = \$L", memberName, readExp) } else { @@ -92,7 +93,7 @@ open class MemberShapeDecodeGenerator( "try \$L.readStructure\$L(schema: \$L)", reader(memberShape, isPayload), "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", - memberShape.schemaVar, + memberShape.schemaVar(writer), ) } val readingClosure = readingClosureUtils.readingClosure(memberShape) @@ -111,7 +112,7 @@ open class MemberShapeDecodeGenerator( "try \$L.readList\$L(schema: \$L)", reader(memberShape, false), "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", - memberShape.schemaVar, + memberShape.schemaVar(writer), ) } val isSparse = listShape.hasTrait() @@ -136,7 +137,7 @@ open class MemberShapeDecodeGenerator( "try \$L.readMap\$L(schema: \$L)", reader(memberShape, false), "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", - memberShape.schemaVar, + memberShape.schemaVar(writer), ) } val isSparse = mapShape.hasTrait() @@ -180,7 +181,7 @@ open class MemberShapeDecodeGenerator( reader(memberShape, isPayload), target.readMethodName, "NonNull".takeIf { decodingUnion || (memberShape.hasTrait() || memberShape.hasTrait() || target.hasTrait()) } ?: "", - memberShape.schemaVar, + memberShape.schemaVar(writer), ) } return writer.format( diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeEncodeGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeEncodeGenerator.kt index ba53bd397..e7457e7ec 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeEncodeGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeEncodeGenerator.kt @@ -23,6 +23,7 @@ import software.amazon.smithy.swift.codegen.integration.serde.readwrite.awsProto import software.amazon.smithy.swift.codegen.integration.serde.readwrite.requestWireProtocol import software.amazon.smithy.swift.codegen.model.getTrait import software.amazon.smithy.swift.codegen.model.hasTrait +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTimestampsTypes abstract class MemberShapeEncodeGenerator( @@ -37,6 +38,10 @@ abstract class MemberShapeEncodeGenerator( private val nodeInfoUtils = NodeInfoUtils(ctx, writer, ctx.service.requestWireProtocol) fun writeMember(memberShape: MemberShape, unionMember: Boolean, errorMember: Boolean) { + if (unionMember && memberShape.target.toString() == "smithy.api#Unit") { + writeUnitMember(memberShape) + return + } val prefix1 = "value.".takeIf { !unionMember } ?: "" val prefix2 = "properties.".takeIf { errorMember } ?: "" val prefix = prefix1 + prefix2 @@ -61,6 +66,16 @@ abstract class MemberShapeEncodeGenerator( } } + private fun writeUnitMember(memberShape: MemberShape) { + val propertyKey = nodeInfoUtils.nodeInfo(memberShape) + writer.write( + "try writer[\$L].write(\$N(), with: \$N.write(value:to:))", + propertyKey, + SmithyReadWriteTypes.Unit, + SmithyReadWriteTypes.Unit, + ) + } + private fun writeStructureOrUnionMember(memberShape: MemberShape, prefix: String) { val memberName = ctx.symbolProvider.toMemberName(memberShape) val propertyKey = nodeInfoUtils.nodeInfo(memberShape) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt index 65ac4881a..19d6334ec 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt @@ -33,7 +33,7 @@ class SchemaGenerator( writer.openBlock( "var \$L: \$N<\$N> {", "}", - shape.schemaVar, + shape.schemaVar(writer), SmithyReadWriteTypes.Schema, ctx.symbolProvider.toSymbol(shape), ) { @@ -48,6 +48,7 @@ class SchemaGenerator( SmithyReadWriteTypes.Schema, ctx.symbolProvider.toSymbol(shape), ) { + writer.write("id: \$S,", shape.id.toString()) writer.write("type: .\$L,", shape.type) if (shape.members().isNotEmpty()) { writer.openBlock("members: [", "],") { @@ -55,26 +56,26 @@ class SchemaGenerator( .filter { it.isInHttpBody() } .filter { !ctx.model.expectShape(it.target).hasTrait() } .forEach { member -> - writer.openBlock(".init(member:", "),") { - writer.openBlock( - "\$N<\$N>.Member<\$N>(", - ")", - SmithyReadWriteTypes.Schema, - ctx.symbolProvider.toSymbol(shape), - ctx.symbolProvider.toSymbol(ctx.model.expectShape(member.target)), - ) { - writer.write("memberSchema: \$L,", member.schemaVar) - writeSetterGetter(writer, shape, member) - writer.unwrite(",\n") - writer.write("") + writer.openBlock(".init(member:", "),") { + writer.openBlock( + "\$N<\$N>.Member<\$N>(", + ")", + SmithyReadWriteTypes.Schema, + ctx.symbolProvider.toSymbol(shape), + ctx.symbolProvider.toSymbol(ctx.model.expectShape(member.target)), + ) { + writer.write("memberSchema: { \$L },", member.schemaVar(writer)) + writeSetterGetter(writer, shape, member) + writer.unwrite(",\n") + writer.write("") + } } } - } writer.unwrite(",\n") writer.write("") } } - targetShape(shape)?.let { writer.write("targetSchema: { \$L },", it.schemaVar) } + targetShape(shape)?.let { writer.write("targetSchema: { \$L },", it.schemaVar(writer)) } shape.id.member.getOrNull()?.let { writer.write("memberName: \$S,", it) } memberShape(shape)?.let { writer.write("containerType: .\$L,", ctx.model.expectShape(it.container).type) } jsonName(shape)?.let { writer.write("jsonName: \$S,", it) } @@ -105,46 +106,56 @@ class SchemaGenerator( private fun writeSetterGetter(writer: SwiftWriter, shape: Shape, member: MemberShape) { val target = ctx.model.expectShape(member.target) val readMethodName = target.readMethodName - val memberIsRequired = member.isRequired - || (member.hasTrait() || target.hasTrait()) - || (shape.isMapShape && member.memberName == "value" && !shape.hasTrait()) - || (shape.isListShape && !shape.hasTrait()) - || shape.isUnionShape + val memberIsRequired = member.isRequired || + (member.hasTrait() || target.hasTrait()) || + (shape.isMapShape && member.memberName == "value" && !shape.hasTrait()) || + (shape.isListShape && !shape.hasTrait()) || + shape.isUnionShape val readMethodExtension = "NonNull".takeIf { memberIsRequired } ?: "" when (shape.type) { ShapeType.STRUCTURE -> { val path = "properties.".takeIf { shape.hasTrait() } ?: "" writer.write( - "readBlock: { \$\$0.\$L\$L = try \$\$1.\$L\$L(schema: \$L) },", + "readBlock: { value, _, reader in value.\$L\$L = try reader.\$L\$L(schema: \$L) },", path, ctx.symbolProvider.toMemberName(member), readMethodName, readMethodExtension, - member.schemaVar, + member.schemaVar(writer), ) } ShapeType.UNION -> { - writer.write( - "readBlock: { value, reader in try reader.\$L(schema: \$L).map { unwrapped in value = .\$L(unwrapped) } },", - readMethodName, - member.schemaVar, - ctx.symbolProvider.toMemberName(member), - ) + if (member.target.toString() != "smithy.api#Unit") { + writer.write( + "readBlock: { value, _, reader in try reader.\$L(schema: \$L).map { value = .\$L(\$\$0) } },", + readMethodName, + member.schemaVar(writer), + ctx.symbolProvider.toMemberName(member), + ) + } else { + writer.write( + "readBlock: { value, _, reader in try reader.\$L(schema: \$L).map { _ in value = .\$L } },", + readMethodName, + member.schemaVar(writer), + ctx.symbolProvider.toMemberName(member), + ) + } } ShapeType.SET, ShapeType.LIST -> { - writer.write("readBlock: { try \$\$0.append(\$\$1.\$L\$L(schema: \$L)) },", + writer.write( + "readBlock: { value, _, reader in try value.append(reader.\$L\$L(schema: \$L)) },", readMethodName, readMethodExtension, - member.schemaVar, + member.schemaVar(writer), ) } ShapeType.MAP -> { if (member.memberName != "key") { writer.write( - "readBlock: { try \$\$0[\"value\"] = \$\$1.\$L\$L(schema: \$L) },", + "readBlock: { value, key, reader in try value[key] = reader.\$L\$L(schema: \$L) },", readMethodName, readMethodExtension, - member.schemaVar, + member.schemaVar(writer), ) } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaShapeUtils.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaShapeUtils.kt index 2bc308dc9..4bb6838bd 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaShapeUtils.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaShapeUtils.kt @@ -4,21 +4,49 @@ import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.model.shapes.ShapeType import software.amazon.smithy.model.traits.EnumTrait -import software.amazon.smithy.swift.codegen.customtraits.NestedTrait +import software.amazon.smithy.swift.codegen.SwiftWriter import software.amazon.smithy.swift.codegen.model.hasTrait +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import kotlin.jvm.optionals.getOrNull -val Shape.schemaVar: String - get() = if (this.id.toString() == "smithy.api#Unit" && !this.hasTrait()) { - ShapeId.from("smithy.api#EnumUnit").schemaVarName() +fun Shape.schemaVar(writer: SwiftWriter): String { + return if (this.id.namespace == "smithy.api") { + this.id.preludeSchemaVarName(writer) } else { this.id.schemaVarName() } +} + +private fun ShapeId.preludeSchemaVarName(writer: SwiftWriter): String { + return when (this.name) { + "Unit" -> writer.format("\$N", SmithyReadWriteTypes.unitSchema) + "String" -> writer.format("\$N", SmithyReadWriteTypes.stringSchema) + "Blob" -> writer.format("\$N", SmithyReadWriteTypes.blobSchema) + "Integer" -> writer.format("\$N", SmithyReadWriteTypes.integerSchema) + "Timestamp" -> writer.format("\$N", SmithyReadWriteTypes.timestampSchema) + "Boolean" -> writer.format("\$N", SmithyReadWriteTypes.booleanSchema) + "Float" -> writer.format("\$N", SmithyReadWriteTypes.floatSchema) + "Double" -> writer.format("\$N", SmithyReadWriteTypes.doubleSchema) + "Long" -> writer.format("\$N", SmithyReadWriteTypes.longSchema) + "Short" -> writer.format("\$N", SmithyReadWriteTypes.shortSchema) + "Byte" -> writer.format("\$N", SmithyReadWriteTypes.byteSchema) + "PrimitiveInteger" -> writer.format("\$N", SmithyReadWriteTypes.primitiveIntegerSchema) + "PrimitiveBoolean" -> writer.format("\$N", SmithyReadWriteTypes.primitiveBooleanSchema) + "PrimitiveFloat" -> writer.format("\$N", SmithyReadWriteTypes.primitiveFloatSchema) + "PrimitiveDouble" -> writer.format("\$N", SmithyReadWriteTypes.primitiveDoubleSchema) + "PrimitiveLong" -> writer.format("\$N", SmithyReadWriteTypes.primitiveLongSchema) + "PrimitiveShort" -> writer.format("\$N", SmithyReadWriteTypes.primitiveShortSchema) + "PrimitiveByte" -> writer.format("\$N", SmithyReadWriteTypes.primitiveByteSchema) + "Document" -> writer.format("\$N", SmithyReadWriteTypes.documentSchema) + else -> throw Exception("Unhandled prelude type converted to schemaVar: ${this.name}") + } +} private fun ShapeId.schemaVarName(): String { val namespacePortion = this.namespace.replace(".", "_") - val memberPortion = this.member.getOrNull()?.let { "_member_$it" } ?: "" - return "${namespacePortion}__${this.name}_schema${memberPortion}" + val namePortion = this.name + val memberPortion = this.member.getOrNull()?.let { "__member_$it" } ?: "" + return "schema__namespace_${namespacePortion}__name_${namePortion}$memberPortion" } val Shape.readMethodName: String @@ -42,5 +70,5 @@ val Shape.readMethodName: String ShapeType.MAP -> "readMap" ShapeType.STRUCTURE, ShapeType.UNION -> "readStructure" ShapeType.MEMBER, ShapeType.SERVICE, ShapeType.RESOURCE, ShapeType.OPERATION, null -> - throw Exception("Unsupported member target type: ${type}") + throw Exception("Unsupported member target type: $type") } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/union/UnionEncodeGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/union/UnionEncodeGenerator.kt index 186970952..470a5f0ce 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/union/UnionEncodeGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/union/UnionEncodeGenerator.kt @@ -30,10 +30,17 @@ class UnionEncodeGenerator( val membersSortedByName: List = members.sortedBy { it.memberName } membersSortedByName.forEach { member -> val memberName = ctx.symbolProvider.toMemberName(member) - writer.write("case let .\$L(\$L):", memberName, memberName) - writer.indent() - writeMember(member, true, false) - writer.dedent() + if (member.target.toString() != "smithy.api#Unit") { + writer.write("case let .\$L(\$L):", memberName, memberName) + writer.indent() + writeMember(member, true, false) + writer.dedent() + } else { + writer.write("case .\$L:", memberName) + writer.indent() + writeMember(member, true, false) + writer.dedent() + } } writer.write("case let .sdkUnknown(sdkUnknown):") writer.indent() diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/EquatableConformanceTransformer.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/EquatableConformanceTransformer.kt index 141db35e2..d37cfdaef 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/EquatableConformanceTransformer.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/EquatableConformanceTransformer.kt @@ -35,7 +35,7 @@ object EquatableConformanceTransformer { // Get all shapes nested within member shapes used as pagination tokens. val shapesToAddEquatableConformanceTraitTo = mutableSetOf() for (tokenMemberShape in paginationTokenMembers) { - val nestedShapes = model.getNestedShapes(tokenMemberShape) + val nestedShapes = model.getNestedShapes(tokenMemberShape).filter { it.id.namespace != "smithy.api" } shapesToAddEquatableConformanceTraitTo.addAll(nestedShapes) } // Add @equatableConformance custom trait to all structure and union shapes diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/NeedsReaderWriterTransformer.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/NeedsReaderWriterTransformer.kt index aa8f1d8f1..a123cdf80 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/NeedsReaderWriterTransformer.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/NeedsReaderWriterTransformer.kt @@ -32,7 +32,9 @@ object NeedsReaderWriterTransformer { private fun transform(model: Model, structure: StructureShape, trait: Trait): Model { // Get all shapes nested under the passed trait - var allShapesNeedingTrait = model.getNestedShapes(structure).filter { !it.hasTrait(trait.toShapeId()) } + var allShapesNeedingTrait = model.getNestedShapes(structure) + .filter { it.id.namespace != "smithy.api" } + .filter { !it.hasTrait(trait.toShapeId()) } if (allShapesNeedingTrait.isEmpty()) { return model } // If it's a struct or union, tag it with the trait. Else leave it unchanged. diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/NestedShapeTransformer.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/NestedShapeTransformer.kt index f79d8470a..1a7f1abd1 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/NestedShapeTransformer.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/NestedShapeTransformer.kt @@ -27,7 +27,7 @@ object NestedShapeTransformer { private fun transformInner(model: Model, service: ServiceShape): Model? { // find all the shapes in this models shapes that have are nested shapes (not a top level input or output) - val allShapesNeedingNested = model.getNestedShapes(service).filter { !it.hasTrait() } + val allShapesNeedingNested = model.getNestedShapes(service).filter { !it.hasTrait() && it.id.namespace != "smithy.api" } if (allShapesNeedingNested.isEmpty()) { return null diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/TestEquatableConformanceTransformer.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/TestEquatableConformanceTransformer.kt index 2c387ded0..63063c3b9 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/TestEquatableConformanceTransformer.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/TestEquatableConformanceTransformer.kt @@ -32,7 +32,7 @@ object TestEquatableConformanceTransformer { // Transform the model by adding the TestEquatableConformanceTrait to shapes that need it // In a later SwiftIntegration, the conformance will be code-generated return ModelTransformer.create().mapShapes(model) { shape -> - if (needsTestEquatableConformance.contains(shape.id)) { + if (needsTestEquatableConformance.contains(shape.id) && shape.id.namespace != "smithy.api") { // If the shape is a structure or union, add the TestEquatableConformanceTrait to it // All other shape types don't need to have Equatable generated for them when (shape.type) { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftintegrations/SmithyEnumUnitIntegration.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftintegrations/SmithyEnumUnitIntegration.kt deleted file mode 100644 index a0684c98c..000000000 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftintegrations/SmithyEnumUnitIntegration.kt +++ /dev/null @@ -1,52 +0,0 @@ -package software.amazon.smithy.swift.codegen.swiftintegrations - -import software.amazon.smithy.model.Model -import software.amazon.smithy.swift.codegen.SwiftDelegator -import software.amazon.smithy.swift.codegen.SwiftSettings -import software.amazon.smithy.swift.codegen.core.SwiftCodegenContext -import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator -import software.amazon.smithy.swift.codegen.integration.SwiftIntegration -import software.amazon.smithy.swift.codegen.model.nestedNamespaceType -import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes -import software.amazon.smithy.swift.codegen.swiftmodules.SwiftTypes -import software.amazon.smithy.swift.codegen.utils.ModelFileUtils - -class SmithyEnumUnitIntegration : SwiftIntegration { - override fun enabledForService(model: Model, settings: SwiftSettings): Boolean { - return true - } - - override fun writeAdditionalFiles( - ctx: SwiftCodegenContext, - protoCtx: ProtocolGenerator.GenerationContext, - delegator: SwiftDelegator - ) { - val service = ctx.settings.getService(ctx.model) - val namespaceName = service.nestedNamespaceType(ctx.symbolProvider).name - val structFilename = ModelFileUtils.filename(ctx.settings, "EnumUnit") - delegator.useFileWriter(structFilename) { writer -> - writer.openBlock("public extension \$L {", "}", namespaceName) { - writer.write("") - writer.openBlock( - "struct EnumUnit: \$N {", - "}", - SwiftTypes.Protocols.Sendable, - ) { - writer.write("public init() {}") - } - } - } - - val schemaFilename = ModelFileUtils.filename(ctx.settings, "EnumUnit+Schema") - delegator.useFileWriter(schemaFilename) { writer -> - writer.openBlock( - "let smithy_api__EnumUnit_schema = { \$N<\$L.EnumUnit>(", - ") }()", - SmithyReadWriteTypes.Schema, - namespaceName, - ) { - writer.write("type: .structure") - } - } - } -} diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftintegrations/TestEquatableConformanceIntegration.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftintegrations/TestEquatableConformanceIntegration.kt index 29f90adc2..b71fea88f 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftintegrations/TestEquatableConformanceIntegration.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftintegrations/TestEquatableConformanceIntegration.kt @@ -70,9 +70,16 @@ class TestEquatableConformanceIntegration : SwiftIntegration { writer.openBlock("switch (lhs, rhs) {", "}") { shape.members().forEach { member -> val enumCaseName = ctx.symbolProvider.toMemberName(member) - writer.write("case (.\$L(let lhs), .\$L(let rhs)):", enumCaseName, enumCaseName) - writer.indent { - writer.write("return lhs == rhs") + if (member.target.toString() != "smithy.api#Unit") { + writer.write("case (.\$L(let lhs), .\$L(let rhs)):", enumCaseName, enumCaseName) + writer.indent { + writer.write("return lhs == rhs") + } + } else { + writer.write("case (.\$L, .\$L):", enumCaseName, enumCaseName) + writer.indent { + writer.write("return true") + } } } writer.write("default: return false") diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt index 75b8ab52b..ac2790a21 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt @@ -29,6 +29,26 @@ object SmithyReadWriteTypes { val Schema = runtimeSymbol("Schema", SwiftDeclaration.STRUCT) val DeserializableShape = runtimeSymbol("DeserializableShape", SwiftDeclaration.PROTOCOL) val ShapeDeserializer = runtimeSymbol("ShapeDeserializer", SwiftDeclaration.PROTOCOL) + val Unit = runtimeSymbol("Unit", SwiftDeclaration.STRUCT) + val unitSchema = runtimeSymbol("unitSchema", SwiftDeclaration.VAR) + val stringSchema = runtimeSymbol("stringSchema", SwiftDeclaration.VAR) + val blobSchema = runtimeSymbol("blobSchema", SwiftDeclaration.VAR) + val integerSchema = runtimeSymbol("integerSchema", SwiftDeclaration.VAR) + val timestampSchema = runtimeSymbol("timestampSchema", SwiftDeclaration.VAR) + val booleanSchema = runtimeSymbol("booleanSchema", SwiftDeclaration.VAR) + val floatSchema = runtimeSymbol("floatSchema", SwiftDeclaration.VAR) + val doubleSchema = runtimeSymbol("doubleSchema", SwiftDeclaration.VAR) + val longSchema = runtimeSymbol("longSchema", SwiftDeclaration.VAR) + val shortSchema = runtimeSymbol("shortSchema", SwiftDeclaration.VAR) + val byteSchema = runtimeSymbol("byteSchema", SwiftDeclaration.VAR) + val primitiveBooleanSchema = runtimeSymbol("primitiveBooleanSchema", SwiftDeclaration.VAR) + val primitiveFloatSchema = runtimeSymbol("primitiveFloatSchema", SwiftDeclaration.VAR) + val primitiveDoubleSchema = runtimeSymbol("primitiveDoubleSchema", SwiftDeclaration.VAR) + val primitiveLongSchema = runtimeSymbol("primitiveLongSchema", SwiftDeclaration.VAR) + val primitiveIntegerSchema = runtimeSymbol("primitiveIntegerSchema", SwiftDeclaration.VAR) + val primitiveShortSchema = runtimeSymbol("primitiveShortSchema", SwiftDeclaration.VAR) + val primitiveByteSchema = runtimeSymbol("primitiveByteSchema", SwiftDeclaration.VAR) + val documentSchema = runtimeSymbol("documentSchema", SwiftDeclaration.VAR) } private fun runtimeSymbol( diff --git a/smithy-swift-codegen/src/main/resources/META-INF/services/software.amazon.smithy.swift.codegen.integration.SwiftIntegration b/smithy-swift-codegen/src/main/resources/META-INF/services/software.amazon.smithy.swift.codegen.integration.SwiftIntegration index e224b8aef..7b58c68d1 100644 --- a/smithy-swift-codegen/src/main/resources/META-INF/services/software.amazon.smithy.swift.codegen.integration.SwiftIntegration +++ b/smithy-swift-codegen/src/main/resources/META-INF/services/software.amazon.smithy.swift.codegen.integration.SwiftIntegration @@ -3,5 +3,4 @@ software.amazon.smithy.swift.codegen.waiters.WaiterIntegration software.amazon.smithy.swift.codegen.ServiceNamespaceIntegration software.amazon.smithy.swift.codegen.DefaultClientConfigurationIntegration software.amazon.smithy.swift.codegen.integration.ExtractTelemetryLoggerConfig -software.amazon.smithy.swift.codegen.swiftintegrations.TestEquatableConformanceIntegration -software.amazon.smithy.swift.codegen.swiftintegrations.SmithyEnumUnitIntegration \ No newline at end of file +software.amazon.smithy.swift.codegen.swiftintegrations.TestEquatableConformanceIntegration \ No newline at end of file From 1dbd2a2e0c519f96d9494db690149e06e4159271 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Sun, 5 Jan 2025 17:37:19 -0600 Subject: [PATCH 09/16] Replace DeserializableShape with factory block --- .../Reader/Reader+ShapeDeserializer.swift | 18 +++++++++--------- .../SmithyReadWrite/DeserializableShape.swift | 16 ---------------- Sources/SmithyReadWrite/Schema/Prelude.swift | 2 +- Sources/SmithyReadWrite/Schema/Schema.swift | 3 +++ Sources/SmithyReadWrite/Schema/Unit.swift | 6 +++++- .../SmithyReadWrite/ShapeDeserializer.swift | 9 ++++++--- .../smithy/swift/codegen/EnumGenerator.kt | 4 ---- .../smithy/swift/codegen/IntEnumGenerator.kt | 4 ---- .../smithy/swift/codegen/StructureGenerator.kt | 13 +++++-------- .../smithy/swift/codegen/UnionGenerator.kt | 5 +---- .../serde/schema/SchemaGenerator.kt | 10 ++++++++-- .../swiftmodules/SmithyReadWriteTypes.kt | 1 - 12 files changed, 38 insertions(+), 53 deletions(-) delete mode 100644 Sources/SmithyReadWrite/DeserializableShape.swift diff --git a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift index ff2f2e1df..4bcd002fa 100644 --- a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift +++ b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift @@ -13,22 +13,22 @@ import protocol Smithy.SmithyDocument import enum SmithyReadWrite.ReaderError @_spi(SmithyReadWrite) import protocol SmithyReadWrite.ShapeDeserializer @_spi(SmithyReadWrite) import protocol SmithyReadWrite.SchemaProtocol -@_spi(SmithyReadWrite) import protocol SmithyReadWrite.DeserializableShape @_spi(SmithyReadWrite) import struct SmithyReadWrite.Schema @_spi(SmithyTimestamps) import enum SmithyTimestamps.TimestampFormat @_spi(SmithyReadWrite) extension Reader: SmithyReadWrite.ShapeDeserializer { - public func readStructure( - schema: SmithyReadWrite.Schema - ) throws -> Target? { + public func readStructure(schema: SmithyReadWrite.Schema) throws -> Target? { let resolvedReader = try resolvedReader(schema: schema) + let structureSchema = resolvedTargetSchema(schema: schema) + guard let factory = structureSchema.factory else { + throw SmithyReadWrite.ReaderError.invalidSchema("Missing factory for structure or union: \(structureSchema.id)") + } guard resolvedReader.hasContent, resolvedReader.jsonNode == .object else { - return resolvedDefault(schema: schema) != nil ? Target() : nil + return resolvedDefault(schema: schema) != nil ? factory() : nil } - var value = Target() - let structureSchema = resolvedTargetSchema(schema: schema) + var value = factory() try structureSchema.members.forEach { memberContainer in try memberContainer.performRead(base: &value, key: "", reader: resolvedReader) } @@ -198,11 +198,11 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { public func readTimestamp(schema: SmithyReadWrite.Schema) throws -> Date? { let resolvedReader = try resolvedReader(schema: schema) guard resolvedReader.hasContent else { - guard let defaultValue = try resolvedDefault(schema: schema) else { return nil } + guard let defaultValue = resolvedDefault(schema: schema) else { return nil } switch defaultValue.type { case .float: let interval = try TimeInterval(defaultValue.asFloat()) - return try Date(timeIntervalSince1970: interval) + return Date(timeIntervalSince1970: interval) case .double: return try Date(timeIntervalSince1970: defaultValue.asDouble()) case .timestamp: diff --git a/Sources/SmithyReadWrite/DeserializableShape.swift b/Sources/SmithyReadWrite/DeserializableShape.swift deleted file mode 100644 index 2925b23d3..000000000 --- a/Sources/SmithyReadWrite/DeserializableShape.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// Copyright Amazon.com Inc. or its affiliates. -// All Rights Reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// - -public protocol DeserializableShape { - - init() -} - -public protocol DeserializableEnum { - - init() -} diff --git a/Sources/SmithyReadWrite/Schema/Prelude.swift b/Sources/SmithyReadWrite/Schema/Prelude.swift index a47b9c5b1..102d13c56 100644 --- a/Sources/SmithyReadWrite/Schema/Prelude.swift +++ b/Sources/SmithyReadWrite/Schema/Prelude.swift @@ -11,7 +11,7 @@ import struct Foundation.Date @_spi(SmithyReadWrite) public var unitSchema: Schema { - Schema(id: "smithy.api#Unit", type: .structure) + Schema(id: "smithy.api#Unit", type: .structure, factory: { Unit() }) } @_spi(SmithyReadWrite) diff --git a/Sources/SmithyReadWrite/Schema/Schema.swift b/Sources/SmithyReadWrite/Schema/Schema.swift index 1d8bb3cba..5746dc49f 100644 --- a/Sources/SmithyReadWrite/Schema/Schema.swift +++ b/Sources/SmithyReadWrite/Schema/Schema.swift @@ -105,6 +105,7 @@ public struct Schema: SchemaProtocol { public let id: String public let type: ShapeType + public let factory: (() -> Base)? public let members: [MemberContainer] public let targetSchema: () -> Schema? public let memberName: String? @@ -120,6 +121,7 @@ public struct Schema: SchemaProtocol { public init( id: String, type: ShapeType, + factory: (() -> Base)? = nil, members: [MemberContainer] = [], targetSchema: @escaping () -> Schema? = { nil }, memberName: String? = nil, @@ -134,6 +136,7 @@ public struct Schema: SchemaProtocol { ) { self.id = id self.type = type + self.factory = factory self.members = members self.targetSchema = targetSchema self.memberName = memberName diff --git a/Sources/SmithyReadWrite/Schema/Unit.swift b/Sources/SmithyReadWrite/Schema/Unit.swift index 6b537e7e3..bd0c8e887 100644 --- a/Sources/SmithyReadWrite/Schema/Unit.swift +++ b/Sources/SmithyReadWrite/Schema/Unit.swift @@ -8,7 +8,11 @@ @_spi(SmithyDocumentImpl) import Smithy @_spi(SmithyReadWrite) -public struct Unit: Sendable, Equatable, DeserializableShape { +public struct Unit: Sendable, Equatable { + + public static func write(value: Unit, to writer: Writer) throws { + try writer.write(Document(StringMapDocument(value: [:]))) + } public init() {} } diff --git a/Sources/SmithyReadWrite/ShapeDeserializer.swift b/Sources/SmithyReadWrite/ShapeDeserializer.swift index d67924cbb..1dd3a6b34 100644 --- a/Sources/SmithyReadWrite/ShapeDeserializer.swift +++ b/Sources/SmithyReadWrite/ShapeDeserializer.swift @@ -12,7 +12,7 @@ import struct Foundation.Date @_spi(SmithyReadWrite) public protocol ShapeDeserializer: SmithyReader { - func readStructure(schema: Schema) throws -> T? + func readStructure(schema: Schema) throws -> T? func readList(schema: Schema<[T]>) throws -> [T]? func readMap(schema: Schema<[String: T]>) throws -> [String: T]? func readBoolean(schema: Schema) throws -> Bool? @@ -54,9 +54,12 @@ public extension ShapeDeserializer { @_spi(SmithyReadWrite) public extension ShapeDeserializer { - func readStructureNonNull(schema: Schema) throws -> T { + func readStructureNonNull(schema: Schema) throws -> T { guard let value = try readStructure(schema: schema) else { - if schema.isRequired { return T() } + guard let factory = schema.factory else { + throw SmithyReadWrite.ReaderError.invalidSchema("Missing factory for structure or union: \(schema.id)") + } + if schema.isRequired { return factory() } throw ReaderError.requiredValueNotPresent } return value diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EnumGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EnumGenerator.kt index a3cb02a1c..a11b98268 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EnumGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/EnumGenerator.kt @@ -154,7 +154,6 @@ class EnumGenerator( "public enum \$enum.name:L: \$L {", "}", conformances, -// SmithyReadWriteTypes.DeserializableShape, ) { createEnumWriterContexts() // add the sdkUnknown case which will always be last @@ -171,9 +170,6 @@ class EnumGenerator( // Generate rawValue internal enum generateRawValueEnumBlock() - - writer.write("") - writer.write("public init() { self = .sdkUnknown(\"\") }") } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/IntEnumGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/IntEnumGenerator.kt index 4896afb1b..bf6584f54 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/IntEnumGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/IntEnumGenerator.kt @@ -53,7 +53,6 @@ class IntEnumGenerator( "public enum \$enum.name:L: \$L {", "}", conformances, -// SmithyReadWriteTypes.DeserializableShape, ) { createEnumWriterContexts() // add the sdkUnknown case which will always be last @@ -70,9 +69,6 @@ class IntEnumGenerator( // Generate rawValue internal enum generateRawValueEnumBlock() - - writer.write("") - writer.write("public init() { self = .sdkUnknown(0) }") } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt index a734885b1..a2322e1b1 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt @@ -26,7 +26,6 @@ import software.amazon.smithy.swift.codegen.model.nestedNamespaceType import software.amazon.smithy.swift.codegen.model.toLowerCamelCase import software.amazon.smithy.swift.codegen.swiftmodules.ClientRuntimeTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes -import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SwiftTypes import software.amazon.smithy.swift.codegen.utils.errorShapeName import software.amazon.smithy.swift.codegen.utils.toUpperCamelCase @@ -119,8 +118,7 @@ class StructureGenerator( writer.writeAvailableAttribute(model, shape) val equatableConformance = writer.format(", \$N", SwiftTypes.Protocols.Equatable).takeIf { shape.hasTrait() } ?: "" writer.openBlock( - "public struct \$struct.name:L: \$N, \$N$equatableConformance {", "}", - SmithyReadWriteTypes.DeserializableShape, + "public struct \$struct.name:L: \$N$equatableConformance {", "}", SwiftTypes.Protocols.Sendable, ) { generateStructMembers() @@ -140,7 +138,7 @@ class StructureGenerator( indirectOrNot = "@Indirect " } writer.writeAvailableAttribute(model, it) - writer.write("\$Lpublic var \$L: \$T = \$D", indirectOrNot, memberName, memberSymbol, memberSymbol) + writer.write("\$Lpublic var \$L: \$T", indirectOrNot, memberName, memberSymbol) } } @@ -163,9 +161,9 @@ class StructureGenerator( writer.write("self.$path\$L = \$L", memberName, memberName) } } - writer.write("") + } else { + writer.write("public init() { }") } - writer.write("public init() {}") } /** @@ -220,12 +218,11 @@ class StructureGenerator( writer.writeAvailableAttribute(model, shape) writer.openBlock( - "public struct \$struct.name:L: \$N, \$error.protocol:N, \$N, \$N, \$N, \$N {", + "public struct \$struct.name:L: \$N, \$error.protocol:N, \$N, \$N, \$N {", ClientRuntimeTypes.Core.ModeledError, ClientRuntimeTypes.Http.HttpError, SwiftTypes.Error, SwiftTypes.Protocols.Sendable, - SmithyReadWriteTypes.DeserializableShape, ) .call { generateErrorStructMembers() } .write("") diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/UnionGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/UnionGenerator.kt index 6d67039f9..10c38e2d4 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/UnionGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/UnionGenerator.kt @@ -18,7 +18,6 @@ import software.amazon.smithy.swift.codegen.model.eventStreamEvents import software.amazon.smithy.swift.codegen.model.expectShape import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.model.nestedNamespaceType -import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SwiftTypes /** @@ -79,7 +78,7 @@ class UnionGenerator( writer.writeAvailableAttribute(model, shape) val indirectOrNot = "indirect ".takeIf { shape.hasTrait() } ?: "" val equatableConformance = writer.format(", \$N", SwiftTypes.Protocols.Equatable).takeIf { shape.hasTrait() } ?: "" - writer.openBlock("public ${indirectOrNot}enum \$union.name:L: \$N, \$N$equatableConformance {", "}", SwiftTypes.Protocols.Sendable, SmithyReadWriteTypes.DeserializableShape) { + writer.openBlock("public ${indirectOrNot}enum \$union.name:L: \$N$equatableConformance {", "}", SwiftTypes.Protocols.Sendable) { // event streams (@streaming union) MAY have variants that target errors. // These errors if encountered on the stream will be thrown as an exception rather // than showing up as one of the possible events the consumer will see on the stream (AsyncThrowingStream). @@ -97,8 +96,6 @@ class UnionGenerator( } // add the sdkUnknown case which will always be last writer.write("case sdkUnknown(\$N)", SwiftTypes.String) - writer.write("") - writer.write("public init() { self = .sdkUnknown(\"\") }") } } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt index 19d6334ec..483097755 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt @@ -42,14 +42,20 @@ class SchemaGenerator( } private fun renderSchemaStruct(shape: Shape) { + val shapeSymbol = ctx.symbolProvider.toSymbol(shape) writer.openBlock( "\$N<\$N>(", ")", SmithyReadWriteTypes.Schema, - ctx.symbolProvider.toSymbol(shape), + shapeSymbol, ) { writer.write("id: \$S,", shape.id.toString()) writer.write("type: .\$L,", shape.type) + if (shape.type == ShapeType.STRUCTURE) { + writer.write("factory: { .init() },") + } else if (shape.type == ShapeType.UNION) { + writer.write("factory: { .sdkUnknown(\"\") },") + } if (shape.members().isNotEmpty()) { writer.openBlock("members: [", "],") { shape.members() @@ -61,7 +67,7 @@ class SchemaGenerator( "\$N<\$N>.Member<\$N>(", ")", SmithyReadWriteTypes.Schema, - ctx.symbolProvider.toSymbol(shape), + shapeSymbol, ctx.symbolProvider.toSymbol(ctx.model.expectShape(member.target)), ) { writer.write("memberSchema: { \$L },", member.schemaVar(writer)) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt index ac2790a21..bf012845f 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt @@ -27,7 +27,6 @@ object SmithyReadWriteTypes { val ReadingClosureBox = runtimeSymbol("ReadingClosureBox", SwiftDeclaration.STRUCT) val WritingClosureBox = runtimeSymbol("WritingClosureBox", SwiftDeclaration.STRUCT) val Schema = runtimeSymbol("Schema", SwiftDeclaration.STRUCT) - val DeserializableShape = runtimeSymbol("DeserializableShape", SwiftDeclaration.PROTOCOL) val ShapeDeserializer = runtimeSymbol("ShapeDeserializer", SwiftDeclaration.PROTOCOL) val Unit = runtimeSymbol("Unit", SwiftDeclaration.STRUCT) val unitSchema = runtimeSymbol("unitSchema", SwiftDeclaration.VAR) From 704fae4f59fe482847e0a9c7ea5bbf88c3579fd9 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Mon, 6 Jan 2025 00:36:03 -0600 Subject: [PATCH 10/16] Codegen cleanup --- .../events/MessageUnmarshallableGenerator.kt | 3 ++- .../integration/HTTPBindingProtocolGenerator.kt | 10 ++++------ .../swift/codegen/utils/SchemaFileUtils.kt | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/utils/SchemaFileUtils.kt diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt index b694d1900..3169f016a 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageUnmarshallableGenerator.kt @@ -99,7 +99,8 @@ class MessageUnmarshallableGenerator( writer.write("}") } writer.write("}") - writer.write("throw try makeError(message, params)") + writer.write("let error = try makeError(message, params)") + writer.write("throw error") } writer.write("case .error(let params):") writer.indent { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt index 77800bddb..86bd9b425 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt @@ -23,7 +23,6 @@ import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.model.shapes.TimestampShape import software.amazon.smithy.model.shapes.UnionShape import software.amazon.smithy.model.traits.EnumTrait -import software.amazon.smithy.model.traits.ErrorTrait import software.amazon.smithy.model.traits.HttpHeaderTrait import software.amazon.smithy.model.traits.HttpLabelTrait import software.amazon.smithy.model.traits.HttpPayloadTrait @@ -76,6 +75,7 @@ import software.amazon.smithy.swift.codegen.model.targetOrSelf import software.amazon.smithy.swift.codegen.supportsStreamingAndIsRPC import software.amazon.smithy.swift.codegen.swiftmodules.ClientRuntimeTypes import software.amazon.smithy.swift.codegen.utils.ModelFileUtils +import software.amazon.smithy.swift.codegen.utils.SchemaFileUtils import software.amazon.smithy.utils.OptionalUtils import java.util.Optional import java.util.logging.Logger @@ -231,7 +231,7 @@ abstract class HTTPBindingProtocolGenerator( private fun renderSchemas(ctx: ProtocolGenerator.GenerationContext, shape: Shape) { val symbol: Symbol = ctx.symbolProvider.toSymbol(shape) val symbolName = symbol.name - val filename = ModelFileUtils.filename(ctx.settings, "${shape.id.name}+Schema") + val filename = SchemaFileUtils.filename(ctx.settings, "${shape.id.name}+Schema") val encodeSymbol = Symbol.builder() .definitionFile(filename) .name(symbolName) @@ -247,7 +247,6 @@ abstract class HTTPBindingProtocolGenerator( ) { val shapeUsesSchemaBasedRead = ctx.service.responseWireProtocol == WireProtocol.JSON && shape.hasTrait() if (shapeUsesSchemaBasedRead && !shape.hasTrait()) { return } - if (ctx.service.responseWireProtocol == WireProtocol.JSON && !shape.hasTrait()) { return } val symbol: Symbol = ctx.symbolProvider.toSymbol(shape) val symbolName = symbol.name val filename = ModelFileUtils.filename(ctx.settings, "$symbolName+ReadWrite") @@ -262,7 +261,6 @@ abstract class HTTPBindingProtocolGenerator( is StructureShape -> { // get all members sorted by name and filter out either all members with other traits OR members with the payload trait val httpBodyMembers = members.filter { it.isInHttpBody() } - val path = "properties.".takeIf { shape.hasTrait() } ?: "" if (shape.hasTrait()) { writer.write("") renderStructEncode(ctx, shape, mapOf(), httpBodyMembers, writer) @@ -277,7 +275,7 @@ abstract class HTTPBindingProtocolGenerator( writer.write("") UnionEncodeGenerator(ctx, shape, members, writer).render() } - if (shape.hasTrait()) { + if (shape.hasTrait() && !shapeUsesSchemaBasedRead) { writer.write("") UnionDecodeGenerator(ctx, shape, members, writer).render() } @@ -400,7 +398,7 @@ abstract class HTTPBindingProtocolGenerator( .map { ctx.model.expectShape(it) } .toSet() - // Input members excluded from schema generation until schema-based deser is implemented + // Input members excluded from schema generation until schema-based deserialization is implemented // val topLevelInputMembers = getHttpBindingOperations(ctx).flatMap { // val inputShape = ctx.model.expectShape(it.input.get()) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/utils/SchemaFileUtils.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/utils/SchemaFileUtils.kt new file mode 100644 index 000000000..378ad5cc5 --- /dev/null +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/utils/SchemaFileUtils.kt @@ -0,0 +1,16 @@ +package software.amazon.smithy.swift.codegen.utils + +import software.amazon.smithy.swift.codegen.SwiftSettings + +class SchemaFileUtils { + + companion object { + fun filename(settings: SwiftSettings, filename: String): String { + return if (settings.mergeModels) { + "Sources/${settings.moduleName}/Schemas.swift" + } else { + "Sources/${settings.moduleName}/schemas/$filename.swift" + } + } + } +} From 13bf7f731bf6a07ddaaa57645c359766f0a0fbe6 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Mon, 6 Jan 2025 13:55:54 -0600 Subject: [PATCH 11/16] Fix codegen tests & lint --- Sources/Smithy/Document/ShapeType.swift | 3 +- .../Reader/Reader+ShapeDeserializer.swift | 18 +++++--- .../HTTPBindingProtocolGenerator.kt | 1 + .../swiftmodules/ClientRuntimeTypes.kt | 6 --- ...pProtocolUnitTestResponseGeneratorTests.kt | 1 - .../StructDecodeGenerationTests.kt | 27 ------------ .../StructEncodeGenerationTests.kt | 16 +++---- .../basicshapes/UnionDecodeGeneratorTests.kt | 29 ------------- .../basicshapes/UnionEncodeGeneratorTests.kt | 42 ------------------- .../OutputResponseDeserializerTests.kt | 4 +- .../requestandresponse/EventStreamTests.kt | 10 ++--- .../HTTPBindingProtocolGeneratorTests.kt | 3 +- .../HttpResponseBindingIgnoreQuery.kt | 3 +- 13 files changed, 34 insertions(+), 129 deletions(-) diff --git a/Sources/Smithy/Document/ShapeType.swift b/Sources/Smithy/Document/ShapeType.swift index f86f32a28..640b5c917 100644 --- a/Sources/Smithy/Document/ShapeType.swift +++ b/Sources/Smithy/Document/ShapeType.swift @@ -43,7 +43,8 @@ public enum ShapeType { public var category: Category { switch self { - case .blob, .boolean, .string, .timestamp, .byte, .short, .integer, .long, .float, .document, .double, .bigDecimal, .bigInteger, .enum, .intEnum: + case .blob, .boolean, .string, .timestamp, .byte, .short, .integer, .long, + .float, .document, .double, .bigDecimal, .bigInteger, .enum, .intEnum: return .simple case .list, .set, .map, .structure, .union: return .aggregate diff --git a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift index 4bcd002fa..c83c58526 100644 --- a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift +++ b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift @@ -23,7 +23,7 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { let resolvedReader = try resolvedReader(schema: schema) let structureSchema = resolvedTargetSchema(schema: schema) guard let factory = structureSchema.factory else { - throw SmithyReadWrite.ReaderError.invalidSchema("Missing factory for structure or union: \(structureSchema.id)") + throw ReaderError.invalidSchema("Missing factory for structure or union: \(structureSchema.id)") } guard resolvedReader.hasContent, resolvedReader.jsonNode == .object else { return resolvedDefault(schema: schema) != nil ? factory() : nil @@ -41,7 +41,9 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { return resolvedDefault(schema: schema) != nil ? [] : nil } let listSchema = resolvedTargetSchema(schema: schema) - guard let memberContainer = listSchema.members.first(where: { $0.member.memberSchema().memberName == "member" }) else { + guard let memberContainer = listSchema.members.first( + where: { $0.member.memberSchema().memberName == "member" } + ) else { throw ReaderError.requiredValueNotPresent } var value = [T]() @@ -52,13 +54,15 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { return value } - public func readMap(schema: Schema<[String: T]>) throws -> [String : T]? { + public func readMap(schema: Schema<[String: T]>) throws -> [String: T]? { let resolvedReader = try resolvedReader(schema: schema) guard resolvedReader.hasContent, resolvedReader.jsonNode == .object else { return resolvedDefault(schema: schema) != nil ? [:] : nil } let mapSchema = resolvedTargetSchema(schema: schema) - guard let valueContainer = mapSchema.members.first(where: { $0.member.memberSchema().memberName == "value" }) else { + guard let valueContainer = mapSchema.members.first( + where: { $0.member.memberSchema().memberName == "value" } + ) else { throw ReaderError.requiredValueNotPresent } var value = [String: T]() @@ -78,7 +82,9 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { let enumSchema = resolvedTargetSchema(schema: schema) guard let rawValue: String = try resolvedReader.readIfPresent() else { return nil } for memberContainer in enumSchema.members { - guard let resolvedEnumValue = try memberContainer.member.memberSchema().enumValue?.asString() ?? memberContainer.member.memberSchema().memberName else { + guard let resolvedEnumValue = + try memberContainer.member.memberSchema().enumValue?.asString() ?? + memberContainer.member.memberSchema().memberName else { throw ReaderError.requiredValueNotPresent } if rawValue == resolvedEnumValue { @@ -208,7 +214,7 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { case .timestamp: return try defaultValue.asTimestamp() default: - throw SmithyReadWrite.ReaderError.invalidSchema("Unsupported timestamp default type: \(defaultValue.type)") + throw ReaderError.invalidSchema("Unsupported timestamp default type: \(defaultValue.type)") } } let memberSchema = schema.type == .member ? schema : nil diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt index 86bd9b425..0bdce9353 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt @@ -245,6 +245,7 @@ abstract class HTTPBindingProtocolGenerator( ctx: ProtocolGenerator.GenerationContext, shape: Shape, ) { + if (!shape.hasTrait() && !shape.hasTrait()) { return } val shapeUsesSchemaBasedRead = ctx.service.responseWireProtocol == WireProtocol.JSON && shape.hasTrait() if (shapeUsesSchemaBasedRead && !shape.hasTrait()) { return } val symbol: Symbol = ctx.symbolProvider.toSymbol(shape) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt index 2be8e927b..d7ab434df 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/ClientRuntimeTypes.kt @@ -98,12 +98,6 @@ object ClientRuntimeTypes { val InterceptorProviders = runtimeSymbol("[ClientRuntime.InterceptorProvider]", null, listOf(InterceptorProvider)) val HttpInterceptorProviders = runtimeSymbol("[ClientRuntime.HttpInterceptorProvider]", null, listOf(HttpInterceptorProvider)) } - - object Model { - val StructureMember = runtimeSymbol("StructureMember", SwiftDeclaration.STRUCT) - val UnionMember = runtimeSymbol("UnionMember", SwiftDeclaration.STRUCT) - val EnumMember = runtimeSymbol("EnumMember", SwiftDeclaration.STRUCT) - } } private fun runtimeSymbol( diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/HttpProtocolUnitTestResponseGeneratorTests.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/HttpProtocolUnitTestResponseGeneratorTests.kt index 1706b32c0..8d7370ec6 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/HttpProtocolUnitTestResponseGeneratorTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/HttpProtocolUnitTestResponseGeneratorTests.kt @@ -167,7 +167,6 @@ open class HttpProtocolUnitTestResponseGeneratorTests { let expected = JsonUnionsOutput( contents: MyUnion.stringvalue("foo") - ) XCTAssertEqual(actual, expected) diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/StructDecodeGenerationTests.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/StructDecodeGenerationTests.kt index 39259ad75..77909d2b2 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/StructDecodeGenerationTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/StructDecodeGenerationTests.kt @@ -59,16 +59,6 @@ extension ExampleClientTypes.Nested4 { try writer["member1"].write(value.member1) try writer["stringMap"].writeMap(value.stringMap, valueWritingClosure: SmithyReadWrite.listWritingClosure(memberWritingClosure: SmithyReadWrite.WritingClosures.writeString(value:to:), memberNodeInfo: "member", isFlattened: false), keyNodeInfo: "key", valueNodeInfo: "value", isFlattened: false) } - - static func read(from reader: SmithyJSON.Reader) throws -> ExampleClientTypes.Nested4 { - guard reader.hasContent else { throw SmithyReadWrite.ReaderError.requiredValueNotPresent } - var value = ExampleClientTypes.Nested4() - value.member1 = try reader["member1"].readIfPresent() - value.intList = try reader["intList"].readListIfPresent(memberReadingClosure: SmithyReadWrite.ReadingClosures.readInt(from:), memberNodeInfo: "member", isFlattened: false) - value.intMap = try reader["intMap"].readMapIfPresent(valueReadingClosure: SmithyReadWrite.ReadingClosures.readInt(from:), keyNodeInfo: "key", valueNodeInfo: "value", isFlattened: false) - value.stringMap = try reader["stringMap"].readMapIfPresent(valueReadingClosure: SmithyReadWrite.listReadingClosure(memberReadingClosure: SmithyReadWrite.ReadingClosures.readString(from:), memberNodeInfo: "member", isFlattened: false), keyNodeInfo: "key", valueNodeInfo: "value", isFlattened: false) - return value - } } """ contents.shouldContainOnlyOnce(expectedContents) @@ -90,15 +80,6 @@ extension ExampleClientTypes.RecursiveShapesInputOutputNested1 { try writer["foo"].write(value.foo) try writer["nested"].write(value.nested, with: ExampleClientTypes.RecursiveShapesInputOutputNested2.write(value:to:)) } - - static func read(from reader: SmithyJSON.Reader) throws -> ExampleClientTypes.RecursiveShapesInputOutputNested1 { - guard reader.hasContent else { throw SmithyReadWrite.ReaderError.requiredValueNotPresent } - var value = ExampleClientTypes.RecursiveShapesInputOutputNested1() - value.foo = try reader["foo"].readIfPresent() - value.nested = try reader["nested"].readIfPresent(with: ExampleClientTypes.RecursiveShapesInputOutputNested2.read(from:)) - return value - } -} """ contents.shouldContainOnlyOnce(expectedContents) } @@ -119,14 +100,6 @@ extension ExampleClientTypes.RecursiveShapesInputOutputNested2 { try writer["bar"].write(value.bar) try writer["recursiveMember"].write(value.recursiveMember, with: ExampleClientTypes.RecursiveShapesInputOutputNested1.write(value:to:)) } - - static func read(from reader: SmithyJSON.Reader) throws -> ExampleClientTypes.RecursiveShapesInputOutputNested2 { - guard reader.hasContent else { throw SmithyReadWrite.ReaderError.requiredValueNotPresent } - var value = ExampleClientTypes.RecursiveShapesInputOutputNested2() - value.bar = try reader["bar"].readIfPresent() - value.recursiveMember = try reader["recursiveMember"].readIfPresent(with: ExampleClientTypes.RecursiveShapesInputOutputNested1.read(from:)) - return value - } } """ contents.shouldContainOnlyOnce(expectedContents) diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/StructEncodeGenerationTests.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/StructEncodeGenerationTests.kt index 165fbcfa1..76cc6a001 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/StructEncodeGenerationTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/StructEncodeGenerationTests.kt @@ -83,10 +83,10 @@ extension Nested4 { static func read(from reader: SmithyJSON.Reader) throws -> Nested4 { guard reader.hasContent else { throw SmithyReadWrite.ReaderError.requiredValueNotPresent } var value = Nested4() - value.member1 = try reader["member1"].readIfPresent() - value.intList = try reader["intList"].readListIfPresent(memberReadingClosure: SmithyReadWrite.ReadingClosures.readInt(from:), memberNodeInfo: "member", isFlattened: false) - value.intMap = try reader["intMap"].readMapIfPresent(valueReadingClosure: SmithyReadWrite.ReadingClosures.readInt(from:), keyNodeInfo: "key", valueNodeInfo: "value", isFlattened: false) - value.stringMap = try reader["stringMap"].readMapIfPresent(valueReadingClosure: SmithyReadWrite.listReadingClosure(memberReadingClosure: SmithyReadWrite.ReadingClosures.readString(from:), memberNodeInfo: "member", isFlattened: false), keyNodeInfo: "key", valueNodeInfo: "value", isFlattened: false) + value.member1 = try reader.readInteger(schema: schema__namespace_com_test__name_Nested4__member_member1) + value.intList = try reader.readList(schema: schema__namespace_com_test__name_Nested4__member_intList) + value.intMap = try reader.readMap(schema: schema__namespace_com_test__name_Nested4__member_intMap) + value.stringMap = try reader.readMap(schema: schema__namespace_com_test__name_Nested4__member_stringMap) return value } } @@ -184,8 +184,8 @@ extension RecursiveShapesInputOutputNested1 { static func read(from reader: SmithyJSON.Reader) throws -> RecursiveShapesInputOutputNested1 { guard reader.hasContent else { throw SmithyReadWrite.ReaderError.requiredValueNotPresent } var value = RecursiveShapesInputOutputNested1() - value.foo = try reader["foo"].readIfPresent() - value.nested = try reader["nested"].readIfPresent(with: RecursiveShapesInputOutputNested2.read(from:)) + value.foo = try reader.readString(schema: schema__namespace_com_test__name_RecursiveShapesInputOutputNested1__member_foo) + value.nested = try reader.readStructure(schema: schema__namespace_com_test__name_RecursiveShapesInputOutputNested1__member_nested) return value } } @@ -213,8 +213,8 @@ extension RecursiveShapesInputOutputNested2 { static func read(from reader: SmithyJSON.Reader) throws -> RecursiveShapesInputOutputNested2 { guard reader.hasContent else { throw SmithyReadWrite.ReaderError.requiredValueNotPresent } var value = RecursiveShapesInputOutputNested2() - value.bar = try reader["bar"].readIfPresent() - value.recursiveMember = try reader["recursiveMember"].readIfPresent(with: RecursiveShapesInputOutputNested1.read(from:)) + value.bar = try reader.readString(schema: schema__namespace_com_test__name_RecursiveShapesInputOutputNested2__member_bar) + value.recursiveMember = try reader.readStructure(schema: schema__namespace_com_test__name_RecursiveShapesInputOutputNested2__member_recursiveMember) return value } } diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/UnionDecodeGeneratorTests.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/UnionDecodeGeneratorTests.kt index eb21f6999..f4b93210d 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/UnionDecodeGeneratorTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/UnionDecodeGeneratorTests.kt @@ -74,35 +74,6 @@ extension ExampleClientTypes.MyUnion { try writer["sdkUnknown"].write(sdkUnknown) } } - - static func read(from reader: SmithyJSON.Reader) throws -> ExampleClientTypes.MyUnion { - guard reader.hasContent else { throw SmithyReadWrite.ReaderError.requiredValueNotPresent } - let name = reader.children.filter { ${'$'}0.hasContent && ${'$'}0.nodeInfo.name != "__type" }.first?.nodeInfo.name - switch name { - case "stringValue": - return .stringvalue(try reader["stringValue"].read()) - case "booleanValue": - return .booleanvalue(try reader["booleanValue"].read()) - case "numberValue": - return .numbervalue(try reader["numberValue"].read()) - case "blobValue": - return .blobvalue(try reader["blobValue"].read()) - case "timestampValue": - return .timestampvalue(try reader["timestampValue"].readTimestamp(format: SmithyTimestamps.TimestampFormat.epochSeconds)) - case "inheritedTimestamp": - return .inheritedtimestamp(try reader["inheritedTimestamp"].readTimestamp(format: SmithyTimestamps.TimestampFormat.httpDate)) - case "enumValue": - return .enumvalue(try reader["enumValue"].read()) - case "listValue": - return .listvalue(try reader["listValue"].readList(memberReadingClosure: SmithyReadWrite.ReadingClosures.readString(from:), memberNodeInfo: "member", isFlattened: false)) - case "mapValue": - return .mapvalue(try reader["mapValue"].readMap(valueReadingClosure: SmithyReadWrite.ReadingClosures.readString(from:), keyNodeInfo: "key", valueNodeInfo: "value", isFlattened: false)) - case "structureValue": - return .structurevalue(try reader["structureValue"].read(with: ExampleClientTypes.GreetingWithErrorsOutput.read(from:))) - default: - return .sdkUnknown(name ?? "") - } - } } """ contents.shouldContainOnlyOnce(expectedContents) diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/UnionEncodeGeneratorTests.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/UnionEncodeGeneratorTests.kt index f59b4ee31..0992b8afd 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/UnionEncodeGeneratorTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/basicshapes/UnionEncodeGeneratorTests.kt @@ -102,35 +102,6 @@ extension ExampleClientTypes.MyUnion { try writer["sdkUnknown"].write(sdkUnknown) } } - - static func read(from reader: SmithyJSON.Reader) throws -> ExampleClientTypes.MyUnion { - guard reader.hasContent else { throw SmithyReadWrite.ReaderError.requiredValueNotPresent } - let name = reader.children.filter { ${'$'}0.hasContent && ${'$'}0.nodeInfo.name != "__type" }.first?.nodeInfo.name - switch name { - case "stringValue": - return .stringvalue(try reader["stringValue"].read()) - case "booleanValue": - return .booleanvalue(try reader["booleanValue"].read()) - case "numberValue": - return .numbervalue(try reader["numberValue"].read()) - case "blobValue": - return .blobvalue(try reader["blobValue"].read()) - case "timestampValue": - return .timestampvalue(try reader["timestampValue"].readTimestamp(format: SmithyTimestamps.TimestampFormat.epochSeconds)) - case "inheritedTimestamp": - return .inheritedtimestamp(try reader["inheritedTimestamp"].readTimestamp(format: SmithyTimestamps.TimestampFormat.httpDate)) - case "enumValue": - return .enumvalue(try reader["enumValue"].read()) - case "listValue": - return .listvalue(try reader["listValue"].readList(memberReadingClosure: SmithyReadWrite.ReadingClosures.readString(from:), memberNodeInfo: "member", isFlattened: false)) - case "mapValue": - return .mapvalue(try reader["mapValue"].readMap(valueReadingClosure: SmithyReadWrite.ReadingClosures.readString(from:), keyNodeInfo: "key", valueNodeInfo: "value", isFlattened: false)) - case "structureValue": - return .structurevalue(try reader["structureValue"].read(with: ExampleClientTypes.GreetingWithErrorsOutput.read(from:))) - default: - return .sdkUnknown(name ?? "") - } - } } """ contents.shouldContainOnlyOnce(expectedContents) @@ -154,19 +125,6 @@ extension ExampleClientTypes.IndirectEnum { try writer["sdkUnknown"].write(sdkUnknown) } } - - static func read(from reader: SmithyJSON.Reader) throws -> ExampleClientTypes.IndirectEnum { - guard reader.hasContent else { throw SmithyReadWrite.ReaderError.requiredValueNotPresent } - let name = reader.children.filter { ${'$'}0.hasContent && ${'$'}0.nodeInfo.name != "__type" }.first?.nodeInfo.name - switch name { - case "some": - return .some(try reader["some"].read(with: ExampleClientTypes.IndirectEnum.read(from:))) - case "other": - return .other(try reader["other"].read()) - default: - return .sdkUnknown(name ?? "") - } - } } """ contents.shouldContainOnlyOnce(expectedContents) diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolspecificserde/awsjson11/OutputResponseDeserializerTests.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolspecificserde/awsjson11/OutputResponseDeserializerTests.kt index 7a446c4b1..ff8c6d1fc 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolspecificserde/awsjson11/OutputResponseDeserializerTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolspecificserde/awsjson11/OutputResponseDeserializerTests.kt @@ -50,8 +50,8 @@ extension SimpleStructureOutput { let responseReader = try SmithyJSON.Reader.from(data: data) let reader = responseReader var value = SimpleStructureOutput() - value.name = try reader["name"].readIfPresent() - value.number = try reader["number"].readIfPresent() + value.name = try reader.readString(schema: schema__namespace_smithy_swift_synthetic__name_SimpleStructureOutput__member_name) + value.number = try reader.readInteger(schema: schema__namespace_smithy_swift_synthetic__name_SimpleStructureOutput__member_number) return value } } diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/requestandresponse/EventStreamTests.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/requestandresponse/EventStreamTests.kt index 84b22a3f9..b04bf5108 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/requestandresponse/EventStreamTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/requestandresponse/EventStreamTests.kt @@ -123,12 +123,12 @@ extension EventStreamTestClientTypes.TestStream { return .messagewithstring(event) case "MessageWithStruct": var event = EventStreamTestClientTypes.MessageWithStruct() - let value = try SmithyJSON.Reader.readFrom(message.payload, with: EventStreamTestClientTypes.TestStruct.read(from:)) + let value = try SmithyJSON.Reader.from(data: message.payload).readStructureNonNull(schema: schema__namespace_aws_protocoltests_restjson__name_TestStruct) event.someStruct = value return .messagewithstruct(event) case "MessageWithUnion": var event = EventStreamTestClientTypes.MessageWithUnion() - let value = try SmithyJSON.Reader.readFrom(message.payload, with: EventStreamTestClientTypes.TestUnion.read(from:)) + let value = try SmithyJSON.Reader.from(data: message.payload).readStructureNonNull(schema: schema__namespace_aws_protocoltests_restjson__name_TestUnion) event.someUnion = value return .messagewithunion(event) case "MessageWithHeaders": @@ -166,14 +166,14 @@ extension EventStreamTestClientTypes.TestStream { event.payload = message.payload return .messagewithheaderandpayload(event) case "MessageWithNoHeaderPayloadTraits": - let value = try SmithyJSON.Reader.readFrom(message.payload, with: EventStreamTestClientTypes.MessageWithNoHeaderPayloadTraits.read(from:)) + let value = try SmithyJSON.Reader.from(data: message.payload).readStructureNonNull(schema: schema__namespace_aws_protocoltests_restjson__name_MessageWithNoHeaderPayloadTraits) return .messagewithnoheaderpayloadtraits(value) case "MessageWithUnboundPayloadTraits": var event = EventStreamTestClientTypes.MessageWithUnboundPayloadTraits() if case .string(let value) = message.headers.value(name: "header") { event.header = value } - let value = try SmithyJSON.Reader.readFrom(message.payload, with: SmithyReadWrite.ReadingClosures.readString(from:)) + let value = try SmithyJSON.Reader.from(data: message.payload).readStringNonNull(schema: SmithyReadWrite.stringSchema) event.unboundString = value return .messagewithunboundpayloadtraits(event) default: @@ -183,7 +183,7 @@ extension EventStreamTestClientTypes.TestStream { let makeError: (SmithyEventStreamsAPI.Message, SmithyEventStreamsAPI.MessageType.ExceptionParams) throws -> Swift.Error = { message, params in switch params.exceptionType { case "SomeError": - let value = try SmithyJSON.Reader.readFrom(message.payload, with: SomeError.read(from:)) + let value = try SmithyJSON.Reader.from(data: message.payload).readStructureNonNull(schema: schema__namespace_aws_protocoltests_restjson__name_SomeError) return value default: let httpResponse = SmithyHTTPAPI.HTTPResponse(body: .data(message.payload), statusCode: .ok) diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/requestandresponse/HTTPBindingProtocolGeneratorTests.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/requestandresponse/HTTPBindingProtocolGeneratorTests.kt index 954053e0b..b9a0e0cf4 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/requestandresponse/HTTPBindingProtocolGeneratorTests.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/requestandresponse/HTTPBindingProtocolGeneratorTests.kt @@ -73,8 +73,9 @@ extension ExplicitStructOutput { let data = try await httpResponse.data() let responseReader = try SmithyJSON.Reader.from(data: data) let reader = responseReader + reader.respectsJSONName = true var value = ExplicitStructOutput() - value.payload1 = try reader.readIfPresent(with: Nested2.read(from:)) + value.payload1 = try reader.readStructure(schema: schema__namespace_smithy_swift_synthetic__name_ExplicitStructOutput__member_payload1) return value } } diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/requestandresponse/responseflow/HttpResponseBindingIgnoreQuery.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/requestandresponse/responseflow/HttpResponseBindingIgnoreQuery.kt index a2044b8b0..3086e443f 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/requestandresponse/responseflow/HttpResponseBindingIgnoreQuery.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/requestandresponse/responseflow/HttpResponseBindingIgnoreQuery.kt @@ -27,8 +27,9 @@ extension IgnoreQueryParamsInResponseOutput { let data = try await httpResponse.data() let responseReader = try SmithyJSON.Reader.from(data: data) let reader = responseReader + reader.respectsJSONName = true var value = IgnoreQueryParamsInResponseOutput() - value.baz = try reader["baz"].readIfPresent() + value.baz = try reader.readString(schema: schema__namespace_smithy_swift_synthetic__name_IgnoreQueryParamsInResponseOutput__member_baz) return value } } From aabbad772f0e5489f4a92c3d807584e0360c567f Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Mon, 6 Jan 2025 15:36:30 -0600 Subject: [PATCH 12/16] Eliminate enum & int enum member schemas --- .../Reader/Reader+ShapeDeserializer.swift | 18 ------------------ .../HTTPBindingProtocolGenerator.kt | 3 +-- .../serde/schema/SchemaGenerator.kt | 3 ++- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift index c83c58526..c81958b1c 100644 --- a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift +++ b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift @@ -81,16 +81,6 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { } let enumSchema = resolvedTargetSchema(schema: schema) guard let rawValue: String = try resolvedReader.readIfPresent() else { return nil } - for memberContainer in enumSchema.members { - guard let resolvedEnumValue = - try memberContainer.member.memberSchema().enumValue?.asString() ?? - memberContainer.member.memberSchema().memberName else { - throw ReaderError.requiredValueNotPresent - } - if rawValue == resolvedEnumValue { - return T(rawValue: rawValue) - } - } return T(rawValue: rawValue) } @@ -100,14 +90,6 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { return try resolvedDefault(schema: schema).map { T(rawValue: try $0.asInteger())! } } guard let rawValue: Int = try resolvedReader.readIfPresent() else { return nil } - for memberContainer in resolvedTargetSchema(schema: schema).members { - guard let resolvedEnumValue = try memberContainer.member.memberSchema().enumValue?.asInteger() else { - throw ReaderError.requiredValueNotPresent - } - if rawValue == resolvedEnumValue { - return T(rawValue: rawValue) - } - } return T(rawValue: rawValue) } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt index 0bdce9353..5e14c126a 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt @@ -69,6 +69,7 @@ import software.amazon.smithy.swift.codegen.model.ShapeMetadata import software.amazon.smithy.swift.codegen.model.findStreamingMember import software.amazon.smithy.swift.codegen.model.hasEventStreamMember import software.amazon.smithy.swift.codegen.model.hasTrait +import software.amazon.smithy.swift.codegen.model.isEnum import software.amazon.smithy.swift.codegen.model.isInputEventStream import software.amazon.smithy.swift.codegen.model.isOutputEventStream import software.amazon.smithy.swift.codegen.model.targetOrSelf @@ -433,8 +434,6 @@ abstract class HTTPBindingProtocolGenerator( RelationshipType.MAP_KEY, RelationshipType.MAP_VALUE, RelationshipType.UNION_MEMBER, - RelationshipType.ENUM_MEMBER, - RelationshipType.INT_ENUM_MEMBER, -> true else -> false } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt index 483097755..043840ee9 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt @@ -19,6 +19,7 @@ import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator import software.amazon.smithy.swift.codegen.integration.isInHttpBody import software.amazon.smithy.swift.codegen.model.getTrait import software.amazon.smithy.swift.codegen.model.hasTrait +import software.amazon.smithy.swift.codegen.model.isEnum import software.amazon.smithy.swift.codegen.swiftmodules.SmithyReadWriteTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTimestampsTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTypes @@ -56,7 +57,7 @@ class SchemaGenerator( } else if (shape.type == ShapeType.UNION) { writer.write("factory: { .sdkUnknown(\"\") },") } - if (shape.members().isNotEmpty()) { + if (shape.members().isNotEmpty() && !shape.isEnum && !shape.isIntEnumShape) { writer.openBlock("members: [", "],") { shape.members() .filter { it.isInHttpBody() } From 086b99a52aaa69ef501c8440eb3a32e5cc9ba59a Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Mon, 6 Jan 2025 15:41:14 -0600 Subject: [PATCH 13/16] Fix ktlint --- .../swift/codegen/integration/HTTPBindingProtocolGenerator.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt index 5e14c126a..a6cfb91e8 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt @@ -69,7 +69,6 @@ import software.amazon.smithy.swift.codegen.model.ShapeMetadata import software.amazon.smithy.swift.codegen.model.findStreamingMember import software.amazon.smithy.swift.codegen.model.hasEventStreamMember import software.amazon.smithy.swift.codegen.model.hasTrait -import software.amazon.smithy.swift.codegen.model.isEnum import software.amazon.smithy.swift.codegen.model.isInputEventStream import software.amazon.smithy.swift.codegen.model.isOutputEventStream import software.amazon.smithy.swift.codegen.model.targetOrSelf From bd9bfdfd3a6fdb6566a68194175f3a9e50caab62 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Mon, 6 Jan 2025 21:07:45 -0600 Subject: [PATCH 14/16] Clean up respectsJSONName propagation --- Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift | 3 --- Sources/SmithyJSON/Reader/Reader.swift | 5 +++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift index c81958b1c..6a8bad058 100644 --- a/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift +++ b/Sources/SmithyJSON/Reader/Reader+ShapeDeserializer.swift @@ -48,7 +48,6 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { } var value = [T]() for child in resolvedReader.children { - child.respectsJSONName = respectsJSONName try memberContainer.performRead(base: &value, key: "", reader: child) } return value @@ -67,7 +66,6 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { } var value = [String: T]() for child in resolvedReader.children { - child.respectsJSONName = respectsJSONName if !mapSchema.isSparse && child.jsonNode == .null { continue } try valueContainer.performRead(base: &value, key: child.nodeInfo.name, reader: child) } @@ -79,7 +77,6 @@ extension Reader: SmithyReadWrite.ShapeDeserializer { guard resolvedReader.hasContent, case .string = resolvedReader.jsonNode else { return try resolvedDefault(schema: schema).map { T(rawValue: try $0.asString())! } } - let enumSchema = resolvedTargetSchema(schema: schema) guard let rawValue: String = try resolvedReader.readIfPresent() else { return nil } return T(rawValue: rawValue) } diff --git a/Sources/SmithyJSON/Reader/Reader.swift b/Sources/SmithyJSON/Reader/Reader.swift index 7c920ebd9..59415ca7b 100644 --- a/Sources/SmithyJSON/Reader/Reader.swift +++ b/Sources/SmithyJSON/Reader/Reader.swift @@ -24,7 +24,9 @@ public final class Reader: SmithyReader { public let nodeInfo: NodeInfo let jsonNode: JSONNode? - public var respectsJSONName = false + public var respectsJSONName = false { + didSet { children.forEach { $0.respectsJSONName = respectsJSONName } } + } public internal(set) var children = [Reader]() public internal(set) weak var parent: Reader? public var hasContent: Bool { jsonNode != nil && jsonNode != .null } @@ -77,7 +79,6 @@ public extension Reader { subscript(nodeInfo: NodeInfo) -> Reader { if let match = children.first(where: { nodeInfo.name == $0.nodeInfo.name }) { - match.respectsJSONName = respectsJSONName return match } else { // The queried node doesn't exist. Return one that has nil content. From a8338811b725d9ee124986af61afd9b364e59806 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Wed, 8 Jan 2025 09:13:05 -0600 Subject: [PATCH 15/16] Revert SwiftWriter changes --- .../amazon/smithy/swift/codegen/StructureGenerator.kt | 4 ++-- .../software/amazon/smithy/swift/codegen/SwiftWriter.kt | 2 +- .../smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt | 2 +- .../swift/codegen/events/MessageMarshallableGenerator.kt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt index a2322e1b1..7f468e640 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/StructureGenerator.kt @@ -151,7 +151,7 @@ class StructureGenerator( val (memberName, memberSymbol) = memberShapeDataContainer.getOrElse(member) { Pair(null, null) } if (memberName == null || memberSymbol == null) continue val terminator = if (index == membersSortedByName.size - 1) "" else "," - writer.write("\$L: \$T = \$D\$L", memberName, memberSymbol, memberSymbol, terminator) + writer.write("\$L: \$D\$L", memberName, memberSymbol, terminator) } } writer.openBlock("{", "}") { @@ -245,7 +245,7 @@ class StructureGenerator( writer.writeAvailableAttribute(model, it) val targetShape = model.expectShape(it.target) val boxedOrNot = "@Boxed ".takeIf { targetShape.hasTrait() } - writer.write("\$Lpublic internal(set) var \$L: \$T = \$D", boxedOrNot, memberName, memberSymbol, memberSymbol) + writer.write("\$Lpublic internal(set) var \$L: \$D", boxedOrNot, memberName, memberSymbol) } } writer.write("") diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftWriter.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftWriter.kt index d052c7517..ed8bc8e06 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftWriter.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftWriter.kt @@ -193,7 +193,7 @@ class SwiftWriter( if (shouldSetDefault) { getDefaultValue(type)?.let { - formatted = "$it" + formatted += " = $it" } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt index c2b1ddc98..c20f9d1ae 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt @@ -57,7 +57,7 @@ class EndpointParamsGenerator( val memberName = param.name.toString().toLowerCamelCase() val memberSymbol = param.toSymbol() val terminator = if (index != parameters.lastIndex) "," else "" - writer.write("$memberName: \$T = \$D$terminator", memberSymbol, memberSymbol) + writer.write("\$L: \$D\$L", memberName, memberSymbol, terminator) } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageMarshallableGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageMarshallableGenerator.kt index f435a58b9..0cc35b848 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageMarshallableGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/events/MessageMarshallableGenerator.kt @@ -45,7 +45,7 @@ class MessageMarshallableGenerator( "var headers: [\$N] = [.init(name: \":message-type\", value: .string(\"event\"))]", SmithyEventStreamsAPITypes.Header ) - write("var payload: \$1T = \$1D", FoundationTypes.Data) + write("var payload: \$D", FoundationTypes.Data) write("switch self {") streamShape.eventStreamEvents(ctx.model).forEach { member -> val memberName = ctx.symbolProvider.toMemberName(member) From 5721d02210e60f061918d01f02d6191e4f435f62 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Wed, 8 Jan 2025 09:24:42 -0600 Subject: [PATCH 16/16] Remove default value from endpoint generator --- .../swift/codegen/endpoints/EndpointParamsGenerator.kt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt index c20f9d1ae..36b076fa4 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/endpoints/EndpointParamsGenerator.kt @@ -147,13 +147,5 @@ fun Parameter.toSymbol(): Symbol { } } - if (isRequired && !default.isPresent) { - when (type) { - ParameterType.STRING -> builder.defaultValue("\"\"") - ParameterType.BOOLEAN -> builder.defaultValue("false") - ParameterType.STRING_ARRAY -> builder.defaultValue("[]") - } - } - return builder.build() }