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