Skip to content
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

Generate enums for server variables #618

Merged
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
Expand Up @@ -1628,9 +1628,15 @@ extension KeywordKind {
}

extension Declaration {
/// Returns a new deprecated variant of the declaration if the provided `description` is not `nil`.
func deprecate(if description: DeprecationDescription?) -> Self {
if let description { return .deprecated(description, self) }
return self
}

/// Returns a new deprecated variant of the declaration if `shouldDeprecate` is true.
func deprecate(if shouldDeprecate: Bool) -> Self {
if shouldDeprecate { return .deprecated(.init(), self) }
func deprecate(if shouldDeprecate: Bool, description: @autoclosure () -> DeprecationDescription = .init()) -> Self {
if shouldDeprecate { return .deprecated(description(), self) }
return self
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,19 @@ enum Constants {

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

/// The name of each generated static function.
static let urlStaticFunc: String = "url"

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

/// Constants related to the OpenAPI server variable object.
enum Variable {

/// The types that the protocol conforms to.
static let conformances: [String] = [TypeName.string.fullyQualifiedSwiftName, "Sendable"]
}
}

/// 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 @@ -14,47 +14,46 @@
import OpenAPIKit

extension TypesFileTranslator {

/// Returns a declaration of a server URL static method defined in
/// the OpenAPI document.
/// Returns a declaration of a server URL static function defined in
/// the OpenAPI document using the supplied name identifier and
/// variable generators.
///
/// If the `deprecated` parameter is supplied the static function
/// will be generated with a name that matches the previous, now
/// deprecated API.
///
/// - Important: The variable generators provided should all
/// be ``RawStringTranslatedServerVariable`` to ensure
/// the generated function matches the previous implementation, this
/// is **not** asserted by this translate function.
///
/// If the `deprecated` parameter is `nil` then the function will
/// be generated with the identifier `url` and must be a member
/// of a namespace to avoid conflicts with other server URL static
/// functions.
///
/// - Parameters:
/// - index: The index of the server in the list of servers defined
/// in the OpenAPI document.
/// - server: The server URL information.
/// - deprecated: A deprecation `@available` annotation to attach
/// to this declaration, or `nil` if the declaration should not be deprecated.
/// - variables: The generators for variables the server has defined.
/// - Returns: A static method declaration, and a name for the variable to
/// declare the method under.
func translateServer(index: Int, server: OpenAPI.Server) -> Declaration {
let methodName = "\(Constants.ServerURL.propertyPrefix)\(index+1)"
let safeVariables = server.variables.map { (key, value) in
(originalKey: key, swiftSafeKey: context.asSwiftSafeName(key), value: value)
}
let parameters: [ParameterDescription] = safeVariables.map { (originalKey, swiftSafeKey, value) in
.init(label: swiftSafeKey, type: .init(TypeName.string), defaultValue: .literal(value.default))
}
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] } ?? [])
)
}
let methodDecl = Declaration.commentable(
.functionComment(abstract: server.description, parameters: safeVariables.map { ($1, $2.description) }),
private func translateServerStaticFunction(
theoriginalbit marked this conversation as resolved.
Show resolved Hide resolved
index: Int,
server: OpenAPI.Server,
deprecated: DeprecationDescription?,
variableGenerators variables: [any ServerVariableGenerator]
) -> Declaration {
let name = deprecated == nil ? Constants.ServerURL.urlStaticFunc : "\(Constants.ServerURL.propertyPrefix)\(index + 1)"
return .commentable(
.functionComment(abstract: server.description, parameters: variables.map(\.functionComment)),
.function(
accessModifier: config.access,
kind: .function(name: methodName, isStatic: true),
parameters: parameters,
kind: .function(name: name, isStatic: true),
parameters: variables.map(\.parameter),
keywords: [.throws],
returnType: .identifierType(TypeName.url),
body: [
Expand All @@ -65,14 +64,68 @@ extension TypesFileTranslator {
.init(
label: "validatingOpenAPIServerURL",
expression: .literal(.string(server.urlTemplate.absoluteString))
), .init(label: "variables", expression: .literal(.array(variableInitializers))),
),
.init(
label: "variables",
expression: .literal(.array(variables.map(\.initializer)))
)
])
)
)
]
).deprecate(if: deprecated)
)
}

/// Returns a declaration of a server URL static function defined in
/// the OpenAPI document. The function is marked as deprecated
/// with a message informing the adopter to use the new type-safe
/// API.
/// - Parameters:
/// - index: The index of the server in the list of servers defined
/// in the OpenAPI document.
/// - server: The server URL information.
/// - Returns: A static function declaration.
func translateServerAsDeprecated(index: Int, server: OpenAPI.Server, renamedTo pathToReplacementSymbol: String) -> Declaration {
let serverVariables = translateServerVariables(index: index, server: server, generateAsEnum: false)
return translateServerStaticFunction(index: index,
server: server,
deprecated: DeprecationDescription(renamed: pathToReplacementSymbol),
variableGenerators: serverVariables)
}

/// Returns a namespace (enum) declaration for a server defined in
/// the OpenAPI document. Within the namespace are enums to
/// represent any variables that also have enum values defined in the
/// OpenAPI document, and a single static function named 'url' which
/// at runtime returns the resolved server URL.
///
/// The server's namespace is named to identify the human-friendly
/// index of the enum (e.g. Server1) and is present to ensure each
/// server definition's variables do not conflict with one another.
/// - Parameters:
/// - index: The index of the server in the list of servers defined
/// in the OpenAPI document.
/// - server: The server URL information.
/// - Returns: A static function declaration.
func translateServer(index: Int, server: OpenAPI.Server) -> (pathToStaticFunction: String, decl: Declaration) {
let serverVariables = translateServerVariables(index: index, server: server, generateAsEnum: true)
let methodDecl = translateServerStaticFunction(index: index,
server: server,
deprecated: nil,
variableGenerators: serverVariables)

let namespaceName = "\(Constants.ServerURL.serverNamespacePrefix)\(index + 1)"
let typeName = TypeName(swiftKeyPath: [Constants.ServerURL.namespace, namespaceName, Constants.ServerURL.urlStaticFunc])
let decl = Declaration.commentable(
server.description.map(Comment.doc(_:)),
.enum(
accessModifier: config.access,
name: namespaceName,
members: serverVariables.compactMap(\.declaration) + CollectionOfOne(methodDecl)
)
)
return methodDecl
return (pathToStaticFunction: typeName.fullyQualifiedSwiftName, decl: decl)
}

/// Returns a declaration of a namespace (enum) called "Servers" that
Expand All @@ -81,7 +134,16 @@ 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: [Declaration] = []

for (index, server) in servers.enumerated() {
let translatedServer = translateServer(index: index, server: server)
serverDecls.append(contentsOf: [
translatedServer.decl,
translateServerAsDeprecated(index: index, server: server, renamedTo: translatedServer.pathToStaticFunction)
])
}

return .commentable(
.doc("Server URLs defined in the OpenAPI document."),
.enum(accessModifier: config.access, name: Constants.ServerURL.namespace, members: serverDecls)
Expand Down
Loading
Loading