Skip to main content

anvil_api/
lib.rs

1//! Generated Rust types for the Anvil 0.5 gRPC API.
2
3pub mod v1 {
4    tonic::include_proto!("anvil.v1");
5}
6
7#[cfg(test)]
8mod tests {
9    use super::v1::{DeletedObject, NeverExisted, ObjectHead, PresentObject, object_head};
10
11    #[test]
12    fn exact_path_states_are_distinct() {
13        let states = [
14            ObjectHead {
15                state: Some(object_head::State::Present(PresentObject {
16                    version: 7,
17                    content_hash: vec![7; 32],
18                    content_length: 99,
19                    content_type: String::new(),
20                })),
21            },
22            ObjectHead {
23                state: Some(object_head::State::Deleted(DeletedObject { version: 8 })),
24            },
25            ObjectHead {
26                state: Some(object_head::State::NeverExisted(NeverExisted {})),
27            },
28        ];
29
30        assert!(matches!(
31            &states[0].state,
32            Some(object_head::State::Present(_))
33        ));
34        assert!(matches!(
35            &states[1].state,
36            Some(object_head::State::Deleted(_))
37        ));
38        assert!(matches!(
39            &states[2].state,
40            Some(object_head::State::NeverExisted(_))
41        ));
42    }
43
44    #[test]
45    fn schema_keeps_removed_capabilities_out() {
46        let schema = include_str!("../proto/anvil.proto").to_ascii_lowercase();
47        for forbidden in [
48            "rpc uploadblob",
49            "rpc publishobject",
50            "rpc putobject",
51            "message blobref",
52            "rpc listprefix",
53            "rpc begintransaction",
54            "rpc committransaction",
55            "personaldb",
56        ] {
57            assert!(!schema.contains(forbidden), "schema contains `{forbidden}`");
58        }
59
60        assert!(schema.contains("executor_nomination_log_index"));
61        assert!(schema.contains("commit_log_index"));
62        assert!(schema.contains("immutable_path_prefixes"));
63        assert!(schema.contains("program_only_path_prefixes"));
64        assert!(!schema.contains("rpc registerprogram"));
65        assert!(!schema.contains("message registerprogram"));
66        assert!(schema.contains("objectaddress program"));
67        assert!(schema.contains("_anvil/programs/{name}@{version}"));
68        assert!(schema.contains("rpc startput(putheader) returns (puttoken)"));
69        assert!(schema.contains("rpc put(stream putrequest) returns (puttoken)"));
70        assert!(schema.contains("rpc putend(puttoken) returns (mutationreceipt)"));
71        for rpc in [
72            "rpc createindex(createindexrequest)",
73            "rpc updateindex(updateindexrequest)",
74            "rpc getindex(getindexrequest)",
75            "rpc listindexes(listindexesrequest)",
76            "rpc deleteindex(deleteindexrequest)",
77            "rpc queryindex(queryindexrequest)",
78        ] {
79            assert!(schema.contains(rpc), "schema is missing `{rpc}`");
80        }
81        assert!(schema.contains("index_kind_tensor"));
82        assert!(schema.contains("tensorindexspec tensor"));
83        assert!(schema.contains("tensorindexquery tensor"));
84
85        for rpc in [
86            "rpc exchangeclientcredentials",
87            "rpc provisiontenant",
88            "rpc createapplication",
89            "rpc rotateapplicationcredential",
90            "rpc disableapplicationcredential",
91            "rpc createbucket",
92            "rpc grantapplicationrole",
93            "rpc revokeapplicationrole",
94            "rpc putschema",
95            "rpc bindschema",
96            "rpc getbinding",
97            "rpc getschema",
98            "rpc mutatetuples",
99            "rpc readtuples",
100            "rpc checkpermission",
101            "rpc checkpermissions",
102            "rpc watchprefix",
103            "rpc listobjects",
104            "rpc deleteversion",
105            "rpc listobjectversions",
106            "rpc setbucketversioning",
107        ] {
108            assert!(schema.contains(rpc), "schema is missing `{rpc}`");
109        }
110        for forbidden in [
111            "rpc createrealm",
112            "rpc deleterealm",
113            "rpc applyschema",
114            "zookie",
115            "caveat",
116            "publication_metadata",
117            "insecure_no_auth",
118            "api_token",
119        ] {
120            assert!(!schema.contains(forbidden), "schema contains `{forbidden}`");
121        }
122    }
123
124    #[test]
125    fn generated_index_client_is_publicly_exposed() {
126        let _: Option<
127            super::v1::index_service_client::IndexServiceClient<tonic::transport::Channel>,
128        > = None;
129    }
130
131    #[test]
132    fn object_surface_has_only_explicit_typed_mutations() {
133        use super::v1::{
134            BulkOperation, BulkPutIfVersionRequest, CreateBucketRequest, DeleteIfVersionRequest,
135            DeleteRequest, DeleteVersionRequest, DeleteVersionResponse, Durability,
136            ListObjectsRequest, ListObjectsResponse, ObjectAddress, ObjectVersioning, PutHeader,
137            PutIfVersionOperation, PutRequest, PutToken, bulk_operation, put_header,
138        };
139
140        let address = Some(ObjectAddress {
141            tenant: "acme".into(),
142            bucket: "objects".into(),
143            path: "one".into(),
144        });
145        let header = PutHeader {
146            address: address.clone(),
147            content_type: "application/json".into(),
148            command_id: "command-1".into(),
149            durability: Durability::Local as i32,
150            operation: Some(put_header::Operation::PutIfVersion(PutIfVersionOperation {
151                expected_version: 8,
152            })),
153        };
154        let frame = PutRequest {
155            token: Some(PutToken {
156                value: b"opaque".to_vec(),
157                expires_at: None,
158            }),
159            chunk: Vec::new(),
160        };
161        assert!(matches!(
162            header.operation,
163            Some(put_header::Operation::PutIfVersion(_))
164        ));
165        assert!(frame.chunk.is_empty());
166
167        let operations = [
168            bulk_operation::Operation::Put(Default::default()),
169            bulk_operation::Operation::PutIfAbsent(Default::default()),
170            bulk_operation::Operation::PutIfVersion(BulkPutIfVersionRequest::default()),
171            bulk_operation::Operation::PutImmutable(Default::default()),
172            bulk_operation::Operation::Delete(DeleteRequest {
173                address: address.clone(),
174                ..Default::default()
175            }),
176            bulk_operation::Operation::DeleteIfVersion(DeleteIfVersionRequest {
177                address: address.clone(),
178                expected_version: 8,
179                ..Default::default()
180            }),
181        ];
182        assert_eq!(
183            operations
184                .into_iter()
185                .map(|operation| BulkOperation {
186                    operation: Some(operation),
187                })
188                .count(),
189            6
190        );
191
192        let current_head_delete = DeleteIfVersionRequest {
193            address: address.clone(),
194            expected_version: 8,
195            ..Default::default()
196        };
197        let retained_version_delete = DeleteVersionRequest {
198            address,
199            version: 7,
200            ..Default::default()
201        };
202        assert_eq!(current_head_delete.expected_version, 8);
203        assert_eq!(retained_version_delete.version, 7);
204        let replaced_current = DeleteVersionResponse {
205            deleted: true,
206            replacement_tombstone_version: Some(9),
207        };
208        assert_eq!(replaced_current.replacement_tombstone_version, Some(9));
209        assert_eq!(
210            CreateBucketRequest::default().versioning,
211            ObjectVersioning::Unversioned as i32
212        );
213
214        let list_request = ListObjectsRequest {
215            tenant: "acme".into(),
216            bucket: "objects".into(),
217            prefix: "reports/".into(),
218            start_after: Some("reports/2025.json".into()),
219            limit: 100,
220        };
221        let list_response = ListObjectsResponse {
222            paths: vec!["reports/2026.json".into()],
223            has_more: false,
224        };
225        assert_eq!(
226            list_request.start_after.as_deref(),
227            Some("reports/2025.json")
228        );
229        assert_eq!(list_response.paths, vec!["reports/2026.json".to_owned()]);
230    }
231
232    #[test]
233    fn authorization_wire_types_preserve_scope_and_typed_unions() {
234        use super::v1::{
235            AnyUsersetSelector, AtLeastRevision, AuthzConsistency, AuthzScope, DirectRelation,
236            InheritRule, NamespaceDefinition, ObjectRef, Permission, PermissionRule,
237            PutSchemaRequest, RelationDefinition, SubjectSelector, Userset, authz_consistency,
238            object_ref, permission_rule, relation_definition, subject, subject_selector,
239        };
240
241        let account = ObjectRef {
242            namespace: "account".into(),
243            id: Some(object_ref::Id::OpaqueId("acme".into())),
244        };
245        let members = Userset {
246            object: Some(account),
247            relation: "member".into(),
248        };
249        let schema = NamespaceDefinition {
250            name: "ledger".into(),
251            relations: vec![
252                RelationDefinition {
253                    name: "reader".into(),
254                    kind: Some(relation_definition::Kind::Direct(DirectRelation {
255                        allowed_subjects: vec![SubjectSelector {
256                            selector: Some(subject_selector::Selector::AnyUserset(
257                                AnyUsersetSelector {
258                                    namespace: "account".into(),
259                                    relation: "member".into(),
260                                },
261                            )),
262                        }],
263                    })),
264                },
265                RelationDefinition {
266                    name: "read".into(),
267                    kind: Some(relation_definition::Kind::Permission(Permission {
268                        rules: vec![PermissionRule {
269                            rule: Some(permission_rule::Rule::Inherit(InheritRule {
270                                relation: "reader".into(),
271                            })),
272                        }],
273                    })),
274                },
275            ],
276        };
277        let subject = super::v1::Subject {
278            kind: Some(subject::Kind::Userset(members)),
279        };
280        let publication = PutSchemaRequest {
281            schema_id: "acme".into(),
282            namespaces: vec![schema],
283        };
284        let consistency = AuthzConsistency {
285            requirement: Some(authz_consistency::Requirement::AtLeast(AtLeastRevision {
286                revision: 42,
287            })),
288        };
289        let system_scope = AuthzScope {
290            storage_tenant: "anvil-internal".into(),
291            realm: "_anvil/system".into(),
292        };
293
294        assert_eq!(publication.namespaces[0].relations.len(), 2);
295        assert!(matches!(subject.kind, Some(subject::Kind::Userset(_))));
296        assert!(matches!(
297            consistency.requirement,
298            Some(authz_consistency::Requirement::AtLeast(AtLeastRevision {
299                revision: 42
300            }))
301        ));
302        assert_eq!(system_scope.realm, "_anvil/system");
303    }
304
305    #[test]
306    fn tuple_mutation_and_batch_checks_share_request_scope() {
307        use super::v1::{
308            AuthzScope, CheckPermissionsRequest, MutateTuplesRequest, ObjectRef, PermissionCheck,
309            RelationTuple, Subject, TupleMutation, authz_consistency, object_ref, subject,
310            tuple_mutation,
311        };
312
313        let scope = AuthzScope {
314            storage_tenant: "acme".into(),
315            realm: "default".into(),
316        };
317        let ledger = ObjectRef {
318            namespace: "ledger".into(),
319            id: Some(object_ref::Id::OpaqueId("main".into())),
320        };
321        let alice = Subject {
322            kind: Some(subject::Kind::Object(ObjectRef {
323                namespace: "user".into(),
324                id: Some(object_ref::Id::OpaqueId("alice".into())),
325            })),
326        };
327        let tuple = RelationTuple {
328            object: Some(ledger.clone()),
329            relation: "reader".into(),
330            subject: Some(alice.clone()),
331        };
332        let mutation = MutateTuplesRequest {
333            scope: Some(scope.clone()),
334            operation_id: "grant-alice".into(),
335            expected_revision: Some(41),
336            mutations: vec![TupleMutation {
337                operation: Some(tuple_mutation::Operation::Add(tuple)),
338            }],
339        };
340        let checks = CheckPermissionsRequest {
341            scope: Some(scope),
342            checks: vec![PermissionCheck {
343                subject: Some(alice),
344                object: Some(ledger),
345                relation: "read".into(),
346            }],
347            consistency: Some(super::v1::AuthzConsistency {
348                requirement: Some(authz_consistency::Requirement::Exact(
349                    super::v1::ExactRevision { revision: 42 },
350                )),
351            }),
352        };
353
354        assert_eq!(mutation.expected_revision, Some(41));
355        assert_eq!(mutation.mutations.len(), 1);
356        assert_eq!(checks.checks.len(), 1);
357        assert!(matches!(
358            checks
359                .consistency
360                .and_then(|consistency| consistency.requirement),
361            Some(authz_consistency::Requirement::Exact(_))
362        ));
363    }
364}