scim_server/schema/
registry.rs1use super::{embedded, types::{AttributeDefinition, AttributeType, Schema}};
7
8use chrono::{DateTime, FixedOffset};
9use serde_json::Value;
10use std::collections::HashMap;
11use std::fs;
12use std::path::Path;
13
14#[derive(Debug, Clone)]
19pub struct SchemaRegistry {
20 core_user_schema: Schema,
21 core_group_schema: Schema,
22 schemas: HashMap<String, Schema>,
23}
24
25impl SchemaRegistry {
26 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
31 Self::with_embedded_schemas()
32 }
33
34 pub fn with_embedded_schemas() -> Result<Self, Box<dyn std::error::Error>> {
40 let core_user_schema = Self::load_schema_from_str(embedded::core_user_schema())?;
41 let core_group_schema = Self::load_schema_from_str(embedded::core_group_schema())?;
42
43 let mut schemas = HashMap::new();
44 schemas.insert(core_user_schema.id.clone(), core_user_schema.clone());
45 schemas.insert(core_group_schema.id.clone(), core_group_schema.clone());
46
47 Ok(Self {
48 core_user_schema,
49 core_group_schema,
50 schemas,
51 })
52 }
53
54 pub fn from_schema_dir<P: AsRef<Path>>(
56 schema_dir: P,
57 ) -> Result<Self, Box<dyn std::error::Error>> {
58 let user_schema_path = schema_dir.as_ref().join("User.json");
59 let core_user_schema = Self::load_schema_from_file(&user_schema_path)?;
60
61 let group_schema_path = schema_dir.as_ref().join("Group.json");
62 let core_group_schema = Self::load_schema_from_file(&group_schema_path)?;
63
64 let mut schemas = HashMap::new();
65 schemas.insert(core_user_schema.id.clone(), core_user_schema.clone());
66 schemas.insert(core_group_schema.id.clone(), core_group_schema.clone());
67
68 Ok(Self {
69 core_user_schema,
70 core_group_schema,
71 schemas,
72 })
73 }
74
75 fn load_schema_from_file<P: AsRef<Path>>(
77 path: P,
78 ) -> Result<Schema, Box<dyn std::error::Error>> {
79 let content = fs::read_to_string(&path)?;
80 Self::load_schema_from_str(&content)
81 }
82
83 fn load_schema_from_str(content: &str) -> Result<Schema, Box<dyn std::error::Error>> {
85 let mut schema: Schema = serde_json::from_str(content)?;
86
87 Self::convert_json_schema(&mut schema);
89
90 Ok(schema)
91 }
92
93 fn convert_json_schema(schema: &mut Schema) {
95 for attr in &mut schema.attributes {
96 Self::convert_attribute_definition(attr);
97 }
98 }
99
100 fn convert_attribute_definition(attr: &mut AttributeDefinition) {
102 for sub_attr in &mut attr.sub_attributes {
107 Self::convert_attribute_definition(sub_attr);
108 }
109 }
110
111 pub fn get_schemas(&self) -> Vec<&Schema> {
113 self.schemas.values().collect()
114 }
115
116 pub fn get_schema(&self, id: &str) -> Option<&Schema> {
118 self.schemas.get(id)
119 }
120
121 pub fn get_user_schema(&self) -> &Schema {
123 &self.core_user_schema
124 }
125
126 pub fn get_group_schema(&self) -> &Schema {
127 &self.core_group_schema
128 }
129
130 pub fn add_schema(&mut self, schema: Schema) -> Result<(), Box<dyn std::error::Error>> {
132 self.schemas.insert(schema.id.clone(), schema);
133 Ok(())
134 }
135
136 pub fn get_schema_by_id(&self, schema_id: &str) -> Option<&Schema> {
138 self.schemas.get(schema_id)
139 }
140
141 pub(super) fn is_valid_datetime_format(&self, value: &str) -> bool {
152 if value.is_empty() {
153 return false;
154 }
155
156 DateTime::<FixedOffset>::parse_from_rfc3339(value).is_ok()
158 }
159
160 pub(super) fn is_valid_base64(&self, value: &str) -> bool {
166 if value.is_empty() {
167 return false;
168 }
169
170 let base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
173 value.chars().all(|c| base64_chars.contains(c))
174 }
175
176 pub(super) fn is_valid_uri_format(&self, value: &str) -> bool {
181 if value.is_empty() {
182 return false;
183 }
184
185 value.contains("://") || value.starts_with("urn:")
188 }
189
190 pub(super) fn get_value_type(value: &Value) -> &'static str {
192 match value {
193 Value::Null => "null",
194 Value::Bool(_) => "boolean",
195 Value::Number(n) if n.is_i64() => "integer",
196 Value::Number(_) => "decimal",
197 Value::String(_) => "string",
198 Value::Array(_) => "array",
199 Value::Object(_) => "object",
200 }
201 }
202
203 pub(super) fn get_complex_attribute_definition(
205 &self,
206 attr_name: &str,
207 ) -> Option<&AttributeDefinition> {
208 self.core_user_schema
210 .attributes
211 .iter()
212 .find(|attr| attr.name == attr_name && matches!(attr.data_type, AttributeType::Complex))
213 }
214}
215
216impl Default for SchemaRegistry {
217 fn default() -> Self {
218 Self::new().expect("Failed to load default schemas")
219 }
220}