1use crate::error::{ValidationError, ValidationResult};
8use crate::resource::value_objects::{
9 Address, EmailAddress, ExternalId, GroupMembers, Meta, MultiValuedAddresses, MultiValuedEmails,
10 MultiValuedPhoneNumbers, Name, PhoneNumber, ResourceId, SchemaUri, UserName,
11};
12use crate::resource::version::RawVersion;
13
14use serde_json::{Map, Value};
15
16#[derive(Debug, Clone)]
22pub struct Resource {
23 pub resource_type: String,
25 pub id: Option<ResourceId>,
27 pub schemas: Vec<SchemaUri>,
29 pub external_id: Option<ExternalId>,
31 pub user_name: Option<UserName>,
33 pub meta: Option<Meta>,
35 pub name: Option<Name>,
37 pub addresses: Option<MultiValuedAddresses>,
39 pub phone_numbers: Option<MultiValuedPhoneNumbers>,
41 pub emails: Option<MultiValuedEmails>,
43 pub members: Option<GroupMembers>,
45 pub attributes: Map<String, Value>,
47}
48
49impl Resource {
50 pub fn from_json(resource_type: String, data: Value) -> ValidationResult<Self> {
77 let obj = data
78 .as_object()
79 .ok_or_else(|| ValidationError::custom("Resource must be a JSON object"))?;
80
81 let id = Self::extract_resource_id(obj)?;
83 let schemas = Self::extract_schemas(obj, &resource_type)?;
84 let external_id = Self::extract_external_id(obj)?;
85 let user_name = Self::extract_user_name(obj)?;
86 let meta = Self::extract_meta(&data)?;
87 let name = Self::extract_name(obj)?;
88 let addresses = Self::extract_addresses(obj)?;
89 let phone_numbers = Self::extract_phone_numbers(obj)?;
90 let emails = Self::extract_emails(obj)?;
91 let members = Self::extract_members(obj)?;
92
93 let mut attributes = obj.clone();
95 attributes.remove("id");
96 attributes.remove("schemas");
97 attributes.remove("externalId");
98 attributes.remove("userName");
99 attributes.remove("meta");
100 attributes.remove("name");
101 attributes.remove("addresses");
102 attributes.remove("phoneNumbers");
103 attributes.remove("emails");
104 attributes.remove("members");
105
106 Ok(Self {
107 resource_type,
108 id,
109 schemas,
110 external_id,
111 user_name,
112 meta,
113 name,
114 addresses,
115 phone_numbers,
116 emails,
117 members,
118 attributes,
119 })
120 }
121
122 pub fn new(
127 resource_type: String,
128 id: Option<ResourceId>,
129 schemas: Vec<SchemaUri>,
130 external_id: Option<ExternalId>,
131 user_name: Option<UserName>,
132 attributes: Map<String, Value>,
133 ) -> Self {
134 Self {
135 resource_type,
136 id,
137 schemas,
138 external_id,
139 user_name,
140 meta: None,
141 name: None,
142 addresses: None,
143 phone_numbers: None,
144 emails: None,
145 members: None,
146 attributes,
147 }
148 }
149
150 pub fn new_with_meta(
154 resource_type: String,
155 id: Option<ResourceId>,
156 schemas: Vec<SchemaUri>,
157 external_id: Option<ExternalId>,
158 user_name: Option<UserName>,
159 meta: Option<Meta>,
160 attributes: Map<String, Value>,
161 ) -> Self {
162 Self {
163 resource_type,
164 id,
165 schemas,
166 external_id,
167 user_name,
168 meta,
169 name: None,
170 addresses: None,
171 phone_numbers: None,
172 emails: None,
173 members: None,
174 attributes,
175 }
176 }
177
178 fn extract_resource_id(obj: &Map<String, Value>) -> ValidationResult<Option<ResourceId>> {
180 if let Some(id_value) = obj.get("id") {
181 if let Some(id_str) = id_value.as_str() {
182 return Ok(Some(ResourceId::new(id_str.to_string())?));
183 } else {
184 return Err(ValidationError::InvalidIdFormat {
185 id: id_value.to_string(),
186 });
187 }
188 }
189 Ok(None)
190 }
191
192 fn extract_schemas(
194 obj: &Map<String, Value>,
195 resource_type: &str,
196 ) -> ValidationResult<Vec<SchemaUri>> {
197 if let Some(schemas_value) = obj.get("schemas") {
198 if let Some(schemas_array) = schemas_value.as_array() {
199 if schemas_array.is_empty() {
200 return Err(ValidationError::EmptySchemas);
201 }
202
203 let mut schemas = Vec::new();
204 for schema_value in schemas_array {
205 if let Some(uri_str) = schema_value.as_str() {
206 schemas.push(SchemaUri::new(uri_str.to_string())?);
207 }
208 }
209 if !schemas.is_empty() {
210 return Ok(schemas);
211 }
212 }
213 }
214
215 let default_uri = match resource_type {
217 "User" => "urn:ietf:params:scim:schemas:core:2.0:User",
218 "Group" => "urn:ietf:params:scim:schemas:core:2.0:Group",
219 _ => return Err(ValidationError::custom("Unknown resource type")),
220 };
221
222 Ok(vec![SchemaUri::new(default_uri.to_string())?])
223 }
224
225 fn extract_external_id(obj: &Map<String, Value>) -> ValidationResult<Option<ExternalId>> {
227 if let Some(ext_id_value) = obj.get("externalId") {
228 if let Some(ext_id_str) = ext_id_value.as_str() {
229 return Ok(Some(ExternalId::new(ext_id_str.to_string())?));
230 } else {
231 return Err(ValidationError::InvalidExternalId);
232 }
233 }
234 Ok(None)
235 }
236
237 fn extract_user_name(obj: &Map<String, Value>) -> ValidationResult<Option<UserName>> {
239 if let Some(username_value) = obj.get("userName") {
240 if let Some(username_str) = username_value.as_str() {
241 return Ok(Some(UserName::new(username_str.to_string())?));
242 } else {
243 return Err(ValidationError::custom(
244 "userName must be a string".to_string(),
245 ));
246 }
247 }
248 Ok(None)
249 }
250
251 fn extract_name(obj: &Map<String, Value>) -> ValidationResult<Option<Name>> {
253 if let Some(name_value) = obj.get("name") {
254 if let Some(_) = name_value.as_object() {
255 let name: Name = serde_json::from_value(name_value.clone())
257 .map_err(|e| ValidationError::custom(format!("Invalid name format: {}", e)))?;
258 return Ok(Some(name));
259 } else {
260 return Err(ValidationError::custom(
261 "name must be an object".to_string(),
262 ));
263 }
264 }
265 Ok(None)
266 }
267
268 fn extract_addresses(
270 obj: &Map<String, Value>,
271 ) -> ValidationResult<Option<MultiValuedAddresses>> {
272 if let Some(addresses_value) = obj.get("addresses") {
273 if let Some(_) = addresses_value.as_array() {
274 let addresses: Vec<Address> = serde_json::from_value(addresses_value.clone())
276 .map_err(|e| {
277 ValidationError::custom(format!("Invalid addresses format: {}", e))
278 })?;
279 if !addresses.is_empty() {
280 let multi_addresses = MultiValuedAddresses::new(addresses)?;
281 return Ok(Some(multi_addresses));
282 }
283 } else {
284 return Err(ValidationError::custom(
285 "addresses must be an array".to_string(),
286 ));
287 }
288 }
289 Ok(None)
290 }
291
292 fn extract_phone_numbers(
294 obj: &Map<String, Value>,
295 ) -> ValidationResult<Option<MultiValuedPhoneNumbers>> {
296 if let Some(phones_value) = obj.get("phoneNumbers") {
297 if let Some(_) = phones_value.as_array() {
298 let phone_numbers: Vec<PhoneNumber> = serde_json::from_value(phones_value.clone())
300 .map_err(|e| {
301 ValidationError::custom(format!("Invalid phoneNumbers format: {}", e))
302 })?;
303 if !phone_numbers.is_empty() {
304 let multi_phones = MultiValuedPhoneNumbers::new(phone_numbers)?;
305 return Ok(Some(multi_phones));
306 }
307 } else {
308 return Err(ValidationError::custom(
309 "phoneNumbers must be an array".to_string(),
310 ));
311 }
312 }
313 Ok(None)
314 }
315
316 fn extract_emails(obj: &Map<String, Value>) -> ValidationResult<Option<MultiValuedEmails>> {
318 if let Some(emails_value) = obj.get("emails") {
319 if let Some(_) = emails_value.as_array() {
320 let emails: Vec<EmailAddress> = serde_json::from_value(emails_value.clone())
322 .map_err(|e| {
323 ValidationError::custom(format!("Invalid emails format: {}", e))
324 })?;
325 if !emails.is_empty() {
326 let multi_emails = MultiValuedEmails::new(emails)?;
327 return Ok(Some(multi_emails));
328 }
329 } else {
330 return Err(ValidationError::custom(
331 "emails must be an array".to_string(),
332 ));
333 }
334 }
335 Ok(None)
336 }
337
338 fn extract_members(obj: &Map<String, Value>) -> ValidationResult<Option<GroupMembers>> {
340 if let Some(members_value) = obj.get("members") {
341 if let Some(_) = members_value.as_array() {
342 let members_data: Vec<serde_json::Value> =
344 serde_json::from_value(members_value.clone()).map_err(|e| {
345 ValidationError::custom(format!("Invalid members format: {}", e))
346 })?;
347
348 let mut members = Vec::new();
349 for member_data in members_data {
350 if let Some(obj) = member_data.as_object() {
351 if let Some(value_str) = obj.get("value").and_then(|v| v.as_str()) {
352 let resource_id = ResourceId::new(value_str.to_string())?;
353 let display = obj
354 .get("display")
355 .and_then(|v| v.as_str())
356 .map(|s| s.to_string());
357 let member_type = obj
358 .get("type")
359 .and_then(|v| v.as_str())
360 .map(|s| s.to_string());
361
362 let member = crate::resource::value_objects::GroupMember::new(
363 resource_id,
364 display,
365 member_type,
366 )?;
367 members.push(member);
368 }
369 }
370 }
371
372 if !members.is_empty() {
373 let group_members = GroupMembers::new(members)?;
374 return Ok(Some(group_members));
375 }
376 } else {
377 return Err(ValidationError::custom(
378 "members must be an array".to_string(),
379 ));
380 }
381 }
382 Ok(None)
383 }
384
385 pub fn get_id(&self) -> Option<&str> {
387 self.id.as_ref().map(|id| id.as_str())
388 }
389
390 pub fn set_id(&mut self, id: &str) -> ValidationResult<()> {
392 self.id = Some(ResourceId::new(id.to_string())?);
393 Ok(())
394 }
395
396 pub fn get(&self, key: &str) -> Option<&Value> {
398 self.attributes.get(key)
399 }
400
401 pub fn get_username(&self) -> Option<&str> {
403 self.user_name.as_ref().map(|name| name.as_str())
404 }
405
406 pub fn get_name(&self) -> Option<&Name> {
408 self.name.as_ref()
409 }
410
411 pub fn get_addresses(&self) -> Option<&MultiValuedAddresses> {
413 self.addresses.as_ref()
414 }
415
416 pub fn get_phone_numbers(&self) -> Option<&MultiValuedPhoneNumbers> {
418 self.phone_numbers.as_ref()
419 }
420
421 pub fn get_emails(&self) -> Option<&MultiValuedEmails> {
423 self.emails.as_ref()
424 }
425
426 pub fn get_members(&self) -> Option<&GroupMembers> {
428 self.members.as_ref()
429 }
430
431 pub fn set_name(&mut self, name: Name) {
433 self.name = Some(name);
434 }
435
436 pub fn set_addresses(&mut self, addresses: MultiValuedAddresses) {
438 self.addresses = Some(addresses);
439 }
440
441 pub fn set_phone_numbers(&mut self, phone_numbers: MultiValuedPhoneNumbers) {
443 self.phone_numbers = Some(phone_numbers);
444 }
445
446 pub fn set_emails(&mut self, emails: MultiValuedEmails) {
448 self.emails = Some(emails);
449 }
450
451 pub fn set_members(&mut self, members: GroupMembers) {
453 self.members = Some(members);
454 }
455
456 pub fn add_address(&mut self, address: Address) -> ValidationResult<()> {
458 match &self.addresses {
459 Some(existing) => {
460 let new_addresses = existing.clone().with_value(address);
461 self.addresses = Some(new_addresses);
462 }
463 None => {
464 let new_addresses = MultiValuedAddresses::single(address);
465 self.addresses = Some(new_addresses);
466 }
467 }
468 Ok(())
469 }
470
471 pub fn add_phone_number(&mut self, phone_number: PhoneNumber) -> ValidationResult<()> {
473 match &self.phone_numbers {
474 Some(existing) => {
475 let new_phones = existing.clone().with_value(phone_number);
476 self.phone_numbers = Some(new_phones);
477 }
478 None => {
479 let new_phones = MultiValuedPhoneNumbers::single(phone_number);
480 self.phone_numbers = Some(new_phones);
481 }
482 }
483 Ok(())
484 }
485
486 pub fn add_email(&mut self, email: EmailAddress) -> ValidationResult<()> {
488 match &self.emails {
489 Some(existing) => {
490 let new_emails = existing.clone().with_value(email);
491 self.emails = Some(new_emails);
492 }
493 None => {
494 let new_emails = MultiValuedEmails::single(email);
495 self.emails = Some(new_emails);
496 }
497 }
498 Ok(())
499 }
500
501 pub fn get_attribute(&self, attribute_name: &str) -> Option<&Value> {
506 self.attributes.get(attribute_name)
507 }
508
509 pub fn set_attribute(&mut self, attribute_name: String, value: Value) {
515 self.attributes.insert(attribute_name, value);
516 }
517
518 pub fn get_schemas(&self) -> Vec<String> {
520 self.schemas
521 .iter()
522 .map(|s| s.as_str().to_string())
523 .collect()
524 }
525
526 pub fn get_schema_uris(&self) -> &[SchemaUri] {
528 &self.schemas
529 }
530
531 pub fn add_metadata(&mut self, base_url: &str, created: &str, last_modified: &str) {
539 let created_dt = chrono::DateTime::parse_from_rfc3339(created)
541 .map(|dt| dt.with_timezone(&chrono::Utc))
542 .unwrap_or_else(|_| chrono::Utc::now());
543
544 let last_modified_dt = chrono::DateTime::parse_from_rfc3339(last_modified)
545 .map(|dt| dt.with_timezone(&chrono::Utc))
546 .unwrap_or_else(|_| chrono::Utc::now());
547
548 let location = if let Some(id) = &self.id {
549 Some(Meta::generate_location(
550 base_url,
551 &self.resource_type,
552 id.as_str(),
553 ))
554 } else {
555 None
556 };
557
558 let version = if self.id.is_some() {
560 let resource_json = self.to_json().unwrap_or_default();
561 let content_bytes = resource_json.to_string().as_bytes().to_vec();
562 let scim_version = RawVersion::from_content(&content_bytes);
563 Some(scim_version.as_str().to_string())
564 } else {
565 None
566 };
567
568 if let Ok(meta) = Meta::new(
570 self.resource_type.clone(),
571 created_dt,
572 last_modified_dt,
573 location,
574 version,
575 ) {
576 self.set_meta(meta);
577 }
578 }
579
580 pub fn is_active(&self) -> bool {
584 self.attributes
585 .get("active")
586 .and_then(|v| v.as_bool())
587 .unwrap_or(true)
588 }
589
590 pub fn to_json(&self) -> ValidationResult<Value> {
595 let mut result = self.attributes.clone();
596
597 if let Some(ref id) = self.id {
599 result.insert("id".to_string(), Value::String(id.as_str().to_string()));
600 }
601
602 let schemas: Vec<Value> = self
603 .schemas
604 .iter()
605 .map(|s| Value::String(s.as_str().to_string()))
606 .collect();
607 result.insert("schemas".to_string(), Value::Array(schemas));
608
609 if let Some(ref external_id) = self.external_id {
610 result.insert(
611 "externalId".to_string(),
612 Value::String(external_id.as_str().to_string()),
613 );
614 }
615
616 if let Some(ref user_name) = self.user_name {
617 result.insert(
618 "userName".to_string(),
619 Value::String(user_name.as_str().to_string()),
620 );
621 }
622
623 if let Some(ref meta) = self.meta {
625 if let Ok(meta_json) = serde_json::to_value(meta) {
626 result.insert("meta".to_string(), meta_json);
627 }
628 }
629
630 if let Some(ref name) = self.name {
632 if let Ok(name_json) = serde_json::to_value(name) {
633 result.insert("name".to_string(), name_json);
634 }
635 }
636
637 if let Some(ref addresses) = self.addresses {
639 let addresses_json = serde_json::to_value(addresses.values())
640 .map_err(|e| ValidationError::custom(format!("Serialization error: {}", e)))?;
641 result.insert("addresses".to_string(), addresses_json);
642 }
643
644 if let Some(ref phone_numbers) = self.phone_numbers {
645 let phones_json = serde_json::to_value(phone_numbers.values())
646 .map_err(|e| ValidationError::custom(format!("Serialization error: {}", e)))?;
647 result.insert("phoneNumbers".to_string(), phones_json);
648 }
649
650 if let Some(ref emails) = self.emails {
651 let emails_json = serde_json::to_value(emails.values())
652 .map_err(|e| ValidationError::custom(format!("Serialization error: {}", e)))?;
653 result.insert("emails".to_string(), emails_json);
654 }
655
656 if let Some(ref members) = self.members {
657 let members_json = serde_json::to_value(members.values())
658 .map_err(|e| ValidationError::custom(format!("Serialization error: {}", e)))?;
659 result.insert("members".to_string(), members_json);
660 }
661
662 Ok(Value::Object(result))
663 }
664
665 pub fn get_external_id(&self) -> Option<&str> {
667 self.external_id.as_ref().map(|id| id.as_str())
668 }
669
670 fn extract_meta(data: &Value) -> ValidationResult<Option<Meta>> {
672 if let Some(meta_value) = data.get("meta") {
673 if let Some(meta_obj) = meta_value.as_object() {
674 let resource_type = if let Some(rt_value) = meta_obj.get("resourceType") {
676 if let Some(rt_str) = rt_value.as_str() {
677 Some(rt_str.to_string())
678 } else {
679 return Err(ValidationError::InvalidMetaStructure);
681 }
682 } else {
683 None
684 };
685
686 let created = meta_obj.get("created").and_then(|v| v.as_str());
687 let last_modified = meta_obj.get("lastModified").and_then(|v| v.as_str());
688
689 if meta_obj.get("created").is_some() && created.is_none() {
691 return Err(ValidationError::InvalidCreatedDateTime);
692 }
693 if meta_obj.get("lastModified").is_some() && last_modified.is_none() {
694 return Err(ValidationError::InvalidModifiedDateTime);
695 }
696
697 if let Some(location_value) = meta_obj.get("location") {
699 if !location_value.is_string() {
700 return Err(ValidationError::InvalidLocationUri);
701 }
702 }
703
704 if let Some(version_value) = meta_obj.get("version") {
706 if !version_value.is_string() {
707 return Err(ValidationError::InvalidVersionFormat);
708 }
709 }
710
711 if let (Some(resource_type), Some(created), Some(last_modified)) =
713 (resource_type, created, last_modified)
714 {
715 let location = meta_obj
716 .get("location")
717 .and_then(|v| v.as_str())
718 .map(|s| s.to_string());
719
720 let version = meta_obj
721 .get("version")
722 .and_then(|v| v.as_str())
723 .map(|s| s.to_string());
724
725 let created_dt = chrono::DateTime::parse_from_rfc3339(created)
727 .map_err(|_| ValidationError::InvalidCreatedDateTime)?
728 .with_timezone(&chrono::Utc);
729
730 let last_modified_dt = chrono::DateTime::parse_from_rfc3339(last_modified)
731 .map_err(|_| ValidationError::InvalidModifiedDateTime)?
732 .with_timezone(&chrono::Utc);
733
734 let meta = Meta::new(
735 resource_type,
736 created_dt,
737 last_modified_dt,
738 location,
739 version,
740 )?;
741 Ok(Some(meta))
742 } else {
743 Ok(None)
745 }
746 } else {
747 Err(ValidationError::InvalidMetaStructure)
748 }
749 } else {
750 Ok(None)
751 }
752 }
753
754 pub fn get_meta(&self) -> Option<&Meta> {
756 self.meta.as_ref()
757 }
758
759 pub fn set_meta(&mut self, meta: Meta) {
761 let meta_json = serde_json::to_value(&meta).unwrap_or(Value::Null);
763 self.set_attribute("meta".to_string(), meta_json);
764 self.meta = Some(meta);
765 }
766
767 pub fn create_meta(&mut self, base_url: &str) -> ValidationResult<()> {
769 let meta = Meta::new_for_creation(self.resource_type.clone())?;
770 let meta_with_location = if let Some(id) = &self.id {
771 let location = Meta::generate_location(base_url, &self.resource_type, id.as_str());
772 meta.with_location(location)?
773 } else {
774 meta
775 };
776 self.set_meta(meta_with_location);
777 Ok(())
778 }
779
780 pub fn update_meta(&mut self) {
782 if let Some(meta) = &self.meta {
783 let updated_meta = meta.with_updated_timestamp();
784 self.set_meta(updated_meta);
785 }
786 }
787}