Skip to content

Commit aa6b3dc

Browse files
authored
feat: Support RPCv2 CBOR wire protocol (#887)
1 parent 5a922d8 commit aa6b3dc

32 files changed

+952
-50
lines changed

Package.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ let package = Package(
5050
.library(name: "SmithyStreams", targets: ["SmithyStreams"]),
5151
.library(name: "SmithyChecksumsAPI", targets: ["SmithyChecksumsAPI"]),
5252
.library(name: "SmithyChecksums", targets: ["SmithyChecksums"]),
53+
.library(name: "SmithyCBOR", targets: ["SmithyCBOR"]),
5354
.library(name: "SmithyWaitersAPI", targets: ["SmithyWaitersAPI"]),
5455
.library(name: "SmithyTestUtil", targets: ["SmithyTestUtil"]),
5556
],
@@ -92,6 +93,7 @@ let package = Package(
9293
"SmithyStreams",
9394
"SmithyChecksumsAPI",
9495
"SmithyChecksums",
96+
"SmithyCBOR",
9597
.product(name: "AwsCommonRuntimeKit", package: "aws-crt-swift"),
9698
],
9799
resources: [
@@ -140,7 +142,7 @@ let package = Package(
140142
),
141143
.target(
142144
name: "SmithyTestUtil",
143-
dependencies: ["ClientRuntime", "SmithyHTTPAPI", "SmithyIdentity"]
145+
dependencies: ["ClientRuntime", "SmithyHTTPAPI", "SmithyIdentity", "SmithyCBOR"]
144146
),
145147
.target(
146148
name: "SmithyIdentity",
@@ -222,6 +224,14 @@ let package = Package(
222224
.product(name: "AwsCommonRuntimeKit", package: "aws-crt-swift")
223225
]
224226
),
227+
.target(
228+
name: "SmithyCBOR",
229+
dependencies: [
230+
"SmithyReadWrite",
231+
"SmithyTimestamps",
232+
.product(name: "AwsCommonRuntimeKit", package: "aws-crt-swift")
233+
]
234+
),
225235
.target(
226236
name: "SmithyWaitersAPI"
227237
),
@@ -235,6 +245,10 @@ let package = Package(
235245
],
236246
resources: [ .process("Resources") ]
237247
),
248+
.testTarget(
249+
name: "SmithyCBORTests",
250+
dependencies: ["SmithyCBOR", "ClientRuntime", "SmithyTestUtil"]
251+
),
238252
.testTarget(
239253
name: "SmithyHTTPClientTests",
240254
dependencies: [
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import AwsCommonRuntimeKit
9+
import Foundation
10+
11+
@_spi(SmithyReadWrite) import protocol SmithyReadWrite.SmithyReader
12+
@_spi(SmithyReadWrite) import enum SmithyReadWrite.ReaderError
13+
@_spi(SmithyReadWrite) import protocol SmithyReadWrite.SmithyWriter
14+
@_spi(Smithy) import struct Smithy.Document
15+
@_spi(Smithy) import protocol Smithy.SmithyDocument
16+
@_spi(SmithyTimestamps) import enum SmithyTimestamps.TimestampFormat
17+
@_spi(SmithyTimestamps) import struct SmithyTimestamps.TimestampFormatter
18+
19+
@_spi(SmithyReadWrite)
20+
public final class Reader: SmithyReader {
21+
public typealias NodeInfo = String
22+
23+
public let cborValue: CBORType?
24+
public let nodeInfo: NodeInfo
25+
public internal(set) var children: [Reader] = []
26+
public internal(set) weak var parent: Reader?
27+
public var hasContent: Bool { cborValue != nil && cborValue != .null }
28+
29+
public init(nodeInfo: NodeInfo, cborValue: CBORType?, parent: Reader? = nil) {
30+
self.nodeInfo = nodeInfo
31+
self.cborValue = cborValue
32+
self.parent = parent
33+
self.children = Self.children(from: cborValue, parent: self)
34+
}
35+
36+
public static func from(data: Data) throws -> Reader {
37+
let decoder = try CBORDecoder(data: [UInt8](data))
38+
let rootValue: CBORType
39+
if decoder.hasNext() {
40+
rootValue = try decoder.popNext()
41+
} else {
42+
rootValue = .null
43+
}
44+
return Reader(nodeInfo: "", cborValue: rootValue, parent: nil)
45+
}
46+
47+
private static func children(from cborValue: CBORType?, parent: Reader) -> [Reader] {
48+
var children = [Reader]()
49+
switch cborValue {
50+
case .map(let map):
51+
for (key, value) in map {
52+
let child = Reader(nodeInfo: key, cborValue: value, parent: parent)
53+
children.append(child)
54+
}
55+
case .array(let array):
56+
for (index, value) in array.enumerated() {
57+
let child = Reader(nodeInfo: "\(index)", cborValue: value, parent: parent)
58+
children.append(child)
59+
}
60+
default:
61+
break
62+
}
63+
return children
64+
}
65+
66+
public subscript(nodeInfo: NodeInfo) -> Reader {
67+
if let match = children.first(where: { $0.nodeInfo == nodeInfo }) {
68+
return match
69+
} else {
70+
return Reader(nodeInfo: nodeInfo, cborValue: nil, parent: self)
71+
}
72+
}
73+
74+
public func readIfPresent() throws -> String? {
75+
switch cborValue {
76+
case .text(let string):
77+
return string
78+
case .indef_text_start:
79+
// Handle concatenation of indefinite-length text
80+
var combinedText = ""
81+
for child in children {
82+
if let chunk = try child.readIfPresent() as String? {
83+
combinedText += chunk
84+
}
85+
}
86+
return combinedText
87+
case .bytes(let data):
88+
return String(data: data, encoding: .utf8)
89+
default:
90+
return nil
91+
}
92+
}
93+
94+
public func readIfPresent() throws -> Int8? {
95+
switch cborValue {
96+
case .int(let intValue): return Int8(intValue)
97+
case .uint(let uintValue): return Int8(uintValue)
98+
default: return nil
99+
}
100+
}
101+
102+
public func readIfPresent() throws -> Int16? {
103+
switch cborValue {
104+
case .int(let intValue): return Int16(intValue)
105+
case .uint(let uintValue): return Int16(uintValue)
106+
default: return nil
107+
}
108+
}
109+
110+
public func readIfPresent() throws -> Int? {
111+
switch cborValue {
112+
case .int(let intValue): return Int(intValue)
113+
case .uint(let uintValue): return Int(uintValue)
114+
default: return nil
115+
}
116+
}
117+
118+
public func readIfPresent() throws -> Float? {
119+
switch cborValue {
120+
case .double(let doubleValue): return Float(doubleValue)
121+
case .int(let intValue): return Float(intValue)
122+
case .uint(let uintValue): return Float(uintValue)
123+
default: return nil
124+
}
125+
}
126+
127+
public func readIfPresent() throws -> Double? {
128+
switch cborValue {
129+
case .double(let doubleValue): return doubleValue
130+
case .int(let intValue): return Double(intValue)
131+
case .uint(let uintValue): return Double(uintValue)
132+
default: return nil
133+
}
134+
}
135+
136+
public func readIfPresent() throws -> Bool? {
137+
switch cborValue {
138+
case .bool(let boolValue): return boolValue
139+
default: return nil
140+
}
141+
}
142+
143+
public func readIfPresent() throws -> Data? {
144+
switch cborValue {
145+
case .bytes(let data): return data
146+
case .text(let string): return Data(base64Encoded: string)
147+
default: return nil
148+
}
149+
}
150+
151+
public func readIfPresent() throws -> Document? {
152+
// No operation. Smithy document not supported in CBOR
153+
return nil
154+
}
155+
156+
public func readIfPresent<T>() throws -> T? where T: RawRepresentable, T.RawValue == Int {
157+
guard let rawValue: Int = try readIfPresent() else { return nil }
158+
return T(rawValue: rawValue)
159+
}
160+
161+
public func readIfPresent<T>() throws -> T? where T: RawRepresentable, T.RawValue == String {
162+
guard let rawValue: String = try readIfPresent() else { return nil }
163+
return T(rawValue: rawValue)
164+
}
165+
166+
public func readTimestampIfPresent(format: TimestampFormat) throws -> Date? {
167+
switch cborValue {
168+
case .double(let doubleValue):
169+
return Date(timeIntervalSince1970: doubleValue)
170+
case .int(let intValue):
171+
return Date(timeIntervalSince1970: Double(intValue))
172+
case .uint(let uintValue):
173+
return Date(timeIntervalSince1970: Double(uintValue))
174+
case .text(let string):
175+
return TimestampFormatter(format: format).date(from: string)
176+
case .date(let dateValue):
177+
return dateValue // Directly return the date value
178+
default:
179+
return nil
180+
}
181+
}
182+
183+
public func readMapIfPresent<Value>(
184+
valueReadingClosure: (Reader) throws -> Value,
185+
keyNodeInfo: NodeInfo,
186+
valueNodeInfo: NodeInfo,
187+
isFlattened: Bool
188+
) throws -> [String: Value]? {
189+
guard let cborValue else { return nil }
190+
guard case .map(let map) = cborValue else { return nil }
191+
var dict = [String: Value]()
192+
for (key, _) in map {
193+
let reader = self[key]
194+
do {
195+
let value = try valueReadingClosure(reader)
196+
dict[key] = value
197+
} catch ReaderError.requiredValueNotPresent {
198+
if !(try reader.readNullIfPresent() ?? false) { throw ReaderError.requiredValueNotPresent }
199+
}
200+
}
201+
return dict
202+
}
203+
204+
public func readListIfPresent<Member>(
205+
memberReadingClosure: (Reader) throws -> Member,
206+
memberNodeInfo: NodeInfo,
207+
isFlattened: Bool
208+
) throws -> [Member]? {
209+
guard let cborValue else { return nil }
210+
guard case .array = cborValue else { return nil }
211+
return try children.map { child in
212+
return try memberReadingClosure(child)
213+
}
214+
}
215+
216+
public func readNullIfPresent() throws -> Bool? {
217+
guard let value = cborValue else {
218+
return nil
219+
}
220+
return value == .null
221+
}
222+
}

0 commit comments

Comments
 (0)