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