openfga_client/
lib.rs

1#![warn(
2    missing_debug_implementations,
3    rust_2018_idioms,
4    unreachable_pub,
5    clippy::pedantic
6)]
7#![forbid(unsafe_code)]
8
9//! # OpenFGA Rust Client
10//!
11//! [![Crates.io](https://img.shields.io/crates/v/openfga-client)](https://crates.io/crates/openfga-client)
12//! [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
13//! [![Tests](https://github.com/vakamo-labs/openfga-client/actions/workflows/ci.yaml/badge.svg)](https://github.com/vakamo-labs/openfga-client/actions/workflows/ci.yaml)
14//!
15//! OpenFGA Rust Client is a type-safe client for OpenFGA with optional Authorization Model management and Authentication (Bearer or Client Credentials).
16//!
17//! ## Features
18//!
19//! * Type-safe client for OpenFGA (gRPC) build on `tonic`
20//! * (JSON) Serialization and deserialization for Authorization Models in addition to protobuf Messages
21//! * Uses `vendored-protoc` for well-known types - Rust files are pre-generated.
22//! * Optional Authorization Model management with Migration hooks. Ideal for stateless deployments. State is managed exclusively in OpenFGA. This enables fully automated model management by your Application without re-writing of Authorization Models on startup.
23//! * Optional Authentication (Bearer or Client Credentials) via the [Middle Crate](https://crates.io/crates/middle). (Feature: `auth-middle`)
24//! * Convenience functions like `read_all_tuples` (handles pagination), `get_store_by_name` and more.
25//!
26//! # Usage
27//!
28//! ## Basic Usage
29//! ```no_run
30//! use openfga_client::client::OpenFgaServiceClient;
31//! use tonic::transport::Channel;
32//!
33//! #[tokio::main]
34//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
35//!     let endpoint = "http://localhost:8081";
36//!     let service_client = OpenFgaServiceClient::connect(endpoint).await?;
37//!
38//!     // Use the client to interact with OpenFGA
39//!     Ok(())
40//! }
41//! ```
42//!
43//! ## Bearer Token Authentication (API-Key)
44//! ```no_run
45//! use openfga_client::{client::BasicOpenFgaServiceClient, url};
46//!
47//! fn main() -> Result<(), Box<dyn std::error::Error>> {
48//!     let endpoint = url::Url::parse("http://localhost:8081")?;
49//!     let token = "your-bearer-token";
50//!     let service_client = BasicOpenFgaServiceClient::new_with_basic_auth(endpoint, token)?;
51//!
52//!     // Use the client to interact with OpenFGA
53//!     Ok(())
54//! }
55//! ```
56//!
57//! ## Client Credential Authentication
58//! ```no_run
59//! use openfga_client::client::BasicOpenFgaServiceClient;
60//! use url::Url;
61//!
62//! #[tokio::main]
63//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
64//!     let endpoint = Url::parse("http://localhost:8081")?;
65//!     let client_id = "your-client-id";
66//!     let client_secret = "your-client-secret";
67//!     let token_endpoint = Url::parse("http://localhost:8081/token")?;
68//!     let scopes = vec!["scope1", "scope2"];
69//!     let service_client = BasicOpenFgaServiceClient::new_with_client_credentials(endpoint, client_id, client_secret, token_endpoint, &scopes).await?;
70//!
71//!     // Use the client to interact with OpenFGA
72//!     Ok(())
73//! }
74//! ```
75//!
76//! ## Authorization Model Management and Migration
77//!
78//! For more details please check the [`TupleModelManager`](`migration::TupleModelManager`).
79//!
80//! Requires the following as part of the Authorization model:
81//! ```text
82//! type auth_model_id
83//! type model_version
84//!   relations
85//!     define openfga_id: [auth_model_id]
86//!     define exists: [auth_model_id:*]
87//! ```
88//!
89//! Usage:
90//! ```no_run
91//! use openfga_client::client::{OpenFgaServiceClient, TupleKeyWithoutCondition};
92//! use openfga_client::migration::{AuthorizationModelVersion, MigrationFn, TupleModelManager};
93//! use openfga_client::tonic::codegen::StdError;
94//!
95//! /// Application specific state passed into migration functions.
96//! ///
97//! /// It must be clone so that in can be passed into *both* pre and post migration hooks.
98//! #[derive(Clone)]
99//! struct MyMigrationState {}
100//!
101//! /// An example MigrationFn.
102//! #[allow(clippy::unused_async)]
103//! async fn v1_1_migration(
104//!     _client: OpenFgaServiceClient<tonic::transport::Channel>,
105//!     _prev_auth_model_id: Option<String>,
106//!     _active_auth_model_id: Option<String>,
107//!     _state: MyMigrationState,
108//! ) -> std::result::Result<(), StdError> {
109//!     // `client` and `state` can be used to read and write tuples from the store
110//!     Ok(())
111//! }
112//!
113//! #[tokio::main]
114//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
115//!     let endpoint = "http://localhost:8081";
116//!     let mut service_client = OpenFgaServiceClient::connect(endpoint).await?;
117//!
118//!     let store_name = "my-store";
119//!     let model_prefix = "my-model";
120//!
121//!     let mut manager = TupleModelManager::new(service_client.clone(), store_name, model_prefix)
122//!         // Migrations are executed in order for models that have not been previously migrated.
123//!         // First model - version 1.0
124//!         .add_model(
125//!             serde_json::from_str(include_str!("../tests/model-manager/v1.0/schema.json"))?,
126//!             AuthorizationModelVersion::new(1, 0),
127//!             // For major version upgrades, this is where tuple migrations go.
128//!             None::<MigrationFn<_, _>>,
129//!             None::<MigrationFn<_, _>>,
130//!         )
131//!         // Second model - version 1.1
132//!         .add_model(
133//!             serde_json::from_str(include_str!("../tests/model-manager/v1.1/schema.json"))?,
134//!             AuthorizationModelVersion::new(1, 1),
135//!             // For major version upgrades, this is where tuple migrations go.
136//!             Some(v1_1_migration),
137//!             None::<MigrationFn<_, _>>,
138//!         );
139//!
140//!     // Perform the migration if necessary
141//!     manager.migrate(MyMigrationState {}).await?;
142//!
143//!     let store_id = service_client
144//!         .get_store_by_name(store_name)
145//!         .await?
146//!         .expect("Store found")
147//!         .id;
148//!     let authorization_model_id = manager
149//!         .get_authorization_model_id(AuthorizationModelVersion::new(1, 1))
150//!         .await?
151//!         .expect("Authorization model found");
152//!     let client = service_client.into_client(&store_id, &authorization_model_id);
153//!
154//!     // Use the client.
155//!     // `store_id` and `authorization_model_id` are stored in the client and attached to all requests.
156//!     let page_size = 100;
157//!     let continuation_token = None;
158//!     let _tuples = client
159//!         .read(
160//!             page_size,
161//!             TupleKeyWithoutCondition {
162//!                 user: "user:peter".to_string(),
163//!                 relation: "owner".to_string(),
164//!                 object: "organization:my-org".to_string(),
165//!             },
166//!             continuation_token,
167//!         )
168//!         .await?;
169//!
170//!     Ok(())
171//! }
172//! ```
173//!
174//! ## License
175//! This project is licensed under the Apache-2.0 License. See the LICENSE file for details.
176//!
177//! ## Contributing
178//! Contributions are welcome! Please open an issue or submit a pull request on GitHub.
179
180pub use prost_types;
181pub use prost_wkt_types;
182pub use tonic;
183pub mod display;
184pub mod error;
185pub mod migration;
186pub use url;
187
188mod client_ext;
189mod conversions;
190mod model_client;
191
192mod generated {
193    #![allow(clippy::all)]
194    #![allow(clippy::pedantic)]
195
196    include!("gen/openfga/v1/openfga.v1.rs");
197}
198
199pub mod client {
200    //! Contains clients to connect to OpenFGA:
201    //!
202    //! * [`OpenFgaServiceClient`] is the generated client that allows full control over all parameters.
203    //! * [`OpenFgaClient`] is a wrapper around the generated client, that provides a more convenient interface and adds `store_id`, `authorization_model_id` and `consistency` to all requests.
204    //!
205    pub use open_fga_service_client::OpenFgaServiceClient;
206
207    #[cfg(feature = "auth-middle")]
208    pub use super::client_ext::{BasicAuthLayer, BasicOpenFgaServiceClient};
209    #[cfg(feature = "auth-middle")]
210    pub use super::model_client::BasicOpenFgaClient;
211    pub use super::{
212        generated::*,
213        model_client::{ConflictBehavior, OpenFgaClient, WriteOptions},
214    };
215}
216
217#[cfg(test)]
218mod test_json_serde {
219    use super::client::*;
220
221    fn test_authorization_model_serde(schema: &str) {
222        let schema_json: serde_json::Value = schema.parse().unwrap();
223        let schema: AuthorizationModel = serde_json::from_value(schema_json.clone()).unwrap();
224        let value = serde_json::to_value(&schema).unwrap();
225        assert_eq!(schema_json, value);
226    }
227    #[test]
228    fn test_serde_custom_roles() {
229        test_authorization_model_serde(include_str!(
230            "../tests/sample-store/custom-roles/schema.json"
231        ));
232    }
233
234    #[test]
235    fn test_serde_entitlements() {
236        test_authorization_model_serde(include_str!(
237            "../tests/sample-store/entitlements/schema.json"
238        ));
239    }
240
241    #[test]
242    fn test_serde_expenses() {
243        test_authorization_model_serde(include_str!("../tests/sample-store/expenses/schema.json"));
244    }
245
246    // gdrive, github, iot, issue-tracker, modular, slack
247    #[test]
248    fn test_serde_gdrive() {
249        test_authorization_model_serde(include_str!("../tests/sample-store/gdrive/schema.json"));
250    }
251
252    #[test]
253    fn test_serde_github() {
254        test_authorization_model_serde(include_str!("../tests/sample-store/github/schema.json"));
255    }
256
257    #[test]
258    fn test_serde_iot() {
259        test_authorization_model_serde(include_str!("../tests/sample-store/iot/schema.json"));
260    }
261
262    #[test]
263    fn test_serde_modular() {
264        test_authorization_model_serde(include_str!("../tests/sample-store/modular/schema.json"));
265    }
266
267    #[test]
268    fn test_serde_slack() {
269        test_authorization_model_serde(include_str!("../tests/sample-store/slack/schema.json"));
270    }
271}