Skip to main content

spicedb_embedded_sys/
memory.rs

1//! Safe, typed wrappers for the memory-transport FFI.
2//!
3//! All marshalling/unmarshalling and unsafe FFI calls are contained here.
4//! The main crate uses these functions for idiomatic, safe APIs.
5
6use std::os::raw::{c_char, c_int, c_uchar, c_ulonglong};
7
8use prost::Message;
9use spicedb_grpc_tonic::v1::{
10    CheckBulkPermissionsRequest, CheckBulkPermissionsResponse, CheckPermissionRequest,
11    CheckPermissionResponse, DeleteRelationshipsRequest, DeleteRelationshipsResponse,
12    ExpandPermissionTreeRequest, ExpandPermissionTreeResponse, ReadSchemaRequest,
13    ReadSchemaResponse, WriteRelationshipsRequest, WriteRelationshipsResponse, WriteSchemaRequest,
14    WriteSchemaResponse,
15};
16
17/// Error from a memory-transport RPC (FFI or decode).
18#[derive(Debug, Clone)]
19pub struct RpcError(pub String);
20
21impl std::fmt::Display for RpcError {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        write!(f, "{}", self.0)
24    }
25}
26
27impl std::error::Error for RpcError {}
28
29/// Result type for memory-transport RPCs.
30pub type RpcResult<T> = Result<T, RpcError>;
31
32/// Raw call: request bytes in, response bytes or error string. Handles all unsafe FFI and buffer freeing.
33fn raw_call(
34    handle: u64,
35    request_bytes: &[u8],
36    f: unsafe extern "C" fn(
37        c_ulonglong,
38        *const c_uchar,
39        c_int,
40        *mut *mut c_uchar,
41        *mut c_int,
42        *mut *mut c_char,
43    ),
44) -> RpcResult<Vec<u8>> {
45    let request_len = c_int::try_from(request_bytes.len())
46        .map_err(|_| RpcError("request too large for FFI (exceeds c_int::MAX)".into()))?;
47
48    let mut out_response_bytes: *mut c_uchar = std::ptr::null_mut();
49    let mut out_response_len: c_int = 0;
50    let mut out_error: *mut c_char = std::ptr::null_mut();
51
52    unsafe {
53        f(
54            handle as c_ulonglong,
55            request_bytes.as_ptr(),
56            request_len,
57            &mut out_response_bytes,
58            &mut out_response_len,
59            &mut out_error,
60        );
61    }
62
63    if !out_error.is_null() {
64        let s = unsafe { std::ffi::CStr::from_ptr(out_error) }
65            .to_string_lossy()
66            .into_owned();
67        unsafe { crate::spicedb_free(out_error) };
68        return Err(RpcError(s));
69    }
70
71    if out_response_len < 0 {
72        return Err(RpcError(format!(
73            "FFI returned negative response length: {}",
74            out_response_len
75        )));
76    }
77    let len = out_response_len as usize;
78    let bytes = if len == 0 {
79        vec![]
80    } else {
81        unsafe { std::slice::from_raw_parts(out_response_bytes, len).to_vec() }
82    };
83    if !out_response_bytes.is_null() {
84        unsafe { crate::spicedb_free_bytes(out_response_bytes as *mut std::ffi::c_void) };
85    }
86    Ok(bytes)
87}
88
89// --- PermissionsService ---
90
91/// PermissionsService.CheckPermission.
92pub fn check_permission(
93    handle: u64,
94    request: &CheckPermissionRequest,
95) -> RpcResult<CheckPermissionResponse> {
96    let mut buf = Vec::new();
97    request
98        .encode(&mut buf)
99        .map_err(|e| RpcError(e.to_string()))?;
100    let bytes = raw_call(handle, &buf, crate::spicedb_permissions_check_permission)?;
101    CheckPermissionResponse::decode(&bytes[..]).map_err(|e| RpcError(e.to_string()))
102}
103
104/// PermissionsService.WriteRelationships.
105pub fn write_relationships(
106    handle: u64,
107    request: &WriteRelationshipsRequest,
108) -> RpcResult<WriteRelationshipsResponse> {
109    let mut buf = Vec::new();
110    request
111        .encode(&mut buf)
112        .map_err(|e| RpcError(e.to_string()))?;
113    let bytes = raw_call(handle, &buf, crate::spicedb_permissions_write_relationships)?;
114    WriteRelationshipsResponse::decode(&bytes[..]).map_err(|e| RpcError(e.to_string()))
115}
116
117/// PermissionsService.DeleteRelationships.
118pub fn delete_relationships(
119    handle: u64,
120    request: &DeleteRelationshipsRequest,
121) -> RpcResult<DeleteRelationshipsResponse> {
122    let mut buf = Vec::new();
123    request
124        .encode(&mut buf)
125        .map_err(|e| RpcError(e.to_string()))?;
126    let bytes = raw_call(
127        handle,
128        &buf,
129        crate::spicedb_permissions_delete_relationships,
130    )?;
131    DeleteRelationshipsResponse::decode(&bytes[..]).map_err(|e| RpcError(e.to_string()))
132}
133
134/// PermissionsService.CheckBulkPermissions.
135pub fn check_bulk_permissions(
136    handle: u64,
137    request: &CheckBulkPermissionsRequest,
138) -> RpcResult<CheckBulkPermissionsResponse> {
139    let mut buf = Vec::new();
140    request
141        .encode(&mut buf)
142        .map_err(|e| RpcError(e.to_string()))?;
143    let bytes = raw_call(
144        handle,
145        &buf,
146        crate::spicedb_permissions_check_bulk_permissions,
147    )?;
148    CheckBulkPermissionsResponse::decode(&bytes[..]).map_err(|e| RpcError(e.to_string()))
149}
150
151/// PermissionsService.ExpandPermissionTree.
152pub fn expand_permission_tree(
153    handle: u64,
154    request: &ExpandPermissionTreeRequest,
155) -> RpcResult<ExpandPermissionTreeResponse> {
156    let mut buf = Vec::new();
157    request
158        .encode(&mut buf)
159        .map_err(|e| RpcError(e.to_string()))?;
160    let bytes = raw_call(
161        handle,
162        &buf,
163        crate::spicedb_permissions_expand_permission_tree,
164    )?;
165    ExpandPermissionTreeResponse::decode(&bytes[..]).map_err(|e| RpcError(e.to_string()))
166}
167
168// --- SchemaService ---
169
170/// SchemaService.ReadSchema.
171pub fn read_schema(handle: u64, request: &ReadSchemaRequest) -> RpcResult<ReadSchemaResponse> {
172    let mut buf = Vec::new();
173    request
174        .encode(&mut buf)
175        .map_err(|e| RpcError(e.to_string()))?;
176    let bytes = raw_call(handle, &buf, crate::spicedb_schema_read_schema)?;
177    ReadSchemaResponse::decode(&bytes[..]).map_err(|e| RpcError(e.to_string()))
178}
179
180/// SchemaService.WriteSchema.
181pub fn write_schema(handle: u64, request: &WriteSchemaRequest) -> RpcResult<WriteSchemaResponse> {
182    let mut buf = Vec::new();
183    request
184        .encode(&mut buf)
185        .map_err(|e| RpcError(e.to_string()))?;
186    let bytes = raw_call(handle, &buf, crate::spicedb_schema_write_schema)?;
187    WriteSchemaResponse::decode(&bytes[..]).map_err(|e| RpcError(e.to_string()))
188}