Derive Macro utoipa::ToSchema

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

Generate reusable OpenAPI schema to be used together with OpenApi.

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.

Doc comments on fields will resolve to field descriptions in generated OpenAPI doc. On struct level doc comments will resolve to object descriptions.

/// This is a pet
#[derive(utoipa::ToSchema)]
struct Pet {
    /// Name for your pet
    name: String,
}

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

  • example = ... Can be json!(...). json!(...) should be something that serde_json::json! can parse as a serde_json::Value.
  • xml(...) Can be used to define Xml object properties applicable to Structs.
  • title = ... Literal string value. Can be used to define title for struct in OpenAPI document. Some OpenAPI code generation libraries also use this field as a name for the struct.
  • rename_all = ... Supports same syntax as serde rename_all attribute. Will rename all fields of the structs accordingly. If both serde rename_all and schema rename_all are defined serde will take precedence.
  • as = ... Can be used to define alternative path and name for the schema what will be used in the OpenAPI. E.g as = path::to::Pet. This would make the schema appear in the generated OpenAPI spec as path.to.Pet.

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

  • example = ... Can be method reference or json!(...).
  • default = ... Can be method reference or json!(...).
  • title = ... Literal string value. Can be used to define title for enum in OpenAPI document. Some OpenAPI code generation libraries also use this field as a name for the enum. Note! Complex enum (enum with other than unit variants) does not support title!
  • rename_all = ... Supports same syntax as serde rename_all attribute. Will rename all variants of the enum accordingly. If both serde rename_all and schema rename_all are defined serde will take precedence.
  • as = ... Can be used to define alternative path and name for the schema what will be used in the OpenAPI. E.g as = path::to::Pet. This would make the schema appear in the generated OpenAPI spec as path.to.Pet.

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

Supports all variant specific configuration options e.g. if variant is UnnamedStruct then unnamed struct type configuration options are supported.

In addition to the variant type specific configuration options enum variants support custom rename attribute. It behaves similarly to serde’s rename attribute. If both serde rename and schema rename are defined serde will take precedence.

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

  • example = ... Can be method reference or json!(...).
  • default = ... Can be method reference or json!(...).
  • format = ... May either be variant of the KnownFormat enum, or otherwise an open value as a string. 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. Object will be rendered as generic OpenAPI object (type: object).
  • title = ... Literal string value. Can be used to define title for struct in OpenAPI document. Some OpenAPI code generation libraries also use this field as a name for the struct.
  • as = ... Can be used to define alternative path and name for the schema what will be used in the OpenAPI. E.g as = path::to::Pet. This would make the schema appear in the generated OpenAPI spec as path.to.Pet.

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

  • example = ... Can be method reference or json!(...).
  • default = ... Can be method reference or json!(...).
  • format = ... May either be variant of the KnownFormat enum, or otherwise an open value as a string. 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. See configuration options at xml attributes of ToSchema
  • 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. Object will be rendered as generic OpenAPI object (type: 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!
  • nullable Defines property is nullable (note this is different to non-required).
  • rename = ... Supports same syntax as serde rename attribute. Will rename field accordingly. If both serde rename and schema rename are defined serde will take precedence.
  • multiple_of = ... Can be used to define multiplier for a value. Value is considered valid division will result an integer. Value must be strictly above 0.
  • maximum = ... Can be used to define inclusive upper bound to a number value.
  • minimum = ... Can be used to define inclusive lower bound to a number value.
  • exclusive_maximum = ... Can be used to define exclusive upper bound to a number value.
  • exclusive_minimum = ... Can be used to define exclusive lower bound to a number value.
  • max_length = ... Can be used to define maximum length for string types.
  • min_length = ... Can be used to define minimum length for string types.
  • pattern = ... Can be used to define valid regular expression in ECMA-262 dialect the field value must match.
  • max_items = ... Can be used to define maximum items allowed for array fields. Value must be non-negative integer.
  • min_items = ... Can be used to define minimum items allowed for array fields. Value must be non-negative integer.
  • with_schema = ... Use schema created by provided function reference instead of the default derived schema. The function must match to fn() -> Into<RefOr<Schema>>. It does not accept arguments and must return anything that can be converted into RefOr<Schema>.

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 at the container level.
  • rename = "..." Supported only at the field or variant level.
  • skip = "..." Supported only at the field or variant level.
  • skip_serializing = "..." Supported only at the field or variant level.
  • tag = "..." Supported at the container level. tag attribute works as a discriminator field for an enum.
  • content = "..." Supported at the container level, allows adjacently-tagged enums. This attribute requires that a tag is present, otherwise serde will trigger a compile-time failure.
  • untagged Supported at the container level. Allows untagged enum representation.
  • default Supported at the container level and field level according to serde attributes.
  • flatten Supported at the field 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>>
    },
}

