-
Notifications
You must be signed in to change notification settings - Fork 122
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
Generate enums for server variables #618
Conversation
Thanks @theoriginalbit!. This is just a courtesy note to acknowledge I’ve seen it but I’ll probably get to it early next week. |
This is great, @theoriginalbit! I only skimmed it, but two things I suspect we need to change before landing it:
Thanks again! I'm sure we'll be able to land this with a few tweaks. |
Thanks so much for taking a look and providing feedback @czechboy0. When I was writing the PR description I did wonder about how this breaking change would be handled, I totally forgot to add the question to the description. I will definitely take a look at the
So just to make sure I'm understanding correctly, in the scenario of the OpenAPI document servers:
- url: https://{environment}.example.com/api/{version}
variables:
environment:
default: prod
enum:
- prod
- staging
- dev
version:
default: v1 You'd like to see it output as the following generated code? /// 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:
/// - 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://example.com/api/{version}",
variables: [
.init(
name: "environment",
value: environment.rawValue
),
.init(
name: "version",
value: version
)
]
)
}
} I'm very happy to make this change, though one thing I want to confirm with you all first since perhaps my understanding of the OpenAPI spec was wrong. My understanding of the spec is that a default-only variable, # Syntax sugar
servers:
- url: https://example.com/api/{version}
variables:
version:
default: v1
# Verbose
servers:
- url: https://example.com/api/{version}
variables:
version:
default: v1
enum:
- v1 If that is the case, then I believe generating a default-only variable as an enum with a single case would be better for the consumer as if the provider later adds extra enum values then updating to that spec doesn't introduce a breaking change (it still uses the default value). Also, perhaps it better communicates that there is only one available option? I could see with the above document a consumer invoking as such simply because it's a string let url = try Servers.server1(version: "v2") Which wouldn't be a runtime issue, even in the latest released implementation, since default-only variables don't get What are your thoughts on this, have I misunderstood the purpose of default-only variables? Should they be open and unrestricted strings? |
Quick (half-baked) thought on how we could do this in a non-API-breaking way so as to be able to land this without a feature flag or major version: could we make these enums Whether this is something we can do will depend on how we interpret the OpenAPI spec w.r.t. whether values outside of those defined in the document are permitted. |
With regard to the OAS:
I take this to mean that, if an enum is provided, then it is a limited set. |
Ah yeah, I totally misread and misunderstood the OAS, that wording definitely suggests that if there is no enum values defined then it's an open field. I'll update the PR later today to revert those specific definitions to be open Strings, and only apply the enum generation to variables defined with an enum field. Using |
Apologies, I ran out of time to address the feedback the other day. The changes I've just pushed fixes both of the issues raised. I've introduced a feature flag, So given the following definition servers:
- url: https://example.com/api
description: Example service deployment.
variables:
environment:
description: Server environment.
default: prod
enum:
- prod
- staging
- dev
version:
default: v1 With the /// Server URLs defined in the OpenAPI document.
internal enum Servers {
/// Example service deployment.
///
/// - Parameters:
/// - environment: Server environment.
/// - version: The overall API version
internal static func server1(
environment: Swift.String = "prod",
version: Swift.String = "v1"
) throws -> Foundation.URL {
try Foundation.URL(
validatingOpenAPIServerURL: "https://example.com/api",
variables: [
.init(
name: "environment",
value: environment,
allowedValues: [
"prod",
"staging",
"dev"
]
),
.init(
name: "version",
value: version
)
]
)
}
} With the /// 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://example.com/api",
variables: [
.init(
name: "environment",
value: environment.rawValue
),
.init(
name: "version",
value: version
)
]
)
}
} I've also added a new test case to validate the output when the feature flag is enabled. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this looks like a good direction, but since this is introducing new generated API, I'd like us to run this through our lightweight proposal process: https://swiftpackageindex.com/apple/swift-openapi-generator/1.3.0/documentation/swift-openapi-generator/proposals
I think some of the names could use community feedback. The proposal doesn't need to be long, much of what you wrote up in the PR description can probably be lifted over to the proposal already.
Some things I'm curious to hear other people's feedback on is:
- the names of the generated types
Variables
- whether we need a dedicated
default
static var, or if the default should be generated inline in theserver3(...)
function
Let us know if you have any questions - thanks again, this will be a great addition 🙂
Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift
Outdated
Show resolved
Hide resolved
0a752ea
to
78bf584
Compare
Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift
Outdated
Show resolved
Hide resolved
0504c42
to
6ad525a
Compare
Okay the implementation looks good, now let's wait for the review to run its course and we'll finalize that once accepted - thanks again! 🙏 |
Awesome, no worries at all. Hopefully some people can provide feedback on the namespace names, otherwise looking forward to this going through the finalising steps. |
Co-authored-by: Honza Dvorsky <[email protected]>
6ad525a
to
ad29a41
Compare
Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServers.swift
Outdated
Show resolved
Hide resolved
Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift
Show resolved
Hide resolved
Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few minor change requests, but looks good overall! Thanks
Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServers.swift
Show resolved
Hide resolved
Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServersVariables.swift
Outdated
Show resolved
Hide resolved
Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServersVariables.swift
Show resolved
Hide resolved
Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServersVariables.swift
Show resolved
Hide resolved
Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift
Show resolved
Hide resolved
Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift
Outdated
Show resolved
Hide resolved
Oh, one more thing:
|
I've addressed the latest round of feedback @czechboy0 :)
None of the examples appeared to use the generated servers URL functions. I also opted to keep one test assertion, for the sake of regression testing, which tested the deprecated API threw if an invalid variable was supplied. Once the deprecated functions are no longer being generated I figured the assertion could be removed, but at least for now it serves a purpose. |
There are places in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One small request, otherwise looks great! Kicking off CI.
lgtm, we just need to get the CI green. The format check just requires you to run e.g. |
Oh and you'll need to move the nested protocol out, as we still support 5.9:
|
Sorry took a bit, had to download Hopefully all fixed now, @czechboy0. |
Thank you for the great contribution, @theoriginalbit! 👏 |
### Motivation As requested by @czechboy0 in #618 I have created this proposal for community feedback. ### Modifications Added the proposal. Also fixed a typo in the document for the proposal process. ### Result N/A ### Test Plan N/A --------- Co-authored-by: Honza Dvorsky <[email protected]>
Motivation
Refer to proposal #629
PR description prior to raising proposal
### MotivationRecently in a project I was using a spec which defined variables similar to below
The generated code to create the default server URL was easy enough being able to utilise the default parameters
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:
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.
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 exampleServer1
, 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-existingServers
namespace. A truncated example:To use the new translator functions the
translateServers
function has been modified to call thetranslateServersVariables
function and insert the declarations as a member alongside the existing static functions for each of the servers. ThetranslateServer(index:server:)
function was also edited to make use of the generated variable enums, and the code which generated the string array forallowedValues
has been removed; runtime validation should no longer be required, as therawValue
of a variable enum is the value defined in the OpenAPI document.Result
The following spec
Would currently generate to the output
But with this PR it would generate to be
Now when it comes to usage
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).
Before this PR:
With this PR:
Result
Refer to #618 (comment)
Test Plan
I have updated the petstore unit tests to reflect the changes made in this PR, see diff.