mod composite_attributes;
mod fields;
mod mixins_types;
mod model_attributes;
mod models;
mod pb;
mod procedures;
mod stream_attribute;
mod type_names;
mod validator_args;
mod validators;
mod views;
use std::collections::BTreeSet;
use cratestack_core::Schema;
use crate::diagnostics::{SchemaError, span_error};
use self::mixins_types::{validate_auth, validate_enums, validate_mixins, validate_types};
use self::models::validate_models;
use self::procedures::{
validate_procedure_api_version_attribute, validate_procedure_deprecated_attribute,
validate_procedure_isolation_attribute,
};
use self::stream_attribute::validate_procedure_stream_attribute;
use self::type_names::{collect_type_names, validate_type_ref};
pub(crate) fn builtin_type_names() -> &'static [&'static str] {
type_names::BUILTIN_TYPES
}
pub(crate) fn validate_schema(
path: &str,
source: &str,
schema: &Schema,
) -> Result<(), SchemaError> {
let type_names = collect_type_names(schema)?;
let mut procedure_names = BTreeSet::new();
for procedure in &schema.procedures {
if !procedure_names.insert(procedure.name.clone()) {
return Err(span_error(
format!("duplicate procedure name `{}`", procedure.name),
procedure.span,
));
}
}
validate_datasource(schema)?;
validate_no_models_under_datasource_none(schema)?;
let page_item_type_names = schema
.models
.iter()
.map(|model| model.name.clone())
.chain(schema.types.iter().map(|ty| ty.name.clone()))
.collect::<BTreeSet<_>>();
validate_models(schema, &type_names, &page_item_type_names)?;
validate_mixins(schema, &type_names, &page_item_type_names)?;
validate_types(schema, &type_names, &page_item_type_names)?;
validate_enums(schema)?;
validate_auth(schema, &type_names, &page_item_type_names)?;
validate_procedures(schema, &type_names, &page_item_type_names)?;
self::views::validate_views(schema)?;
let _ = (path, source);
Ok(())
}
fn validate_datasource(schema: &Schema) -> Result<(), SchemaError> {
if let Some(datasource) = &schema.datasource {
let provider = datasource_provider(schema);
if let Some(provider) = provider
&& provider != "postgresql"
&& provider != "sqlite"
&& provider != "none"
{
return Err(span_error(
format!(
"unsupported datasource provider `{provider}`; expected `postgresql`, `sqlite`, or `none`"
),
datasource.span,
));
}
}
Ok(())
}
fn datasource_provider(schema: &Schema) -> Option<&str> {
schema
.datasource
.as_ref()?
.entries
.iter()
.find(|entry| entry.key == "provider")
.map(|entry| entry.value.trim_matches('"'))
}
fn validate_no_models_under_datasource_none(schema: &Schema) -> Result<(), SchemaError> {
if datasource_provider(schema) != Some("none") {
return Ok(());
}
if let Some(model) = schema.models.first() {
return Err(span_error(
format!(
"model `{}` is not allowed: schema declares `datasource {{ provider = \"none\" }}`, \
which forbids any `model` block (this schema is procedures-only, no database is \
configured)",
model.name
),
model.span,
));
}
Ok(())
}
fn validate_procedures(
schema: &Schema,
type_names: &BTreeSet<String>,
page_item_type_names: &BTreeSet<String>,
) -> Result<(), SchemaError> {
for procedure in &schema.procedures {
for arg in &procedure.args {
validate_type_ref(
type_names,
page_item_type_names,
&arg.ty,
procedure.span,
false,
)?;
}
validate_type_ref(
type_names,
page_item_type_names,
&procedure.return_type,
procedure.span,
true,
)?;
validate_procedure_isolation_attribute(procedure)?;
validate_procedure_api_version_attribute(procedure)?;
validate_procedure_deprecated_attribute(procedure)?;
validate_procedure_stream_attribute(procedure)?;
}
Ok(())
}