Skip to content

Commit d2f5c1c

Browse files
authored
Added SES Event type (#130)
motivation: support event triggers from SES changes: Added SES Event type
1 parent 437a60d commit d2f5c1c

File tree

4 files changed

+289
-0
lines changed

4 files changed

+289
-0
lines changed

Sources/AWSLambdaEvents/SES.swift

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2017-2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import struct Foundation.Date
16+
17+
// https://docs.aws.amazon.com/lambda/latest/dg/services-ses.html
18+
19+
public enum SES {
20+
public struct Event: Decodable {
21+
public struct Record: Decodable {
22+
public let eventSource: String
23+
public let eventVersion: String
24+
public let ses: Message
25+
}
26+
27+
public let records: [Record]
28+
29+
public enum CodingKeys: String, CodingKey {
30+
case records = "Records"
31+
}
32+
}
33+
34+
public struct Message: Decodable {
35+
public let mail: Mail
36+
public let receipt: Receipt
37+
}
38+
39+
public struct Mail: Decodable {
40+
public let commonHeaders: CommonHeaders
41+
public let destination: [String]
42+
public let headers: [Header]
43+
public let headersTruncated: Bool
44+
public let messageId: String
45+
public let source: String
46+
@ISO8601WithFractionalSecondsCoding public var timestamp: Date
47+
}
48+
49+
public struct CommonHeaders: Decodable {
50+
public let bcc: [String]?
51+
public let cc: [String]?
52+
@RFC5322DateTimeCoding public var date: Date
53+
public let from: [String]
54+
public let messageId: String
55+
public let returnPath: String?
56+
public let subject: String?
57+
public let to: [String]?
58+
}
59+
60+
public struct Header: Decodable {
61+
public let name: String
62+
public let value: String
63+
}
64+
65+
public struct Receipt: Decodable {
66+
public let action: Action
67+
public let dmarcPolicy: DMARCPolicy?
68+
public let dmarcVerdict: Verdict?
69+
public let dkimVerdict: Verdict
70+
public let processingTimeMillis: Int
71+
public let recipients: [String]
72+
public let spamVerdict: Verdict
73+
public let spfVerdict: Verdict
74+
@ISO8601WithFractionalSecondsCoding public var timestamp: Date
75+
public let virusVerdict: Verdict
76+
}
77+
78+
public struct Action: Decodable {
79+
public let functionArn: String
80+
public let invocationType: String
81+
public let type: String
82+
}
83+
84+
public struct Verdict: Decodable {
85+
public let status: Status
86+
}
87+
88+
public enum DMARCPolicy: String, Decodable {
89+
case none
90+
case quarantine
91+
case reject
92+
}
93+
94+
public enum Status: String, Decodable {
95+
case pass = "PASS"
96+
case fail = "FAIL"
97+
case gray = "GRAY"
98+
case processingFailed = "PROCESSING_FAILED"
99+
}
100+
}

Sources/AWSLambdaEvents/Utils/DateWrappers.swift

+29
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import struct Foundation.Date
16+
import class Foundation.DateFormatter
1617
import class Foundation.ISO8601DateFormatter
18+
import struct Foundation.Locale
1719

1820
@propertyWrapper
1921
public struct ISO8601Coding: Decodable {
@@ -67,3 +69,30 @@ public struct ISO8601WithFractionalSecondsCoding: Decodable {
6769
return formatter
6870
}
6971
}
72+
73+
@propertyWrapper
74+
public struct RFC5322DateTimeCoding: Decodable {
75+
public let wrappedValue: Date
76+
77+
public init(wrappedValue: Date) {
78+
self.wrappedValue = wrappedValue
79+
}
80+
81+
public init(from decoder: Decoder) throws {
82+
let container = try decoder.singleValueContainer()
83+
let dateString = try container.decode(String.self)
84+
guard let date = Self.dateFormatter.date(from: dateString) else {
85+
throw DecodingError.dataCorruptedError(in: container, debugDescription:
86+
"Expected date to be in RFC5322 date-time format with fractional seconds, but `\(dateString)` does not forfill format")
87+
}
88+
self.wrappedValue = date
89+
}
90+
91+
private static let dateFormatter: DateFormatter = Self.createDateFormatter()
92+
private static func createDateFormatter() -> DateFormatter {
93+
let formatter = DateFormatter()
94+
formatter.dateFormat = "EEE, d MMM yyy HH:mm:ss z"
95+
formatter.locale = Locale(identifier: "en_US_POSIX")
96+
return formatter
97+
}
98+
}
+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2017-2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
@testable import AWSLambdaEvents
16+
import XCTest
17+
18+
class SESTests: XCTestCase {
19+
static let eventBody = """
20+
{
21+
"Records": [
22+
{
23+
"eventSource": "aws:ses",
24+
"eventVersion": "1.0",
25+
"ses": {
26+
"mail": {
27+
"commonHeaders": {
28+
"date": "Wed, 7 Oct 2015 12:34:56 -0700",
29+
"from": [
30+
"Jane Doe <[email protected]>"
31+
],
32+
"messageId": "<0123456789example.com>",
33+
"returnPath": "[email protected]",
34+
"subject": "Test Subject",
35+
"to": [
36+
37+
]
38+
},
39+
"destination": [
40+
41+
],
42+
"headers": [
43+
{
44+
"name": "Return-Path",
45+
"value": "<[email protected]>"
46+
},
47+
{
48+
"name": "Received",
49+
"value": "from mailer.example.com (mailer.example.com [203.0.113.1]) by inbound-smtp.eu-west-1.amazonaws.com with SMTP id o3vrnil0e2ic28trm7dfhrc2v0cnbeccl4nbp0g1 for [email protected]; Wed, 07 Oct 2015 12:34:56 +0000 (UTC)"
50+
}
51+
],
52+
"headersTruncated": true,
53+
"messageId": "5h5auqp1oa1bg49b2q8f8tmli1oju8pcma2haao1",
54+
"source": "[email protected]",
55+
"timestamp": "1970-01-01T00:00:00.000Z"
56+
},
57+
"receipt": {
58+
"action": {
59+
"functionArn": "arn:aws:lambda:eu-west-1:123456789012:function:Example",
60+
"invocationType": "Event",
61+
"type": "Lambda"
62+
},
63+
"dkimVerdict": {
64+
"status": "PASS"
65+
},
66+
"processingTimeMillis": 574,
67+
"recipients": [
68+
69+
70+
],
71+
"spamVerdict": {
72+
"status": "PASS"
73+
},
74+
"spfVerdict": {
75+
"status": "PROCESSING_FAILED"
76+
},
77+
"timestamp": "1970-01-01T00:00:00.000Z",
78+
"virusVerdict": {
79+
"status": "FAIL"
80+
}
81+
}
82+
}
83+
}
84+
]
85+
}
86+
"""
87+
88+
func testSimpleEventFromJSON() {
89+
let data = Data(SESTests.eventBody.utf8)
90+
var event: SES.Event?
91+
XCTAssertNoThrow(event = try JSONDecoder().decode(SES.Event.self, from: data))
92+
93+
guard let record = event?.records.first else {
94+
XCTFail("Expected to have one record")
95+
return
96+
}
97+
98+
XCTAssertEqual(record.eventSource, "aws:ses")
99+
XCTAssertEqual(record.eventVersion, "1.0")
100+
XCTAssertEqual(record.ses.mail.commonHeaders.date.description, "2015-10-07 19:34:56 +0000")
101+
XCTAssertEqual(record.ses.mail.commonHeaders.from[0], "Jane Doe <[email protected]>")
102+
XCTAssertEqual(record.ses.mail.commonHeaders.messageId, "<0123456789example.com>")
103+
XCTAssertEqual(record.ses.mail.commonHeaders.returnPath, "[email protected]")
104+
XCTAssertEqual(record.ses.mail.commonHeaders.subject, "Test Subject")
105+
XCTAssertEqual(record.ses.mail.commonHeaders.to?[0], "[email protected]")
106+
XCTAssertEqual(record.ses.mail.destination[0], "[email protected]")
107+
XCTAssertEqual(record.ses.mail.headers[0].name, "Return-Path")
108+
XCTAssertEqual(record.ses.mail.headers[0].value, "<[email protected]>")
109+
XCTAssertEqual(record.ses.mail.headers[1].name, "Received")
110+
XCTAssertEqual(record.ses.mail.headers[1].value, "from mailer.example.com (mailer.example.com [203.0.113.1]) by inbound-smtp.eu-west-1.amazonaws.com with SMTP id o3vrnil0e2ic28trm7dfhrc2v0cnbeccl4nbp0g1 for [email protected]; Wed, 07 Oct 2015 12:34:56 +0000 (UTC)")
111+
XCTAssertEqual(record.ses.mail.headersTruncated, true)
112+
XCTAssertEqual(record.ses.mail.messageId, "5h5auqp1oa1bg49b2q8f8tmli1oju8pcma2haao1")
113+
XCTAssertEqual(record.ses.mail.source, "[email protected]")
114+
XCTAssertEqual(record.ses.mail.timestamp.description, "1970-01-01 00:00:00 +0000")
115+
116+
XCTAssertEqual(record.ses.receipt.action.functionArn, "arn:aws:lambda:eu-west-1:123456789012:function:Example")
117+
XCTAssertEqual(record.ses.receipt.action.invocationType, "Event")
118+
XCTAssertEqual(record.ses.receipt.action.type, "Lambda")
119+
XCTAssertEqual(record.ses.receipt.dkimVerdict.status, .pass)
120+
XCTAssertEqual(record.ses.receipt.processingTimeMillis, 574)
121+
XCTAssertEqual(record.ses.receipt.recipients[0], "[email protected]")
122+
XCTAssertEqual(record.ses.receipt.recipients[1], "[email protected]")
123+
XCTAssertEqual(record.ses.receipt.spamVerdict.status, .pass)
124+
XCTAssertEqual(record.ses.receipt.spfVerdict.status, .processingFailed)
125+
XCTAssertEqual(record.ses.receipt.timestamp.description, "1970-01-01 00:00:00 +0000")
126+
XCTAssertEqual(record.ses.receipt.virusVerdict.status, .fail)
127+
}
128+
}

Tests/AWSLambdaEventsTests/Utils/DateWrapperTests.swift

+32
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,36 @@ class DateWrapperTests: XCTestCase {
7979
XCTAssertNil(context.underlyingError)
8080
}
8181
}
82+
83+
func testRFC5322DateTimeCodingWrapperSuccess() {
84+
struct TestEvent: Decodable {
85+
@RFC5322DateTimeCoding
86+
var date: Date
87+
}
88+
89+
let json = #"{"date":"Thu, 5 Apr 2012 23:47:37 +0200"}"#
90+
var event: TestEvent?
91+
XCTAssertNoThrow(event = try JSONDecoder().decode(TestEvent.self, from: json.data(using: .utf8)!))
92+
93+
XCTAssertEqual(event?.date.description, "2012-04-05 21:47:37 +0000")
94+
}
95+
96+
func testRFC5322DateTimeCodingWrapperFailure() {
97+
struct TestEvent: Decodable {
98+
@RFC5322DateTimeCoding
99+
var date: Date
100+
}
101+
102+
let date = "Thu, 5 Apr 2012 23:47 +0200" // missing seconds
103+
let json = #"{"date":"\#(date)"}"#
104+
XCTAssertThrowsError(_ = try JSONDecoder().decode(TestEvent.self, from: json.data(using: .utf8)!)) { error in
105+
guard case DecodingError.dataCorrupted(let context) = error else {
106+
XCTFail("Unexpected error: \(error)"); return
107+
}
108+
109+
XCTAssertEqual(context.codingPath.compactMap { $0.stringValue }, ["date"])
110+
XCTAssertEqual(context.debugDescription, "Expected date to be in RFC5322 date-time format with fractional seconds, but `\(date)` does not forfill format")
111+
XCTAssertNil(context.underlyingError)
112+
}
113+
}
82114
}

0 commit comments

Comments
 (0)