Currently this blog repo includes the source code for the Swift Lambda function that processes events incoming from GitHub Actions via SQS. As I write this, I am working on the code for parsing these events, using Codable
Swift structures.
Package Structure
The package has three modules:
- IssueProcessorLambda: lambda function code
- Blog: main logic for parsing and processing events
- BlogTests: unit tests for the main logic
By moving the logic outside of the lambda function module, I can minimize the code in the lambda binary, which cannot be unit tested.
Package Dependencies
Swift Lambda Runtime and Swift Log are the main dependencies used:
Lambda Source Code
As mentioned, the lambda handler is pretty thin. The goal is to put all logic on Blog
(or other modules) so it can be unit tested, and fully independent from AWS Lambda.
struct Handler: LambdaHandler {
typealias In = SQS.Event
typealias Out = String // Response type
let parser: IssueParser
let processor: IssueProcessor
/// Business logic is initialized during lambda cold start
/// - Parameter context: Lambda initialization context, provided by AWS
init(context: Lambda.InitializationContext) {
parser = IssueParser(logger: context.logger)
processor = IssueProcessor(logger: context.logger)
}
func handle(context: Lambda.Context, event: In, callback: @escaping (Result<Out, Error>) -> Void) {
do {
for message in event.records {
let githubContext = try parser.parse(eventPayload: message.body)
try processor.process(githubEvent: githubContext.event)
}
callback(.success(""))
}
catch {
callback(.failure(error))
}
}
}
Lambda.run(Handler.init)
Codable Structures
So far, my structures look like this (no need to parse all fields from the event):
public struct GitHubContext: Codable {
public let eventName: String
public let event: GitHubEvent
}
public struct GitHubEvent: Codable {
public let action: String
public let issue: GitHubIssue
}
public struct GitHubIssue: Codable {
public let number: Int
public let state: String
public let body: String
public let title: String
public let labels: [GitHubLabel]
public let createdAt: Date
public let updatedAt: Date
}
public struct GitHubLabel: Codable {
public let color: String
public let name: String
}
The IssueParser
object is listed below. Most of the code is to prepare the JSONDecoder
to automatically convert property names from snake_case
to camelCase
and to automatically parse ISO8601 dates.
public struct IssueParser {
let logger: Logger
let decoder: JSONDecoder
let dateFormatter: DateFormatter
public init(logger: Logger) {
self.logger = logger
dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(dateFormatter)
}
public func parse(eventPayload: String) throws -> GitHubContext {
return try decoder.decode(GitHubContext.self, from: Data(eventPayload.utf8))
}
}
Finally, IssueProcessor
will handle the rest, right now it just dumps the event to CloudWatch, so I can verify things are working as expected.
public struct IssueProcessor {
let logger: Logger
public init(logger: Logger) {
self.logger = logger
}
public func process(githubEvent: GitHubEvent) throws {
logger.debug("Event: \(githubEvent)")
// Stay tuned for more!
}
}
Stay tuned for more!
Update
Here is the screenshot with the above article dumped in CloudWatch logs 🎉
This article was written as an issue on my Blog repository on GitHub (see Issue #5)