Skip to main content

rok_sqlc/
config.rs

1use std::collections::BTreeMap;
2use std::path::PathBuf;
3
4/// What to generate and how: the one configuration model behind the CLI and
5/// [`generate`](crate::generate).
6///
7/// New fields may be added in minor releases, so build a `Config` from
8/// [`Config::default`] and assign the fields you need:
9///
10/// ```no_run
11/// let mut config = rok_sqlc::Config::default();
12/// config.schema = vec!["migrations/0001_init.sql".into()];
13/// config.output = "src/db".into();
14/// rok_sqlc::generate(&config)?;
15/// # Ok::<(), rok_sqlc::Error>(())
16/// ```
17#[derive(Debug, Clone, PartialEq, Eq)]
18#[non_exhaustive]
19pub struct Config {
20    /// Schema inputs, combined in the order given: a file, or a directory
21    /// whose `*.sql` migrations are read in filename order, skipping
22    /// `*.down.sql`.
23    pub schema: Vec<PathBuf>,
24    /// Annotated query files or directories. A single file becomes the
25    /// generated `queries.rs`. A directory (its `*.sql` files) or several
26    /// inputs become one `queries/<stem>.rs` module per file, declared by a
27    /// generated `queries/mod.rs`; each stem must be a snake_case Rust
28    /// module name, and stems must be unique.
29    ///
30    /// Switching between the two layouts removes the old layout's generated
31    /// files as stale. Left empty, a previously generated `queries.rs` or
32    /// `queries/` is kept and still declared.
33    pub queries: Vec<PathBuf>,
34    /// The directory generated modules are written to. Defaults to `src/db`.
35    pub output: PathBuf,
36    /// Generate only the table with this SQL or module name.
37    pub resource: Option<String>,
38    /// Plan the run and report it without touching the filesystem.
39    pub dry_run: bool,
40    /// Write and remove nothing; report missing, changed, and stale files.
41    /// Any such drift fails the run. Takes precedence over `dry_run`.
42    pub check: bool,
43    /// Overwrite existing files that were not generated by rok-sqlc.
44    pub force: bool,
45    /// Skip unsupported tables and queries (listed in the report) instead
46    /// of failing.
47    pub lenient: bool,
48    /// Opt-in conventions about what certain columns mean across tables.
49    /// All off by default; [`Conventions::rok`] gives the Rok preset.
50    pub conventions: Conventions,
51    /// Extra derives for the generated rows, typed ids, key structs, enums,
52    /// inputs, and patches (ADR 0013). Empty by default.
53    pub derive: Vec<Derive>,
54    /// Renames and opt-outs of inferred relationships (ADR 0014), by table
55    /// (its SQL name, schema-qualified name, or module name) and then by the
56    /// relationship's inferred name. Empty by default.
57    ///
58    /// ```
59    /// use rok_sqlc::RelationshipChange;
60    /// let mut config = rok_sqlc::Config::default();
61    /// let invoices = config.relationships.entry("invoices".into()).or_default();
62    /// invoices.insert("created_by_user".into(), RelationshipChange::Rename("author".into()));
63    /// invoices.insert("approved_by_user".into(), RelationshipChange::OptOut);
64    /// ```
65    pub relationships: BTreeMap<String, BTreeMap<String, RelationshipChange>>,
66}
67
68/// What configuration does to one inferred relationship (the
69/// `[relationships]` section of the config file).
70#[derive(Debug, Clone, PartialEq, Eq)]
71#[non_exhaustive]
72pub enum RelationshipChange {
73    /// Generate the relationship under this name instead: its loader becomes
74    /// `load_<name>`. The name must be a snake_case Rust identifier and must
75    /// not collide with the table's other relationships.
76    Rename(String),
77    /// Generate nothing for the relationship, nor for its other side (the
78    /// inverse of a belongs-to, or the belongs-to of an inverse).
79    OptOut,
80}
81
82/// An opt-in derive for generated types.
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
84#[non_exhaustive]
85pub enum Derive {
86    /// `serde::Serialize` and `serde::Deserialize`. Typed ids serialize as
87    /// their inner value and enum variants as their database label. The
88    /// consuming crate needs `serde` with its `derive` feature, and the
89    /// `serde` feature of `uuid`, `chrono`, and `rust_decimal` where the
90    /// generated code uses them; the generated `mod.rs` lists them.
91    Serde,
92}
93
94impl Derive {
95    /// The name the config file, the JSON payload, and `--derive` use.
96    pub fn name(self) -> &'static str {
97        match self {
98            Derive::Serde => "serde",
99        }
100    }
101}
102
103impl Default for Config {
104    fn default() -> Self {
105        Self {
106            schema: Vec::new(),
107            queries: Vec::new(),
108            output: PathBuf::from("src/db"),
109            resource: None,
110            dry_run: false,
111            check: false,
112            force: false,
113            lenient: false,
114            conventions: Conventions::default(),
115            derive: Vec::new(),
116            relationships: BTreeMap::new(),
117        }
118    }
119}
120
121/// Opt-in conventions: columns that mean the same thing in every table that
122/// has them (ADR 0010). Each convention applies only to the tables that have
123/// the named column, and all are off by default.
124///
125/// ```
126/// let mut conventions = rok_sqlc::Conventions::rok();
127/// conventions.soft_delete_column = Some("removed_at".into());
128/// let mut config = rok_sqlc::Config::default();
129/// config.conventions = conventions;
130/// ```
131#[derive(Debug, Clone, Default, PartialEq, Eq)]
132#[non_exhaustive]
133pub struct Conventions {
134    /// The tenant column, which scopes every generated operation on a table
135    /// to one tenant given by the caller.
136    pub tenant_column: Option<String>,
137    /// The PostgreSQL setting the generated session helper sets to the
138    /// current tenant, for row-level security policies.
139    pub tenant_setting: Option<String>,
140    /// The PostgreSQL setting the generated session helper sets to the
141    /// acting user, for audit triggers.
142    pub user_setting: Option<String>,
143    /// The soft-delete column: a nullable timestamp recording when a row was
144    /// deleted.
145    pub soft_delete_column: Option<String>,
146    /// Managed columns, maintained by the database (defaults and triggers):
147    /// left out of create inputs and update patches, kept in rows.
148    pub managed_columns: Vec<String>,
149    /// The version column, for optimistic concurrency on update.
150    pub version_column: Option<String>,
151}
152
153impl Conventions {
154    /// The preset names [`Conventions::preset`] knows.
155    pub const PRESETS: &[&str] = &["rok"];
156
157    /// The Rok ERP's conventions: tenant column `organization_id` (with the
158    /// settings `app.organization_id` and `app.user_id`), soft-delete column
159    /// `deleted_at`, managed columns `updated_at` and `row_version`, and
160    /// version column `row_version`.
161    pub fn rok() -> Self {
162        Self {
163            tenant_column: Some("organization_id".to_owned()),
164            tenant_setting: Some("app.organization_id".to_owned()),
165            user_setting: Some("app.user_id".to_owned()),
166            soft_delete_column: Some("deleted_at".to_owned()),
167            managed_columns: vec!["updated_at".to_owned(), "row_version".to_owned()],
168            version_column: Some("row_version".to_owned()),
169        }
170    }
171
172    /// The preset with this name (one of [`Conventions::PRESETS`]), or
173    /// `None` for an unknown name.
174    pub fn preset(name: &str) -> Option<Self> {
175        match name {
176            "rok" => Some(Self::rok()),
177            _ => None,
178        }
179    }
180}