Skip to main content

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