-
Notifications
You must be signed in to change notification settings - Fork 130
Allow substituting types #764
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
base: main
Are you sure you want to change the base?
Conversation
Sorry this went unacknowledged until now, and thank you for taking the time to think about this.
I'm lightly in favour of this approach since, until we have a holistic solution, it provides an escape-hatch. We've also previously mentioned that extensions might be a suitable solution to some of these problems: #375 (comment). It definitely opens up a bunch of questions and caveats. I see this was motivated by the discussion on providing more strongly typed scalar types and agree this is tempting for things like using a UUID type, instead of a string. My biggest concern would be how this would be used with complex schemas and the burden this shifts to adopters for providing a type that satisfies the schema, especially when that schema is itself referenced in other schemas. Relatedly there was some work in #627 to move targeted types over which is something I'm supportive of but was lacking an API evolution story. Potentially the config to opt-in might be good enough. Do you see this feature only being valuable if it can provide substitution for richer types or is there value exploring something more targeted? @czechboy0 WDYT to this too? |
No worries at all! i appreciate the response. I also would classify it as a "escape-hatch" solution and it is inherently "unsafe" as you are relying on manually ensuring that it adheres to the schema... and i would happily take that tradeoff (obviously 😅) In our project, where we would like to migrate to generating our models, instead of hand-maintaining them. The main reasons this escape-hatch would be welcomed are:
I can see this approach breaking down if the schema uses custom "format" without defining a custom named schema. This would IMO something worth-wile to investigate as well. User:
type: object
properties:
name:
type: string
avatar_url: # there is (currently) no way to swap out this type
type: string
format: uri |
So you're seeing this as a tool to unlock incremental adoption. That's an interesting idea.
More generally, this approach only works by patching the OpenAPI document. Are there any other approaches that would allow for something similar but still be useful to adopters who consume a doc from a third-party? Maybe that's a step too far. I'm still interested in what @czechboy0 thinks about this proposal. If it were targeted, well-tested, and documented as a only an escape-hatch, WDYT to us moving forward? |
I think it's a reasonable approach, yeah. An important decision will be whether this would only support primitive type replacement, ie to solve the typesafe scalars problem, or if it'd be supported to replace even object and other composite types - would be nice, just a heads up that it might be more involved implementation-wise. It'll mean not generating inline types, not validating them, etc. But I'm in support of this in principle - to use vendor extensions to override which type reference is generated. |
Great to hear the approach is good. I would very much prefer to support object types. I tried to have a deeper look into the implementation as you mentioned, specifically not generating inline types, not validating them. Maybe a bit too optimistic, but i have the feeling that most of this is already accounted for. Here is a example (the latest revision shows the change adding the vendor-extension on a object type) But i am not super-familiar with the codebase and would appreciate any pointers to specific spots i could check out. |
Might be easiest for you to try to create a draft PR, while ensuring all tests still pass? Will be easier to iterate there. Once you have confidence that your approach works, writing a lightweight proposal would be great (we have a template for those). Thanks for looking into this! |
This PR already contains a POC version of the proposed changes, and the tests pass. Should i create a dedicated github-issue for this as well? (and a additional PR introducing the evolution proposal) |
I see, would you be able to add more tests, for other schema types? Especially: object, allOf/oneOf/anyOf (including nesting), nested object types, things like that. I'd also propose a slightly different name: |
d21b7af
to
8646df8
Compare
Hi finally had the time to look into this again. I hope you don't mind that the time i can contribute to this can be a bit sporadic, with full-time job and family. I do want to give an update on my progress so far:
I do have some concerns that This i think would also be a useful, but separate, feature. I used Still on my todo-list:
|
@simonjbeaumont @czechboy0 I added some more tests and also opened a PR on I also have written a draft of an evolution proposal (currently this is in this PR and i can move that to a separate PR) In writing this i am reconsidering a bit if vendor-extensions are the best approach to this as the config approach in "Alternatives Considered" also seems quite straightforward, while also being easily usable with third-party open-api documents Any Feedback on the implementation or the proposal would be welcom |
This is great progress, @simonbility! Since this might end up being a pretty large change, once all said and done, I wonder if we could consider scoping the first iteration down a bit, and extending it later in a separate PR/proposal. Specifically, I wonder if the most constrained version that still fulfills a big chunk of the requirement could look like:
While that might require some OpenAPI docs to get changed, it'd just be to move some inline types to be named top level types. Otherwise no change needed, and it'd be config driven. I suspect that'll end up a much smaller implementation, because only the generation of Types.swift needs to be changed, and only in the one place where we iterate through top level types. It should also play nicely with both filtering and cycle detection. If we can deliver this successfully, that'll prove out the concept without closing any doors to future enhancements. WDYT, @simonbility @simonjbeaumont? |
I like the idea of the reduced scope but IIUC from the above comments it would only have limited value for the adopter. Maybe it's a good step in the right direction though. |
Im perfectly happy to keep this scoped to top-level types with the config based mapping. Thats fairly close to my initial version where only later on i added inline properties,… for "completeness" sake. Ill make the adaptations for the scoped down version at some point in the following days. |
I made the modifications and also updated the Proposal document https://github.com/apple/swift-openapi-generator/blob/2130376fb452a278ceec897963f3da2fa6de1be0/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0014.md Some questions/topics i wanted to bring up. Should this be constrained to
|
Can you elaborate on this point? My assumption was that the adopter is responsible for making sure that their replacement type is indeed compatible with the replaced type. For example, if you replace an object with a string, the generator will let you do it (because it doesn't know what the type will be at runtime that you're replacing it with), but it likely won't work. What's the awareness that you think the generator should have here? |
I assumed wronglythe code converting a type to query parameters was generated by the generator, but i now see that its actually based on top of Codable types. |
No worries, sounds good. |
I would still have a question about how to approach the configuration. Short JSON Name typeOverrides:
UUID: Foundation.UUID Fully qualified JSON Path
The fully qualified JSON Path theoretically allows overriding not only Based on that my approach would be to keep this scoped to '#/components/schemas' and go with the Short JSON Name approach. This is also whats implemented at the moment. Do you agree with that? |
Mostly yes, I do wonder if we can make more room for others with:
And in the future, if we support inline schemas, we'll recognize it from the key being a full JSON pointer, rather than just a single word. |
I like that approach 👍 Allows this to stay narrowly focused on schemas while not closing any doors for future extensions. Also makes it a bit more obvious that this is replacing schemas |
Made the adaptations here f14f710 |
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.
Thanks @simonbility - I think the proposal is ready to be reviewed by the community. Could you open a separate PR with just the proposal, and we'll kick off the review process? Thanks for working on this! 🙏
Build and run the server using: | ||
|
||
```console | ||
% swift run hello-world-server |
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.
This might need updating, since this example doesn't currently have any executable targets.
Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateSchema.swift
Outdated
Show resolved
Hide resolved
Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/Constants.swift
Outdated
Show resolved
Hide resolved
featureFlags: resolvedFeatureFlags | ||
) | ||
} | ||
let (diagnostics, finalizeDiagnostics) = preparedDiagnosticsCollector(outputPath: diagnosticsOutputPath) | ||
let doc = self.docPath | ||
let typeOverridesDescription = """ | ||
|
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.
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.
This one actually was on purpose.
The intent was to add a linebreak after TypeOverrides
and then have all the overrides (only schemas at the moment) indented under it.
- TypeOverrides:<-this is the linebreak
- Schemas: "Old"->"New"
Without it would look like this
- TypeOverrides: - Schemas: "Old"->"New"
Tests/OpenAPIGeneratorCoreTests/Translator/TypesTranslator/Test_typeOverrides.swift
Outdated
Show resolved
Hide resolved
Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0014.md
Outdated
Show resolved
Hide resolved
Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0014.md
Outdated
Show resolved
Hide resolved
Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0014.md
Outdated
Show resolved
Hide resolved
Co-authored-by: Honza Dvorsky <[email protected]>
Addressed all the Feedback, and added a separate PR with only the Proposal here #774 |
Motivation
Picking up after some time from this issue #375
There are some usecases described here that i think could be addressed by this.
I suspect there are some "bigger picture" decisions (maybe proposals) needing to happen so i wanted to get the ball rolling :)
Modifications
I made a small change allowing to "swap in" any type instead of generating one, by using a vendor-extension (
x-swift-open-api-substitute-type
)Result
The following spec
would generate code like this (abbreviated)
Test Plan
I did write a test but suspect theres, other parts affected that i missed