spicedb_grpc/
lib.rs

1#![doc = include_str!("../README.md")]
2
3pub mod authzed {
4    pub mod api {
5        pub mod v1 {
6            include!("gen/authzed.api.v1.rs");
7        }
8    }
9}
10pub mod google {
11    pub mod rpc {
12        include!("gen/google.api.rs");
13        include!("gen/google.rpc.rs");
14    }
15}
16pub mod validate {
17    include!("gen/validate.rs");
18}
19
20#[cfg(test)]
21mod test {
22    use std::env;
23
24    use tokio::test;
25    use tonic::metadata::MetadataValue;
26    use tonic::transport::Channel;
27    use tonic::Request;
28
29    use crate::authzed::api::v1::check_debug_trace::Permissionship;
30    use crate::authzed::api::v1::consistency::Requirement;
31    use crate::authzed::api::v1::permissions_service_client::PermissionsServiceClient;
32    use crate::authzed::api::v1::schema_service_client::SchemaServiceClient;
33    use crate::authzed::api::v1::{
34        relationship_update, CheckPermissionRequest, Consistency, DeleteRelationshipsRequest,
35        ObjectReference, ReadRelationshipsRequest, ReadSchemaRequest, Relationship,
36        RelationshipFilter, RelationshipUpdate, SubjectReference, WriteRelationshipsRequest,
37        WriteSchemaRequest,
38    };
39
40    #[test]
41    pub async fn test_spicedb() {
42        let spicedb_url =
43            env::var("SPICEDB_URL").unwrap_or_else(|_| "http://localhost:50051".to_string());
44
45        let preshared_key =
46            env::var("SPICEDB_PRESHARED_KEY").unwrap_or_else(|_| "spicedb".to_string());
47        let preshared_key: MetadataValue<_> = format!("bearer {preshared_key}").parse().unwrap();
48
49        let channel = Channel::from_shared(spicedb_url)
50            .unwrap()
51            .connect()
52            .await
53            .unwrap();
54
55        let interceptor = move |mut req: Request<()>| {
56            req.metadata_mut()
57                .insert("authorization", preshared_key.clone());
58            Ok(req)
59        };
60
61        let mut schemas =
62            SchemaServiceClient::with_interceptor(channel.clone(), interceptor.clone());
63
64        let mut permissions =
65            PermissionsServiceClient::with_interceptor(channel.clone(), interceptor.clone());
66
67        let schema = r#"
68definition user {}
69
70definition document {
71    relation viewer: user
72    relation editor: user
73
74    permission view = viewer + editor
75    permission edit = editor
76}
77"#;
78
79        // Write schema
80        let response = schemas
81            .write_schema(WriteSchemaRequest {
82                schema: schema.to_string(),
83            })
84            .await
85            .unwrap()
86            .into_inner();
87        assert!(response.written_at.is_some());
88
89        // Read schema
90        let response = schemas
91            .read_schema(ReadSchemaRequest {})
92            .await
93            .unwrap()
94            .into_inner();
95        assert_eq!(
96            response.schema_text.split_whitespace().collect::<Vec<_>>(),
97            schema.split_whitespace().collect::<Vec<_>>()
98        );
99        assert!(response.read_at.is_some());
100
101        let doc1 = ObjectReference {
102            object_type: "document".to_string(),
103            object_id: "doc1".to_string(),
104        };
105
106        let user1 = ObjectReference {
107            object_type: "user".to_string(),
108            object_id: "user1".to_string(),
109        };
110
111        let relationship = Relationship {
112            resource: Some(doc1.clone()),
113            relation: "viewer".to_string(),
114            subject: Some(SubjectReference {
115                object: Some(user1.clone()),
116                optional_relation: "".to_string(),
117            }),
118            optional_caveat: None,
119        };
120
121        // Write relationship
122        let response = permissions
123            .write_relationships(WriteRelationshipsRequest {
124                updates: vec![RelationshipUpdate {
125                    operation: relationship_update::Operation::Touch.into(),
126                    relationship: Some(relationship.clone()),
127                }],
128                optional_preconditions: vec![],
129            })
130            .await
131            .unwrap()
132            .into_inner();
133        assert!(response.written_at.is_some());
134
135        let relationship_filter = RelationshipFilter {
136            resource_type: "document".to_string(),
137            optional_resource_id: "doc1".to_string(),
138            optional_resource_id_prefix: "".to_string(),
139            optional_relation: "viewer".to_string(),
140            optional_subject_filter: None,
141        };
142
143        let fully_consistent = Some(Consistency {
144            requirement: Some(Requirement::FullyConsistent(true)),
145        });
146
147        // Read relationship
148        let mut response = permissions
149            .read_relationships(ReadRelationshipsRequest {
150                consistency: fully_consistent.clone(),
151                relationship_filter: Some(relationship_filter.clone()),
152                optional_limit: 0,
153                optional_cursor: None,
154            })
155            .await
156            .unwrap()
157            .into_inner();
158        let relation = response.message().await.unwrap().unwrap();
159        assert!(relation.read_at.is_some());
160        assert_eq!(relation.relationship.unwrap(), relationship);
161        assert!(relation.after_result_cursor.is_some());
162        assert!(response.message().await.unwrap().is_none());
163
164        // Check permission
165        let response = permissions
166            .check_permission(CheckPermissionRequest {
167                consistency: fully_consistent.clone(),
168                resource: Some(doc1.clone()),
169                permission: "viewer".to_string(),
170                subject: Some(SubjectReference {
171                    object: Some(user1.clone()),
172                    optional_relation: "".to_string(),
173                }),
174                context: None,
175                with_tracing: false,
176            })
177            .await
178            .unwrap()
179            .into_inner();
180        assert!(response.checked_at.is_some());
181        assert_eq!(
182            response.permissionship,
183            Permissionship::HasPermission.into()
184        );
185
186        // Delete relationship
187        let response = permissions
188            .delete_relationships(DeleteRelationshipsRequest {
189                relationship_filter: Some(relationship_filter.clone()),
190                optional_preconditions: vec![],
191                optional_limit: 0,
192                optional_allow_partial_deletions: false,
193            })
194            .await
195            .unwrap()
196            .into_inner();
197        assert!(response.deleted_at.is_some());
198    }
199}