Add serde default attribute for MyValue struct. Similarly default could be added to individual fields as well. If default is given the field’s affected will be treated as optional.

 #[derive(utoipa::ToSchema, serde::Deserialize, Default)]
 #[serde(default)]
 struct MyValue {
     field: String
 }

#[repr(...)] attribute support

Serde repr allows field-less enums be represented by their numeric value.

  • repr(u*) for unsigned integer.
  • repr(i*) for signed integer.

Supported schema attributes

  • example = ... Can be method reference or json!(...).
  • default = ... Can be method reference or json!(...).
  • title = ... Literal string value. Can be used to define title for enum in OpenAPI document. Some OpenAPI code generation libraries also use this field as a name for the enum. Note! Complex enum (enum with other than unit variants) does not support title!
  • as = ... Can be used to define alternative path and name for the schema what will be used in the OpenAPI. E.g as = path::to::Pet. This would make the schema appear in the generated OpenAPI spec as path.to.Pet.

Create enum with numeric values.

#[derive(ToSchema)]
#[repr(u8)]
#[schema(default = default_value, example = 2)]
enum Mode {
    One = 1,
    Two,
 }

fn default_value() -> u8 {
    1
}

You can use skip and tag attributes from serde.

#[derive(ToSchema, serde::Serialize)]
#[repr(i8)]
#[serde(tag = "code")]
enum ExitCode {
    Error = -1,
    #[serde(skip)]
    Unknown = 0,
    Ok = 1,
 }

Generic schemas with aliases

Schemas can also be generic which allows reusing types. This enables certain behaviour patters where super type declares 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 syntactic 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

Simple example of a Pet with descriptions and object level example.

/// This is a pet.
#[derive(ToSchema)]
#[schema(example = json!({"name": "bob the cat", "id": 0}))]
struct Pet {
    /// Unique id of a pet.
    id: u64,
    /// Name of a pet.
    name: String,
    /// Age of a pet if known.
    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::KnownFormat(KnownFormat::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 (type: object) in OpenAPI spec.

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

Serde rename / rename_all will take precedence over schema rename / rename_all.

#[derive(utoipa::ToSchema, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
#[schema(rename_all = "UPPERCASE")]
enum Random {
    #[serde(rename = "string_value")]
    #[schema(rename = "custom_value")]
    String(String),

    Number {
        id: i32,
    }
}

Add title to the enum.

#[derive(utoipa::ToSchema)]
#[schema(title = "UserType")]
enum UserType {
    Admin,
    Moderator,
    User,
}

Example with validation attributes.

#[derive(utoipa::ToSchema)]
struct Item {
    #[schema(maximum = 10, minimum = 5, multiple_of = 2.5)]
    id: i32,
    #[schema(max_length = 10, min_length = 5, pattern = "[a-z]*")]
    value: String,
    #[schema(max_items = 5, min_items = 1)]
    items: Vec<String>,
}

Use schema_with to manually implement schema for a field.

fn custom_type() -> Object {
    ObjectBuilder::new()
        .schema_type(utoipa::openapi::SchemaType::String)
        .format(Some(utoipa::openapi::SchemaFormat::Custom(
            "email".to_string(),
        )))
        .description(Some("this is the description"))
        .build()
}

#[derive(utoipa::ToSchema)]
struct Value {
    #[schema(schema_with = custom_type)]
    id: String,
}

Use as attribute to change the name and the path of the schema in the generated OpenAPI spec.

 #[derive(utoipa::ToSchema)]
 #[schema(as = api::models::person::Person)]
 struct Person {
     name: String,
 }

More examples for value_type in IntoParams derive docs.