1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//! Rejection, by name, of field attributes that must never be inert:
//! attributes the language used to accept and no longer does, and
//! attributes that read as access control but were never wired up at field
//! position in the first place.
//!
//! `.cstack` attributes parse generically into an opaque
//! `Attribute { raw, span }` (see `crate::parse::fields`), and there is no
//! blanket "unknown attribute" rejection pass — an unrecognised attribute is
//! simply inert. That default is fine for an attribute that never existed,
//! but it is the wrong answer here:
//!
//! - a schema carrying `@pb(3)` from before 0.8.5 would keep parsing after
//! the 0.8.5 protobuf removal while silently meaning nothing, and the
//! author would get no signal that the pins they wrote are now dead text.
//! - `@allow(...)` / `@deny(...)` at field position parse and look exactly
//! like the real, supported policy attributes of the same name at
//! *procedure* position (`cratestack-macros/src/policy/procedure.rs`) and
//! *model/view* position as `@@allow`/`@@deny` (double-`@`,
//! `cratestack-macros/src/policy/model.rs`) — but no codegen reads a
//! single-`@` `@allow`/`@deny` off a *field*. A schema author reading
//! `bucket String @allow(auth().role == "system")` gets `schema OK` and
//! reasonably believes the field is access-controlled; it is not
//! (cratestack#679). Field-level read policy isn't implemented, so the
//! only honest outcome is a loud parse error naming the real
//! alternatives, not silent acceptance.
//!
//! So both classes are rejected by name, individually, here. This is
//! deliberately not a generic unknown-attribute pass: adding one would
//! change the behaviour of every attribute the validators intentionally
//! ignore today, which is a far larger and unrelated language change (see
//! cratestack#679's discussion — that issue's typo-class half, e.g.
//! `@raedonly` silently dropping `@readonly`, is intentionally NOT addressed
//! by this module).
//!
//! **When adding an entry to [`REJECTED_FIELD_ATTRIBUTES`], check every call
//! site.** Because this is opt-in per declaration kind rather than a single
//! central pass, a missed call site fails *silently* — the attribute goes
//! back to being inert, which is the exact bug this module exists to
//! prevent. There are five, one per field-bearing declaration: `model` and
//! `view` (`validate::models`, `validate::views`), and `mixin`, `type`, and
//! the `auth` block (all three in `validate::mixins_types`). Enum variants
//! are not one: `cratestack_core::EnumVariant` carries no attributes.
//! `tests_field_attrs::pb_field_attribute_is_rejected_on_every_field_bearing_declaration`
//! and its `@allow`/`@deny` counterparts cover all five and are the guard
//! against a sixth being added without a matching call.
use Field;
use crate;
/// Attributes rejected at field position, with the guidance shown when one
/// is still present in a schema.
///
/// Keyed by bare attribute name; matching also covers the `@name(...)`
/// argument form. Matching is on the bare single-`@` name exactly (or that
/// name followed by `(`), so it does not touch the double-`@` model/view
/// policy forms (`@@allow`, `@@deny`), which are unrelated, real, supported
/// attributes on a different declaration's attribute list.
const REJECTED_FIELD_ATTRIBUTES: & = &;
pub
// Tests live in `crate::tests_field_attrs` rather than here: they go
// through `parse_schema`, which exercises all five call sites (model, view,
// mixin, type, auth block) and the real user-facing message, instead of the
// helper in isolation.