-
Notifications
You must be signed in to change notification settings - Fork 86
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
utoipa proof of concept on orderbook endpoint #2338
utoipa proof of concept on orderbook endpoint #2338
Conversation
CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅ |
I have read the CLA Document and I hereby sign the CLA |
recheck |
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 for taking a crack at this! This will probably make our lives a whole lot easier long term.
Good thing you started with this endpoint. It's probably the most complex and shows most of the challenges that will have to be tackled along the way.
I think we might have to introduce a new translation layer that turns a struct that is purely there for defining the API into a struct that is used throughout the code base. For the least amount of churn the used struct should probably stay the current order struct. Then the new type can be more geared towards optimal support for utoipa.
As a follow up idea it would be cool if we could have a CI job that checks whether some PR changed a public facing API so we don't have changes by accident.
crates/model/src/order.rs
Outdated
#[serde(rename_all = "camelCase")] | ||
#[schema(example = json!({ |
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.
Does it get checked that this type actually satisfies the interface during compilation?
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.
I think it would be better to specify example value closer to the field or even type. Currently it would be a bit difficult to maintain any changes.
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.
Is this even necessary, given that you specify example values in each field's definition?
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 json structure was not type checked. I agree with the group that it's better to add example values on each fields definition.
I couldn't get example types on each field definition to work previously but now I've determined its cause is the serde flattened
types. Will investigate further.
crates/model/src/order.rs
Outdated
#[serde_as(as = "HexOrDecimalU256")] | ||
#[schema(value_type = String)] | ||
pub sell_amount: U256, |
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.
For things like U256
and H160
I think it makes sense to add a new type that implements ToSchema
and the sometimes custom deserialization logic. This will be annoying for the types in the orderbook because we used the API types in the business logic but eventually we should have a separation of concerns and the introduction of utoipa seems like a good option.
request_body = OrderCreation, | ||
responses( | ||
(status = 200, description = "Order has been accepted.", body = OrderUid), | ||
(status = 400, description = "Error during order validation."), |
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.
Is it possible to define this on an error struct returned by this endpoint instead?
That way it could probably be enforced that every error must have a description.
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.
Maybe we can implement https://docs.rs/utoipa/latest/utoipa/trait.IntoResponses.html on AddOrderError
crates/orderbook/src/openapi.rs
Outdated
let builder: OpenApiBuilder = orderbook::api::ApiDoc::openapi().into(); | ||
let doc = builder.servers(Some(servers)).info(info).build(); | ||
|
||
println!("{}", doc.to_pretty_json().unwrap()); |
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.
Currently, we need this file for debugging only?
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.
Should be to_yaml
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.
@squadgazzz I assume there is an offline process for hosting the openapi yaml file on the website, this binary is used to produce that file. This could be automated somehow at build time in build.rs
or with CI jobs.
In the future there is warp / swagger integration which could be explored to actually host swagger from the rest service but I've left this as follow up work.
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 is a really nice contribution already!
There are some small inconsistencies (maybe you can submit the generated yml file in the PR for ease of review). It would be great if we can move the response code definitions closer to where they actually get created.
Already for the struct definitions (which change more frequently) this is a great improvement.
crates/model/src/order.rs
Outdated
/// If set, the backend enforces that this address matches what is decoded | ||
/// as the *signer* of the signature. This helps catch errors with | ||
/// invalid signature encodings as the backend might otherwise silently | ||
/// work with an unexpected address that for example does not have | ||
/// any balance. |
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.
If you look at the generated yaml, for some reason the flattened
types don't show up as named arguments on the top of OrderCreation
but instead unnamed in the allOf
definition. Any idea why that is?
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 seems to cause a bigger issue, the field attributes (ie. "example") on the flattened type seem to overwrite the parent types definition, investigating further.
crates/model/src/order.rs
Outdated
#[serde(rename_all = "camelCase")] | ||
#[schema(example = json!({ |
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.
Is this even necessary, given that you specify example values in each field's definition?
crates/orderbook/src/openapi.rs
Outdated
let builder: OpenApiBuilder = orderbook::api::ApiDoc::openapi().into(); | ||
let doc = builder.servers(Some(servers)).info(info).build(); | ||
|
||
println!("{}", doc.to_pretty_json().unwrap()); |
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.
Should be to_yaml
request_body = OrderCreation, | ||
responses( | ||
(status = 200, description = "Order has been accepted.", body = OrderUid), | ||
(status = 400, description = "Error during order validation."), |
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.
Maybe we can implement https://docs.rs/utoipa/latest/utoipa/trait.IntoResponses.html on AddOrderError
Added some small fixes, continuing with the following bigger items in priority order:
|
This pull request has been marked as stale because it has been inactive a while. Please update this pull request or it will be automatically closed. |
Changes:
Known issues:
|
I think this is in line with our goal of phasing out the use of the shared model's crate type. Creating a stable API representation and letting the business logic use their own domain types going forward (eventually removing the models crate altogether) is something we've talked about internally. |
lint failure looks to be due to local-node errors look genuine, as related to the changed endpoint - I'll reproduce this locally today. forked-node - not sure about these looks like connectivity issues. |
These errors are due to the fact that this PR originated from a forked repo and github doesn't allow the use of secrets in workflows for those. Additionally these tests are known to not be 100% stable. 😅 |
Try checking in your |
Committed After reviewing the I don't like using the same name for the two objects (utoipa friendly & complex internal) but If we're going to change one its going to need to be the internal type (otherwise it'll be a breaking change, forcing changes on API callers). |
This pull request has been marked as stale because it has been inactive a while. Please update this pull request or it will be automatically closed. |
I've opened an issue with utoipa to try and figure out how to quote H160 address strings so they are not interpreted as decimal numbers by swagger: juhaku/utoipa#875 |
…/cowprotocol-services into utoipa-auto-create-openapi-yml
This pull request has been marked as stale because it has been inactive a while. Please update this pull request or it will be automatically closed. |
Description
Proof of concept usage of
utoipa
to automatically generate openapi yaml files for 1 orderbook api endpoint for the issue here: #2203.Looking for feedback from core dev team before progression.
Changes
utoipa
crate.create_order
endpoint and dependent structs.ApiDoc
struct insideapi.rs
to reference / describe the endpoint(s) via the annotations.openapi
binary which referencesapi::ApiDoc
and prints the openapi schema to stdout (to be included in release automation later).How to test
Notes
#[schema(value_type = XXX)]
annotation.