tablo_core/declaration/tenancy.rs
1//! The declaration mistakes of a resource's tenancy, and their wording.
2
3use std::fmt;
4
5/// Why a resource's tenancy cannot scope its rows:
6/// [`DeclarationErrorKind::Tenancy`](super::DeclarationErrorKind::Tenancy).
7#[derive(Debug, Clone, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum TenancyFault {
10 /// No tenancy declared over a model with a `TenantId` column, which would serve every
11 /// tenant's rows.
12 ///
13 /// Declare `Tenancy::column` over that field, or `Tenancy::none()` to serve every tenant.
14 Undeclared {
15 /// The model's name.
16 model: &'static str,
17 /// The model's tenant columns.
18 columns: Vec<&'static str>,
19 },
20 /// No tenancy declared over a model that belongs to one a mounted resource scopes by tenant,
21 /// which would serve every tenant's children.
22 ///
23 /// Declare `Tenancy::via` through that relation, or `Tenancy::none()` to serve every tenant.
24 UndeclaredChild {
25 /// The model's name.
26 model: &'static str,
27 /// The `belongs_to` relation to the tenant-owned parent.
28 relation: String,
29 },
30 /// No tenancy declared on a panel that acts for tenants: it offers a tenant registration or
31 /// settings page, or mounts a resource over tenant-owned rows.
32 ///
33 /// Declare `Tenancy::column` or `Tenancy::via`, or `Tenancy::none()` to serve every tenant.
34 UndeclaredOnTenantPanel {
35 /// The model's name.
36 model: &'static str,
37 },
38 /// A `Tenancy::column` lens that names no field of the model.
39 ///
40 /// Name the model's `TenantId` field, or use `Tenancy::via`.
41 ColumnNotAField,
42 /// A `Tenancy::via` lens that names one field of the model.
43 ///
44 /// Use `Tenancy::column`.
45 ViaOwnColumn,
46 /// A `Tenancy::via` lens whose first step is no `belongs_to` relation.
47 ///
48 /// Start at the relation whose parent holds the tenant.
49 ViaWithoutBelongsTo,
50 /// A `Tenancy::via` resource whose form writes the parent's foreign key other than through a
51 /// relationship field over a tenant-scoped resource of the parent's model.
52 ///
53 /// Write the key through such a choice, so the write re-checks its tenant.
54 UnguardedForeignKey {
55 /// The `belongs_to` relation the lens steps through.
56 relation: String,
57 /// The foreign-key columns written another way.
58 keys: Vec<String>,
59 },
60}
61
62impl fmt::Display for TenancyFault {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 match self {
65 Self::Undeclared { model, columns } => write!(
66 f,
67 "`{model}` has the tenant column `{}` and the resource declares no tenancy, so \
68 it would serve every tenant's rows: declare \
69 `Tenancy::column(lens!({model}.{}))`, or `Tenancy::none()` to serve every \
70 tenant on purpose",
71 columns.join("`, `"),
72 columns.first().copied().unwrap_or_default(),
73 ),
74 Self::UndeclaredChild { model, relation } => write!(
75 f,
76 "`{model}` belongs to `{relation}`, whose rows a tenant-scoped resource serves, \
77 and the resource declares no tenancy, so it would serve every tenant's rows: \
78 declare `Tenancy::via({model}::fields().{relation}().<tenant column>())`, or \
79 `Tenancy::none()` to serve every tenant on purpose"
80 ),
81 Self::UndeclaredOnTenantPanel { model } => write!(
82 f,
83 "the panel acts for tenants and the resource over `{model}` declares no tenancy: \
84 declare `Tenancy::column` or `Tenancy::via` when its rows belong to a tenant, or \
85 `Tenancy::none()` to serve every tenant on purpose"
86 ),
87 Self::ColumnNotAField => f.write_str(
88 "the `Tenancy::column` lens names no field of the model: name a field typed \
89 `TenantId`, or use `Tenancy::via` for a tenant reached through a relation",
90 ),
91 Self::ViaOwnColumn => f.write_str(
92 "the `Tenancy::via` lens names one field of the model: use `Tenancy::column` for \
93 the model's own tenant column",
94 ),
95 Self::ViaWithoutBelongsTo => f.write_str(
96 "the `Tenancy::via` lens does not start at a `belongs_to` relation: the tenant \
97 must be the parent's its foreign key names",
98 ),
99 Self::UnguardedForeignKey { relation, keys } => write!(
100 f,
101 "foreign key `{}` is not written through a relationship field over a \
102 tenant-scoped resource of `{relation}`'s model: the write re-checks only such a \
103 field's key, so any other could attach the row to another tenant's parent",
104 keys.join("`, `")
105 ),
106 }
107 }
108}