Skip to content
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

Add external ref(...) attribute #409

Merged
merged 1 commit into from
Dec 11, 2022
Merged

Add external ref(...) attribute #409

merged 1 commit into from
Dec 11, 2022

Conversation

juhaku
Copy link
Owner

@juhaku juhaku commented Dec 11, 2022

Add external ref(...) attribute for response body and reqeust body. This allows to use external json file schema for request body or reponse body.

This PR will not add any support for giving guarantees of accessibility of the externally referenced json value. Those guaratees are deemed to be done by the user.

This commit will add support for following syntax.

 // external ref on request body
 #[utoipa::path(get, path = "/item", request_body(content = ref("./MyUser.json")))]
 #[allow(dead_code)]
 fn get_item() {}

 // external ref on response body
 #[utoipa::path(
     get,
     path = "/foo",
     responses(
         (status = 200, body = ref("./MyUser.json"))
     )
 )]
 #[allow(unused)]
 fn get_item() {}

Resolves #242

Add external ref(...) attribute for response body and reqeust body. This
allows to use external json file schema for request body or reponse
body.

This PR will not add any support for giving guarantees of accessibility
of the externally referenced json value. Those guaratees are deemed to
be done by the user.

This commit will add support for following syntax.
```rust
 // external ref on request body
 #[utoipa::path(get, path = "/item", request_body(content = ref("./MyUser.json")))]
 #[allow(dead_code)]
 fn get_item() {}

 // external ref on response body
 #[utoipa::path(
     get,
     path = "/foo",
     responses(
         (status = 200, body = ref("./MyUser.json"))
     )
 )]
 #[allow(unused)]
 fn get_item() {}
```
@juhaku juhaku force-pushed the feature-external-ref branch from 0cc693c to 79bc7db Compare December 11, 2022 20:29
@juhaku juhaku merged commit 71f46ec into master Dec 11, 2022
@juhaku juhaku deleted the feature-external-ref branch December 11, 2022 20:35
@xlambein
Copy link

Quick question: does this PR make it possible to specify the reference as a Rust computation? I'm trying to find a way to do something like this:

request_body(content = ref(format!("{}#/defs/SomeExternalSchema", EXTERNAL_API_SPEC))

but it does not compile. Back in ye ole days of Utoipa 1, I used to be able to define a custom component that would produce such a Ref, but that doesn't seem possible anymore.

@juhaku
Copy link
Owner Author

juhaku commented Dec 12, 2022

No it does not allow. It currently only expects to be literal string. Though support for this could be added.

Back in ye ole days of Utoipa 1, I used to be able to define a custom component that would produce such a Ref, but that doesn't seem possible anymore.

If it was possible, it might have been bug back then 😅. I don't remember implementing support for it.

@juhaku
Copy link
Owner Author

juhaku commented Dec 12, 2022

Getting back to this. By the desing that has existed from the beginning it seems not be possible though.

When we define type like this

let variable = "MyDefinition.json";
#[utoipa::path(get, path = "/item", request_body(content = ref(format!("{}/#User", variable))))]
fn get_item() {}

We get following type generation and this leads to error show below. Basically the variable is accessible from the generated structs since they live in totally different context.

let variable = "MyDefinition.json";

pub struct __path_get_item;
impl utoipa::Path for __path_get_item {
    fn path() -> &'static str {
        "/item"
    }
    fn path_item(default_tag: Option<&str>) -> utoipa::openapi::path::PathItem {
        utoipa::openapi::PathItem::new(
            utoipa::openapi::PathItemType::Get,
            utoipa::openapi::path::OperationBuilder::new()
                .request_body(Some(
                    utoipa::openapi::request_body::RequestBodyBuilder::new()
                        .content(
                            "application/json",
                            utoipa::openapi::content::ContentBuilder::new()
                                .schema(utoipa::openapi::schema::Ref::new(format!(
                                    "{}/#User",
                                     variable
                                )))
                                .build(),
                        )
                        .build(),
                )),
        )
    }
}

image

However hope is not totally lost. Currently there is function called schema_with for ToSchema named fields what allows to override the schema in compile time programmatically for specific field. If we add similar functionality to override the body programmatically then it might become possible.

fn my_fn() -> RequestBody {
  let variable = "MyDefinition";
   RequestBodyBuilder::new().content(Content::new(Ref::new(format!("{}", variable)))).build()
}

#[utoipa::path(get, path = "/item", request_body(body_with = my_fn)]
fn get_item() {}

But with this as well it's not all roces, the function would be accpeting no arguments and must be available for utoipa::path to call. This indicates that the variable there either must be local to the function or it must be const.

@xlambein
Copy link

If it was possible, it might have been bug back then

To be clear: I don't think it was possible with macros. This is how I used to be able to have a ref in my spec with utoipa version 1.*:

impl utoipa::Component for SomeExternalSchema {
    fn component() -> utoipa::openapi::schema::Component {
        utoipa::openapi::schema::Ref::new(format!(
            "{}#/$defs/SomeExternalSchema",
            EXTERNAL_API_SPEC
        ))
        .into()
    }
}

#[derive(OpenApi)]
#[openapi(handlers(...), components(SomeExternalSchema))]
pub struct ApiDoc;

must be local to the function or it must be const

For my use case this is OK: I have a constant variable that points to the external JSON API, pinned to a specific version. I just need to format! it to append the specific schema (#/$defs/SomeExternalSchema), and that should work 🤔

@juhaku
Copy link
Owner Author

juhaku commented Dec 12, 2022

Right, this is also possible now as well. The definition of component trait is just changed a bit.

This is is how it could be achieved with code against master branch (unrelased yet).

impl utoipa::ToSchema for SomeExternalSchema {
  fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
    utoipa::openapi::RefOr::Ref(utoipa::openapi::schema::Ref::new(format!(
            "{}#/$defs/SomeExternalSchema",
            EXTERNAL_API_SPEC
        )))
  }
}

@juhaku
Copy link
Owner Author

juhaku commented Dec 12, 2022

True, currently in latest release the definition of ToSchema is as seen below. And Ref is not part of Schema.

trait ToSchema {
  fn schema() -> utoipa::openapi::schema::ToSchema;
}

This makes it indeed impossible to implement now in 2 serie unfortunately. However the next release what will be 3 will fix it by adding RefOr<Schema> to the definition.

@xlambein
Copy link

I'll be looking forward to that release then :] Thanks for the help & for making this library!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Released
Development

Successfully merging this pull request may close these issues.

Specify schema by reference
2 participants