Derive Macro utoipa::ToSchema

source · []
#[derive(ToSchema)]
{
    // Attributes available to this derive:
    #[schema]
    #[aliases]
}
Expand description

ToSchema derive macro.

This is #[derive] implementation for ToSchema trait. The macro accepts one schema attribute optionally which can be used to enhance generated documentation. The attribute can be placed at item level or field level in struct and enums. Currently placing this attribute to unnamed field does not have any effect.

You can use the Rust’s own #[deprecated] attribute on any struct, enum or field to mark it as deprecated and it will reflect to the generated OpenAPI spec.

#[deprecated] attribute supports adding additional details such as a reason and or since version but this is is not supported in OpenAPI. OpenAPI has only a boolean flag to determine deprecation. While it is totally okay to declare deprecated with reason #[deprecated = "There is better way to do this"] the reason would not render in OpenAPI spec.

Struct Optional Configuration Options for #[schema(...)]

  • example = ... Can be either json!(...) or literal string that can be parsed to json. json! should be something that serde_json::json! can parse as a serde_json::Value. 1
  • xml(...) Can be used to define Xml object properties applicable to Structs.

Enum Optional Configuration Options for #[schema(...)]

  • example = ... Can be literal value, method reference or json!(...). 2
  • default = ... Can be literal value, method reference or json!(...). 2

Unnamed Field Struct Optional Configuration Options for #[schema(...)]

  • example = ... Can be literal value, method reference or json!(...). 2
  • default = ... Can be literal value, method reference or json!(...). 2
  • format = ... Any variant of a SchemaFormat to use for the property. By default the format is derived from the type of the property according OpenApi spec.
  • value_type = ... Can be used to override default type derived from type of the field used in OpenAPI spec. This is useful in cases where the default type does not correspond to the actual type e.g. when any third-party types are used which are not ToSchemas nor primitive types. Value can be any Rust type what normally could be used to serialize to JSON or custom type such as Object.

Named Fields Optional Configuration Options for #[schema(...)]

  • example = ... Can be literal value, method reference or json!(...). 2
  • default = ... Can be literal value, method reference or json!(...). 2
  • format = ... Any variant of a SchemaFormat to use for the property. By default the format is derived from the type of the property according OpenApi spec.
  • write_only Defines property is only used in write operations POST,PUT,PATCH but not in GET
  • read_only Defines property is only used in read operations GET but not in POST,PUT,PATCH
  • xml(...) Can be used to define Xml object properties applicable to named fields.
  • value_type = ... Can be used to override default type derived from type of the field used in OpenAPI spec. This is useful in cases where the default type does not correspond to the actual type e.g. when any third-party types are used which are not ToSchemas nor primitive types. Value can be any Rust type what normally could be used to serialize to JSON or custom type such as Object.
  • inline If the type of this field implements ToSchema, then the schema definition will be inlined. warning: Don’t use this for recursive data types!

Xml attribute Configuration Options

  • xml(name = "...") Will set name for property or type.
  • xml(namespace = "...") Will set namespace for xml element which needs to be valid uri.
  • xml(prefix = "...") Will set prefix for name.
  • xml(attribute) Will translate property to xml attribute instead of xml element.
  • xml(wrapped) Will make wrapped xml element.
  • xml(wrapped(name = "wrap_name")) Will override the wrapper elements name.

See Xml for more details.

Partial #[serde(...)] attributes support

ToSchema derive has partial support for serde attributes. These supported attributes will reflect to the generated OpenAPI doc. For example if #[serde(skip)] is defined the attribute will not show up in the OpenAPI spec at all since it will not never be serialized anyway. Similarly the rename and rename_all will reflect to the generated OpenAPI doc.

  • rename_all = "..." Supported in container level.
  • rename = "..." Supported only in field or variant level.
  • skip = "..." Supported only in field or variant level.
  • tag = "..." Supported in container level.

Other serde attributes works as is but does not have any effect on the generated OpenAPI doc.

Note! tag attribute has some limitations like it cannot be used with unnamed field structs and tuple types. See more at enum representation docs.

#[derive(Serialize, ToSchema)]
struct Foo(String);

#[derive(Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
enum Bar {
    UnitValue,
    #[serde(rename_all = "camelCase")]
    NamedFields {
        #[serde(rename = "id")]
        named_id: &'static str,
        name_list: Option<Vec<String>>
    },
    UnnamedFields(Foo),
    #[serde(skip)]
    SkipMe,
}

