use oas3::{
Spec,
spec::{ObjectOrReference, ObjectSchema, Operation, Response},
};
use super::{SchemaConverter, cache::SharedSchemaCache};
use crate::generator::{
ast::{
ContentCategory, EnumToken, EnumVariantToken, MethodNameToken, ResponseEnumDef, ResponseVariant, RustPrimitive,
StatusCodeToken, StructKind, StructMethod, StructMethodKind, TypeRef, status_code_to_variant_name,
},
naming::{
constants::{DEFAULT_RESPONSE_DESCRIPTION, DEFAULT_RESPONSE_VARIANT},
identifiers::to_rust_type_name,
inference as naming,
},
schema_registry::SchemaRegistry,
};
pub(crate) fn build_response_enum(
schema_converter: &SchemaConverter,
spec: &Spec,
name: &str,
operation: &Operation,
path: &str,
schema_cache: &mut SharedSchemaCache,
) -> Option<ResponseEnumDef> {
let responses = operation.responses.as_ref()?;
let mut variants = vec![];
let base_name = to_rust_type_name(name);
for (status_code_str, resp_ref) in responses {
let Ok(response) = resp_ref.resolve(spec) else {
continue;
};
let status_code = status_code_str
.parse::<StatusCodeToken>()
.unwrap_or(StatusCodeToken::Default);
let variant_name = status_code_to_variant_name(status_code, &response);
let (schema_type, content_category) =
extract_response_schema_info(schema_converter, &response, path, status_code, schema_cache)
.ok()
.unwrap_or((None, ContentCategory::Json));
variants.push(ResponseVariant {
status_code,
variant_name,
description: response.description.clone(),
schema_type,
content_category,
});
}
let variants = normalize_response_variants(variants);
if variants.is_empty() {
return None;
}
Some(ResponseEnumDef {
name: EnumToken::new(&base_name),
docs: vec![format!("Response types for {}", operation.operation_id.as_ref()?)],
variants,
request_type: None,
})
}
fn extract_response_schema_info(
schema_converter: &SchemaConverter,
response: &Response,
path: &str,
status_code: StatusCodeToken,
schema_cache: &mut SharedSchemaCache,
) -> anyhow::Result<(Option<TypeRef>, ContentCategory)> {
let Some((content_type, media_type)) = response.content.iter().next() else {
return Ok((None, ContentCategory::Json));
};
let content_category = ContentCategory::from_content_type(content_type);
if content_category == ContentCategory::Binary && status_code.is_success() {
return Ok((Some(TypeRef::new(RustPrimitive::Bytes)), content_category));
}
let Some(schema_ref) = media_type.schema.as_ref() else {
return Ok((None, content_category));
};
let type_ref = match schema_ref {
ObjectOrReference::Ref { ref_path, .. } => {
SchemaRegistry::extract_ref_name(ref_path).map(|name| TypeRef::new(to_rust_type_name(&name)))
}
ObjectOrReference::Object(inline_schema) => {
resolve_inline_response_schema(schema_converter, inline_schema, path, status_code, schema_cache)?
}
};
Ok((type_ref, content_category))
}
fn resolve_inline_response_schema(
schema_converter: &SchemaConverter,
inline_schema: &ObjectSchema,
path: &str,
status_code: StatusCodeToken,
schema_cache: &mut SharedSchemaCache,
) -> anyhow::Result<Option<TypeRef>> {
if inline_schema.properties.is_empty() && inline_schema.schema_type.is_none() {
return Ok(None);
}
if inline_schema.properties.is_empty()
&& let Ok(primitive_ref) = schema_converter.resolve_type(inline_schema)
&& !matches!(primitive_ref.base_type, RustPrimitive::Custom(_))
{
return Ok(Some(primitive_ref));
}
let type_name =
resolve_response_inline_struct_name(schema_converter, inline_schema, path, status_code, schema_cache)?;
Ok(Some(TypeRef::new(type_name)))
}
fn resolve_response_inline_struct_name(
schema_converter: &SchemaConverter,
inline_schema: &ObjectSchema,
path: &str,
status_code: StatusCodeToken,
schema_cache: &mut SharedSchemaCache,
) -> anyhow::Result<String> {
if let Some(cached) = schema_cache.get_type_name(inline_schema)? {
return Ok(cached);
}
let base_name = naming::infer_name_from_context(inline_schema, path, status_code.as_str());
let unique_name = schema_cache.make_unique_name(&base_name);
let result = schema_converter.convert_struct(
&unique_name,
inline_schema,
Some(StructKind::Schema),
Some(schema_cache),
)?;
schema_cache.register_type(inline_schema, &unique_name, result.inline_types, result.result)?;
Ok(unique_name)
}
fn normalize_response_variants(mut variants: Vec<ResponseVariant>) -> Vec<ResponseVariant> {
if variants.is_empty() {
return variants;
}
let has_default = variants.iter().any(|v| v.status_code.is_default());
if !has_default {
variants.push(ResponseVariant {
status_code: StatusCodeToken::Default,
variant_name: EnumVariantToken::new(DEFAULT_RESPONSE_VARIANT),
description: Some(DEFAULT_RESPONSE_DESCRIPTION.to_string()),
schema_type: None,
content_category: ContentCategory::Json,
});
}
variants
}
pub(crate) fn build_parse_response_method(response_enum: &EnumToken, variants: &[ResponseVariant]) -> StructMethod {
StructMethod {
name: MethodNameToken::new("parse_response"),
docs: vec!["Parse the HTTP response into the response enum.".to_string()],
kind: StructMethodKind::ParseResponse {
response_enum: response_enum.clone(),
variants: variants.to_vec(),
},
attrs: vec![],
}
}