You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
### Motivation
Refer to proposal
#629
<details>
<summary>PR description prior to raising proposal</summary>
### Motivation
Recently in a project I was using a spec which defined variables similar
to below
```yaml
servers:
- url: https://{environment}.example.com/api/{version}
variables:
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 parameters
```swift
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:
```swift
/// 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.
```swift
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.
### Modifications
I have introduced a new set of types translator functions in the file
`translateServersVariables.swift` which can create the enum declarations
for the variables. If there are no variables defined then no declaration
is generated.
Each variable defined in the OpenAPI document is generated as an enum
with a case that represents each enum in the document. Each enum is also
generated with a static computed property with the name `default` which
returns the default value as required by the OpenAPI spec. These
individual variable enums are then namespaced according to the server
they are applicable for, for example `Server1`, allowing servers to have
identically named variables with different enum values. Finally each of
the server namespace enums are members of a final namespace,
`Variables`, which exists as a member of the pre-existing `Servers`
namespace. A truncated example:
```swift
enum Servers { // enum generated prior to this PR
enum Variables {
enum Server1 {
enum VariableName1 {
// ...
}
enum VariableName2 {
// ...
}
}
}
static func server1(/* ... */) throws -> Foundation.URL { /* declaration prior to this PR */ }
}
```
To use the new translator functions the `translateServers` function has
been modified to call the `translateServersVariables` function and
insert the declarations as a member alongside the existing static
functions for each of the servers. The `translateServer(index:server:)`
function was also edited to make use of the generated variable enums,
and the code which generated the string array for `allowedValues` has
been removed; runtime validation should no longer be required, as the
`rawValue` of a variable enum is the value defined in the OpenAPI
document.
### Result
The following spec
```yaml
servers:
- url: https://{environment}.example.com/api/
variables:
environment:
default: prod
enum:
- prod
- staging
- dev
```
Would currently generate to the output
```swift
/// Server URLs defined in the OpenAPI document.
internal enum Servers {
///
/// - Parameters:
/// - environment:
internal static func server1(environment: Swift.String = "prod") throws -> Foundation.URL {
try Foundation.URL(
validatingOpenAPIServerURL: "https://{environment}.example.com/api/",
variables: [
.init(
name: "environment",
value: environment,
allowedValues: [
"prod",
"staging",
"dev"
]
)
]
)
}
}
```
But with this PR it would generate to be
```swift
/// 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 {
/// 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
}
}
}
}
///
/// - Parameters:
/// - environment:
internal static func server1(environment: Variables.Server1.Environment = Variables.Server1.Environment.default) throws -> Foundation.URL {
try Foundation.URL(
validatingOpenAPIServerURL: "https://{environment}.example.com/api/",
variables: [
.init(
name: "environment",
value: environment.rawValue
)
]
)
}
}
```
Now when it comes to usage
```swift
let url = try Servers.server1() // ✅ works
let url = try Servers.server1(environment: .default) // ✅ works
let url = try Servers.server1(environment: .staging) // ✅ works
let url = try Servers.server1(environment: .stg) // ❌ compiler error, stg not defined on the enum
// some time later staging gets removed from OpenAPI document
let url = try Servers.server1(environment: . staging) // ❌ compiler error, staging not defined on the enum
```
If the document does not define enum values for the variable, an enum is
still generated with a single member (the default required by the spec).
```yaml
servers:
- url: https://example.com/api/{version}
variables:
version:
default: v1
```
Before this PR:
```swift
/// Server URLs defined in the OpenAPI document.
internal enum Servers {
///
/// - Parameters:
/// - version:
internal static func server1(version: Swift.String = "v1") throws -> Foundation.URL {
try Foundation.URL(
validatingOpenAPIServerURL: "https://example.com/api/{version}",
variables: [
.init(
name: "version",
value: version
)
]
)
}
}
```
With this PR:
```swift
/// 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 {
/// The "version" variable defined in the OpenAPI document.
///
/// The default value is "v1".
internal enum Version: Swift.String {
case v1
/// The default variable.
internal static var `default`: Version {
return Version.v1
}
}
}
}
///
/// - Parameters:
/// - version:
internal static func server1(version: Variables.Server1.Version = Variables.Server1.Version.default) throws -> Foundation.URL {
try Foundation.URL(
validatingOpenAPIServerURL: "https://example.com/api/{version}",
variables: [
.init(
name: "version",
value: version.rawValue
)
]
)
}
}
```
</details>
### Result
Refer to
#618 (comment)
### Test Plan
I have updated the petstore unit tests to reflect the changes made in
this PR, see diff.
---------
Co-authored-by: Honza Dvorsky <honza@apple.com>
0 commit comments