kurtbuilds / openapiv3

Rust Open API v3 Structs and Enums for easy deserialization with serde

Home Page:https://crates.io/crates/openapiv3-extended

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Nullable enums don't parse appropriately

coriolinus opened this issue · comments

OpenAPI has a rule: for an enum to be nullable, it must explicitly include null within its enumeration. IMO this was a bad call, but it's well-documented as the rule. 1 2.

However, adding null to an enum's variant list forces the schema kind to parse as Any:

/*
[dependencies]
openapiv3 = { version = "2.2.1", package = "openapiv3-extended" }
serde_yaml = "0.9.22"
*/

use openapiv3::{OpenAPI, SchemaKind, Type};

const SCHEMA: &str = r#"
openapi: "3.0.3"
info:
  title: demo unexpected schema parse behavior
  version: "0.1.0"

paths: {}

components:
  schemas:
    Color:
      type: string
      nullable: true
      enum: [red, green, blue, null]
"#;

fn main() {
    let spec: OpenAPI = serde_yaml::from_str(SCHEMA).unwrap();
    let color_schema = spec
        .components
        .as_ref()
        .unwrap()
        .schemas
        .get("Color")
        .unwrap()
        .resolve(&spec);
    dbg!(&color_schema);
    assert!(matches!(
        &color_schema.schema_kind,
        SchemaKind::Type(Type::String(_))
    ));
}

This fails, with this output:

$ cargo run
Tue 27 Jun 2023 12:35:16 PM CEST
    Finished dev [unoptimized + debuginfo] target(s) in 0.09s
     Running `target/debug/demo-openapi-parse-fail`
[src/main.rs:36] &color_schema = Schema {
    schema_data: SchemaData {
        nullable: true,
        read_only: false,
        write_only: false,
        deprecated: false,
        external_docs: None,
        example: None,
        title: None,
        description: None,
        discriminator: None,
        default: None,
        extensions: {},
    },
    schema_kind: Any(
        AnySchema {
            typ: Some(
                "string",
            ),
            pattern: None,
            multiple_of: None,
            exclusive_minimum: None,
            exclusive_maximum: None,
            minimum: None,
            maximum: None,
            properties: {},
            required: [],
            additional_properties: None,
            min_properties: None,
            max_properties: None,
            items: None,
            min_items: None,
            max_items: None,
            unique_items: None,
            enumeration: [
                String("red"),
                String("green"),
                String("blue"),
                Null,
            ],
            format: None,
            min_length: None,
            max_length: None,
            one_of: [],
            all_of: [],
            any_of: [],
            not: None,
        },
    ),
}
thread 'main' panicked at 'assertion failed: matches!(& color_schema.schema_kind, SchemaKind :: Type(Type :: String(_)))', src/main.rs:37:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Given that schema authors are forced to include null among the enum variants, it's not helpful to lose the rest of the parse information by parsing as AnySchema. Instead, it would be more helpful to parse as Type::String (or whatever the type definition actually refers to) and simply strip the null variant from the variants list. We can still losslessly reconstruct the original schema from the OpenApi instance, because we have nullable: true and enumeration.is_some().