From b236c1a08f2abe7b503b00de6484bd835f4b4636 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Fri, 8 Nov 2024 14:02:40 -0500 Subject: [PATCH 01/25] add CBOR support to ServiceUtils (WireProtocol, AWSProtocol) --- .../integration/serde/readwrite/ServiceUtils.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ServiceUtils.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ServiceUtils.kt index a88ff8a7d..467935fa9 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ServiceUtils.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ServiceUtils.kt @@ -7,16 +7,18 @@ import software.amazon.smithy.aws.traits.protocols.Ec2QueryTrait import software.amazon.smithy.aws.traits.protocols.RestJson1Trait import software.amazon.smithy.aws.traits.protocols.RestXmlTrait import software.amazon.smithy.model.shapes.ServiceShape +import software.amazon.smithy.protocol.traits.Rpcv2CborTrait import software.amazon.smithy.swift.codegen.model.hasTrait -// An enum expressing the six defined AWS protocols used with Smithy. +// An enum expressing the seven defined AWS protocols used with Smithy. enum class AWSProtocol { - REST_XML, AWS_QUERY, EC2_QUERY, REST_JSON_1, AWS_JSON_1_0, AWS_JSON_1_1 + RPCV2_CBOR, REST_XML, AWS_QUERY, EC2_QUERY, REST_JSON_1, AWS_JSON_1_0, AWS_JSON_1_1 } // The AWS protocols that may be used with Smithy. val ServiceShape.awsProtocol: AWSProtocol get() = when { + hasTrait() -> AWSProtocol.RPCV2_CBOR hasTrait() -> AWSProtocol.REST_XML hasTrait() -> AWSProtocol.AWS_QUERY hasTrait() -> AWSProtocol.EC2_QUERY @@ -28,7 +30,7 @@ val ServiceShape.awsProtocol: AWSProtocol // The wire protocols that an AWS protocol may use over the wire for its requests or responses. enum class WireProtocol { - XML, FORM_URL, JSON + XML, FORM_URL, JSON, CBOR } // The wire protocol used for this service's requests. @@ -37,6 +39,7 @@ val ServiceShape.requestWireProtocol: WireProtocol AWSProtocol.AWS_QUERY, AWSProtocol.EC2_QUERY -> WireProtocol.FORM_URL AWSProtocol.REST_XML -> WireProtocol.XML AWSProtocol.REST_JSON_1, AWSProtocol.AWS_JSON_1_0, AWSProtocol.AWS_JSON_1_1 -> WireProtocol.JSON + AWSProtocol.RPCV2_CBOR -> WireProtocol.CBOR } // The wire protocol used for this service's responses. @@ -44,11 +47,12 @@ val ServiceShape.responseWireProtocol: WireProtocol get() = when (awsProtocol) { AWSProtocol.REST_XML, AWSProtocol.AWS_QUERY, AWSProtocol.EC2_QUERY -> WireProtocol.XML AWSProtocol.REST_JSON_1, AWSProtocol.AWS_JSON_1_0, AWSProtocol.AWS_JSON_1_1 -> WireProtocol.JSON + AWSProtocol.RPCV2_CBOR -> WireProtocol.CBOR } // Whether this AWS protocol is RPC. val ServiceShape.isRPCBound: Boolean get() = when (awsProtocol) { - AWSProtocol.AWS_JSON_1_0, AWSProtocol.AWS_JSON_1_1, AWSProtocol.AWS_QUERY, AWSProtocol.EC2_QUERY -> true + AWSProtocol.AWS_JSON_1_0, AWSProtocol.AWS_JSON_1_1, AWSProtocol.AWS_QUERY, AWSProtocol.EC2_QUERY, AWSProtocol.RPCV2_CBOR -> true else -> false } From c6a872ca89c5167fe628930c40a082b636487e81 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Fri, 8 Nov 2024 14:39:51 -0500 Subject: [PATCH 02/25] add configurable protocol resolution with a default priority list --- .../smithy/swift/codegen/SwiftSettings.kt | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSettings.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSettings.kt index 0f6d7df65..784050130 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSettings.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSettings.kt @@ -5,6 +5,12 @@ package software.amazon.smithy.swift.codegen +import software.amazon.smithy.aws.traits.protocols.AwsJson1_0Trait +import software.amazon.smithy.aws.traits.protocols.AwsJson1_1Trait +import software.amazon.smithy.aws.traits.protocols.AwsQueryTrait +import software.amazon.smithy.aws.traits.protocols.Ec2QueryTrait +import software.amazon.smithy.aws.traits.protocols.RestJson1Trait +import software.amazon.smithy.aws.traits.protocols.RestXmlTrait import software.amazon.smithy.codegen.core.CodegenException import software.amazon.smithy.model.Model import software.amazon.smithy.model.knowledge.ServiceIndex @@ -13,6 +19,7 @@ import software.amazon.smithy.model.node.StringNode import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.protocol.traits.Rpcv2CborTrait import java.util.logging.Logger import kotlin.streams.toList @@ -29,6 +36,17 @@ private const val SWIFT_VERSION = "swiftVersion" private const val MERGE_MODELS = "mergeModels" private const val COPYRIGHT_NOTICE = "copyrightNotice" +// Prioritized list of protocols supported for code generation +private val DEFAULT_PROTOCOL_RESOLUTION_PRIORITY = setOf( + Rpcv2CborTrait.ID, + AwsJson1_0Trait.ID, + AwsJson1_1Trait.ID, + RestJson1Trait.ID, + RestXmlTrait.ID, + AwsQueryTrait.ID, + Ec2QueryTrait.ID, +) + class SwiftSettings( val service: ShapeId, val moduleName: String, @@ -161,19 +179,28 @@ class SwiftSettings( * @param serviceIndex Service index containing the support * @param service Service to get the protocols from if "protocols" is not set. * @param supportedProtocolTraits The set of protocol traits supported by the generator. + * @param configuredProtocolPriority Optional configured protocol priority list, used to override the default priority. * @return Returns the resolved protocol name. * @throws UnresolvableProtocolException if no protocol could be resolved. */ fun resolveServiceProtocol( serviceIndex: ServiceIndex, service: ServiceShape, - supportedProtocolTraits: Set + supportedProtocolTraits: Set, + configuredProtocolPriority: Set? = null ): ShapeId { val resolvedProtocols: Set = serviceIndex.getProtocols(service).keys - val protocol = resolvedProtocols.firstOrNull(supportedProtocolTraits::contains) + + // Use the configured protocol priority if provided; otherwise, fall back to the default priority + val protocolResolutionPriority = configuredProtocolPriority ?: DEFAULT_PROTOCOL_RESOLUTION_PRIORITY + + // Find the highest-priority protocol that is both supported and resolved + val protocol = protocolResolutionPriority + .firstOrNull { it in resolvedProtocols && it in supportedProtocolTraits } + return protocol ?: throw UnresolvableProtocolException( "The ${service.id} service supports the following unsupported protocols $resolvedProtocols. " + - "The following protocol generators were found on the class path: $supportedProtocolTraits" + "The following protocol generators were found on the class path: $supportedProtocolTraits" ) } } From 37fdc0e4bfa01c7cfd3875e9eca99273c031228f Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Wed, 13 Nov 2024 14:48:05 -0500 Subject: [PATCH 03/25] make function makeQueryCompatibleError generic to work across multiple protocols --- .../httpResponse/HTTPResponseBindingErrorGenerator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingErrorGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingErrorGenerator.kt index 8bccc6caf..bc2edb967 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingErrorGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/httpResponse/HTTPResponseBindingErrorGenerator.kt @@ -94,7 +94,7 @@ class HTTPResponseBindingErrorGenerator( if (ctx.service.hasTrait()) { writer.write("let errorDetails = httpResponse.headers.value(for: \"x-amzn-query-error\")") writer.write( - "let baseError = try \$N.makeQueryCompatibleAWSJsonError(httpResponse: httpResponse, responseReader: responseReader, noErrorWrapping: \$L, errorDetails: errorDetails)", + "let baseError = try \$N.makeQueryCompatibleError(httpResponse: httpResponse, responseReader: responseReader, noErrorWrapping: \$L, errorDetails: errorDetails)", customizations.baseErrorSymbol, noErrorWrapping ) From 10dccdd2db7529c72edcfa50ababd9ed7c3070a2 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 19 Dec 2024 15:50:18 -0500 Subject: [PATCH 04/25] current version only has 3/69 failing protocol tests --- Package.swift | 12 +- Sources/SmithyCBOR/Reader/Reader.swift | 378 ++++++++++++++++++ Sources/SmithyCBOR/Writer/Writer.swift | 227 +++++++++++ Sources/SmithyReadWrite/SmithyReader.swift | 1 + Sources/SmithyTestUtil/CBORComparator.swift | 278 +++++++++++++ .../RequestTestUtil/HttpRequestTestBase.swift | 3 + Sources/SmithyTestUtil/XCTAssertions.swift | 19 + gradle.properties | 2 +- smithy-swift-codegen/build.gradle.kts | 1 + .../swift/codegen/ShapeValueGenerator.kt | 8 +- .../smithy/swift/codegen/SwiftDependency.kt | 1 + .../swift/codegen/SwiftSymbolProvider.kt | 4 +- .../smithy/swift/codegen/SyntheticClone.kt | 2 +- .../HttpProtocolClientGenerator.kt | 1 + .../HttpProtocolUnitTestErrorGenerator.kt | 24 +- .../HttpProtocolUnitTestRequestGenerator.kt | 37 +- .../HttpProtocolUnitTestResponseGenerator.kt | 68 ++-- .../core/StaticHttpBindingResolver.kt | 6 +- .../member/MemberShapeDecodeGenerator.kt | 10 +- .../member/MemberShapeEncodeGenerator.kt | 2 +- .../serde/readwrite/NodeInfoUtils.kt | 7 + .../serde/struct/StructEncodeGenerator.kt | 3 + .../swift/codegen/model/AddOperationShapes.kt | 2 + .../smithy/swift/codegen/model/ShapeExt.kt | 2 +- .../codegen/swiftmodules/SmithyCBORTypes.kt | 26 ++ .../MockHTTPRPCv2CBORProtocolGenerator.kt | 46 +++ 26 files changed, 1118 insertions(+), 52 deletions(-) create mode 100644 Sources/SmithyCBOR/Reader/Reader.swift create mode 100644 Sources/SmithyCBOR/Writer/Writer.swift create mode 100644 Sources/SmithyTestUtil/CBORComparator.swift create mode 100644 smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyCBORTypes.kt create mode 100644 smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRPCv2CBORProtocolGenerator.kt diff --git a/Package.swift b/Package.swift index 80672a57f..aa6e6c573 100644 --- a/Package.swift +++ b/Package.swift @@ -50,6 +50,7 @@ let package = Package( .library(name: "SmithyStreams", targets: ["SmithyStreams"]), .library(name: "SmithyChecksumsAPI", targets: ["SmithyChecksumsAPI"]), .library(name: "SmithyChecksums", targets: ["SmithyChecksums"]), + .library(name: "SmithyCBOR", targets: ["SmithyCBOR"]), .library(name: "SmithyWaitersAPI", targets: ["SmithyWaitersAPI"]), .library(name: "SmithyTestUtil", targets: ["SmithyTestUtil"]), ], @@ -92,6 +93,7 @@ let package = Package( "SmithyStreams", "SmithyChecksumsAPI", "SmithyChecksums", + "SmithyCBOR", .product(name: "AwsCommonRuntimeKit", package: "aws-crt-swift"), ], resources: [ @@ -140,7 +142,7 @@ let package = Package( ), .target( name: "SmithyTestUtil", - dependencies: ["ClientRuntime", "SmithyHTTPAPI", "SmithyIdentity"] + dependencies: ["ClientRuntime", "SmithyHTTPAPI", "SmithyIdentity", "SmithyCBOR"] ), .target( name: "SmithyIdentity", @@ -222,6 +224,14 @@ let package = Package( .product(name: "AwsCommonRuntimeKit", package: "aws-crt-swift") ] ), + .target( + name: "SmithyCBOR", + dependencies: [ + "SmithyReadWrite", + "SmithyTimestamps", + .product(name: "AwsCommonRuntimeKit", package: "aws-crt-swift") + ] + ), .target( name: "SmithyWaitersAPI" ), diff --git a/Sources/SmithyCBOR/Reader/Reader.swift b/Sources/SmithyCBOR/Reader/Reader.swift new file mode 100644 index 000000000..1851dc397 --- /dev/null +++ b/Sources/SmithyCBOR/Reader/Reader.swift @@ -0,0 +1,378 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AwsCommonRuntimeKit +import Foundation + +@_spi(SmithyReadWrite) import protocol SmithyReadWrite.SmithyReader +@_spi(SmithyReadWrite) import protocol SmithyReadWrite.SmithyWriter +@_spi(Smithy) import struct Smithy.Document +@_spi(Smithy) import protocol Smithy.SmithyDocument +@_spi(SmithyTimestamps) import enum SmithyTimestamps.TimestampFormat +@_spi(SmithyTimestamps) import struct SmithyTimestamps.TimestampFormatter +import SmithyReadWrite + +@_spi(SmithyReadWrite) +public final class Reader: SmithyReader { + public typealias NodeInfo = String + + // The CBOR value represented as a CBORType + public let cborValue: CBORType? + public let nodeInfo: NodeInfo + public internal(set) var children: [Reader] = [] + public internal(set) weak var parent: Reader? + public var hasContent: Bool { cborValue != nil && cborValue != .null } + + // MARK: - Initializer + + public init(nodeInfo: NodeInfo, cborValue: CBORType?, parent: Reader? = nil) { + self.nodeInfo = nodeInfo + self.cborValue = cborValue + self.parent = parent + self.children = Self.children(from: cborValue, parent: self) + } + + enum ReaderError: Error { + case unexpectedType(expected: String, actual: CBORType) + case invalidData(description: String) + case missingKey + case indefiniteBreakNotFound + case requiredValueNotPresent + } + + private static func children(from cborValue: CBORType?, parent: Reader) -> [Reader] { + var children = [Reader]() + switch cborValue { + case .map(let map): + for (key, value) in map { + // Ensure key is treated as a child node + let valueReader = Reader(nodeInfo: "\(key)", cborValue: value, parent: parent) + valueReader.children = Self.children(from: value, parent: valueReader) // Recursively process nested children + + children.append(valueReader) + } + case .array(let array): + for (index, value) in array.enumerated() { + let child = Reader(nodeInfo: "\(index)", cborValue: value, parent: parent) + child.children = Self.children(from: value, parent: child) // Recursively process nested children + children.append(child) + } + default: + break + } + return children + } + + // MARK: - SmithyReader Protocol Methods + + public static func from(data: Data) throws -> Reader { + let decoder = try CBORDecoder(data: [UInt8](data)) + var rootValue: CBORType? + + if decoder.hasNext() { + rootValue = try decoder.popNext() + } else { + rootValue = .null + } + + switch rootValue { + case .indef_map_start: + let map = try decodeIndefiniteMap(decoder) + return Reader(nodeInfo: "Indefinite Map", cborValue: .map(map)) + + case .indef_array_start: + let array = try decodeIndefiniteArray(decoder) + return Reader(nodeInfo: "Indefinite Array", cborValue: .array(array)) + + case .indef_text_start: + let text = try decodeIndefiniteText(decoder) + return Reader(nodeInfo: "Indefinite Text", cborValue: .text(text)) + + case .indef_bytes_start: + let bytes = try decodeIndefiniteBytes(decoder) + return Reader(nodeInfo: "Indefinite Bytes", cborValue: .bytes(bytes)) + + default: + return Reader(nodeInfo: "", cborValue: rootValue) + } + } + + // can be nested + private static func decodeIndefiniteMap(_ decoder: CBORDecoder) throws -> [String: CBORType] { + var map: [String: CBORType] = [:] + while decoder.hasNext() { + let key = try decoder.popNext() + if case .indef_break = key { break } + + guard case .text(let keyString) = key else { + throw ReaderError.unexpectedType(expected: "text", actual: key) + } + + let value = try decoder.popNext() + if case .indef_break = value { break } + + // Check if the value is another indefinite structure and decode it. + let decodedValue: CBORType + switch value { + case .indef_map_start: + decodedValue = .map(try decodeIndefiniteMap(decoder)) + case .indef_array_start: + decodedValue = .array(try decodeIndefiniteArray(decoder)) + case .indef_text_start: + decodedValue = .text(try decodeIndefiniteText(decoder)) + case .indef_bytes_start: + decodedValue = .bytes(try decodeIndefiniteBytes(decoder)) + default: + decodedValue = value + } + + map[keyString] = decodedValue + } + return map + } + + // can be nested + private static func decodeIndefiniteArray(_ decoder: CBORDecoder) throws -> [CBORType] { + var array: [CBORType] = [] + + while decoder.hasNext() { + let value = try decoder.popNext() + if case .indef_break = value { break } + + let decodedValue: CBORType + switch value { + case .indef_map_start: + decodedValue = .map(try decodeIndefiniteMap(decoder)) + case .indef_array_start: + decodedValue = .array(try decodeIndefiniteArray(decoder)) + case .indef_text_start: + decodedValue = .text(try decodeIndefiniteText(decoder)) + case .indef_bytes_start: + decodedValue = .bytes(try decodeIndefiniteBytes(decoder)) + default: + decodedValue = value + } + + array.append(decodedValue) + } + + return array + } + + private static func decodeIndefiniteText(_ decoder: CBORDecoder) throws -> String { + var text = "" + + while decoder.hasNext() { + let chunk = try decoder.popNext() + + // Handle the end of the indefinite-length text + if case .indef_break = chunk { + break + } + + // Append text chunks + if case .text(let chunkString) = chunk { + text += chunkString + } else if case .indef_text_start = chunk { + // Handle nested indefinite text starts (recursion) + text += try decodeIndefiniteText(decoder) + } else { + // Unexpected type + throw ReaderError.unexpectedType(expected: "text or indef_text_start", actual: chunk) + } + } + + return text + } + + // wont be nested + private static func decodeIndefiniteBytes(_ decoder: CBORDecoder) throws -> Data { + var bytes = Data() + while decoder.hasNext() { + let chunk = try decoder.popNext() + if case .indef_break = chunk { break } + + guard case .bytes(let chunkData) = chunk else { + throw ReaderError.unexpectedType(expected: "bytes", actual: chunk) + } + bytes.append(chunkData) + } + return bytes + } + + public subscript(nodeInfo: NodeInfo) -> Reader { + if let match = children.first(where: { $0.nodeInfo == nodeInfo }) { + return match + } else { + return Reader(nodeInfo: nodeInfo, cborValue: nil, parent: self) + } + } + + public func readIfPresent() throws -> String? { + switch cborValue { + case .text(let string): + return string + case .indef_text_start: + // Handle concatenation of indefinite-length text + var combinedText = "" + for child in children { + if let chunk = try child.readIfPresent() as String? { + combinedText += chunk + } + } + return combinedText + case .bytes(let data): + return String(data: data, encoding: .utf8) + default: + return nil + } + } + + public func readIfPresent() throws -> Int8? { + switch cborValue { + case .int(let intValue): return Int8(intValue) + case .uint(let uintValue): return Int8(uintValue) + default: return nil + } + } + + public func readIfPresent() throws -> Int16? { + switch cborValue { + case .int(let intValue): return Int16(intValue) + case .uint(let uintValue): return Int16(uintValue) + default: return nil + } + } + + public func readIfPresent() throws -> Int? { + switch cborValue { + case .int(let intValue): return Int(intValue) + case .uint(let uintValue): return Int(uintValue) + default: return nil + } + } + + public func readIfPresent() throws -> Float? { + switch cborValue { + case .double(let doubleValue): return Float(doubleValue) + case .int(let intValue): return Float(intValue) + case .uint(let uintValue): return Float(uintValue) + default: return nil + } + } + + public func readIfPresent() throws -> Double? { + switch cborValue { + case .double(let doubleValue): return doubleValue + case .int(let intValue): return Double(intValue) + case .uint(let uintValue): return Double(uintValue) + default: return nil + } + } + + public func readIfPresent() throws -> Bool? { + switch cborValue { + case .bool(let boolValue): return boolValue + default: return nil + } + } + + public func readIfPresent() throws -> Data? { + switch cborValue { + case .bytes(let data): return data + case .text(let string): return Data(base64Encoded: string) + default: return nil + } + } + + public func readIfPresent() throws -> Document? { + guard let cborValue else { return nil } + return Document(cborValue as! SmithyDocument) + } + + public func readIfPresent() throws -> T? where T: RawRepresentable, T.RawValue == Int { + guard let rawValue: Int = try readIfPresent() else { return nil } + return T(rawValue: rawValue) + } + + public func readIfPresent() throws -> T? where T: RawRepresentable, T.RawValue == String { + guard let rawValue: String = try readIfPresent() else { return nil } + return T(rawValue: rawValue) + } + + public func readTimestampIfPresent(format: TimestampFormat) throws -> Date? { + switch cborValue { + case .double(let doubleValue): + return Date(timeIntervalSince1970: doubleValue) + case .int(let intValue): + return Date(timeIntervalSince1970: Double(intValue)) + case .uint(let uintValue): + return Date(timeIntervalSince1970: Double(uintValue)) + case .text(let string): + return TimestampFormatter(format: format).date(from: string) + case .date(let dateValue): + return dateValue // Directly return the date value + default: + return nil + } + } + + public func readMapIfPresent( + valueReadingClosure: (Reader) throws -> Value, + keyNodeInfo: NodeInfo, + valueNodeInfo: NodeInfo, + isFlattened: Bool + ) throws -> [String: Value]? { + guard let cborValue else { return nil } + guard case .map(let map) = cborValue else { return nil } + var dict = [String: Value]() + for (key, _) in map { + let reader = self[key] + do { + let value = try valueReadingClosure(reader) + dict[key] = value + } catch ReaderError.requiredValueNotPresent { + if !(try reader.readNullIfPresent() ?? false) { throw ReaderError.requiredValueNotPresent } + } + } + return dict + } + + public func readListIfPresent( + memberReadingClosure: (Reader) throws -> Member, + memberNodeInfo: NodeInfo, + isFlattened: Bool + ) throws -> [Member]? { + guard let cborValue else { return nil } + guard case .array(_) = cborValue else { return nil } + + return try children.map { child in + // Check if the child is an indefinite-length text + if case .indef_text_start = child.cborValue { + var combinedText = "" + for grandChild in child.children { + if let chunk = try grandChild.readIfPresent() as String? { + combinedText += chunk + } + } + // Return the combined text + return combinedText as! Member + } else { + // Handle regular values + return try memberReadingClosure(child) + } + } + } + + public func readNullIfPresent() throws -> Bool? { + if cborValue == .null { + return true + } else { + return nil + } + } +} diff --git a/Sources/SmithyCBOR/Writer/Writer.swift b/Sources/SmithyCBOR/Writer/Writer.swift new file mode 100644 index 000000000..54a5b98d8 --- /dev/null +++ b/Sources/SmithyCBOR/Writer/Writer.swift @@ -0,0 +1,227 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AwsCommonRuntimeKit +import Foundation + +@_spi(SmithyReadWrite) import protocol SmithyReadWrite.SmithyReader +@_spi(SmithyReadWrite) import protocol SmithyReadWrite.SmithyWriter +@_spi(Smithy) import struct Smithy.Document +@_spi(Smithy) import protocol Smithy.SmithyDocument +@_spi(SmithyTimestamps) import enum SmithyTimestamps.TimestampFormat +@_spi(SmithyTimestamps) import struct SmithyTimestamps.TimestampFormatter + +@_spi(SmithyReadWrite) +public final class Writer: SmithyWriter { + public typealias NodeInfo = String + + let nodeInfo: NodeInfo + var cborValue: CBORType? + var children: [Writer] = [] + weak var parent: Writer? + + // MARK: - Initializer + + public required init(nodeInfo: NodeInfo) { + self.nodeInfo = nodeInfo + } + + public required init(nodeInfo: NodeInfo, parent: Writer? = nil) { + self.nodeInfo = nodeInfo + self.parent = parent + } + + // MARK: - SmithyWriter Protocol Methods + + public func data() throws -> Data { + let encoder = try CBOREncoder() + try encode(encoder: encoder) + let encodedBytes = encoder.getEncoded() + print("Encoded CBOR: \(encodedBytes.map { String(format: "0x%02X", $0) }.joined(separator: ", "))") + return Data(encodedBytes) + } + + public subscript(nodeInfo: NodeInfo) -> Writer { + if let child = children.first(where: { $0.nodeInfo == nodeInfo }) { + return child + } else { + let newChild = Writer(nodeInfo: nodeInfo, parent: self) + children.append(newChild) + return newChild + } + } + + public func write(_ value: Bool?) throws { + guard let value else { return } + self.cborValue = .bool(value) + } + + public func write(_ value: String?) throws { + guard let value else { return } + self.cborValue = .text(value) + } + + public func write(_ value: Float?) throws { + guard let value else { return } + self.cborValue = .double(Double(value)) + } + + public func write(_ value: Double?) throws { + guard let value else { return } + self.cborValue = .double(value) + } + + public func write(_ value: Int?) throws { + guard let value else { return } + self.cborValue = .int(Int64(value)) + } + + public func write(_ value: Int8?) throws { + guard let value else { return } + self.cborValue = .int(Int64(value)) + } + + public func write(_ value: Int16?) throws { + guard let value else { return } + self.cborValue = .int(Int64(value)) + } + + public func write(_ value: UInt8?) throws { + guard let value else { return } + self.cborValue = .uint(UInt64(value)) + } + + public func write(_ value: Data?) throws { + guard let value else { return } + self.cborValue = .bytes(value) + } + + public func write(_ value: SmithyDocument?) throws { + // No operation. Smithy document not supported in CBOR + } + + public func writeTimestamp(_ value: Date?, format: TimestampFormat) throws { + guard let value else { return } + switch format { + case .epochSeconds: + self.cborValue = .date(value) + case .dateTime, .httpDate: + let string = TimestampFormatter(format: format).string(from: value) + self.cborValue = .text(string) + } + } + + public func write(_ value: T?) throws where T: RawRepresentable, T.RawValue == Int { + if let rawValue = value?.rawValue { + try write(rawValue) + } + } + + public func write(_ value: T?) throws where T: RawRepresentable, T.RawValue == String { + if let rawValue = value?.rawValue { + try write(rawValue) + } + } + + public func writeMap( + _ value: [String: T]?, + valueWritingClosure: (T, Writer) throws -> Void, + keyNodeInfo: NodeInfo, + valueNodeInfo: NodeInfo, + isFlattened: Bool + ) throws { + guard let value else { return } + var map: [String: CBORType] = [:] + + for (key, val) in value { + let writer = self[key] + try valueWritingClosure(val, writer) + + // If the writer itself doesn't have a cborValue, build it from its children + if writer.cborValue == nil, !writer.children.isEmpty { + var childMap: [String: CBORType] = [:] + for child in writer.children { + if let childCborValue = child.cborValue { + childMap[child.nodeInfo] = childCborValue + } + } + writer.cborValue = .map(childMap) // Construct the map for the writer + } + + // Add to the parent map + if let cborValue = writer.cborValue { + map[key] = cborValue + } + } + + // Assign the constructed map to the current writer + self.cborValue = .map(map) + } + + public func writeList( + _ value: [T]?, + memberWritingClosure: (T, Writer) throws -> Void, + memberNodeInfo: NodeInfo, + isFlattened: Bool + ) throws { + guard let value else { return } + var array: [CBORType] = [] + for val in value { + let writer = Writer(nodeInfo: "", parent: self) + try memberWritingClosure(val, writer) + if let cborValue = writer.cborValue { + array.append(cborValue) + } + } + self.cborValue = .array(array) + } + + public func writeNull() throws { + self.cborValue = .null + } + + private func encode(encoder: CBOREncoder) throws { + if let cborValue = self.cborValue { + // Encode the CBOR value directly if it exists + encoder.encode(cborValue) + } else if !children.isEmpty { + // Determine if this should be an array or a map + let allKeysAreIntegers = children.allSatisfy { Int($0.nodeInfo) != nil } + + if allKeysAreIntegers { + // Encode as an indefinite-length array + encoder.encode(.indef_array_start) + for child in children { + try child.encode(encoder: encoder) + } + encoder.encode(.indef_break) + } else { + // Encode as an indefinite-length map + encoder.encode(.indef_map_start) + for child in children { + guard !(child.cborValue == nil && child.children.isEmpty) else { + continue + } + encoder.encode(.text(child.nodeInfo)) // Key for the child + + if let cborValue = child.cborValue { + encoder.encode(cborValue) // Encode the value directly + } else { + try child.encode(encoder: encoder) // Recursively encode nested maps/arrays + } + } + encoder.encode(.indef_break) + } + } else { + // No value and no children: encode an empty map + encoder.encode(.indef_map_start) + encoder.encode(.indef_break) + } + } + + +} diff --git a/Sources/SmithyReadWrite/SmithyReader.swift b/Sources/SmithyReadWrite/SmithyReader.swift index 7904a12c1..968a071b3 100644 --- a/Sources/SmithyReadWrite/SmithyReader.swift +++ b/Sources/SmithyReadWrite/SmithyReader.swift @@ -208,4 +208,5 @@ public extension SmithyReader { public enum ReaderError: Error { case requiredValueNotPresent + case invalidBase64 } diff --git a/Sources/SmithyTestUtil/CBORComparator.swift b/Sources/SmithyTestUtil/CBORComparator.swift new file mode 100644 index 000000000..8824813d2 --- /dev/null +++ b/Sources/SmithyTestUtil/CBORComparator.swift @@ -0,0 +1,278 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +import Foundation +import AwsCommonRuntimeKit + +import Foundation +@_spi(SmithyReadWrite) import SmithyCBOR + +public struct CBORComparator { + /// Returns true if the logical CBOR values represented by the `Reader` instances are equal. + /// - Parameters: + /// - dataA: The first CBOR data object to compare. + /// - dataB: The second CBOR data object to compare. + /// - Returns: Returns true if the CBOR documents are equal. + public static func cborData(_ dataA: Data, isEqualTo dataB: Data) throws -> Bool { + let readerA = try Reader.from(data: dataA) + let readerB = try Reader.from(data: dataB) + return compareReaders(readerA, readerB) + } + + private static func compareReaders(_ readerA: Reader, _ readerB: Reader) -> Bool { + if !anyCBORValuesAreEqual(readerA.cborValue, readerB.cborValue) { + return false + } + + // Compare children recursively + let childrenA = readerA.children.sorted(by: { $0.nodeInfo < $1.nodeInfo }) + let childrenB = readerB.children.sorted(by: { $0.nodeInfo < $1.nodeInfo }) + + guard childrenA.count == childrenB.count else { + return false + } + + for (childA, childB) in zip(childrenA, childrenB) { + if childA.nodeInfo != childB.nodeInfo || !compareReaders(childA, childB) { + return false + } + } + + return true + } + +// private static func areMapsEqual(_ mapA: [String: CBORType], _ mapB: [String: CBORType]) -> Bool { +// // Ensure the keys match +// guard Set(mapA.keys) == Set(mapB.keys) else { return false } +// +// // Compare values for each key +// for key in mapA.keys { +// guard let valueA = mapA[key], let valueB = mapB[key] else { +// return false +// } +// +// switch (valueA, valueB) { +// case (.map(let nestedMapA), .map(let nestedMapB)): +// if !areMapsEqual(nestedMapA, nestedMapB) { +// return false +// } +//// case (.array(let nestedArrayA), .array(let nestedArrayB)): +//// if !areArraysEqual(nestedArrayA, nestedArrayB) { +//// return false +//// } +// default: +// if valueA != valueB && String(describing: valueA) != String(describing: valueB) { +// return false +// } +// } +// } +// return true +// } + + fileprivate static func anyCBORValuesAreEqual(_ lhs: CBORType?, _ rhs: CBORType?) -> Bool { + switch (lhs, rhs) { + case (nil, nil): + return true + case (nil, _), (_, nil): + return false + case (.null, .null): + return true + case (.undefined, .undefined): + return true + case (.bool(let lVal), .bool(let rVal)): + return lVal == rVal + case (.text(let lVal), .text(let rVal)): + return lVal == rVal + case (.bytes(let lVal), .bytes(let rVal)): + return lVal == rVal + case (.date(let lVal), .date(let rVal)): + return lVal == rVal + case (.tag(let lVal), .tag(let rVal)): + return lVal == rVal + + // Numeric comparisons + case (.uint(let lVal), .uint(let rVal)): + return lVal == rVal + case (.int(let lVal), .int(let rVal)): + return lVal == rVal + case (.double(let lVal), .double(let rVal)): + return (lVal.isNaN && rVal.isNaN) || lVal == rVal + + // Cross-type numeric comparisons + case (.uint(let lVal), .double(let rVal)): + return Double(lVal) == rVal + case (.int(let lVal), .double(let rVal)): + return Double(lVal) == rVal + case (.double(let lVal), .uint(let rVal)): + return lVal == Double(rVal) + case (.double(let lVal), .int(let rVal)): + return lVal == Double(rVal) + case (.uint(let lVal), .int(let rVal)): + return rVal >= 0 && UInt64(rVal) == lVal + case (.int(let lVal), .uint(let rVal)): + return lVal >= 0 && UInt64(lVal) == rVal + + // Arrays + case (.array(let lArr), .array(let rArr)): + return anyCBORArraysAreEqual(lArr, rArr) + + // Maps + case (.map(let lMap), .map(let rMap)): + return anyCBORDictsAreEqual(lMap, rMap) + + // Indefinite types + case (.indef_map_start, .indef_map_start), + (.indef_text_start, .indef_text_start), + (.indef_array_start, .indef_array_start), + (.indef_bytes_start, .indef_bytes_start), + (.indef_break, .indef_break): + return true + + default: + return false + } + } + + fileprivate static func anyCBORArraysAreEqual(_ lhs: [CBORType], _ rhs: [CBORType]) -> Bool { + guard lhs.count == rhs.count else { return false } + for i in 0.. Bool { + // Keys must match exactly. Order does not matter. + guard lhs.keys.sorted() == rhs.keys.sorted() else { return false } + for key in lhs.keys { + if !anyCBORValuesAreEqual(lhs[key], rhs[key]) { + return false + } + } + return true + } +} + +//public struct CBORComparator { +// /// Returns true if the CBOR documents, for the corresponding data objects, are equal. +// /// - Parameters: +// /// - dataA: The first CBOR data object to compare. +// /// - dataB: The second CBOR data object to compare. +// /// - Returns: Returns true if the CBOR documents are equal. +// public static func cborData(_ dataA: Data, isEqualTo dataB: Data) throws -> Bool { +// let decodedA = try CBORDecoder(data: [UInt8](dataA)) +// let decodedB = try CBORDecoder(data: [UInt8](dataB)) +// while decodedA.hasNext() && decodedB.hasNext() { +// let valueA = try decodedA.popNext() +// let valueB = try decodedB.popNext() +// if !anyCBORValuesAreEqual(valueA, valueB) { +// return false +// } +// } +// if decodedA.hasNext() || decodedB.hasNext() { +// return false // unequal lengths +// } +// return true +// } +//} +// +//fileprivate func anyCBORValuesAreEqual(_ lhs: CBORType?, _ rhs: CBORType?) -> Bool { +// switch (lhs, rhs) { +// case (nil, nil): +// return true +// case (nil, _), (_, nil): +// return false +// case (.null, .null): +// return true +// case (.undefined, .undefined): +// return true +// case (.bool(let lVal), .bool(let rVal)): +// return lVal == rVal +// case (.text(let lVal), .text(let rVal)): +// return lVal == rVal +// case (.bytes(let lVal), .bytes(let rVal)): +// return lVal == rVal +// case (.date(let lVal), .date(let rVal)): +// return lVal == rVal +// case (.tag(let lVal), .tag(let rVal)): +// return lVal == rVal +// +// case (.uint(let lVal), .uint(let rVal)): +// return lVal == rVal +// case (.int(let lVal), .int(let rVal)): +// return lVal == rVal +// case (.double(let lVal), .double(let rVal)): +// return lVal == rVal +// +// // Extended cross-type numeric comparisons +// case (.uint(let lVal), .int(let rVal)): +// guard rVal >= 0 else { return false } +// return UInt64(rVal) == lVal +// case (.int(let lVal), .uint(let rVal)): +// guard lVal >= 0 else { return false } +// return UInt64(lVal) == rVal +// case (.uint(let lVal), .double(let rVal)): +// return Double(lVal) == rVal +// case (.int(let lVal), .double(let rVal)): +// return Double(lVal) == rVal +// case (.double(let lVal), .uint(let rVal)): +// return lVal == Double(rVal) +// case (.double(let lVal), .int(let rVal)): +// return lVal == Double(rVal) +// +// // New equivalency for integer and float with the same value +// case (.double(let lVal), .uint(let rVal)): +// return lVal == Double(rVal) +// case (.double(let lVal), .int(let rVal)): +// return lVal == Double(rVal) +// case (.uint(let lVal), .double(let rVal)): +// return Double(lVal) == rVal +// case (.int(let lVal), .double(let rVal)): +// return Double(lVal) == rVal +// +// case (.array(let lArr), .array(let rArr)): +// return anyCBORArraysAreEqual(lArr, rArr) +// case (.map(let lMap), .map(let rMap)): +// return anyCBORDictsAreEqual(lMap, rMap) +// +// case (.indef_map_start, .indef_map_start): +// return true +// case (.indef_text_start, .indef_text_start): +// return true +// case (.indef_array_start, .indef_array_start): +// return true +// case (.indef_bytes_start, .indef_bytes_start): +// return true +// case (.indef_break, .indef_break): +// return true +// +// default: +// return false +// } +//} +// +//fileprivate func anyCBORArraysAreEqual(_ lhs: [CBORType], _ rhs: [CBORType]) -> Bool { +// guard lhs.count == rhs.count else { return false } +// for i in 0.. Bool { +// // Keys must match exactly. Order does not matter. +// guard lhs.keys.sorted() == rhs.keys.sorted() else { return false } +// for key in lhs.keys { +// if !anyCBORValuesAreEqual(lhs[key], rhs[key]) { +// return false +// } +// } +// return true +//} diff --git a/Sources/SmithyTestUtil/RequestTestUtil/HttpRequestTestBase.swift b/Sources/SmithyTestUtil/RequestTestUtil/HttpRequestTestBase.swift index 9049183c3..d6331f644 100644 --- a/Sources/SmithyTestUtil/RequestTestUtil/HttpRequestTestBase.swift +++ b/Sources/SmithyTestUtil/RequestTestUtil/HttpRequestTestBase.swift @@ -20,6 +20,7 @@ public enum HTTPBodyContentType { case xml case json case formURL + case cbor } open class HttpRequestTestBase: XCTestCase { @@ -303,6 +304,8 @@ open class HttpRequestTestBase: XCTestCase { XCTAssertJSONDataEqual(actual, expected, message(), file: file, line: line) case .formURL: XCTAssertFormURLDataEqual(actual, expected, message(), file: file, line: line) + case .cbor: + XCTAssertCBORDataEqual(actual, expected, message(), file: file, line: line) } } else if expected != nil && actual == nil { XCTFail("actual data in ByteStream is nil but expected is not", file: file, line: line) diff --git a/Sources/SmithyTestUtil/XCTAssertions.swift b/Sources/SmithyTestUtil/XCTAssertions.swift index a53212fdc..023ef3547 100644 --- a/Sources/SmithyTestUtil/XCTAssertions.swift +++ b/Sources/SmithyTestUtil/XCTAssertions.swift @@ -6,6 +6,7 @@ // import Foundation +import AwsCommonRuntimeKit import XCTest public func XCTAssertJSONDataEqual( @@ -30,6 +31,24 @@ public func XCTAssertJSONDataEqual( } } +public func XCTAssertCBORDataEqual( + _ expression1: @autoclosure () throws -> Data, + _ expression2: @autoclosure () throws -> Data, + _ message: @autoclosure () -> String = "", + file: StaticString = #filePath, + line: UInt = #line +) { + do { + let data1 = try expression1() + let data2 = try expression2() + + XCTAssertTrue(try CBORComparator.cborData(data1, isEqualTo: data2), message(), file: file, line: line) + } catch { + XCTFail("Failed to evaluate CBOR with error: \(error)", file: file, line: line) + } +} + + public func XCTAssertXMLDataEqual( _ expression1: @autoclosure () throws -> Data, _ expression2: @autoclosure () throws -> Data, diff --git a/gradle.properties b/gradle.properties index 0fddf21da..d6f7479d7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ kotlin.code.style=official # config # codegen -smithyVersion=1.52.1 +smithyVersion=1.53.0 smithyGradleVersion=0.6.0 # kotlin diff --git a/smithy-swift-codegen/build.gradle.kts b/smithy-swift-codegen/build.gradle.kts index 06c2a7112..39d1141e8 100644 --- a/smithy-swift-codegen/build.gradle.kts +++ b/smithy-swift-codegen/build.gradle.kts @@ -37,6 +37,7 @@ dependencies { api("org.jsoup:jsoup:$jsoupVersion") implementation("software.amazon.smithy:smithy-protocol-test-traits:$smithyVersion") implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion") + implementation("software.amazon.smithy:smithy-protocol-traits:$smithyVersion") testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion") testImplementation("io.kotest:kotest-assertions-core-jvm:$kotestVersion") implementation("software.amazon.smithy:smithy-rules-engine:$smithyVersion") diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/ShapeValueGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/ShapeValueGenerator.kt index 673da8e55..e5b7ec7ed 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/ShapeValueGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/ShapeValueGenerator.kt @@ -28,6 +28,7 @@ import software.amazon.smithy.model.traits.EnumTrait import software.amazon.smithy.model.traits.StreamingTrait import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.model.toMemberNames +import software.amazon.smithy.swift.codegen.swiftmodules.FoundationTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyStreamsTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTimestampsTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTypes @@ -160,11 +161,12 @@ class ShapeValueGenerator( } ShapeType.BLOB -> { if (shape.hasTrait()) { - writer.writeInline(".stream(\$N(data: ", SmithyStreamsTypes.Core.BufferedStream) - ".data(using: .utf8)!, isClosed: true))" + writer.writeInline(".stream(\$N(data: \$N(", SmithyStreamsTypes.Core.BufferedStream, FoundationTypes.Data) + ".utf8), isClosed: true))" } else { // TODO: properly handle this optional with an unwrapped statement before it's passed as a value to a shape. - ".data(using: .utf8)!" + writer.writeInline("\$N(", FoundationTypes.Data) + ".utf8)" } } else -> { "" } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftDependency.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftDependency.kt index 6e3e5f916..2b508915a 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftDependency.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftDependency.kt @@ -80,6 +80,7 @@ class SwiftDependency( val SMITHY_JSON = smithySwiftDependency("SmithyJSON") val SMITHY_FORM_URL = smithySwiftDependency("SmithyFormURL") val SMITHY_WAITERS_API = smithySwiftDependency("SmithyWaitersAPI") + val SMITHY_CBOR = smithySwiftDependency("SmithyCBOR") fun smithySwiftDependency(name: String): SwiftDependency { return SwiftDependency( diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt index a1234bd82..4942e639b 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSymbolProvider.kt @@ -380,14 +380,14 @@ class SwiftSymbolProvider(private val model: Model, val swiftSettings: SwiftSett if (shape.hasTrait()) { { writer -> writer.format( - "\$N.data(\$N(\"$literal\".utf8))", + "\$N.data(\$N(base64Encoded: \"$literal\"))", SmithyTypes.ByteStream, FoundationTypes.Data ) } } else { { writer -> - writer.format("\$N(\"$literal\".utf8)", FoundationTypes.Data) + writer.format("\$N(base64Encoded: \"$literal\")", FoundationTypes.Data) } } ) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SyntheticClone.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SyntheticClone.kt index b7b85c157..3b0c71fa1 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SyntheticClone.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SyntheticClone.kt @@ -58,6 +58,6 @@ class SyntheticClone private constructor(builder: Builder) : AbstractTrait(ID, b } init { - archetype = builder.archetype + archetype = requireNotNull(builder.archetype) { "Original ShapeId is required for SyntheticClone trait" } } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolClientGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolClientGenerator.kt index 3906da510..e777ec0e5 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolClientGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolClientGenerator.kt @@ -4,6 +4,7 @@ */ package software.amazon.smithy.swift.codegen.integration +import jdk.jfr.ContentType import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.Model import software.amazon.smithy.model.knowledge.OperationIndex diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt index dc820554b..c46307b05 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt @@ -6,11 +6,13 @@ package software.amazon.smithy.swift.codegen.integration import software.amazon.smithy.codegen.core.CodegenException import software.amazon.smithy.model.shapes.Shape +import software.amazon.smithy.protocol.traits.Rpcv2CborTrait import software.amazon.smithy.protocoltests.traits.HttpResponseTestCase import software.amazon.smithy.swift.codegen.SwiftDependency -import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ResponseErrorClosureUtils +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.* import software.amazon.smithy.swift.codegen.model.toUpperCamelCase import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes +import java.util.* open class HttpProtocolUnitTestErrorGenerator protected constructor(builder: Builder) : HttpProtocolUnitTestResponseGenerator(builder) { @@ -76,7 +78,25 @@ open class HttpProtocolUnitTestErrorGenerator protected constructor(builder: Bui override fun renderExpectedBody(test: HttpResponseTestCase) { if (test.body.isPresent && test.body.get().isNotBlank()) { - val data = writer.format("Data(\"\"\"\n\$L\n\"\"\".utf8)", test.body.get().replace("\\\"", "\\\\\"")) + val isCbor = ctx.service.requestWireProtocol == WireProtocol.CBOR + || ctx.service.awsProtocol == AWSProtocol.RPCV2_CBOR + || ctx.service.responseWireProtocol == WireProtocol.CBOR + || test.protocol == Rpcv2CborTrait.ID + + val bodyContent = test.body.get().replace("\\\"", "\\\\\"") + val data: String = if (isCbor) { + // Attempt to decode Base64 data once for CBOR + try { + val decodedBytes = Base64.getDecoder().decode(bodyContent) + "Data([${decodedBytes.joinToString(", ") { byte -> "0x%02X".format(byte) }}])" + } catch (e: IllegalArgumentException) { + // Fallback to Swift Data representation for invalid Base64 + "Data(\"\"\"\n$bodyContent\n\"\"\".utf8)" + } + } else { + // Non-CBOR protocols default + "Data(\"\"\"\n$bodyContent\n\"\"\".utf8)" + } writer.write("content: .data(\$L)", data) } else { writer.write("content: nil") diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt index 4586e8c28..950b28da8 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt @@ -17,6 +17,7 @@ import software.amazon.smithy.swift.codegen.model.toLowerCamelCase import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyStreamsTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTestUtilTypes +import java.util.* open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: Builder) : HttpProtocolUnitTestGenerator(builder) { @@ -58,14 +59,35 @@ open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: B if (test.body.isPresent && test.body.get().isNotBlank()) { operation.input.ifPresent { val inputShape = model.expectShape(it) as StructureShape - val data = writer.format( - "Data(\"\"\"\n\$L\n\"\"\".utf8)", - test.body.get().replace("\\\"", "\\\\\""), - ) - // depending on the shape of the input, wrap the expected body in a stream or not + val data = if (ctx.service.requestWireProtocol == WireProtocol.CBOR) { + // Attempt to decode the Base64 data + try { + val decodedBytes = Base64.getDecoder().decode(test.body.get()) + // Format decoded bytes into a Swift Data array representation + "Data([${decodedBytes.joinToString(", ") { byte -> "0x%02X".format(byte) }}])" + } catch (e: IllegalArgumentException) { + // Fallback to original string if decoding fails + writer.format( + "Data(\"\"\"\n\$L\n\"\"\".utf8)", + test.body.get().replace("\\\"", "\\\\\"") + ) + } + } else { + // Default case for non-CBOR protocols + writer.format( + "Data(\"\"\"\n\$L\n\"\"\".utf8)", + test.body.get().replace("\\\"", "\\\\\"") + ) + } + + // Depending on the shape of the input, wrap the expected body in a stream or not if (inputShape.hasStreamingMember(model)) { - // wrapping to CachingStream required for test asserts which reads body multiple times - writer.write("body: .stream(\$N(data: \$L, isClosed: true)),", SmithyStreamsTypes.Core.BufferedStream, data) + // Wrapping to CachingStream required for test asserts which reads body multiple times + writer.write( + "body: .stream(\$N(data: \$L, isClosed: true)),", + SmithyStreamsTypes.Core.BufferedStream, + data + ) } else { writer.write("body: .data(\$L),", data) } @@ -133,6 +155,7 @@ open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: B WireProtocol.XML -> ".xml" WireProtocol.JSON -> ".json" WireProtocol.FORM_URL -> ".formURL" + WireProtocol.CBOR -> ".cbor" } writer.write("try await self.genericAssertEqualHttpBodyData(expected: expectedHttpBody!, actual: actualHttpBody!, contentType: \$L)", contentType) } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt index 541c6defe..d099f1791 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt @@ -11,13 +11,15 @@ import software.amazon.smithy.model.traits.DefaultTrait import software.amazon.smithy.model.traits.HttpHeaderTrait import software.amazon.smithy.model.traits.HttpPayloadTrait import software.amazon.smithy.model.traits.HttpPrefixHeadersTrait +import software.amazon.smithy.protocol.traits.Rpcv2CborTrait import software.amazon.smithy.protocoltests.traits.HttpResponseTestCase import software.amazon.smithy.swift.codegen.ShapeValueGenerator import software.amazon.smithy.swift.codegen.hasStreamingMember -import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ResponseClosureUtils +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.* import software.amazon.smithy.swift.codegen.model.RecursiveShapeBoxer import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.swiftmodules.SmithyStreamsTypes +import java.util.Base64 /** * Generates HTTP protocol unit tests for `httpResponseTest` cases @@ -60,35 +62,49 @@ open class HttpProtocolUnitTestResponseGenerator protected constructor(builder: } open fun renderExpectedBody(test: HttpResponseTestCase) { - if (test.body.isPresent) { - operation.output.ifPresent { - if (test.body.get().isNotBlank()) { - val outputShape = model.expectShape(it) as StructureShape - val data = writer.format( - "Data(\"\"\"\n\$L\n\"\"\".utf8)", - test.body.get().replace("\\\"", "\\\\\"") + if (!test.body.isPresent) { + writer.write("content: nil") + return + } + + val bodyContent = test.body.get() + val isCbor = ctx.service.requestWireProtocol == WireProtocol.CBOR + || ctx.service.awsProtocol == AWSProtocol.RPCV2_CBOR + || ctx.service.responseWireProtocol == WireProtocol.CBOR + || test.protocol == Rpcv2CborTrait.ID + + val data: String = if (isCbor) { + // Attempt to decode Base64 data once for CBOR + try { + val decodedBytes = Base64.getDecoder().decode(bodyContent) + "Data([${decodedBytes.joinToString(", ") { byte -> "0x%02X".format(byte) }}])" + } catch (e: IllegalArgumentException) { + // Fallback to Swift Data representation for invalid Base64 + "Data(\"\"\"\n$bodyContent\n\"\"\".utf8)" + } + } else { + // Non-CBOR protocols default + "Data(\"\"\"\n$bodyContent\n\"\"\".utf8)" + } + + operation.output.ifPresent { + val outputShape = model.expectShape(it) as StructureShape + + if (bodyContent.isNotBlank()) { + if (outputShape.hasStreamingMember(model)) { + writer.write( + "content: .stream(\$N(data: \$L, isClosed: true))", + SmithyStreamsTypes.Core.BufferedStream, + data ) - // depending on the shape of the output, we may need to wrap the body in a stream - if (outputShape.hasStreamingMember(model)) { - // wrapping to CachingStream required for test asserts which reads body multiple times - writer.write( - "content: .stream(\$N(data: \$L, isClosed: true))", - SmithyStreamsTypes.Core.BufferedStream, - data - ) - } else { - writer.write("content: .data(\$L)", data) - } - } else if (test.body.get().isBlank() && bodyHasDefaultValue()) { - // Expected body is blank but not because it's nil, but because it's a default empty blob value - writer.write("content: .data(Data(\"\".utf8))") } else { - // Expected body is blank and underlying member shape does not have default values. - writer.write("content: nil") + writer.write("content: .data($data)") } + } else if (bodyContent.isBlank() && bodyHasDefaultValue()) { + writer.write("content: .data($data)") + } else { + writer.write("content: nil") } - } else { - writer.write("content: nil") } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt index 20ba78e33..c23d1dd8c 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt @@ -24,9 +24,9 @@ import software.amazon.smithy.swift.codegen.model.expectTrait import software.amazon.smithy.swift.codegen.model.hasTrait open class StaticHttpBindingResolver( - private val context: ProtocolGenerator.GenerationContext, - private val httpTrait: HttpTrait, - private val defaultContentType: String + protected val context: ProtocolGenerator.GenerationContext, + protected val httpTrait: HttpTrait, + protected val defaultContentType: String ) : HttpBindingResolver { override fun httpTrait(operationShape: OperationShape): HttpTrait { return httpTrait diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt index 510c1cd6b..e0bbde818 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeDecodeGenerator.kt @@ -243,14 +243,16 @@ open class MemberShapeDecodeGenerator( writer.addImport(FoundationTypes.Data) return if (targetShape.hasTrait()) { writer.format( - " ?? \$N.data(\$N(\"$value\".utf8))", + " ?? \$N.data(\$N(base64Encoded: \$S))", SmithyTypes.ByteStream, - FoundationTypes.Data + FoundationTypes.Data, + value, ) } else { writer.format( - " ?? \$N(\"$value\".utf8)", - FoundationTypes.Data + " ?? \$N(base64Encoded: \$S)", + FoundationTypes.Data, + value, ) } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeEncodeGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeEncodeGenerator.kt index ba53bd397..c4fc0cd4d 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeEncodeGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeEncodeGenerator.kt @@ -161,7 +161,7 @@ object TimestampUtils { private fun defaultTimestampFormat(ctx: ProtocolGenerator.GenerationContext): String { return when (ctx.service.requestWireProtocol) { WireProtocol.XML, WireProtocol.FORM_URL -> TimestampFormatTrait.DATE_TIME - WireProtocol.JSON -> TimestampFormatTrait.EPOCH_SECONDS + WireProtocol.JSON, WireProtocol.CBOR -> TimestampFormatTrait.EPOCH_SECONDS } } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/NodeInfoUtils.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/NodeInfoUtils.kt index d89e71a61..eb8521c0f 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/NodeInfoUtils.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/NodeInfoUtils.kt @@ -76,6 +76,13 @@ class NodeInfoUtils( return member.memberName } } + WireProtocol.CBOR -> { + if (forRootNode) { + return "\"\"" + } else { + return member.memberName + } + } } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/struct/StructEncodeGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/struct/StructEncodeGenerator.kt index b52e72b60..b3abad4d1 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/struct/StructEncodeGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/struct/StructEncodeGenerator.kt @@ -19,6 +19,7 @@ import software.amazon.smithy.swift.codegen.integration.serde.readwrite.requestW import software.amazon.smithy.swift.codegen.integration.serde.readwrite.responseWireProtocol import software.amazon.smithy.swift.codegen.model.ShapeMetadata import software.amazon.smithy.swift.codegen.model.isError +import software.amazon.smithy.swift.codegen.swiftmodules.SmithyCBORTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyFormURLTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyJSONTypes @@ -69,6 +70,7 @@ val ServiceShape.writerSymbol: Symbol get() = when (requestWireProtocol) { WireProtocol.XML -> SmithyXMLTypes.Writer WireProtocol.JSON -> SmithyJSONTypes.Writer + WireProtocol.CBOR -> SmithyCBORTypes.Writer WireProtocol.FORM_URL -> SmithyFormURLTypes.Writer } @@ -76,5 +78,6 @@ val ServiceShape.readerSymbol: Symbol get() = when (responseWireProtocol) { WireProtocol.XML -> SmithyXMLTypes.Reader WireProtocol.JSON -> SmithyJSONTypes.Reader + WireProtocol.CBOR -> SmithyCBORTypes.Reader WireProtocol.FORM_URL -> throw Exception("Reading from Form URL data not supported") } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/AddOperationShapes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/AddOperationShapes.kt index 991c0e0fa..30988fc3c 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/AddOperationShapes.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/AddOperationShapes.kt @@ -12,6 +12,7 @@ import software.amazon.smithy.model.shapes.ServiceShape import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.model.shapes.ShapeId import software.amazon.smithy.model.shapes.StructureShape +import software.amazon.smithy.model.traits.UnitTypeTrait import software.amazon.smithy.model.traits.XmlNameTrait import software.amazon.smithy.swift.codegen.SyntheticClone import java.util.logging.Logger @@ -80,6 +81,7 @@ class AddOperationShapes { opShapeId.name + suffix ) ) + .addTrait(SyntheticClone.builder().archetype(UnitTypeTrait.UNIT).build()) .build() } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/ShapeExt.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/ShapeExt.kt index 7d56a63a6..1c0938157 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/ShapeExt.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/model/ShapeExt.kt @@ -39,7 +39,7 @@ inline fun Model.expectShape(shapeId: ShapeId): T = inline fun Model.expectShape(shapeId: String): T = this.expectShape(ShapeId.from(shapeId), T::class.java) -internal fun Shape.targetOrSelf(model: Model) = when (this) { +fun Shape.targetOrSelf(model: Model) = when (this) { is MemberShape -> model.expectShape(this.target) else -> this } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyCBORTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyCBORTypes.kt new file mode 100644 index 000000000..8685e8416 --- /dev/null +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyCBORTypes.kt @@ -0,0 +1,26 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ +package software.amazon.smithy.swift.codegen.swiftmodules + +import software.amazon.smithy.codegen.core.Symbol +import software.amazon.smithy.swift.codegen.SwiftDeclaration +import software.amazon.smithy.swift.codegen.SwiftDependency + +object SmithyCBORTypes { + val Writer = runtimeSymbol("Writer", SwiftDeclaration.CLASS, listOf(SmithyReadWriteTypes.SmithyWriter)) + val Reader = runtimeSymbol("Reader", SwiftDeclaration.CLASS, listOf(SmithyReadWriteTypes.SmithyReader)) +} + +private fun runtimeSymbol( + name: String, + declaration: SwiftDeclaration, + additionalImports: List = emptyList(), +): Symbol = SwiftSymbol.make( + name, + declaration, + SwiftDependency.SMITHY_CBOR, + additionalImports, + listOf("SmithyReadWrite"), +) diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRPCv2CBORProtocolGenerator.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRPCv2CBORProtocolGenerator.kt new file mode 100644 index 000000000..7d498fdd1 --- /dev/null +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRPCv2CBORProtocolGenerator.kt @@ -0,0 +1,46 @@ +package software.amazon.smithy.swift.codegen.protocolgeneratormocks + +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.protocol.traits.Rpcv2CborTrait +import software.amazon.smithy.swift.codegen.integration.DefaultHTTPProtocolCustomizations +import software.amazon.smithy.swift.codegen.integration.HTTPBindingProtocolGenerator +import software.amazon.smithy.swift.codegen.integration.HttpProtocolTestGenerator +import software.amazon.smithy.swift.codegen.integration.HttpProtocolUnitTestErrorGenerator +import software.amazon.smithy.swift.codegen.integration.HttpProtocolUnitTestRequestGenerator +import software.amazon.smithy.swift.codegen.integration.HttpProtocolUnitTestResponseGenerator +import software.amazon.smithy.swift.codegen.integration.ProtocolGenerator +import software.amazon.smithy.swift.codegen.requestandresponse.TestHttpProtocolClientGeneratorFactory + +class MockRPCv2CBORProtocolCustomizations() : DefaultHTTPProtocolCustomizations() + +class MockHTTPRPCv2CBORProtocolGenerator : HTTPBindingProtocolGenerator(MockRPCv2CBORProtocolCustomizations()) { + override val defaultContentType: String = "application/cbor" + override val protocol: ShapeId = Rpcv2CborTrait.ID + override val httpProtocolClientGeneratorFactory = TestHttpProtocolClientGeneratorFactory() + override val shouldRenderEncodableConformance = false + + override fun addProtocolSpecificMiddleware(ctx: ProtocolGenerator.GenerationContext, operation: OperationShape) { + // Intentionally empty + } + + override fun generateProtocolUnitTests(ctx: ProtocolGenerator.GenerationContext): Int { + val requestTestBuilder = HttpProtocolUnitTestRequestGenerator.Builder() + val responseTestBuilder = HttpProtocolUnitTestResponseGenerator.Builder() + val errorTestBuilder = HttpProtocolUnitTestErrorGenerator.Builder() + + return HttpProtocolTestGenerator( + ctx, + requestTestBuilder, + responseTestBuilder, + errorTestBuilder, + customizations, + getProtocolHttpBindingResolver(ctx, defaultContentType), + ).generateProtocolTests() + } +} From 50ceb88f37a2a0b16969ea21af0677143d75b2e7 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 19 Dec 2024 19:07:22 -0500 Subject: [PATCH 05/25] ALL TESTS PASSING WOOOO --- Sources/SmithyCBOR/Reader/Reader.swift | 163 ++----------------------- Sources/SmithyCBOR/Writer/Writer.swift | 150 ++++++++++++----------- 2 files changed, 91 insertions(+), 222 deletions(-) diff --git a/Sources/SmithyCBOR/Reader/Reader.swift b/Sources/SmithyCBOR/Reader/Reader.swift index 1851dc397..bf314fe74 100644 --- a/Sources/SmithyCBOR/Reader/Reader.swift +++ b/Sources/SmithyCBOR/Reader/Reader.swift @@ -20,15 +20,12 @@ import SmithyReadWrite public final class Reader: SmithyReader { public typealias NodeInfo = String - // The CBOR value represented as a CBORType public let cborValue: CBORType? public let nodeInfo: NodeInfo public internal(set) var children: [Reader] = [] public internal(set) weak var parent: Reader? public var hasContent: Bool { cborValue != nil && cborValue != .null } - // MARK: - Initializer - public init(nodeInfo: NodeInfo, cborValue: CBORType?, parent: Reader? = nil) { self.nodeInfo = nodeInfo self.cborValue = cborValue @@ -36,12 +33,15 @@ public final class Reader: SmithyReader { self.children = Self.children(from: cborValue, parent: self) } - enum ReaderError: Error { - case unexpectedType(expected: String, actual: CBORType) - case invalidData(description: String) - case missingKey - case indefiniteBreakNotFound - case requiredValueNotPresent + public static func from(data: Data) throws -> Reader { + let decoder = try CBORDecoder(data: [UInt8](data)) + let rootValue: CBORType + if decoder.hasNext() { + rootValue = try decoder.popNext() + } else { + rootValue = .null + } + return Reader(nodeInfo: "", cborValue: rootValue, parent: nil) } private static func children(from cborValue: CBORType?, parent: Reader) -> [Reader] { @@ -49,16 +49,12 @@ public final class Reader: SmithyReader { switch cborValue { case .map(let map): for (key, value) in map { - // Ensure key is treated as a child node - let valueReader = Reader(nodeInfo: "\(key)", cborValue: value, parent: parent) - valueReader.children = Self.children(from: value, parent: valueReader) // Recursively process nested children - - children.append(valueReader) + let child = Reader(nodeInfo: key, cborValue: value, parent: parent) + children.append(child) } case .array(let array): for (index, value) in array.enumerated() { let child = Reader(nodeInfo: "\(index)", cborValue: value, parent: parent) - child.children = Self.children(from: value, parent: child) // Recursively process nested children children.append(child) } default: @@ -67,143 +63,6 @@ public final class Reader: SmithyReader { return children } - // MARK: - SmithyReader Protocol Methods - - public static func from(data: Data) throws -> Reader { - let decoder = try CBORDecoder(data: [UInt8](data)) - var rootValue: CBORType? - - if decoder.hasNext() { - rootValue = try decoder.popNext() - } else { - rootValue = .null - } - - switch rootValue { - case .indef_map_start: - let map = try decodeIndefiniteMap(decoder) - return Reader(nodeInfo: "Indefinite Map", cborValue: .map(map)) - - case .indef_array_start: - let array = try decodeIndefiniteArray(decoder) - return Reader(nodeInfo: "Indefinite Array", cborValue: .array(array)) - - case .indef_text_start: - let text = try decodeIndefiniteText(decoder) - return Reader(nodeInfo: "Indefinite Text", cborValue: .text(text)) - - case .indef_bytes_start: - let bytes = try decodeIndefiniteBytes(decoder) - return Reader(nodeInfo: "Indefinite Bytes", cborValue: .bytes(bytes)) - - default: - return Reader(nodeInfo: "", cborValue: rootValue) - } - } - - // can be nested - private static func decodeIndefiniteMap(_ decoder: CBORDecoder) throws -> [String: CBORType] { - var map: [String: CBORType] = [:] - while decoder.hasNext() { - let key = try decoder.popNext() - if case .indef_break = key { break } - - guard case .text(let keyString) = key else { - throw ReaderError.unexpectedType(expected: "text", actual: key) - } - - let value = try decoder.popNext() - if case .indef_break = value { break } - - // Check if the value is another indefinite structure and decode it. - let decodedValue: CBORType - switch value { - case .indef_map_start: - decodedValue = .map(try decodeIndefiniteMap(decoder)) - case .indef_array_start: - decodedValue = .array(try decodeIndefiniteArray(decoder)) - case .indef_text_start: - decodedValue = .text(try decodeIndefiniteText(decoder)) - case .indef_bytes_start: - decodedValue = .bytes(try decodeIndefiniteBytes(decoder)) - default: - decodedValue = value - } - - map[keyString] = decodedValue - } - return map - } - - // can be nested - private static func decodeIndefiniteArray(_ decoder: CBORDecoder) throws -> [CBORType] { - var array: [CBORType] = [] - - while decoder.hasNext() { - let value = try decoder.popNext() - if case .indef_break = value { break } - - let decodedValue: CBORType - switch value { - case .indef_map_start: - decodedValue = .map(try decodeIndefiniteMap(decoder)) - case .indef_array_start: - decodedValue = .array(try decodeIndefiniteArray(decoder)) - case .indef_text_start: - decodedValue = .text(try decodeIndefiniteText(decoder)) - case .indef_bytes_start: - decodedValue = .bytes(try decodeIndefiniteBytes(decoder)) - default: - decodedValue = value - } - - array.append(decodedValue) - } - - return array - } - - private static func decodeIndefiniteText(_ decoder: CBORDecoder) throws -> String { - var text = "" - - while decoder.hasNext() { - let chunk = try decoder.popNext() - - // Handle the end of the indefinite-length text - if case .indef_break = chunk { - break - } - - // Append text chunks - if case .text(let chunkString) = chunk { - text += chunkString - } else if case .indef_text_start = chunk { - // Handle nested indefinite text starts (recursion) - text += try decodeIndefiniteText(decoder) - } else { - // Unexpected type - throw ReaderError.unexpectedType(expected: "text or indef_text_start", actual: chunk) - } - } - - return text - } - - // wont be nested - private static func decodeIndefiniteBytes(_ decoder: CBORDecoder) throws -> Data { - var bytes = Data() - while decoder.hasNext() { - let chunk = try decoder.popNext() - if case .indef_break = chunk { break } - - guard case .bytes(let chunkData) = chunk else { - throw ReaderError.unexpectedType(expected: "bytes", actual: chunk) - } - bytes.append(chunkData) - } - return bytes - } - public subscript(nodeInfo: NodeInfo) -> Reader { if let match = children.first(where: { $0.nodeInfo == nodeInfo }) { return match diff --git a/Sources/SmithyCBOR/Writer/Writer.swift b/Sources/SmithyCBOR/Writer/Writer.swift index 54a5b98d8..cc28c28b9 100644 --- a/Sources/SmithyCBOR/Writer/Writer.swift +++ b/Sources/SmithyCBOR/Writer/Writer.swift @@ -18,25 +18,21 @@ import Foundation @_spi(SmithyReadWrite) public final class Writer: SmithyWriter { public typealias NodeInfo = String - + let nodeInfo: NodeInfo var cborValue: CBORType? var children: [Writer] = [] weak var parent: Writer? - - // MARK: - Initializer - + public required init(nodeInfo: NodeInfo) { self.nodeInfo = nodeInfo } - + public required init(nodeInfo: NodeInfo, parent: Writer? = nil) { self.nodeInfo = nodeInfo self.parent = parent } - // MARK: - SmithyWriter Protocol Methods - public func data() throws -> Data { let encoder = try CBOREncoder() try encode(encoder: encoder) @@ -45,6 +41,45 @@ public final class Writer: SmithyWriter { return Data(encodedBytes) } + private func encode(encoder: CBOREncoder) throws { + if let cborValue = self.cborValue { + // Encode the CBOR value directly if it exists + encoder.encode(cborValue) + } else if !children.isEmpty { + // Determine if this should be an array or a map + let allKeysAreIntegers = children.allSatisfy { Int($0.nodeInfo) != nil } + + if allKeysAreIntegers { + // Encode as an indefinite-length array + encoder.encode(.indef_array_start) + for child in children { + try child.encode(encoder: encoder) + } + encoder.encode(.indef_break) + } else { + // Encode as an indefinite-length map + encoder.encode(.indef_map_start) + for child in children { + guard !(child.cborValue == nil && child.children.isEmpty) else { + continue + } + encoder.encode(.text(child.nodeInfo)) // Key for the child + + if let cborValue = child.cborValue { + encoder.encode(cborValue) // Encode the value directly + } else { + try child.encode(encoder: encoder) // Recursively encode nested maps/arrays + } + } + encoder.encode(.indef_break) + } + } else { + // No value and no children: encode an empty map + encoder.encode(.indef_map_start) + encoder.encode(.indef_break) + } + } + public subscript(nodeInfo: NodeInfo) -> Writer { if let child = children.first(where: { $0.nodeInfo == nodeInfo }) { return child @@ -54,56 +89,56 @@ public final class Writer: SmithyWriter { return newChild } } - + public func write(_ value: Bool?) throws { guard let value else { return } self.cborValue = .bool(value) } - + public func write(_ value: String?) throws { guard let value else { return } self.cborValue = .text(value) } - + public func write(_ value: Float?) throws { guard let value else { return } self.cborValue = .double(Double(value)) } - + public func write(_ value: Double?) throws { guard let value else { return } self.cborValue = .double(value) } - + public func write(_ value: Int?) throws { guard let value else { return } self.cborValue = .int(Int64(value)) } - + public func write(_ value: Int8?) throws { guard let value else { return } self.cborValue = .int(Int64(value)) } - + public func write(_ value: Int16?) throws { guard let value else { return } self.cborValue = .int(Int64(value)) } - + public func write(_ value: UInt8?) throws { guard let value else { return } self.cborValue = .uint(UInt64(value)) } - + public func write(_ value: Data?) throws { guard let value else { return } self.cborValue = .bytes(value) } - + public func write(_ value: SmithyDocument?) throws { // No operation. Smithy document not supported in CBOR } - + public func writeTimestamp(_ value: Date?, format: TimestampFormat) throws { guard let value else { return } switch format { @@ -114,19 +149,19 @@ public final class Writer: SmithyWriter { self.cborValue = .text(string) } } - + public func write(_ value: T?) throws where T: RawRepresentable, T.RawValue == Int { if let rawValue = value?.rawValue { try write(rawValue) } } - + public func write(_ value: T?) throws where T: RawRepresentable, T.RawValue == String { if let rawValue = value?.rawValue { try write(rawValue) } } - + public func writeMap( _ value: [String: T]?, valueWritingClosure: (T, Writer) throws -> Void, @@ -136,11 +171,11 @@ public final class Writer: SmithyWriter { ) throws { guard let value else { return } var map: [String: CBORType] = [:] - + for (key, val) in value { let writer = self[key] try valueWritingClosure(val, writer) - + // If the writer itself doesn't have a cborValue, build it from its children if writer.cborValue == nil, !writer.children.isEmpty { var childMap: [String: CBORType] = [:] @@ -151,17 +186,17 @@ public final class Writer: SmithyWriter { } writer.cborValue = .map(childMap) // Construct the map for the writer } - + // Add to the parent map if let cborValue = writer.cborValue { map[key] = cborValue } } - + // Assign the constructed map to the current writer self.cborValue = .map(map) } - + public func writeList( _ value: [T]?, memberWritingClosure: (T, Writer) throws -> Void, @@ -169,59 +204,34 @@ public final class Writer: SmithyWriter { isFlattened: Bool ) throws { guard let value else { return } + var array: [CBORType] = [] + for val in value { - let writer = Writer(nodeInfo: "", parent: self) - try memberWritingClosure(val, writer) - if let cborValue = writer.cborValue { + // Create a child writer for each list element + let childWriter = Writer(nodeInfo: memberNodeInfo, parent: self) + try memberWritingClosure(val, childWriter) + + // If the child writer has a cborValue, add it to the array + if let cborValue = childWriter.cborValue { array.append(cborValue) + } else if !childWriter.children.isEmpty { + // If no cborValue but has children, create a map from its children + var childMap: [String: CBORType] = [:] + for child in childWriter.children { + if let childCborValue = child.cborValue { + childMap[child.nodeInfo] = childCborValue + } + } + array.append(.map(childMap)) // Append the constructed map } } + + // Assign the array to the current writer's cborValue self.cborValue = .array(array) } public func writeNull() throws { self.cborValue = .null } - - private func encode(encoder: CBOREncoder) throws { - if let cborValue = self.cborValue { - // Encode the CBOR value directly if it exists - encoder.encode(cborValue) - } else if !children.isEmpty { - // Determine if this should be an array or a map - let allKeysAreIntegers = children.allSatisfy { Int($0.nodeInfo) != nil } - - if allKeysAreIntegers { - // Encode as an indefinite-length array - encoder.encode(.indef_array_start) - for child in children { - try child.encode(encoder: encoder) - } - encoder.encode(.indef_break) - } else { - // Encode as an indefinite-length map - encoder.encode(.indef_map_start) - for child in children { - guard !(child.cborValue == nil && child.children.isEmpty) else { - continue - } - encoder.encode(.text(child.nodeInfo)) // Key for the child - - if let cborValue = child.cborValue { - encoder.encode(cborValue) // Encode the value directly - } else { - try child.encode(encoder: encoder) // Recursively encode nested maps/arrays - } - } - encoder.encode(.indef_break) - } - } else { - // No value and no children: encode an empty map - encoder.encode(.indef_map_start) - encoder.encode(.indef_break) - } - } - - } From ebde137a24db09ebe51344803ac261d531d0e422 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 24 Dec 2024 12:00:38 -0500 Subject: [PATCH 06/25] all tests passing now --- Package.swift | 2 +- Sources/SmithyCBOR/Writer/Writer.swift | 34 ++++++------------- .../HTTPBindingProtocolGenerator.kt | 9 ++--- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/Package.swift b/Package.swift index 21f64cd59..9cb77c2d2 100644 --- a/Package.swift +++ b/Package.swift @@ -56,7 +56,7 @@ let package = Package( ], dependencies: { var dependencies: [Package.Dependency] = [ - .package(url: "https://github.com/awslabs/aws-crt-swift.git", exact: "0.40.0"), + .package(url: "https://github.com/awslabs/aws-crt-swift.git", exact: "0.42.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), ] let isDocCEnabled = ProcessInfo.processInfo.environment["AWS_SWIFT_SDK_ENABLE_DOCC"] != nil diff --git a/Sources/SmithyCBOR/Writer/Writer.swift b/Sources/SmithyCBOR/Writer/Writer.swift index cc28c28b9..8ca423830 100644 --- a/Sources/SmithyCBOR/Writer/Writer.swift +++ b/Sources/SmithyCBOR/Writer/Writer.swift @@ -46,33 +46,21 @@ public final class Writer: SmithyWriter { // Encode the CBOR value directly if it exists encoder.encode(cborValue) } else if !children.isEmpty { - // Determine if this should be an array or a map - let allKeysAreIntegers = children.allSatisfy { Int($0.nodeInfo) != nil } - - if allKeysAreIntegers { - // Encode as an indefinite-length array - encoder.encode(.indef_array_start) - for child in children { - try child.encode(encoder: encoder) + // Encode as an indefinite-length map + encoder.encode(.indef_map_start) + for child in children { + guard !(child.cborValue == nil && child.children.isEmpty) else { + continue } - encoder.encode(.indef_break) - } else { - // Encode as an indefinite-length map - encoder.encode(.indef_map_start) - for child in children { - guard !(child.cborValue == nil && child.children.isEmpty) else { - continue - } - encoder.encode(.text(child.nodeInfo)) // Key for the child + encoder.encode(.text(child.nodeInfo)) // Key for the child - if let cborValue = child.cborValue { - encoder.encode(cborValue) // Encode the value directly - } else { - try child.encode(encoder: encoder) // Recursively encode nested maps/arrays - } + if let cborValue = child.cborValue { + encoder.encode(cborValue) // Encode the value directly + } else { + try child.encode(encoder: encoder) // Recursively encode nested maps/arrays } - encoder.encode(.indef_break) } + encoder.encode(.indef_break) } else { // No value and no children: encode an empty map encoder.encode(.indef_map_start) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HTTPBindingProtocolGenerator.kt index 0346bc967..308cc4e11 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 @@ -415,16 +415,12 @@ abstract class HTTPBindingProtocolGenerator( operationMiddleware.appendMiddleware(operation, RetryMiddleware(ctx.model, ctx.symbolProvider, retryErrorInfoProviderSymbol)) operationMiddleware.appendMiddleware(operation, SignerMiddleware(ctx.model, ctx.symbolProvider)) addProtocolSpecificMiddleware(ctx, operation) - /* - * Auth scheme middleware must be appended to codegen operation stack AFTER protocol specific middleware is appended. - * This is because endpoint middleware must be generated into client operation stack first with position: .before, - * AFTER which auth scheme must be generated into client operation stack with position: .before, which ensures - * auth scheme middleware is executed FIRST before endpoint middleware is executed, as SRA flow defines. - */ operationMiddleware.appendMiddleware(operation, AuthSchemeMiddleware(ctx.model, ctx.symbolProvider)) for (integration in ctx.integrations) { integration.customizeMiddleware(ctx, operation, operationMiddleware) } + // must be last to support adding business metrics + addUserAgentMiddleware(ctx, operation) } } @@ -457,6 +453,7 @@ abstract class HTTPBindingProtocolGenerator( } protected abstract fun addProtocolSpecificMiddleware(ctx: ProtocolGenerator.GenerationContext, operation: OperationShape) + protected abstract fun addUserAgentMiddleware(ctx: ProtocolGenerator.GenerationContext, operation: OperationShape) /** * Get the operations with HTTP Bindings. From 6b1e91cbeee400b223583c04fb9d23f81531f4a6 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 24 Dec 2024 13:52:13 -0500 Subject: [PATCH 07/25] add getter for private context --- .../codegen/integration/HTTPBindingProtocolGenerator.kt | 1 + .../protocols/core/StaticHttpBindingResolver.kt | 8 +++++--- 2 files changed, 6 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 308cc4e11..80d5b39e9 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 @@ -453,6 +453,7 @@ abstract class HTTPBindingProtocolGenerator( } protected abstract fun addProtocolSpecificMiddleware(ctx: ProtocolGenerator.GenerationContext, operation: OperationShape) + protected abstract fun addUserAgentMiddleware(ctx: ProtocolGenerator.GenerationContext, operation: OperationShape) /** diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt index c23d1dd8c..0428792e2 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt @@ -24,10 +24,12 @@ import software.amazon.smithy.swift.codegen.model.expectTrait import software.amazon.smithy.swift.codegen.model.hasTrait open class StaticHttpBindingResolver( - protected val context: ProtocolGenerator.GenerationContext, - protected val httpTrait: HttpTrait, - protected val defaultContentType: String + private val context: ProtocolGenerator.GenerationContext, + private val httpTrait: HttpTrait, + private val defaultContentType: String ) : HttpBindingResolver { + protected fun getContext(): ProtocolGenerator.GenerationContext = context + override fun httpTrait(operationShape: OperationShape): HttpTrait { return httpTrait } From 7a321a01e9c44e3c3c9df5a70d3c19d1582980e9 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 24 Dec 2024 14:12:34 -0500 Subject: [PATCH 08/25] add addUserAgentMiddleware fun to mocks --- .../MockHTTPAWSJson11ProtocolGenerator.kt | 4 ++++ .../MockHTTPEC2QueryProtocolGenerator.kt | 4 ++++ .../MockHTTPRestJsonProtocolGenerator.kt | 4 ++++ .../MockHTTPRestXMLProtocolGenerator.kt | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPAWSJson11ProtocolGenerator.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPAWSJson11ProtocolGenerator.kt index 7a0f0965d..ba806d5cc 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPAWSJson11ProtocolGenerator.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPAWSJson11ProtocolGenerator.kt @@ -71,6 +71,10 @@ class MockHTTPAWSJson11ProtocolGenerator() : HTTPBindingProtocolGenerator(MockAW // Intentionally empty } + override fun addUserAgentMiddleware(ctx: ProtocolGenerator.GenerationContext, operation: OperationShape) { + // Intentionally empty + } + override fun getProtocolHttpBindingResolver(ctx: ProtocolGenerator.GenerationContext, defaultContentType: String): HttpBindingResolver = MockJsonHttpBindingResolver(ctx, defaultContentType) } diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPEC2QueryProtocolGenerator.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPEC2QueryProtocolGenerator.kt index 9cc95f880..51c031110 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPEC2QueryProtocolGenerator.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPEC2QueryProtocolGenerator.kt @@ -48,6 +48,10 @@ class MockHTTPEC2QueryProtocolGenerator : HTTPBindingProtocolGenerator(MockEC2Qu // Intentionally empty } + override fun addUserAgentMiddleware(ctx: ProtocolGenerator.GenerationContext, operation: OperationShape) { + // Intentionally empty + } + override fun getProtocolHttpBindingResolver(ctx: ProtocolGenerator.GenerationContext, defaultContentType: String): HttpBindingResolver = MockEC2QueryHttpBindingResolver(ctx, defaultContentType) diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRestJsonProtocolGenerator.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRestJsonProtocolGenerator.kt index 271b08c93..bdae20c8b 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRestJsonProtocolGenerator.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRestJsonProtocolGenerator.kt @@ -28,6 +28,10 @@ class MockHTTPRestJsonProtocolGenerator : HTTPBindingProtocolGenerator(MockRestJ // Intentionally empty } + override fun addUserAgentMiddleware(ctx: ProtocolGenerator.GenerationContext, operation: OperationShape) { + // Intentionally empty + } + override fun generateProtocolUnitTests(ctx: ProtocolGenerator.GenerationContext): Int { val requestTestBuilder = HttpProtocolUnitTestRequestGenerator.Builder() val responseTestBuilder = HttpProtocolUnitTestResponseGenerator.Builder() diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRestXMLProtocolGenerator.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRestXMLProtocolGenerator.kt index 35e53c237..962eec1a3 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRestXMLProtocolGenerator.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRestXMLProtocolGenerator.kt @@ -29,6 +29,10 @@ class MockHTTPRestXMLProtocolGenerator : HTTPBindingProtocolGenerator(MockRestXM // Intentionally empty } + override fun addUserAgentMiddleware(ctx: ProtocolGenerator.GenerationContext, operation: OperationShape) { + // Intentionally empty + } + override fun generateProtocolUnitTests(ctx: ProtocolGenerator.GenerationContext): Int { val requestTestBuilder = HttpProtocolUnitTestRequestGenerator.Builder() val responseTestBuilder = HttpProtocolUnitTestResponseGenerator.Builder() From 3e1f88458be0e77bcf5c5605ab64f0f718f8c118 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 24 Dec 2024 14:30:14 -0500 Subject: [PATCH 09/25] expose only service name instead of whole context --- .../integration/protocols/core/StaticHttpBindingResolver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt index 0428792e2..4be5182ba 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt @@ -28,7 +28,7 @@ open class StaticHttpBindingResolver( private val httpTrait: HttpTrait, private val defaultContentType: String ) : HttpBindingResolver { - protected fun getContext(): ProtocolGenerator.GenerationContext = context + protected fun getServiceName(): String = context.service.id.name override fun httpTrait(operationShape: OperationShape): HttpTrait { return httpTrait From 079a6c332f330b12d95894c4b4edbd518764c523 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 24 Dec 2024 14:32:56 -0500 Subject: [PATCH 10/25] implement addUserAgentMiddleware in CBOR mock --- .../MockHTTPRPCv2CBORProtocolGenerator.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRPCv2CBORProtocolGenerator.kt b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRPCv2CBORProtocolGenerator.kt index 7d498fdd1..f5977a0e6 100644 --- a/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRPCv2CBORProtocolGenerator.kt +++ b/smithy-swift-codegen/src/test/kotlin/software/amazon/smithy/swift/codegen/protocolgeneratormocks/MockHTTPRPCv2CBORProtocolGenerator.kt @@ -29,6 +29,10 @@ class MockHTTPRPCv2CBORProtocolGenerator : HTTPBindingProtocolGenerator(MockRPCv // Intentionally empty } + override fun addUserAgentMiddleware(ctx: ProtocolGenerator.GenerationContext, operation: OperationShape) { + // Intentionally empty + } + override fun generateProtocolUnitTests(ctx: ProtocolGenerator.GenerationContext): Int { val requestTestBuilder = HttpProtocolUnitTestRequestGenerator.Builder() val responseTestBuilder = HttpProtocolUnitTestResponseGenerator.Builder() From b8475ce6513c6d00a1d47dc776e10c1f6480793c Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 26 Dec 2024 13:52:08 -0500 Subject: [PATCH 11/25] fix some lint issues --- Sources/SmithyCBOR/Reader/Reader.swift | 4 +- Sources/SmithyCBOR/Writer/Writer.swift | 44 +++--- Sources/SmithyTestUtil/CBORComparator.swift | 149 -------------------- 3 files changed, 24 insertions(+), 173 deletions(-) diff --git a/Sources/SmithyCBOR/Reader/Reader.swift b/Sources/SmithyCBOR/Reader/Reader.swift index bf314fe74..c2d30ab0c 100644 --- a/Sources/SmithyCBOR/Reader/Reader.swift +++ b/Sources/SmithyCBOR/Reader/Reader.swift @@ -9,12 +9,12 @@ import AwsCommonRuntimeKit import Foundation @_spi(SmithyReadWrite) import protocol SmithyReadWrite.SmithyReader +@_spi(SmithyReadWrite) import enum SmithyReadWrite.ReaderError @_spi(SmithyReadWrite) import protocol SmithyReadWrite.SmithyWriter @_spi(Smithy) import struct Smithy.Document @_spi(Smithy) import protocol Smithy.SmithyDocument @_spi(SmithyTimestamps) import enum SmithyTimestamps.TimestampFormat @_spi(SmithyTimestamps) import struct SmithyTimestamps.TimestampFormatter -import SmithyReadWrite @_spi(SmithyReadWrite) public final class Reader: SmithyReader { @@ -207,7 +207,7 @@ public final class Reader: SmithyReader { isFlattened: Bool ) throws -> [Member]? { guard let cborValue else { return nil } - guard case .array(_) = cborValue else { return nil } + guard case .array = cborValue else { return nil } return try children.map { child in // Check if the child is an indefinite-length text diff --git a/Sources/SmithyCBOR/Writer/Writer.swift b/Sources/SmithyCBOR/Writer/Writer.swift index 8ca423830..d52268b00 100644 --- a/Sources/SmithyCBOR/Writer/Writer.swift +++ b/Sources/SmithyCBOR/Writer/Writer.swift @@ -18,16 +18,16 @@ import Foundation @_spi(SmithyReadWrite) public final class Writer: SmithyWriter { public typealias NodeInfo = String - + let nodeInfo: NodeInfo var cborValue: CBORType? var children: [Writer] = [] weak var parent: Writer? - + public required init(nodeInfo: NodeInfo) { self.nodeInfo = nodeInfo } - + public required init(nodeInfo: NodeInfo, parent: Writer? = nil) { self.nodeInfo = nodeInfo self.parent = parent @@ -77,56 +77,56 @@ public final class Writer: SmithyWriter { return newChild } } - + public func write(_ value: Bool?) throws { guard let value else { return } self.cborValue = .bool(value) } - + public func write(_ value: String?) throws { guard let value else { return } self.cborValue = .text(value) } - + public func write(_ value: Float?) throws { guard let value else { return } self.cborValue = .double(Double(value)) } - + public func write(_ value: Double?) throws { guard let value else { return } self.cborValue = .double(value) } - + public func write(_ value: Int?) throws { guard let value else { return } self.cborValue = .int(Int64(value)) } - + public func write(_ value: Int8?) throws { guard let value else { return } self.cborValue = .int(Int64(value)) } - + public func write(_ value: Int16?) throws { guard let value else { return } self.cborValue = .int(Int64(value)) } - + public func write(_ value: UInt8?) throws { guard let value else { return } self.cborValue = .uint(UInt64(value)) } - + public func write(_ value: Data?) throws { guard let value else { return } self.cborValue = .bytes(value) } - + public func write(_ value: SmithyDocument?) throws { // No operation. Smithy document not supported in CBOR } - + public func writeTimestamp(_ value: Date?, format: TimestampFormat) throws { guard let value else { return } switch format { @@ -137,19 +137,19 @@ public final class Writer: SmithyWriter { self.cborValue = .text(string) } } - + public func write(_ value: T?) throws where T: RawRepresentable, T.RawValue == Int { if let rawValue = value?.rawValue { try write(rawValue) } } - + public func write(_ value: T?) throws where T: RawRepresentable, T.RawValue == String { if let rawValue = value?.rawValue { try write(rawValue) } } - + public func writeMap( _ value: [String: T]?, valueWritingClosure: (T, Writer) throws -> Void, @@ -159,11 +159,11 @@ public final class Writer: SmithyWriter { ) throws { guard let value else { return } var map: [String: CBORType] = [:] - + for (key, val) in value { let writer = self[key] try valueWritingClosure(val, writer) - + // If the writer itself doesn't have a cborValue, build it from its children if writer.cborValue == nil, !writer.children.isEmpty { var childMap: [String: CBORType] = [:] @@ -174,17 +174,17 @@ public final class Writer: SmithyWriter { } writer.cborValue = .map(childMap) // Construct the map for the writer } - + // Add to the parent map if let cborValue = writer.cborValue { map[key] = cborValue } } - + // Assign the constructed map to the current writer self.cborValue = .map(map) } - + public func writeList( _ value: [T]?, memberWritingClosure: (T, Writer) throws -> Void, diff --git a/Sources/SmithyTestUtil/CBORComparator.swift b/Sources/SmithyTestUtil/CBORComparator.swift index 8824813d2..244ecb825 100644 --- a/Sources/SmithyTestUtil/CBORComparator.swift +++ b/Sources/SmithyTestUtil/CBORComparator.swift @@ -4,9 +4,7 @@ // // SPDX-License-Identifier: Apache-2.0 // -import Foundation import AwsCommonRuntimeKit - import Foundation @_spi(SmithyReadWrite) import SmithyCBOR @@ -44,34 +42,6 @@ public struct CBORComparator { return true } -// private static func areMapsEqual(_ mapA: [String: CBORType], _ mapB: [String: CBORType]) -> Bool { -// // Ensure the keys match -// guard Set(mapA.keys) == Set(mapB.keys) else { return false } -// -// // Compare values for each key -// for key in mapA.keys { -// guard let valueA = mapA[key], let valueB = mapB[key] else { -// return false -// } -// -// switch (valueA, valueB) { -// case (.map(let nestedMapA), .map(let nestedMapB)): -// if !areMapsEqual(nestedMapA, nestedMapB) { -// return false -// } -//// case (.array(let nestedArrayA), .array(let nestedArrayB)): -//// if !areArraysEqual(nestedArrayA, nestedArrayB) { -//// return false -//// } -// default: -// if valueA != valueB && String(describing: valueA) != String(describing: valueB) { -// return false -// } -// } -// } -// return true -// } - fileprivate static func anyCBORValuesAreEqual(_ lhs: CBORType?, _ rhs: CBORType?) -> Bool { switch (lhs, rhs) { case (nil, nil): @@ -157,122 +127,3 @@ public struct CBORComparator { return true } } - -//public struct CBORComparator { -// /// Returns true if the CBOR documents, for the corresponding data objects, are equal. -// /// - Parameters: -// /// - dataA: The first CBOR data object to compare. -// /// - dataB: The second CBOR data object to compare. -// /// - Returns: Returns true if the CBOR documents are equal. -// public static func cborData(_ dataA: Data, isEqualTo dataB: Data) throws -> Bool { -// let decodedA = try CBORDecoder(data: [UInt8](dataA)) -// let decodedB = try CBORDecoder(data: [UInt8](dataB)) -// while decodedA.hasNext() && decodedB.hasNext() { -// let valueA = try decodedA.popNext() -// let valueB = try decodedB.popNext() -// if !anyCBORValuesAreEqual(valueA, valueB) { -// return false -// } -// } -// if decodedA.hasNext() || decodedB.hasNext() { -// return false // unequal lengths -// } -// return true -// } -//} -// -//fileprivate func anyCBORValuesAreEqual(_ lhs: CBORType?, _ rhs: CBORType?) -> Bool { -// switch (lhs, rhs) { -// case (nil, nil): -// return true -// case (nil, _), (_, nil): -// return false -// case (.null, .null): -// return true -// case (.undefined, .undefined): -// return true -// case (.bool(let lVal), .bool(let rVal)): -// return lVal == rVal -// case (.text(let lVal), .text(let rVal)): -// return lVal == rVal -// case (.bytes(let lVal), .bytes(let rVal)): -// return lVal == rVal -// case (.date(let lVal), .date(let rVal)): -// return lVal == rVal -// case (.tag(let lVal), .tag(let rVal)): -// return lVal == rVal -// -// case (.uint(let lVal), .uint(let rVal)): -// return lVal == rVal -// case (.int(let lVal), .int(let rVal)): -// return lVal == rVal -// case (.double(let lVal), .double(let rVal)): -// return lVal == rVal -// -// // Extended cross-type numeric comparisons -// case (.uint(let lVal), .int(let rVal)): -// guard rVal >= 0 else { return false } -// return UInt64(rVal) == lVal -// case (.int(let lVal), .uint(let rVal)): -// guard lVal >= 0 else { return false } -// return UInt64(lVal) == rVal -// case (.uint(let lVal), .double(let rVal)): -// return Double(lVal) == rVal -// case (.int(let lVal), .double(let rVal)): -// return Double(lVal) == rVal -// case (.double(let lVal), .uint(let rVal)): -// return lVal == Double(rVal) -// case (.double(let lVal), .int(let rVal)): -// return lVal == Double(rVal) -// -// // New equivalency for integer and float with the same value -// case (.double(let lVal), .uint(let rVal)): -// return lVal == Double(rVal) -// case (.double(let lVal), .int(let rVal)): -// return lVal == Double(rVal) -// case (.uint(let lVal), .double(let rVal)): -// return Double(lVal) == rVal -// case (.int(let lVal), .double(let rVal)): -// return Double(lVal) == rVal -// -// case (.array(let lArr), .array(let rArr)): -// return anyCBORArraysAreEqual(lArr, rArr) -// case (.map(let lMap), .map(let rMap)): -// return anyCBORDictsAreEqual(lMap, rMap) -// -// case (.indef_map_start, .indef_map_start): -// return true -// case (.indef_text_start, .indef_text_start): -// return true -// case (.indef_array_start, .indef_array_start): -// return true -// case (.indef_bytes_start, .indef_bytes_start): -// return true -// case (.indef_break, .indef_break): -// return true -// -// default: -// return false -// } -//} -// -//fileprivate func anyCBORArraysAreEqual(_ lhs: [CBORType], _ rhs: [CBORType]) -> Bool { -// guard lhs.count == rhs.count else { return false } -// for i in 0.. Bool { -// // Keys must match exactly. Order does not matter. -// guard lhs.keys.sorted() == rhs.keys.sorted() else { return false } -// for key in lhs.keys { -// if !anyCBORValuesAreEqual(lhs[key], rhs[key]) { -// return false -// } -// } -// return true -//} From 36628b4d2aaaa845bce4516ae6704bdb8e06030e Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 26 Dec 2024 14:02:03 -0500 Subject: [PATCH 12/25] fix some lint issues --- .../amazon/smithy/swift/codegen/SwiftSettings.kt | 2 +- .../codegen/integration/HttpProtocolClientGenerator.kt | 1 - .../integration/HttpProtocolUnitTestErrorGenerator.kt | 10 ++++------ 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSettings.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSettings.kt index 784050130..46ed092a6 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSettings.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSettings.kt @@ -200,7 +200,7 @@ class SwiftSettings( return protocol ?: throw UnresolvableProtocolException( "The ${service.id} service supports the following unsupported protocols $resolvedProtocols. " + - "The following protocol generators were found on the class path: $supportedProtocolTraits" + "The following protocol generators were found on the class path: $supportedProtocolTraits" ) } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolClientGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolClientGenerator.kt index e777ec0e5..3906da510 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolClientGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolClientGenerator.kt @@ -4,7 +4,6 @@ */ package software.amazon.smithy.swift.codegen.integration -import jdk.jfr.ContentType import software.amazon.smithy.codegen.core.Symbol import software.amazon.smithy.model.Model import software.amazon.smithy.model.knowledge.OperationIndex diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt index c46307b05..f42b2b16a 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt @@ -9,10 +9,8 @@ import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.protocol.traits.Rpcv2CborTrait import software.amazon.smithy.protocoltests.traits.HttpResponseTestCase import software.amazon.smithy.swift.codegen.SwiftDependency -import software.amazon.smithy.swift.codegen.integration.serde.readwrite.* import software.amazon.smithy.swift.codegen.model.toUpperCamelCase import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes -import java.util.* open class HttpProtocolUnitTestErrorGenerator protected constructor(builder: Builder) : HttpProtocolUnitTestResponseGenerator(builder) { @@ -78,10 +76,10 @@ open class HttpProtocolUnitTestErrorGenerator protected constructor(builder: Bui override fun renderExpectedBody(test: HttpResponseTestCase) { if (test.body.isPresent && test.body.get().isNotBlank()) { - val isCbor = ctx.service.requestWireProtocol == WireProtocol.CBOR - || ctx.service.awsProtocol == AWSProtocol.RPCV2_CBOR - || ctx.service.responseWireProtocol == WireProtocol.CBOR - || test.protocol == Rpcv2CborTrait.ID + val isCbor = ctx.service.requestWireProtocol == WireProtocol.CBOR || + ctx.service.awsProtocol == AWSProtocol.RPCV2_CBOR || + ctx.service.responseWireProtocol == WireProtocol.CBOR || + test.protocol == Rpcv2CborTrait.ID val bodyContent = test.body.get().replace("\\\"", "\\\\\"") val data: String = if (isCbor) { From 9b2880bc4ae62a11bc2fc8a549dba0b7099bfff8 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 26 Dec 2024 14:10:43 -0500 Subject: [PATCH 13/25] fix imports --- .../codegen/integration/HttpProtocolUnitTestErrorGenerator.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt index f42b2b16a..3bf2bf8f4 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt @@ -8,9 +8,13 @@ import software.amazon.smithy.codegen.core.CodegenException import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.protocol.traits.Rpcv2CborTrait import software.amazon.smithy.protocoltests.traits.HttpResponseTestCase +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.AWSProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ResponseClosureUtils +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.WireProtocol import software.amazon.smithy.swift.codegen.SwiftDependency import software.amazon.smithy.swift.codegen.model.toUpperCamelCase import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes +import java.util.Base64 open class HttpProtocolUnitTestErrorGenerator protected constructor(builder: Builder) : HttpProtocolUnitTestResponseGenerator(builder) { From f14c31b7f1edfcecd79592770f22afe7d8f99421 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 26 Dec 2024 14:23:36 -0500 Subject: [PATCH 14/25] more import fixing --- .../codegen/integration/HttpProtocolUnitTestErrorGenerator.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt index 3bf2bf8f4..138e9d2c8 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt @@ -9,7 +9,11 @@ import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.protocol.traits.Rpcv2CborTrait import software.amazon.smithy.protocoltests.traits.HttpResponseTestCase import software.amazon.smithy.swift.codegen.integration.serde.readwrite.AWSProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.awsProtocol import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ResponseClosureUtils +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ResponseErrorClosureUtils +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.requestWireProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.responseWireProtocol import software.amazon.smithy.swift.codegen.integration.serde.readwrite.WireProtocol import software.amazon.smithy.swift.codegen.SwiftDependency import software.amazon.smithy.swift.codegen.model.toUpperCamelCase From 2601702c49e8b43544ae57ecd934489a137c700c Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 26 Dec 2024 14:28:46 -0500 Subject: [PATCH 15/25] more lint fixes --- .../HttpProtocolUnitTestErrorGenerator.kt | 1 - .../HttpProtocolUnitTestRequestGenerator.kt | 2 +- .../HttpProtocolUnitTestResponseGenerator.kt | 15 ++++++++++----- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt index 138e9d2c8..94a4d1c9a 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt @@ -10,7 +10,6 @@ import software.amazon.smithy.protocol.traits.Rpcv2CborTrait import software.amazon.smithy.protocoltests.traits.HttpResponseTestCase import software.amazon.smithy.swift.codegen.integration.serde.readwrite.AWSProtocol import software.amazon.smithy.swift.codegen.integration.serde.readwrite.awsProtocol -import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ResponseClosureUtils import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ResponseErrorClosureUtils import software.amazon.smithy.swift.codegen.integration.serde.readwrite.requestWireProtocol import software.amazon.smithy.swift.codegen.integration.serde.readwrite.responseWireProtocol diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt index 950b28da8..2b75173f4 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt @@ -17,7 +17,7 @@ import software.amazon.smithy.swift.codegen.model.toLowerCamelCase import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyStreamsTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTestUtilTypes -import java.util.* +import java.util.Base64 open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: Builder) : HttpProtocolUnitTestGenerator(builder) { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt index d099f1791..b20dcb536 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt @@ -15,7 +15,12 @@ import software.amazon.smithy.protocol.traits.Rpcv2CborTrait import software.amazon.smithy.protocoltests.traits.HttpResponseTestCase import software.amazon.smithy.swift.codegen.ShapeValueGenerator import software.amazon.smithy.swift.codegen.hasStreamingMember -import software.amazon.smithy.swift.codegen.integration.serde.readwrite.* +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.AWSProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.awsProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ResponseErrorClosureUtils +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.requestWireProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.responseWireProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.WireProtocol import software.amazon.smithy.swift.codegen.model.RecursiveShapeBoxer import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.swiftmodules.SmithyStreamsTypes @@ -68,10 +73,10 @@ open class HttpProtocolUnitTestResponseGenerator protected constructor(builder: } val bodyContent = test.body.get() - val isCbor = ctx.service.requestWireProtocol == WireProtocol.CBOR - || ctx.service.awsProtocol == AWSProtocol.RPCV2_CBOR - || ctx.service.responseWireProtocol == WireProtocol.CBOR - || test.protocol == Rpcv2CborTrait.ID + val isCbor = ctx.service.requestWireProtocol == WireProtocol.CBOR || + ctx.service.awsProtocol == AWSProtocol.RPCV2_CBOR || + ctx.service.responseWireProtocol == WireProtocol.CBOR || + test.protocol == Rpcv2CborTrait.ID val data: String = if (isCbor) { // Attempt to decode Base64 data once for CBOR From 2713fbc365c797dd0aae5e5d165ca0b4507f2c5a Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 26 Dec 2024 14:32:01 -0500 Subject: [PATCH 16/25] more lint --- .../software/amazon/smithy/swift/codegen/SwiftSettings.kt | 2 +- .../integration/HttpProtocolUnitTestResponseGenerator.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSettings.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSettings.kt index 46ed092a6..239418117 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSettings.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/SwiftSettings.kt @@ -200,7 +200,7 @@ class SwiftSettings( return protocol ?: throw UnresolvableProtocolException( "The ${service.id} service supports the following unsupported protocols $resolvedProtocols. " + - "The following protocol generators were found on the class path: $supportedProtocolTraits" + "The following protocol generators were found on the class path: $supportedProtocolTraits" ) } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt index b20dcb536..586a650fe 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt @@ -17,9 +17,9 @@ import software.amazon.smithy.swift.codegen.ShapeValueGenerator import software.amazon.smithy.swift.codegen.hasStreamingMember import software.amazon.smithy.swift.codegen.integration.serde.readwrite.AWSProtocol import software.amazon.smithy.swift.codegen.integration.serde.readwrite.awsProtocol -import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ResponseErrorClosureUtils -import software.amazon.smithy.swift.codegen.integration.serde.readwrite.requestWireProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ResponseClosureUtils import software.amazon.smithy.swift.codegen.integration.serde.readwrite.responseWireProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.requestWireProtocol import software.amazon.smithy.swift.codegen.integration.serde.readwrite.WireProtocol import software.amazon.smithy.swift.codegen.model.RecursiveShapeBoxer import software.amazon.smithy.swift.codegen.model.hasTrait From 2de217137841512bd29b44345c912831ff9222ab Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 26 Dec 2024 14:45:37 -0500 Subject: [PATCH 17/25] try reorder --- .../integration/HttpProtocolUnitTestErrorGenerator.kt | 8 ++++---- .../integration/HttpProtocolUnitTestRequestGenerator.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt index 94a4d1c9a..b345f2b0b 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt @@ -4,20 +4,20 @@ */ package software.amazon.smithy.swift.codegen.integration +import java.util.Base64 import software.amazon.smithy.codegen.core.CodegenException import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.protocol.traits.Rpcv2CborTrait import software.amazon.smithy.protocoltests.traits.HttpResponseTestCase +import software.amazon.smithy.swift.codegen.SwiftDependency import software.amazon.smithy.swift.codegen.integration.serde.readwrite.AWSProtocol -import software.amazon.smithy.swift.codegen.integration.serde.readwrite.awsProtocol import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ResponseErrorClosureUtils +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.WireProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.awsProtocol import software.amazon.smithy.swift.codegen.integration.serde.readwrite.requestWireProtocol import software.amazon.smithy.swift.codegen.integration.serde.readwrite.responseWireProtocol -import software.amazon.smithy.swift.codegen.integration.serde.readwrite.WireProtocol -import software.amazon.smithy.swift.codegen.SwiftDependency import software.amazon.smithy.swift.codegen.model.toUpperCamelCase import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes -import java.util.Base64 open class HttpProtocolUnitTestErrorGenerator protected constructor(builder: Builder) : HttpProtocolUnitTestResponseGenerator(builder) { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt index 2b75173f4..afc56677f 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt @@ -4,6 +4,7 @@ */ package software.amazon.smithy.swift.codegen.integration +import java.util.Base64 import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.protocoltests.traits.HttpRequestTestCase import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait @@ -17,7 +18,6 @@ import software.amazon.smithy.swift.codegen.model.toLowerCamelCase import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyStreamsTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTestUtilTypes -import java.util.Base64 open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: Builder) : HttpProtocolUnitTestGenerator(builder) { From 2b3120e64cd283c884139b50f0bc2bd71b3d98d9 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 26 Dec 2024 15:10:04 -0500 Subject: [PATCH 18/25] ran ktlintFormat --- Sources/SmithyCBOR/Writer/Writer.swift | 1 - .../integration/HttpProtocolUnitTestErrorGenerator.kt | 2 +- .../integration/HttpProtocolUnitTestRequestGenerator.kt | 2 +- .../integration/HttpProtocolUnitTestResponseGenerator.kt | 6 +++--- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Sources/SmithyCBOR/Writer/Writer.swift b/Sources/SmithyCBOR/Writer/Writer.swift index d52268b00..00974911c 100644 --- a/Sources/SmithyCBOR/Writer/Writer.swift +++ b/Sources/SmithyCBOR/Writer/Writer.swift @@ -37,7 +37,6 @@ public final class Writer: SmithyWriter { let encoder = try CBOREncoder() try encode(encoder: encoder) let encodedBytes = encoder.getEncoded() - print("Encoded CBOR: \(encodedBytes.map { String(format: "0x%02X", $0) }.joined(separator: ", "))") return Data(encodedBytes) } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt index b345f2b0b..ce0895912 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestErrorGenerator.kt @@ -4,7 +4,6 @@ */ package software.amazon.smithy.swift.codegen.integration -import java.util.Base64 import software.amazon.smithy.codegen.core.CodegenException import software.amazon.smithy.model.shapes.Shape import software.amazon.smithy.protocol.traits.Rpcv2CborTrait @@ -18,6 +17,7 @@ import software.amazon.smithy.swift.codegen.integration.serde.readwrite.requestW import software.amazon.smithy.swift.codegen.integration.serde.readwrite.responseWireProtocol import software.amazon.smithy.swift.codegen.model.toUpperCamelCase import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes +import java.util.Base64 open class HttpProtocolUnitTestErrorGenerator protected constructor(builder: Builder) : HttpProtocolUnitTestResponseGenerator(builder) { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt index afc56677f..2b75173f4 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestRequestGenerator.kt @@ -4,7 +4,6 @@ */ package software.amazon.smithy.swift.codegen.integration -import java.util.Base64 import software.amazon.smithy.model.shapes.StructureShape import software.amazon.smithy.protocoltests.traits.HttpRequestTestCase import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait @@ -18,6 +17,7 @@ import software.amazon.smithy.swift.codegen.model.toLowerCamelCase import software.amazon.smithy.swift.codegen.swiftmodules.SmithyHTTPAPITypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyStreamsTypes import software.amazon.smithy.swift.codegen.swiftmodules.SmithyTestUtilTypes +import java.util.Base64 open class HttpProtocolUnitTestRequestGenerator protected constructor(builder: Builder) : HttpProtocolUnitTestGenerator(builder) { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt index 586a650fe..0f1c83494 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/HttpProtocolUnitTestResponseGenerator.kt @@ -16,11 +16,11 @@ import software.amazon.smithy.protocoltests.traits.HttpResponseTestCase import software.amazon.smithy.swift.codegen.ShapeValueGenerator import software.amazon.smithy.swift.codegen.hasStreamingMember import software.amazon.smithy.swift.codegen.integration.serde.readwrite.AWSProtocol -import software.amazon.smithy.swift.codegen.integration.serde.readwrite.awsProtocol import software.amazon.smithy.swift.codegen.integration.serde.readwrite.ResponseClosureUtils -import software.amazon.smithy.swift.codegen.integration.serde.readwrite.responseWireProtocol -import software.amazon.smithy.swift.codegen.integration.serde.readwrite.requestWireProtocol import software.amazon.smithy.swift.codegen.integration.serde.readwrite.WireProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.awsProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.requestWireProtocol +import software.amazon.smithy.swift.codegen.integration.serde.readwrite.responseWireProtocol import software.amazon.smithy.swift.codegen.model.RecursiveShapeBoxer import software.amazon.smithy.swift.codegen.model.hasTrait import software.amazon.smithy.swift.codegen.swiftmodules.SmithyStreamsTypes From d7da330c2b273c94b4d66815097998655d176b1d Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 31 Dec 2024 13:05:37 -0500 Subject: [PATCH 19/25] address PR comments --- Sources/SmithyCBOR/Reader/Reader.swift | 12 ++++++++---- Sources/SmithyReadWrite/SmithyReader.swift | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Sources/SmithyCBOR/Reader/Reader.swift b/Sources/SmithyCBOR/Reader/Reader.swift index c2d30ab0c..e426e8362 100644 --- a/Sources/SmithyCBOR/Reader/Reader.swift +++ b/Sources/SmithyCBOR/Reader/Reader.swift @@ -149,8 +149,8 @@ public final class Reader: SmithyReader { } public func readIfPresent() throws -> Document? { - guard let cborValue else { return nil } - return Document(cborValue as! SmithyDocument) + // No operation. Smithy document not supported in CBOR + return nil } public func readIfPresent() throws -> T? where T: RawRepresentable, T.RawValue == Int { @@ -218,8 +218,12 @@ public final class Reader: SmithyReader { combinedText += chunk } } - // Return the combined text - return combinedText as! Member + // Safely return the combined text + if let result = combinedText as? Member { + return result + } else { + throw ReaderError.invalidType("Expected Member type, but got \(type(of: combinedText))") + } } else { // Handle regular values return try memberReadingClosure(child) diff --git a/Sources/SmithyReadWrite/SmithyReader.swift b/Sources/SmithyReadWrite/SmithyReader.swift index 968a071b3..7fc95afe1 100644 --- a/Sources/SmithyReadWrite/SmithyReader.swift +++ b/Sources/SmithyReadWrite/SmithyReader.swift @@ -208,5 +208,5 @@ public extension SmithyReader { public enum ReaderError: Error { case requiredValueNotPresent - case invalidBase64 + case invalidType(String) } From 6a607aefcb23d78e036f3bf058ac241d46082b1f Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 31 Dec 2024 14:51:39 -0500 Subject: [PATCH 20/25] ignore timestampFormat on member for generating reading/writing closures --- Sources/SmithyCBOR/Writer/Writer.swift | 6 +++--- Sources/SmithyReadWrite/SmithyWriter.swift | 4 ++++ .../integration/serde/readwrite/ReadingClosureUtils.kt | 10 +++++++++- .../integration/serde/readwrite/WritingClosureUtils.kt | 10 +++++++++- .../swift/codegen/swiftmodules/SmithyReadWriteTypes.kt | 3 ++- 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Sources/SmithyCBOR/Writer/Writer.swift b/Sources/SmithyCBOR/Writer/Writer.swift index 00974911c..4820afd83 100644 --- a/Sources/SmithyCBOR/Writer/Writer.swift +++ b/Sources/SmithyCBOR/Writer/Writer.swift @@ -10,6 +10,7 @@ import Foundation @_spi(SmithyReadWrite) import protocol SmithyReadWrite.SmithyReader @_spi(SmithyReadWrite) import protocol SmithyReadWrite.SmithyWriter +@_spi(SmithyReadWrite) import enum SmithyReadWrite.WriterError @_spi(Smithy) import struct Smithy.Document @_spi(Smithy) import protocol Smithy.SmithyDocument @_spi(SmithyTimestamps) import enum SmithyTimestamps.TimestampFormat @@ -131,9 +132,8 @@ public final class Writer: SmithyWriter { switch format { case .epochSeconds: self.cborValue = .date(value) - case .dateTime, .httpDate: - let string = TimestampFormatter(format: format).string(from: value) - self.cborValue = .text(string) + default: + throw WriterError.invalidType("Only .epochSeconds timestamp format is supported!") } } diff --git a/Sources/SmithyReadWrite/SmithyWriter.swift b/Sources/SmithyReadWrite/SmithyWriter.swift index 62f992122..e27d4132f 100644 --- a/Sources/SmithyReadWrite/SmithyWriter.swift +++ b/Sources/SmithyReadWrite/SmithyWriter.swift @@ -75,3 +75,7 @@ public extension SmithyWriter { return try writingClosure(value, self) } } + +public enum WriterError: Error { + case invalidType(String) +} diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt index 9c1908cbc..9dfe35958 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt @@ -61,11 +61,19 @@ class ReadingClosureUtils( ) } shape is TimestampShape -> { + val resolvedFormat = if (ctx.service.responseWireProtocol == WireProtocol.CBOR) { + // Force EPOCH_SECONDS if CBOR + TimestampFormatTrait.Format.EPOCH_SECONDS + } else { + // Default logic + TimestampUtils.timestampFormat(ctx, memberTimestampFormatTrait, shape) + } + writer.format( "\$N(format: \$N\$L)", SmithyReadWriteTypes.timestampReadingClosure, SmithyTimestampsTypes.TimestampFormat, - TimestampUtils.timestampFormat(ctx, memberTimestampFormatTrait, shape), + resolvedFormat, ) } shape is EnumShape || shape is IntEnumShape || shape.hasTrait() -> { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt index 926197439..b9f57100a 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt @@ -69,11 +69,19 @@ class WritingClosureUtils( ) } shape is TimestampShape -> { + val resolvedFormat = if (ctx.service.requestWireProtocol == WireProtocol.CBOR) { + // Force EPOCH_SECONDS if CBOR + TimestampFormatTrait.Format.EPOCH_SECONDS + } else { + // Default logic + TimestampUtils.timestampFormat(ctx, memberTimestampFormatTrait, shape) + } + writer.format( "\$N(format: \$N\$L)", SmithyReadWriteTypes.timestampWritingClosure, SmithyTimestampsTypes.TimestampFormat, - TimestampUtils.timestampFormat(ctx, memberTimestampFormatTrait, shape), + resolvedFormat, ) } shape is EnumShape || shape is IntEnumShape || shape.hasTrait() -> { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/swiftmodules/SmithyReadWriteTypes.kt index 939892200..93257fdd9 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 @@ -13,7 +13,8 @@ import software.amazon.smithy.swift.codegen.SwiftDependency object SmithyReadWriteTypes { val SmithyReader = runtimeSymbol("SmithyReader", SwiftDeclaration.PROTOCOL) val SmithyWriter = runtimeSymbol("SmithyWriter", SwiftDeclaration.PROTOCOL) - val ReaderError = runtimeSymbol("ReaderError", SwiftDeclaration.ENUM, emptyList()) + val ReaderError = runtimeSymbol("ReaderError", SwiftDeclaration.ENUM) + val WriterError = runtimeSymbol("WriterError", SwiftDeclaration.ENUM) val mapWritingClosure = runtimeSymbol("mapWritingClosure", SwiftDeclaration.FUNC) val listWritingClosure = runtimeSymbol("listWritingClosure", SwiftDeclaration.FUNC) val timestampWritingClosure = runtimeSymbol("timestampWritingClosure", SwiftDeclaration.FUNC) From dde4eba95b2b481abe4a15599312d32f44ddb048 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 31 Dec 2024 14:59:19 -0500 Subject: [PATCH 21/25] change format type --- .../codegen/integration/serde/readwrite/ReadingClosureUtils.kt | 2 +- .../codegen/integration/serde/readwrite/WritingClosureUtils.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt index 9dfe35958..64a1d1d25 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt @@ -63,7 +63,7 @@ class ReadingClosureUtils( shape is TimestampShape -> { val resolvedFormat = if (ctx.service.responseWireProtocol == WireProtocol.CBOR) { // Force EPOCH_SECONDS if CBOR - TimestampFormatTrait.Format.EPOCH_SECONDS + SmithyTimestampsTypes.TimestampFormat.EPOCH_SECONDS } else { // Default logic TimestampUtils.timestampFormat(ctx, memberTimestampFormatTrait, shape) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt index b9f57100a..457b0e47e 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt @@ -71,7 +71,7 @@ class WritingClosureUtils( shape is TimestampShape -> { val resolvedFormat = if (ctx.service.requestWireProtocol == WireProtocol.CBOR) { // Force EPOCH_SECONDS if CBOR - TimestampFormatTrait.Format.EPOCH_SECONDS + SmithyTimestampsTypes.TimestampFormat.EPOCH_SECONDS } else { // Default logic TimestampUtils.timestampFormat(ctx, memberTimestampFormatTrait, shape) From 40f271f33b6074048c52bd0aabec561fd78d7fc5 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 31 Dec 2024 15:03:25 -0500 Subject: [PATCH 22/25] change format type --- .../codegen/integration/serde/readwrite/ReadingClosureUtils.kt | 2 +- .../codegen/integration/serde/readwrite/WritingClosureUtils.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt index 64a1d1d25..976bcff00 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt @@ -63,7 +63,7 @@ class ReadingClosureUtils( shape is TimestampShape -> { val resolvedFormat = if (ctx.service.responseWireProtocol == WireProtocol.CBOR) { // Force EPOCH_SECONDS if CBOR - SmithyTimestampsTypes.TimestampFormat.EPOCH_SECONDS + ".epochSeconds" } else { // Default logic TimestampUtils.timestampFormat(ctx, memberTimestampFormatTrait, shape) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt index 457b0e47e..b9310a1f3 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt @@ -71,7 +71,7 @@ class WritingClosureUtils( shape is TimestampShape -> { val resolvedFormat = if (ctx.service.requestWireProtocol == WireProtocol.CBOR) { // Force EPOCH_SECONDS if CBOR - SmithyTimestampsTypes.TimestampFormat.EPOCH_SECONDS + ".epochSeconds" } else { // Default logic TimestampUtils.timestampFormat(ctx, memberTimestampFormatTrait, shape) From 3accc450b401a9410d9cf426294cc1495ca73536 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Tue, 31 Dec 2024 15:11:30 -0500 Subject: [PATCH 23/25] short circuit timestamp format resolution for cbor --- .../serde/member/MemberShapeEncodeGenerator.kt | 6 ++++++ .../integration/serde/readwrite/ReadingClosureUtils.kt | 10 +--------- .../integration/serde/readwrite/WritingClosureUtils.kt | 10 +--------- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeEncodeGenerator.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeEncodeGenerator.kt index c4fc0cd4d..671ec4572 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeEncodeGenerator.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/member/MemberShapeEncodeGenerator.kt @@ -150,6 +150,12 @@ abstract class MemberShapeEncodeGenerator( object TimestampUtils { fun timestampFormat(ctx: ProtocolGenerator.GenerationContext, memberTimestampFormatTrait: TimestampFormatTrait?, timestampShape: TimestampShape): String { + // CBOR wire protocol ignores TimestampFormatTrait + if (ctx.service.requestWireProtocol == WireProtocol.CBOR) { + return ".epochSeconds" + } + + // Resolve TimestampFormatTrait normally val timestampFormat = memberTimestampFormatTrait?.value ?: timestampShape.getTrait()?.value ?: defaultTimestampFormat(ctx) return when (timestampFormat) { TimestampFormatTrait.EPOCH_SECONDS -> ".epochSeconds" diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt index 976bcff00..9c1908cbc 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/ReadingClosureUtils.kt @@ -61,19 +61,11 @@ class ReadingClosureUtils( ) } shape is TimestampShape -> { - val resolvedFormat = if (ctx.service.responseWireProtocol == WireProtocol.CBOR) { - // Force EPOCH_SECONDS if CBOR - ".epochSeconds" - } else { - // Default logic - TimestampUtils.timestampFormat(ctx, memberTimestampFormatTrait, shape) - } - writer.format( "\$N(format: \$N\$L)", SmithyReadWriteTypes.timestampReadingClosure, SmithyTimestampsTypes.TimestampFormat, - resolvedFormat, + TimestampUtils.timestampFormat(ctx, memberTimestampFormatTrait, shape), ) } shape is EnumShape || shape is IntEnumShape || shape.hasTrait() -> { diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt index b9310a1f3..926197439 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/serde/readwrite/WritingClosureUtils.kt @@ -69,19 +69,11 @@ class WritingClosureUtils( ) } shape is TimestampShape -> { - val resolvedFormat = if (ctx.service.requestWireProtocol == WireProtocol.CBOR) { - // Force EPOCH_SECONDS if CBOR - ".epochSeconds" - } else { - // Default logic - TimestampUtils.timestampFormat(ctx, memberTimestampFormatTrait, shape) - } - writer.format( "\$N(format: \$N\$L)", SmithyReadWriteTypes.timestampWritingClosure, SmithyTimestampsTypes.TimestampFormat, - resolvedFormat, + TimestampUtils.timestampFormat(ctx, memberTimestampFormatTrait, shape), ) } shape is EnumShape || shape is IntEnumShape || shape.hasTrait() -> { From 537b93c4dc7dc3f5bf55fb2ae2202f18d3917885 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Wed, 8 Jan 2025 16:36:16 -0500 Subject: [PATCH 24/25] address PR comments --- Sources/SmithyCBOR/Reader/Reader.swift | 25 +++---------------- .../core/StaticHttpBindingResolver.kt | 3 ++- .../swiftmodules/SmithyReadWriteTypes.kt | 4 +-- 3 files changed, 7 insertions(+), 25 deletions(-) diff --git a/Sources/SmithyCBOR/Reader/Reader.swift b/Sources/SmithyCBOR/Reader/Reader.swift index e426e8362..baf9a56d2 100644 --- a/Sources/SmithyCBOR/Reader/Reader.swift +++ b/Sources/SmithyCBOR/Reader/Reader.swift @@ -208,34 +208,15 @@ public final class Reader: SmithyReader { ) throws -> [Member]? { guard let cborValue else { return nil } guard case .array = cborValue else { return nil } - return try children.map { child in - // Check if the child is an indefinite-length text - if case .indef_text_start = child.cborValue { - var combinedText = "" - for grandChild in child.children { - if let chunk = try grandChild.readIfPresent() as String? { - combinedText += chunk - } - } - // Safely return the combined text - if let result = combinedText as? Member { - return result - } else { - throw ReaderError.invalidType("Expected Member type, but got \(type(of: combinedText))") - } - } else { - // Handle regular values - return try memberReadingClosure(child) - } + return try memberReadingClosure(child) } } public func readNullIfPresent() throws -> Bool? { - if cborValue == .null { - return true - } else { + guard let value = cborValue else { return nil } + return value == .null } } diff --git a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt index 4be5182ba..9625f1760 100644 --- a/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt +++ b/smithy-swift-codegen/src/main/kotlin/software/amazon/smithy/swift/codegen/integration/protocols/core/StaticHttpBindingResolver.kt @@ -28,7 +28,8 @@ open class StaticHttpBindingResolver( private val httpTrait: HttpTrait, private val defaultContentType: String ) : HttpBindingResolver { - protected fun getServiceName(): String = context.service.id.name + protected val serviceName: String + get() = context.service.id.name override fun httpTrait(operationShape: OperationShape): HttpTrait { return httpTrait 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 93257fdd9..9d09c1822 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 @@ -13,8 +13,8 @@ import software.amazon.smithy.swift.codegen.SwiftDependency object SmithyReadWriteTypes { val SmithyReader = runtimeSymbol("SmithyReader", SwiftDeclaration.PROTOCOL) val SmithyWriter = runtimeSymbol("SmithyWriter", SwiftDeclaration.PROTOCOL) - val ReaderError = runtimeSymbol("ReaderError", SwiftDeclaration.ENUM) - val WriterError = runtimeSymbol("WriterError", SwiftDeclaration.ENUM) + val ReaderError = runtimeSymbol("ReaderError", SwiftDeclaration.ENUM, emptyList()) + val WriterError = runtimeSymbol("WriterError", SwiftDeclaration.ENUM, emptyList()) val mapWritingClosure = runtimeSymbol("mapWritingClosure", SwiftDeclaration.FUNC) val listWritingClosure = runtimeSymbol("listWritingClosure", SwiftDeclaration.FUNC) val timestampWritingClosure = runtimeSymbol("timestampWritingClosure", SwiftDeclaration.FUNC) From 7181815ee4f86be68a9905f718e0310c1244c6f6 Mon Sep 17 00:00:00 2001 From: David Yaffe Date: Thu, 9 Jan 2025 13:10:54 -0500 Subject: [PATCH 25/25] add comparator test --- Package.swift | 4 ++ Tests/SmithyCBORTests/ReaderTests.swift | 72 +++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 Tests/SmithyCBORTests/ReaderTests.swift diff --git a/Package.swift b/Package.swift index 9cb77c2d2..9a2aa8767 100644 --- a/Package.swift +++ b/Package.swift @@ -245,6 +245,10 @@ let package = Package( ], resources: [ .process("Resources") ] ), + .testTarget( + name: "SmithyCBORTests", + dependencies: ["SmithyCBOR", "ClientRuntime", "SmithyTestUtil"] + ), .testTarget( name: "SmithyHTTPClientTests", dependencies: [ diff --git a/Tests/SmithyCBORTests/ReaderTests.swift b/Tests/SmithyCBORTests/ReaderTests.swift new file mode 100644 index 000000000..43b6e8a15 --- /dev/null +++ b/Tests/SmithyCBORTests/ReaderTests.swift @@ -0,0 +1,72 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import XCTest +@testable @_spi(SmithyReadWrite) import SmithyCBOR +import SmithyTestUtil + +class ReaderTests: XCTestCase { + + func test_readsNull() async throws { + let cborData = Data() + let reader = try SmithyCBOR.Reader.from(data: cborData) + XCTAssertEqual(reader.cborValue, .null) + } + + func test_compare_nulls() async throws { + let cborData1 = Data() + let cborData2 = Data() + XCTAssertTrue(try CBORComparator.cborData(cborData1, isEqualTo: cborData2)) + } + + func test_compare_complex() async throws { + // cborData1 is semantically different from cborData2, but logically equivalent (reordered keys in the map) + let cborData1 = Data([ + 0xBF, // Start of outer indefinite-length map + + // Key: "defaults" + 0x68, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, 0x73, + 0xBF, // Start of "defaults" map + + // Key: "customString" + 0x6C, 0x63, 0x75, 0x73, 0x74, 0x6F, 0x6D, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, + // Indefinite-length string for "hello" + 0x7F, 0x65, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0xFF, + + // Key: "defaultString" + 0x6D, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, + 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, + // Indefinite-length string for "hi" + 0x7F, 0x62, 0x68, 0x69, 0xFF, + + 0xFF, // End of the "defaults" map + 0xFF // End of indefinite-length map + ]) + let cborData2 = Data([ + 0xBF, // Start of outer indefinite-length map + + // Key: "defaults" + 0x68, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, 0x73, + 0xBF, // Start of "defaults" map + + // Key: "defaultString" + 0x6D, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, + 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, + // Indefinite-length string for "hi" + 0x7F, 0x62, 0x68, 0x69, 0xFF, + + // Key: "customString" + 0x6C, 0x63, 0x75, 0x73, 0x74, 0x6F, 0x6D, 0x53, 0x74, 0x72, 0x69, 0x6E, 0x67, + // Indefinite-length string for "hello" + 0x7F, 0x65, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0xFF, + + 0xFF, // End of the "defaults" map + 0xFF // End of indefinite-length map + ]) + XCTAssertTrue(try CBORComparator.cborData(cborData1, isEqualTo: cborData2)) + } +}