cratestack_parser/lib.rs
1mod diagnostics;
2mod line_helpers;
3mod parse;
4mod relation_actions;
5mod relation_helpers;
6mod validate;
7
8#[cfg(test)]
9mod tests_basic;
10#[cfg(test)]
11mod tests_docs;
12#[cfg(test)]
13mod tests_enums;
14#[cfg(test)]
15mod tests_field_attrs;
16#[cfg(test)]
17mod tests_list_arity;
18#[cfg(test)]
19mod tests_mixins;
20#[cfg(test)]
21mod tests_model_attrs;
22#[cfg(test)]
23mod tests_model_unique;
24#[cfg(test)]
25mod tests_procedures;
26#[cfg(test)]
27mod tests_relation_actions;
28#[cfg(test)]
29mod tests_relations;
30#[cfg(test)]
31mod tests_relations_policy;
32#[cfg(test)]
33mod tests_stream_attribute;
34#[cfg(test)]
35mod tests_transport;
36#[cfg(test)]
37mod tests_types;
38#[cfg(test)]
39mod tests_validators;
40#[cfg(test)]
41mod tests_version;
42#[cfg(test)]
43mod tests_views;
44
45use std::path::Path;
46
47pub use diagnostics::SchemaError;
48
49/// Canonical scalar type names built into the `.cstack` language (e.g.
50/// `String`, `Int`, `Decimal`, ...), including `Page` (which is valid only
51/// as a procedure return type — see `validate::type_names::validate_type_ref`
52/// — not as a plain field type).
53///
54/// This is the same list `cratestack-lsp`'s autocompletion and the
55/// `cratestack-pg`/`cratestack-sqlite` emitter/decoder round-trip tests
56/// assert against, so a new builtin scalar only has to be added here once —
57/// see cratestack#232 for why that matters (this list had already silently
58/// drifted from the LSP's hand-copied one before this accessor existed).
59pub fn builtin_type_names() -> &'static [&'static str] {
60 validate::builtin_type_names()
61}
62
63#[cfg(test)]
64use relation_helpers::parse_relation_attribute;
65
66pub fn parse_schema(source: &str) -> Result<cratestack_core::Schema, SchemaError> {
67 parse_schema_named("<schema>", source)
68}
69
70pub fn parse_schema_named(
71 path: &str,
72 source: &str,
73) -> Result<cratestack_core::Schema, SchemaError> {
74 let schema = parse::parse_schema_only(source)?;
75 validate::validate_schema(path, source, &schema)?;
76 Ok(schema)
77}
78
79/// Parse a `.cstack` source into a [`cratestack_core::Schema`] WITHOUT
80/// running [`validate::validate_schema`].
81///
82/// Prefer [`parse_schema`] for any new source — this exists for two
83/// legitimate cases where the validated pipeline understates what a
84/// `Schema` value can actually be:
85///
86/// 1. A committed `migrations/*/schema.snapshot.json` can predate a
87/// validation rule added later. `cratestack-cli`'s `migrate diff`
88/// deserializes that "previous" snapshot directly and never re-runs
89/// `validate_schema` on it (only the *new* side, parsed fresh from the
90/// `.cstack` source, goes through [`parse_schema_file`]) — so an emitter
91/// can still legitimately be handed a shape the current validator would
92/// reject at the source level.
93/// 2. Tests that deliberately exercise an emitter's rendering logic for
94/// such an already-invalid shape, to prove the emitter itself still
95/// behaves sanely if that shape arrives via (1) — see
96/// `cratestack-migrate`'s `emit::postgres::tests::enums` for an example
97/// (a list-valued enum column, rejected by cratestack#229/#236 at parse
98/// time, but still real input to the Postgres emitter via a pre-#236
99/// snapshot).
100pub fn parse_schema_unvalidated(source: &str) -> Result<cratestack_core::Schema, SchemaError> {
101 parse::parse_schema_only(source)
102}
103
104pub fn parse_schema_file(path: impl AsRef<Path>) -> Result<cratestack_core::Schema, SchemaError> {
105 let path = path.as_ref();
106 let source = std::fs::read_to_string(path).map_err(|error| {
107 SchemaError::new(
108 format!("failed to read schema file {}: {error}", path.display()),
109 0..0,
110 1,
111 )
112 })?;
113 parse_schema_named(&path.display().to_string(), &source)
114}