AsSchema

Derive Macro AsSchema 

Source
#[derive(AsSchema)]
{
    // Attributes available to this derive:
    #[schema]
}
Expand description

Derive macro for AsSchema trait.

§Implementation Notes

§1. Description Concatenation

#[derive(AsSchema)]
#[schema(description = "Top description ")]
#[schema(description = "continuation")]
struct Type {
    #[schema(description = "field description ")]
    #[schema(description = "continuation")]
    field: String,
}

assert_eq!(
    Type::as_schema(),
    Schema {
        description: "Top description continuation".to_owned(),
        r#type: SchemaType::Object as i32,
        properties: [(
            "field".to_owned(),
             Schema {
                description: "field description continuation".to_owned(),
                ..String::as_schema()
             }
        )].into(),
        required: ["field".to_owned()].into(),
        ..Default::default()
    }
)

§1. Custom Schema Generation

as_schema - Direct schema override:

#[derive(AsSchema)]
struct Timestamp {
    #[schema(as_schema = "datetime_schema")]
    millis: i64,
}

/// Simple schema function signature
fn datetime_schema() -> Schema {
    ...
}

as_schema_generic - Handle generic types:

use std::marker::PhantomData;

struct Wrapper<T> {
    inner: T,
}

#[derive(AsSchema)]
struct Data {
    #[schema(as_schema_generic = "wrapper_schema")]
    field: Wrapper<String>,
}

fn wrapper_schema<T: AsSchema>() -> (Schema, PhantomData<Wrapper<T>>) {
    ...
}

// NOTE: For enums with struct data:
#[derive(AsSchema)]
enum E {
    #[schema(as_schema_generic = "variant1_schema")]
    Variant {a: String, b: String},
}

// Notice that the return is a tuple
fn variant1_schema() -> (Schema, PhantomData<(String, String)>) {
    ...
}

// Although it is more ideal to use ordinary as_schema in this example.

§2. Name Transformation

rename_all vs rename_all_with:

#[derive(AsSchema)]
#[schema(rename_all = "snake_case")]  // Built-in conventions
struct SimpleCase { /* ... */ }

#[derive(AsSchema)]
#[schema(rename_all_with = "reverse_names")]  // Custom function
struct CustomCase {
    field_one: String, // Becomes "eno_dleif"
}

/// Must be deterministic pure function
fn reverse_names(s: &str) -> String {
    s.chars().rev().collect()
}

§3. Type Handling Nuances

Tuples:

struct SimpleTuple(u32);          // → Transparent wrapper
struct MixedTuple(u32, String);   // → Array with unspecified items
struct UniformTuple(u32, u32);    // → Array with item(u32 here) schema

For more control, use AsSchemaWithSerde.

Enums:

  • Data-less enums become string enums
#[derive(AsSchema)]
enum Status {
    Active,
    Inactive
}

assert_eq!(
    Status::as_schema(),
    Schema {
        r#type: SchemaType::String as i32,
        format: "enum".to_owned(),
        r#enum: ["Active".to_owned(), "Inactive".to_owned()].into(),
        ..Default::default()
    }
)
  • Data-containing enums become structural objects with all fields unrequired by default. Return matches serde deserialization.
#[derive(AsSchema)]
enum Response {
    Success { data: String },
    Error(String),
}

assert_eq!(
    Response::as_schema(),
    Schema {
        r#type: SchemaType::Object as i32,
        properties: [
            (
                "Success".to_owned(),
                Schema {
                    r#type: SchemaType::Object as i32,
                    properties: [("data".to_owned(), String::as_schema())].into(),
                    required: ["data".to_owned()].into(),
                    ..Default::default()
                }
            ),
            ("Error".to_owned(), String::as_schema())
        ].into(),
        required: vec![], // None is required by default
        ..Default::default()
    }
)