Skip to content

[Runtime] Generate server variables #64

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

Merged
merged 3 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions Sources/OpenAPIRuntime/Conversion/ServerVariable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import Foundation

extension URL {
/// Returns a validated server URL created from the URL template, or
/// throws an error.
/// - Parameter
/// - string: A URL string.
/// - variables: A map of variable values to substitute into the URL
/// template.
/// - Throws: If the provided string doesn't convert to URL.
@_spi(Generated)
public init(
validatingOpenAPIServerURL string: String,
variables: [ServerVariable]
) throws {
var urlString = string
for variable in variables {
let name = variable.name
let value = variable.value
if let allowedValues = variable.allowedValues {
guard allowedValues.contains(value) else {
throw RuntimeError.invalidServerVariableValue(
name: name,
value: value,
allowedValues: allowedValues
)
}
}
urlString = urlString.replacingOccurrences(of: "{\(name)}", with: value)
}
guard let url = Self(string: urlString) else {
throw RuntimeError.invalidServerURL(urlString)
}
self = url
}
}

/// A variable of a server URL template in the OpenAPI document.
@_spi(Generated)
public struct ServerVariable: Sendable, Hashable {

/// The name of the variable.
public var name: String

/// The value to be substituted into the URL template.
public var value: String

/// A list of allowed values from the OpenAPI document.
///
/// Nil means that any value is allowed.
public var allowedValues: [String]?

/// Creates a new server variable.
/// - Parameters:
/// - name: The name of the variable.
/// - value: The value to be substituted into the URL template.
/// - allowedValues: A list of allowed values from the OpenAPI document.
public init(name: String, value: String, allowedValues: [String]? = nil) {
self.name = name
self.value = value
self.allowedValues = allowedValues
}
}
4 changes: 4 additions & 0 deletions Sources/OpenAPIRuntime/Errors/RuntimeError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ internal enum RuntimeError: Error, CustomStringConvertible, LocalizedError, Pret

// Miscs
case invalidServerURL(String)
case invalidServerVariableValue(name: String, value: String, allowedValues: [String])
case invalidExpectedContentType(String)
case invalidHeaderFieldName(String)
case invalidBase64String(String)
Expand Down Expand Up @@ -70,6 +71,9 @@ internal enum RuntimeError: Error, CustomStringConvertible, LocalizedError, Pret
switch self {
case .invalidServerURL(let string):
return "Invalid server URL: \(string)"
case .invalidServerVariableValue(name: let name, value: let value, allowedValues: let allowedValues):
return
"Invalid server variable named: '\(name)', which has the value: '\(value)', but the only allowed values are: \(allowedValues.map { "'\($0)'" }.joined(separator: ", "))"
case .invalidExpectedContentType(let string):
return "Invalid expected content type: '\(string)'"
case .invalidHeaderFieldName(let name):
Expand Down
83 changes: 83 additions & 0 deletions Tests/OpenAPIRuntimeTests/Conversion/Test_ServerVariable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import XCTest
@_spi(Generated) @testable import OpenAPIRuntime

final class Test_ServerVariable: Test_Runtime {

func testOnlyConstants() throws {
XCTAssertEqual(
try URL(
validatingOpenAPIServerURL: "https://example.com",
variables: []
)
.absoluteString,
"https://example.com"
)
XCTAssertEqual(
try URL(
validatingOpenAPIServerURL: "https://example.com/api",
variables: []
)
.absoluteString,
"https://example.com/api"
)
XCTAssertEqual(
try URL(
validatingOpenAPIServerURL: "/api",
variables: []
)
.absoluteString,
"/api"
)
}

func testVariables() throws {
XCTAssertEqual(
try URL(
validatingOpenAPIServerURL: "https://{subdomain}.example.com:{port}/{baseURL}",
variables: [
.init(name: "subdomain", value: "test"),
.init(name: "port", value: "443", allowedValues: ["443", "8443"]),
.init(name: "baseURL", value: "v1"),
]
)
.absoluteString,
"https://test.example.com:443/v1"
)
XCTAssertThrowsError(
try URL(
validatingOpenAPIServerURL: "https://{subdomain}.example.com:{port}/{baseURL}",
variables: [
.init(name: "subdomain", value: "test"),
.init(name: "port", value: "foo", allowedValues: ["443", "8443"]),
.init(name: "baseURL", value: "v1"),
]
),
"Should have thrown an error",
{ error in
guard
case let .invalidServerVariableValue(name: name, value: value, allowedValues: allowedValues) = error
as? RuntimeError
else {
XCTFail("Expected error, but not this: \(error)")
return
}
XCTAssertEqual(name, "port")
XCTAssertEqual(value, "foo")
XCTAssertEqual(allowedValues, ["443", "8443"])
}
)
}
}