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//! #[allow(clippy::unused_async)]
96//! async fn v1_1_migration(
97//!     client: OpenFgaServiceClient<tonic::transport::Channel>,
98//! ) -> std::result::Result<(), StdError> {
99//!     let _ = client;
100//!     Ok(())
101//! }
102//!
103//! #[tokio::main]
104//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
105//!     let endpoint = "http://localhost:8081";
106//!     let mut service_client = OpenFgaServiceClient::connect(endpoint).await?;
107//!
108//!     let store_name = "my-store";
109//!     let model_prefix = "my-model";
110//!
111//!     let mut manager = TupleModelManager::new(service_client.clone(), store_name, model_prefix)
112//!         // Migrations are executed in order for models that have not been previously migrated.
113//!         // First model - version 1.0
114//!         .add_model(
115//!             serde_json::from_str(include_str!("../tests/model-manager/v1.0/schema.json"))?,
116//!             AuthorizationModelVersion::new(1, 0),
117//!             // For major version upgrades, this is where tuple migrations go.
118//!             None::<MigrationFn<_>>,
119//!             None::<MigrationFn<_>>,
120//!         )
121//!         // Second model - version 1.1
122//!         .add_model(
123//!             serde_json::from_str(include_str!("../tests/model-manager/v1.1/schema.json"))?,
124//!             AuthorizationModelVersion::new(1, 1),
125//!             // For major version upgrades, this is where tuple migrations go.
126//!             Some(v1_1_migration),
127//!             None::<MigrationFn<_>>,
128//!         );
129//!
130//!     // Perform the migration if necessary
131//!     manager.migrate().await?;
132//!
133//!     let store_id = service_client
134//!         .get_store_by_name(store_name)
135//!         .await?
136//!         .expect("Store found")
137//!         .id;
138//!     let authorization_model_id = manager
139//!         .get_authorization_model_id(AuthorizationModelVersion::new(1, 1))
140//!         .await?
141//!         .expect("Authorization model found");
142//!     let client = service_client.into_client(&store_id, &authorization_model_id);
143//!
144//!     // Use the client.
145//!     // `store_id` and `authorization_model_id` are stored in the client and attached to all requests.
146//!     let page_size = 100;
147//!     let continuation_token = None;
148//!     let _tuples = client
149//!         .read(
150//!             page_size,
151//!             TupleKeyWithoutCondition {
152//!                 user: "user:peter".to_string(),
153//!                 relation: "owner".to_string(),
154//!                 object: "organization:my-org".to_string(),
155//!             },
156//!             continuation_token,
157//!         )
158//!         .await?;
159//!
160//!     Ok(())
161//! }
162//! ```
163//!
164//! ## License
165//! This project is licensed under the Apache-2.0 License. See the LICENSE file for details.
166//!
167//! ## Contributing
168//! Contributions are welcome! Please open an issue or submit a pull request on GitHub.
169
170pub use prost_types;
171pub use prost_wkt_types;
172pub use tonic;
173pub mod display;
174pub mod error;
175pub mod migration;
176pub use url;
177
178mod client_ext;
179mod conversions;
180mod model_client;
181
182mod generated {
183    #![allow(clippy::all)]
184    #![allow(clippy::pedantic)]
185
186    include!("gen/openfga.v1.rs");
187}
188
189pub mod client {
190    //! Contains clients to connect to OpenFGA:
191    //!
192    //! * [`OpenFgaServiceClient`] is the generated client that allows full control over all parameters.
193    //! * [`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.
194    //!
195    pub use open_fga_service_client::OpenFgaServiceClient;
196
197    #[cfg(feature = "auth-middle")]
198    pub use super::client_ext::{BasicAuthLayer, BasicOpenFgaServiceClient};
199    #[cfg(feature = "auth-middle")]
200    pub use super::model_client::BasicOpenFgaClient;
201    pub use super::{generated::*, model_client::OpenFgaClient};
202}
203
204#[cfg(test)]
205mod test_json_serde {
206    use super::client::*;
207
208    fn test_authorization_model_serde(schema: &str) {
209        let schema_json: serde_json::Value = schema.parse().unwrap();
210        let schema: AuthorizationModel = serde_json::from_value(schema_json.clone()).unwrap();
211        let value = serde_json::to_value(&schema).unwrap();
212        assert_eq!(schema_json, value);
213    }
214    #[test]
215    fn test_serde_custom_roles() {
216        test_authorization_model_serde(include_str!(
217            "../tests/sample-store/custom-roles/schema.json"
218        ));
219    }
220
221    #[test]
222    fn test_serde_entitlements() {
223        test_authorization_model_serde(include_str!(
224            "../tests/sample-store/entitlements/schema.json"
225        ));
226    }
227
228    #[test]
229    fn test_serde_expenses() {
230        test_authorization_model_serde(include_str!("../tests/sample-store/expenses/schema.json"));
231    }
232
233    // gdrive, github, iot, issue-tracker, modular, slack
234    #[test]
235    fn test_serde_gdrive() {
236        test_authorization_model_serde(include_str!("../tests/sample-store/gdrive/schema.json"));
237    }
238
239    #[test]
240    fn test_serde_github() {
241        test_authorization_model_serde(include_str!("../tests/sample-store/github/schema.json"));
242    }
243
244    #[test]
245    fn test_serde_iot() {
246        test_authorization_model_serde(include_str!("../tests/sample-store/iot/schema.json"));
247    }
248
249    #[test]
250    fn test_serde_modular() {
251        test_authorization_model_serde(include_str!("../tests/sample-store/modular/schema.json"));
252    }
253
254    #[test]
255    fn test_serde_slack() {
256        test_authorization_model_serde(include_str!("../tests/sample-store/slack/schema.json"));
257    }
258}