Skip to content
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

Added SNS & SQS Events #46

Merged
merged 7 commits into from
Apr 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions Sources/AWSLambdaEvents/SNS.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright (c) 2017-2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import struct Foundation.Date

/// https://github.com/aws/aws-lambda-go/blob/master/events/sns.go
public enum SNS {
public struct Event: Decodable {
public struct Record: Decodable {
public let eventVersion: String
public let eventSubscriptionArn: String
public let eventSource: String
public let sns: Message

public enum CodingKeys: String, CodingKey {
case eventVersion = "EventVersion"
case eventSubscriptionArn = "EventSubscriptionArn"
case eventSource = "EventSource"
case sns = "Sns"
}
}

public let records: [Record]

public enum CodingKeys: String, CodingKey {
case records = "Records"
}
}

public struct Message {
public enum Attribute {
case string(String)
case binary([UInt8])
}

public let signature: String
public let messageId: String
public let type: String
public let topicArn: String
public let messageAttributes: [String: Attribute]
public let signatureVersion: String

@ISO8601WithFractionalSecondsCoding
public var timestamp: Date
public let signingCertURL: String
public let message: String
public let unsubscribeUrl: String
public let subject: String?
}
}

extension SNS.Message: Decodable {
enum CodingKeys: String, CodingKey {
case signature = "Signature"
case messageId = "MessageId"
case type = "Type"
case topicArn = "TopicArn"
case messageAttributes = "MessageAttributes"
case signatureVersion = "SignatureVersion"
case timestamp = "Timestamp"
case signingCertURL = "SigningCertUrl"
case message = "Message"
case unsubscribeUrl = "UnsubscribeUrl"
case subject = "Subject"
}
}

extension SNS.Message.Attribute: Equatable {}

extension SNS.Message.Attribute: Decodable {
enum CodingKeys: String, CodingKey {
case dataType = "Type"
case dataValue = "Value"
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

let dataType = try container.decode(String.self, forKey: .dataType)
// https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html#SNSMessageAttributes.DataTypes
switch dataType {
case "String":
let value = try container.decode(String.self, forKey: .dataValue)
self = .string(value)
case "Binary":
let base64encoded = try container.decode(String.self, forKey: .dataValue)
let bytes = try base64encoded.base64decoded()
self = .binary(bytes)
default:
throw DecodingError.dataCorruptedError(forKey: .dataType, in: container, debugDescription: """
Unexpected value \"\(dataType)\" for key \(CodingKeys.dataType).
Expected `String` or `Binary`.
""")
}
}
}
96 changes: 96 additions & 0 deletions Sources/AWSLambdaEvents/SQS.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright (c) 2017-2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

/// https://github.com/aws/aws-lambda-go/blob/master/events/sqs.go
public enum SQS {
public struct Event: Decodable {
public let records: [Message]

enum CodingKeys: String, CodingKey {
case records = "Records"
}
}

public struct Message {
/// https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_MessageAttributeValue.html
public enum Attribute {
case string(String)
case binary([UInt8])
case number(String)
}

public let messageId: String
public let receiptHandle: String
public var body: String
public let md5OfBody: String
public let md5OfMessageAttributes: String?
public let attributes: [String: String]
public let messageAttributes: [String: Attribute]
tomerd marked this conversation as resolved.
Show resolved Hide resolved
public let eventSourceArn: String
public let eventSource: String
public let awsRegion: AWSRegion
}
}

extension SQS.Message: Decodable {
enum CodingKeys: String, CodingKey {
case messageId
case receiptHandle
case body
case md5OfBody
case md5OfMessageAttributes
case attributes
case messageAttributes
case eventSourceArn = "eventSourceARN"
case eventSource
case awsRegion
}
}

extension SQS.Message.Attribute: Equatable {}

extension SQS.Message.Attribute: Decodable {
enum CodingKeys: String, CodingKey {
case dataType
case stringValue
case binaryValue

// BinaryListValue and StringListValue are unimplemented since
// they are not implemented as discussed here:
// https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_MessageAttributeValue.html
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

let dataType = try container.decode(String.self, forKey: .dataType)
switch dataType {
case "String":
let value = try container.decode(String.self, forKey: .stringValue)
self = .string(value)
case "Number":
let value = try container.decode(String.self, forKey: .stringValue)
self = .number(value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should .number just be the numeric value instead of the AWSNumber decoding envelope?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't thought about this so far. Tried it today and ran into two issues:

  1. Protocol 'Numeric' can only be used as a generic constraint because it has Self or associated type requirements.
  public enum Attribute {
    case string(String)
    case binary([UInt8])
    case number(Numeric)
}

I don't think we should make Attribute generic.

  1. Even if this was working, how would we parse the number in this case? Try to parse into Int and if that doesn't work parse into Double?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was thinking the enum option will include int and double instead of generic number. wdyt?

Copy link
Member Author

@fabianfett fabianfett Mar 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds reasonable, but we would not use the original data format which by itself is just a plain String. Anyway, how would we parse this? Try Int first? If this doesn't work try to parse Double? Your call.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomerd do you have any input on this?

Copy link
Contributor

@tomerd tomerd Mar 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be clear, I was not pushing against using AWSNumber for decoding, but against "leaking" it in the enum. I guess you are asking if we should first try AWSNumber's double accessor or the int accessor first to determine how to populate the enum?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. I can follow you in not wanting to not the leak the AWSNumber type. Do you have an idea in mind how you would like to achieve this?

case "Binary":
let base64encoded = try container.decode(String.self, forKey: .binaryValue)
let bytes = try base64encoded.base64decoded()
self = .binary(bytes)
default:
throw DecodingError.dataCorruptedError(forKey: .dataType, in: container, debugDescription: """
Unexpected value \"\(dataType)\" for key \(CodingKeys.dataType).
Expected `String`, `Binary` or `Number`.
""")
}
}
}
Loading