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    /// A custom filter must declare them once any mounted resource restricts a field, of
44    /// whichever model.
45    ///
46    /// Declare `.reading(Reads::of(..))` on a `ComputedColumn`, a described `TextColumn`, a row
47    /// color or a row link, or implement `reads` on a custom column or filter. A `QueryFilter`, a
48    /// tab and a summary read theirs off their predicates.
49    UndeclaredReads {
50        /// `"column"`, `"filter"`, `"row color"` or `"row link"`.
51        what: &'static str,
52        /// The column's or the filter's name.
53        name: String,
54    },
55    /// A column or a filter reads records of a model a mounted resource restricts a field of,
56    /// under no field access a panel can mask.
57    ///
58    /// Show the records through a mounted resource: `RelationColumn::new::<R>` naming the fields it
59    /// reads, `RelationColumn::of::<R>` for their title, or a relationship filter over the foreign
60    /// key. A closure over the row that declares it reads only the row's own fields reads a copy
61    /// without its relations, which a page loading them does not refuse.
62    UnmaskedRelation {
63        /// The column's or the filter's name.
64        name: String,
65        /// The related model's name.
66        model: String,
67    },
68    /// A column or a filter reads a relation the mount cannot read off the model's schema, so it
69    /// cannot tell whether the relation reaches records a mounted resource restricts.
70    ///
71    /// Declare the relation as a `Deferred` field of the model.
72    UnresolvedRelation {
73        /// The column's or the filter's name.
74        name: String,
75    },
76    /// A dependent choice narrows its options by a field a mounted resource restricts, so the
77    /// options would show which records hold each value of it.
78    ///
79    /// Narrow by a field every user may view.
80    NarrowedBy {
81        /// The field the choice narrows by.
82        field: String,
83    },
84}
85
86impl fmt::Display for FieldAccessFault {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        match self {
89            Self::Duplicate => f.write_str(
90                "the field has two `field_access` declarations: declare it once, with both its \
91                 `view` and its `edit` rule",
92            ),
93            Self::Inverse => f.write_str(
94                "the field is a `has_many` or a `has_one`, whose records the relation table shows \
95                 and links: restrict them with the related resource's policy, or drop the \
96                 relation for the users who may not see it",
97            ),
98            Self::SoftDelete => f.write_str(
99                "the field is the soft-delete column, whose state the trash control and the \
100                 Restore and Delete permanently buttons show: restrict who restores and deletes \
101                 with the policy's `Restore` and `ForceDelete` instead",
102            ),
103            Self::RecordTitle => f.write_str(
104                "the record title reads the field, and every page and relationship choice names \
105                 the record by its title: title the record by a field every user may view",
106            ),
107            Self::RecordKey => f.write_str(
108                "the field is a primary key, which every URL and relationship choice names a record by: \
109                 restrict a field that is not the key"
110            ),
111            Self::Watched => f.write_str(
112                "a form condition, a dependent choice or a comparison watches the field, so what \
113                 the form shows or refuses would tell what the field holds: watch or compare with \
114                 a field every user may view",
115            ),
116            Self::UndeclaredReads { what, name } => write!(
117                f,
118                "{what} `{name}` declares no fields it reads, so the panel cannot tell whether it \
119                 shows a restricted one: declare them with `.reading(Reads::of(..))`, or \
120                 implement `reads` on a custom {what}"
121            ),
122            Self::UnmaskedRelation { name, model } => write!(
123                f,
124                "`{name}` reads `{model}` records under no mounted resource's field access, and a \
125                 mounted resource restricts a field of `{model}`: read them through a resource, \
126                 with `RelationColumn::new::<R>` naming the fields it reads or `RelationColumn::of::<R>` \
127                 for their title, and have a computed or custom column, a text column's closure, a row \
128                 color or a row link declare it reads only the row's own fields, or keep it off a \
129                 page that loads them"
130            ),
131            Self::NarrowedBy { field } => write!(
132                f,
133                "a dependent choice narrows its options by `{field}`, which a mounted resource \
134                 restricts, so the options would show which records hold each value of it: narrow \
135                 by a field every user may view"
136            ),
137            Self::UnresolvedRelation { name } => write!(
138                f,
139                "`{name}` reads a relation the mount cannot read off the model's schema, so it \
140                 cannot show that no restricted record reaches it"
141            ),
142        }
143    }
144}