Skip to content

Client type constructors sometimes ignore bounds in OpenAPI spec #986

@taspelund

Description

@taspelund

I have a custom Rust type on the server side that wraps a u8 and limits its valid values to just 0-63. With a custom JsonSchema impl, these upper/lower bounds are encoded into the OpenAPI spec:

/// DSCP value for BGP TCP connections (0-63).
///
/// RFC 4271 Appendix E recommends CS6 (48) for BGP traffic.
/// Default: CS6 (48).
      634: #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
      635: #[serde(try_from = "u8", into = "u8")]
      636: pub struct Dscp(u8);

impl Dscp {
    /// CS6 (48) — the RFC 4271 Appendix E recommended default.
    pub const CS6: Self = Self(48);

    /// Create a new DSCP value, returning an error if out of range.
    pub fn new(val: u8) -> Result<Self, String> {
        if val > 63 {
            Err(format!("DSCP value {val} out of range (0-63)"))
        } else {
            Ok(Self(val))
        }
    }

    /// Return the raw numeric value.
    pub fn value(self) -> u8 {
        self.0
    }
}

impl Default for Dscp {
    fn default() -> Self {
        Self::CS6
    }
}

impl TryFrom<u8> for Dscp {
    type Error = String;
    fn try_from(val: u8) -> Result<Self, Self::Error> {
        Self::new(val)
    }
}

impl From<Dscp> for u8 {
    fn from(d: Dscp) -> u8 {
        d.0
    }
}

impl JsonSchema for Dscp {
    fn schema_name() -> String {
        "Dscp".to_string()
    }
    fn json_schema(
        _g: &mut schemars::r#gen::SchemaGenerator,
    ) -> schemars::schema::Schema {
        schemars::schema::SchemaObject {
            instance_type: Some(schemars::schema::InstanceType::Integer.into()),
            format: Some("uint8".to_string()),
            number: Some(Box::new(schemars::schema::NumberValidation {
                minimum: Some(0.0),
                maximum: Some(63.0),
                ..Default::default()
            })),
            metadata: Some(Box::new(schemars::schema::Metadata {
                description: Some(
                    "DSCP value (0-63). Default: CS6 (48).".to_string(),
                ),
                ..Default::default()
            })),
            ..Default::default()
        }
        .into()
    }
}

OpenAPI snippet:

"Dscp": {
  "description": "DSCP value (0-63). Default: CS6 (48).",
  "type": "integer",
  "format": "uint8",
  "minimum": 0,
  "maximum": 63
},

However, the generated client type has an infallible constructor (impl From<u8> for Dscp) which allows the client to construct invalid values where the type invariants are not upheld.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions