tamasfe / aide

An API documentation library
Apache License 2.0
417 stars 70 forks source link

How to implement JsonSchema for other types? #131

Closed jansedlon closed 7 months ago

jansedlon commented 7 months ago

Hi!

Just a disclosure, I'm exploring Rust, if there's something obvious, I probably don't see it.

Is there an example for how implement JsonSchema for custom types?

I have this struct that's returned as a part of Axum response and JsonSchema is not implemented for sqlx::types::Json

#[derive(Serialize, FromRow, Debug, JsonSchema)]
struct Store {
    id: String,
    theme_id: String,
    display_name: String,
    status: StoreStatus,
    slug: String,
    gdpr: Option<String>,
    gdpr_enabled: bool,
    theme_overrides: sqlx::types::Json<StoreTheme>,
    // the trait bound `sqlx::types::Json<StoreTheme>: JsonSchema` is not satisfied
    owner_id: String,
    owner_profile_picture: Option<String>,
    owner_social_links: sqlx::types::JsonValue,
    owner_name: String,
    owner_username: String,
    owner_bio: Option<String>,
}

Thank you for any suggestions!

Wicpar commented 7 months ago

you have two options here:

  1. do not use the sqlx json type except when you insert extract into the database (easiest)
  2. create a newtype that wraps it and implements jsonschema

Your issues is more one of how to use sqlx. you don't need FromRow to use the query_as! macro, and generally you don't directly query the struct for things like this. using it like this might be easier

query!("select a as "a: sqlx::types::Json<MyA>" from ...").map(|rec| Mystruct { a: rec.a.0 } ).fetch_one(db).await
tamasfe commented 7 months ago

You can also override the type schemars generates for a field:

#[derive(Serialize, FromRow, Debug, JsonSchema)]
struct Store {
    #[schemars(with = "StoreTheme")]
    theme_overrides: sqlx::types::Json<StoreTheme>,
}

It's convenient, but it's also easy to forget to update the attribute if you change the field's type.

As a side note, I'd rather not have any internal types (e.g. used by database queries) in API request/response types, and would totally separate the two, even duplicating every field and creating From<...> conversions if needed. This way the API won't depend on internal libraries and you are less likely to leak implementation details as well.