Skip to content

Commit 23bf155

Browse files
committed
Remove use of JSONEncoder for Error Reporting
1 parent c2673d4 commit 23bf155

File tree

2 files changed

+80
-39
lines changed

2 files changed

+80
-39
lines changed

Sources/AWSLambdaRuntime/LambdaRuntimeClient.swift

Lines changed: 62 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
import class Foundation.JSONEncoder
1615
import Logging
1716
import NIO
1817
import NIOHTTP1
@@ -68,14 +67,10 @@ extension Lambda {
6867
body = buffer
6968
case .failure(let error):
7069
url += Consts.postErrorURLSuffix
71-
let error = ErrorResponse(errorType: Consts.functionError, errorMessage: "\(error)")
72-
switch error.toJson() {
73-
case .failure(let jsonError):
74-
return self.eventLoop.makeFailedFuture(RuntimeError.json(jsonError))
75-
case .success(let json):
76-
body = self.allocator.buffer(capacity: json.utf8.count)
77-
body!.writeString(json)
78-
}
70+
let errorResponse = ErrorResponse(errorType: Consts.functionError, errorMessage: "\(error)")
71+
let bytes = errorResponse.toJSONBytes()
72+
body = self.allocator.buffer(capacity: bytes.count)
73+
body!.writeBytes(bytes)
7974
}
8075
logger.debug("reporting results to lambda runtime engine using \(url)")
8176
return self.httpClient.post(url: url, body: body).flatMapThrowing { response in
@@ -99,28 +94,23 @@ extension Lambda {
9994
func reportInitializationError(logger: Logger, error: Error) -> EventLoopFuture<Void> {
10095
let url = Consts.postInitErrorURL
10196
let errorResponse = ErrorResponse(errorType: Consts.initializationError, errorMessage: "\(error)")
102-
var body: ByteBuffer
103-
switch errorResponse.toJson() {
104-
case .failure(let jsonError):
105-
return self.eventLoop.makeFailedFuture(RuntimeError.json(jsonError))
106-
case .success(let json):
107-
body = self.allocator.buffer(capacity: json.utf8.count)
108-
body.writeString(json)
109-
logger.warning("reporting initialization error to lambda runtime engine using \(url)")
110-
return self.httpClient.post(url: url, body: body).flatMapThrowing { response in
111-
guard response.status == .accepted else {
112-
throw RuntimeError.badStatusCode(response.status)
113-
}
114-
return ()
115-
}.flatMapErrorThrowing { error in
116-
switch error {
117-
case HTTPClient.Errors.timeout:
118-
throw RuntimeError.upstreamError("timeout")
119-
case HTTPClient.Errors.connectionResetByPeer:
120-
throw RuntimeError.upstreamError("connectionResetByPeer")
121-
default:
122-
throw error
123-
}
97+
let bytes = errorResponse.toJSONBytes()
98+
var body = self.allocator.buffer(capacity: bytes.count)
99+
body.writeBytes(bytes)
100+
logger.warning("reporting initialization error to lambda runtime engine using \(url)")
101+
return self.httpClient.post(url: url, body: body).flatMapThrowing { response in
102+
guard response.status == .accepted else {
103+
throw RuntimeError.badStatusCode(response.status)
104+
}
105+
return ()
106+
}.flatMapErrorThrowing { error in
107+
switch error {
108+
case HTTPClient.Errors.timeout:
109+
throw RuntimeError.upstreamError("timeout")
110+
case HTTPClient.Errors.connectionResetByPeer:
111+
throw RuntimeError.upstreamError("connectionResetByPeer")
112+
default:
113+
throw error
124114
}
125115
}
126116
}
@@ -142,15 +132,48 @@ internal struct ErrorResponse: Codable {
142132
var errorMessage: String
143133
}
144134

145-
private extension ErrorResponse {
146-
func toJson() -> Result<String, Error> {
147-
let encoder = JSONEncoder()
148-
do {
149-
let data = try encoder.encode(self)
150-
return .success(String(data: data, encoding: .utf8) ?? "unknown error")
151-
} catch {
152-
return .failure(error)
135+
internal extension ErrorResponse {
136+
func toJSONBytes() -> [UInt8] {
137+
var bytes = [UInt8]()
138+
bytes.append(UInt8(ascii: "{"))
139+
bytes.append(contentsOf: #""errorType":"# .utf8)
140+
self.encodeString(self.errorType, bytes: &bytes)
141+
bytes.append(contentsOf: #","errorMessage":"# .utf8)
142+
self.encodeString(self.errorMessage, bytes: &bytes)
143+
bytes.append(UInt8(ascii: "}"))
144+
return bytes
145+
}
146+
147+
private func encodeString(_ string: String, bytes: inout [UInt8]) {
148+
bytes.append(UInt8(ascii: "\""))
149+
let stringBytes = string.utf8
150+
var startCopyIndex = stringBytes.startIndex
151+
var nextIndex = startCopyIndex
152+
153+
while nextIndex != stringBytes.endIndex {
154+
switch stringBytes[nextIndex] {
155+
case 0 ..< 32, UInt8(ascii: "\""), UInt8(ascii: "\\"):
156+
// All Unicode characters may be placed within the
157+
// quotation marks, except for the characters that MUST be escaped:
158+
// quotation mark, reverse solidus, and the control characters (U+0000
159+
// through U+001F).
160+
// https://tools.ietf.org/html/rfc7159#section-7
161+
162+
// copy the current range over
163+
bytes.append(contentsOf: stringBytes[startCopyIndex ..< nextIndex])
164+
bytes.append(UInt8(ascii: "\\"))
165+
bytes.append(stringBytes[nextIndex])
166+
167+
nextIndex = stringBytes.index(after: nextIndex)
168+
startCopyIndex = nextIndex
169+
default:
170+
nextIndex = stringBytes.index(after: nextIndex)
171+
}
153172
}
173+
174+
// copy everything, that hasn't been copied yet
175+
bytes.append(contentsOf: stringBytes[startCopyIndex ..< nextIndex])
176+
bytes.append(UInt8(ascii: "\""))
154177
}
155178
}
156179

Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTest.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,24 @@ class LambdaRuntimeClientTest: XCTestCase {
191191
}
192192
}
193193

194+
func testErrorResponseToJSON() {
195+
// we want to check if quotes and back slashes are correctly escaped
196+
let windowsError = ErrorResponse(
197+
errorType: "error",
198+
errorMessage: #"underlyingError: "An error with a windows path C:\Windows\""#
199+
)
200+
let windowsBytes = windowsError.toJSONBytes()
201+
XCTAssertEqual(#"{"errorType":"error","errorMessage":"underlyingError: \"An error with a windows path C:\\Windows\\\""}"#, String(decoding: windowsBytes, as: Unicode.UTF8.self))
202+
203+
// we want to check if unicode sequences work
204+
let emojiError = ErrorResponse(
205+
errorType: "error",
206+
errorMessage: #"🥑👨‍👩‍👧‍👧👩‍👩‍👧‍👧👨‍👨‍👧"#
207+
)
208+
let emojiBytes = emojiError.toJSONBytes()
209+
XCTAssertEqual(#"{"errorType":"error","errorMessage":"🥑👨‍👩‍👧‍👧👩‍👩‍👧‍👧👨‍👨‍👧"}"#, String(decoding: emojiBytes, as: Unicode.UTF8.self))
210+
}
211+
194212
class Behavior: LambdaServerBehavior {
195213
var state = 0
196214

0 commit comments

Comments
 (0)