|
12 | 12 | //
|
13 | 13 | //===----------------------------------------------------------------------===//
|
14 | 14 |
|
15 |
| -import Foundation // for JSON |
| 15 | +import class Foundation.JSONDecoder |
| 16 | +import class Foundation.JSONEncoder |
| 17 | +import NIO |
| 18 | +import NIOFoundationCompat |
16 | 19 |
|
17 | 20 | /// Extension to the `Lambda` companion to enable execution of Lambdas that take and return `Codable` payloads.
|
18 |
| -/// This is the most common way to use this library in AWS Lambda, since its JSON based. |
19 | 21 | extension Lambda {
|
20 |
| - /// Run a Lambda defined by implementing the `LambdaCodableClosure` closure, having `In` and `Out` extending `Decodable` and `Encodable` respectively. |
| 22 | + /// Run a Lambda defined by implementing the `CodableLambdaClosure` function. |
21 | 23 | ///
|
22 |
| - /// - note: This is a blocking operation that will run forever, as it's lifecycle is managed by the AWS Lambda Runtime Engine. |
23 |
| - public static func run<In: Decodable, Out: Encodable>(_ closure: @escaping LambdaCodableClosure<In, Out>) { |
24 |
| - self.run(LambdaClosureWrapper(closure)) |
| 24 | + /// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine. |
| 25 | + @inlinable |
| 26 | + public static func run<In: Decodable, Out: Encodable>(_ closure: @escaping CodableLambdaClosure<In, Out>) { |
| 27 | + self.run(closure: closure) |
| 28 | + } |
| 29 | + |
| 30 | + /// Run a Lambda defined by implementing the `CodableVoidLambdaClosure` function. |
| 31 | + /// |
| 32 | + /// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine. |
| 33 | + @inlinable |
| 34 | + public static func run<In: Decodable>(_ closure: @escaping CodableVoidLambdaClosure<In>) { |
| 35 | + self.run(closure: closure) |
| 36 | + } |
| 37 | + |
| 38 | + // for testing |
| 39 | + @inlinable |
| 40 | + @discardableResult |
| 41 | + internal static func run<In: Decodable, Out: Encodable>(configuration: Configuration = .init(), closure: @escaping CodableLambdaClosure<In, Out>) -> Result<Int, Error> { |
| 42 | + return self.run(configuration: configuration, handler: CodableLambdaClosureWrapper(closure)) |
25 | 43 | }
|
26 | 44 |
|
27 | 45 | // for testing
|
28 |
| - internal static func run<In: Decodable, Out: Encodable>(configuration: Configuration = .init(), closure: @escaping LambdaCodableClosure<In, Out>) -> LambdaLifecycleResult { |
29 |
| - return self.run(configuration: configuration, handler: LambdaClosureWrapper(closure)) |
| 46 | + @inlinable |
| 47 | + @discardableResult |
| 48 | + internal static func run<In: Decodable>(configuration: Configuration = .init(), closure: @escaping CodableVoidLambdaClosure<In>) -> Result<Int, Error> { |
| 49 | + return self.run(configuration: configuration, handler: CodableVoidLambdaClosureWrapper(closure)) |
30 | 50 | }
|
31 | 51 | }
|
32 | 52 |
|
33 |
| -/// A callback for a Lambda that returns a `Result<Out, Error>` result type, having `Out` extend `Encodable`. |
34 |
| -public typealias LambdaCodableCallback<Out> = (Result<Out, Error>) -> Void |
| 53 | +/// A processing closure for a Lambda that takes a `In` and returns a `Result<Out, Error>` via a `CompletionHandler` asynchronously. |
| 54 | +public typealias CodableLambdaClosure<In: Decodable, Out: Encodable> = (Lambda.Context, In, @escaping (Result<Out, Error>) -> Void) -> Void |
35 | 55 |
|
36 |
| -/// A processing closure for a Lambda that takes an `In` and returns an `Out` via `LambdaCodableCallback<Out>` asynchronously, |
37 |
| -/// having `In` and `Out` extending `Decodable` and `Encodable` respectively. |
38 |
| -public typealias LambdaCodableClosure<In, Out> = (Lambda.Context, In, LambdaCodableCallback<Out>) -> Void |
| 56 | +/// A processing closure for a Lambda that takes a `In` and returns a `Result<Void, Error>` via a `CompletionHandler` asynchronously. |
| 57 | +public typealias CodableVoidLambdaClosure<In: Decodable> = (Lambda.Context, In, @escaping (Result<Void, Error>) -> Void) -> Void |
39 | 58 |
|
40 |
| -/// A processing protocol for a Lambda that takes an `In` and returns an `Out` via `LambdaCodableCallback<Out>` asynchronously, |
41 |
| -/// having `In` and `Out` extending `Decodable` and `Encodable` respectively. |
42 |
| -public protocol LambdaCodableHandler: LambdaHandler { |
43 |
| - associatedtype In: Decodable |
44 |
| - associatedtype Out: Encodable |
| 59 | +@usableFromInline |
| 60 | +internal struct CodableLambdaClosureWrapper<In: Decodable, Out: Encodable>: LambdaHandler { |
| 61 | + @usableFromInline |
| 62 | + typealias In = In |
| 63 | + @usableFromInline |
| 64 | + typealias Out = Out |
45 | 65 |
|
46 |
| - func handle(context: Lambda.Context, payload: In, callback: @escaping LambdaCodableCallback<Out>) |
47 |
| - var codec: LambdaCodableCodec<In, Out> { get } |
48 |
| -} |
| 66 | + private let closure: CodableLambdaClosure<In, Out> |
49 | 67 |
|
50 |
| -/// Default implementation for `LambdaCodableHandler` codec which uses JSON via `LambdaCodableJsonCodec`. |
51 |
| -/// Advanced users that want to inject their own codec can do it by overriding this. |
52 |
| -public extension LambdaCodableHandler { |
53 |
| - var codec: LambdaCodableCodec<In, Out> { |
54 |
| - return LambdaCodableJsonCodec<In, Out>() |
| 68 | + @usableFromInline |
| 69 | + init(_ closure: @escaping CodableLambdaClosure<In, Out>) { |
| 70 | + self.closure = closure |
55 | 71 | }
|
56 |
| -} |
57 |
| - |
58 |
| -/// LambdaCodableCodec is an abstract/empty implementation for codec which does `Encodable` -> `[UInt8]` encoding and `[UInt8]` -> `Decodable' decoding. |
59 |
| -// TODO: would be nicer to use a protocol instead of this "abstract class", but generics get in the way |
60 |
| -public class LambdaCodableCodec<In: Decodable, Out: Encodable> { |
61 |
| - func encode(_: Out) -> Result<[UInt8], Error> { fatalError("not implmented") } |
62 |
| - func decode(_: [UInt8]) -> Result<In, Error> { fatalError("not implmented") } |
63 |
| -} |
64 | 72 |
|
65 |
| -/// Default implementation of `Encodable` -> `[UInt8]` encoding and `[UInt8]` -> `Decodable' decoding |
66 |
| -public extension LambdaCodableHandler { |
67 |
| - func handle(context: Lambda.Context, payload: [UInt8], callback: @escaping LambdaCallback) { |
68 |
| - switch self.codec.decode(payload) { |
69 |
| - case .failure(let error): |
70 |
| - return callback(.failure(Errors.requestDecoding(error))) |
71 |
| - case .success(let payloadAsCodable): |
72 |
| - self.handle(context: context, payload: payloadAsCodable) { result in |
73 |
| - switch result { |
74 |
| - case .failure(let error): |
75 |
| - return callback(.failure(error)) |
76 |
| - case .success(let encodable): |
77 |
| - switch self.codec.encode(encodable) { |
78 |
| - case .failure(let error): |
79 |
| - return callback(.failure(Errors.responseEncoding(error))) |
80 |
| - case .success(let codableAsBytes): |
81 |
| - return callback(.success(codableAsBytes)) |
82 |
| - } |
83 |
| - } |
84 |
| - } |
85 |
| - } |
| 73 | + @usableFromInline |
| 74 | + func handle(context: Lambda.Context, payload: In, callback: @escaping (Result<Out, Error>) -> Void) { |
| 75 | + self.closure(context, payload, callback) |
86 | 76 | }
|
87 | 77 | }
|
88 | 78 |
|
89 |
| -/// LambdaCodableJsonCodec is an implementation of `LambdaCodableCodec` which does `Encodable` -> `[UInt8]` encoding and `[UInt8]` -> `Decodable' decoding |
90 |
| -/// using JSONEncoder and JSONDecoder respectively. |
91 |
| -// This is a class as encoder amd decoder are a class, which means its cheaper to hold a reference to both in a class then a struct. |
92 |
| -private final class LambdaCodableJsonCodec<In: Decodable, Out: Encodable>: LambdaCodableCodec<In, Out> { |
93 |
| - private let encoder = JSONEncoder() |
94 |
| - private let decoder = JSONDecoder() |
95 |
| - |
96 |
| - public override func encode(_ value: Out) -> Result<[UInt8], Error> { |
97 |
| - do { |
98 |
| - return .success(try [UInt8](self.encoder.encode(value))) |
99 |
| - } catch { |
100 |
| - return .failure(error) |
101 |
| - } |
| 79 | +@usableFromInline |
| 80 | +internal struct CodableVoidLambdaClosureWrapper<In: Decodable>: LambdaHandler { |
| 81 | + @usableFromInline |
| 82 | + typealias In = In |
| 83 | + @usableFromInline |
| 84 | + typealias Out = Void |
| 85 | + |
| 86 | + private let closure: CodableVoidLambdaClosure<In> |
| 87 | + |
| 88 | + @usableFromInline |
| 89 | + init(_ closure: @escaping CodableVoidLambdaClosure<In>) { |
| 90 | + self.closure = closure |
102 | 91 | }
|
103 | 92 |
|
104 |
| - public override func decode(_ data: [UInt8]) -> Result<In, Error> { |
105 |
| - do { |
106 |
| - return .success(try self.decoder.decode(In.self, from: Data(data))) |
107 |
| - } catch { |
108 |
| - return .failure(error) |
109 |
| - } |
| 93 | + @usableFromInline |
| 94 | + func handle(context: Lambda.Context, payload: In, callback: @escaping (Result<Out, Error>) -> Void) { |
| 95 | + self.closure(context, payload, callback) |
110 | 96 | }
|
111 | 97 | }
|
112 | 98 |
|
113 |
| -private struct LambdaClosureWrapper<In: Decodable, Out: Encodable>: LambdaCodableHandler { |
114 |
| - typealias Codec = LambdaCodableJsonCodec<In, Out> |
115 |
| - |
116 |
| - private let closure: LambdaCodableClosure<In, Out> |
117 |
| - init(_ closure: @escaping LambdaCodableClosure<In, Out>) { |
118 |
| - self.closure = closure |
| 99 | +/// Implementation of a`ByteBuffer` to `In` and `Out` to `ByteBuffer` codec |
| 100 | +/// Using Foundation's JSONEncoder and JSONDecoder |
| 101 | +/// Advanced users that want to inject their own codec can do it by overriding these functions. |
| 102 | +public extension EventLoopLambdaHandler where In: Decodable, Out: Encodable { |
| 103 | + func encode(allocator: ByteBufferAllocator, value: Out) throws -> ByteBuffer? { |
| 104 | + // nio will resize the buffer if necessary |
| 105 | + // FIXME: reusable JSONEncoder and buffer |
| 106 | + var buffer = allocator.buffer(capacity: 1024) |
| 107 | + try JSONEncoder().encode(value, into: &buffer) |
| 108 | + return buffer |
119 | 109 | }
|
120 | 110 |
|
121 |
| - public func handle(context: Lambda.Context, payload: In, callback: @escaping LambdaCodableCallback<Out>) { |
122 |
| - self.closure(context, payload, callback) |
| 111 | + func decode(buffer: ByteBuffer) throws -> In { |
| 112 | + // FIXME: reusable JSONDecoder |
| 113 | + return try JSONDecoder().decode(In.self, from: buffer) |
123 | 114 | }
|
124 | 115 | }
|
125 | 116 |
|
126 |
| -private enum Errors: Error { |
127 |
| - case responseEncoding(Error) |
128 |
| - case requestDecoding(Error) |
| 117 | +public extension EventLoopLambdaHandler where In: Decodable, Out == Void { |
| 118 | + func encode(allocator: ByteBufferAllocator, value: Void) throws -> ByteBuffer? { |
| 119 | + return nil |
| 120 | + } |
| 121 | + |
| 122 | + func decode(buffer: ByteBuffer) throws -> In { |
| 123 | + // FIXME: reusable JSONDecoder |
| 124 | + return try JSONDecoder().decode(In.self, from: buffer) |
| 125 | + } |
129 | 126 | }
|
0 commit comments