use std::collections::BTreeSet;
use cratestack_core::{ExtensionKind, SourceSpan, TypeRef, canonical_geometry_subtype};
use crate::diagnostics::{SchemaError, span_error};
const COMMON_SUBTYPES: &str =
"Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, GeometryCollection";
pub(super) fn validate_spatial_type_ref(
declared_extensions: &BTreeSet<ExtensionKind>,
type_ref: &TypeRef,
span: SourceSpan,
) -> Result<(), SchemaError> {
let name = type_ref.name.as_str();
if !declared_extensions.contains(&ExtensionKind::Postgis) {
return Err(span_error(
format!(
"field type `{name}` requires `extension postgis {{ }}` to be declared in this \
schema — see docs/design/extensions.md §6b"
),
span,
));
}
if type_ref.arity == cratestack_core::TypeArity::List {
return Err(span_error(
format!(
"`{name}` fields cannot be list-valued (`{name}[]`) — PostGIS has no array-of-\
geography column type; use a `MultiPoint`/`MultiPolygon` subtype to hold several \
shapes in one value"
),
span,
));
}
if type_ref.int_args.len() > 1 {
return Err(span_error(
format!(
"`{name}` accepts at most one integer SRID argument, e.g. `{name}(Point, 4326)`"
),
span,
));
}
if type_ref.ident_args.len() > 1 {
return Err(span_error(
format!(
"`{name}` accepts at most one geometry subtype argument, e.g. \
`{name}(Point, 4326)`"
),
span,
));
}
if type_ref.ident_args.is_empty() && !type_ref.int_args.is_empty() {
return Err(span_error(
format!(
"`{name}` cannot take an SRID without a geometry subtype — write \
`{name}(Point, {srid})` rather than `{name}({srid})`",
srid = type_ref.int_args[0]
),
span,
));
}
if let Some(subtype) = type_ref.ident_args.first()
&& canonical_geometry_subtype(subtype).is_none()
{
return Err(span_error(
format!(
"unknown geometry subtype `{subtype}` in `{name}({subtype})` — expected one of \
{COMMON_SUBTYPES}, optionally suffixed `Z`, `M`, or `ZM` for 3D/measured \
geometries (e.g. `PointZM`)"
),
span,
));
}
Ok(())
}