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 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 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 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 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 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 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}