Skip to content

Commit 2334ad5

Browse files
tomerdGitHub Enterprise
authored and
GitHub Enterprise
committed
update code to swift5 and nio2 (#10)
* update code to swift5 and nio2 motivation: support newer version of swift and underlying dependencies changes: * adjust code to swift 5 * use swift 5 result type and better error handling for json encoding/decoding * adjust code to nio 2 * add dependency on swift-log for logging instead of print statements * make logger as part of context * improve error handling * add dependecy on swift-backtrace to capture crashes * improve lifecycle * adjust tests * add default formatting rules and fix formatting * improve readme
1 parent b06e2f1 commit 2334ad5

21 files changed

+439
-329
lines changed

.swiftformat

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# file options
2+
3+
--exclude .build
4+
5+
# format options
6+
7+
--self insert
8+
--patternlet inline
9+
--stripunusedargs unnamed-only
10+
--comments ignore
11+
12+
# rules

Package.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
// swift-tools-version:4.0
1+
// swift-tools-version:5.0
22

33
import PackageDescription
44

55
var targets: [PackageDescription.Target] = [
6-
.target(name: "SwiftAwsLambda", dependencies: ["NIOHTTP1"]),
6+
.target(name: "SwiftAwsLambda", dependencies: ["Logging", "Backtrace", "NIOHTTP1"]),
77
.target(name: "SwiftAwsLambdaSample", dependencies: ["SwiftAwsLambda"]),
88
.target(name: "SwiftAwsLambdaStringSample", dependencies: ["SwiftAwsLambda"]),
99
.target(name: "SwiftAwsLambdaCodableSample", dependencies: ["SwiftAwsLambda"]),
@@ -19,7 +19,9 @@ let package = Package(
1919
.executable(name: "SwiftAwsLambdaCodableSample", targets: ["SwiftAwsLambdaCodableSample"]),
2020
],
2121
dependencies: [
22-
.package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"),
22+
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"),
23+
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
24+
.package(url: "https://github.com/ianpartridge/swift-backtrace.git", from: "1.1.0"),
2325
],
2426
targets: targets
2527
)

Sources/SwiftAwsLambda/HttpClient.swift

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ internal class HTTPClient {
2626

2727
func get(url: String) -> EventLoopFuture<HTTPResponse> {
2828
guard let request = HTTPRequest(url: url, method: .GET) else {
29-
return self.eventLoop.newFailedFuture(error: HTTPClientError.invalidRequest)
29+
return self.eventLoop.makeFailedFuture(HTTPClientError.invalidRequest)
3030
}
3131
return self.execute(request)
3232
}
3333

3434
func post(url: String, body: ByteBuffer? = nil) -> EventLoopFuture<HTTPResponse> {
3535
guard let request = HTTPRequest(url: url, method: .POST, body: body) else {
36-
return self.eventLoop.newFailedFuture(error: HTTPClientError.invalidRequest)
36+
return self.eventLoop.makeFailedFuture(HTTPClientError.invalidRequest)
3737
}
3838
return self.execute(request)
3939
}
@@ -42,18 +42,16 @@ internal class HTTPClient {
4242
let bootstrap = ClientBootstrap(group: eventLoop)
4343
.channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1)
4444
.channelInitializer { channel in
45-
channel.pipeline.addHTTPClientHandlers().then {
46-
channel.pipeline.add(handler: HTTPPartsHandler())
47-
}.then {
48-
channel.pipeline.add(handler: UnaryHTTPHandler())
45+
channel.pipeline.addHTTPClientHandlers().flatMap {
46+
channel.pipeline.addHandlers([HTTPPartsHandler(), UnaryHTTPHandler()])
4947
}
5048
}
5149

52-
return bootstrap.connect(host: request.host, port: request.port).then { channel in
53-
let promise: EventLoopPromise<HTTPResponse> = channel.eventLoop.newPromise()
50+
return bootstrap.connect(host: request.host, port: request.port).flatMap { channel in
51+
let promise = channel.eventLoop.makePromise(of: HTTPResponse.self)
5452
let requestWrapper = HTTPRequestWrapper(request: request, promise: promise)
5553

56-
return channel.writeAndFlush(requestWrapper).then { _ in
54+
return channel.writeAndFlush(NIOAny(requestWrapper)).flatMap { _ in
5755
promise.futureResult
5856
}
5957
}
@@ -135,12 +133,12 @@ private struct HTTPResponseAccumulator {
135133
switch self.state {
136134
case .idle:
137135
preconditionFailure("no head received before body")
138-
case let .head(head):
136+
case .head(let head):
139137
self.state = .body(head, part)
140138
case .body(let head, var body):
141139
var part = part
142-
body.write(buffer: &part)
143-
state = .body(head, body)
140+
body.writeBuffer(&part)
141+
self.state = .body(head, body)
144142
case .end:
145143
preconditionFailure("request already processed")
146144
}
@@ -155,13 +153,13 @@ private class HTTPPartsHandler: ChannelInboundHandler, ChannelOutboundHandler {
155153

156154
var accumulator = HTTPResponseAccumulator()
157155

158-
func write(ctx: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
156+
func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
159157
let request = unwrapOutboundIn(data)
160158

161159
var head = HTTPRequestHead(version: request.version, method: request.method, uri: request.target)
162160
var headers = request.headers
163161

164-
if request.version.major == 1 && request.version.minor == 1 && !request.headers.contains(name: "Host") {
162+
if request.version.major == 1, request.version.minor == 1, !request.headers.contains(name: "Host") {
165163
headers.add(name: "Host", value: request.host)
166164
}
167165

@@ -173,43 +171,43 @@ private class HTTPPartsHandler: ChannelInboundHandler, ChannelOutboundHandler {
173171

174172
let part = HTTPClientRequestPart.head(head)
175173

176-
let headPromise: EventLoopPromise<Void> = ctx.eventLoop.newPromise()
177-
let bodyPromise: EventLoopPromise<Void> = ctx.eventLoop.newPromise()
174+
let headPromise = context.eventLoop.makePromise(of: Void.self)
175+
let bodyPromise = context.eventLoop.makePromise(of: Void.self)
178176

179-
ctx.write(wrapOutboundOut(part), promise: headPromise)
177+
context.write(wrapOutboundOut(part), promise: headPromise)
180178

181179
if let body = request.body {
182180
let part = HTTPClientRequestPart.body(.byteBuffer(body))
183181

184-
ctx.write(wrapOutboundOut(part), promise: bodyPromise)
182+
context.write(wrapOutboundOut(part), promise: bodyPromise)
185183
} else {
186-
bodyPromise.succeed(result: ())
184+
bodyPromise.succeed(())
187185
}
188186

189187
if let promise = promise {
190-
headPromise.futureResult.then { bodyPromise.futureResult }.cascade(promise: promise)
188+
headPromise.futureResult.flatMap { bodyPromise.futureResult }.cascade(to: promise)
191189
}
192190

193-
ctx.flush()
191+
context.flush()
194192
}
195193

196-
func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
194+
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
197195
let response = unwrapInboundIn(data)
198196

199197
switch response {
200-
case let .head(head):
198+
case .head(let head):
201199
self.accumulator.handle(head)
202-
case let .body(body):
200+
case .body(let body):
203201
self.accumulator.handle(body)
204202
case .end:
205203
switch self.accumulator.state {
206204
case .idle:
207205
preconditionFailure("no head received before end")
208-
case let .head(head):
209-
ctx.fireChannelRead(wrapInboundOut(HTTPResponse(status: head.status, headers: head.headers, body: nil)))
206+
case .head(let head):
207+
context.fireChannelRead(wrapInboundOut(HTTPResponse(status: head.status, headers: head.headers, body: nil)))
210208
self.accumulator.state = .end
211-
case let .body(head, body):
212-
ctx.fireChannelRead(wrapInboundOut(HTTPResponse(status: head.status, headers: head.headers, body: body)))
209+
case .body(let head, let body):
210+
context.fireChannelRead(wrapInboundOut(HTTPResponse(status: head.status, headers: head.headers, body: body)))
213211
self.accumulator.state = .end
214212
case .end:
215213
preconditionFailure("request already processed")
@@ -225,36 +223,38 @@ private class UnaryHTTPHandler: ChannelInboundHandler, ChannelOutboundHandler {
225223

226224
var buffer = CircularBuffer<EventLoopPromise<HTTPResponse>>()
227225

228-
func write(ctx: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
226+
func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
229227
let wrapper = unwrapOutboundIn(data)
230228
buffer.append(wrapper.promise)
231229
var request = wrapper.request
232230
request.headers.add(name: "Connection", value: "close")
233-
ctx.writeAndFlush(wrapOutboundOut(request), promise: promise)
231+
context.writeAndFlush(wrapOutboundOut(request), promise: promise)
234232
}
235233

236-
func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
234+
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
237235
let response = unwrapInboundIn(data)
238-
let promise = buffer.removeFirst()
239-
promise.succeed(result: response)
240-
ctx.close(promise: nil)
236+
let promise = self.buffer.removeFirst()
237+
context.close().whenComplete { _ in
238+
promise.succeed(response)
239+
}
241240
}
242241

243-
func errorCaught(ctx: ChannelHandlerContext, error: Error) {
244-
// In HTTP we should fail all promises as we close the Channel.
245-
self.failAllPromises(error: error)
246-
ctx.close(promise: nil)
242+
func errorCaught(context: ChannelHandlerContext, error: Error) {
243+
context.close().whenComplete { _ in
244+
// In HTTP we should fail all promises as we close the Channel.
245+
self.failAllPromises(error: error)
246+
}
247247
}
248248

249-
func channelInactive(ctx: ChannelHandlerContext) {
249+
func channelInactive(context: ChannelHandlerContext) {
250250
// Fail all promises
251251
self.failAllPromises(error: HTTPClientError.connectionClosed)
252-
ctx.fireChannelInactive()
252+
context.fireChannelInactive()
253253
}
254254

255255
private func failAllPromises(error: Error) {
256256
while let promise = buffer.first {
257-
promise.fail(error: error)
257+
promise.fail(error)
258258
self.buffer.removeFirst()
259259
}
260260
}

Sources/SwiftAwsLambda/Lambda+Codable.swift

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ extension Lambda {
4343
}
4444

4545
/// A result type for a Lambda that returns a generic `Out`, having `Out` extend `Encodable`.
46-
public typealias LambdaCodableResult<Out> = ResultType<Out, String>
46+
public typealias LambdaCodableResult<Out> = Result<Out, Error>
4747

4848
/// A callback for a Lambda that returns a `LambdaCodableResult<Out>` result type, having `Out` extend `Encodable`.
4949
public typealias LambdaCodableCallback<Out> = (LambdaCodableResult<Out>) -> Void
@@ -73,27 +73,31 @@ public extension LambdaCodableHandler {
7373
/// LambdaCodableCodec is an abstract/empty implementation for codec which does `Encodable` -> `[UInt8]` encoding and `[UInt8]` -> `Decodable' decoding.
7474
// TODO: would be nicer to use a protocol instead of this "abstract class", but generics get in the way
7575
public class LambdaCodableCodec<In: Decodable, Out: Encodable> {
76-
func encode(_: Out) -> [UInt8]? { return nil }
77-
func decode(_: [UInt8]) -> In? { return nil }
76+
func encode(_: Out) -> Result<[UInt8], Error> { fatalError("not implmented") }
77+
func decode(_: [UInt8]) -> Result<In, Error> { fatalError("not implmented") }
7878
}
7979

8080
/// Default implementation of `Encodable` -> `[UInt8]` encoding and `[UInt8]` -> `Decodable' decoding
8181
public extension LambdaCodableHandler {
8282
func handle(context: LambdaContext, payload: [UInt8], callback: @escaping (LambdaResult) -> Void) {
83-
guard let payloadAsCodable = codec.decode(payload) else {
84-
return callback(.failure("failed decoding payload (in)"))
85-
}
86-
self.handle(context: context, payload: payloadAsCodable, callback: { result in
87-
switch result {
88-
case let .success(encodable):
89-
guard let codableAsBytes = self.codec.encode(encodable) else {
90-
return callback(.failure("failed encoding result (out)"))
83+
switch self.codec.decode(payload) {
84+
case .failure(let error):
85+
return callback(.failure(Errors.requestDecoding(error)))
86+
case .success(let payloadAsCodable):
87+
self.handle(context: context, payload: payloadAsCodable) { result in
88+
switch result {
89+
case .failure(let error):
90+
return callback(.failure(error))
91+
case .success(let encodable):
92+
switch self.codec.encode(encodable) {
93+
case .failure(let error):
94+
return callback(.failure(Errors.responseEncoding(error)))
95+
case .success(let codableAsBytes):
96+
return callback(.success(codableAsBytes))
97+
}
9198
}
92-
return callback(.success(codableAsBytes))
93-
case let .failure(error):
94-
return callback(.failure(error))
9599
}
96-
})
100+
}
97101
}
98102
}
99103

@@ -103,12 +107,20 @@ public extension LambdaCodableHandler {
103107
private class LambdaCodableJsonCodec<In: Decodable, Out: Encodable>: LambdaCodableCodec<In, Out> {
104108
private let encoder = JSONEncoder()
105109
private let decoder = JSONDecoder()
106-
public override func encode(_ value: Out) -> [UInt8]? {
107-
return try? [UInt8](self.encoder.encode(value))
110+
public override func encode(_ value: Out) -> Result<[UInt8], Error> {
111+
do {
112+
return .success(try [UInt8](self.encoder.encode(value)))
113+
} catch {
114+
return .failure(error)
115+
}
108116
}
109117

110-
public override func decode(_ data: [UInt8]) -> In? {
111-
return try? self.decoder.decode(In.self, from: Data(data))
118+
public override func decode(_ data: [UInt8]) -> Result<In, Error> {
119+
do {
120+
return .success(try self.decoder.decode(In.self, from: Data(data)))
121+
} catch {
122+
return .failure(error)
123+
}
112124
}
113125
}
114126

@@ -124,3 +136,8 @@ private struct LambdaClosureWrapper<In: Decodable, Out: Encodable>: LambdaCodabl
124136
self.closure(context, payload, callback)
125137
}
126138
}
139+
140+
private enum Errors: Error {
141+
case responseEncoding(Error)
142+
case requestDecoding(Error)
143+
}

Sources/SwiftAwsLambda/Lambda+String.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ extension Lambda {
4040
}
4141

4242
/// A result type for a Lambda that returns a `String`.
43-
public typealias LambdaStringResult = ResultType<String, String>
43+
public typealias LambdaStringResult = Result<String, Error>
4444

4545
/// A callback for a Lambda that returns a `LambdaStringResult` result type.
4646
public typealias LambdaStringCallback = (LambdaStringResult) -> Void
@@ -56,14 +56,14 @@ public protocol LambdaStringHandler: LambdaHandler {
5656
/// Default implementation of `String` -> `[UInt8]` encoding and `[UInt8]` -> `String' decoding
5757
public extension LambdaStringHandler {
5858
func handle(context: LambdaContext, payload: [UInt8], callback: @escaping LambdaCallback) {
59-
self.handle(context: context, payload: String(decoding: payload, as: UTF8.self), callback: { result in
59+
self.handle(context: context, payload: String(decoding: payload, as: UTF8.self)) { result in
6060
switch result {
61-
case let .success(string):
61+
case .success(let string):
6262
return callback(.success([UInt8](string.utf8)))
63-
case let .failure(error):
63+
case .failure(let error):
6464
return callback(.failure(error))
6565
}
66-
})
66+
}
6767
}
6868
}
6969

0 commit comments

Comments
 (0)