Skip to content

UUID type support #627

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

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions Sources/_OpenAPIGeneratorCore/FeatureFlags.swift
Original file line number Diff line number Diff line change
@@ -28,6 +28,10 @@
public enum FeatureFlag: String, Hashable, Codable, CaseIterable, Sendable {
// needs to be here for the enum to compile
case empty
/// UUID support
///
/// Enable interpretation of `type: string, format: uuid` as `Foundation.UUID` typed data.
case uuidSupport
}

/// A set of enabled feature flags.
Original file line number Diff line number Diff line change
@@ -38,7 +38,10 @@ enum Constants {
ImportDescription(moduleName: Constants.Import.runtime, spi: "Generated"),
ImportDescription(
moduleName: "Foundation",
moduleTypes: ["struct Foundation.URL", "struct Foundation.Data", "struct Foundation.Date"],
moduleTypes: [
"struct Foundation.URL", "struct Foundation.Data", "struct Foundation.Date",
"struct Foundation.UUID",
],
preconcurrency: .onOS(["Linux"])
),
]
Original file line number Diff line number Diff line change
@@ -15,4 +15,7 @@ import OpenAPIKit

extension FileTranslator {
// Add helpers for reading feature flags below.

/// A boolean value indicating whether the `uuid` format on schemas should be followed.
var supportUUIDFormat: Bool { config.featureFlags.contains(.uuidSupport) }
}
Original file line number Diff line number Diff line change
@@ -47,7 +47,9 @@ protocol FileTranslator {
extension FileTranslator {

/// A new context from the file translator.
var context: TranslatorContext { TranslatorContext(asSwiftSafeName: { $0.safeForSwiftCode }) }
var context: TranslatorContext {
TranslatorContext(asSwiftSafeName: { $0.safeForSwiftCode }, enableUUIDSupport: supportUUIDFormat)
}
}

/// A set of configuration values for concrete file translators.
@@ -58,4 +60,6 @@ struct TranslatorContext {
/// - Parameter string: The string to convert to be safe for Swift.
/// - Returns: A Swift-safe version of the input string.
var asSwiftSafeName: (String) -> String
/// A variable that indicates the presence of the `uuidSupport` feature flag.
var enableUUIDSupport: Bool
}
Original file line number Diff line number Diff line change
@@ -50,6 +50,9 @@ extension TypeName {
/// Returns the type name for the URL type.
static var url: Self { .foundation("URL") }

/// Returns the type name for the UUID type.
static var uuid: Self { .foundation("UUID") }

/// Returns the type name for the DecodingError type.
static var decodingError: Self { .swift("DecodingError") }

Original file line number Diff line number Diff line change
@@ -313,6 +313,7 @@ struct TypeMatcher {
default:
switch core.format {
case .dateTime: typeName = .date
case .uuid where context.enableUUIDSupport: typeName = .uuid
default: typeName = .string
}
}
2 changes: 1 addition & 1 deletion Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ class Test_Core: XCTestCase {
func makeTranslator(
components: OpenAPI.Components = .noComponents,
diagnostics: any DiagnosticCollector = PrintingDiagnosticCollector(),
featureFlags: FeatureFlags = []
featureFlags: FeatureFlags = [.uuidSupport]
) -> TypesFileTranslator {
makeTypesTranslator(components: components, diagnostics: diagnostics, featureFlags: featureFlags)
}
Original file line number Diff line number Diff line change
@@ -144,7 +144,7 @@ final class Test_OperationDescription: Test_Core {
endpoint: endpoint,
pathParameters: pathItem.parameters,
components: .init(),
context: .init(asSwiftSafeName: { $0 })
context: .init(asSwiftSafeName: { $0 }, enableUUIDSupport: true)
)
}
}
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@ final class Test_TypeMatcher: Test_Core {
(.string(contentEncoding: .base64), "OpenAPIRuntime.Base64EncodedData"),
(.string(.init(format: .date), .init()), "Swift.String"),
(.string(.init(format: .dateTime), .init()), "Foundation.Date"),
(.string(.init(format: .uuid), .init()), "Foundation.UUID"),

(.integer, "Swift.Int"), (.integer(.init(format: .int32), .init()), "Swift.Int32"),
(.integer(.init(format: .int64), .init()), "Swift.Int64"),
Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@ final class FileBasedReferenceTests: XCTestCase {
#endif
}

func testPetstore() throws { try _test(referenceProject: .init(name: .petstore)) }
func testPetstore() throws { try _test(referenceProject: .init(name: .petstore), featureFlags: [.uuidSupport]) }

// MARK: - Private

Original file line number Diff line number Diff line change
@@ -79,6 +79,7 @@ paths:
required: true
schema:
type: string
format: uuid
My-Tracing-Header:
$ref: '#/components/headers/TracingHeader'
content:
Original file line number Diff line number Diff line change
@@ -4,10 +4,12 @@
@preconcurrency import struct Foundation.URL
@preconcurrency import struct Foundation.Data
@preconcurrency import struct Foundation.Date
@preconcurrency import struct Foundation.UUID
#else
import struct Foundation.URL
import struct Foundation.Data
import struct Foundation.Date
import struct Foundation.UUID
#endif
import HTTPTypes
/// Service for managing pet metadata.
@@ -107,7 +109,7 @@ public struct Client: APIProtocol {
My_hyphen_Response_hyphen_UUID: try converter.getRequiredHeaderFieldAsURI(
in: response.headerFields,
name: "My-Response-UUID",
as: Swift.String.self
as: Foundation.UUID.self
),
My_hyphen_Tracing_hyphen_Header: try converter.getOptionalHeaderFieldAsURI(
in: response.headerFields,
Original file line number Diff line number Diff line change
@@ -4,10 +4,12 @@
@preconcurrency import struct Foundation.URL
@preconcurrency import struct Foundation.Data
@preconcurrency import struct Foundation.Date
@preconcurrency import struct Foundation.UUID
#else
import struct Foundation.URL
import struct Foundation.Data
import struct Foundation.Date
import struct Foundation.UUID
#endif
import HTTPTypes
extension APIProtocol {
@@ -199,7 +201,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol {
My_hyphen_Request_hyphen_UUID: try converter.getOptionalHeaderFieldAsURI(
in: request.headerFields,
name: "My-Request-UUID",
as: Swift.String.self
as: Foundation.UUID.self
),
accept: try converter.extractAcceptHeaderIfPresent(in: request.headerFields)
)
Original file line number Diff line number Diff line change
@@ -4,10 +4,12 @@
@preconcurrency import struct Foundation.URL
@preconcurrency import struct Foundation.Data
@preconcurrency import struct Foundation.Date
@preconcurrency import struct Foundation.UUID
#else
import struct Foundation.URL
import struct Foundation.Data
import struct Foundation.Date
import struct Foundation.UUID
#endif
/// A type that performs HTTP operations defined by the OpenAPI document.
public protocol APIProtocol: Sendable {
@@ -1820,15 +1822,15 @@ public enum Operations {
/// Request identifier
///
/// - Remark: Generated from `#/paths/pets/GET/header/My-Request-UUID`.
public var My_hyphen_Request_hyphen_UUID: Swift.String?
public var My_hyphen_Request_hyphen_UUID: Foundation.UUID?
public var accept: [OpenAPIRuntime.AcceptHeaderContentType<Operations.listPets.AcceptableContentType>]
/// Creates a new `Headers`.
///
/// - Parameters:
/// - My_hyphen_Request_hyphen_UUID: Request identifier
/// - accept:
public init(
My_hyphen_Request_hyphen_UUID: Swift.String? = nil,
My_hyphen_Request_hyphen_UUID: Foundation.UUID? = nil,
accept: [OpenAPIRuntime.AcceptHeaderContentType<Operations.listPets.AcceptableContentType>] = .defaultValues()
) {
self.My_hyphen_Request_hyphen_UUID = My_hyphen_Request_hyphen_UUID
@@ -1856,7 +1858,7 @@ public enum Operations {
/// Response identifier
///
/// - Remark: Generated from `#/paths/pets/GET/responses/200/headers/My-Response-UUID`.
public var My_hyphen_Response_hyphen_UUID: Swift.String
public var My_hyphen_Response_hyphen_UUID: Foundation.UUID
/// A description here.
///
/// - Remark: Generated from `#/paths/pets/GET/responses/200/headers/My-Tracing-Header`.
@@ -1867,7 +1869,7 @@ public enum Operations {
/// - My_hyphen_Response_hyphen_UUID: Response identifier
/// - My_hyphen_Tracing_hyphen_Header: A description here.
public init(
My_hyphen_Response_hyphen_UUID: Swift.String,
My_hyphen_Response_hyphen_UUID: Foundation.UUID,
My_hyphen_Tracing_hyphen_Header: Components.Headers.TracingHeader? = nil
) {
self.My_hyphen_Response_hyphen_UUID = My_hyphen_Response_hyphen_UUID
Original file line number Diff line number Diff line change
@@ -1462,6 +1462,36 @@ final class SnippetBasedReferenceTests: XCTestCase {
"""
)
}
func testComponentsSchemasUUID() throws {
try self.assertSchemasTranslation(
featureFlags: [.uuidSupport],
"""
schemas:
MyUUID:
type: string
format: uuid
""",
"""
public enum Schemas {
public typealias MyUUID = Foundation.UUID
}
"""
)
// Without UUID support, the schema will be translated as a string
try self.assertSchemasTranslation(
"""
schemas:
MyUUID:
type: string
format: uuid
""",
"""
public enum Schemas {
public typealias MyUUID = Swift.String
}
"""
)
}

func testComponentsSchemasBase64() throws {
try self.assertSchemasTranslation(
4 changes: 0 additions & 4 deletions Tests/PetstoreConsumerTests/Common.swift
Original file line number Diff line number Diff line change
@@ -14,10 +14,6 @@
import XCTest
import HTTPTypes

extension Operations.listPets.Output {
static var success: Self { .ok(.init(headers: .init(My_hyphen_Response_hyphen_UUID: "abcd"), body: .json([]))) }
}

extension HTTPRequest {
/// Initializes an HTTP request with the specified path, HTTP method, and header fields.
///
13 changes: 9 additions & 4 deletions Tests/PetstoreConsumerTests/Test_Client.swift
Original file line number Diff line number Diff line change
@@ -36,6 +36,8 @@ final class Test_Client: XCTestCase {
}

func testListPets_200() async throws {
let requestUUID = UUID(uuidString: "da6811e6-112f-494e-8bdd-7f8b2367cb66")!
let responseUUID = UUID(uuidString: "b1c601c1-8963-460b-9fe4-fda2f73da64f")!
transport = .init { (request: HTTPRequest, body: HTTPBody?, baseURL: URL, operationID: String) in
XCTAssertEqual(operationID, "listPets")
XCTAssertEqual(
@@ -44,12 +46,15 @@ final class Test_Client: XCTestCase {
)
XCTAssertEqual(baseURL.absoluteString, "/api")
XCTAssertEqual(request.method, .get)
XCTAssertEqual(request.headerFields, [.accept: "application/json", .init("My-Request-UUID")!: "abcd-1234"])
XCTAssertEqual(
request.headerFields,
[.accept: "application/json", .init("My-Request-UUID")!: requestUUID.uuidString]
)
XCTAssertNil(body)
return try HTTPResponse(
status: .ok,
headerFields: [
.contentType: "application/json", .init("my-response-uuid")!: "abcd",
.contentType: "application/json", .init("my-response-uuid")!: responseUUID.uuidString,
.init("my-tracing-header")!: "1234",
]
)
@@ -67,14 +72,14 @@ final class Test_Client: XCTestCase {
let response = try await client.listPets(
.init(
query: .init(limit: 24, habitat: .water, feeds: [.herbivore, .carnivore], since: .test),
headers: .init(My_hyphen_Request_hyphen_UUID: "abcd-1234")
headers: .init(My_hyphen_Request_hyphen_UUID: requestUUID)
)
)
guard case let .ok(value) = response else {
XCTFail("Unexpected response: \(response)")
return
}
XCTAssertEqual(value.headers.My_hyphen_Response_hyphen_UUID, "abcd")
XCTAssertEqual(value.headers.My_hyphen_Response_hyphen_UUID, responseUUID)
XCTAssertEqual(value.headers.My_hyphen_Tracing_hyphen_Header, "1234")
switch value.body {
case .json(let pets): XCTAssertEqual(pets, [.init(id: 1, name: "Fluffz")])
13 changes: 9 additions & 4 deletions Tests/PetstoreConsumerTests/Test_Server.swift
Original file line number Diff line number Diff line change
@@ -28,15 +28,20 @@ final class Test_Server: XCTestCase {
}

func testListPets_200() async throws {
let requestUUID = UUID(uuidString: "da6811e6-112f-494e-8bdd-7f8b2367cb66")!
let responseUUID = UUID(uuidString: "b1c601c1-8963-460b-9fe4-fda2f73da64f")!
client = .init(listPetsBlock: { input in
XCTAssertEqual(input.query.limit, 24)
XCTAssertEqual(input.query.habitat, .water)
XCTAssertEqual(input.query.since, .test)
XCTAssertEqual(input.query.feeds, [.carnivore, .herbivore])
XCTAssertEqual(input.headers.My_hyphen_Request_hyphen_UUID, "abcd-1234")
XCTAssertEqual(input.headers.My_hyphen_Request_hyphen_UUID, requestUUID)
return .ok(
.init(
headers: .init(My_hyphen_Response_hyphen_UUID: "abcd", My_hyphen_Tracing_hyphen_Header: "1234"),
headers: .init(
My_hyphen_Response_hyphen_UUID: responseUUID,
My_hyphen_Tracing_hyphen_Header: "1234"
),
body: .json([.init(id: 1, name: "Fluffz")])
)
)
@@ -45,7 +50,7 @@ final class Test_Server: XCTestCase {
.init(
soar_path: "/api/pets?limit=24&habitat=water&feeds=carnivore&feeds=herbivore&since=\(Date.testString)",
method: .get,
headerFields: [.init("My-Request-UUID")!: "abcd-1234"]
headerFields: [.init("My-Request-UUID")!: requestUUID.uuidString]
),
nil,
.init()
@@ -54,7 +59,7 @@ final class Test_Server: XCTestCase {
XCTAssertEqual(
response.headerFields,
[
.init("My-Response-UUID")!: "abcd", .init("My-Tracing-Header")!: "1234",
.init("My-Response-UUID")!: responseUUID.uuidString, .init("My-Tracing-Header")!: "1234",
.contentType: "application/json; charset=utf-8", .contentLength: "47",
]
)