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

Creating aliases outside the context of the original struct #790

Open
ctron opened this issue Oct 31, 2023 · 9 comments
Open

Creating aliases outside the context of the original struct #790

ctron opened this issue Oct 31, 2023 · 9 comments

Comments

@ctron
Copy link
Contributor

ctron commented Oct 31, 2023

Assuming I have some struct:

pub struct SearchResult<T> {};

Which I use throughout different APIs. that makes it impossible to use the #[alias], as it doesn't know anything about the downstream types. And even does not have access to them.

I tried using an simple pub type SearchResult = common::SearchResult<Foo>, using it as part of the actix function, as well as registering it to the OpenAPI schema. However, this again results in an object referencing T:

      properties:
        result:
          $ref: '#/components/schemas/T'
@ctron
Copy link
Contributor Author

ctron commented Oct 31, 2023

The docs say:

The #[aliases(...)] is just syntactic sugar and will create Rust type aliases behind the scenes which then can be later referenced anywhere in code.

That doesn't seem to be the case, as creating a manual type alias and using that instead doesn't work. It seems to take the name, but then falls back to the same problem.

@ctron
Copy link
Contributor Author

ctron commented Oct 31, 2023

I tried with:

#[derive(ToSchema)]
pub struct SearchResultFoo(SearchResult<Foo>>);

However, that only creates a direct ref to SearchResult again. Falling back o the original problem.

And it doesn't look like there's an inline option for the newtype approach. Maybe that would be the resolution to the issue.

@ctron
Copy link
Contributor Author

ctron commented Oct 31, 2023

Actually I would expect this to work, but it doesn't:

pub struct SearchResultFoo(SearchResult<Vec<Foo>>);

impl<'__s> ToSchema<'__s> for SearchResultFoo {
    fn schema() -> (&'__s str, RefOr<Schema>) {
        ("SearchResultFoo", schema!(#[inline] SearchResult<Vec<Foo>>))
    }
}

@ctron
Copy link
Contributor Author

ctron commented Oct 31, 2023

I would expect this to work too:

#[derive(ToSchema)]
#[aliases(SearchResultFoo = LocalSearchResult<Vec<Foo>>)]
pub struct LocalSearchResult<T>(SearchResult<T>);

However that fails to compile with:

error[E0277]: the trait bound `Schema: std::convert::From<utoipa::openapi::Ref>` is not satisfied
  --> my.rs:11:10
   |
11 | #[derive(ToSchema)]
   |          ^^^^^^^^ the trait `std::convert::From<utoipa::openapi::Ref>` is not implemented for `Schema`
   |

@ctron
Copy link
Contributor Author

ctron commented Oct 31, 2023

The only thing that seems to work is to copy the type into the downstream creates … which is far from optimal.

@juhaku
Copy link
Owner

juhaku commented Oct 31, 2023

Rust's type aliases are quite tricky because because they are not real types thus no traits can be applied directly to them. More over when the proc macro is compiled it reads the available source code as token stream and there is no way of evaluating at compile time what would be the actual type of a rust type alias that I know.

I know there is an existing issue that suggests creating a "global" register for type aliases that utoipa could leverage when it is compiled by looking for a registered schema by the type alias. Something like this could be possibly added in future. Also to this one PRs are wellcome. 🙂

Now to implement schema for a third party type, there are basically 3 options.

  1. Create internal type that is equivalent to the external type and implement ToSchema for it
  2. Implement ToSchema manually via SchemaBuilder and modify the schema before using by adding the manually created schema to it.
  3. Create dummy struct that and manually implement ToSchema for it in a way that the schema represents the external type structure. (Basically a combination of a 1 & 2 option)

The 1. option is a quite close to the serde's with attribute. As elaborated here more: https://serde.rs/remote-derive.html the same principles matter.

More about this topic here: #656 (comment) #507 #390

@ctron
Copy link
Contributor Author

ctron commented Nov 2, 2023

What about adding an alias/with/external attribute to the OpenApi derive? Where you can add an arbitrary schema created by an fn under a certain name.

@juhaku
Copy link
Owner

juhaku commented Nov 8, 2023

What about adding an alias/with/external attribute to the OpenApi derive? Where you can add an arbitrary schema created by an fn under a certain name.

If it is an arbitrary schema it has a type of a Schema most likely when it is returned from the function. To add it to the OpenApi one must edit the OpenApi manually e.g. with the Modify trait or directly calling the functions of the OpenApi struct before it is consumed by the SwaggerUi or any other struct or function. The ComponentsBuilder accepts anything that can be converted to the Schema as seen here:

pub fn schema<S: Into<String>, I: Into<RefOr<Schema>>>(mut self, name: S, schema: I) -> Self {

The Components can be converted easily to the ComponentsBuilder with .into() call. Otherwise you can directly access the fields of the Components struct and modify them.

At the moment if I am not mistaken the OpenApi derive macro needs a ToSchema trait implementation.

@JakkuSakura
Copy link

I tried the "global" register for type aliases in my other project but it didn't work.

Rust makes no gaurentee that one process will handle all the proc macro invocations. From my observation, a process is spawned for EACH invocation.

To make it work, a file db like SQLite or shared memory is needed

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

No branches or pull requests

3 participants