use crate::error::{BuildError, BuildResult, ScimResult};
use crate::schema::{Schema, SchemaRegistry};
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
#[derive(Debug)]
pub struct Uninitialized;
#[derive(Debug)]
pub struct Ready;
pub struct SchemaDiscovery<State = Ready> {
inner: Option<DiscoveryInner>,
_state: PhantomData<State>,
}
impl<State> std::fmt::Debug for SchemaDiscovery<State> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SchemaDiscovery")
.field("inner", &self.inner.is_some())
.field("state", &std::any::type_name::<State>())
.finish()
}
}
struct DiscoveryInner {
schema_registry: SchemaRegistry,
service_config: ServiceProviderConfig,
}
impl std::fmt::Debug for DiscoveryInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DiscoveryInner")
.field("schema_registry", &"SchemaRegistry")
.field("service_config", &self.service_config)
.finish()
}
}
impl SchemaDiscovery<Uninitialized> {
pub fn new() -> BuildResult<SchemaDiscovery<Ready>> {
let schema_registry =
SchemaRegistry::with_embedded_schemas().map_err(|_e| BuildError::SchemaLoadError {
schema_id: "Core".to_string(),
})?;
let service_config = ServiceProviderConfig::default();
let inner = DiscoveryInner {
schema_registry,
service_config,
};
Ok(SchemaDiscovery {
inner: Some(inner),
_state: PhantomData,
})
}
}
impl SchemaDiscovery<Ready> {
pub async fn get_schemas(&self) -> ScimResult<Vec<Schema>> {
let inner = self.inner.as_ref().expect("Server should be initialized");
Ok(inner
.schema_registry
.get_schemas()
.into_iter()
.cloned()
.collect())
}
pub async fn get_schema(&self, id: &str) -> ScimResult<Option<Schema>> {
let inner = self.inner.as_ref().expect("Server should be initialized");
Ok(inner.schema_registry.get_schema(id).cloned())
}
pub async fn get_service_provider_config(&self) -> ScimResult<ServiceProviderConfig> {
let inner = self.inner.as_ref().expect("Server should be initialized");
Ok(inner.service_config.clone())
}
pub fn schema_registry(&self) -> &SchemaRegistry {
let inner = self
.inner
.as_ref()
.expect("Discovery component should be initialized");
&inner.schema_registry
}
pub fn service_config(&self) -> &ServiceProviderConfig {
let inner = self
.inner
.as_ref()
.expect("Discovery component should be initialized");
&inner.service_config
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ServiceProviderConfig {
#[serde(rename = "patch")]
pub patch_supported: bool,
#[serde(rename = "bulk")]
pub bulk_supported: bool,
#[serde(rename = "filter")]
pub filter_supported: bool,
#[serde(rename = "changePassword")]
pub change_password_supported: bool,
#[serde(rename = "sort")]
pub sort_supported: bool,
#[serde(rename = "etag")]
pub etag_supported: bool,
#[serde(rename = "authenticationSchemes")]
pub authentication_schemes: Vec<AuthenticationScheme>,
#[serde(rename = "bulk.maxOperations")]
pub bulk_max_operations: Option<u32>,
#[serde(rename = "bulk.maxPayloadSize")]
pub bulk_max_payload_size: Option<u64>,
#[serde(rename = "filter.maxResults")]
pub filter_max_results: Option<u32>,
}
impl Default for ServiceProviderConfig {
fn default() -> Self {
Self {
patch_supported: false,
bulk_supported: false,
filter_supported: false,
change_password_supported: false,
sort_supported: false,
etag_supported: false,
authentication_schemes: vec![],
bulk_max_operations: None,
bulk_max_payload_size: None,
filter_max_results: Some(200),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AuthenticationScheme {
pub name: String,
pub description: String,
#[serde(rename = "specUri")]
pub spec_uri: Option<String>,
#[serde(rename = "documentationUri")]
pub documentation_uri: Option<String>,
#[serde(rename = "type")]
pub auth_type: String,
pub primary: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_discovery_creation() {
let discovery = SchemaDiscovery::new().expect("Failed to create discovery component");
let schemas = discovery
.get_schemas()
.await
.expect("Failed to get schemas");
assert!(!schemas.is_empty());
}
#[tokio::test]
async fn test_schema_access() {
let discovery = SchemaDiscovery::new().expect("Failed to create discovery component");
let user_schema = discovery
.get_schema("urn:ietf:params:scim:schemas:core:2.0:User")
.await
.expect("Failed to get schema");
assert!(user_schema.is_some());
if let Some(schema) = user_schema {
assert_eq!(schema.name, "User");
}
}
#[test]
fn test_service_provider_config() {
let config = ServiceProviderConfig::default();
assert!(!config.patch_supported);
assert!(!config.bulk_supported);
assert!(!config.filter_supported);
}
#[tokio::test]
async fn test_tutorial_example_works() {
let discovery = SchemaDiscovery::new()
.expect("SchemaDiscovery::new() should work with embedded schemas");
let schemas = discovery
.get_schemas()
.await
.expect("get_schemas() should work");
assert!(
!schemas.is_empty(),
"Should have at least one schema available"
);
println!("Available schemas: {}", schemas.len());
let config = discovery
.get_service_provider_config()
.await
.expect("get_service_provider_config() should work");
println!("Bulk operations supported: {}", config.bulk_supported);
let user_schema = discovery
.get_schema("urn:ietf:params:scim:schemas:core:2.0:User")
.await
.expect("Should be able to get User schema");
assert!(user_schema.is_some(), "User schema should be available");
}
}