satay-codegen 0.1.1

Generate Rust client code from OpenAPI 3.1 documents
Documentation
pub(crate) mod constraint;
mod operation;
mod satay;
mod schema;

use super::resolve::ResolvedDocument;
use crate::error::ValidationError;
use crate::model::{
    EnumVariant, HttpMethod, IntegerType, ParameterLocation, ParseAs, PathSegment, RangeScalar,
    Validation,
};

#[derive(Debug)]
pub(crate) struct ValidatedDocument<'a> {
    pub(crate) resolved: ResolvedDocument<'a>,
    pub(crate) components: Vec<ValidatedComponent>,
    pub(crate) operations: Vec<ValidatedOperation>,
}

#[derive(Debug, Clone)]
pub(crate) struct ValidatedComponent {
    pub(crate) schema_name: String,
    pub(crate) description: Option<String>,
    pub(crate) kind: ValidatedComponentKind,
}

#[derive(Debug, Clone)]
pub(crate) enum ValidatedComponentKind {
    Reference(String),
    Struct(Vec<ValidatedField>),
    Type(ValidatedType),
}

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

#[derive(Debug, Clone)]
pub(crate) struct ValidatedType {
    pub(crate) kind: ValidatedTypeKind,
    pub(crate) nullable: bool,
    pub(crate) validation: Option<Validation>,
    pub(crate) description: Option<String>,
    pub(crate) treat_error_as_none: bool,
}

impl ValidatedType {
    pub(crate) fn named(rust_name: String) -> Self {
        Self {
            kind: ValidatedTypeKind::Named(rust_name),
            nullable: false,
            validation: None,
            description: None,
            treat_error_as_none: false,
        }
    }

    pub(crate) fn is_nullable(&self) -> bool {
        self.nullable
    }

    pub(crate) fn is_array(&self) -> bool {
        matches!(self.kind, ValidatedTypeKind::Array(_))
    }
}

#[derive(Debug, Clone)]
pub(crate) enum ValidatedTypeKind {
    Named(String),
    String,
    ParsedString(ParseAs),
    ParsedInteger(ParseAs),
    Integer(IntegerType),
    F32,
    F64,
    Bool,
    Array(Box<ValidatedType>),
    Enum(Vec<EnumVariant>),
    Range(RangeScalar),
}

#[derive(Debug)]
pub(crate) struct ValidatedOperation {
    pub(crate) operation_id: String,
    pub(crate) description: Option<String>,
    pub(crate) method: HttpMethod,
    pub(crate) path: String,
    pub(crate) path_segments: Vec<PathSegment>,
    pub(crate) parameters: Vec<ValidatedParameter>,
    pub(crate) request_body: Option<ValidatedRequestBody>,
    pub(crate) responses: Vec<ValidatedResponse>,
}

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

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

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

pub(crate) fn validate_document<'a>(
    document: ResolvedDocument<'a>,
) -> Result<ValidatedDocument<'a>, ValidationError> {
    let openapi = document.spec.openapi.as_str();

    if !is_supported_openapi_version(openapi) {
        return Err(ValidationError::UnsupportedOpenApiVersion {
            version: openapi.to_owned(),
        });
    }

    let components = schema::validate_components(&document)?;
    let operations = operation::validate_operations(&document)?;

    Ok(ValidatedDocument {
        resolved: document,
        components,
        operations,
    })
}

fn is_supported_openapi_version(version: &str) -> bool {
    version.starts_with("3.1.")
}