use std::collections::BTreeSet;
use serde_json::Value;
pub fn resolve_schema_reference<'a>(
root_schema: &'a Value,
schema: &'a Value,
) -> Option<&'a Value> {
if let Some(reference) = schema.get("$ref").and_then(Value::as_str) {
return resolve_json_pointer_ref(root_schema, reference);
}
schema
.get("allOf")
.and_then(Value::as_array)
.and_then(|schemas| schemas.first())
.and_then(|schema| schema.get("$ref"))
.and_then(Value::as_str)
.and_then(|reference| resolve_json_pointer_ref(root_schema, reference))
}
fn resolve_json_pointer_ref<'a>(root_schema: &'a Value, reference: &str) -> Option<&'a Value> {
let pointer = reference.strip_prefix('#')?;
root_schema.pointer(pointer)
}
pub fn collect_transitive_schema_refs(
schema: &Value,
definitions: &mut BTreeSet<String>,
defs: &mut BTreeSet<String>,
) {
let current_definitions = definitions.clone();
let current_defs = defs.clone();
let mut referenced_definitions = BTreeSet::new();
let mut referenced_defs = BTreeSet::new();
if let Some(schema_map) = schema.get("definitions").and_then(Value::as_object) {
for name in ¤t_definitions {
if let Some(schema) = schema_map.get(name) {
collect_schema_refs(
schema,
true,
&mut referenced_definitions,
&mut referenced_defs,
);
}
}
}
if let Some(schema_map) = schema.get("$defs").and_then(Value::as_object) {
for name in ¤t_defs {
if let Some(schema) = schema_map.get(name) {
collect_schema_refs(
schema,
true,
&mut referenced_definitions,
&mut referenced_defs,
);
}
}
}
definitions.extend(referenced_definitions);
defs.extend(referenced_defs);
}
pub fn collect_schema_refs(
value: &Value,
include_schema_maps: bool,
definitions: &mut BTreeSet<String>,
defs: &mut BTreeSet<String>,
) {
match value {
Value::Object(object) => {
if let Some(reference) = object.get("$ref").and_then(Value::as_str) {
collect_schema_ref(reference, definitions, defs);
}
for (key, child) in object {
if !include_schema_maps && matches!(key.as_str(), "definitions" | "$defs") {
continue;
}
collect_schema_refs(child, include_schema_maps, definitions, defs);
}
}
Value::Array(items) => {
for item in items {
collect_schema_refs(item, include_schema_maps, definitions, defs);
}
}
Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
}
}
fn collect_schema_ref(
reference: &str,
definitions: &mut BTreeSet<String>,
defs: &mut BTreeSet<String>,
) {
if let Some(name) = schema_ref_name(reference, "#/definitions/") {
definitions.insert(name);
} else if let Some(name) = schema_ref_name(reference, "#/$defs/") {
defs.insert(name);
}
}
fn schema_ref_name(reference: &str, prefix: &str) -> Option<String> {
let name = reference.strip_prefix(prefix)?.split('/').next()?;
Some(decode_json_pointer_token(name))
}
fn decode_json_pointer_token(token: &str) -> String {
token.replace("~1", "/").replace("~0", "~")
}
pub fn retain_schema_map(schema: &mut Value, key: &str, used_names: &BTreeSet<String>) {
let Some(object) = schema.as_object_mut() else {
return;
};
let Some(schema_map) = object.get_mut(key).and_then(Value::as_object_mut) else {
return;
};
schema_map.retain(|name, _| used_names.contains(name));
if schema_map.is_empty() {
object.remove(key);
}
}