satay-codegen 0.1.0

Generate Rust client code from OpenAPI 3.1 documents
Documentation
#[derive(Debug)]
pub(crate) struct Api {
    pub(crate) server_url: String,
    pub(crate) api_key_security_schemes: Vec<ApiKeySecurityScheme>,
    pub(crate) components: Vec<Component>,
    pub(crate) constrained_types: Vec<ConstrainedType>,
    pub(crate) operations: Vec<Operation>,
}

impl Api {
    pub(crate) fn new(
        server_url: String,
        api_key_security_schemes: Vec<ApiKeySecurityScheme>,
        components: Vec<Component>,
        constrained_types: Vec<ConstrainedType>,
        operations: Vec<Operation>,
    ) -> Self {
        Self {
            server_url,
            api_key_security_schemes,
            components,
            constrained_types,
            operations,
        }
    }
}

#[derive(Debug)]
pub(crate) struct ApiKeySecurityScheme {
    pub(crate) location: ApiKeyLocation,
    pub(crate) wire_name: String,
    pub(crate) rust_name: String,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ApiKeyLocation {
    Header,
    Query,
}

#[derive(Debug, Clone)]
pub(crate) struct Component {
    pub(crate) rust_name: String,
    pub(crate) description: Option<String>,
    pub(crate) kind: ComponentKind,
}

#[derive(Debug, Clone)]
pub(crate) enum ComponentKind {
    Struct(Vec<Field>),
    Enum(Vec<EnumVariant>),
    Range(RangeType),
    Alias(TypeRef),
    Nutype(ConstrainedType),
}

#[derive(Debug, Clone)]
pub(crate) struct RangeType {
    pub(crate) rust_name: String,
    pub(crate) description: Option<String>,
    pub(crate) scalar: RangeScalar,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RangeTypeRef {
    pub(crate) rust_name: String,
    pub(crate) scalar: RangeScalar,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RangeScalar {
    Integer(IntegerType),
    F32,
    F64,
}

#[derive(Debug, Clone)]
pub(crate) struct ConstrainedType {
    pub(crate) rust_name: String,
    pub(crate) description: Option<String>,
    pub(crate) inner: TypeRef,
    pub(crate) validation: Validation,
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Validation {
    String {
        min_length: Option<u64>,
        max_length: Option<u64>,
        pattern: Option<String>,
    },
    Integer {
        minimum: Option<IntegerLimit>,
        maximum: Option<IntegerLimit>,
    },
    Number {
        minimum: Option<FloatLimit>,
        maximum: Option<FloatLimit>,
    },
    Array {
        min_items: Option<u64>,
        max_items: Option<u64>,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct IntegerLimit {
    pub(crate) value: i128,
    pub(crate) exclusive: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum IntegerType {
    U8,
    U16,
    U32,
    U64,
    I8,
    I16,
    I32,
    I64,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct FloatLimit {
    pub(crate) value: f64,
    pub(crate) exclusive: bool,
}

#[derive(Debug, Clone)]
pub(crate) struct Field {
    pub(crate) wire_name: String,
    pub(crate) rust_name: String,
    pub(crate) description: Option<String>,
    pub(crate) ty: TypeRef,
    pub(crate) required: bool,
    pub(crate) treat_error_as_none: bool,
}

#[derive(Debug, Clone)]
pub(crate) struct EnumVariant {
    pub(crate) wire_name: String,
    pub(crate) rust_name: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum TypeRef {
    String,
    ParsedString(ParseAs),
    ParsedInteger(ParseAs),
    Integer(IntegerType),
    F32,
    F64,
    Bool,
    Array(Box<TypeRef>),
    Range(RangeTypeRef),
    Named(String),
    Constrained {
        rust_name: String,
        inner: Box<TypeRef>,
    },
    Option(Box<TypeRef>),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ParseAs {
    U8,
    U16,
    U32,
    U64,
    I8,
    I16,
    I32,
    I64,
    F32,
    F64,
    Bool,
    Date,
    NaiveDateTime,
    OffsetDateTime,
    Time,
    IntegerRange,
    NumberRange,
}

#[derive(Debug)]
pub(crate) struct Operation {
    pub(crate) fn_name: String,
    pub(crate) description: Option<String>,
    pub(crate) input_name: String,
    pub(crate) response_name: String,
    pub(crate) method: HttpMethod,
    pub(crate) path: String,
    pub(crate) path_segments: Vec<PathSegment>,
    pub(crate) parameters: Vec<Parameter>,
    pub(crate) request_body: Option<RequestBody>,
    pub(crate) responses: Vec<ResponseCase>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum HttpMethod {
    Delete,
    Get,
    Head,
    Options,
    Patch,
    Post,
    Put,
    Trace,
}

#[derive(Debug, Clone)]
pub(crate) enum PathSegment {
    Literal(String),
    Parameter(String),
}

#[derive(Debug, Clone)]
pub(crate) struct Parameter {
    pub(crate) location: ParameterLocation,
    pub(crate) wire_name: String,
    pub(crate) rust_name: String,
    pub(crate) description: Option<String>,
    pub(crate) ty: TypeRef,
    pub(crate) required: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ParameterLocation {
    Path,
    Query,
    Header,
}

#[derive(Debug)]
pub(crate) struct RequestBody {
    pub(crate) field_name: String,
    pub(crate) description: Option<String>,
    pub(crate) content_type: String,
    pub(crate) ty: TypeRef,
    pub(crate) required: bool,
}

#[derive(Debug)]
pub(crate) struct ResponseCase {
    pub(crate) status: u16,
    pub(crate) variant_name: String,
    pub(crate) description: Option<String>,
    pub(crate) body: Option<TypeRef>,
}

impl TypeRef {
    pub(crate) fn option(inner: TypeRef) -> Self {
        match inner {
            already @ Self::Option(_) => already,
            other => Self::Option(Box::new(other)),
        }
    }

    pub(crate) fn is_option(&self) -> bool {
        matches!(self, Self::Option(_))
    }

    pub(crate) fn non_option(&self) -> &TypeRef {
        match self {
            Self::Option(inner) => inner.non_option(),
            other => other,
        }
    }
}

impl ParseAs {
    pub(crate) fn from_wire(value: &str) -> Option<Self> {
        match value {
            "u8" => Some(Self::U8),
            "u16" => Some(Self::U16),
            "u32" => Some(Self::U32),
            "u64" => Some(Self::U64),
            "i8" => Some(Self::I8),
            "i16" => Some(Self::I16),
            "i32" => Some(Self::I32),
            "i64" => Some(Self::I64),
            "f32" => Some(Self::F32),
            "f64" => Some(Self::F64),
            "bool" => Some(Self::Bool),
            "date" => Some(Self::Date),
            "naive-datetime" => Some(Self::NaiveDateTime),
            "offset-datetime" => Some(Self::OffsetDateTime),
            "time" => Some(Self::Time),
            "integer-range" => Some(Self::IntegerRange),
            "number-range" => Some(Self::NumberRange),
            _ => None,
        }
    }
}

impl IntegerType {
    pub(crate) fn from_wire(value: &str) -> Option<Self> {
        match value {
            "u8" => Some(Self::U8),
            "u16" => Some(Self::U16),
            "u32" => Some(Self::U32),
            "u64" => Some(Self::U64),
            "i8" => Some(Self::I8),
            "i16" => Some(Self::I16),
            "i32" => Some(Self::I32),
            "i64" => Some(Self::I64),
            _ => None,
        }
    }

    pub(crate) fn min_value(self) -> i128 {
        match self {
            Self::U8 | Self::U16 | Self::U32 | Self::U64 => 0,
            Self::I8 => i128::from(i8::MIN),
            Self::I16 => i128::from(i16::MIN),
            Self::I32 => i128::from(i32::MIN),
            Self::I64 => i128::from(i64::MIN),
        }
    }

    pub(crate) fn max_value(self) -> i128 {
        match self {
            Self::U8 => i128::from(u8::MAX),
            Self::U16 => i128::from(u16::MAX),
            Self::U32 => i128::from(u32::MAX),
            Self::U64 => i128::from(u64::MAX),
            Self::I8 => i128::from(i8::MAX),
            Self::I16 => i128::from(i16::MAX),
            Self::I32 => i128::from(i32::MAX),
            Self::I64 => i128::from(i64::MAX),
        }
    }
}

pub(crate) fn is_array_type(ty: &TypeRef) -> bool {
    match ty {
        TypeRef::Array(_) => true,
        TypeRef::Constrained { inner, .. } => is_array_type(inner.non_option()),
        TypeRef::Option(inner) => is_array_type(inner.non_option()),
        _ => false,
    }
}

impl HttpMethod {
    pub(crate) fn rust_const(self) -> &'static str {
        match self {
            Self::Delete => "DELETE",
            Self::Get => "GET",
            Self::Head => "HEAD",
            Self::Options => "OPTIONS",
            Self::Patch => "PATCH",
            Self::Post => "POST",
            Self::Put => "PUT",
            Self::Trace => "TRACE",
        }
    }

    pub(crate) fn operation_prefix(self) -> &'static str {
        match self {
            Self::Delete => "delete",
            Self::Get => "get",
            Self::Head => "head",
            Self::Options => "options",
            Self::Patch => "patch",
            Self::Post => "post",
            Self::Put => "put",
            Self::Trace => "trace",
        }
    }
}