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}