Skip to content

Commit

Permalink
Generate enums for server variables
Browse files Browse the repository at this point in the history
  • Loading branch information
theoriginalbit committed Sep 7, 2024
1 parent 9985908 commit 0508b76
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ enum Constants {

/// The prefix of each generated method name.
static let propertyPrefix: String = "server"

/// The name of the server variables namespace.
static let variablesNamespace: String = "Variables"

/// The prefix of the namespace that contains server specific variables.
static let serverVariablesNamespacePrefix: String = "Server"

/// The name of the generated default computed property.
static let defaultPropertyName: String = "default"
}

/// Constants related to the configuration type, which is used by both
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,30 @@ extension TypesFileTranslator {
(originalKey: key, swiftSafeKey: swiftSafeName(for: key), value: value)
}
let parameters: [ParameterDescription] = safeVariables.map { (originalKey, swiftSafeKey, value) in
.init(label: swiftSafeKey, type: .init(TypeName.string), defaultValue: .literal(value.default))
let memberPath: [String] = [
Constants.ServerURL.variablesNamespace,
translateServerVariablesEnumName(for: index),
translateVariableToEnumName((originalKey, value))
]
return .init(
label: swiftSafeName(for: originalKey),
type: .member(memberPath),
defaultValue: .identifierType(.member(memberPath + CollectionOfOne(Constants.ServerURL.defaultPropertyName)))
)
}
let variableInitializers: [Expression] = safeVariables.map { (originalKey, swiftSafeKey, value) in
let allowedValuesArg: FunctionArgumentDescription?
if let allowedValues = value.enum {
allowedValuesArg = .init(
label: "allowedValues",
expression: .literal(.array(allowedValues.map { .literal($0) }))
)
} else {
allowedValuesArg = nil
}
return .dot("init")
.call(
[
.init(label: "name", expression: .literal(originalKey)),
.init(label: "value", expression: .identifierPattern(swiftSafeKey)),
] + (allowedValuesArg.flatMap { [$0] } ?? [])
.init(
label: "value",
expression: .memberAccess(.init(
left: .identifierPattern(swiftSafeKey),
right: "rawValue"
))
),
]
)
}
let methodDecl = Declaration.commentable(
Expand Down Expand Up @@ -81,7 +87,12 @@ extension TypesFileTranslator {
/// - Parameter servers: The servers to include in the extension.
/// - Returns: A declaration of an enum namespace of the server URLs type.
func translateServers(_ servers: [OpenAPI.Server]) -> Declaration {
let serverDecls = servers.enumerated().map(translateServer)
var serverDecls = servers.enumerated().map(translateServer)

if let variablesDecl = translateServersVariables(servers) {
serverDecls.insert(variablesDecl, at: 0)
}

return .commentable(
.doc("Server URLs defined in the OpenAPI document."),
.enum(accessModifier: config.access, name: Constants.ServerURL.namespace, members: serverDecls)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//===----------------------------------------------------------------------===//
//
// 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 OpenAPIKit

extension TypesFileTranslator {

/// Returns the name used for the enum which represents a server variable defined in the OpenAPI
/// document.
/// - Parameter variable: The variable information.
/// - Returns: A name that can be safely used for the enum.
func translateVariableToEnumName(_ variable: (key: String, value: OpenAPI.Server.Variable)) -> String {
return swiftSafeName(for: variable.key.localizedCapitalized)
}

/// Returns the name used for the namespace (enum) which contains a specific server's variables.
/// - Parameter index: The array index of the server.
/// - Returns: A name that can be safely used for the namespace.
func translateServerVariablesEnumName(for index: Int) -> String {
return "\(Constants.ServerURL.serverVariablesNamespacePrefix)\(index + 1)"
}

/// Returns a declaration of a variable enum case for the provided value. If the value can be
/// safely represented as an identifier then the enum case is name only, otherwise the case
/// will have a raw value set to the provided value to satisfy the OpenAPI document
/// requirements.
/// - Parameter value: The variable's enum value.
/// - Returns: A enum case declaration named by the supplied value.
func translateVariableCase(_ value: String) -> Declaration {
let caseName = swiftSafeName(for: value)
if caseName == value {
return .enumCase(name: caseName, kind: .nameOnly)
} else {
return .enumCase(name: caseName, kind: .nameWithRawValue(.string(value)))
}
}

/// Returns a declaration of a variable enum defined in the OpenAPI document. Including
/// a static computed property named default which returns the default defined in the
/// document.
/// - Parameter variable: The variable information.
/// - Returns: An enum declaration.
func translateServerVariable(_ variable: (key: String, value: OpenAPI.Server.Variable)) -> Declaration {
let enumName = translateVariableToEnumName(variable)
var casesDecls: [Declaration]

if let enums = variable.value.enum {
casesDecls = enums.map(translateVariableCase)
} else {
casesDecls = [translateVariableCase(variable.value.default)]
}
casesDecls.append(.commentable(
.doc("The default variable."),
.variable(
accessModifier: config.access,
isStatic: true,
kind: .var,
left: .identifierPattern("`\(Constants.ServerURL.defaultPropertyName)`"),
type: .member(enumName),
getter: [
.expression(
.return(
.memberAccess(.init(
left: .identifierPattern(enumName),
right: swiftSafeName(for: variable.value.default)
))
)
),
]
)
))

return .commentable(
.doc("""
The "\(variable.key)" variable defined in the OpenAPI document.
The default value is "\(variable.value.default)".
"""),
.enum(isFrozen: true, accessModifier: config.access, name: enumName, conformances: [TypeName.string.fullyQualifiedSwiftName], members: casesDecls)
)
}

/// Returns a declaration of a namespace (enum) for a specific server and will define
/// one enum member for each of the server's variables in the OpenAPI Document.
/// If the server does not define variables, no declaration will be generated.
/// - Parameters:
/// - index: The index of the server in the list of servers defined
/// in the OpenAPI document.
/// - server: The server variables information.
/// - Returns: A declaration of the server variables namespace, or `nil` if no
/// variables are declared.
func translateServerVariables(index: Int, server: OpenAPI.Server) -> Declaration? {
if server.variables.isEmpty {
return nil
}

let typeName = translateServerVariablesEnumName(for: index)
let variableDecls = server.variables.map(translateServerVariable)
return .commentable(
.doc("The variables for Server\(index + 1) defined in the OpenAPI document."),
.enum(accessModifier: config.access, name: typeName, members: variableDecls)
)
}

/// Returns a declaration of a namespace (enum) called "Variables" that
/// defines one namespace (enum) per server URL that defines variables
/// in the OpenAPI document. If no server URL defines variables then no
/// declaration is generated.
/// - Parameter servers: The servers to include in the extension.
/// - Returns: A declaration of an enum namespace of the server URLs type.
func translateServersVariables(_ servers: [OpenAPI.Server]) -> Declaration? {
let variableDecls = servers.enumerated().compactMap(translateServerVariables)
if variableDecls.isEmpty {
return nil
}

return .commentable(
.doc("Server URL variables defined in the OpenAPI document."),
.enum(accessModifier: config.access, name: Constants.ServerURL.variablesNamespace, members: variableDecls)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,53 @@ extension APIProtocol {

/// Server URLs defined in the OpenAPI document.
public enum Servers {
/// Server URL variables defined in the OpenAPI document.
public enum Variables {
/// The variables for Server3 defined in the OpenAPI document.
public enum Server3 {
/// The "protocol" variable defined in the OpenAPI document.
///
/// The default value is "https".
@frozen public enum _Protocol: Swift.String {
case https
/// The default variable.
public static var `default`: _Protocol {
return _Protocol.https
}
}
/// The "subdomain" variable defined in the OpenAPI document.
///
/// The default value is "test".
@frozen public enum Subdomain: Swift.String {
case test
/// The default variable.
public static var `default`: Subdomain {
return Subdomain.test
}
}
/// The "port" variable defined in the OpenAPI document.
///
/// The default value is "443".
@frozen public enum Port: Swift.String {
case _443 = "443"
case _8443 = "8443"
/// The default variable.
public static var `default`: Port {
return Port._443
}
}
/// The "basePath" variable defined in the OpenAPI document.
///
/// The default value is "v1".
@frozen public enum Basepath: Swift.String {
case v1
/// The default variable.
public static var `default`: Basepath {
return Basepath.v1
}
}
}
}
/// Example Petstore implementation service
public static func server1() throws -> Foundation.URL {
try Foundation.URL(
Expand All @@ -174,33 +221,29 @@ public enum Servers {
/// - port:
/// - basePath: The base API path.
public static func server3(
_protocol: Swift.String = "https",
subdomain: Swift.String = "test",
port: Swift.String = "443",
basePath: Swift.String = "v1"
_protocol: Variables.Server3._Protocol = Variables.Server3._Protocol.default,
subdomain: Variables.Server3.Subdomain = Variables.Server3.Subdomain.default,
port: Variables.Server3.Port = Variables.Server3.Port.default,
basePath: Variables.Server3.Basepath = Variables.Server3.Basepath.default
) throws -> Foundation.URL {
try Foundation.URL(
validatingOpenAPIServerURL: "{protocol}://{subdomain}.example.com:{port}/{basePath}",
variables: [
.init(
name: "protocol",
value: _protocol
value: _protocol.rawValue
),
.init(
name: "subdomain",
value: subdomain
value: subdomain.rawValue
),
.init(
name: "port",
value: port,
allowedValues: [
"443",
"8443"
]
value: port.rawValue
),
.init(
name: "basePath",
value: basePath
value: basePath.rawValue
)
]
)
Expand Down

0 comments on commit 0508b76

Please sign in to comment.