From 495f2e1eeaec06c484a4c477feac47608f5d9d94 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Tue, 4 Nov 2025 15:23:33 -0600 Subject: [PATCH 01/13] Disable input/output serializers --- Sources/Smithy/Schema/Node.swift | 109 ++++++++++++++++++ Sources/Smithy/Schema/Prelude.swift | 85 ++++++++++++++ Sources/Smithy/Schema/Schema.swift | 34 ++++++ .../{Document => Schema}/ShapeType.swift | 0 .../swift/codegen/DirectedSwiftCodegen.kt | 1 + .../HTTPBindingProtocolGenerator.kt | 105 ++++++++++++++++- .../codegen/integration/ProtocolGenerator.kt | 2 + .../serde/schema/SchemaGenerator.kt | 76 ++++++++++++ .../serde/schema/SchemaShapeUtils.kt | 45 ++++++++ .../serde/schema/SwiftNodeUtils.kt | 44 +++++++ .../swiftmodules/SmithyReadWriteTypes.kt | 5 + .../swift/codegen/swiftmodules/SmithyTypes.kt | 30 ++++- .../swift/codegen/swiftmodules/SwiftTypes.kt | 1 + .../swift/codegen/utils/SchemaFileUtils.kt | 17 +++ 14 files changed, 548 insertions(+), 6 deletions(-) create mode 100644 Sources/Smithy/Schema/Node.swift create mode 100644 Sources/Smithy/Schema/Prelude.swift create mode 100644 Sources/Smithy/Schema/Schema.swift rename Sources/Smithy/{Document => Schema}/ShapeType.swift (100%) create mode 100644 smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt 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/integration/serde/schema/SwiftNodeUtils.kt create mode 100644 smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/utils/SchemaFileUtils.kt diff --git a/Sources/Smithy/Schema/Node.swift b/Sources/Smithy/Schema/Node.swift new file mode 100644 index 000000000..55f2c8618 --- /dev/null +++ b/Sources/Smithy/Schema/Node.swift @@ -0,0 +1,109 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// Contains the value of a Smithy Node. +/// +/// Smithy node data is basically the same as the data that can be stored in JSON. +/// The root of a Smithy node may be of any case, i.e. unlike JSON, the root element is not limited to object or list. +/// +/// See the definition of node value in the Smithy spec: https://smithy.io/2.0/spec/model.html#node-values +public enum Node: Sendable { + case object([String: Node]) + case list([Node]) + case string(String) + case number(Double) + case boolean(Bool) + case null +} + +public extension Node { + + /// Returns the object dictionary if this Node is `.object`, else returns `nil`. + var object: [String: Node]? { + guard case .object(let value) = self else { return nil } + return value + } + + /// Returns the array of `Node` if this node is `.list`, else returns `nil`. + var list: [Node]? { + guard case .list(let value) = self else { return nil } + return value + } + + /// Returns the string if this node is `.string`, else returns `nil`. + var string: String? { + guard case .string(let value) = self else { return nil } + return value + } + + /// Returns the Double if this node is `.number`, else returns `nil`. + var number: Double? { + guard case .number(let value) = self else { return nil } + return value + } + + /// Returns the `Bool` value if this node is `.boolean`, else returns `nil`. + var boolean: Bool? { + guard case .boolean(let value) = self else { return nil } + return value + } + + /// Returns `true` if this node is `.null`, else returns `false`. + var null: Bool { + guard case .null = self else { return false } + return true + } +} + +extension Node: ExpressibleByDictionaryLiteral { + + public init(dictionaryLiteral elements: (String, Node)...) { + self = .object(Dictionary(uniqueKeysWithValues: elements)) + } +} + +extension Node: ExpressibleByArrayLiteral { + + public init(arrayLiteral elements: Node...) { + self = .list(elements) + } +} + +extension Node: ExpressibleByStringLiteral { + + public init(stringLiteral value: String) { + self = .string(value) + } +} + +extension Node: ExpressibleByIntegerLiteral { + + public init(integerLiteral value: IntegerLiteralType) { + self = .number(Double(value)) + } +} + +extension Node: ExpressibleByFloatLiteral { + + public init(floatLiteral value: FloatLiteralType) { + self = .number(Double(value)) + } +} + +extension Node: ExpressibleByBooleanLiteral { + + public init(booleanLiteral value: BooleanLiteralType) { + self = .boolean(value) + } +} + +extension Node: ExpressibleByNilLiteral { + + public init(nilLiteral: ()) { + self = .null + } +} diff --git a/Sources/Smithy/Schema/Prelude.swift b/Sources/Smithy/Schema/Prelude.swift new file mode 100644 index 000000000..79775e127 --- /dev/null +++ b/Sources/Smithy/Schema/Prelude.swift @@ -0,0 +1,85 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// Below are schemas for all model shapes defined in the Smithy 2.0 prelude. +// Schemas for custom Smithy types may use these schemas in their definitions. + +public var unitSchema: Schema { + Schema(id: "smithy.api#Unit", type: .structure) +} + +public var booleanSchema: Schema { + Schema(id: "smithy.api#Boolean", type: .boolean) +} + +public var stringSchema: Schema { + Schema(id: "smithy.api#String", type: .string) +} + +public var integerSchema: Schema { + Schema(id: "smithy.api#Integer", type: .integer) +} + +public var blobSchema: Schema { + Schema(id: "smithy.api#Blob", type: .blob) +} + +public var timestampSchema: Schema { + Schema(id: "smithy.api#Timestamp", type: .timestamp) +} + +public var byteSchema: Schema { + Schema(id: "smithy.api#Byte", type: .byte) +} + +public var shortSchema: Schema { + Schema(id: "smithy.api#Short", type: .short) +} + +public var longSchema: Schema { + Schema(id: "smithy.api#Long", type: .long) +} + +public var floatSchema: Schema { + Schema(id: "smithy.api#Float", type: .float) +} + +public var doubleSchema: Schema { + Schema(id: "smithy.api#Double", type: .double) +} + +public var documentSchema: Schema { + Schema(id: "smithy.api#PrimitiveDocument", type: .document) +} + +public var primitiveBooleanSchema: Schema { + Schema(id: "smithy.api#PrimitiveBoolean", type: .boolean, traits: ["smithy.api#default": false]) +} + +public var primitiveIntegerSchema: Schema { + Schema(id: "smithy.api#PrimitiveInteger", type: .integer, traits: ["smithy.api#default": 0]) +} + +public var primitiveByteSchema: Schema { + Schema(id: "smithy.api#PrimitiveByte", type: .byte, traits: ["smithy.api#default": 0]) +} + +public var primitiveShortSchema: Schema { + Schema(id: "smithy.api#PrimitiveShort", type: .short, traits: ["smithy.api#default": 0]) +} + +public var primitiveLongSchema: Schema { + Schema(id: "smithy.api#PrimitiveLong", type: .long, traits: ["smithy.api#default": 0]) +} + +public var primitiveFloatSchema: Schema { + Schema(id: "smithy.api#PrimitiveFloat", type: .float, traits: ["smithy.api#default": 0]) +} + +public var primitiveDoubleSchema: Schema { + Schema(id: "smithy.api#PrimitiveDouble", type: .double, traits: ["smithy.api#default": 0]) +} diff --git a/Sources/Smithy/Schema/Schema.swift b/Sources/Smithy/Schema/Schema.swift new file mode 100644 index 000000000..14644b702 --- /dev/null +++ b/Sources/Smithy/Schema/Schema.swift @@ -0,0 +1,34 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +public class Schema { + public let id: String + public let type: ShapeType + public let traits: [String: Node] + public let members: [Schema] + public let memberName: String? + public let target: Schema? + public let index: Int + + public init( + id: String, + type: ShapeType, + traits: [String: Node] = [:], + members: [Schema] = [], + memberName: String? = nil, + target: Schema? = nil, + index: Int = -1 + ) { + self.id = id + self.type = type + self.traits = traits + self.members = members + self.memberName = memberName + self.target = target + self.index = index + } +} diff --git a/Sources/Smithy/Document/ShapeType.swift b/Sources/Smithy/Schema/ShapeType.swift similarity index 100% rename from Sources/Smithy/Document/ShapeType.swift rename to Sources/Smithy/Schema/ShapeType.swift 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 19f04bf0c..4123503ba 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 @@ -81,6 +81,7 @@ class DirectedSwiftCodegen( generateMessageMarshallable(ctx) generateMessageUnmarshallable(ctx) generateCodableConformanceForNestedTypes(ctx) + generateSchemas(ctx) initializeMiddleware(ctx) 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 3b277f731..001aa70b9 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 @@ -58,6 +59,7 @@ 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.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 @@ -72,6 +74,7 @@ import software.amazon.smithy.swift.codegen.model.isOutputEventStream 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 @@ -139,6 +142,7 @@ abstract class HTTPBindingProtocolGenerator( override var serviceErrorProtocolSymbol: Symbol = ClientRuntimeTypes.Http.HttpError override fun generateSerializers(ctx: ProtocolGenerator.GenerationContext) { + if (usesSchemaBasedSerialization(ctx)) { return } // temporary condition // render conformance to HttpRequestBinding for all input shapes val inputShapesWithHttpBindings: MutableSet = mutableSetOf() for (operation in getHttpBindingOperations(ctx)) { @@ -201,10 +205,40 @@ abstract class HTTPBindingProtocolGenerator( } } + private fun usesSchemaBasedSerialization(ctx: ProtocolGenerator.GenerationContext): Boolean = + // This fun is temporary; it will be eliminated when all services/protocols are moved to schema-based + ctx.service.allTraits.keys.any { it.name == "rpcv2Cbor" || it.name == "awsJson1_0" || it.name == "awsJson1_1" } + + override fun generateSchemas(ctx: ProtocolGenerator.GenerationContext) { + if (!usesSchemaBasedSerialization(ctx)) { return } // temporary condition + val nestedShapes = resolveShapesNeedingSchema(ctx) + .filter { it.type != ShapeType.MEMBER } // Member schemas are only rendered in-line + nestedShapes.forEach { renderSchemas(ctx, it) } + } + + private fun renderSchemas( + ctx: ProtocolGenerator.GenerationContext, + shape: Shape, + ) { + val symbol: Symbol = ctx.symbolProvider.toSymbol(shape) + val symbolName = symbol.name + val filename = SchemaFileUtils.filename(ctx.settings, "${shape.id.name}+Schema") + val encodeSymbol = + Symbol + .builder() + .definitionFile(filename) + .name(symbolName) + .build() + ctx.delegator.useShapeWriter(encodeSymbol) { writer -> + SchemaGenerator(ctx, writer).renderSchema(shape) + } + } + fun renderCodableExtension( ctx: ProtocolGenerator.GenerationContext, shape: Shape, ) { + if (!usesSchemaBasedSerialization(ctx)) { return } // temporary condition if (!shape.hasTrait() && !shape.hasTrait()) { return } @@ -250,11 +284,11 @@ abstract class HTTPBindingProtocolGenerator( } private fun resolveInputShapes(ctx: ProtocolGenerator.GenerationContext): Map> { - var shapesInfo: MutableMap> = mutableMapOf() + val shapesInfo: MutableMap> = mutableMapOf() val operations = getHttpBindingOperations(ctx) for (operation in operations) { val inputType = ctx.model.expectShape(operation.input.get()) - var metadata = + val metadata = mapOf( Pair(ShapeMetadata.OPERATION_SHAPE, operation), Pair(ShapeMetadata.SERVICE_VERSION, ctx.service.version), @@ -363,6 +397,73 @@ abstract class HTTPBindingProtocolGenerator( return resolved } + private fun resolveShapesNeedingSchema(ctx: ProtocolGenerator.GenerationContext): Set { + val topLevelInputMembers = getHttpBindingOperations(ctx).flatMap { + val inputShape = ctx.model.expectShape(it.input.get()) + inputShape.members() + } + .map { ctx.model.expectShape(it.target) } + .toSet() + + val topLevelOutputMembers = + getHttpBindingOperations(ctx) + .map { ctx.model.expectShape(it.output.get()) } + .toSet() + + val topLevelErrorMembers = + getHttpBindingOperations(ctx) + .flatMap { it.errors } + .map { ctx.model.expectShape(it) } + .toSet() + + val topLevelServiceErrorMembers = + ctx.service.errors + .map { ctx.model.expectShape(it) } + .toSet() + + val allTopLevelMembers = + topLevelInputMembers + .union(topLevelOutputMembers) + .union(topLevelErrorMembers) + .union(topLevelServiceErrorMembers) + + return walkNestedShapesRequiringSchema(ctx, allTopLevelMembers) + } + + 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 { + // 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 + } + // 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 fa558d524..bfcf3479b 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 @@ -122,6 +122,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..0f1b98533 --- /dev/null +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaGenerator.kt @@ -0,0 +1,76 @@ +package software.amazon.smithy.swift.codegen.integration.serde.schema + +import software.amazon.smithy.model.shapes.MemberShape +import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.swift.codegen.SwiftWriter +import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTypes +import kotlin.jvm.optionals.getOrNull + +class SchemaGenerator( + val ctx: ProtocolGenerator.GenerationContext, + val writer: SwiftWriter, +) { + fun renderSchema(shape: Shape) { + writer.openBlock( + "var \$L: \$N {", + "}", + shape.schemaVar(writer), + SmithyTypes.Schema, + ) { + renderSchemaStruct(shape) + writer.unwrite(",\n") + writer.write("") + } + } + + private fun renderSchemaStruct(shape: Shape, index: Int? = null) { + writer.openBlock(".init(", "),") { + writer.write("id: \$S,", shape.id.toString()) + writer.write("type: .\$L,", shape.type) + val relevantTraits = shape.allTraits.filter { permittedTraitIDs.contains(it.key.toString()) } + if (relevantTraits.isNotEmpty()) { + writer.openBlock("traits: [", "],") { + relevantTraits.forEach { trait -> + writer.write( + "\$S: \$L,", + trait.key.toString(), + trait.value.toNode().toSwiftNode(writer), + ) + } + writer.unwrite(",\n") + writer.write("") + } + } + if (shape.members().isNotEmpty()) { + writer.openBlock("members: [", "],") { + shape.members().withIndex().forEach { renderSchemaStruct(it.value, it.index) } + writer.unwrite(",\n") + writer.write("") + } + } + shape.id.member + .getOrNull() + ?.let { writer.write("memberName: \$S,", it) } + targetShape(shape)?.let { writer.write("target: \$L,", it.schemaVar(writer)) } + index?.let { writer.write("index: \$L,", it) } + writer.unwrite(",\n") + writer.write("") + } + } + + private fun targetShape(shape: Shape): Shape? = memberShape(shape)?.let { ctx.model.expectShape(it.target) } + + private fun memberShape(shape: Shape): MemberShape? = shape.asMemberShape().getOrNull() +} + +private val permittedTraitIDs: Set = + setOf( + "smithy.api#sparse", + "smithy.api#enumValue", + "smithy.api#jsonName", + "smithy.api#required", + "smithy.api#default", + "smithy.api#timestampFormat", + "smithy.api#httpPayload", + ) 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..104f5a0f5 --- /dev/null +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaShapeUtils.kt @@ -0,0 +1,45 @@ +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.swift.codegen.SwiftWriter +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTypes +import kotlin.jvm.optionals.getOrNull + +fun Shape.schemaVar(writer: SwiftWriter): String = + if (this.id.namespace == "smithy.api") { + this.id.preludeSchemaVarName(writer) + } else { + this.id.schemaVarName() + } + +private fun ShapeId.preludeSchemaVarName(writer: SwiftWriter): String = + when (this.name) { + "Unit" -> writer.format("\$N", SmithyTypes.unitSchema) + "String" -> writer.format("\$N", SmithyTypes.stringSchema) + "Blob" -> writer.format("\$N", SmithyTypes.blobSchema) + "Integer" -> writer.format("\$N", SmithyTypes.integerSchema) + "Timestamp" -> writer.format("\$N", SmithyTypes.timestampSchema) + "Boolean" -> writer.format("\$N", SmithyTypes.booleanSchema) + "Float" -> writer.format("\$N", SmithyTypes.floatSchema) + "Double" -> writer.format("\$N", SmithyTypes.doubleSchema) + "Long" -> writer.format("\$N", SmithyTypes.longSchema) + "Short" -> writer.format("\$N", SmithyTypes.shortSchema) + "Byte" -> writer.format("\$N", SmithyTypes.byteSchema) + "PrimitiveInteger" -> writer.format("\$N", SmithyTypes.primitiveIntegerSchema) + "PrimitiveBoolean" -> writer.format("\$N", SmithyTypes.primitiveBooleanSchema) + "PrimitiveFloat" -> writer.format("\$N", SmithyTypes.primitiveFloatSchema) + "PrimitiveDouble" -> writer.format("\$N", SmithyTypes.primitiveDoubleSchema) + "PrimitiveLong" -> writer.format("\$N", SmithyTypes.primitiveLongSchema) + "PrimitiveShort" -> writer.format("\$N", SmithyTypes.primitiveShortSchema) + "PrimitiveByte" -> writer.format("\$N", SmithyTypes.primitiveByteSchema) + "Document" -> writer.format("\$N", SmithyTypes.documentSchema) + else -> throw Exception("Unhandled prelude type converted to schemaVar: ${this.name}") + } + +private fun ShapeId.schemaVarName(): String { + assert(this.member.getOrNull() == null) + val namespacePortion = this.namespace.replace(".", "_") + val namePortion = this.name + return "schema__${namespacePortion}__${namePortion}" +} diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SwiftNodeUtils.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SwiftNodeUtils.kt new file mode 100644 index 000000000..b76c3624d --- /dev/null +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SwiftNodeUtils.kt @@ -0,0 +1,44 @@ +package software.amazon.smithy.swift.codegen.integration.serde.schema + +import software.amazon.smithy.model.node.ArrayNode +import software.amazon.smithy.model.node.BooleanNode +import software.amazon.smithy.model.node.Node +import software.amazon.smithy.model.node.NullNode +import software.amazon.smithy.model.node.NumberNode +import software.amazon.smithy.model.node.ObjectNode +import software.amazon.smithy.model.node.StringNode +import software.amazon.smithy.swift.codegen.SwiftWriter + +fun Node.toSwiftNode(writer: SwiftWriter): String = + when (this) { + is ObjectNode -> { + if (members.isEmpty()) { + writer.format("[:]") + } else { + val contents = + members.map { + writer.format("\$S:\$L", it.key, it.value.toSwiftNode(writer)) + } + writer.format("[\$L]", contents.joinToString(",")) + } + } + is ArrayNode -> { + val contents = elements.map { it.toSwiftNode(writer) } + writer.format("[\$L]", contents.joinToString(",")) + } + is StringNode -> { + writer.format("\$S", value) + } + is NumberNode -> { + writer.format("\$L", value) + } + is BooleanNode -> { + writer.format("\$L", value) + } + is NullNode -> { + writer.format("nil") + } + else -> { + throw Exception("Unknown node type") + } + } 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 070e26034..d383db5a1 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,6 +27,11 @@ object SmithyReadWriteTypes { val WritingClosures = runtimeSymbol("WritingClosures", SwiftDeclaration.ENUM) val ReadingClosureBox = runtimeSymbol("ReadingClosureBox", SwiftDeclaration.STRUCT) val WritingClosureBox = runtimeSymbol("WritingClosureBox", SwiftDeclaration.STRUCT) + val ShapeSerializer = runtimeSymbol("ShapeSerializer", SwiftDeclaration.PROTOCOL) + val ShapeDeserializer = runtimeSymbol("ShapeDeserializer", SwiftDeclaration.PROTOCOL) + val SerializableStruct = runtimeSymbol("SerializableStruct", SwiftDeclaration.PROTOCOL) + val DeserializableStruct = runtimeSymbol("DeserializableStruct", SwiftDeclaration.PROTOCOL) + val Unit = runtimeSymbol("Unit", SwiftDeclaration.STRUCT) } 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 4e8bd7caf..d9641d472 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,16 +21,38 @@ object SmithyTypes { val LogAgent = runtimeSymbol("LogAgent", SwiftDeclaration.PROTOCOL) val RequestMessageSerializer = runtimeSymbol("RequestMessageSerializer", SwiftDeclaration.PROTOCOL) val URIQueryItem = runtimeSymbol("URIQueryItem", SwiftDeclaration.STRUCT) + val Schema = runtimeSymbol("Schema", SwiftDeclaration.CLASS) + 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( name: String, - declaration: SwiftDeclaration? = null, + 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 79dc669b1..24d437fe4 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 StringList = SwiftSymbol.make( "[String]", 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..240b99803 --- /dev/null +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/utils/SchemaFileUtils.kt @@ -0,0 +1,17 @@ +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 = + if (settings.mergeModels) { + "Sources/${settings.moduleName}/Schemas.swift" + } else { + "Sources/${settings.moduleName}/schemas/$filename.swift" + } + } +} From ca5b3314f0ae8eaddca9574f3d1f000bfb3c0f2a Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Wed, 5 Nov 2025 09:21:51 -0600 Subject: [PATCH 02/13] add ShapeID, doc comments --- Package.swift | 4 + Sources/Smithy/Schema/Node.swift | 2 +- Sources/Smithy/Schema/Prelude.swift | 117 +++++++++--------- Sources/Smithy/Schema/Schema.swift | 54 ++++++-- Sources/Smithy/Schema/ShapeID.swift | 42 +++++++ Sources/Smithy/Schema/ShapeType.swift | 3 +- Tests/SmithyTests/ShapeIDTests.swift | 22 ++++ .../HTTPBindingProtocolGenerator.kt | 2 + .../serde/schema/SchemaGenerator.kt | 50 ++++---- .../serde/schema/SchemaShapeUtils.kt | 44 +++---- .../integration/serde/schema/SchemaTraits.kt | 12 ++ .../swift/codegen/swiftmodules/SmithyTypes.kt | 20 +-- 12 files changed, 240 insertions(+), 132 deletions(-) create mode 100644 Sources/Smithy/Schema/ShapeID.swift create mode 100644 Tests/SmithyTests/ShapeIDTests.swift create mode 100644 smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaTraits.kt diff --git a/Package.swift b/Package.swift index a781bbcb8..c8b62c889 100644 --- a/Package.swift +++ b/Package.swift @@ -268,6 +268,10 @@ let package = Package( ], resources: [ .process("Resources") ] ), + .testTarget( + name: "SmithyTests", + dependencies: ["Smithy"] + ), .testTarget( name: "SmithyCBORTests", dependencies: ["SmithyCBOR", "ClientRuntime", "SmithyTestUtil"] diff --git a/Sources/Smithy/Schema/Node.swift b/Sources/Smithy/Schema/Node.swift index 55f2c8618..ce6c2d64e 100644 --- a/Sources/Smithy/Schema/Node.swift +++ b/Sources/Smithy/Schema/Node.swift @@ -8,7 +8,7 @@ /// Contains the value of a Smithy Node. /// /// Smithy node data is basically the same as the data that can be stored in JSON. -/// The root of a Smithy node may be of any case, i.e. unlike JSON, the root element is not limited to object or list. +/// The root of a Smithy node may be of any type, i.e. unlike JSON, the root element is not limited to object or list. /// /// See the definition of node value in the Smithy spec: https://smithy.io/2.0/spec/model.html#node-values public enum Node: Sendable { diff --git a/Sources/Smithy/Schema/Prelude.swift b/Sources/Smithy/Schema/Prelude.swift index 79775e127..4658ccfc2 100644 --- a/Sources/Smithy/Schema/Prelude.swift +++ b/Sources/Smithy/Schema/Prelude.swift @@ -8,78 +8,83 @@ // Below are schemas for all model shapes defined in the Smithy 2.0 prelude. // Schemas for custom Smithy types may use these schemas in their definitions. -public var unitSchema: Schema { - Schema(id: "smithy.api#Unit", type: .structure) -} +public enum Prelude { -public var booleanSchema: Schema { - Schema(id: "smithy.api#Boolean", type: .boolean) -} + public static var unitSchema: Schema { + Schema(id: .init("smithy.api", "Unit"), type: .structure) + } -public var stringSchema: Schema { - Schema(id: "smithy.api#String", type: .string) -} + public static var booleanSchema: Schema { + Schema(id: .init("smithy.api", "Boolean"), type: .boolean) + } -public var integerSchema: Schema { - Schema(id: "smithy.api#Integer", type: .integer) -} + public static var stringSchema: Schema { + Schema(id: .init("smithy.api", "String"), type: .string) + } -public var blobSchema: Schema { - Schema(id: "smithy.api#Blob", type: .blob) -} + public static var integerSchema: Schema { + Schema(id: .init("smithy.api", "Integer"), type: .integer) + } -public var timestampSchema: Schema { - Schema(id: "smithy.api#Timestamp", type: .timestamp) -} + public static var blobSchema: Schema { + Schema(id: .init("smithy.api", "Blob"), type: .blob) + } -public var byteSchema: Schema { - Schema(id: "smithy.api#Byte", type: .byte) -} + public static var timestampSchema: Schema { + Schema(id: .init("smithy.api", "Timestamp"), type: .timestamp) + } -public var shortSchema: Schema { - Schema(id: "smithy.api#Short", type: .short) -} + public static var byteSchema: Schema { + Schema(id: .init("smithy.api", "Byte"), type: .byte) + } -public var longSchema: Schema { - Schema(id: "smithy.api#Long", type: .long) -} + public static var shortSchema: Schema { + Schema(id: .init("smithy.api", "Short"), type: .short) + } -public var floatSchema: Schema { - Schema(id: "smithy.api#Float", type: .float) -} + public static var longSchema: Schema { + Schema(id: .init("smithy.api", "Long"), type: .long) + } -public var doubleSchema: Schema { - Schema(id: "smithy.api#Double", type: .double) -} + public static var floatSchema: Schema { + Schema(id: .init("smithy.api", "Float"), type: .float) + } -public var documentSchema: Schema { - Schema(id: "smithy.api#PrimitiveDocument", type: .document) -} + public static var doubleSchema: Schema { + Schema(id: .init("smithy.api", "Double"), type: .double) + } -public var primitiveBooleanSchema: Schema { - Schema(id: "smithy.api#PrimitiveBoolean", type: .boolean, traits: ["smithy.api#default": false]) -} + public static var documentSchema: Schema { + Schema(id: .init("smithy.api", "PrimitiveDocument"), type: .document) + } -public var primitiveIntegerSchema: Schema { - Schema(id: "smithy.api#PrimitiveInteger", type: .integer, traits: ["smithy.api#default": 0]) -} + public static var primitiveBooleanSchema: Schema { + Schema(id: .init("smithy.api", "PrimitiveBoolean"), type: .boolean, traits: [defaultTraitID: false]) + } -public var primitiveByteSchema: Schema { - Schema(id: "smithy.api#PrimitiveByte", type: .byte, traits: ["smithy.api#default": 0]) -} + public static var primitiveIntegerSchema: Schema { + Schema(id: .init("smithy.api", "PrimitiveInteger"), type: .integer, traits: [defaultTraitID: 0]) + } -public var primitiveShortSchema: Schema { - Schema(id: "smithy.api#PrimitiveShort", type: .short, traits: ["smithy.api#default": 0]) -} + public static var primitiveByteSchema: Schema { + Schema(id: .init("smithy.api", "PrimitiveByte"), type: .byte, traits: [defaultTraitID: 0]) + } -public var primitiveLongSchema: Schema { - Schema(id: "smithy.api#PrimitiveLong", type: .long, traits: ["smithy.api#default": 0]) -} + public static var primitiveShortSchema: Schema { + Schema(id: .init("smithy.api", "PrimitiveShort"), type: .short, traits: [defaultTraitID: 0]) + } -public var primitiveFloatSchema: Schema { - Schema(id: "smithy.api#PrimitiveFloat", type: .float, traits: ["smithy.api#default": 0]) -} + public static var primitiveLongSchema: Schema { + Schema(id: .init("smithy.api", "PrimitiveLong"), type: .long, traits: [defaultTraitID: 0]) + } -public var primitiveDoubleSchema: Schema { - Schema(id: "smithy.api#PrimitiveDouble", type: .double, traits: ["smithy.api#default": 0]) + public static var primitiveFloatSchema: Schema { + Schema(id: .init("smithy.api", "PrimitiveFloat"), type: .float, traits: [defaultTraitID: 0]) + } + + public static var primitiveDoubleSchema: Schema { + Schema(id: .init("smithy.api", "PrimitiveDouble"), type: .double, traits: [defaultTraitID: 0]) + } } + +private let defaultTraitID = ShapeID("smithy.api", "default") diff --git a/Sources/Smithy/Schema/Schema.swift b/Sources/Smithy/Schema/Schema.swift index 14644b702..ff94fd859 100644 --- a/Sources/Smithy/Schema/Schema.swift +++ b/Sources/Smithy/Schema/Schema.swift @@ -5,21 +5,50 @@ // SPDX-License-Identifier: Apache-2.0 // +/// A structure which describes selected Smithy model information for a Smithy model shape. +/// +/// Typically, the Schema contains only modeled info & properties that are relevant to +/// serialization, transport bindings, and other functions performed by the SDK. public class Schema { - public let id: String + + /// The Smithy shape ID for the shape described by this schema. + public let id: ShapeID + + /// The type of the shape being described. public let type: ShapeType - public let traits: [String: Node] + + /// A dictionary of the described shape's trait shape IDs to Nodes with trait data. + /// + /// Not all traits for a shape will be represented in the schema; + /// typically the Schema contains only the traits relevant to the client-side SDK. + public let traits: [ShapeID: Node] + + /// The member schemas for this schema, if any. + /// + /// Typically only a schema of type Structure, Union, Enum, IntEnum, List or Map will have members. public let members: [Schema] - public let memberName: String? + + /// The target schema for this schema. Will only be used when this is a member schema. public let target: Schema? - public let index: Int + /// The index of this schema, if it represents a Smithy member. + /// + /// For a member schema, index will be set to its index in the members array. + /// For other types of schema, index will be `-1`. + /// + /// This index is intended for use as a performance enhancement when looking up member schemas + /// during deserialization. + public let index: Int + + /// Creates a new Schema using the passed parameters. + /// + /// No validation is performed on the parameters since calls to this initializer + /// are almost always code-generated from a previously validated Smithy model. public init( - id: String, + id: ShapeID, type: ShapeType, - traits: [String: Node] = [:], + traits: [ShapeID: Node] = [:], members: [Schema] = [], - memberName: String? = nil, target: Schema? = nil, index: Int = -1 ) { @@ -27,8 +56,17 @@ public class Schema { self.type = type self.traits = traits self.members = members - self.memberName = memberName self.target = target self.index = index } } + +public extension Schema { + + /// The member name for this schema, if any. + /// + /// Member name is computed from the schema's ID. + var memberName: String? { + id.member + } +} diff --git a/Sources/Smithy/Schema/ShapeID.swift b/Sources/Smithy/Schema/ShapeID.swift new file mode 100644 index 000000000..b78a4d3bb --- /dev/null +++ b/Sources/Smithy/Schema/ShapeID.swift @@ -0,0 +1,42 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// Represents a single Smithy shape ID. +/// +/// Shape ID is described in the Smithy 2.0 spec [here](https://smithy.io/2.0/spec/model.html#shape-id). +public struct ShapeID: Hashable { + public let namespace: String + public let name: String + public let member: String? + + /// Creates a Shape ID for a Smithy shape. + /// + /// This initializer does no validation of length or of allowed characters in the Shape ID; + /// that is to be ensured by the caller (typically calls to this initializer will be code-generated + /// from previously validated Smithy models.) + /// - Parameters: + /// - namespace: The namespace for this shape, i.e. `smithy.api`. + /// - name: The name for this shape, i.e. `Integer`. + /// - member: The optional member name for this shape. + public init(_ namespace: String, _ name: String, _ member: String? = nil) { + self.namespace = namespace + self.name = name + self.member = member + } +} + +extension ShapeID: CustomStringConvertible { + + /// Returns the absolute Shape ID in a single, printable string. + public var description: String { + if let member = self.member { + return "\(namespace)#\(name)$\(member)" + } else { + return "\(namespace)#\(name)" + } + } +} diff --git a/Sources/Smithy/Schema/ShapeType.swift b/Sources/Smithy/Schema/ShapeType.swift index 5b3de70a1..310a24fe8 100644 --- a/Sources/Smithy/Schema/ShapeType.swift +++ b/Sources/Smithy/Schema/ShapeType.swift @@ -5,8 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 // -/// 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 +/// 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 { case blob case boolean diff --git a/Tests/SmithyTests/ShapeIDTests.swift b/Tests/SmithyTests/ShapeIDTests.swift new file mode 100644 index 000000000..d4b28de16 --- /dev/null +++ b/Tests/SmithyTests/ShapeIDTests.swift @@ -0,0 +1,22 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +import Smithy + +class ShapeIDTests: XCTestCase { + + func test_description_noMember() { + let subject = ShapeID("smithy.test", "TestShape") + XCTAssertEqual(subject.description, "smithy.test#TestShape") + } + + func test_description_withMember() { + let subject = ShapeID("smithy.test", "TestShape", "TestMember") + XCTAssertEqual(subject.description, "smithy.test#TestShape$TestMember") + } +} 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 001aa70b9..278497c00 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 @@ -191,12 +191,14 @@ abstract class HTTPBindingProtocolGenerator( } override fun generateDeserializers(ctx: ProtocolGenerator.GenerationContext) { + if (usesSchemaBasedSerialization(ctx)) { return } // temporary condition val httpOperations = getHttpBindingOperations(ctx) val httpBindingResolver = getProtocolHttpBindingResolver(ctx, defaultContentType) httpResponseGenerator.render(ctx, httpOperations, httpBindingResolver) } override fun generateCodableConformanceForNestedTypes(ctx: ProtocolGenerator.GenerationContext) { + if (usesSchemaBasedSerialization(ctx)) { return } // temporary condition val nestedShapes = resolveShapesNeedingCodableConformance(ctx) .filter { !it.isEventStreaming } 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 0f1b98533..234f8f59f 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 @@ -2,6 +2,7 @@ package software.amazon.smithy.swift.codegen.integration.serde.schema import software.amazon.smithy.model.shapes.MemberShape import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.swift.codegen.SwiftWriter import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTypes @@ -26,51 +27,50 @@ class SchemaGenerator( private fun renderSchemaStruct(shape: Shape, index: Int? = null) { writer.openBlock(".init(", "),") { - writer.write("id: \$S,", shape.id.toString()) + writer.write( + "id: \$L,", + shapeID(shape.id), + ) writer.write("type: .\$L,", shape.type) val relevantTraits = shape.allTraits.filter { permittedTraitIDs.contains(it.key.toString()) } if (relevantTraits.isNotEmpty()) { writer.openBlock("traits: [", "],") { relevantTraits.forEach { trait -> writer.write( - "\$S: \$L,", - trait.key.toString(), + "\$L: \$L,", + shapeID(trait.key), trait.value.toNode().toSwiftNode(writer), ) } - writer.unwrite(",\n") - writer.write("") } } if (shape.members().isNotEmpty()) { writer.openBlock("members: [", "],") { shape.members().withIndex().forEach { renderSchemaStruct(it.value, it.index) } - writer.unwrite(",\n") - writer.write("") } } - shape.id.member - .getOrNull() - ?.let { writer.write("memberName: \$S,", it) } - targetShape(shape)?.let { writer.write("target: \$L,", it.schemaVar(writer)) } - index?.let { writer.write("index: \$L,", it) } + targetShape(shape)?.let { + writer.write("target: \$L,", it.schemaVar(writer)) + } + index?.let { + writer.write("index: \$L,", it) + } writer.unwrite(",\n") writer.write("") } } - private fun targetShape(shape: Shape): Shape? = memberShape(shape)?.let { ctx.model.expectShape(it.target) } + private fun shapeID(id: ShapeId): String = + writer.format( + ".init(\$S, \$S\$L)", + id.namespace, + id.name, + id.member.getOrNull()?.let { writer.format(", \$S", it) } ?: "", + ) - private fun memberShape(shape: Shape): MemberShape? = shape.asMemberShape().getOrNull() -} + private fun targetShape(shape: Shape): Shape? = + memberShape(shape)?.let { ctx.model.expectShape(it.target) } -private val permittedTraitIDs: Set = - setOf( - "smithy.api#sparse", - "smithy.api#enumValue", - "smithy.api#jsonName", - "smithy.api#required", - "smithy.api#default", - "smithy.api#timestampFormat", - "smithy.api#httpPayload", - ) + private fun memberShape(shape: Shape): MemberShape? = + shape.asMemberShape().getOrNull() +} 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 104f5a0f5..ac2f7b2bb 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 @@ -13,29 +13,31 @@ fun Shape.schemaVar(writer: SwiftWriter): String = this.id.schemaVarName() } -private fun ShapeId.preludeSchemaVarName(writer: SwiftWriter): String = - when (this.name) { - "Unit" -> writer.format("\$N", SmithyTypes.unitSchema) - "String" -> writer.format("\$N", SmithyTypes.stringSchema) - "Blob" -> writer.format("\$N", SmithyTypes.blobSchema) - "Integer" -> writer.format("\$N", SmithyTypes.integerSchema) - "Timestamp" -> writer.format("\$N", SmithyTypes.timestampSchema) - "Boolean" -> writer.format("\$N", SmithyTypes.booleanSchema) - "Float" -> writer.format("\$N", SmithyTypes.floatSchema) - "Double" -> writer.format("\$N", SmithyTypes.doubleSchema) - "Long" -> writer.format("\$N", SmithyTypes.longSchema) - "Short" -> writer.format("\$N", SmithyTypes.shortSchema) - "Byte" -> writer.format("\$N", SmithyTypes.byteSchema) - "PrimitiveInteger" -> writer.format("\$N", SmithyTypes.primitiveIntegerSchema) - "PrimitiveBoolean" -> writer.format("\$N", SmithyTypes.primitiveBooleanSchema) - "PrimitiveFloat" -> writer.format("\$N", SmithyTypes.primitiveFloatSchema) - "PrimitiveDouble" -> writer.format("\$N", SmithyTypes.primitiveDoubleSchema) - "PrimitiveLong" -> writer.format("\$N", SmithyTypes.primitiveLongSchema) - "PrimitiveShort" -> writer.format("\$N", SmithyTypes.primitiveShortSchema) - "PrimitiveByte" -> writer.format("\$N", SmithyTypes.primitiveByteSchema) - "Document" -> writer.format("\$N", SmithyTypes.documentSchema) +private fun ShapeId.preludeSchemaVarName(writer: SwiftWriter): String { + val propertyName = when (this.name) { + "Unit" -> "unitSchema" + "String" -> "stringSchema" + "Blob" -> "blobSchema" + "Integer" -> "integerSchema" + "Timestamp" -> "timestampSchema" + "Boolean" -> "booleanSchema" + "Float" -> "floatSchema" + "Double" -> "doubleSchema" + "Long" -> "longSchema" + "Short" -> "shortSchema" + "Byte" -> "byteSchema" + "PrimitiveInteger" -> "primitiveIntegerSchema" + "PrimitiveBoolean" -> "primitiveBooleanSchema" + "PrimitiveFloat" -> "primitiveFloatSchema" + "PrimitiveDouble" -> "primitiveDoubleSchema" + "PrimitiveLong" -> "primitiveLongSchema" + "PrimitiveShort" -> "primitiveShortSchema" + "PrimitiveByte" -> "primitiveByteSchema" + "Document" -> "documentSchema" else -> throw Exception("Unhandled prelude type converted to schemaVar: ${this.name}") } + return writer.format("\$N.\$L", SmithyTypes.Prelude, propertyName) +} private fun ShapeId.schemaVarName(): String { assert(this.member.getOrNull() == null) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaTraits.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaTraits.kt new file mode 100644 index 000000000..91a446af6 --- /dev/null +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/schema/SchemaTraits.kt @@ -0,0 +1,12 @@ +package software.amazon.smithy.swift.codegen.integration.serde.schema + +val permittedTraitIDs: Set = + setOf( + "smithy.api#sparse", + "smithy.api#enumValue", + "smithy.api#jsonName", + "smithy.api#required", + "smithy.api#default", + "smithy.api#timestampFormat", + "smithy.api#httpPayload", + ) 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 d9641d472..560632fdb 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 @@ -22,25 +22,7 @@ object SmithyTypes { val RequestMessageSerializer = runtimeSymbol("RequestMessageSerializer", SwiftDeclaration.PROTOCOL) val URIQueryItem = runtimeSymbol("URIQueryItem", SwiftDeclaration.STRUCT) val Schema = runtimeSymbol("Schema", SwiftDeclaration.CLASS) - 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) + val Prelude = runtimeSymbol("Prelude", SwiftDeclaration.ENUM) } private fun runtimeSymbol( From 212120efe53751192510aa81569690069b7e839c Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Wed, 5 Nov 2025 10:17:42 -0600 Subject: [PATCH 03/13] Re-enable serde code --- .../amazon/smithy/swift/codegen/DirectedSwiftCodegen.kt | 2 -- .../codegen/integration/HTTPBindingProtocolGenerator.kt | 8 ++++---- 2 files changed, 4 insertions(+), 6 deletions(-) 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 4123503ba..94a56b8bc 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 @@ -72,7 +72,6 @@ class DirectedSwiftCodegen( LOGGER.info("Generating Swift client for service ${directive.settings().service}") - var shouldGenerateTestTarget = false context.protocolGenerator?.apply { val ctx = ProtocolGenerator.GenerationContext(settings, model, service, symbolProvider, integrations, this.protocol, writers) LOGGER.info("[${service.id}] Generating serde for protocol ${this.protocol}") @@ -87,7 +86,6 @@ class DirectedSwiftCodegen( LOGGER.info("[${service.id}] Generating unit tests for protocol ${this.protocol}") val numProtocolUnitTestsGenerated = generateProtocolUnitTests(ctx) - shouldGenerateTestTarget = (numProtocolUnitTestsGenerated > 0) LOGGER.info("[${service.id}] Generated $numProtocolUnitTestsGenerated tests for protocol ${this.protocol}") 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 278497c00..edce23ef9 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 @@ -142,7 +142,7 @@ abstract class HTTPBindingProtocolGenerator( override var serviceErrorProtocolSymbol: Symbol = ClientRuntimeTypes.Http.HttpError override fun generateSerializers(ctx: ProtocolGenerator.GenerationContext) { - if (usesSchemaBasedSerialization(ctx)) { return } // temporary condition +// if (usesSchemaBasedSerialization(ctx)) { return } // temporary condition // render conformance to HttpRequestBinding for all input shapes val inputShapesWithHttpBindings: MutableSet = mutableSetOf() for (operation in getHttpBindingOperations(ctx)) { @@ -191,14 +191,14 @@ abstract class HTTPBindingProtocolGenerator( } override fun generateDeserializers(ctx: ProtocolGenerator.GenerationContext) { - if (usesSchemaBasedSerialization(ctx)) { return } // temporary condition +// if (usesSchemaBasedSerialization(ctx)) { return } // temporary condition val httpOperations = getHttpBindingOperations(ctx) val httpBindingResolver = getProtocolHttpBindingResolver(ctx, defaultContentType) httpResponseGenerator.render(ctx, httpOperations, httpBindingResolver) } override fun generateCodableConformanceForNestedTypes(ctx: ProtocolGenerator.GenerationContext) { - if (usesSchemaBasedSerialization(ctx)) { return } // temporary condition +// if (usesSchemaBasedSerialization(ctx)) { return } // temporary condition val nestedShapes = resolveShapesNeedingCodableConformance(ctx) .filter { !it.isEventStreaming } @@ -240,7 +240,7 @@ abstract class HTTPBindingProtocolGenerator( ctx: ProtocolGenerator.GenerationContext, shape: Shape, ) { - if (!usesSchemaBasedSerialization(ctx)) { return } // temporary condition +// if (!usesSchemaBasedSerialization(ctx)) { return } // temporary condition if (!shape.hasTrait() && !shape.hasTrait()) { return } From fcdf1bf6822c445ac7b15e2ee7c4bc29627c7ba1 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Wed, 5 Nov 2025 11:12:14 -0600 Subject: [PATCH 04/13] Cleanup --- Sources/Smithy/Schema/Schema.swift | 12 ++--- Sources/Smithy/Schema/ShapeID.swift | 6 +-- Sources/Smithy/Schema/ShapeType.swift | 2 +- .../HTTPBindingProtocolGenerator.kt | 31 ++++++------ .../serde/schema/SchemaGenerator.kt | 11 +++-- .../serde/schema/SchemaShapeUtils.kt | 47 ++++++++++--------- .../swiftmodules/SmithyReadWriteTypes.kt | 5 -- 7 files changed, 57 insertions(+), 57 deletions(-) diff --git a/Sources/Smithy/Schema/Schema.swift b/Sources/Smithy/Schema/Schema.swift index ff94fd859..f8cac05fb 100644 --- a/Sources/Smithy/Schema/Schema.swift +++ b/Sources/Smithy/Schema/Schema.swift @@ -5,24 +5,24 @@ // SPDX-License-Identifier: Apache-2.0 // -/// A structure which describes selected Smithy model information for a Smithy model shape. +/// A class which describes selected Smithy model information for a Smithy model shape. /// /// Typically, the Schema contains only modeled info & properties that are relevant to /// serialization, transport bindings, and other functions performed by the SDK. -public class Schema { +public final class Schema: Sendable { /// The Smithy shape ID for the shape described by this schema. public let id: ShapeID - + /// The type of the shape being described. public let type: ShapeType - + /// A dictionary of the described shape's trait shape IDs to Nodes with trait data. /// /// Not all traits for a shape will be represented in the schema; /// typically the Schema contains only the traits relevant to the client-side SDK. public let traits: [ShapeID: Node] - + /// The member schemas for this schema, if any. /// /// Typically only a schema of type Structure, Union, Enum, IntEnum, List or Map will have members. @@ -39,7 +39,7 @@ public class Schema { /// This index is intended for use as a performance enhancement when looking up member schemas /// during deserialization. public let index: Int - + /// Creates a new Schema using the passed parameters. /// /// No validation is performed on the parameters since calls to this initializer diff --git a/Sources/Smithy/Schema/ShapeID.swift b/Sources/Smithy/Schema/ShapeID.swift index b78a4d3bb..647bbfeb8 100644 --- a/Sources/Smithy/Schema/ShapeID.swift +++ b/Sources/Smithy/Schema/ShapeID.swift @@ -8,11 +8,11 @@ /// Represents a single Smithy shape ID. /// /// Shape ID is described in the Smithy 2.0 spec [here](https://smithy.io/2.0/spec/model.html#shape-id). -public struct ShapeID: Hashable { +public struct ShapeID: Sendable, Hashable { public let namespace: String public let name: String public let member: String? - + /// Creates a Shape ID for a Smithy shape. /// /// This initializer does no validation of length or of allowed characters in the Shape ID; @@ -30,7 +30,7 @@ public struct ShapeID: Hashable { } extension ShapeID: CustomStringConvertible { - + /// Returns the absolute Shape ID in a single, printable string. public var description: String { if let member = self.member { diff --git a/Sources/Smithy/Schema/ShapeType.swift b/Sources/Smithy/Schema/ShapeType.swift index 310a24fe8..43a97cc5b 100644 --- a/Sources/Smithy/Schema/ShapeType.swift +++ b/Sources/Smithy/Schema/ShapeType.swift @@ -6,7 +6,7 @@ // /// 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 ShapeType: Sendable { case blob case boolean case string 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 edce23ef9..cfd98792a 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 @@ -142,7 +142,7 @@ abstract class HTTPBindingProtocolGenerator( override var serviceErrorProtocolSymbol: Symbol = ClientRuntimeTypes.Http.HttpError override fun generateSerializers(ctx: ProtocolGenerator.GenerationContext) { -// if (usesSchemaBasedSerialization(ctx)) { return } // temporary condition +// if (usesSchemaBasedSerialization(ctx)) return // temporary condition // render conformance to HttpRequestBinding for all input shapes val inputShapesWithHttpBindings: MutableSet = mutableSetOf() for (operation in getHttpBindingOperations(ctx)) { @@ -191,14 +191,14 @@ abstract class HTTPBindingProtocolGenerator( } override fun generateDeserializers(ctx: ProtocolGenerator.GenerationContext) { -// if (usesSchemaBasedSerialization(ctx)) { return } // temporary condition +// if (usesSchemaBasedSerialization(ctx)) return // temporary condition val httpOperations = getHttpBindingOperations(ctx) val httpBindingResolver = getProtocolHttpBindingResolver(ctx, defaultContentType) httpResponseGenerator.render(ctx, httpOperations, httpBindingResolver) } override fun generateCodableConformanceForNestedTypes(ctx: ProtocolGenerator.GenerationContext) { -// if (usesSchemaBasedSerialization(ctx)) { return } // temporary condition +// if (usesSchemaBasedSerialization(ctx)) return // temporary condition val nestedShapes = resolveShapesNeedingCodableConformance(ctx) .filter { !it.isEventStreaming } @@ -209,12 +209,14 @@ abstract class HTTPBindingProtocolGenerator( private fun usesSchemaBasedSerialization(ctx: ProtocolGenerator.GenerationContext): Boolean = // This fun is temporary; it will be eliminated when all services/protocols are moved to schema-based - ctx.service.allTraits.keys.any { it.name == "rpcv2Cbor" || it.name == "awsJson1_0" || it.name == "awsJson1_1" } + ctx.service.allTraits.keys + .any { it.name == "rpcv2Cbor" } override fun generateSchemas(ctx: ProtocolGenerator.GenerationContext) { - if (!usesSchemaBasedSerialization(ctx)) { return } // temporary condition - val nestedShapes = resolveShapesNeedingSchema(ctx) - .filter { it.type != ShapeType.MEMBER } // Member schemas are only rendered in-line + if (!usesSchemaBasedSerialization(ctx)) return // temporary condition + val nestedShapes = + resolveShapesNeedingSchema(ctx) + .filter { it.type != ShapeType.MEMBER } // Member schemas are only rendered in-line nestedShapes.forEach { renderSchemas(ctx, it) } } @@ -240,7 +242,7 @@ abstract class HTTPBindingProtocolGenerator( ctx: ProtocolGenerator.GenerationContext, shape: Shape, ) { -// if (!usesSchemaBasedSerialization(ctx)) { return } // temporary condition +// if (!usesSchemaBasedSerialization(ctx)) return // temporary condition if (!shape.hasTrait() && !shape.hasTrait()) { return } @@ -400,12 +402,13 @@ abstract class HTTPBindingProtocolGenerator( } private fun resolveShapesNeedingSchema(ctx: ProtocolGenerator.GenerationContext): Set { - val topLevelInputMembers = getHttpBindingOperations(ctx).flatMap { - val inputShape = ctx.model.expectShape(it.input.get()) - inputShape.members() - } - .map { ctx.model.expectShape(it.target) } - .toSet() + val topLevelInputMembers = + getHttpBindingOperations(ctx) + .flatMap { + val inputShape = ctx.model.expectShape(it.input.get()) + inputShape.members() + }.map { ctx.model.expectShape(it.target) } + .toSet() val topLevelOutputMembers = getHttpBindingOperations(ctx) 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 234f8f59f..dd666794b 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 @@ -25,7 +25,10 @@ class SchemaGenerator( } } - private fun renderSchemaStruct(shape: Shape, index: Int? = null) { + private fun renderSchemaStruct( + shape: Shape, + index: Int? = null, + ) { writer.openBlock(".init(", "),") { writer.write( "id: \$L,", @@ -68,9 +71,7 @@ class SchemaGenerator( id.member.getOrNull()?.let { writer.format(", \$S", it) } ?: "", ) - private fun targetShape(shape: Shape): Shape? = - memberShape(shape)?.let { ctx.model.expectShape(it.target) } + private fun targetShape(shape: Shape): Shape? = memberShape(shape)?.let { ctx.model.expectShape(it.target) } - private fun memberShape(shape: Shape): MemberShape? = - shape.asMemberShape().getOrNull() + private fun memberShape(shape: Shape): MemberShape? = shape.asMemberShape().getOrNull() } 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 ac2f7b2bb..74af64df7 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 @@ -14,28 +14,29 @@ fun Shape.schemaVar(writer: SwiftWriter): String = } private fun ShapeId.preludeSchemaVarName(writer: SwiftWriter): String { - val propertyName = when (this.name) { - "Unit" -> "unitSchema" - "String" -> "stringSchema" - "Blob" -> "blobSchema" - "Integer" -> "integerSchema" - "Timestamp" -> "timestampSchema" - "Boolean" -> "booleanSchema" - "Float" -> "floatSchema" - "Double" -> "doubleSchema" - "Long" -> "longSchema" - "Short" -> "shortSchema" - "Byte" -> "byteSchema" - "PrimitiveInteger" -> "primitiveIntegerSchema" - "PrimitiveBoolean" -> "primitiveBooleanSchema" - "PrimitiveFloat" -> "primitiveFloatSchema" - "PrimitiveDouble" -> "primitiveDoubleSchema" - "PrimitiveLong" -> "primitiveLongSchema" - "PrimitiveShort" -> "primitiveShortSchema" - "PrimitiveByte" -> "primitiveByteSchema" - "Document" -> "documentSchema" - else -> throw Exception("Unhandled prelude type converted to schemaVar: ${this.name}") - } + val propertyName = + when (this.name) { + "Unit" -> "unitSchema" + "String" -> "stringSchema" + "Blob" -> "blobSchema" + "Integer" -> "integerSchema" + "Timestamp" -> "timestampSchema" + "Boolean" -> "booleanSchema" + "Float" -> "floatSchema" + "Double" -> "doubleSchema" + "Long" -> "longSchema" + "Short" -> "shortSchema" + "Byte" -> "byteSchema" + "PrimitiveInteger" -> "primitiveIntegerSchema" + "PrimitiveBoolean" -> "primitiveBooleanSchema" + "PrimitiveFloat" -> "primitiveFloatSchema" + "PrimitiveDouble" -> "primitiveDoubleSchema" + "PrimitiveLong" -> "primitiveLongSchema" + "PrimitiveShort" -> "primitiveShortSchema" + "PrimitiveByte" -> "primitiveByteSchema" + "Document" -> "documentSchema" + else -> throw Exception("Unhandled prelude type converted to schemaVar: ${this.name}") + } return writer.format("\$N.\$L", SmithyTypes.Prelude, propertyName) } @@ -43,5 +44,5 @@ private fun ShapeId.schemaVarName(): String { assert(this.member.getOrNull() == null) val namespacePortion = this.namespace.replace(".", "_") val namePortion = this.name - return "schema__${namespacePortion}__${namePortion}" + return "schema__${namespacePortion}__$namePortion" } 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 d383db5a1..070e26034 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,11 +27,6 @@ object SmithyReadWriteTypes { val WritingClosures = runtimeSymbol("WritingClosures", SwiftDeclaration.ENUM) val ReadingClosureBox = runtimeSymbol("ReadingClosureBox", SwiftDeclaration.STRUCT) val WritingClosureBox = runtimeSymbol("WritingClosureBox", SwiftDeclaration.STRUCT) - val ShapeSerializer = runtimeSymbol("ShapeSerializer", SwiftDeclaration.PROTOCOL) - val ShapeDeserializer = runtimeSymbol("ShapeDeserializer", SwiftDeclaration.PROTOCOL) - val SerializableStruct = runtimeSymbol("SerializableStruct", SwiftDeclaration.PROTOCOL) - val DeserializableStruct = runtimeSymbol("DeserializableStruct", SwiftDeclaration.PROTOCOL) - val Unit = runtimeSymbol("Unit", SwiftDeclaration.STRUCT) } private fun runtimeSymbol( From aa823be48321468663fd3ba689ad2ec6484772cc Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Sun, 9 Nov 2025 10:53:04 -0600 Subject: [PATCH 05/13] feat: Add Swift-native codegen plugin --- Package.swift | 19 ++++++ .../SmithyCodeGeneratorPlugin.swift | 59 +++++++++++++++++++ .../SmithyCodegenCLI/SmithyCodegenCLI.swift | 41 +++++++++++++ Sources/SmithyCodegenCore/SmithyModel.swift | 18 ++++++ .../swift/codegen/DirectedSwiftCodegen.kt | 3 + .../codegen/SmithyModelFileInfoGenerator.kt | 19 ++++++ .../smithy/swift/codegen/SwiftWriter.kt | 2 +- .../HTTPBindingProtocolGenerator.kt | 5 +- 8 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift create mode 100644 Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift create mode 100644 Sources/SmithyCodegenCore/SmithyModel.swift create mode 100644 smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt diff --git a/Package.swift b/Package.swift index 940b1e6bf..03e338843 100644 --- a/Package.swift +++ b/Package.swift @@ -53,10 +53,12 @@ let package = Package( .library(name: "SmithyCBOR", targets: ["SmithyCBOR"]), .library(name: "SmithyWaitersAPI", targets: ["SmithyWaitersAPI"]), .library(name: "SmithyTestUtil", targets: ["SmithyTestUtil"]), + .plugin(name: "SmithyCodeGenerator", targets: ["SmithyCodeGenerator"]), ], dependencies: { var dependencies: [Package.Dependency] = [ .package(url: "https://github.com/awslabs/aws-crt-swift.git", exact: "0.54.2"), + .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.0.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), .package(url: "https://github.com/open-telemetry/opentelemetry-swift", from: "1.13.0"), ] @@ -258,6 +260,23 @@ let package = Package( .target( name: "SmithyWaitersAPI" ), + .plugin( + name: "SmithyCodeGenerator", + capability: .buildTool(), + dependencies: [ + "SmithyCodegenCLI", + ] + ), + .executableTarget( + name: "SmithyCodegenCLI", + dependencies: [ + "SmithyCodegenCore", + .product(name: "ArgumentParser", package: "swift-argument-parser"), + ] + ), + .target( + name: "SmithyCodegenCore" + ), .testTarget( name: "ClientRuntimeTests", dependencies: [ diff --git a/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift b/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift new file mode 100644 index 000000000..dd19b6e2f --- /dev/null +++ b/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift @@ -0,0 +1,59 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Foundation +import PackagePlugin + +@main +struct SmithyCodeGeneratorPlugin: BuildToolPlugin { + + func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { + // This plugin only runs for package targets that can have source files. + guard let sourceFiles = target.sourceModule?.sourceFiles else { return [] } + + // Retrieve the `SmithySchemaCodegenTool` from the plugin's tools. + let generatorTool = try context.tool(named: "SmithyCodegenCLI") + + // Construct a build command for each source file with a particular suffix. + return try sourceFiles.map(\.path).compactMap { + try createBuildCommand(for: $0, in: context.pluginWorkDirectory, with: generatorTool.path) + } + } + + private func createBuildCommand(for inputPath: Path, in outputDirectoryPath: Path, with generatorToolPath: Path) throws -> Command? { + // Skip any file that isn't the model.json for this service. + guard inputPath.lastComponent == "smithy-model-file-info.txt" else { return nil } + + // Get the smithy model path. + let locationData = try Data(contentsOf: URL(filePath: inputPath.string)) + guard let location = String(data: locationData, encoding: .utf8) else { + throw SmithySchemaGeneratorPluginError("smithy-model-file-info.txt did not contain valid UTF-8") + } + let modelPathURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + .appendingPathComponent(location.trimmingCharacters(in: .whitespacesAndNewlines)) + let modelPath = Path(modelPathURL.path) + + // Return a command that will run during the build to generate the output file. + let inputName = inputPath.lastComponent + let outputPath = outputDirectoryPath.appending("Schemas.swift") + return .buildCommand( + displayName: "Generating Schemas.swift from \(inputName)", + executable: generatorToolPath, + arguments: [modelPath, outputPath], + inputFiles: [inputPath, modelPath], + outputFiles: [outputPath] + ) + } +} + +struct SmithySchemaGeneratorPluginError: Error { + let localizedDescription: String + + init(_ localizedDescription: String) { + self.localizedDescription = localizedDescription + } +} diff --git a/Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift b/Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift new file mode 100644 index 000000000..d4dd7f0b6 --- /dev/null +++ b/Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift @@ -0,0 +1,41 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import ArgumentParser +import Foundation +import SmithyCodegenCore + +@main +struct SmithyCodegenCLI: AsyncParsableCommand { + + @Argument(help: "The full path to the JSON model file.") + var modelPath: String + + @Argument(help: "The full path to write the output file. Intermediate directories will be created as needed.") + var outputPath: String + + func run() async throws { + guard FileManager.default.fileExists(atPath: modelPath) else { + throw SmithySchemaCodegenToolError(localizedDescription: "no file at model path") + } + let model = try SmithyModel(modelFileURL: URL(fileURLWithPath: modelPath)) + + let outputFileURL = URL(fileURLWithPath: outputPath) + let contents = """ + // My Awesome File + + /// The count of bytes in the model. + public let modelCount = \(model.count) + """ + try FileManager.default.createDirectory(at: outputFileURL.deletingLastPathComponent(), withIntermediateDirectories: true) + try Data(contents.utf8).write(to: outputFileURL) + } +} + +struct SmithySchemaCodegenToolError: Error { + let localizedDescription: String +} diff --git a/Sources/SmithyCodegenCore/SmithyModel.swift b/Sources/SmithyCodegenCore/SmithyModel.swift new file mode 100644 index 000000000..f5dbc7b4a --- /dev/null +++ b/Sources/SmithyCodegenCore/SmithyModel.swift @@ -0,0 +1,18 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Foundation.Data +import struct Foundation.URL + +public struct SmithyModel { + public let count: Int + + public init(modelFileURL: URL) throws { + let modelData = try Data(contentsOf: modelFileURL) + self.count = modelData.count + } +} 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 94a56b8bc..154348e74 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 @@ -107,6 +107,9 @@ class DirectedSwiftCodegen( DependencyJSONGenerator(ctx).writePackageJSON(writers.dependencies) } + LOGGER.info("Generating Smithy model file info") + SmithyModelFileInfoGenerator(ctx).writeSmithyModelFileInfo() + LOGGER.info("Flushing swift writers") writers.flushWriters() } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt new file mode 100644 index 000000000..e8b588349 --- /dev/null +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt @@ -0,0 +1,19 @@ +package software.amazon.smithy.swift.codegen + +import software.amazon.smithy.aws.traits.ServiceTrait +import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator +import software.amazon.smithy.swift.codegen.model.expectTrait + +class SmithyModelFileInfoGenerator( + val ctx: ProtocolGenerator.GenerationContext, +) { + fun writeSmithyModelFileInfo() { + val model = ctx.service.expectTrait().sdkId + .replace(",", "") + .lowercase() + .replace(" ", "-") + ctx.delegator.useFileWriter("Sources/${ctx.settings.moduleName}/smithy-model-file-info.txt") { writer -> + writer.write("codegen/sdk-codegen/aws-models/${model}.json") + } + } +} \ No newline at end of file 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 49c989f82..e6520e37a 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 @@ -175,7 +175,7 @@ class SwiftWriter( // Also leave out the headers when JSON or the version file is being written, // as indicated by the file extension. val isPackageManifest = fullPackageName == "Package" - val isNonSwiftSourceFile = listOf(".json", ".version").any { fullPackageName.endsWith(it) } + val isNonSwiftSourceFile = listOf(".json", ".version", ".txt").any { fullPackageName.endsWith(it) } val noHeader = isPackageManifest || isNonSwiftSourceFile return contents.takeIf { noHeader } ?: (copyrightNotice + imports + contents) } 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 cfd98792a..9c940f40b 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 @@ -209,8 +209,9 @@ abstract class HTTPBindingProtocolGenerator( private fun usesSchemaBasedSerialization(ctx: ProtocolGenerator.GenerationContext): Boolean = // This fun is temporary; it will be eliminated when all services/protocols are moved to schema-based - ctx.service.allTraits.keys - .any { it.name == "rpcv2Cbor" } + false +// ctx.service.allTraits.keys +// .any { it.name == "rpcv2Cbor" } override fun generateSchemas(ctx: ProtocolGenerator.GenerationContext) { if (!usesSchemaBasedSerialization(ctx)) return // temporary condition From e817d55c1c4c4f956e4c89995281a162c115a5c5 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Sun, 9 Nov 2025 11:09:48 -0600 Subject: [PATCH 06/13] Fix lint --- .../SmithyCodeGeneratorPlugin.swift | 6 +++++- Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift | 7 +++++-- .../swift/codegen/SmithyModelFileInfoGenerator.kt | 15 +++++++++------ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift b/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift index dd19b6e2f..5d51bf3a3 100644 --- a/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift +++ b/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift @@ -24,7 +24,11 @@ struct SmithyCodeGeneratorPlugin: BuildToolPlugin { } } - private func createBuildCommand(for inputPath: Path, in outputDirectoryPath: Path, with generatorToolPath: Path) throws -> Command? { + private func createBuildCommand( + for inputPath: Path, + in outputDirectoryPath: Path, + with generatorToolPath: Path + ) throws -> Command? { // Skip any file that isn't the model.json for this service. guard inputPath.lastComponent == "smithy-model-file-info.txt" else { return nil } diff --git a/Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift b/Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift index d4dd7f0b6..f965c8ad4 100644 --- a/Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift +++ b/Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift @@ -27,11 +27,14 @@ struct SmithyCodegenCLI: AsyncParsableCommand { let outputFileURL = URL(fileURLWithPath: outputPath) let contents = """ // My Awesome File - + /// The count of bytes in the model. public let modelCount = \(model.count) """ - try FileManager.default.createDirectory(at: outputFileURL.deletingLastPathComponent(), withIntermediateDirectories: true) + try FileManager.default.createDirectory( + at: outputFileURL.deletingLastPathComponent(), + withIntermediateDirectories: true + ) try Data(contents.utf8).write(to: outputFileURL) } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt index e8b588349..65533e24e 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt @@ -8,12 +8,15 @@ class SmithyModelFileInfoGenerator( val ctx: ProtocolGenerator.GenerationContext, ) { fun writeSmithyModelFileInfo() { - val model = ctx.service.expectTrait().sdkId - .replace(",", "") - .lowercase() - .replace(" ", "-") + val model = + ctx.service + .expectTrait() + .sdkId + .replace(",", "") + .lowercase() + .replace(" ", "-") ctx.delegator.useFileWriter("Sources/${ctx.settings.moduleName}/smithy-model-file-info.txt") { writer -> - writer.write("codegen/sdk-codegen/aws-models/${model}.json") + writer.write("codegen/sdk-codegen/aws-models/$model.json") } } -} \ No newline at end of file +} From 6a9c537af096532f9e70f1b3a5845cf6a5db26ee Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Sun, 9 Nov 2025 11:29:43 -0600 Subject: [PATCH 07/13] Make service trait optional --- .../codegen/SmithyModelFileInfoGenerator.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt index 65533e24e..8e1a14f25 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt @@ -2,21 +2,23 @@ package software.amazon.smithy.swift.codegen import software.amazon.smithy.aws.traits.ServiceTrait import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator -import software.amazon.smithy.swift.codegen.model.expectTrait +import software.amazon.smithy.swift.codegen.model.getTrait class SmithyModelFileInfoGenerator( val ctx: ProtocolGenerator.GenerationContext, ) { fun writeSmithyModelFileInfo() { - val model = - ctx.service - .expectTrait() + ctx.service.getTrait()?.let { serviceTrait -> + val filename = "Sources/${ctx.settings.moduleName}/smithy-model-file-info.txt" + val modelFileName = serviceTrait .sdkId - .replace(",", "") .lowercase() + .replace(",", "") .replace(" ", "-") - ctx.delegator.useFileWriter("Sources/${ctx.settings.moduleName}/smithy-model-file-info.txt") { writer -> - writer.write("codegen/sdk-codegen/aws-models/$model.json") + val contents = "codegen/sdk-codegen/aws-models/$modelFileName.json" + ctx.delegator.useFileWriter(filename) { writer -> + writer.write(contents) + } } } } From 33cc093b8eb2f0a9ed6177d77d7cc99500634819 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Sun, 9 Nov 2025 11:34:09 -0600 Subject: [PATCH 08/13] Fix ktlint again --- .../swift/codegen/SmithyModelFileInfoGenerator.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt index 8e1a14f25..ae02c4ff4 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt @@ -10,11 +10,12 @@ class SmithyModelFileInfoGenerator( fun writeSmithyModelFileInfo() { ctx.service.getTrait()?.let { serviceTrait -> val filename = "Sources/${ctx.settings.moduleName}/smithy-model-file-info.txt" - val modelFileName = serviceTrait - .sdkId - .lowercase() - .replace(",", "") - .replace(" ", "-") + val modelFileName = + serviceTrait + .sdkId + .lowercase() + .replace(",", "") + .replace(" ", "-") val contents = "codegen/sdk-codegen/aws-models/$modelFileName.json" ctx.delegator.useFileWriter(filename) { writer -> writer.write(contents) From 6b545756f277b9471f4d34e79758d455b57a282c Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Sun, 9 Nov 2025 11:44:12 -0600 Subject: [PATCH 09/13] Refactor code generator plugin --- .../SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift b/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift index 5d51bf3a3..616dffbb0 100644 --- a/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift +++ b/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift @@ -34,11 +34,12 @@ struct SmithyCodeGeneratorPlugin: BuildToolPlugin { // Get the smithy model path. let locationData = try Data(contentsOf: URL(filePath: inputPath.string)) - guard let location = String(data: locationData, encoding: .utf8) else { + let locationString = String(data: locationData, encoding: .utf8) + guard let location = locationString?.trimmingCharacters(in: .whitespacesAndNewlines) else { throw SmithySchemaGeneratorPluginError("smithy-model-file-info.txt did not contain valid UTF-8") } - let modelPathURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) - .appendingPathComponent(location.trimmingCharacters(in: .whitespacesAndNewlines)) + let currentWorkingDirectoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + let modelPathURL = currentWorkingDirectoryURL.appendingPathComponent(location) let modelPath = Path(modelPathURL.path) // Return a command that will run during the build to generate the output file. From ae474f12c5019617f75fa23e1fc22b91d002fd3b Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Sun, 9 Nov 2025 15:52:23 -0600 Subject: [PATCH 10/13] Convert smithy model info file to .json --- .../SmithyCodeGeneratorPlugin.swift | 26 ++++++++++--------- .../codegen/SmithyModelFileInfoGenerator.kt | 4 +-- .../smithy/swift/codegen/SwiftWriter.kt | 2 +- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift b/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift index 616dffbb0..7e2e9b5b7 100644 --- a/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift +++ b/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift @@ -29,28 +29,26 @@ struct SmithyCodeGeneratorPlugin: BuildToolPlugin { in outputDirectoryPath: Path, with generatorToolPath: Path ) throws -> Command? { - // Skip any file that isn't the model.json for this service. - guard inputPath.lastComponent == "smithy-model-file-info.txt" else { return nil } + let currentWorkingDirectoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + + // Skip any file that isn't the smithy-model-info.json for this service. + guard inputPath.lastComponent == "smithy-model-info.json" else { return nil } // Get the smithy model path. - let locationData = try Data(contentsOf: URL(filePath: inputPath.string)) - let locationString = String(data: locationData, encoding: .utf8) - guard let location = locationString?.trimmingCharacters(in: .whitespacesAndNewlines) else { - throw SmithySchemaGeneratorPluginError("smithy-model-file-info.txt did not contain valid UTF-8") - } - let currentWorkingDirectoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) - let modelPathURL = currentWorkingDirectoryURL.appendingPathComponent(location) + let modelInfoData = try Data(contentsOf: URL(filePath: inputPath.string)) + let smithyModelInfo = try JSONDecoder().decode(SmithyModelInfo.self, from: modelInfoData) + let modelPathURL = currentWorkingDirectoryURL.appendingPathComponent(smithyModelInfo.path) let modelPath = Path(modelPathURL.path) // Return a command that will run during the build to generate the output file. let inputName = inputPath.lastComponent - let outputPath = outputDirectoryPath.appending("Schemas.swift") + let schemasSwiftPath = outputDirectoryPath.appending("Schemas.swift") return .buildCommand( displayName: "Generating Schemas.swift from \(inputName)", executable: generatorToolPath, - arguments: [modelPath, outputPath], + arguments: [modelPath, schemasSwiftPath], inputFiles: [inputPath, modelPath], - outputFiles: [outputPath] + outputFiles: [schemasSwiftPath] ) } } @@ -62,3 +60,7 @@ struct SmithySchemaGeneratorPluginError: Error { self.localizedDescription = localizedDescription } } + +struct SmithyModelInfo: Codable { + let path: String +} diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt index ae02c4ff4..0662d8d7d 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SmithyModelFileInfoGenerator.kt @@ -9,7 +9,7 @@ class SmithyModelFileInfoGenerator( ) { fun writeSmithyModelFileInfo() { ctx.service.getTrait()?.let { serviceTrait -> - val filename = "Sources/${ctx.settings.moduleName}/smithy-model-file-info.txt" + val filename = "Sources/${ctx.settings.moduleName}/smithy-model-info.json" val modelFileName = serviceTrait .sdkId @@ -18,7 +18,7 @@ class SmithyModelFileInfoGenerator( .replace(" ", "-") val contents = "codegen/sdk-codegen/aws-models/$modelFileName.json" ctx.delegator.useFileWriter(filename) { writer -> - writer.write(contents) + writer.write("{\"path\":\"$contents\"}") } } } 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 e6520e37a..49c989f82 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 @@ -175,7 +175,7 @@ class SwiftWriter( // Also leave out the headers when JSON or the version file is being written, // as indicated by the file extension. val isPackageManifest = fullPackageName == "Package" - val isNonSwiftSourceFile = listOf(".json", ".version", ".txt").any { fullPackageName.endsWith(it) } + val isNonSwiftSourceFile = listOf(".json", ".version").any { fullPackageName.endsWith(it) } val noHeader = isPackageManifest || isNonSwiftSourceFile return contents.takeIf { noHeader } ?: (copyrightNotice + imports + contents) } From 70f76690662eb3761fc5f5221b6d2fdf3fc25da4 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Sun, 9 Nov 2025 15:58:10 -0600 Subject: [PATCH 11/13] Fix Swift 5.9 URL initializer --- Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift b/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift index 7e2e9b5b7..4f830d1c7 100644 --- a/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift +++ b/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift @@ -35,7 +35,7 @@ struct SmithyCodeGeneratorPlugin: BuildToolPlugin { guard inputPath.lastComponent == "smithy-model-info.json" else { return nil } // Get the smithy model path. - let modelInfoData = try Data(contentsOf: URL(filePath: inputPath.string)) + let modelInfoData = try Data(contentsOf: URL(fileURLWithPath: inputPath.string)) let smithyModelInfo = try JSONDecoder().decode(SmithyModelInfo.self, from: modelInfoData) let modelPathURL = currentWorkingDirectoryURL.appendingPathComponent(smithyModelInfo.path) let modelPath = Path(modelPathURL.path) From 3d623e6fb7cf32d5ad774982f02f2576dff31fd4 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Sun, 9 Nov 2025 17:24:09 -0600 Subject: [PATCH 12/13] Code cleanup --- .../SmithyCodeGeneratorPlugin.swift | 34 +++++++++---------- .../SmithyCodegenCLI/SmithyCodegenCLI.swift | 9 ++++- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift b/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift index 4f830d1c7..d3e1bede5 100644 --- a/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift +++ b/Plugins/SmithyCodeGenerator/SmithyCodeGeneratorPlugin.swift @@ -5,7 +5,10 @@ // SPDX-License-Identifier: Apache-2.0 // -import Foundation +import struct Foundation.Data +import class Foundation.FileManager +import class Foundation.JSONDecoder +import struct Foundation.URL import PackagePlugin @main @@ -15,12 +18,12 @@ struct SmithyCodeGeneratorPlugin: BuildToolPlugin { // This plugin only runs for package targets that can have source files. guard let sourceFiles = target.sourceModule?.sourceFiles else { return [] } - // Retrieve the `SmithySchemaCodegenTool` from the plugin's tools. - let generatorTool = try context.tool(named: "SmithyCodegenCLI") + // Retrieve the `SmithyCodegenCLI` tool from the plugin's tools. + let smithyCodegenCLITool = try context.tool(named: "SmithyCodegenCLI") // Construct a build command for each source file with a particular suffix. return try sourceFiles.map(\.path).compactMap { - try createBuildCommand(for: $0, in: context.pluginWorkDirectory, with: generatorTool.path) + try createBuildCommand(for: $0, in: context.pluginWorkDirectory, with: smithyCodegenCLITool.path) } } @@ -41,26 +44,23 @@ struct SmithyCodeGeneratorPlugin: BuildToolPlugin { let modelPath = Path(modelPathURL.path) // Return a command that will run during the build to generate the output file. - let inputName = inputPath.lastComponent - let schemasSwiftPath = outputDirectoryPath.appending("Schemas.swift") + let modelCountSwiftPath = outputDirectoryPath.appending("ModelCount.swift") return .buildCommand( - displayName: "Generating Schemas.swift from \(inputName)", + displayName: "Generating Swift source files from \(smithyModelInfo.path)", executable: generatorToolPath, - arguments: [modelPath, schemasSwiftPath], + arguments: [modelPath, modelCountSwiftPath], inputFiles: [inputPath, modelPath], - outputFiles: [schemasSwiftPath] + outputFiles: [modelCountSwiftPath] ) } } -struct SmithySchemaGeneratorPluginError: Error { - let localizedDescription: String - - init(_ localizedDescription: String) { - self.localizedDescription = localizedDescription - } +/// Codable structure for reading the contents of `smithy-model-info.json` +private struct SmithyModelInfo: Decodable { + /// The path to the model, from the root of the target's project. Required. + let path: String } -struct SmithyModelInfo: Codable { - let path: String +struct Err: Error { + var localizedDescription: String { "boom" } } diff --git a/Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift b/Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift index f965c8ad4..4aa65a65d 100644 --- a/Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift +++ b/Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift @@ -26,7 +26,14 @@ struct SmithyCodegenCLI: AsyncParsableCommand { let outputFileURL = URL(fileURLWithPath: outputPath) let contents = """ - // My Awesome File + // + // Copyright Amazon.com Inc. or its affiliates. + // All Rights Reserved. + // + // SPDX-License-Identifier: Apache-2.0 + // + + // Code generated by SmithyCodegenCLI. DO NOT EDIT! /// The count of bytes in the model. public let modelCount = \(model.count) From d535e662bbfc658bcf6bb4f0666cc1e928498fb0 Mon Sep 17 00:00:00 2001 From: Josh Elkins Date: Sun, 9 Nov 2025 17:54:24 -0600 Subject: [PATCH 13/13] Revert disabling of schemas --- .../codegen/integration/HTTPBindingProtocolGenerator.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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 9c940f40b..cfd98792a 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 @@ -209,9 +209,8 @@ abstract class HTTPBindingProtocolGenerator( private fun usesSchemaBasedSerialization(ctx: ProtocolGenerator.GenerationContext): Boolean = // This fun is temporary; it will be eliminated when all services/protocols are moved to schema-based - false -// ctx.service.allTraits.keys -// .any { it.name == "rpcv2Cbor" } + ctx.service.allTraits.keys + .any { it.name == "rpcv2Cbor" } override fun generateSchemas(ctx: ProtocolGenerator.GenerationContext) { if (!usesSchemaBasedSerialization(ctx)) return // temporary condition