Skip to content

Commit

Permalink
[Bug] Fix mixed path components (#491)
Browse files Browse the repository at this point in the history
### Motivation

Fixes #490.

### Modifications

Refactors the logic for composing path parameters on the client to
support mixed components, which contain both a constant and a variable
part (or two variable parts, etc).

### Result

Support more OpenAPI docs.

### Test Plan

Expanded a snippet test to cover this.
  • Loading branch information
czechboy0 authored Dec 15, 2023
1 parent 9ba2457 commit 50cf5ac
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//
//===----------------------------------------------------------------------===//
import OpenAPIKit
import Foundation

/// A wrapper of an OpenAPI operation that includes the information
/// about the parent containers of the operation, such as its path
Expand Down Expand Up @@ -244,6 +245,11 @@ extension OperationDescription {
)
}

/// The regular expression for parsing subcomponents of path components.
///
/// Either a parameter `{foo}` or a constant value `foo`.
private static let pathParameterRegex = try! NSRegularExpression(pattern: #"(\{[a-zA-Z0-9_]+\})|([^{}]+)"#)

/// Returns a string that contains the template to be generated for
/// the client that fills in path parameters, and an array expression
/// with the parameter values.
Expand All @@ -257,19 +263,35 @@ extension OperationDescription {
// in which the parameters are used.
var newComponents: [String] = []
for component in path.components {
guard component.hasPrefix("{") && component.hasSuffix("}") else {
newComponents.append(component)
continue
}
let componentName = String(component.dropFirst().dropLast())
guard pathParameterNames.contains(componentName) else {
throw GenericError(
message:
"Parameter '\(componentName)' used in the path '\(self.path.rawValue)', but not found in the defined list of path parameters."
)
let matches = Self.pathParameterRegex.matches(
in: component,
options: [],
range: NSRange(location: 0, length: component.utf16.count)
)
var subcomponents: [String] = []
for match in matches {
for i in 1..<match.numberOfRanges {
let range = match.range(at: i)
guard range.location != NSNotFound, let swiftRange = Range(range, in: component) else {
continue
}
let value = component[swiftRange]
if value.hasPrefix("{") && value.hasSuffix("}") {
let componentName = String(value.dropFirst().dropLast())
guard pathParameterNames.contains(componentName) else {
throw GenericError(
message:
"Parameter '\(componentName)' used in the path '\(self.path.rawValue)', but not found in the defined list of path parameters."
)
}
orderedPathParameters.append(componentName)
subcomponents.append("{}")
} else {
subcomponents.append(String(value))
}
}
}
orderedPathParameters.append(componentName)
newComponents.append("{}")
newComponents.append(subcomponents.joined())
}
let newPath = OpenAPI.Path(newComponents, trailingSlash: path.trailingSlash)
let names: [Expression] = orderedPathParameters.map { param in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2349,7 +2349,7 @@ final class SnippetBasedReferenceTests: XCTestCase {
func testRequestWithPathParams() throws {
try self.assertRequestInTypesClientServerTranslation(
"""
/foo/a/{a}/b/{b}:
/foo/a/{a}/b/{b}/c{num}:
get:
parameters:
- name: b
Expand All @@ -2362,6 +2362,11 @@ final class SnippetBasedReferenceTests: XCTestCase {
required: true
schema:
type: string
- name: num
in: path
required: true
schema:
type: integer
operationId: getFoo
responses:
default:
Expand All @@ -2372,12 +2377,15 @@ final class SnippetBasedReferenceTests: XCTestCase {
public struct Path: Sendable, Hashable {
public var b: Swift.String
public var a: Swift.String
public var num: Swift.Int
public init(
b: Swift.String,
a: Swift.String
a: Swift.String,
num: Swift.Int
) {
self.b = b
self.a = a
self.num = num
}
}
public var path: Operations.getFoo.Input.Path
Expand All @@ -2389,10 +2397,11 @@ final class SnippetBasedReferenceTests: XCTestCase {
client: """
{ input in
let path = try converter.renderedPath(
template: "/foo/a/{}/b/{}",
template: "/foo/a/{}/b/{}/c{}",
parameters: [
input.path.a,
input.path.b
input.path.b,
input.path.num
]
)
var request: HTTPTypes.HTTPRequest = .init(
Expand All @@ -2415,6 +2424,11 @@ final class SnippetBasedReferenceTests: XCTestCase {
in: metadata.pathParameters,
name: "a",
as: Swift.String.self
),
num: try converter.getPathParameterAsURI(
in: metadata.pathParameters,
name: "num",
as: Swift.Int.self
)
)
return Operations.getFoo.Input(path: path)
Expand Down

0 comments on commit 50cf5ac

Please sign in to comment.