-
Notifications
You must be signed in to change notification settings - Fork 125
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
Wrap operation methods in specialized methods (1 success response, 1 content type) #145
Comments
Hi @MahdiBM, thanks for writing this up in a lot of detail. A few things to consider, that'll hopefully explain why what you suggest hasn't been done yet. We certainly could make some improvements in this area, but we so far focused on just getting the building blocks correct (since users can build conveniences on top of the generated code). That said, we are tracking generating more convenience sugar with #22. In your example, you flattened the inputs into one namespace. That assumes that the names are unique, but they might not be. You can totally have a header named The second thing your example assumes, but isn't actually part of the OpenAPI specification, is knowing which response represents the single success that you'd want to return, and which responses should throw an error. An OpenAPI document can totally have an operation with 2 or more successful responses, and only the user knows what's "success" and "failure" to them. It's also a bit of an anti-pattern to throw indiscriminately on all non-success status codes, since the author of the service that provides the OpenAPI document might have done the hard work to specify multiple error response status codes, each of which should be handled differently by the user. For example, 401, 404, and 429 should presumably be handled very differently - one should maybe refresh auth, one should fail outright, and one should retry with a backoff. If, as a client, you want to handle all the same way, that's fine, but the generator doesn't really want to encourage that - it's up to you to write that guard/else, but ideally you'd write a switch for each response and handle each response appropriately. And you'd write wrappers that do that for you to avoid having to repeat it at all the call sites. On the Thanks again for taking the time and hopefully some of the explanations above shed more light on why, if we want to support the full flexibility of the OpenAPI specification, we can't easily make assumptions like: a single success response, unique parameter names, etc, that could make the generated code a bit more convenient. That said, we do want to make improvements in this area, so we'd like to continue the conversation. It'll just be a bit more work than might seem at first, and needs to be well designed, to ensure we don't create limitations on top of the OpenAPI speciation or overly complicate the generator logic. One way to go in this area I've been thinking about, and you already mentioned, is to generate some extra "hooks" in code that people can write generic functions on. For example, if we had a protocol |
huge +1 from me for the simplified access to the response body. A protocol like I understand the focus on supporting theoretically complicated response types, but I think we can all agree that <insert high percentage here> of real-life API usage is "get the body on 2xx or throw" Its great that you can dig into all the details of a response for the complex cases, but for the (my guess) most used use-case the API is not very ergonomic. In terms of naming: So, something like this would feel swifty to me: do { let body = try response.successBody }
catch let error as ClientError { ... } |
Just to clarify here – I'm not disputing that For even more detail, check out the Project scope and goals and API stability of generated code, which outline how we think about the generated code, and how we recommend adopters use it. TL;DR - it's not meant to fully replace hand-written packages for specific services. But it can help you write those hand-written packages much more safely, without any manual copy-pasting of string JSON keys, and encourages you to handle all responses, both successes and failures. Hope this sheds some light on why the current design of the project is like this, and how we aim to gradually improve the ergonomics over time. 🙂 |
In case someone wants to pick up designing a solution here – we very much want to minimize how much extra code needs to be generated, and how much complexity has to be added to the generator in this process. So a modest proposal that makes meaningful progress has a higher chance of success than a proposal of an overly specific sugar that doesn't generalize very much, and is complex to implement. It's a spectrum of course, just wanted to publicize some of the criteria that'll be important during review. But of course, making the adopter experience as good as possible overall is the most important thing 🙂 |
@czechboy0 Thanks for the response.
Right. Fair point. I still think this could be a very low priority feature request which is fine to possibly take a few years to happen.
Valid point, but i don't see a real problem. There could be multiple functions to
Right again, I understand your concerns, but i don't think those are deal breakers. Assuming there is a function like
The reasons are all basically related to performance:
Yeah i think that's something to improve on as well. I like the I also understand one of the main problems in front of what i'm proposing. All in all, I'm still not sure exactly which way to go. I definitely need to think more about this, and possibly take a deeper look at the OpenAPI specification. |
My first thought as a user when hitting a similar issue was to try to add convenience methods that were unique to my implementation but that's not possible at the moment as each type is different. Could we wrap all generated types in a protocol (e.g. I agree that the generator itself should probably not make assumptions about use cases—this way, a user has full control over how they chose to handle the response. |
Agreed, I think the best way would be to add hooks on the generated types (such as marker and real protocols), which then adopters can extend using generics or macros, matching their preferred style. |
I was thinking we could also go full in the macro way instead of adding protocols etc... |
Macros could help here for sure, but calling a macro still needs to typecheck correctly (before expansion), and I don't think we today have enough type information on the generated types to make even macros useful. And once we add those, some combination of generic functions and macros might help us make the ergonomics for simple cases nicer. |
### Motivation We'd like to run a proposal for generating additional API to simplify providing operation inputs and handling operation outputs for simple API calls with just one successful outcome. ### Modifications - Add SOAR-0007: Shorthand APIs for operation inputs and outputs (See the proposal itself for details)[^1] ### Result n/a ### Test Plan n/a ### Related Issues #22, #104, #105, #145 [^1]: https://github.com/apple/swift-openapi-generator/pull/291/files --------- Signed-off-by: Si Beaumont <[email protected]>
Here's an example of some code i'm repeating again and again in Penny:
I think this function should roughly look like this. optimally:
So let's analyze how the second function is better:
case let
or nestedswitch
statements.decode()
the response only after requested by user.decode()
function itself is supposed to throw a proper error if the response isn't a success response.decodeError()
function or the like to not check for success of the request considering one might be in fact trying to decode the error response.What i mentioned above is basically what i've tried and implemented in DiscordBM, and i've really liked, which is achieved by:
decode()
the response to that type when needed.DiscordClient
functions will return one of those types (for the most part), depending on if Discord's response is documented to have a body.There are somethings worth noting:
I still think a lot of this is achievable in relatively short term, and almost all of it in long term.
Right now there is no way for one to make the decoding process easier for themselves.
For example if all json responses conformed to a single protocol, i could have at least set up some functions to ease my work.
The text was updated successfully, but these errors were encountered: