use schemars::JsonSchema;
use schemars::Map;
use schemars::r#gen::SchemaGenerator as InnerGenerator;
use schemars::r#gen::SchemaSettings;
use schemars::schema::InstanceType;
use schemars::schema::ObjectValidation;
use schemars::schema::Schema;
use schemars::schema::SchemaObject;
use schemars::schema::SingleOrVec;
use schemars::visit::Visitor;
use schemars::visit::visit_schema_object;
use serde_json::Value;
pub struct SchemaGenerator(InnerGenerator);
impl AsRef<InnerGenerator> for SchemaGenerator {
fn as_ref(&self) -> &InnerGenerator {
&self.0
}
}
impl AsMut<InnerGenerator> for SchemaGenerator {
fn as_mut(&mut self) -> &mut InnerGenerator {
&mut self.0
}
}
impl Default for SchemaGenerator {
fn default() -> Self {
Self::new()
}
}
impl SchemaGenerator {
pub fn new() -> Self {
let mut settings = SchemaSettings::openapi3();
settings.visitors.push(Box::new(ReplaceNullType));
Self(InnerGenerator::new(settings))
}
pub fn generate<T: JsonSchema>(&mut self) -> Schema {
let mut schema = self.0.subschema_for::<T>();
for v in self.0.visitors_mut() {
v.visit_schema(&mut schema);
}
schema
}
pub fn generate_refless<T: JsonSchema>(&mut self) -> Schema {
let mut schema = T::json_schema(&mut self.0);
for v in self.0.visitors_mut() {
v.visit_schema(&mut schema);
}
schema
}
pub fn generate_object<T: JsonSchema>(&mut self) -> Option<Box<ObjectValidation>> {
let schema = self.generate_refless::<T>();
match schema {
Schema::Object(SchemaObject {
object: Some(object),
..
}) => Some(object),
_ => None,
}
}
pub fn into_definitions(mut self) -> Map<String, Schema> {
let mut definitions = self.0.take_definitions();
for v in self.0.visitors_mut() {
for s in definitions.values_mut() {
v.visit_schema(s);
}
}
definitions
}
}
#[derive(Debug, Clone)]
struct ReplaceNullType;
impl Visitor for ReplaceNullType {
fn visit_schema_object(&mut self, schema: &mut SchemaObject) {
#[expect(clippy::collapsible_if, reason = "Only allowed on newer rust versions")]
if let Some(SingleOrVec::Single(boxed)) = &schema.instance_type {
if **boxed == InstanceType::Null {
schema.instance_type = None;
schema
.extensions
.insert("nullable".to_string(), Value::Bool(true));
}
}
visit_schema_object(self, schema);
}
}