Skip to main content

tablo_core/declaration/
field_access.rs

1//! The declaration mistakes of a resource's field access, and their wording.
2
3use std::fmt;
4
5/// Why a [`field_access`](crate::ResourceDef::field_access) cannot hide its field everywhere the
6/// panel shows it:
7/// [`DeclarationErrorKind::FieldAccess`](super::DeclarationErrorKind::FieldAccess).
8#[derive(Debug, Clone, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum FieldAccessFault {
11    /// Two declarations over one field.
12    ///
13    /// Declare it once, with both its `view` and its `edit` rule.
14    Duplicate,
15    /// The field is the inverse side of a relation, a `#[has_many]` or a `#[has_one]`, whose
16    /// records the related resource's relation table and policy show and link.
17    ///
18    /// Restrict them with the related resource's policy.
19    Inverse,
20    /// The field is the [soft-delete](crate::ResourceDef::soft_delete) column, whose state the
21    /// trash control and the Restore buttons show every user.
22    ///
23    /// Restrict restores and force deletes with the policy's `Restore` and `ForceDelete`.
24    SoftDelete,
25    /// The record title reads the field, and every page and relationship choice names a record
26    /// by its title. Asked of a rule with a `view` rule only.
27    ///
28    /// Title the record by a field every user may view.
29    RecordTitle,
30    /// A rule hides a primary key. The key is every record's URL id and its default label, which
31    /// every page and relationship choice shows, so no user may be kept from seeing it.
32    ///
33    /// Restrict a field that is not the key.
34    RecordKey,
35    /// A form condition, a dependent choice or another field's comparison watches the field, so
36    /// what the form shows or refuses would tell what it holds. Asked of a rule with a `view`
37    /// rule only.
38    ///
39    /// Watch or compare with a field every user may view.
40    Watched,
41    /// A column or a filter of a resource over a model a panel restricts a field of does not
42    /// declare the fields it reads, so the panel cannot tell whether it shows the restricted one.
43    ///
44    /// Declare `.reading(Reads::of(..))` on a `ComputedColumn`, a `QueryFilter` or a described
45    /// `TextColumn`, or implement `reads` on a custom column or filter.
46    UndeclaredReads {
47        /// `"column"` or `"filter"`.
48        what: &'static str,
49        /// The column's or the filter's name.
50        name: String,
51    },
52    /// A column or a filter reads records of a model a mounted resource restricts a field of,
53    /// under no field access a panel can mask.
54    ///
55    /// Show the records through a mounted resource: `RelationColumn::new::<R>` naming the fields it
56    /// reads, `RelationColumn::of::<R>` for their title, or a relationship filter over the foreign
57    /// key.
58    UnmaskedRelation {
59        /// The column's or the filter's name.
60        name: String,
61        /// The related model's name.
62        model: String,
63    },
64    /// A column or a filter reads a relation the mount cannot read off the model's schema, so it
65    /// cannot tell whether the relation reaches records a mounted resource restricts.
66    ///
67    /// Declare the relation as a `Deferred` field of the model.
68    UnresolvedRelation {
69        /// The column's or the filter's name.
70        name: String,
71    },
72    /// A dependent choice narrows its options by a field a mounted resource restricts, so the
73    /// options would show which records hold each value of it.
74    ///
75    /// Narrow by a field every user may view.
76    NarrowedBy {
77        /// The field the choice narrows by.
78        field: String,
79    },
80}
81
82impl fmt::Display for FieldAccessFault {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        match self {
85            Self::Duplicate => f.write_str(
86                "the field has two `field_access` declarations: declare it once, with both its \
87                 `view` and its `edit` rule",
88            ),
89            Self::Inverse => f.write_str(
90                "the field is a `has_many` or a `has_one`, whose records the relation table shows \
91                 and links: restrict them with the related resource's policy, or drop the \
92                 relation for the users who may not see it",
93            ),
94            Self::SoftDelete => f.write_str(
95                "the field is the soft-delete column, whose state the trash control and the \
96                 Restore and Delete permanently buttons show: restrict who restores and deletes \
97                 with the policy's `Restore` and `ForceDelete` instead",
98            ),
99            Self::RecordTitle => f.write_str(
100                "the record title reads the field, and every page and relationship choice names \
101                 the record by its title: title the record by a field every user may view",
102            ),
103            Self::RecordKey => f.write_str(
104                "the field is a primary key, which every URL and relationship choice names a record by: \
105                 restrict a field that is not the key"
106            ),
107            Self::Watched => f.write_str(
108                "a form condition, a dependent choice or a comparison watches the field, so what \
109                 the form shows or refuses would tell what the field holds: watch or compare with \
110                 a field every user may view",
111            ),
112            Self::UndeclaredReads { what, name } => write!(
113                f,
114                "{what} `{name}` declares no fields it reads, so the panel cannot tell whether it \
115                 shows a restricted one: declare them with `.reading(Reads::of(..))`, or \
116                 implement `reads` on a custom {what}"
117            ),
118            Self::UnmaskedRelation { name, model } => write!(
119                f,
120                "`{name}` reads `{model}` records under no mounted resource's field access, and a \
121                 mounted resource restricts a field of `{model}`: read them through a resource, \
122                 with `RelationColumn::new::<R>` naming the fields it reads or `RelationColumn::of::<R>` \
123                 for their title, and keep a computed or custom column off a page that loads them"
124            ),
125            Self::NarrowedBy { field } => write!(
126                f,
127                "a dependent choice narrows its options by `{field}`, which a mounted resource \
128                 restricts, so the options would show which records hold each value of it: narrow \
129                 by a field every user may view"
130            ),
131            Self::UnresolvedRelation { name } => write!(
132                f,
133                "`{name}` reads a relation the mount cannot read off the model's schema, so it \
134                 cannot show that no restricted record reaches it"
135            ),
136        }
137    }
138}