use std::{collections::HashSet, rc::Rc, sync::Arc};
use oas3::Spec;
use crate::generator::{
ast::{ClientRootNode, OperationInfo, RustType, constants::HttpHeaderRef},
codegen::{GeneratedResult, SchemaCodeGenerator, Visibility},
converter::{
CodegenConfig, ConverterContext, GenerationTarget, OperationsProcessor, SchemaConverter, SerdeUsageRecorder,
build_server_trait, cache::SharedSchemaCache,
},
metrics::GenerationStats,
mode::GenerationMode,
operation_registry::OperationRegistry,
postprocess::PostprocessOutput,
schema_registry::SchemaRegistry,
};
const OAS3_GEN_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Debug)]
pub struct Orchestrator {
spec: Spec,
visibility: Visibility,
config: CodegenConfig,
operation_registry: OperationRegistry,
}
struct GenerationArtifacts {
rust_types: Vec<RustType>,
operations_info: Vec<OperationInfo>,
serde_recorder: SerdeUsageRecorder,
unique_headers: Vec<HttpHeaderRef>,
stats: GenerationStats,
config: CodegenConfig,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GeneratedFinalOutput {
pub code: GeneratedResult,
pub stats: GenerationStats,
}
impl GeneratedFinalOutput {
pub fn new(code: GeneratedResult, stats: GenerationStats) -> Self {
Self { code, stats }
}
}
impl Orchestrator {
#[must_use]
pub fn new(
spec: Spec,
visibility: Visibility,
config: CodegenConfig,
only_operations: Option<&HashSet<String>>,
excluded_operations: Option<&HashSet<String>>,
) -> Self {
let operation_registry = OperationRegistry::with_filters(&spec, only_operations, excluded_operations);
Self {
spec,
visibility,
config,
operation_registry,
}
}
pub fn generate(&self, mode: &dyn GenerationMode, source_path: &str) -> anyhow::Result<GeneratedFinalOutput> {
let artifacts = self.collect_generation_artifacts();
let serde_usage = artifacts.serde_recorder.into_usage_map();
let postprocessed = PostprocessOutput::new(
artifacts.rust_types,
artifacts.operations_info,
serde_usage,
artifacts.config.target,
artifacts.unique_headers,
);
let server_trait_def = if artifacts.config.target == GenerationTarget::Server {
build_server_trait(&postprocessed.operations)
} else {
None
};
let codegen = SchemaCodeGenerator::builder()
.config(artifacts.config)
.rust_types(postprocessed.types)
.operations(postprocessed.operations)
.header_refs(postprocessed.header_refs)
.uses(postprocessed.uses)
.client(ClientRootNode::from(&self.spec))
.maybe_server_trait(server_trait_def)
.visibility(self.visibility)
.source_path(source_path.to_string())
.gen_version(OAS3_GEN_VERSION.to_string())
.build();
let code = mode.generate(&codegen)?;
Ok(GeneratedFinalOutput::new(code, artifacts.stats))
}
fn collect_generation_artifacts(&self) -> GenerationArtifacts {
let mut stats = GenerationStats::default();
let mut schema_graph = SchemaRegistry::new(&self.spec, &mut stats);
let mut cache = SharedSchemaCache::new();
cache.initialize_from_schemas(schema_graph.schemas());
let union_fingerprints = cache.union_fingerprints().clone();
let (cycle_info, filtered_schemas) = schema_graph.initialize(
&self.operation_registry,
self.config.include_all_schemas(),
&union_fingerprints,
);
let schema_graph = Arc::new(schema_graph);
let schema_names = schema_graph.scan_and_compute_names().unwrap_or_default();
let filtered_schemas = filtered_schemas.map(Arc::new);
cache.set_precomputed_names(
schema_names.names,
schema_names.enum_names,
schema_names.schema_metadata,
);
let context = Rc::new(ConverterContext::new(
schema_graph.clone(),
self.config.clone(),
cache,
filtered_schemas.clone(),
));
let converter = SchemaConverter::new(&context);
let mut rust_types = converter.convert_all_schemas(filtered_schemas.as_deref(), &mut stats);
let processor = OperationsProcessor::new(context.clone(), &converter);
let operation_results = processor.process_all(self.operation_registry.operations());
rust_types.extend(operation_results.types);
rust_types.extend(context.cache.borrow_mut().take_types());
stats.record_orphaned_schemas(if let Some(ref schemas) = filtered_schemas {
let total = schema_graph.keys().len();
total.saturating_sub(schemas.len())
} else {
0
});
stats.record_warnings(operation_results.warnings);
stats.record_rust_types(&rust_types);
stats.record_operations(&operation_results.operations);
stats.record_cycles(cycle_info);
stats.record_client_methods(operation_results.operations.len());
stats.record_client_headers(operation_results.unique_headers.len());
GenerationArtifacts {
rust_types,
operations_info: operation_results.operations,
serde_recorder: operation_results.usage_recorder,
unique_headers: operation_results.unique_headers.into_iter().collect::<Vec<_>>(),
stats,
config: context.config.clone(),
}
}
}