Skip to content

Commit

Permalink
Merge pull request #157 from mattpolzin/less-crashing-more-errors
Browse files Browse the repository at this point in the history
Fix crash on decoding error for responses within components object.
  • Loading branch information
mattpolzin authored Oct 24, 2020
2 parents 55e8672 + 7fc5a99 commit f534aa9
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ extension OpenAPI.Error.Decoding.Response {
}
}

public var contextString: String { statusCode.rawValue }
public var contextString: String { "" }

public var errorCategory: ErrorCategory {
switch context {
Expand Down Expand Up @@ -70,8 +70,10 @@ extension OpenAPI.Error.Decoding.Response {
var codingPath = Self.relativePath(from: error.codingPath)
let code = codingPath.removeFirst().stringValue.lowercased()

// this part of the coding path is structurally guaranteed to be a status code.
statusCode = OpenAPI.Response.StatusCode(rawValue: code)!
// this part of the coding path is structurally guaranteed to be a status code
// unless we are in the components in which case the status code is not
// relevant.
statusCode = OpenAPI.Response.StatusCode(rawValue: code) ?? .default
context = .inconsistency(error)
relativeCodingPath = Array(codingPath)
}
Expand All @@ -80,8 +82,10 @@ extension OpenAPI.Error.Decoding.Response {
var codingPath = Self.relativePath(from: error.codingPathWithoutSubject)
let code = codingPath.removeFirst().stringValue.lowercased()

// this part of the coding path is structurally guaranteed to be a status code.
statusCode = OpenAPI.Response.StatusCode(rawValue: code)!
// this part of the coding path is structurally guaranteed to be a status code
// unless we are in the components in which case the status code is not
// relevant.
statusCode = OpenAPI.Response.StatusCode(rawValue: code) ?? .default
context = .other(error)
relativeCodingPath = Array(codingPath)
}
Expand All @@ -95,8 +99,10 @@ extension OpenAPI.Error.Decoding.Response {
var codingPath = Self.relativePath(from: eitherError.codingPath)
let code = codingPath.removeFirst().stringValue.lowercased()

// this part of the coding path is structurally guaranteed to be a status code.
statusCode = OpenAPI.Response.StatusCode(rawValue: code)!
// this part of the coding path is structurally guaranteed to be a status code
// unless we are in the components in which case the status code is not
// relevant.
statusCode = OpenAPI.Response.StatusCode(rawValue: code) ?? .default
context = .neither(eitherError)
relativeCodingPath = Array(codingPath)
}
Expand Down
10 changes: 8 additions & 2 deletions Sources/OpenAPIKit/Header/Header.swift
Original file line number Diff line number Diff line change
Expand Up @@ -283,10 +283,16 @@ extension OpenAPI.Header: Decodable {
schemaOrContent = .init(content)
case (nil, let schema?):
schemaOrContent = .init(schema)
default:
case (nil, nil):
throw InconsistencyError(
subjectName: "Header",
details: "A single path parameter must specify one but not both `content` and `schema`",
details: "A header parameter must specify either `content` or `schema`",
codingPath: decoder.codingPath
)
case (_, _):
throw InconsistencyError(
subjectName: "Header",
details: "A header must specify one but not both `content` and `schema`",
codingPath: decoder.codingPath
)
}
Expand Down
10 changes: 8 additions & 2 deletions Sources/OpenAPIKit/Parameter/Parameter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -260,10 +260,16 @@ extension OpenAPI.Parameter: Decodable {
schemaOrContent = .init(content)
case (nil, let schema?):
schemaOrContent = .init(schema)
default:
case (nil, nil):
throw InconsistencyError(
subjectName: name,
details: "A single path parameter must specify one but not both `content` and `schema`",
details: "A parameter must specify either `content` or `schema`",
codingPath: decoder.codingPath
)
case (_, _):
throw InconsistencyError(
subjectName: name,
details: "A parameter must specify one but not both `content` and `schema`",
codingPath: decoder.codingPath
)
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/OpenAPIKitCompatibilitySuite/GitHubAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ final class GitHubAPICampatibilityTests: XCTestCase {
githubAPI = Result {
try YAMLDecoder().decode(
OpenAPI.Document.self,
from: String(contentsOf: URL(string: "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/ghes-2.21/ghes-2.21.yaml")!)
from: String(contentsOf: URL(string: "https://raw.githubusercontent.com/github/rest-api-description/v1.0.0-rc.1/descriptions/ghes-2.21/ghes-2.21.yaml")!)
)
}
}
Expand Down
27 changes: 27 additions & 0 deletions Tests/OpenAPIKitErrorReportingTests/ComponentErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,31 @@ final class ComponentErrorTests: XCTestCase {
XCTAssertEqual(openAPIError.codingPath.map { $0.stringValue }, ["components", "schemas", "h#llo"])
}
}

func test_badResponseBecauseOfHeaderInsideComponents() {
let documentYML =
"""
openapi: "3.0.0"
info:
title: test
version: 1.0
paths: {}
components:
responses:
IllFormed:
description: hello world
headers:
MissingSchemaKey:
type: string # should be nested within a `schema` object
content: {}
"""

XCTAssertThrowsError(try testDecoder.decode(OpenAPI.Document.self, from: documentYML)) { error in

let openAPIError = OpenAPI.Error(from: error)

XCTAssertEqual(openAPIError.localizedDescription, "Found neither a $ref nor a Header. \n\nHeader could not be decoded because:\nInconsistency encountered when parsing `Header`: A header parameter must specify either `content` or `schema`..")
XCTAssertEqual(openAPIError.codingPath.map { $0.stringValue }, ["components", "responses", "IllFormed", "headers", "MissingSchemaKey"])
}
}
}
4 changes: 2 additions & 2 deletions Tests/OpenAPIKitErrorReportingTests/PathsErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ final class PathsErrorTests: XCTestCase {

let openAPIError = OpenAPI.Error(from: error)

XCTAssertEqual(openAPIError.localizedDescription, "Found neither a $ref nor a Parameter in .parameters[0] under the `/hello/world` path. \n\nParameter could not be decoded because:\nInconsistency encountered when parsing `world`: A single path parameter must specify one but not both `content` and `schema`.."
XCTAssertEqual(openAPIError.localizedDescription, "Found neither a $ref nor a Parameter in .parameters[0] under the `/hello/world` path. \n\nParameter could not be decoded because:\nInconsistency encountered when parsing `world`: A parameter must specify either `content` or `schema`.."
)
XCTAssertEqual(
openAPIError.codingPath.map { $0.stringValue },
Expand Down Expand Up @@ -176,7 +176,7 @@ final class PathsErrorTests: XCTestCase {

let openAPIError = OpenAPI.Error(from: error)

XCTAssertEqual(openAPIError.localizedDescription, "Found neither a $ref nor a Parameter in .parameters[0] under the `/hello/world` path. \n\nParameter could not be decoded because:\nInconsistency encountered when parsing `world`: A single path parameter must specify one but not both `content` and `schema`.."
XCTAssertEqual(openAPIError.localizedDescription, "Found neither a $ref nor a Parameter in .parameters[0] under the `/hello/world` path. \n\nParameter could not be decoded because:\nInconsistency encountered when parsing `world`: A parameter must specify one but not both `content` and `schema`.."
)
XCTAssertEqual(
openAPIError.codingPath.map { $0.stringValue },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ final class ResponseErrorTests: XCTestCase {

let openAPIError = OpenAPI.Error(from: error)

XCTAssertEqual(openAPIError.localizedDescription, "Found neither a $ref nor a Header in .headers.hi for the status code '200' response of the **GET** endpoint under `/hello/world`. \n\nHeader could not be decoded because:\nInconsistency encountered when parsing `Header`: A single path parameter must specify one but not both `content` and `schema`..")
XCTAssertEqual(openAPIError.localizedDescription, "Found neither a $ref nor a Header in .headers.hi for the status code '200' response of the **GET** endpoint under `/hello/world`. \n\nHeader could not be decoded because:\nInconsistency encountered when parsing `Header`: A header must specify one but not both `content` and `schema`..")
XCTAssertEqual(openAPIError.codingPath.map { $0.stringValue }, [
"paths",
"/hello/world",
Expand Down

0 comments on commit f534aa9

Please sign in to comment.