-
Notifications
You must be signed in to change notification settings - Fork 35
feat: Add schema + code generation #989
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: epic/sbs
Are you sure you want to change the base?
Changes from 4 commits
495f2e1
ca5b331
212120e
fcdf1bf
955e848
58b8969
5a911a4
ce8d3ac
b2152c2
f4fcab3
88f7be9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| // | ||
| // Copyright Amazon.com Inc. or its affiliates. | ||
| // All Rights Reserved. | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
|
|
||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This type holds the actual content of a trait. |
||
| /// 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 | ||
| } | ||
|
|
||
| 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 | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| // | ||
| // 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 enum Prelude { | ||
|
|
||
| public static var unitSchema: Schema { | ||
| Schema(id: .init("smithy.api", "Unit"), type: .structure) | ||
| } | ||
|
|
||
| public static var booleanSchema: Schema { | ||
| Schema(id: .init("smithy.api", "Boolean"), type: .boolean) | ||
| } | ||
|
|
||
| public static var stringSchema: Schema { | ||
| Schema(id: .init("smithy.api", "String"), type: .string) | ||
| } | ||
|
|
||
| public static var integerSchema: Schema { | ||
| Schema(id: .init("smithy.api", "Integer"), type: .integer) | ||
| } | ||
|
|
||
| public static var blobSchema: Schema { | ||
| Schema(id: .init("smithy.api", "Blob"), type: .blob) | ||
| } | ||
|
|
||
| public static var timestampSchema: Schema { | ||
| Schema(id: .init("smithy.api", "Timestamp"), type: .timestamp) | ||
| } | ||
|
|
||
| public static var byteSchema: Schema { | ||
| Schema(id: .init("smithy.api", "Byte"), type: .byte) | ||
| } | ||
|
|
||
| public static var shortSchema: Schema { | ||
| Schema(id: .init("smithy.api", "Short"), type: .short) | ||
| } | ||
|
|
||
| public static var longSchema: Schema { | ||
| Schema(id: .init("smithy.api", "Long"), type: .long) | ||
| } | ||
|
|
||
| public static var floatSchema: Schema { | ||
| Schema(id: .init("smithy.api", "Float"), type: .float) | ||
| } | ||
|
|
||
| public static var doubleSchema: Schema { | ||
| Schema(id: .init("smithy.api", "Double"), type: .double) | ||
| } | ||
|
|
||
| public static var documentSchema: Schema { | ||
| Schema(id: .init("smithy.api", "PrimitiveDocument"), type: .document) | ||
| } | ||
|
|
||
| public static var primitiveBooleanSchema: Schema { | ||
| Schema(id: .init("smithy.api", "PrimitiveBoolean"), type: .boolean, traits: [defaultTraitID: false]) | ||
| } | ||
|
|
||
| public static var primitiveIntegerSchema: Schema { | ||
| Schema(id: .init("smithy.api", "PrimitiveInteger"), type: .integer, traits: [defaultTraitID: 0]) | ||
| } | ||
|
|
||
| public static var primitiveByteSchema: Schema { | ||
| Schema(id: .init("smithy.api", "PrimitiveByte"), type: .byte, traits: [defaultTraitID: 0]) | ||
| } | ||
|
|
||
| public static var primitiveShortSchema: Schema { | ||
| Schema(id: .init("smithy.api", "PrimitiveShort"), type: .short, traits: [defaultTraitID: 0]) | ||
| } | ||
|
|
||
| public static var primitiveLongSchema: Schema { | ||
| Schema(id: .init("smithy.api", "PrimitiveLong"), type: .long, traits: [defaultTraitID: 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") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| // | ||
| // Copyright Amazon.com Inc. or its affiliates. | ||
| // All Rights Reserved. | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
|
|
||
| /// 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 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. | ||
| public let members: [Schema] | ||
|
|
||
| /// The target schema for this schema. Will only be used when this is a member schema. | ||
| public let target: Schema? | ||
|
|
||
| /// 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: ShapeID, | ||
| type: ShapeType, | ||
| traits: [ShapeID: Node] = [:], | ||
| members: [Schema] = [], | ||
| target: Schema? = nil, | ||
| index: Int = -1 | ||
| ) { | ||
| self.id = id | ||
| self.type = type | ||
| self.traits = traits | ||
| self.members = members | ||
| 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 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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: 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; | ||
| /// 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)" | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // | ||
| // Copyright Amazon.com Inc. or its affiliates. | ||
| // All Rights Reserved. | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
|
|
||
| import XCTest | ||
| import Smithy | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simple test on ShapeID to ensure it renders to String properly. |
||
|
|
||
| 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") | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,7 +72,6 @@ class DirectedSwiftCodegen( | |
|
|
||
| LOGGER.info("Generating Swift client for service ${directive.settings().service}") | ||
|
|
||
| var shouldGenerateTestTarget = false | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unused property |
||
| 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}") | ||
|
|
@@ -81,12 +80,12 @@ class DirectedSwiftCodegen( | |
| generateMessageMarshallable(ctx) | ||
| generateMessageUnmarshallable(ctx) | ||
| generateCodableConformanceForNestedTypes(ctx) | ||
| generateSchemas(ctx) | ||
|
|
||
| initializeMiddleware(ctx) | ||
|
|
||
| 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}") | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a test target to the
Smithytarget since none previously existed.