Add custom tag to change JSON representation to be internally tagged.

#[derive(Serialize, ToSchema)]
struct Foo(String);

#[derive(Serialize, ToSchema)]
#[serde(tag = "tag")]
enum Bar {
    UnitValue,
    NamedFields {
        id: &'static str,
        names: Option<Vec<String>>
    },
}

Generic schemas with aliases

Schemas can also be generic which allows reusing types. This enables certain behaviour patters where super type delcares common code for type aliases.

In this example we have common Status type which accepts one generic type. It is then defined with #[aliases(...)] that it is going to be used with std::string::String and i32 values. The generic argument could also be another ToSchema as well.

#[derive(ToSchema)]
#[aliases(StatusMessage = Status<String>, StatusNumber = Status<i32>)]
struct Status<T> {
    value: T
}

#[derive(OpenApi)]
#[openapi(
    components(schemas(StatusMessage, StatusNumber))
)]
struct ApiDoc;

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

Note! You should never register generic type itself in components(...) so according above example Status<...> should not be registered because it will not render the type correctly and will cause an error in generated OpenAPI spec.

Examples

Example struct with struct level example.

#[derive(ToSchema)]
#[schema(example = json!({"name": "bob the cat", "id": 0}))]
struct Pet {
    id: u64,
    name: String,
    age: Option<i32>,
}

The schema attribute can also be placed at field level as follows.

#[derive(ToSchema)]
struct Pet {
    #[schema(example = 1, default = 0)]
    id: u64,
    name: String,
    age: Option<i32>,
}

You can also use method reference for attribute values.

#[derive(ToSchema)]
struct Pet {
    #[schema(example = u64::default, default = u64::default)]
    id: u64,
    #[schema(default = default_name)]
    name: String,
    age: Option<i32>,
}

fn default_name() -> String {
    "bob".to_string()
}

For enums and unnamed field structs you can define schema at type level.

#[derive(ToSchema)]
#[schema(example = "Bus")]
enum VehicleType {
    Rocket, Car, Bus, Submarine
}

Also you write complex enum combining all above types.

#[derive(ToSchema)]
enum ErrorResponse {
    InvalidCredentials,
    #[schema(default = String::default, example = "Pet not found")]
    NotFound(String),
    System {
        #[schema(example = "Unknown system failure")]
        details: String,
    }
}

It is possible to specify the title of each variant to help generators create named structures.

#[derive(ToSchema)]
enum ErrorResponse {
    #[schema(title = "InvalidCredentials")]
    InvalidCredentials,
    #[schema(title = "NotFound")]
    NotFound(String),
}

Use xml attribute to manipulate xml output.

#[derive(ToSchema)]
#[schema(xml(name = "user", prefix = "u", namespace = "https://user.xml.schema.test"))]
struct User {
    #[schema(xml(attribute, prefix = "u"))]
    id: i64,
    #[schema(xml(name = "user_name", prefix = "u"))]
    username: String,
    #[schema(xml(wrapped(name = "linkList"), name = "link"))]
    links: Vec<String>,
    #[schema(xml(wrapped, name = "photo_url"))]
    photos_urls: Vec<String>
}

Use of Rust’s own #[deprecated] attribute will reflect to generated OpenAPI spec.

#[derive(ToSchema)]
#[deprecated]
struct User {
    id: i64,
    username: String,
    links: Vec<String>,
    #[deprecated]
    photos_urls: Vec<String>
}

Enforce type being used in OpenAPI spec to String with value_type and set format to octet stream with SchemaFormat::Binary.

#[derive(ToSchema)]
struct Post {
    id: i32,
    #[schema(value_type = String, format = Binary)]
    value: Vec<u8>,
}

Enforce type being used in OpenAPI spec to String with value_type option.

#[derive(ToSchema)]
#[schema(value_type = String)]
struct Value(i64);

Override the Bar reference with a custom::NewBar reference.

#[derive(ToSchema)]
struct Value {
    #[schema(value_type = custom::NewBar)]
    field: Bar,
};

Use a virtual Object type to render generic object in OpenAPI spec.

#[derive(ToSchema)]
struct Value {
    #[schema(value_type = Object)]
    field: Bar,
};

More examples for value_type in IntoParams derive docs.


  1. json feature need to be enabled for json!(...) type to work. 

  2. Values are converted to string if json feature is not enabled.