-
Notifications
You must be signed in to change notification settings - Fork 113
Added AWSLambdaEvents library #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
7864e00
to
1f26995
Compare
Local
|
Sources/AWSLambdaEvents/S3.swift
Outdated
import Foundation | ||
import NIO | ||
|
||
// https://github.com/aws/aws-lambda-go/blob/master/events/s3.go |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no better/official doc?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sources/AWSLambdaEvents/SQS.swift
Outdated
case .binary(let byteBuffer): | ||
let base64 = byteBuffer.withUnsafeReadableBytes { (pointer) -> String in | ||
String(base64Encoding: pointer) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@weissi not gonna like this :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this is a pattern we need for base64 maybe we add a ByteBuffer extension in that area of the code to keep it DRY and minimize chances of getting unsafe access wrong
|
||
//===----------------------------------------------------------------------===// | ||
// This is a vendored version from: | ||
// https://github.com/fabianfett/swift-base64-kit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
faster than foundation's?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tldr: yes.
https://github.com/fabianfett/swift-base64-kit#performance
https://github.com/fabianfett/swift-base64-kit/runs/364930115 (Expand Run test
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay. was this reviewed by the nio team already, or should I review?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the encoding was reviewed afaik, the decoding wasn't.
) | ||
|
||
do { | ||
let data = try JSONEncoder().encodeAsByteBuffer(resp, allocator: ByteBufferAllocator()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XCTAssertNoThrows, but then reading the value becomes a bit odd ... XCTAssertNoThrows should really be returning T
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, that's why most of my tests looks like this. one can of course do
func testBla() throws {
let a = try blub()
}
But if an error is thrown the error isn't printed anywhere in a simple and easy to read way. That's why I decided to go the expressive/repetitive way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I always write these tests like that now:
func testFoo() /* no throws! */ {
var maybeThing: Thing?
XCTAssertNoThrow(maybeThing = try makeThing())
guard let thing = maybeThing else {
XCTFail("couldn't make thing")
return
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do almost the same as @weissi
var thing: Thing!
XCTAssertNoThrow(thing = try makeThing())
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tomerd do you want me to change the tests to your format?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
low priority, but would be nicer imo. throwing tests are a bit of a smell
3ec24d8
to
5020b90
Compare
let data = SQSTests.testPayload.data(using: .utf8)! | ||
do { | ||
let decoder = JSONDecoder() | ||
let event = try decoder.decode(SQS.Event.self, from: data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in this case you could just write
var event: Event?
XCTAssertNoThrow(event = try decoder.decode(...)
XCTAssertEqual(1, event?.records.first)
guard let message = event?.records.first else {
XCTFail(...)
return
}
|
||
// https://github.com/aws/aws-lambda-go/blob/master/events/apigw.go | ||
|
||
public enum APIGateway { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we namespace these with AWS
to make sure generic terms like APIGateway
that may refer to user's own code or other SDK that may be integrated into this will have enough "space"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do already have a namespace (the package-name), don't we?
public let elb: ELBContext | ||
} | ||
|
||
public let httpMethod: HTTPMethod |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we explore NIO types like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting question. As you might have noticed that this is code copied over from my lambda runtime. The reason I choose to expose those NIO types is. I wanted to build something barebone, allowing as many use-cases as possible in an environment that people might be aware of while using only needed Foundation types.
I don't know what API design goal we have in mind for this library? AsyncHTTPClient uses the same nio types btw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd argue that AsyncHTTPClient and this library are at a different level of abstraction and target different audiences. I dont see a strong reason for the target audience of Lambda to need and learn about NIO in general or ByteBuffer specifically, while Data (or [Int8] if we want to avoid Foundation) is a much more natural type for them. when it comes to HTTPMethod and HTTPHeaders, I would argue that we should not leak NIO types as this is an implementation detail leaking to user's APIs, and we can easily wrap them with our own.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if it makes so much sense to reimplement: HTTPMethod, HTTPStatusCode and HTTPHeaders. What do you think exporting those with:
@_exported import struct NIOHTTP1.HTTPMethod
Otherwise for some code we would just duplicate it I guess.
import struct Foundation.Data | ||
import class Foundation.JSONDecoder | ||
|
||
public protocol DecodableBody { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this need to be public?
} | ||
|
||
public struct TargetGroupResponse { | ||
public let statusCode: HTTPResponseStatus |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same question on exposing NIO types
public struct TargetGroupResponse { | ||
public let statusCode: HTTPResponseStatus | ||
public let statusDescription: String? | ||
public let headers: HTTPHeaders? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
statusCode: HTTPResponseStatus, | ||
statusDescription: String? = nil, | ||
headers: HTTPHeaders? = nil, | ||
body: String = "", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't it optional?
self.isBase64Encoded = try container.decode(Bool.self, forKey: .isBase64Encoded) | ||
|
||
let body = try container.decode(String.self, forKey: .body) | ||
self.body = body != "" ? body : nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why empty string comparison?
Sources/AWSLambdaEvents/ALB.swift
Outdated
// MARK: - Response - | ||
|
||
extension ALB.TargetGroupResponse: Encodable { | ||
public static let MultiValueHeadersEnabledKey = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs to be public?
singleValueHeaders[name] = value | ||
} | ||
try container.encode(singleValueHeaders, forKey: .headers) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe you can sue reduce
in some of these headers transformations
Sources/AWSLambdaEvents/ALB.swift
Outdated
encoder: JSONEncoder = JSONEncoder() | ||
) throws { | ||
var headers = headers ?? HTTPHeaders() | ||
headers.add(name: "Content-Type", value: "application/json") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you shed some light on why this is needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to signal that the content is json. The Application Load Balancer (ALB)/APIGatway api is quite interesting:
- You send back a json that mimics the properties of an http response. That's why you've properties like
headers
,statusCode
andpayload
. So the ALB.TargetGroupResponse here is the json equivalent of a HTTPResponse. - If you want to send json back to the client you will have a json structure that is your httpresponse (ALB.TargetGroupResponse) and within this structure you will have your actual json payload and headers. Thats why I set http headers here within the json payload.
- so the outer json payload and http headers (from the actual http response) are there to communicate with the control plane, whereas the inner http headers and payload are communicated back to the user making the request against the apigateway/application load balancer.
Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kinda. since you are receiving headers from the caller, I think we should check that content type is not set before writing on top of it potentially?
Sources/AWSLambdaEvents/SNS.swift
Outdated
|
||
extension SNS.Message: DecodableBody { | ||
public var body: String? { | ||
return self.message != "" ? self.message : nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the empty string?
Sources/AWSLambdaEvents/SNS.swift
Outdated
public struct Message { | ||
public enum Attribute { | ||
case string(String) | ||
case binary(ByteBuffer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto re ByteBuffer
Sources/AWSLambdaEvents/SNS.swift
Outdated
try container.encode("Binary", forKey: .dataType) | ||
try container.encode(base64, forKey: .dataValue) | ||
case .string(let string): | ||
try container.encode("String", forKey: .dataType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using const/enum for string/binary?
Sources/AWSLambdaEvents/SQS.swift
Outdated
/// https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_MessageAttributeValue.html | ||
public enum Attribute { | ||
case string(String) | ||
case binary(ByteBuffer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto re ByteBuffer
Sources/AWSLambdaEvents/SQS.swift
Outdated
buffer.moveReaderIndex(to: bytes.count) | ||
|
||
self = .binary(buffer) | ||
default: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const to enum for String/Number/Binary
data = Data(bytes) | ||
} else { | ||
// TBD: Can this ever fail? I wouldn't think so... | ||
data = payload.data(using: .utf8)! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a guard would be better than force unwrap
values.forEach { value in | ||
nioHeaders.append((key, value)) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe can use reduce?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could do that, but i'm afraid that this could lead to some ref count overhead since the array would be copied around quite a lot:
- we get the target array as input
- we need to create a
var output = input
(ref count) - we write to the
output.append()
- still referenced by theinput
, this means CoW - we return output (ref count?!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe if we make all our headers [String: [String]] this becomes simpler / redundant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fabianfett awesome work, very useful addition. comments and ideas inline, did not review base64 code or tests yet
f775b5e
to
db7f9e6
Compare
Sources/AWSLambdaEvents/ALB.swift
Outdated
self.statusDescription = statusDescription | ||
self.headers = headers | ||
|
||
var buffer = try encoder.encodeAsByteBuffer(payload, allocator: ByteBufferAllocator()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would be nice to find a way to provide an allocator into all such methods
public let resourceId: String | ||
public let apiId: String | ||
public let resourcePath: String | ||
public let httpMethod: String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here we are using String and not the nio type. maybe we can do the same in other places, or come up with our own enum?
fb799a6
to
0764f2e
Compare
fixed a stupid bug Remove bytebuffer usages.
0764f2e
to
ee65380
Compare
Motivation
Lambda has a number of triggers within the AWS ecosystem, these triggers create events that are then handed to Lambda. To allow faster development with Swift and Lambda we should include as many events as possible with our Lambda.
In Go the batteries are included as well: https://github.com/aws/aws-lambda-go/tree/master/events
Changes
AWSLambaEvents
AWSLambaEventsTests
AWSLambaEvents
vends thefabianfett/swift-base64-kit
Base64 implementation that is also vended in SwiftNIOWebsocketAWSLambaEvents
links Foundation (primarily forDate
andDateFormatter
)Open ends