apple / swift-openapi-generator

Generate Swift client and server code from an OpenAPI document.
https://swiftpackageindex.com/apple/swift-openapi-generator/documentation
Apache License 2.0
1.45k stars 121 forks source link

Generate enums for server variables #628

Closed theoriginalbit closed 1 month ago

theoriginalbit commented 2 months ago

Motivation

Recently in a project I was using a spec which defined variables similar to below

servers:
  - url: https://{environment}.example.com/api/{version}
    description: Example service deployment.
    variables:
      environment:
        description: Server environment.
        default: prod
        enum:
          - prod
          - staging
          - dev
      version:
        default: v1

The generated code to create the default server URL was easy enough being able to utilise the default variable

let serverURL = try Servers.server1()

But when I wanted to use a different variable I noticed that the parameter was generated as a string and it didn't expose the other allowed values that were defined in the OpenAPI document. It generated the following code:

/// Server URLs defined in the OpenAPI document.
internal enum Servers {
    ///
    /// - Parameters:
    ///   - environment:
    ///   - version:
    internal static func server1(
        environment: Swift.String = "prod",
        version: Swift.String = "v1"
    ) throws -> Foundation.URL {
        try Foundation.URL(
            validatingOpenAPIServerURL: "https://{environment}.example.com/api/{version}",
            variables: [
                .init(
                    name: "environment",
                    value: environment,
                    allowedValues: [
                        "prod",
                        "staging",
                        "dev"
                    ]
                ),
                .init(
                    name: "version",
                    value: version
                )
            ]
        )
    }
}

This meant usage needed to involve runtime checks whether the supplied variable was valid and if the OpenAPI document were to ever remove an option it could only be discovered at runtime.

let serverURL = try Servers.server1(environment: "stg") // might be a valid environment, might not

Looking into the OpenAPI spec for server templating and the implementation of the extension URL.init(validatingOpenAPIServerURL:variables:) I realised that the variables could very easily be represented by an enum in the generated code. By doing so it would also provide a compiler checked way to use a non-default variable.

Proposed solution

Server variables that only declare a default value should remain as strings, but if a variable declares the enum property should have a Swift enum generated to represent the values, allowing implementers to know the other possible values and have the compiler guarantee only an allowed value is used; currently this guarantee is at runtime.

Given the same configuration from the motivation, the Servers namespace (enum) would get generated to include enums for the variables.

/// Server URLs defined in the OpenAPI document.
internal enum Servers {
    /// Server URL variables defined in the OpenAPI document.
    internal enum Variables {
        /// The variables for Server1 defined in the OpenAPI document.
        internal enum Server1 {
            /// Server environment.
            ///
            /// The "environment" variable defined in the OpenAPI document. The default value is "prod".
            internal enum Environment: Swift.String {
                case prod
                case staging
                case dev
                /// The default variable.
                internal static var `default`: Environment {
                    return Environment.prod
                }
            }
        }
    }
    /// Example service deployment.
    ///
    /// - Parameters:
    ///   - environment: Server environment.
    ///   - version:
    internal static func server1(
        environment: Variables.Server1.Environment = Variables.Server1.Environment.default,
        version: Swift.String = "v1"
    ) throws -> Foundation.URL {
        try Foundation.URL(
            validatingOpenAPIServerURL: "https://{environment}.example.com/api/{version}",
            variables: [
                .init(
                    name: "environment",
                    value: environment.rawValue
                ),
                .init(
                    name: "version",
                    value: version
                )
            ]
        )
    }
}

Alternatives considered

No response

Additional information

No response