Line data Source code
1 : // SPDX-License-Identifier: EUPL-1.2
2 : //! Distributed operation names (CIM 009 clause 4.20).
3 : //!
4 : //! Table 4.20-1 names every API operation a Context Source may declare in the
5 : //! 5.2.9 `operations` member; Table 4.20-2 names five groups that each stand
6 : //! for a set of them. The vocabulary lives here because two independent
7 : //! places read it — the registration validator that decides which names are
8 : //! accepted, and the match index that decides which registrations a
9 : //! distributed operation reaches. A name present in one and absent from the
10 : //! other is a registration the API takes and the broker never forwards to.
11 :
12 : /// Table 4.20-1: every named API operation, in the table's own order.
13 : ///
14 : /// The order is APPEND-ONLY. A backend may store a set of these as a bitmask
15 : /// whose bit position is the index (the Postgres `csource_index.ops` column
16 : /// does), so reordering or removing a name rewrites the meaning of rows
17 : /// already written: it is a migration, never an edit.
18 : pub const OPERATION_NAMES: &[&str] = &[
19 : "createEntity",
20 : "updateEntity",
21 : "appendAttrs",
22 : "updateAttrs",
23 : "deleteAttrs",
24 : "deleteEntity",
25 : "createBatch",
26 : "upsertBatch",
27 : "updateBatch",
28 : "deleteBatch",
29 : "upsertTemporal",
30 : "appendAttrsTemporal",
31 : "deleteAttrsTemporal",
32 : "updateAttrInstanceTemporal",
33 : "deleteAttrInstanceTemporal",
34 : "deleteTemporal",
35 : "mergeEntity",
36 : "replaceEntity",
37 : "replaceAttrs",
38 : "mergeBatch",
39 : "purgeEntity",
40 : "retrieveEntity",
41 : "queryEntity",
42 : "queryBatch",
43 : "retrieveTemporal",
44 : "queryTemporal",
45 : "retrieveEntityTypes",
46 : "retrieveEntityTypeDetails",
47 : "retrieveEntityTypeInfo",
48 : "retrieveAttrTypes",
49 : "retrieveAttrTypeDetails",
50 : "retrieveAttrTypeInfo",
51 : "createSubscription",
52 : "updateSubscription",
53 : "retrieveSubscription",
54 : "querySubscription",
55 : "deleteSubscription",
56 : "retrieveEntityMap",
57 : "updateEntityMap",
58 : "deleteEntityMap",
59 : "createEntityMapQueryEntity",
60 : "createEntityMapQueryTemporal",
61 : "retrieveContextSourceIdentity",
62 : ];
63 :
64 : /// Table 4.20-2: the five group names. They are legal `operations` values
65 : /// alongside the individual names of Table 4.20-1 and are expanded to their
66 : /// members before an operation is matched against a registration.
67 : pub const OPERATION_GROUPS: &[&str] = &[
68 : "federationOps",
69 : "associationOps",
70 : "updateOps",
71 : "retrieveOps",
72 : "redirectionOps",
73 : ];
74 :
75 : /// 4.20: "If no specific subset of operations is defined for a Context Source
76 : /// Registration, the default set of operations matches the group defined as
77 : /// federationOps."
78 : pub const DEFAULT_OPERATION_GROUP: &str = "federationOps";
79 :
80 : /// The Table 4.20-2 members of one group name, or `None` when the name is not
81 : /// a group (an individual operation name, or a name outside the vocabulary).
82 80783 : pub fn group_members(name: &str) -> Option<&'static [&'static str]> {
83 : /// federationOps: the consumption and subscription operations plus the
84 : /// EntityMap support operations, minus createEntityMapQueryTemporal.
85 : const FEDERATION: &[&str] = &[
86 : "retrieveEntity",
87 : "queryEntity",
88 : "queryBatch",
89 : "retrieveEntityTypes",
90 : "retrieveEntityTypeDetails",
91 : "retrieveEntityTypeInfo",
92 : "retrieveAttrTypes",
93 : "retrieveAttrTypeDetails",
94 : "retrieveAttrTypeInfo",
95 : "createSubscription",
96 : "updateSubscription",
97 : "retrieveSubscription",
98 : "querySubscription",
99 : "deleteSubscription",
100 : "retrieveEntityMap",
101 : "updateEntityMap",
102 : "deleteEntityMap",
103 : "createEntityMapQueryEntity",
104 : "retrieveContextSourceIdentity",
105 : ];
106 : /// associationOps: federationOps WITHOUT the EntityMap support operations.
107 : const ASSOCIATION: &[&str] = &[
108 : "retrieveEntity",
109 : "queryEntity",
110 : "queryBatch",
111 : "retrieveEntityTypes",
112 : "retrieveEntityTypeDetails",
113 : "retrieveEntityTypeInfo",
114 : "retrieveAttrTypes",
115 : "retrieveAttrTypeDetails",
116 : "retrieveAttrTypeInfo",
117 : "createSubscription",
118 : "updateSubscription",
119 : "retrieveSubscription",
120 : "querySubscription",
121 : "deleteSubscription",
122 : "retrieveContextSourceIdentity",
123 : ];
124 : const UPDATE: &[&str] = &[
125 : "updateEntity",
126 : "updateAttrs",
127 : "replaceEntity",
128 : "replaceAttrs",
129 : ];
130 : const RETRIEVE: &[&str] = &["retrieveEntity", "queryEntity"];
131 : const REDIRECTION: &[&str] = &[
132 : "createEntity",
133 : "updateEntity",
134 : "appendAttrs",
135 : "updateAttrs",
136 : "deleteAttrs",
137 : "deleteEntity",
138 : "mergeEntity",
139 : "replaceEntity",
140 : "replaceAttrs",
141 : "retrieveEntity",
142 : "queryEntity",
143 : "purgeEntity",
144 : "retrieveEntityTypes",
145 : "retrieveEntityTypeDetails",
146 : "retrieveEntityTypeInfo",
147 : "retrieveAttrTypes",
148 : "retrieveAttrTypeDetails",
149 : "retrieveAttrTypeInfo",
150 : "retrieveEntityMap",
151 : "updateEntityMap",
152 : "deleteEntityMap",
153 : "createEntityMapQueryEntity",
154 : "retrieveContextSourceIdentity",
155 : ];
156 80783 : match name {
157 80783 : "federationOps" => Some(FEDERATION),
158 30000 : "associationOps" => Some(ASSOCIATION),
159 29956 : "updateOps" => Some(UPDATE),
160 29934 : "retrieveOps" => Some(RETRIEVE),
161 28418 : "redirectionOps" => Some(REDIRECTION),
162 25744 : _ => None,
163 : }
164 80783 : }
165 :
166 : #[cfg(test)]
167 : mod tests {
168 : use super::*;
169 :
170 : /// Table 4.20-1 as printed: 43 names, no repeats, and the four sections
171 : /// in the table's order (provision, consumption, subscription, support).
172 : #[test]
173 2 : fn table_4_20_1_is_the_whole_vocabulary() {
174 2 : assert_eq!(OPERATION_NAMES.len(), 43);
175 2 : let mut sorted = OPERATION_NAMES.to_vec();
176 2 : sorted.sort_unstable();
177 2 : sorted.dedup();
178 2 : assert_eq!(sorted.len(), OPERATION_NAMES.len(), "duplicate name");
179 2 : assert_eq!(OPERATION_NAMES[0], "createEntity");
180 2 : assert_eq!(OPERATION_NAMES[20], "purgeEntity");
181 2 : assert_eq!(OPERATION_NAMES[21], "retrieveEntity");
182 2 : assert_eq!(OPERATION_NAMES[32], "createSubscription");
183 2 : assert_eq!(OPERATION_NAMES[37], "retrieveEntityMap");
184 2 : assert_eq!(OPERATION_NAMES[42], "retrieveContextSourceIdentity");
185 2 : }
186 :
187 : /// A group expands only to operations of Table 4.20-1, and a group name
188 : /// is never itself an operation name — the two value spaces are
189 : /// disjoint, which is what lets a validator accept both lists.
190 : #[test]
191 2 : fn table_4_20_2_groups_expand_within_the_vocabulary() {
192 10 : for g in OPERATION_GROUPS {
193 10 : assert!(
194 10 : !OPERATION_NAMES.contains(g),
195 : "{g} is both a group and an operation"
196 : );
197 10 : let members = group_members(g).unwrap_or_else(|| panic!("{g} has no members"));
198 10 : assert!(!members.is_empty());
199 126 : for m in members {
200 126 : assert!(OPERATION_NAMES.contains(m), "{g} expands to unknown {m}");
201 : }
202 : }
203 2 : assert_eq!(group_members("federationOps").map(<[&str]>::len), Some(19));
204 2 : assert_eq!(group_members("associationOps").map(<[&str]>::len), Some(15));
205 2 : assert_eq!(group_members("updateOps").map(<[&str]>::len), Some(4));
206 2 : assert_eq!(group_members("retrieveOps").map(<[&str]>::len), Some(2));
207 2 : assert_eq!(group_members("redirectionOps").map(<[&str]>::len), Some(23));
208 2 : assert_eq!(group_members("retrieveEntity"), None);
209 2 : assert_eq!(group_members("notAGroup"), None);
210 2 : }
211 :
212 : /// Table 4.20-2: associationOps is federationOps without the EntityMap
213 : /// support operations, and createEntityMapQueryTemporal is in no group.
214 : #[test]
215 2 : fn association_is_federation_without_the_entity_maps() {
216 2 : let fed = group_members("federationOps").expect("federationOps");
217 2 : let assoc = group_members("associationOps").expect("associationOps");
218 38 : let only_fed: Vec<_> = fed.iter().filter(|m| !assoc.contains(m)).copied().collect();
219 2 : assert_eq!(
220 : only_fed,
221 : [
222 : "retrieveEntityMap",
223 : "updateEntityMap",
224 : "deleteEntityMap",
225 : "createEntityMapQueryEntity"
226 : ]
227 : );
228 10 : for g in OPERATION_GROUPS {
229 10 : let members = group_members(g).expect("group");
230 10 : assert!(!members.contains(&"createEntityMapQueryTemporal"), "{g}");
231 : }
232 2 : }
233 :
234 : /// The default an absent `operations` member stands for is a group name,
235 : /// so both readers expand it the same way.
236 : #[test]
237 2 : fn the_default_is_a_group() {
238 2 : assert!(OPERATION_GROUPS.contains(&DEFAULT_OPERATION_GROUP));
239 2 : assert!(group_members(DEFAULT_OPERATION_GROUP).is_some());
240 2 : }
241 : }
|