use std::any::TypeId;
use schemars::{JsonSchema, Schema, SchemaGenerator, generate::SchemaSettings};
use serde_json::Value;
pub(crate) type SchemaFactory = fn() -> Value;
#[derive(Debug, Clone, Copy)]
pub(crate) struct ExtendedSchemaFactory {
root: SchemaFactory,
subschema: fn(&mut SchemaGenerator) -> Schema,
}
impl ExtendedSchemaFactory {
pub(crate) fn root(self) -> Value {
(self.root)()
}
}
pub(crate) fn schema_for<T>() -> Value
where
T: ?Sized + JsonSchema,
{
let mut schema = schema_generator().into_root_schema_for::<T>().to_value();
if let Value::Object(root) = &mut schema {
root.remove("$schema");
root.remove("title");
}
schema
}
pub(crate) fn output_schema_factory<T>() -> Option<SchemaFactory>
where
T: crate::__private::HandlerContract,
{
(TypeId::of::<T::Output>() != TypeId::of::<()>()).then_some(schema_for::<T::Output>)
}
pub(crate) fn extended_schema_factory<T>() -> ExtendedSchemaFactory
where
T: ?Sized + JsonSchema,
{
ExtendedSchemaFactory { root: schema_for::<T>, subschema: extended_subschema_for::<T> }
}
pub(crate) fn compose_extended_schemas(
application: ExtendedSchemaFactory,
command: ExtendedSchemaFactory,
) -> Value {
let mut generator = schema_generator();
let application = (application.subschema)(&mut generator).to_value();
let command = (command.subschema)(&mut generator).to_value();
let definitions = generator.take_definitions(true);
let mut schema = serde_json::Map::new();
schema.insert("allOf".to_owned(), Value::Array(vec![application, command]));
if !definitions.is_empty() {
schema.insert("$defs".to_owned(), Value::Object(definitions));
}
Value::Object(schema)
}
fn extended_subschema_for<T>(generator: &mut SchemaGenerator) -> Schema
where
T: ?Sized + JsonSchema,
{
generator.subschema_for::<T>()
}
fn schema_generator() -> SchemaGenerator {
SchemaSettings::draft2020_12().for_serialize().into_generator()
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(JsonSchema)]
#[expect(dead_code, reason = "fixture is reflected into JSON Schema")]
struct Fixture {
value: String,
}
#[test]
fn root_schemas_omit_dialect_and_type_title() {
let schema = schema_for::<Fixture>();
assert!(schema.get("$schema").is_none());
assert!(schema.get("title").is_none());
assert!(schema["properties"].get("value").is_some());
}
}