Skip to content

Generate OpenAPI response schema (application/json) for function return types - #257

Open
bduncanj wants to merge 43 commits into
OxygenFramework:masterfrom
bduncanj:fix/return-types
Open

Generate OpenAPI response schema (application/json) for function return types#257
bduncanj wants to merge 43 commits into
OxygenFramework:masterfrom
bduncanj:fix/return-types

Conversation

@bduncanj

Copy link
Copy Markdown
Contributor

This builds on PR 255

Understand there may be some discussion around architecture, and this rather selfishly caters to my use case, in which I'm building a REST API and so know all object responses will be of type application/json.

For functions which return a type that can be inferred, this generates an application/json content type in the OpenAPI 200 response payload.

  • Does not work with json() render function (as this converts type to string first).
  • Assumes that you always want an application/json return type. (1)

(1) I'd like to find the code that decides how to serialize the response from a HTTP handler and use that to infer the content type of the response schema.

Other:

  • Default values for primitives no longer wrapped in quotes
  • Defaults for everything else passed through JSON3.write() - this fixes default values for structs.
  • Real fields mapped to number in OpenAPI
  • Dict fields no longer throw an error (from my notes, need to confirm this was actually an issue)

bduncanj added 30 commits April 5, 2025 18:04
@ndortega

ndortega commented Apr 11, 2025

Copy link
Copy Markdown
Member

Internally we use the HTTP.sniff function to detect the content type inside the format_response function. This is the "low" level function that handles the final content serialization before the response is returned
https://github.com/OxygenFramework/Oxygen.jl/blob/v1.7.1/src/utilities/misc.jl#L147

@bduncanj

Copy link
Copy Markdown
Contributor Author

Thanks @ndortega. Perhaps a safe next step might be manually encode the logic found in the automatic dispatch of format_response!() such that anything which is not HTTP.Response, Union{Number, Bool, Char, Symbol} or AbstractString has an application/json content type generated.

The exception (for now) would be responses generated with json() render function. I've a private branch (fix/return-types-2) which tackles this by introducing a ResponseWrapper struct which is returned by json(), html(), etc.

It is then consumed by an additional format_response!(req, wrapper::ResponseWrapper) function which converts it into a HTTP.Messages.Response object. This branch works superficially (generates an OpenAPI schema) but the unit tests are failing.

I'd like your thoughts on this before putting more time into it.

@ndortega

Copy link
Copy Markdown
Member

Hi @bduncanj,

Yeah, I think your approach does make sense, the only way to track those types would be to return some wrapper type to hold that information. Now as far as using that information to generate types, I'm not seeing you pass the underlying content_type - but I assume you're just holding off until I responded.

One approach I'd recommend:
1.) keep the render functions mostly unchanged and just wrap the resulting HTTP.Response object with the wrapper type - this keeps the serialization logic for each branch segmented which may not help when it comes to code duplication but is helpful when adding new content types through extensions. There is a protobuf handler in the extensions folder which would also need to be updated so that all render functions have a consistent interface. If done this way, it would make it easy to overload the equality operator so you can compare the wrapper type vs http response objects (if needed).

Let me know what you think of my suggestion

@bduncanj

bduncanj commented Apr 14, 2025

Copy link
Copy Markdown
Contributor Author

@ndortega thanks for the feedback.

A pattern which achieves this with the least boilerplate would be something like this in which ResponseWrapper has a single field (HTTP.Messages.Response) and a type parameter.

In the branch above (fix/return-types-3) I generate the returned schema from this generic type parameter. We could extract the MIME type from the HTTP Response (drop the ;charset portion). We have flexibility to return ResponseWrapper struct where there is an object schema to describe or else just a HTTP.Messages.Response. In both cases the MIME return type can be extracted from HTTP.Response object. When ResponseWrapper absent (or Any) response schema can just be {}.

Happy to continue in this direction if the above sounds good. LMK if you'd like me to extend work to Protobuf extension or leave that returning a HTTP.Messages.Response.

@ndortega

ndortega commented Apr 24, 2025

Copy link
Copy Markdown
Member

@bduncanj
Sorry about the late reply

I'm a big fan of using type parameters to help simplify code, so that approach sounds good to me. But there is one constraint: consistent api - whatever pattern we go with should alos be used in our package extensions.

So to answer your question, I'd like these changes to also extend to the protobuf extension

@Euraxluo

Euraxluo commented Jun 19, 2025

Copy link
Copy Markdown

I use a method of modifying templates and creating macros to obtain the return type, and it works.

I think the return type should be obtainable from the macro.

https://github.com/Juliaoscode/JuliaOS/pull/84

image image image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants