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.

PackageModules

Package Dependencies

Swift Lambda Runtime and Swift Log are the main dependencies used:

PackageDependencies

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 🎉

Screen Shot 2020-12-22 at 4 08 42 PM


This article was written as an issue on my Blog repository on GitHub (see Issue #5)