use bytes::Bytes;
use spicedb_grpc::authzed::api::v1::{
permissions_service_client::PermissionsServiceClient,
schema_service_client::SchemaServiceClient, watch_service_client::WatchServiceClient, *,
};
use tonic::{
metadata::{Ascii, MetadataValue},
service::{interceptor::InterceptedService, Interceptor},
transport::Channel,
Request, Status, Streaming,
};
use crate::result::Result;
#[derive(Clone, Debug)]
pub struct SpicedbClient {
pub channel: Channel,
schemas: SchemaServiceClient<InterceptedService<Channel, SpicedbMiddleware>>,
permissions: PermissionsServiceClient<InterceptedService<Channel, SpicedbMiddleware>>,
watch: WatchServiceClient<InterceptedService<Channel, SpicedbMiddleware>>,
}
impl SpicedbClient {
pub async fn from_url_and_preshared_key(
url: impl Into<Bytes>,
preshared_key: impl ToString,
) -> Result<Self> {
let interceptor = SpicedbMiddleware {
preshared_key: Box::new(format!("bearer {}", preshared_key.to_string()).parse()?),
};
let channel = Channel::from_shared(url)?.connect().await?;
let schemas = SchemaServiceClient::with_interceptor(channel.clone(), interceptor.clone());
let permissions =
PermissionsServiceClient::with_interceptor(channel.clone(), interceptor.clone());
let watch = WatchServiceClient::with_interceptor(channel.clone(), interceptor.clone());
Ok(SpicedbClient {
channel,
schemas,
permissions,
watch,
})
}
pub async fn read_schema(&mut self) -> Result<ReadSchemaResponse> {
let response = self
.schemas
.read_schema(ReadSchemaRequest {})
.await
.unwrap()
.into_inner();
Ok(response)
}
pub async fn write_schema(&mut self, schema: impl ToString) -> Result<WriteSchemaResponse> {
let response = self
.schemas
.write_schema(WriteSchemaRequest {
schema: schema.to_string(),
})
.await?
.into_inner();
Ok(response)
}
pub async fn read_relationships(
&mut self,
request: ReadRelationshipsRequest,
) -> Result<Streaming<ReadRelationshipsResponse>> {
let stream = self
.permissions
.read_relationships(request)
.await?
.into_inner();
Ok(stream)
}
pub async fn write_relationships(
&mut self,
request: WriteRelationshipsRequest,
) -> Result<WriteRelationshipsResponse> {
let response = self
.permissions
.write_relationships(request)
.await?
.into_inner();
Ok(response)
}
pub async fn delete_relationships(
&mut self,
request: DeleteRelationshipsRequest,
) -> Result<DeleteRelationshipsResponse> {
let response = self
.permissions
.delete_relationships(request)
.await?
.into_inner();
Ok(response)
}
pub async fn check_permission(
&mut self,
request: CheckPermissionRequest,
) -> Result<CheckPermissionResponse> {
let response = self
.permissions
.check_permission(request)
.await?
.into_inner();
Ok(response)
}
pub async fn check_bulk_permissions(
&mut self,
request: CheckBulkPermissionsRequest,
) -> Result<CheckBulkPermissionsResponse> {
let response = self
.permissions
.check_bulk_permissions(request)
.await?
.into_inner();
Ok(response)
}
pub async fn expand_permission_tree(
&mut self,
request: ExpandPermissionTreeRequest,
) -> Result<ExpandPermissionTreeResponse> {
let response = self
.permissions
.expand_permission_tree(request)
.await?
.into_inner();
Ok(response)
}
pub async fn lookup_resources(
&mut self,
request: LookupResourcesRequest,
) -> Result<Streaming<LookupResourcesResponse>> {
let response = self
.permissions
.lookup_resources(request)
.await?
.into_inner();
Ok(response)
}
pub async fn lookup_subjects(
&mut self,
request: LookupSubjectsRequest,
) -> Result<Streaming<LookupSubjectsResponse>> {
let response = self
.permissions
.lookup_subjects(request)
.await?
.into_inner();
Ok(response)
}
pub async fn watch(&mut self, request: WatchRequest) -> Result<Streaming<WatchResponse>> {
let response = self.watch.watch(request).await?.into_inner();
Ok(response)
}
}
#[derive(Clone)]
struct SpicedbMiddleware {
preshared_key: Box<MetadataValue<Ascii>>,
}
impl Interceptor for SpicedbMiddleware {
fn call(&mut self, mut request: Request<()>) -> Result<tonic::Request<()>, Status> {
request
.metadata_mut()
.insert("authorization", (*self.preshared_key).clone());
Ok(request)
}
}
#[cfg(test)]
mod test {
use std::env;
use tokio::test;
use crate::reader::*;
use super::*;
#[test]
pub async fn test_spicedb() {
let spicedb_url =
env::var("SPICEDB_URL").unwrap_or_else(|_| "http://localhost:50051".to_string());
let preshared_key =
env::var("SPICEDB_PRESHARED_KEY").unwrap_or_else(|_| "spicedb".to_string());
let mut client = SpicedbClient::from_url_and_preshared_key(spicedb_url, preshared_key)
.await
.unwrap();
let schema = r#"
definition user {}
definition document {
relation viewer: user
relation editor: user
permission view = viewer + editor
permission edit = editor
}
"#;
let response = client.write_schema(schema).await.unwrap();
assert!(response.written_at().is_some());
let response = client.read_schema().await.unwrap();
assert_eq!(
response
.schema_text()
.split_whitespace()
.collect::<Vec<_>>(),
schema.split_whitespace().collect::<Vec<_>>()
);
assert!(response.read_at().is_some());
}
}