Skip to content

Commit 8f651e8

Browse files
committed
Use a struct for LambdaContext (fix #169)
1 parent 0a6af5b commit 8f651e8

File tree

2 files changed

+182
-5
lines changed

2 files changed

+182
-5
lines changed

Sources/AWSLambdaRuntime/LambdaContext.swift

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,70 @@ import Dispatch
1616
import Logging
1717
import NIOCore
1818

19+
// MARK: - Client Context
20+
21+
/// AWS Mobile SDK client fields.
22+
public struct ClientApplication: Codable, Sendable {
23+
/// The mobile app installation id
24+
public let installationId: String?
25+
/// The app title for the mobile app as registered with AWS' mobile services.
26+
public let appTitle: String?
27+
/// The version name of the application as registered with AWS' mobile services.
28+
public let appVersionName: String?
29+
/// The app version code.
30+
public let appVersionCode: String?
31+
/// The package name for the mobile application invoking the function
32+
public let appPackageName: String?
33+
34+
private enum CodingKeys: String, CodingKey {
35+
case installationId = "installation_id"
36+
case appTitle = "app_title"
37+
case appVersionName = "app_version_name"
38+
case appVersionCode = "app_version_code"
39+
case appPackageName = "app_package_name"
40+
}
41+
42+
public init(
43+
installationId: String? = nil,
44+
appTitle: String? = nil,
45+
appVersionName: String? = nil,
46+
appVersionCode: String? = nil,
47+
appPackageName: String? = nil
48+
) {
49+
self.installationId = installationId
50+
self.appTitle = appTitle
51+
self.appVersionName = appVersionName
52+
self.appVersionCode = appVersionCode
53+
self.appPackageName = appPackageName
54+
}
55+
}
56+
57+
/// For invocations from the AWS Mobile SDK, data about the client application and device.
58+
public struct ClientContext: Codable, Sendable {
59+
/// Information about the mobile application invoking the function.
60+
public let client: ClientApplication?
61+
/// Custom properties attached to the mobile event context.
62+
public let custom: [String: String]?
63+
/// Environment settings from the mobile client.
64+
public let environment: [String: String]?
65+
66+
private enum CodingKeys: String, CodingKey {
67+
case client
68+
case custom
69+
case environment = "env"
70+
}
71+
72+
public init(
73+
client: ClientApplication? = nil,
74+
custom: [String: String]? = nil,
75+
environment: [String: String]? = nil
76+
) {
77+
self.client = client
78+
self.custom = custom
79+
self.environment = environment
80+
}
81+
}
82+
1983
// MARK: - Context
2084

2185
/// Lambda runtime context.
@@ -27,7 +91,7 @@ public struct LambdaContext: CustomDebugStringConvertible, Sendable {
2791
let invokedFunctionARN: String
2892
let deadline: DispatchWallTime
2993
let cognitoIdentity: String?
30-
let clientContext: String?
94+
let clientContext: ClientContext?
3195
let logger: Logger
3296

3397
init(
@@ -36,7 +100,7 @@ public struct LambdaContext: CustomDebugStringConvertible, Sendable {
36100
invokedFunctionARN: String,
37101
deadline: DispatchWallTime,
38102
cognitoIdentity: String?,
39-
clientContext: String?,
103+
clientContext: ClientContext?,
40104
logger: Logger
41105
) {
42106
self.requestID = requestID
@@ -77,7 +141,7 @@ public struct LambdaContext: CustomDebugStringConvertible, Sendable {
77141
}
78142

79143
/// For invocations from the AWS Mobile SDK, data about the client application and device.
80-
public var clientContext: String? {
144+
public var clientContext: ClientContext? {
81145
self.storage.clientContext
82146
}
83147

@@ -94,7 +158,7 @@ public struct LambdaContext: CustomDebugStringConvertible, Sendable {
94158
invokedFunctionARN: String,
95159
deadline: DispatchWallTime,
96160
cognitoIdentity: String? = nil,
97-
clientContext: String? = nil,
161+
clientContext: ClientContext? = nil,
98162
logger: Logger
99163
) {
100164
self.storage = _Storage(
@@ -117,7 +181,7 @@ public struct LambdaContext: CustomDebugStringConvertible, Sendable {
117181
}
118182

119183
public var debugDescription: String {
120-
"\(Self.self)(requestID: \(self.requestID), traceID: \(self.traceID), invokedFunctionARN: \(self.invokedFunctionARN), cognitoIdentity: \(self.cognitoIdentity ?? "nil"), clientContext: \(self.clientContext ?? "nil"), deadline: \(self.deadline))"
184+
"\(Self.self)(requestID: \(self.requestID), traceID: \(self.traceID), invokedFunctionARN: \(self.invokedFunctionARN), cognitoIdentity: \(self.cognitoIdentity ?? "nil"), clientContext: \(String(describing: self.clientContext)), deadline: \(self.deadline))"
121185
}
122186

123187
/// This interface is not part of the public API and must not be used by adopters. This API is not part of semver versioning.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2017-2022 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 Testing
16+
@testable import AWSLambdaRuntime
17+
import Foundation
18+
19+
@Suite("LambdaContext ClientContext Tests")
20+
struct LambdaContextTests {
21+
22+
@Test("ClientContext with full data resolves correctly")
23+
func clientContextWithFullDataResolves() throws {
24+
let custom = ["key": "value"]
25+
let environment = ["key": "value"]
26+
let clientContext = ClientContext(
27+
client: ClientApplication(
28+
installationId: "test-id",
29+
appTitle: "test-app",
30+
appVersionName: "1.0",
31+
appVersionCode: "100",
32+
appPackageName: "com.test.app"
33+
),
34+
custom: custom,
35+
environment: environment
36+
)
37+
38+
let encoder = JSONEncoder()
39+
let clientContextData = try encoder.encode(clientContext)
40+
41+
// Verify JSON encoding/decoding works correctly
42+
let decoder = JSONDecoder()
43+
let decodedClientContext = try decoder.decode(ClientContext.self, from: clientContextData)
44+
45+
let decodedClient = try #require(decodedClientContext.client)
46+
let originalClient = try #require(clientContext.client)
47+
48+
#expect(decodedClient.installationId == originalClient.installationId)
49+
#expect(decodedClient.appTitle == originalClient.appTitle)
50+
#expect(decodedClient.appVersionName == originalClient.appVersionName)
51+
#expect(decodedClient.appVersionCode == originalClient.appVersionCode)
52+
#expect(decodedClient.appPackageName == originalClient.appPackageName)
53+
#expect(decodedClientContext.custom == clientContext.custom)
54+
#expect(decodedClientContext.environment == clientContext.environment)
55+
}
56+
57+
@Test("ClientContext with empty data resolves correctly")
58+
func clientContextWithEmptyDataResolves() throws {
59+
let emptyClientContextJSON = "{}"
60+
let emptyClientContextData = emptyClientContextJSON.data(using: .utf8)!
61+
62+
let decoder = JSONDecoder()
63+
let decodedClientContext = try decoder.decode(ClientContext.self, from: emptyClientContextData)
64+
65+
// With empty JSON, we expect nil values for optional fields
66+
#expect(decodedClientContext.client == nil)
67+
#expect(decodedClientContext.custom == nil)
68+
#expect(decodedClientContext.environment == nil)
69+
}
70+
71+
@Test("ClientContext with AWS Lambda JSON payload decodes correctly")
72+
func clientContextWithAWSLambdaJSONPayload() throws {
73+
let jsonPayload = """
74+
{
75+
"client": {
76+
"installation_id": "example-id",
77+
"app_title": "Example App",
78+
"app_version_name": "1.0",
79+
"app_version_code": "1",
80+
"app_package_name": "com.example.app"
81+
},
82+
"custom": {
83+
"customKey": "customValue"
84+
},
85+
"env": {
86+
"platform": "Android",
87+
"platform_version": "10"
88+
}
89+
}
90+
"""
91+
92+
let jsonData = jsonPayload.data(using: .utf8)!
93+
let decoder = JSONDecoder()
94+
let decodedClientContext = try decoder.decode(ClientContext.self, from: jsonData)
95+
96+
// Verify client application data
97+
let client = try #require(decodedClientContext.client)
98+
#expect(client.installationId == "example-id")
99+
#expect(client.appTitle == "Example App")
100+
#expect(client.appVersionName == "1.0")
101+
#expect(client.appVersionCode == "1")
102+
#expect(client.appPackageName == "com.example.app")
103+
104+
// Verify custom properties
105+
let custom = try #require(decodedClientContext.custom)
106+
#expect(custom["customKey"] == "customValue")
107+
108+
// Verify environment settings
109+
let environment = try #require(decodedClientContext.environment)
110+
#expect(environment["platform"] == "Android")
111+
#expect(environment["platform_version"] == "10")
112+
}
113+
}

0 commit comments

Comments
 (0)