use super::ExtendedType;
use crate::collections::HashMap;
use crate::collections::HashSet;
use crate::parser::FileId;
use crate::schema::ScalarType;
use crate::validation::diagnostics::DiagnosticData;
use crate::validation::directive::validate_directive_definitions;
use crate::validation::enum_::validate_enum_definition;
use crate::validation::input_object::validate_input_object_definition;
use crate::validation::interface::validate_interface_definition;
use crate::validation::object::validate_object_type_definition;
use crate::validation::scalar::validate_scalar_definition;
use crate::validation::schema::validate_schema_definition;
use crate::validation::union_::validate_union_definition;
use crate::validation::DiagnosticList;
use crate::Name;
use crate::Node;
use crate::Schema;
use std::sync::OnceLock;
pub(crate) fn validate_schema(errors: &mut DiagnosticList, schema: &mut Schema) {
let mut builtin_scalars = BuiltInScalars::new();
validate_schema_definition(errors, schema);
validate_directive_definitions(errors, schema, &mut builtin_scalars);
for (name, def) in &schema.types {
validate_type_system_name(errors, name, def.describe());
match def {
ExtendedType::Scalar(def) => validate_scalar_definition(errors, schema, def),
ExtendedType::Object(def) => {
validate_object_type_definition(errors, schema, &mut builtin_scalars, def)
}
ExtendedType::Interface(def) => {
validate_interface_definition(errors, schema, &mut builtin_scalars, def)
}
ExtendedType::Union(def) => validate_union_definition(errors, schema, def),
ExtendedType::Enum(def) => validate_enum_definition(errors, schema, def),
ExtendedType::InputObject(def) => {
validate_input_object_definition(errors, schema, &mut builtin_scalars, def)
}
}
}
if !builtin_scalars.all_used() {
schema.types.retain(|name, def| {
if !def.is_built_in() {
return true;
}
if !builtin_scalars.all.contains_key(name) {
return true;
}
if builtin_scalars.used_and_defined.contains(name) {
return true;
}
false
})
}
for name in builtin_scalars.used_and_undefined {
let def = &builtin_scalars.all[&name];
schema
.types
.insert(def.name.clone(), ExtendedType::Scalar(def.clone()));
}
}
pub(crate) fn validate_type_system_name(
errors: &mut DiagnosticList,
name: &Name,
describe: &'static str,
) {
let location = name.location();
let is_built_in = location.is_some_and(|loc| loc.file_id == FileId::BUILT_IN);
if !is_built_in && name.starts_with("__") {
errors.push(
location,
DiagnosticData::ReservedName {
name: name.clone(),
describe,
},
);
}
}
pub(crate) struct BuiltInScalars {
all: &'static HashMap<Name, Node<ScalarType>>,
used_and_defined: HashSet<Name>,
used_and_undefined: HashSet<Name>,
}
impl BuiltInScalars {
fn new() -> Self {
static ALL: OnceLock<HashMap<Name, Node<ScalarType>>> = OnceLock::new();
let all = ALL.get_or_init(|| {
super::SchemaBuilder::built_in()
.schema
.types
.iter()
.filter_map(|(name, def)| {
if let ExtendedType::Scalar(def) = def {
Some((name.clone(), def.clone()))
} else {
None
}
})
.collect()
});
Self {
all,
used_and_defined: HashSet::default(),
used_and_undefined: HashSet::default(),
}
}
pub(crate) fn record_type_ref(&mut self, schema: &Schema, name: &Name) -> bool {
let is_built_in_scalar = self.all.contains_key(name);
if is_built_in_scalar {
if schema.types.contains_key(name) {
self.used_and_defined.insert(name.clone());
} else {
self.used_and_undefined.insert(name.clone());
}
}
is_built_in_scalar
}
fn all_used(&self) -> bool {
let used_count = self.used_and_defined.len() + self.used_and_undefined.len();
used_count == self.all.len()
}
}