diff --git a/Package.swift b/Package.swift index 70d3a7cc8..a6ea3fb89 100644 --- a/Package.swift +++ b/Package.swift @@ -303,9 +303,15 @@ let package = Package( .executableTarget( name: "SmithyCodegenCLI", dependencies: [ + "SmithyCodegenCore", .product(name: "ArgumentParser", package: "swift-argument-parser"), ] ), + .target( + name: "SmithyCodegenCore", + dependencies: ["SmithySerialization"], + resources: [ .process("Resources") ] + ), .testTarget( name: "ClientRuntimeTests", dependencies: [ @@ -393,5 +399,9 @@ let package = Package( name: "SmithyStreamsTests", dependencies: ["SmithyStreams", "Smithy"] ), + .testTarget( + name: "SmithyCodegenCoreTests", + dependencies: ["SmithyCodegenCore"] + ), ].compactMap { $0 } ) diff --git a/Sources/ClientRuntime/Config/DefaultSDKRuntimeConfiguration.swift b/Sources/ClientRuntime/Config/DefaultSDKRuntimeConfiguration.swift index 4868cc7ee..f6dd0d46a 100644 --- a/Sources/ClientRuntime/Config/DefaultSDKRuntimeConfiguration.swift +++ b/Sources/ClientRuntime/Config/DefaultSDKRuntimeConfiguration.swift @@ -13,10 +13,10 @@ import protocol SmithyHTTPAuthAPI.AuthSchemeResolver import protocol SmithyHTTPAuthAPI.AuthSchemeResolverParameters import struct SmithyRetries.DefaultRetryStrategy import struct SmithyRetries.ExponentialBackoffStrategy -import SmithyTelemetryAPI import protocol SmithyRetriesAPI.RetryErrorInfoProvider import protocol SmithyRetriesAPI.RetryStrategy import struct SmithyRetriesAPI.RetryStrategyOptions +import SmithyTelemetryAPI public struct DefaultSDKRuntimeConfiguration { diff --git a/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngine.swift b/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngine.swift index 1b61a9467..8658bb00d 100644 --- a/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngine.swift +++ b/Sources/ClientRuntime/Networking/Http/CRT/CRTClientEngine.swift @@ -19,8 +19,8 @@ import protocol SmithyHTTPAPI.HTTPClient import class SmithyHTTPAPI.HTTPRequest import class SmithyHTTPAPI.HTTPResponse import enum SmithyHTTPAPI.HTTPStatusCode -import class SmithyHTTPClientAPI.HttpTelemetry import enum SmithyHTTPClientAPI.HttpMetricsAttributesKeys +import class SmithyHTTPClientAPI.HttpTelemetry import class SmithyStreams.BufferedStream import SmithyTelemetryAPI #if os(Linux) diff --git a/Sources/ClientRuntime/Networking/Http/CRT/HTTP2Stream+ByteStream.swift b/Sources/ClientRuntime/Networking/Http/CRT/HTTP2Stream+ByteStream.swift index 7d871a63e..492f9cde9 100644 --- a/Sources/ClientRuntime/Networking/Http/CRT/HTTP2Stream+ByteStream.swift +++ b/Sources/ClientRuntime/Networking/Http/CRT/HTTP2Stream+ByteStream.swift @@ -8,8 +8,8 @@ import AwsCommonRuntimeKit import struct Smithy.Attributes import enum Smithy.ByteStream -import class SmithyHTTPClientAPI.HttpTelemetry import enum SmithyHTTPClientAPI.HttpMetricsAttributesKeys +import class SmithyHTTPClientAPI.HttpTelemetry extension HTTP2Stream { /// Returns the recommended size, in bytes, for the data to write diff --git a/Sources/ClientRuntime/Networking/Http/URLSession/FoundationStreamBridge.swift b/Sources/ClientRuntime/Networking/Http/URLSession/FoundationStreamBridge.swift index a5c420e18..54044b683 100644 --- a/Sources/ClientRuntime/Networking/Http/URLSession/FoundationStreamBridge.swift +++ b/Sources/ClientRuntime/Networking/Http/URLSession/FoundationStreamBridge.swift @@ -13,8 +13,6 @@ import class Foundation.DispatchQueue import class Foundation.InputStream import class Foundation.NSObject import class Foundation.OutputStream -import class SmithyHTTPClientAPI.HttpTelemetry -import enum SmithyHTTPClientAPI.HttpMetricsAttributesKeys import class Foundation.RunLoop import class Foundation.Stream import protocol Foundation.StreamDelegate @@ -24,6 +22,8 @@ import class Foundation.Timer import struct Smithy.Attributes import protocol Smithy.LogAgent import protocol Smithy.ReadableStream +import enum SmithyHTTPClientAPI.HttpMetricsAttributesKeys +import class SmithyHTTPClientAPI.HttpTelemetry /// Reads data from a smithy-swift native `ReadableStream` and streams the data through to a Foundation `InputStream`. /// diff --git a/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionHTTPClient.swift b/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionHTTPClient.swift index 2a0c6ea20..aab80dc2a 100644 --- a/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionHTTPClient.swift +++ b/Sources/ClientRuntime/Networking/Http/URLSession/URLSessionHTTPClient.swift @@ -18,8 +18,6 @@ import class Foundation.NSRecursiveLock import var Foundation.NSURLAuthenticationMethodClientCertificate import var Foundation.NSURLAuthenticationMethodServerTrust import struct Foundation.TimeInterval -import class SmithyHTTPClientAPI.HttpTelemetry -import enum SmithyHTTPClientAPI.HttpMetricsAttributesKeys import class Foundation.URLAuthenticationChallenge import struct Foundation.URLComponents import class Foundation.URLCredential @@ -29,7 +27,6 @@ import class Foundation.URLResponse import class Foundation.URLSession import class Foundation.URLSessionConfiguration import protocol Foundation.URLSessionDataDelegate -import SmithyTelemetryAPI import class Foundation.URLSessionDataTask import class Foundation.URLSessionTask import class Foundation.URLSessionTaskMetrics @@ -44,7 +41,10 @@ import protocol SmithyHTTPAPI.HTTPClient import class SmithyHTTPAPI.HTTPRequest import class SmithyHTTPAPI.HTTPResponse import enum SmithyHTTPAPI.HTTPStatusCode +import enum SmithyHTTPClientAPI.HttpMetricsAttributesKeys +import class SmithyHTTPClientAPI.HttpTelemetry import class SmithyStreams.BufferedStream +import SmithyTelemetryAPI /// A client that can be used to make requests to AWS services using `Foundation`'s `URLSession` HTTP client. /// diff --git a/Sources/Smithy/Schema/ShapeType.swift b/Sources/Smithy/ShapeType.swift similarity index 100% rename from Sources/Smithy/Schema/ShapeType.swift rename to Sources/Smithy/ShapeType.swift diff --git a/Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift b/Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift index b8b247155..d4e7c320f 100644 --- a/Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift +++ b/Sources/SmithyCodegenCLI/SmithyCodegenCLI.swift @@ -7,6 +7,7 @@ import ArgumentParser import Foundation +import struct SmithyCodegenCore.CodeGenerator @main struct SmithyCodegenCLI: AsyncParsableCommand { @@ -31,12 +32,8 @@ struct SmithyCodegenCLI: AsyncParsableCommand { // If --schemas-path was supplied, create the schema file URL let schemasFileURL = resolve(paramName: "--schemas-path", path: schemasPath) - // All file URLs needed for code generation have now been resolved. - // Implement code generation here. - if let schemasFileURL { - print("Schemas file path: \(schemasFileURL)") - FileManager.default.createFile(atPath: schemasFileURL.path, contents: Data()) - } + // Use resolved file URLs to run code generator + try CodeGenerator(modelFileURL: modelFileURL, schemasFileURL: schemasFileURL).run() } private func currentWorkingDirectoryFileURL() -> URL { diff --git a/Sources/SmithyCodegenCore/AST/ASTError.swift b/Sources/SmithyCodegenCore/AST/ASTError.swift new file mode 100644 index 000000000..3e32bc097 --- /dev/null +++ b/Sources/SmithyCodegenCore/AST/ASTError.swift @@ -0,0 +1,14 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +public struct ASTError: Error { + public let localizedDescription: String + + init(_ localizedDescription: String) { + self.localizedDescription = localizedDescription + } +} diff --git a/Sources/SmithyCodegenCore/AST/ASTMember.swift b/Sources/SmithyCodegenCore/AST/ASTMember.swift new file mode 100644 index 000000000..f03fefef9 --- /dev/null +++ b/Sources/SmithyCodegenCore/AST/ASTMember.swift @@ -0,0 +1,11 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +struct ASTMember: Decodable { + let target: String + let traits: [String: ASTNode]? +} diff --git a/Sources/SmithyCodegenCore/AST/ASTModel.swift b/Sources/SmithyCodegenCore/AST/ASTModel.swift new file mode 100644 index 000000000..5ff69fb15 --- /dev/null +++ b/Sources/SmithyCodegenCore/AST/ASTModel.swift @@ -0,0 +1,12 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +struct ASTModel: Decodable { + let smithy: String + let metadata: ASTNode? + let shapes: [String: ASTShape] +} diff --git a/Sources/SmithyCodegenCore/AST/ASTNode.swift b/Sources/SmithyCodegenCore/AST/ASTNode.swift new file mode 100644 index 000000000..8d5a61c44 --- /dev/null +++ b/Sources/SmithyCodegenCore/AST/ASTNode.swift @@ -0,0 +1,45 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// Contains the value of a Smithy Node, as used in a JSON AST. +/// +/// 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 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 +enum ASTNode: Sendable { + case object([String: ASTNode]) + case list([ASTNode]) + case string(String) + case number(Double) + case boolean(Bool) + case null +} + +extension ASTNode: Decodable { + + init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + if container.decodeNil() { + self = .null + } else if let bool = try? container.decode(Bool.self) { + self = .boolean(bool) + } else if let int = try? container.decode(Int.self) { + self = .number(Double(int)) + } else if let double = try? container.decode(Double.self) { + self = .number(double) + } else if let string = try? container.decode(String.self) { + self = .string(string) + } else if let array = try? container.decode([ASTNode].self) { + self = .list(array) + } else if let dictionary = try? container.decode([String: ASTNode].self) { + self = .object(dictionary) + } else { + throw ASTError("Undecodable value in AST node") + } + } +} diff --git a/Sources/SmithyCodegenCore/AST/ASTReference.swift b/Sources/SmithyCodegenCore/AST/ASTReference.swift new file mode 100644 index 000000000..6132359df --- /dev/null +++ b/Sources/SmithyCodegenCore/AST/ASTReference.swift @@ -0,0 +1,10 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +struct ASTReference: Decodable { + let target: String +} diff --git a/Sources/SmithyCodegenCore/AST/ASTShape.swift b/Sources/SmithyCodegenCore/AST/ASTShape.swift new file mode 100644 index 000000000..92cb16bd7 --- /dev/null +++ b/Sources/SmithyCodegenCore/AST/ASTShape.swift @@ -0,0 +1,31 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +struct ASTShape: Decodable { + let type: ASTType + let traits: [String: ASTNode]? + let member: ASTMember? + let key: ASTMember? + let value: ASTMember? + let members: [String: ASTMember]? + let version: String? + let operations: [ASTReference]? + let resources: [ASTReference]? + let errors: [ASTReference]? + let rename: [String: String]? + let identifiers: [String: ASTReference]? + let properties: [String: ASTReference]? + let create: ASTReference? + let put: ASTReference? + let read: ASTReference? + let update: ASTReference? + let delete: ASTReference? + let list: ASTReference? + let collectionOperations: [ASTReference]? + let input: ASTReference? + let output: ASTReference? +} diff --git a/Sources/SmithyCodegenCore/AST/ASTType.swift b/Sources/SmithyCodegenCore/AST/ASTType.swift new file mode 100644 index 000000000..cdf3758bf --- /dev/null +++ b/Sources/SmithyCodegenCore/AST/ASTType.swift @@ -0,0 +1,37 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +enum ASTType: String, Decodable { + // These cases are all the Smithy shape types + case blob + case boolean + case string + case timestamp + case byte + case short + case integer + case long + case float + case document + case double + case bigDecimal + case bigInteger + case `enum` + case intEnum + case list + case set + case map + case structure + case union + case member + case service + case resource + case operation + + // Special for AST, added 'apply' case + case apply +} diff --git a/Sources/SmithyCodegenCore/CodeGenerator.swift b/Sources/SmithyCodegenCore/CodeGenerator.swift new file mode 100644 index 000000000..3c735faa0 --- /dev/null +++ b/Sources/SmithyCodegenCore/CodeGenerator.swift @@ -0,0 +1,30 @@ +// +// 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 CodeGenerator { + let modelFileURL: URL + let schemasFileURL: URL? + + public init(modelFileURL: URL, schemasFileURL: URL?) { + self.modelFileURL = modelFileURL + self.schemasFileURL = schemasFileURL + } + + public func run() throws { + // Load the model from the model file + let model = try Model(modelFileURL: modelFileURL) + + // If a schema file URL was provided, generate it + if let schemasFileURL { + let schemaContents = try SmithySchemaCodegen().generate(model: model) + try Data(schemaContents.utf8).write(to: schemasFileURL) + } + } +} diff --git a/Sources/SmithyCodegenCore/CodegenError.swift b/Sources/SmithyCodegenCore/CodegenError.swift new file mode 100644 index 000000000..6734b7329 --- /dev/null +++ b/Sources/SmithyCodegenCore/CodegenError.swift @@ -0,0 +1,14 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +struct CodegenError: Error { + let localizedDescription: String + + init(_ localizedDescription: String) { + self.localizedDescription = localizedDescription + } +} diff --git a/Sources/SmithyCodegenCore/Model/Model.swift b/Sources/SmithyCodegenCore/Model/Model.swift new file mode 100644 index 000000000..6a081b9c9 --- /dev/null +++ b/Sources/SmithyCodegenCore/Model/Model.swift @@ -0,0 +1,87 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Foundation.Data +import class Foundation.JSONDecoder +import struct Foundation.URL + +public class Model { + public let version: String + public let metadata: Node? + public let shapes: [ShapeID: Shape] + + public convenience init(modelFileURL: URL) throws { + let modelData = try Data(contentsOf: modelFileURL) + let astModel = try JSONDecoder().decode(ASTModel.self, from: modelData) + try self.init(astModel: astModel) + } + + init(astModel: ASTModel) throws { + self.version = astModel.smithy + self.metadata = astModel.metadata?.modelNode + let idToShapePairs = try astModel.shapes.map { try Self.shapePair(id: $0.key, astShape: $0.value) } + let idToMemberShapePairs = try astModel.shapes.flatMap { astShape in + try Self.memberShapePairs(id: astShape.key, astShape: astShape.value) + } + self.shapes = Dictionary(uniqueKeysWithValues: idToShapePairs + idToMemberShapePairs) + + // self is now initialized, perform post-initialization wireup + + // set the Shapes with references back to this model + self.shapes.values.forEach { $0.model = self } + + // set the memberIDs for each Shape + self.shapes.values.filter { $0.type != .member }.forEach { shape in + let namespace = shape.id.namespace + let name = shape.id.name + let memberIDs: [ShapeID] = Array(self.shapes.keys) + let filteredMemberIDs = memberIDs.filter { + $0.namespace == namespace && $0.name == name && $0.member != nil + } + shape.memberIDs = filteredMemberIDs.sorted() + } + } + + private static func shapePair(id: String, astShape: ASTShape) throws -> (ShapeID, Shape) { + let shapeID = try ShapeID(id) + let idToTraitPairs = try astShape.traits?.map { (try ShapeID($0.key), $0.value.modelNode) } ?? [] + let shape = Shape( + id: shapeID, + type: astShape.type.modelType, + traits: Dictionary(uniqueKeysWithValues: idToTraitPairs), + targetID: nil + ) + return (shapeID, shape) + } + + private static func memberShapePairs(id: String, astShape: ASTShape) throws -> [(ShapeID, Shape)] { + var baseMembers = (astShape.members ?? [:]) + if let member = astShape.member { + baseMembers["member"] = member + } + if let key = astShape.key { + baseMembers["key"] = key + } + if let value = astShape.value { + baseMembers["value"] = value + } + return try baseMembers.map { astMember in + let memberID = ShapeID(id: try ShapeID(id), member: astMember.key) + let traitPairs = try astMember.value.traits?.map { (try ShapeID($0.key), $0.value.modelNode) } + let traits = Dictionary(uniqueKeysWithValues: traitPairs ?? []) + let targetID = try ShapeID(astMember.value.target) + return (memberID, Shape(id: memberID, type: .member, traits: traits, targetID: targetID)) + } + } + + func expectShape(id: ShapeID) throws -> Shape { + guard let shape = shapes[id] else { + throw ModelError("ShapeID \(id) was expected in model but not found") + } + return shape + } +} diff --git a/Sources/SmithyCodegenCore/Model/ModelError.swift b/Sources/SmithyCodegenCore/Model/ModelError.swift new file mode 100644 index 000000000..97d45fdb5 --- /dev/null +++ b/Sources/SmithyCodegenCore/Model/ModelError.swift @@ -0,0 +1,14 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +public struct ModelError: Error { + public let localizedDescription: String + + init(_ localizedDescription: String) { + self.localizedDescription = localizedDescription + } +} diff --git a/Sources/SmithyCodegenCore/Model/Node.swift b/Sources/SmithyCodegenCore/Model/Node.swift new file mode 100644 index 000000000..c56c50736 --- /dev/null +++ b/Sources/SmithyCodegenCore/Model/Node.swift @@ -0,0 +1,63 @@ +// +// 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 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 { + case object([String: Node]) + case list([Node]) + case string(String) + case number(Double) + case boolean(Bool) + case null +} + +extension ASTNode { + + /// Creates a model Node from a AST-specific ASTNode. + var modelNode: Node { + switch self { + case .object(let object): + return .object(object.mapValues { $0.modelNode }) + case .list(let list): + return .list(list.map { $0.modelNode }) + case .string(let value): + return .string(value) + case .number(let value): + return .number(value) + case .boolean(let value): + return .boolean(value) + case .null: + return .null + } + } +} + +extension Node { + + var rendered: String { + switch self { + case .object(let object): + guard !object.isEmpty else { return "[:]" } + return "[" + object.map { "\($0.key.literal): \($0.value.rendered)" }.joined(separator: ",") + "]" + case .list(let list): + return "[" + list.map { $0.rendered }.joined(separator: ", ") + "]" + case .string(let string): + return string.literal + case .number(let number): + return "\(number)" + case .boolean(let bool): + return "\(bool)" + case .null: + return "nil" + } + } +} diff --git a/Sources/SmithyCodegenCore/Model/Shape.swift b/Sources/SmithyCodegenCore/Model/Shape.swift new file mode 100644 index 000000000..73b36dcc7 --- /dev/null +++ b/Sources/SmithyCodegenCore/Model/Shape.swift @@ -0,0 +1,86 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +public class Shape { + public let id: ShapeID + public let type: ShapeType + public internal(set) var traits: [ShapeID: Node] + var targetID: ShapeID? + var memberIDs: [ShapeID] = [] + weak var model: Model? + + public init(id: ShapeID, type: ShapeType, traits: [ShapeID: Node], targetID: ShapeID?) { + self.id = id + self.type = type + self.traits = traits + self.targetID = targetID + } + + public func hasTrait(_ traitID: ShapeID) -> Bool { + traits[traitID] != nil + } + + public var members: [Shape] { + get throws { + guard let model else { throw ModelError("id \"\(id)\" model not set") } + return memberIDs.map { model.shapes[$0]! } + } + } + + public var target: Shape? { + get throws { + guard let targetID else { return nil } + guard let model else { throw ModelError("id \"\(id)\" model not set") } + return model.shapes[targetID] + } + } + + public var children: Set { + get throws { + var c = Set() + try children(children: &c) + return c + } + } + + private func children(children: inout Set) throws { + let shapes = try candidates(for: self) + for shape in shapes { + if children.contains(shape) { continue } + children.insert(shape) + try shape.members.map { try $0.target }.compactMap { $0 }.forEach { + children.insert($0) + try $0.children(children: &children) + } + } + } + + private func candidates(for shape: Shape) throws -> [Shape] { + (try [try shape.target] + shape.members.map { try $0.target }).compactMap { $0 } + } +} + +extension Shape: Hashable { + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } +} + +extension Shape: Equatable { + + public static func == (lhs: Shape, rhs: Shape) -> Bool { + lhs.id == rhs.id + } +} + +extension Shape: Comparable { + + public static func < (lhs: Shape, rhs: Shape) -> Bool { + lhs.id < rhs.id + } +} diff --git a/Sources/SmithyCodegenCore/Model/ShapeID.swift b/Sources/SmithyCodegenCore/Model/ShapeID.swift new file mode 100644 index 000000000..9792290bc --- /dev/null +++ b/Sources/SmithyCodegenCore/Model/ShapeID.swift @@ -0,0 +1,67 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// Represents a Smithy shape ID. +/// +/// The id that ShapeID is created from is presumed to be properly formed, since this type will usually +/// be constructed from previously validated models. +/// +/// 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? + + public init(_ id: String) throws { + let splitOnPound = id.split(separator: "#") + guard splitOnPound.count == 2 else { + throw ModelError("id \"\(id)\" does not have a #") + } + guard let namespace = splitOnPound.first, !namespace.isEmpty else { + throw ModelError("id \"\(id)\" does not have a nonempty namespace") + } + self.namespace = String(namespace) + let splitOnDollar = splitOnPound.last!.split(separator: "$") + switch splitOnDollar.count { + case 2: + self.name = String(splitOnDollar.first!) + self.member = String(splitOnDollar.last!) + case 1: + self.name = String(splitOnDollar.first!) + self.member = nil + default: + throw ModelError("id \"\(id)\" has more than one $") + } + } + + public init(id: ShapeID, member: String) { + self.namespace = id.namespace + self.name = id.name + self.member = member + } + + public var id: String { + if let member { + return "\(namespace)#\(name)$\(member)" + } else { + return "\(namespace)#\(name)" + } + } +} + +extension ShapeID: Comparable { + + public static func < (lhs: ShapeID, rhs: ShapeID) -> Bool { + return lhs.id.lowercased() < rhs.id.lowercased() + } +} + +extension ShapeID: CustomStringConvertible { + + /// Returns the absolute Shape ID in a single, printable string. + public var description: String { id } +} diff --git a/Sources/SmithyCodegenCore/Model/ShapeType.swift b/Sources/SmithyCodegenCore/Model/ShapeType.swift new file mode 100644 index 000000000..7d0d47dec --- /dev/null +++ b/Sources/SmithyCodegenCore/Model/ShapeType.swift @@ -0,0 +1,91 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// 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). +public enum ShapeType: String { + case blob + case boolean + case string + case timestamp + case byte + case short + case integer + case long + case float + case document + case double + case bigDecimal + case bigInteger + case `enum` + case intEnum + case list + case set + case map + case structure + case union + case member + case service + case resource + case operation +} + +extension ASTType { + var modelType: ShapeType { + switch self { + case .blob: + return .blob + case .boolean: + return .boolean + case .string: + return .string + case .timestamp: + return .timestamp + case .byte: + return .byte + case .short: + return .short + case .integer: + return .integer + case .long: + return .long + case .float: + return .float + case .document: + return .document + case .double: + return .double + case .bigDecimal: + return .bigDecimal + case .bigInteger: + return .bigInteger + case .`enum`: + return .`enum` + case .intEnum: + return .intEnum + case .list: + return .list + case .set: + return .set + case .map: + return .map + case .structure: + return .structure + case .union: + return .union + case .member: + return .member + case .service: + return .service + case .resource: + return .resource + case .operation: + return .operation + case .apply: + fatalError("\"apply\" AST shapes not implemented") + } + } +} diff --git a/Sources/SmithyCodegenCore/Resources/DefaultSwiftHeader.txt b/Sources/SmithyCodegenCore/Resources/DefaultSwiftHeader.txt new file mode 100644 index 000000000..8f50fc772 --- /dev/null +++ b/Sources/SmithyCodegenCore/Resources/DefaultSwiftHeader.txt @@ -0,0 +1,8 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// Code generated by SmithyCodegenCLI. DO NOT EDIT! diff --git a/Sources/SmithyCodegenCore/Schemas/Shape+Schema.swift b/Sources/SmithyCodegenCore/Schemas/Shape+Schema.swift new file mode 100644 index 000000000..16cbb532b --- /dev/null +++ b/Sources/SmithyCodegenCore/Schemas/Shape+Schema.swift @@ -0,0 +1,54 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +extension Shape { + + func schemaVarName() throws -> String { + if id.namespace == "smithy.api" { + try id.preludeSchemaVarName() + } else { + try id.schemaVarName() + } + } +} + +private extension ShapeID { + + func preludeSchemaVarName() throws -> String { + let propertyName = + switch name { + case "Unit": "unitSchema" + case "String": "stringSchema" + case "Blob": "blobSchema" + case "Integer": "integerSchema" + case "Timestamp": "timestampSchema" + case "Boolean": "booleanSchema" + case "Float": "floatSchema" + case "Double": "doubleSchema" + case "Long": "longSchema" + case "Short": "shortSchema" + case "Byte": "byteSchema" + case "PrimitiveInteger": "primitiveIntegerSchema" + case "PrimitiveBoolean": "primitiveBooleanSchema" + case "PrimitiveFloat": "primitiveFloatSchema" + case "PrimitiveDouble": "primitiveDoubleSchema" + case "PrimitiveLong": "primitiveLongSchema" + case "PrimitiveShort": "primitiveShortSchema" + case "PrimitiveByte": "primitiveByteSchema" + case "Document": "documentSchema" + default: throw CodegenError("Unhandled prelude type converted to schemaVar: \"\(name)\"") + } + return "SmithySerialization.Prelude.\(propertyName)" + } + + func schemaVarName() throws -> String { + guard member == nil else { throw CodegenError("Assigning member schema to a var") } + let namespacePortion = namespace.replacingOccurrences(of: ".", with: "_") + let namePortion = name + return "schema2__\(namespacePortion)__\(namePortion)" + } +} diff --git a/Sources/SmithyCodegenCore/Schemas/SmithySchemaCodegen.swift b/Sources/SmithyCodegenCore/Schemas/SmithySchemaCodegen.swift new file mode 100644 index 000000000..d2aefa445 --- /dev/null +++ b/Sources/SmithyCodegenCore/Schemas/SmithySchemaCodegen.swift @@ -0,0 +1,98 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import SmithySerialization + +package struct SmithySchemaCodegen { + + package init() {} + + package func generate(model: Model) throws -> String { + let writer = SwiftWriter() + writer.write("import class SmithySerialization.Schema") + writer.write("import enum SmithySerialization.Prelude") + writer.write("") + + writer.write("// Model has \(model.shapes.count) shapes") + writer.write("// Model has \(model.shapes.values.filter { $0.type == .service }.count) services") + writer.write("// Model has \(model.shapes.values.filter { $0.type == .operation }.count) operations") + writer.write("// Model has \(model.shapes.values.filter { $0.type == .resource }.count) resources") + writer.write("// Model has \(model.shapes.values.filter { $0.type == .structure }.count) structures") + writer.write("// Model has \(model.shapes.values.filter { $0.type == .union }.count) unions") + writer.write("// Model has \(model.shapes.values.filter { $0.type == .enum }.count) enums") + writer.write("// Model has \(model.shapes.values.filter { $0.type == .intEnum }.count) intEnums") + writer.write("// Model has \(model.shapes.values.filter { $0.type == .member }.count) members") + writer.write("") + + // Write schemas for all inputs & outputs and their descendants. + let shapes = try model.shapes.values + .filter { $0.type == .structure } + .filter { + try $0.hasTrait(try .init("smithy.api#input")) || + $0.hasTrait(try .init("smithy.api#output")) || + $0.hasTrait(try .init("smithy.api#error"))} + .map { try [$0] + $0.children } + .flatMap { $0 } + let sortedShapes = Array(Set(shapes)).sorted { $0.id.id.lowercased() < $1.id.id.lowercased() } + writer.write("// Number of schemas: \(sortedShapes.count)") + writer.write("") + for shape in sortedShapes { + try writer.openBlock("public var \(try shape.schemaVarName()): SmithySerialization.Schema {", "}") { writer in + try writeSchema(writer: writer, shape: shape) + writer.unwrite(",") + } + writer.write("") + } + return writer.finalize() + } + + private func writeSchema(writer: SwiftWriter, shape: Shape, index: Int? = nil) throws { + try writer.openBlock(".init(", "),") { writer in + writer.write("id: \(shape.id.rendered),") + writer.write("type: .\(shape.type),") + let relevantTraitIDs = shape.traits.keys.filter { permittedTraitIDs.contains($0.id) } + let traitIDs = Array(relevantTraitIDs).sorted() + if !traitIDs.isEmpty { + writer.openBlock("traits: [", "],") { writer in + for traitID in traitIDs { + let trait = shape.traits[traitID]! + writer.write("\(traitID.rendered): \(trait.rendered),") + } + } + } + let members = try shape.members + if !members.isEmpty { + try writer.openBlock("members: [", "],") { writer in + for (index, member) in members.enumerated() { + try writeSchema(writer: writer, shape: member, index: index) + } + } + } + if let target = try shape.target { + writer.write("target: \(try target.schemaVarName()),") + } + if let index { + writer.write("index: \(index),") + } + writer.unwrite(",") + } + } +} + +extension ShapeID { + + var rendered: String { + let namespaceLiteral = namespace.literal + let nameLiteral = name.literal + if let member { + let memberLiteral = member.literal + return ".init(\(namespaceLiteral), \(nameLiteral), \(memberLiteral))" + } else { + return ".init(\(namespaceLiteral), \(nameLiteral))" + } + } +} diff --git a/Sources/SmithyCodegenCore/String+Utils.swift b/Sources/SmithyCodegenCore/String+Utils.swift new file mode 100644 index 000000000..1bb57c035 --- /dev/null +++ b/Sources/SmithyCodegenCore/String+Utils.swift @@ -0,0 +1,23 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +extension String { + + /// Escapes special characters in the string, then surrounds it in double quotes + /// to form a Swift string literal. + var literal: String { + let escaped = description + .replacingOccurrences(of: "\0", with: "\\0") + .replacingOccurrences(of: "\\", with: "\\\\") + .replacingOccurrences(of: "\t", with: "\\t") + .replacingOccurrences(of: "\n", with: "\\n") + .replacingOccurrences(of: "\r", with: "\\r") + .replacingOccurrences(of: "\"", with: "\\\"") + .replacingOccurrences(of: "\'", with: "\\'") + return "\"\(escaped)\"" + } +} diff --git a/Sources/SmithyCodegenCore/SwiftWriter.swift b/Sources/SmithyCodegenCore/SwiftWriter.swift new file mode 100644 index 000000000..a6b30a202 --- /dev/null +++ b/Sources/SmithyCodegenCore/SwiftWriter.swift @@ -0,0 +1,59 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import class Foundation.Bundle +import struct Foundation.Data +import struct Foundation.URL + +class SwiftWriter { + + private var lines: [String] + + var indentLevel = 0 + + init(includeHeader: Bool = true) { + if includeHeader { + let defaultHeaderFileURL = Bundle.module.url(forResource: "DefaultSwiftHeader", withExtension: "txt")! + // swiftlint:disable:next force_try + let defaultHeader = try! String(data: Data(contentsOf: defaultHeaderFileURL), encoding: .utf8)! + self.lines = defaultHeader.split(separator: "\n", omittingEmptySubsequences: false).map { String($0) } + } else { + self.lines = [] + } + } + + func indent() { + indentLevel += 4 + } + + func dedent() { + indentLevel -= 4 + } + + func write(_ line: String) { + lines.append(String(repeating: " ", count: indentLevel) + line) + } + + func unwrite(_ text: String) { + guard let lastIndex = lines.indices.last else { return } + if lines[lastIndex].hasSuffix(text) { + lines[lastIndex].removeLast(text.count) + } + } + + func openBlock(_ openWith: String, _ closeWith: String, contents: (SwiftWriter) throws -> Void) rethrows { + write(openWith) + indent() + try contents(self) + dedent() + write(closeWith) + } + + func finalize() -> String { + return lines.joined(separator: "\n").appending("\n") + } +} diff --git a/Sources/Smithy/Schema/Node.swift b/Sources/SmithySerialization/Schema/Node.swift similarity index 100% rename from Sources/Smithy/Schema/Node.swift rename to Sources/SmithySerialization/Schema/Node.swift diff --git a/Sources/Smithy/Schema/Prelude.swift b/Sources/SmithySerialization/Schema/Prelude.swift similarity index 100% rename from Sources/Smithy/Schema/Prelude.swift rename to Sources/SmithySerialization/Schema/Prelude.swift diff --git a/Sources/Smithy/Schema/Schema.swift b/Sources/SmithySerialization/Schema/Schema.swift similarity index 98% rename from Sources/Smithy/Schema/Schema.swift rename to Sources/SmithySerialization/Schema/Schema.swift index f8cac05fb..720e9800a 100644 --- a/Sources/Smithy/Schema/Schema.swift +++ b/Sources/SmithySerialization/Schema/Schema.swift @@ -5,6 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 // +import enum Smithy.ShapeType + /// 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 diff --git a/Sources/SmithySerialization/Schema/SchemaTraits.swift b/Sources/SmithySerialization/Schema/SchemaTraits.swift new file mode 100644 index 000000000..43d9ce385 --- /dev/null +++ b/Sources/SmithySerialization/Schema/SchemaTraits.swift @@ -0,0 +1,21 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// The trait IDs that should be copied into schemas. Other traits are omitted for brevity. +/// +/// This list can be expanded as features are added to Smithy/SDK that use them. +public let permittedTraitIDs: Set = [ + "smithy.api#sparse", + "smithy.api#input", + "smithy.api#output", + "smithy.api#error", + "smithy.api#enumValue", + "smithy.api#jsonName", + "smithy.api#required", + "smithy.api#default", + "smithy.api#timestampFormat", +] diff --git a/Sources/Smithy/Schema/ShapeID.swift b/Sources/SmithySerialization/Schema/ShapeID.swift similarity index 100% rename from Sources/Smithy/Schema/ShapeID.swift rename to Sources/SmithySerialization/Schema/ShapeID.swift diff --git a/Sources/SmithySwiftNIO/SwiftNIOHTTPClient.swift b/Sources/SmithySwiftNIO/SwiftNIOHTTPClient.swift index 69761212c..0dde0b521 100644 --- a/Sources/SmithySwiftNIO/SwiftNIOHTTPClient.swift +++ b/Sources/SmithySwiftNIO/SwiftNIOHTTPClient.swift @@ -6,6 +6,9 @@ // import AsyncHTTPClient +import struct Foundation.Date +import struct Foundation.URLComponents +import struct Foundation.URLQueryItem import NIOCore import NIOHTTP1 import NIOPosix @@ -15,9 +18,6 @@ import SmithyHTTPAPI import SmithyHTTPClientAPI import SmithyStreams import SmithyTelemetryAPI -import struct Foundation.Date -import struct Foundation.URLComponents -import struct Foundation.URLQueryItem /// AsyncHTTPClient-based HTTP client implementation that conforms to SmithyHTTPAPI.HTTPClient /// This implementation is thread-safe and supports concurrent request execution. diff --git a/Tests/SmithyCodegenCoreTests/ShapeIDTests.swift b/Tests/SmithyCodegenCoreTests/ShapeIDTests.swift new file mode 100644 index 000000000..928aedfbd --- /dev/null +++ b/Tests/SmithyCodegenCoreTests/ShapeIDTests.swift @@ -0,0 +1,32 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +import SmithyCodegenCore + +final class ShapeIDTests: XCTestCase { + + func test_init_createsShapeIDWithNamespace() throws { + let subject = try ShapeID("smithy.test#TestName$TestMember") + XCTAssertEqual(subject.namespace, "smithy.test") + } + + func test_init_createsShapeIDWithName() throws { + let subject = try ShapeID("smithy.test#TestName$TestMember") + XCTAssertEqual(subject.name, "TestName") + } + + func test_init_createsShapeIDWithMember() throws { + let subject = try ShapeID("smithy.test#TestName$TestMember") + XCTAssertEqual(subject.member, "TestMember") + } + + func test_init_createsShapeIDWithoutMember() throws { + let subject = try ShapeID("smithy.test#TestName") + XCTAssertNil(subject.member) + } +} 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 3ad36bb44..ed05e4a00 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 @@ -99,6 +99,9 @@ class DirectedSwiftCodegen( integrations.forEach { it.writeAdditionalFiles(context, ctx, writers) } } + LOGGER.info("[${service.id}] Generating Smithy model file info") + SmithyModelFileInfoGenerator(context).writeSmithyModelFileInfo() + LOGGER.info("[${service.id}] Generating package manifest file") PackageManifestGenerator(context).writePackageManifest(writers.dependencies) 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 0662d8d7d..8f4a69d8a 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 @@ -1,14 +1,14 @@ 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.core.GenerationContext import software.amazon.smithy.swift.codegen.model.getTrait class SmithyModelFileInfoGenerator( - val ctx: ProtocolGenerator.GenerationContext, + val ctx: GenerationContext, ) { fun writeSmithyModelFileInfo() { - ctx.service.getTrait()?.let { serviceTrait -> + ctx.model.serviceShapes.firstOrNull()?.getTrait()?.let { serviceTrait -> val filename = "Sources/${ctx.settings.moduleName}/smithy-model-info.json" val modelFileName = serviceTrait @@ -17,7 +17,7 @@ class SmithyModelFileInfoGenerator( .replace(",", "") .replace(" ", "-") val contents = "codegen/sdk-codegen/aws-models/$modelFileName.json" - ctx.delegator.useFileWriter(filename) { writer -> + ctx.writerDelegator().useFileWriter(filename) { writer -> writer.write("{\"path\":\"$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..5c383e3cd 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,18 +209,35 @@ 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 val nestedShapes = resolveShapesNeedingSchema(ctx) .filter { it.type != ShapeType.MEMBER } // Member schemas are only rendered in-line - nestedShapes.forEach { renderSchemas(ctx, it) } + .sorted() + val file = SchemaFileUtils.filename(ctx.settings, "whatever") + ctx.delegator.useFileWriter(file) { writer -> + writer.write("// Model has ${ctx.model.shapes().count()} shapes") + writer.write("// Model has ${ctx.model.shapes().filter { it.type == ShapeType.SERVICE }.count()} services") + writer.write("// Model has ${ctx.model.shapes().filter { it.type == ShapeType.OPERATION }.count()} operations") + writer.write("// Model has ${ctx.model.shapes().filter { it.type == ShapeType.RESOURCE }.count()} resources") + writer.write("// Model has ${ctx.model.shapes().filter { it.type == ShapeType.STRUCTURE }.count()} structures") + writer.write("// Model has ${ctx.model.shapes().filter { it.type == ShapeType.UNION }.count()} unions") + writer.write("// Model has ${ctx.model.shapes().filter { it.type == ShapeType.ENUM }.count()} enums") + writer.write("// Model has ${ctx.model.shapes().filter { it.type == ShapeType.INT_ENUM }.count()} intEnums") + writer.write("// Model has ${ctx.model.shapes().filter { it.type == ShapeType.MEMBER }.count()} members") + writer.write("") + writer.write("// Number of schemas: ${nestedShapes.count()}") + writer.write("") + } + nestedShapes.forEach { renderSchema(ctx, it) } } - private fun renderSchemas( + private fun renderSchema( ctx: ProtocolGenerator.GenerationContext, shape: Shape, ) { @@ -404,10 +421,7 @@ 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) } + .map { ctx.model.expectShape(it.input.get()) } .toSet() val topLevelOutputMembers = 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 dd666794b..843821d46 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 @@ -49,7 +49,11 @@ class SchemaGenerator( } if (shape.members().isNotEmpty()) { writer.openBlock("members: [", "],") { - shape.members().withIndex().forEach { renderSchemaStruct(it.value, it.index) } + shape + .members() + .sorted() + .withIndex() + .forEach { renderSchemaStruct(it.value, it.index) } } } targetShape(shape)?.let { 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 index 91a446af6..dd0ee07c0 100644 --- 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 @@ -3,10 +3,12 @@ package software.amazon.smithy.swift.codegen.integration.serde.schema val permittedTraitIDs: Set = setOf( "smithy.api#sparse", + "smithy.api#input", + "smithy.api#output", + "smithy.api#error", "smithy.api#enumValue", "smithy.api#jsonName", "smithy.api#required", "smithy.api#default", "smithy.api#timestampFormat", - "smithy.api#httpPayload", )