-
Notifications
You must be signed in to change notification settings - Fork 35
fix!: Throw error on invalid encoded data in response #984
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6489caa
7c431b8
2513cb6
f9fdfe8
8c250b8
da8cbd1
ecf449a
50898f5
b78a3be
b085e73
e09e9f5
15f665d
98e660d
13bcf00
216cda6
e7f6ca7
16cdbf1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // | ||
| // Copyright Amazon.com Inc. or its affiliates. | ||
| // All Rights Reserved. | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
|
|
||
| /// The data being deserialized was invalid, and a response could not be parsed. | ||
| /// | ||
| /// If an error of this type is received, it indicates a bug either on the server or client. Please file a [bug ticket](https://github.com/smithy-lang/smithy-swift/issues). | ||
| public struct ResponseDecodingError { | ||
|
|
||
| /// The error thrown by the deserializing implementation. | ||
| /// | ||
| /// The exact error returned may vary depending on the encoding in use. | ||
| public let wrapped: any Swift.Error // this will be the underlying error thrown by the parser implementation | ||
|
|
||
| public init(wrapped: any Error) { | ||
| self.wrapped = wrapped | ||
| } | ||
| } | ||
|
|
||
| extension ResponseDecodingError: Error { | ||
|
|
||
| public var localizedDescription: String { | ||
| "The data in the response could not be parsed. More info: \(wrapped)" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
|
|
||
| import struct Foundation.Data | ||
| @preconcurrency import libxml2 | ||
| import struct SmithySerialization.ResponseDecodingError | ||
|
|
||
| extension Reader { | ||
|
|
||
|
|
@@ -21,7 +22,9 @@ extension Reader { | |
| guard let buffer else { return Reader() } | ||
|
|
||
| // Read the buffer into a XML document tree | ||
| guard let doc = xmlReadMemory(buffer.pointee.content, Int32(count), "", "UTF-8", 0) else { return Reader() } | ||
| guard let doc = xmlReadMemory(buffer.pointee.content, Int32(count), "", "UTF-8", 0) else { | ||
| throw ResponseDecodingError(wrapped: XMLError.parsingError) | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of returning an empty parsed body, throw an |
||
|
|
||
| // Get rootNode ptr. Just a ptr to inside the doc struct, so no memory allocated | ||
| guard let rootNode = xmlDocGetRootElement(doc) else { return Reader() } | ||
|
|
@@ -154,4 +157,5 @@ private struct XMLError: Error { | |
| static let memoryError = XMLError("XML buffer could not be allocated") | ||
| static let invalidNode = XMLError("XML node was invalid") | ||
| static let invalidNodeName = XMLError("XML node name was invalid") | ||
| static let parsingError = XMLError("The XML could not be parsed") | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -224,19 +224,20 @@ class OrchestratorTests: XCTestCase { | |
| .attributes(attributes) | ||
| .serialize({ input, builder, _ in | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The tests below used JSON fragments (i.e. a JSON string only) for serde since fragments were allowed and it kept the test setup simpler. Tests now serialize JSON objects so the |
||
| trace.append("serialize") | ||
| let data = try JSONEncoder().encode(["foo": input.foo]) | ||
| builder.withMethod(.get) | ||
| .withPath("/") | ||
| .withHost("localhost") | ||
| .withBody(.data(try! JSONEncoder().encode(input.foo))) | ||
| .withBody(.data(data)) | ||
| }) | ||
| .deserialize({ response, _ in | ||
| trace.append("deserialize") | ||
| if (200..<300).contains(response.statusCode.rawValue) { | ||
| guard case let .data(data) = response.body else { | ||
| return TestOutput(bar: "") | ||
| } | ||
| let bar = try! JSONDecoder().decode(String.self, from: data!) | ||
| return TestOutput(bar: bar) | ||
| let object = try! JSONDecoder().decode([String: String].self, from: data!) | ||
| return TestOutput(bar: object["foo"]!) | ||
| } else { | ||
| let responseReader = try SmithyJSON.Reader.from(data: try await response.data()) | ||
| let baseError = try TestBaseError(httpResponse: response, responseReader: responseReader, noErrorWrapping: true) | ||
|
|
@@ -1374,7 +1375,8 @@ class OrchestratorTests: XCTestCase { | |
| let orchestrator = traceOrchestrator(trace: trace) | ||
| .retryStrategy(DefaultRetryStrategy(options: RetryStrategyOptions(backoffStrategy: ImmediateBackoffStrategy()))) | ||
| .serialize({ (input: TestInput, builder: HTTPRequestBuilder, context) in | ||
| builder.withBody(.data(Data("\"\(input.foo)\"".utf8))) | ||
| let data = try JSONEncoder().encode(["foo": input.foo]) | ||
| builder.withBody(.data(data)) | ||
| }) | ||
| .executeRequest(executeRequest) | ||
| let result = await asyncResult { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
|
|
||
| import XCTest | ||
| @testable @_spi(SmithyReadWrite) import SmithyXML | ||
| import struct SmithySerialization.ResponseDecodingError | ||
|
|
||
| class ReaderTests: XCTestCase { | ||
| let xmlData = Data(""" | ||
|
|
@@ -59,4 +60,19 @@ class ReaderTests: XCTestCase { | |
| XCTAssertEqual(reader.children[1].nodeInfo.namespaceDef, .init(prefix: "a2abc", uri: "https://def.ghi.com")) | ||
| XCTAssertEqual(reader.children.count, 3) | ||
| } | ||
|
|
||
| func test_invalidXML_throws() throws { | ||
| // Same data as above, but the last closing tag is missing its '<' | ||
| let invalidXML = Data(""" | ||
| <a xmlns=\"https://abc.def.com\"> | ||
| <a1 d=\"def\">x</a1> | ||
| <a2 xmlns:a2abc=\"https://def.ghi.com\" e=\"efg\">y</a2> | ||
| <a3>z</a3> | ||
| /a> | ||
| """.utf8) | ||
|
|
||
| XCTAssertThrowsError(try Reader.from(data: invalidXML)) { error in | ||
| XCTAssert(error is ResponseDecodingError) | ||
| } | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test above verifies that |
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InvalidEncodingErrorto match the error struct nameResponseDecodingErrorinstead ofResponseEncodingErrorsince SDK client is decoding response rather than encoding itThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed as recommended