1use bytes::Bytes;
2use spicedb_grpc::authzed::api::v1::{
3 permissions_service_client::PermissionsServiceClient,
4 schema_service_client::SchemaServiceClient, watch_service_client::WatchServiceClient, *,
5};
6use tonic::{
7 metadata::{Ascii, MetadataValue},
8 service::{interceptor::InterceptedService, Interceptor},
9 transport::Channel,
10 Request, Status, Streaming,
11};
12
13use crate::result::Result;
14
15#[derive(Clone, Debug)]
17pub struct SpicedbClient {
18 pub channel: Channel,
19 schemas: SchemaServiceClient<InterceptedService<Channel, SpicedbMiddleware>>,
20 permissions: PermissionsServiceClient<InterceptedService<Channel, SpicedbMiddleware>>,
21 watch: WatchServiceClient<InterceptedService<Channel, SpicedbMiddleware>>,
22}
23
24impl SpicedbClient {
25 pub async fn from_url_and_preshared_key(
38 url: impl Into<Bytes>,
39 preshared_key: impl ToString,
40 ) -> Result<Self> {
41 let interceptor = SpicedbMiddleware {
42 preshared_key: Box::new(format!("bearer {}", preshared_key.to_string()).parse()?),
43 };
44
45 let channel = Channel::from_shared(url)?.connect().await?;
46
47 let schemas = SchemaServiceClient::with_interceptor(channel.clone(), interceptor.clone());
48
49 let permissions =
50 PermissionsServiceClient::with_interceptor(channel.clone(), interceptor.clone());
51
52 let watch = WatchServiceClient::with_interceptor(channel.clone(), interceptor.clone());
53
54 Ok(SpicedbClient {
55 channel,
56 schemas,
57 permissions,
58 watch,
59 })
60 }
61
62 pub async fn read_schema(&mut self) -> Result<ReadSchemaResponse> {
68 let response = self
69 .schemas
70 .read_schema(ReadSchemaRequest {})
71 .await
72 .unwrap()
73 .into_inner();
74
75 Ok(response)
76 }
77
78 pub async fn write_schema(&mut self, schema: impl ToString) -> Result<WriteSchemaResponse> {
80 let response = self
81 .schemas
82 .write_schema(WriteSchemaRequest {
83 schema: schema.to_string(),
84 })
85 .await?
86 .into_inner();
87
88 Ok(response)
89 }
90
91 pub async fn read_relationships(
93 &mut self,
94 request: ReadRelationshipsRequest,
95 ) -> Result<Streaming<ReadRelationshipsResponse>> {
96 let stream = self
97 .permissions
98 .read_relationships(request)
99 .await?
100 .into_inner();
101
102 Ok(stream)
103 }
104
105 pub async fn write_relationships(
109 &mut self,
110 request: WriteRelationshipsRequest,
111 ) -> Result<WriteRelationshipsResponse> {
112 let response = self
113 .permissions
114 .write_relationships(request)
115 .await?
116 .into_inner();
117
118 Ok(response)
119 }
120
121 pub async fn delete_relationships(
126 &mut self,
127 request: DeleteRelationshipsRequest,
128 ) -> Result<DeleteRelationshipsResponse> {
129 let response = self
130 .permissions
131 .delete_relationships(request)
132 .await?
133 .into_inner();
134
135 Ok(response)
136 }
137
138 pub async fn check_permission(
141 &mut self,
142 request: CheckPermissionRequest,
143 ) -> Result<CheckPermissionResponse> {
144 let response = self
145 .permissions
146 .check_permission(request)
147 .await?
148 .into_inner();
149
150 Ok(response)
151 }
152
153 pub async fn check_bulk_permissions(
155 &mut self,
156 request: CheckBulkPermissionsRequest,
157 ) -> Result<CheckBulkPermissionsResponse> {
158 let response = self
159 .permissions
160 .check_bulk_permissions(request)
161 .await?
162 .into_inner();
163
164 Ok(response)
165 }
166
167 pub async fn expand_permission_tree(
171 &mut self,
172 request: ExpandPermissionTreeRequest,
173 ) -> Result<ExpandPermissionTreeResponse> {
174 let response = self
175 .permissions
176 .expand_permission_tree(request)
177 .await?
178 .into_inner();
179
180 Ok(response)
181 }
182
183 pub async fn lookup_resources(
186 &mut self,
187 request: LookupResourcesRequest,
188 ) -> Result<Streaming<LookupResourcesResponse>> {
189 let response = self
190 .permissions
191 .lookup_resources(request)
192 .await?
193 .into_inner();
194
195 Ok(response)
196 }
197
198 pub async fn lookup_subjects(
201 &mut self,
202 request: LookupSubjectsRequest,
203 ) -> Result<Streaming<LookupSubjectsResponse>> {
204 let response = self
205 .permissions
206 .lookup_subjects(request)
207 .await?
208 .into_inner();
209
210 Ok(response)
211 }
212
213 pub async fn watch(&mut self, request: WatchRequest) -> Result<Streaming<WatchResponse>> {
224 let response = self.watch.watch(request).await?.into_inner();
225
226 Ok(response)
227 }
228}
229
230#[derive(Clone)]
231struct SpicedbMiddleware {
232 preshared_key: Box<MetadataValue<Ascii>>,
233}
234
235impl Interceptor for SpicedbMiddleware {
236 fn call(&mut self, mut request: Request<()>) -> Result<tonic::Request<()>, Status> {
237 request
238 .metadata_mut()
239 .insert("authorization", (*self.preshared_key).clone());
240 Ok(request)
241 }
242}
243
244#[cfg(test)]
245mod test {
246 use std::env;
247
248 use tokio::test;
249
250 use crate::reader::*;
251
252 use super::*;
253
254 #[test]
255 pub async fn test_spicedb() {
256 let spicedb_url =
257 env::var("SPICEDB_URL").unwrap_or_else(|_| "http://localhost:50051".to_string());
258
259 let preshared_key =
260 env::var("SPICEDB_PRESHARED_KEY").unwrap_or_else(|_| "spicedb".to_string());
261
262 let mut client = SpicedbClient::from_url_and_preshared_key(spicedb_url, preshared_key)
263 .await
264 .unwrap();
265
266 let schema = r#"
267definition user {}
268
269definition document {
270 relation viewer: user
271 relation editor: user
272
273 permission view = viewer + editor
274 permission edit = editor
275}
276"#;
277
278 let response = client.write_schema(schema).await.unwrap();
280 assert!(response.written_at().is_some());
281
282 let response = client.read_schema().await.unwrap();
284 assert_eq!(
285 response
286 .schema_text()
287 .split_whitespace()
288 .collect::<Vec<_>>(),
289 schema.split_whitespace().collect::<Vec<_>>()
290 );
291 assert!(response.read_at().is_some());
292 }
293}