1use crate::be::IdxKey;
15use crate::migration_data;
16use crate::prelude::*;
17use crate::valueset::ValueSet;
18use concread::cowcell::*;
19use hashbrown::{HashMap, HashSet};
20use std::collections::BTreeSet;
21use tracing::trace;
22use uuid::Uuid;
23
24pub struct Schema {
41 classes: CowCell<HashMap<AttrString, SchemaClass>>,
42 attributes: CowCell<HashMap<Attribute, SchemaAttribute>>,
43 unique_cache: CowCell<Vec<Attribute>>,
44 ref_cache: CowCell<HashMap<Attribute, SchemaAttribute>>,
45}
46
47pub struct SchemaWriteTransaction<'a> {
51 classes: CowCellWriteTxn<'a, HashMap<AttrString, SchemaClass>>,
52 attributes: CowCellWriteTxn<'a, HashMap<Attribute, SchemaAttribute>>,
53
54 unique_cache: CowCellWriteTxn<'a, Vec<Attribute>>,
55 ref_cache: CowCellWriteTxn<'a, HashMap<Attribute, SchemaAttribute>>,
56}
57
58pub struct SchemaReadTransaction {
60 classes: CowCellReadTxn<HashMap<AttrString, SchemaClass>>,
61 attributes: CowCellReadTxn<HashMap<Attribute, SchemaAttribute>>,
62
63 unique_cache: CowCellReadTxn<Vec<Attribute>>,
64 ref_cache: CowCellReadTxn<HashMap<Attribute, SchemaAttribute>>,
65}
66
67#[derive(Debug, Clone, Copy, Default)]
68pub enum Replicated {
69 #[default]
70 True,
71 False,
72}
73
74impl From<Replicated> for bool {
75 fn from(value: Replicated) -> bool {
76 match value {
77 Replicated::True => true,
78 Replicated::False => false,
79 }
80 }
81}
82
83impl From<bool> for Replicated {
84 fn from(value: bool) -> Self {
85 match value {
86 true => Replicated::True,
87 false => Replicated::False,
88 }
89 }
90}
91
92#[derive(Debug, Clone, Default)]
100pub struct SchemaAttribute {
101 pub name: Attribute,
102 pub uuid: Uuid,
103 pub description: String,
104 pub multivalue: bool,
106 pub unique: bool,
108 pub phantom: bool,
112 pub sync_allowed: bool,
115
116 pub replicated: Replicated,
118 pub indexed: bool,
120 pub syntax: SyntaxType,
122}
123
124impl SchemaAttribute {
125 pub fn try_from(value: &Entry<EntrySealed, EntryCommitted>) -> Result<Self, OperationError> {
126 let uuid = value.get_uuid();
130
131 if !value.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into()) {
133 admin_error!(
134 "class {} not present - {:?}",
135 EntryClass::AttributeType,
136 uuid
137 );
138 return Err(OperationError::InvalidSchemaState(format!(
139 "missing {}",
140 EntryClass::AttributeType
141 )));
142 }
143
144 let name = value
146 .get_ava_single_iutf8(Attribute::AttributeName)
147 .map(|s| s.into())
148 .ok_or_else(|| {
149 admin_error!("missing {} - {:?}", Attribute::AttributeName, uuid);
150 OperationError::InvalidSchemaState("missing attributename".to_string())
151 })?;
152 let description = value
154 .get_ava_single_utf8(Attribute::Description)
155 .map(|s| s.to_string())
156 .ok_or_else(|| {
157 admin_error!("missing {} - {}", Attribute::Description, name);
158 OperationError::InvalidSchemaState("missing description".to_string())
159 })?;
160
161 let multivalue = value
163 .get_ava_single_bool(Attribute::MultiValue)
164 .ok_or_else(|| {
165 admin_error!("missing {} - {}", Attribute::MultiValue, name);
166 OperationError::InvalidSchemaState("missing multivalue".to_string())
167 })?;
168
169 let unique = value
170 .get_ava_single_bool(Attribute::Unique)
171 .ok_or_else(|| {
172 admin_error!("missing {} - {}", Attribute::Unique, name);
173 OperationError::InvalidSchemaState("missing unique".to_string())
174 })?;
175
176 let phantom = value
177 .get_ava_single_bool(Attribute::Phantom)
178 .unwrap_or_default();
179
180 let sync_allowed = value
181 .get_ava_single_bool(Attribute::SyncAllowed)
182 .unwrap_or_default();
183
184 let replicated = value
187 .get_ava_single_bool(Attribute::Replicated)
188 .map(Replicated::from)
189 .unwrap_or_default();
190
191 let indexed = value
192 .get_ava_single_bool(Attribute::Indexed)
193 .unwrap_or_default();
194
195 let syntax = value
197 .get_ava_single_syntax(Attribute::Syntax)
198 .ok_or_else(|| {
199 admin_error!("missing {} - {}", Attribute::Syntax, name);
200 OperationError::InvalidSchemaState(format!("missing {}", Attribute::Syntax))
201 })?;
202
203 trace!(?name, ?indexed);
204
205 Ok(SchemaAttribute {
206 name,
207 uuid,
208 description,
209 multivalue,
210 unique,
211 phantom,
212 sync_allowed,
213 replicated,
214 indexed,
215 syntax,
216 })
217 }
218
219 pub fn validate_partialvalue(
223 &self,
224 a: &Attribute,
225 v: &PartialValue,
226 ) -> Result<(), SchemaError> {
227 let r = match self.syntax {
228 SyntaxType::Boolean => matches!(v, PartialValue::Bool(_)),
229 SyntaxType::SyntaxId => matches!(v, PartialValue::Syntax(_)),
230 SyntaxType::IndexId => matches!(v, PartialValue::Index(_)),
231 SyntaxType::Uuid => matches!(v, PartialValue::Uuid(_)),
232 SyntaxType::ReferenceUuid => matches!(v, PartialValue::Refer(_)),
233 SyntaxType::Utf8StringInsensitive => matches!(v, PartialValue::Iutf8(_)),
234 SyntaxType::Utf8StringIname => matches!(v, PartialValue::Iname(_)),
235 SyntaxType::Utf8String => matches!(v, PartialValue::Utf8(_)),
236 SyntaxType::JsonFilter => matches!(v, PartialValue::JsonFilt(_)),
237 SyntaxType::Credential => matches!(v, PartialValue::Cred(_)),
238 SyntaxType::SecretUtf8String => matches!(v, PartialValue::SecretValue),
239 SyntaxType::SshKey => matches!(v, PartialValue::SshKey(_)),
240 SyntaxType::SecurityPrincipalName => matches!(v, PartialValue::Spn(_, _)),
241 SyntaxType::Uint32 => matches!(v, PartialValue::Uint32(_)),
242 SyntaxType::Int64 => matches!(v, PartialValue::Int64(_)),
243 SyntaxType::Uint64 => matches!(v, PartialValue::Uint64(_)),
244 SyntaxType::Cid => matches!(v, PartialValue::Cid(_)),
245 SyntaxType::NsUniqueId => matches!(v, PartialValue::Nsuniqueid(_)),
246 SyntaxType::DateTime => matches!(v, PartialValue::DateTime(_)),
247 SyntaxType::EmailAddress => matches!(v, PartialValue::EmailAddress(_)),
248 SyntaxType::Url => matches!(v, PartialValue::Url(_)),
249 SyntaxType::OauthScope => matches!(v, PartialValue::OauthScope(_)),
250 SyntaxType::OauthScopeMap => matches!(v, PartialValue::Refer(_)),
251 SyntaxType::OauthClaimMap => {
252 matches!(v, PartialValue::Iutf8(_))
253 || matches!(v, PartialValue::Refer(_))
254 || matches!(v, PartialValue::OauthClaimValue(_, _, _))
255 || matches!(v, PartialValue::OauthClaim(_, _))
256 }
257 SyntaxType::PrivateBinary => matches!(v, PartialValue::PrivateBinary),
258 SyntaxType::IntentToken => matches!(v, PartialValue::IntentToken(_)),
259 SyntaxType::Passkey => matches!(v, PartialValue::Passkey(_)),
260 SyntaxType::AttestedPasskey => matches!(v, PartialValue::AttestedPasskey(_)),
261 SyntaxType::Session => matches!(v, PartialValue::Refer(_)),
263 SyntaxType::ApiToken => matches!(v, PartialValue::Refer(_)),
264 SyntaxType::Oauth2Session => matches!(v, PartialValue::Refer(_)),
265 SyntaxType::JwsKeyEs256 => matches!(v, PartialValue::Iutf8(_)),
267 SyntaxType::JwsKeyRs256 => matches!(v, PartialValue::Iutf8(_)),
268 SyntaxType::UiHint => matches!(v, PartialValue::UiHint(_)),
269 SyntaxType::EcKeyPrivate => matches!(v, PartialValue::SecretValue),
270 SyntaxType::TotpSecret => matches!(v, PartialValue::Utf8(_)),
272 SyntaxType::AuditLogString => matches!(v, PartialValue::Utf8(_)),
273 SyntaxType::Image => matches!(v, PartialValue::Utf8(_)),
274 SyntaxType::CredentialType => matches!(v, PartialValue::CredentialType(_)),
275
276 SyntaxType::HexString | SyntaxType::Certificate | SyntaxType::KeyInternal => {
277 matches!(v, PartialValue::HexString(_))
278 }
279
280 SyntaxType::WebauthnAttestationCaList => false,
281 SyntaxType::ApplicationPassword => {
282 matches!(v, PartialValue::Uuid(_)) || matches!(v, PartialValue::Refer(_))
283 }
284 SyntaxType::Sha256 => matches!(v, PartialValue::Sha256(_)),
285 SyntaxType::Json | SyntaxType::Message => false,
288 };
289 if r {
290 Ok(())
291 } else {
292 error!(
293 ?a,
294 ?self,
295 ?v,
296 "validate_partialvalue InvalidAttributeSyntax"
297 );
298 Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
299 }
300 }
301
302 pub fn validate_value(&self, a: &Attribute, v: &Value) -> Result<(), SchemaError> {
303 let r = v.validate()
304 && match self.syntax {
305 SyntaxType::Boolean => matches!(v, Value::Bool(_)),
306 SyntaxType::SyntaxId => matches!(v, Value::Syntax(_)),
307 SyntaxType::IndexId => matches!(v, Value::Index(_)),
308 SyntaxType::Uuid => matches!(v, Value::Uuid(_)),
309 SyntaxType::ReferenceUuid => matches!(v, Value::Refer(_)),
310 SyntaxType::Utf8StringInsensitive => matches!(v, Value::Iutf8(_)),
311 SyntaxType::Utf8StringIname => matches!(v, Value::Iname(_)),
312 SyntaxType::Utf8String => matches!(v, Value::Utf8(_)),
313 SyntaxType::JsonFilter => matches!(v, Value::JsonFilt(_)),
314 SyntaxType::Credential => matches!(v, Value::Cred(_, _)),
315 SyntaxType::SecretUtf8String => matches!(v, Value::SecretValue(_)),
316 SyntaxType::SshKey => matches!(v, Value::SshKey(_, _)),
317 SyntaxType::SecurityPrincipalName => matches!(v, Value::Spn(_, _)),
318 SyntaxType::Uint32 => matches!(v, Value::Uint32(_)),
319 SyntaxType::Int64 => matches!(v, Value::Int64(_)),
320 SyntaxType::Uint64 => matches!(v, Value::Uint64(_)),
321 SyntaxType::Cid => matches!(v, Value::Cid(_)),
322 SyntaxType::NsUniqueId => matches!(v, Value::Nsuniqueid(_)),
323 SyntaxType::DateTime => matches!(v, Value::DateTime(_)),
324 SyntaxType::EmailAddress => matches!(v, Value::EmailAddress(_, _)),
325 SyntaxType::Url => matches!(v, Value::Url(_)),
326 SyntaxType::OauthScope => matches!(v, Value::OauthScope(_)),
327 SyntaxType::OauthScopeMap => matches!(v, Value::OauthScopeMap(_, _)),
328 SyntaxType::OauthClaimMap => {
329 matches!(v, Value::OauthClaimValue(_, _, _))
330 || matches!(v, Value::OauthClaimMap(_, _))
331 }
332 SyntaxType::PrivateBinary => matches!(v, Value::PrivateBinary(_)),
333 SyntaxType::IntentToken => matches!(v, Value::IntentToken(_, _)),
334 SyntaxType::Passkey => matches!(v, Value::Passkey(_, _, _)),
335 SyntaxType::AttestedPasskey => matches!(v, Value::AttestedPasskey(_, _, _)),
336 SyntaxType::Session => matches!(v, Value::Session(_, _)),
337 SyntaxType::ApiToken => matches!(v, Value::ApiToken(_, _)),
338 SyntaxType::Oauth2Session => matches!(v, Value::Oauth2Session(_, _)),
339 SyntaxType::JwsKeyEs256 => matches!(v, Value::JwsKeyEs256(_)),
340 SyntaxType::JwsKeyRs256 => matches!(v, Value::JwsKeyRs256(_)),
341 SyntaxType::UiHint => matches!(v, Value::UiHint(_)),
342 SyntaxType::TotpSecret => matches!(v, Value::TotpSecret(_, _)),
343 SyntaxType::AuditLogString => matches!(v, Value::Utf8(_)),
344 SyntaxType::Image => matches!(v, Value::Image(_)),
345 SyntaxType::CredentialType => matches!(v, Value::CredentialType(_)),
346 SyntaxType::WebauthnAttestationCaList => {
347 matches!(v, Value::WebauthnAttestationCaList(_))
348 }
349 SyntaxType::KeyInternal => matches!(v, Value::KeyInternal { .. }),
350 SyntaxType::HexString => matches!(v, Value::HexString(_)),
351 SyntaxType::Certificate => matches!(v, Value::Certificate(_)),
352 SyntaxType::ApplicationPassword => matches!(v, Value::ApplicationPassword(..)),
353 SyntaxType::Json => matches!(v, Value::Json(_)),
354 SyntaxType::Sha256 => matches!(v, Value::Sha256(_)),
355 SyntaxType::EcKeyPrivate => matches!(v, Value::SecretValue(_)),
356 SyntaxType::Message => false,
357 };
358 if r {
359 Ok(())
360 } else {
361 error!(
362 ?a,
363 ?self,
364 ?v,
365 "validate_value failure - InvalidAttributeSyntax"
366 );
367 Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
368 }
369 }
370
371 pub fn validate_ava(&self, a: &Attribute, ava: &ValueSet) -> Result<(), SchemaError> {
372 trace!("Checking for valid {:?} -> {:?}", self.name, ava);
373 if !self.multivalue && ava.len() > 1 {
375 admin_error!("Ava len > 1 on single value attribute!");
377 return Err(SchemaError::InvalidAttributeSyntax(a.to_string()));
378 };
379 let valid = self.syntax == ava.syntax();
381 if valid && ava.validate(self) {
382 Ok(())
383 } else {
384 error!(
385 ?a,
386 "validate_ava - InvalidAttributeSyntax for {:?}", self.syntax
387 );
388 Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
389 }
390 }
391}
392
393#[derive(Debug, Clone, Default)]
411pub struct SchemaClass {
412 pub name: AttrString,
413 pub uuid: Uuid,
414 pub description: String,
415 pub sync_allowed: bool,
416 pub systemmay: Vec<Attribute>,
418 pub may: Vec<Attribute>,
419 pub systemmust: Vec<Attribute>,
420 pub must: Vec<Attribute>,
421 pub systemsupplements: Vec<AttrString>,
426 pub supplements: Vec<AttrString>,
427 pub systemexcludes: Vec<AttrString>,
429 pub excludes: Vec<AttrString>,
430}
431
432impl SchemaClass {
433 pub fn try_from(value: &Entry<EntrySealed, EntryCommitted>) -> Result<Self, OperationError> {
434 let uuid = value.get_uuid();
436 if !value.attribute_equality(Attribute::Class, &EntryClass::ClassType.into()) {
438 error!("class classtype not present - {:?}", uuid);
439 return Err(OperationError::InvalidSchemaState(
440 "missing classtype".to_string(),
441 ));
442 }
443
444 let name = value
446 .get_ava_single_iutf8(Attribute::ClassName)
447 .map(AttrString::from)
448 .ok_or_else(|| {
449 error!("missing {} - {:?}", Attribute::ClassName, uuid);
450 OperationError::InvalidSchemaState(format!("missing {}", Attribute::ClassName))
451 })?;
452
453 let description = value
455 .get_ava_single_utf8(Attribute::Description)
456 .map(String::from)
457 .ok_or_else(|| {
458 error!("missing {} - {}", Attribute::Description, name);
459 OperationError::InvalidSchemaState(format!("missing {}", Attribute::Description))
460 })?;
461
462 let sync_allowed = value
463 .get_ava_single_bool(Attribute::SyncAllowed)
464 .unwrap_or(false);
465
466 let systemmay = value
468 .get_ava_iter_iutf8(Attribute::SystemMay)
469 .into_iter()
470 .flat_map(|iter| iter.map(Attribute::from))
471 .collect();
472 let systemmust = value
473 .get_ava_iter_iutf8(Attribute::SystemMust)
474 .into_iter()
475 .flat_map(|iter| iter.map(Attribute::from))
476 .collect();
477 let may = value
478 .get_ava_iter_iutf8(Attribute::May)
479 .into_iter()
480 .flat_map(|iter| iter.map(Attribute::from))
481 .collect();
482 let must = value
483 .get_ava_iter_iutf8(Attribute::Must)
484 .into_iter()
485 .flat_map(|iter| iter.map(Attribute::from))
486 .collect();
487
488 let systemsupplements = value
489 .get_ava_iter_iutf8(Attribute::SystemSupplements)
490 .map(|i| i.map(|v| v.into()).collect())
491 .unwrap_or_default();
492 let supplements = value
493 .get_ava_iter_iutf8(Attribute::Supplements)
494 .map(|i| i.map(|v| v.into()).collect())
495 .unwrap_or_default();
496 let systemexcludes = value
497 .get_ava_iter_iutf8(Attribute::SystemExcludes)
498 .map(|i| i.map(|v| v.into()).collect())
499 .unwrap_or_default();
500 let excludes = value
501 .get_ava_iter_iutf8(Attribute::Excludes)
502 .map(|i| i.map(|v| v.into()).collect())
503 .unwrap_or_default();
504
505 Ok(SchemaClass {
506 name,
507 uuid,
508 description,
509 sync_allowed,
510 systemmay,
511 may,
512 systemmust,
513 must,
514 systemsupplements,
515 supplements,
516 systemexcludes,
517 excludes,
518 })
519 }
520
521 pub fn may_iter(&self) -> impl Iterator<Item = &Attribute> {
524 self.systemmay
525 .iter()
526 .chain(self.may.iter())
527 .chain(self.systemmust.iter())
528 .chain(self.must.iter())
529 }
530}
531
532pub trait SchemaTransaction {
533 fn get_classes(&self) -> &HashMap<AttrString, SchemaClass>;
534 fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute>;
535
536 fn get_attributes_unique(&self) -> &Vec<Attribute>;
537 fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute>;
538
539 fn validate(&self) -> Vec<Result<(), ConsistencyError>> {
540 let mut res = Vec::with_capacity(0);
541
542 let class_snapshot = self.get_classes();
543 let attribute_snapshot = self.get_attributes();
544
545 let mut unique_uuid_set = HashSet::new();
550 class_snapshot
551 .values()
552 .map(|class| &class.uuid)
553 .chain(attribute_snapshot.values().map(|attr| &attr.uuid))
554 .for_each(|uuid| {
555 if !unique_uuid_set.insert(uuid) {
557 res.push(Err(ConsistencyError::SchemaUuidNotUnique(*uuid)))
558 }
559 });
560
561 class_snapshot.values().for_each(|class| {
562 class
564 .systemmay
565 .iter()
566 .chain(class.may.iter())
567 .chain(class.systemmust.iter())
568 .chain(class.must.iter())
569 .for_each(|a| {
570 match attribute_snapshot.get(a) {
571 Some(attr) => {
572 if attr.phantom {
574 res.push(Err(ConsistencyError::SchemaClassPhantomAttribute(
575 class.name.to_string(),
576 a.to_string(),
577 )))
578 }
579 }
580 None => {
581 res.push(Err(ConsistencyError::SchemaClassMissingAttribute(
583 class.name.to_string(),
584 a.to_string(),
585 )))
586 }
587 }
588 })
589 }); res
591 }
592
593 fn is_replicated(&self, attr: &Attribute) -> bool {
594 match self.get_attributes().get(attr) {
595 Some(a_schema) => {
596 a_schema.replicated.into() && !a_schema.phantom
599 }
600 None => {
601 warn!(
602 "Attribute {} was not found in schema during replication request",
603 attr
604 );
605 false
606 }
607 }
608 }
609
610 fn is_multivalue(&self, attr: &Attribute) -> Result<bool, SchemaError> {
611 match self.get_attributes().get(attr) {
612 Some(a_schema) => Ok(a_schema.multivalue),
613 None => {
614 Err(SchemaError::InvalidAttribute(attr.to_string()))
616 }
617 }
618 }
619
620 fn normalise_attr_if_exists(&self, an: &str) -> Option<Attribute> {
621 let attr = Attribute::from(an);
622 if self.get_attributes().contains_key(&attr) {
623 Some(attr)
624 } else {
625 None
626 }
627 }
628
629 fn query_attrs_difference(
630 &self,
631 prev_class: &BTreeSet<&str>,
632 new_iutf8: &BTreeSet<&str>,
633 ) -> Result<(BTreeSet<&str>, BTreeSet<&str>), SchemaError> {
634 let schema_classes = self.get_classes();
635
636 let mut invalid_classes = Vec::with_capacity(0);
637
638 let prev_attrs: BTreeSet<&str> = prev_class
639 .iter()
640 .filter_map(|cls| match schema_classes.get(*cls) {
641 Some(x) => Some(x.may_iter()),
642 None => {
643 admin_debug!("invalid class: {:?}", cls);
644 invalid_classes.push(cls.to_string());
645 None
646 }
647 })
648 .flatten()
650 .map(|s| s.as_str())
651 .collect();
652
653 if !invalid_classes.is_empty() {
654 return Err(SchemaError::InvalidClass(invalid_classes));
655 };
656
657 let new_attrs: BTreeSet<&str> = new_iutf8
658 .iter()
659 .filter_map(|cls| match schema_classes.get(*cls) {
660 Some(x) => Some(x.may_iter()),
661 None => {
662 admin_debug!("invalid class: {:?}", cls);
663 invalid_classes.push(cls.to_string());
664 None
665 }
666 })
667 .flatten()
669 .map(|s| s.as_str())
670 .collect();
671
672 if !invalid_classes.is_empty() {
673 return Err(SchemaError::InvalidClass(invalid_classes));
674 };
675
676 let removed = prev_attrs.difference(&new_attrs).copied().collect();
677 let added = new_attrs.difference(&prev_attrs).copied().collect();
678
679 Ok((added, removed))
680 }
681}
682
683impl SchemaWriteTransaction<'_> {
684 pub fn commit(self) -> Result<(), OperationError> {
690 let SchemaWriteTransaction {
691 classes,
692 attributes,
693 unique_cache,
694 ref_cache,
695 } = self;
696
697 unique_cache.commit();
698 ref_cache.commit();
699 classes.commit();
700 attributes.commit();
701 Ok(())
702 }
703
704 pub fn update_attributes<I: Iterator<Item = SchemaAttribute>>(
705 &mut self,
706 attributetypes: I,
707 ) -> Result<(), OperationError> {
708 self.attributes.clear();
710
711 self.unique_cache.clear();
712 self.ref_cache.clear();
713 attributetypes.for_each(|a| {
717 if a.syntax == SyntaxType::ReferenceUuid ||
719 a.syntax == SyntaxType::OauthScopeMap ||
720 a.syntax == SyntaxType::OauthClaimMap ||
721 a.syntax == SyntaxType::Oauth2Session ||
723 a.syntax == SyntaxType::ApplicationPassword
725 {
728 self.ref_cache.insert(a.name.clone(), a.clone());
729 }
730 if a.unique {
731 self.unique_cache.push(a.name.clone());
732 }
733 self.attributes.insert(a.name.clone(), a);
735 });
736
737 Ok(())
738 }
739
740 pub fn update_classes<I: Iterator<Item = SchemaClass>>(
741 &mut self,
742 classtypes: I,
743 ) -> Result<(), OperationError> {
744 self.classes.clear();
746 classtypes.into_iter().for_each(|a| {
750 self.classes.insert(a.name.clone(), a);
751 });
752 Ok(())
753 }
754
755 pub fn to_entries(&self) -> Vec<Entry<EntryInit, EntryNew>> {
756 let r: Vec<_> = self
757 .attributes
758 .values()
759 .map(Entry::<EntryInit, EntryNew>::from)
760 .chain(
761 self.classes
762 .values()
763 .map(Entry::<EntryInit, EntryNew>::from),
764 )
765 .collect();
766 r
767 }
768
769 pub fn reload_idxmeta(&self) -> Vec<IdxKey> {
770 self.get_attributes()
771 .values()
772 .flat_map(|a| {
773 if a.indexed || a.unique {
775 a.syntax.index_types()
776 } else {
777 &[]
778 }
779 .iter()
780 .map(move |itype: &IndexType| IdxKey {
781 attr: a.name.clone(),
782 itype: *itype,
783 })
784 })
785 .collect()
786 }
787
788 #[instrument(level = "debug", name = "schema::generate_in_memory", skip_all)]
797 pub fn generate_in_memory(&mut self) -> Result<(), OperationError> {
798 self.update_attributes(migration_data::system::attributes().into_iter())?;
801 self.update_classes(migration_data::system::classes().into_iter())?;
802
803 let r = self.validate();
804 if r.is_empty() {
805 debug!("schema validate -> passed");
806 Ok(())
807 } else {
808 error!(err = ?r, "schema validate -> errors");
809 Err(OperationError::ConsistencyError(
810 r.into_iter().filter_map(|v| v.err()).collect(),
811 ))
812 }
813 }
814
815 #[instrument(level = "debug", name = "schema::extend_in_memory", skip_all)]
816 pub fn extend_in_memory(
817 &mut self,
818 extra_attrs: Vec<SchemaAttribute>,
819 extra_classes: Vec<SchemaClass>,
820 ) -> Result<(), OperationError> {
821 self.update_attributes(
822 migration_data::system::attributes()
823 .into_iter()
824 .chain(extra_attrs.into_iter()),
825 )?;
826 self.update_classes(
827 migration_data::system::classes()
828 .into_iter()
829 .chain(extra_classes.into_iter()),
830 )?;
831
832 let r = self.validate();
833 if r.is_empty() {
834 debug!("schema validate -> passed");
835 Ok(())
836 } else {
837 error!(err = ?r, "schema validate -> errors");
838 Err(OperationError::ConsistencyError(
839 r.into_iter().filter_map(|v| v.err()).collect(),
840 ))
841 }
842 }
843}
844
845impl SchemaTransaction for SchemaWriteTransaction<'_> {
846 fn get_attributes_unique(&self) -> &Vec<Attribute> {
847 &self.unique_cache
848 }
849
850 fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute> {
851 &self.ref_cache
852 }
853
854 fn get_classes(&self) -> &HashMap<AttrString, SchemaClass> {
855 &self.classes
856 }
857
858 fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute> {
859 &self.attributes
860 }
861}
862
863impl SchemaTransaction for SchemaReadTransaction {
864 fn get_attributes_unique(&self) -> &Vec<Attribute> {
865 &self.unique_cache
866 }
867
868 fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute> {
869 &self.ref_cache
870 }
871
872 fn get_classes(&self) -> &HashMap<AttrString, SchemaClass> {
873 &self.classes
874 }
875
876 fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute> {
877 &self.attributes
878 }
879}
880
881impl Schema {
882 pub fn new() -> Result<Self, OperationError> {
883 let s = Schema {
884 classes: CowCell::new(HashMap::with_capacity(128)),
885 attributes: CowCell::new(HashMap::with_capacity(128)),
886 unique_cache: CowCell::new(Vec::with_capacity(0)),
887 ref_cache: CowCell::new(HashMap::with_capacity(64)),
888 };
889 let mut sw = s.write();
890 let r1 = sw.generate_in_memory();
891 debug_assert!(r1.is_ok());
892 r1?;
893 let r2 = sw.commit().map(|_| s);
894 debug_assert!(r2.is_ok());
895 r2
896 }
897
898 pub fn read(&self) -> SchemaReadTransaction {
899 SchemaReadTransaction {
900 classes: self.classes.read(),
901 attributes: self.attributes.read(),
902 unique_cache: self.unique_cache.read(),
903 ref_cache: self.ref_cache.read(),
904 }
905 }
906
907 pub fn write(&self) -> SchemaWriteTransaction<'_> {
908 SchemaWriteTransaction {
909 classes: self.classes.write(),
910 attributes: self.attributes.write(),
911 unique_cache: self.unique_cache.write(),
912 ref_cache: self.ref_cache.write(),
913 }
914 }
915
916 #[cfg(test)]
917 pub(crate) fn write_blocking(&self) -> SchemaWriteTransaction<'_> {
918 self.write()
919 }
920}
921
922#[cfg(test)]
923mod tests {
924 use crate::prelude::*;
925 use crate::schema::{Schema, SchemaAttribute, SchemaClass, SchemaTransaction, SyntaxType};
926 use uuid::Uuid;
927
928 macro_rules! validate_schema {
931 ($sch:ident) => {{
932 let r: Result<Vec<()>, ConsistencyError> = $sch.validate().into_iter().collect();
934 assert!(r.is_ok());
935 }};
936 }
937
938 macro_rules! sch_from_entry_ok {
939 (
940 $e:expr,
941 $type:ty
942 ) => {{
943 let ev1 = $e.into_sealed_committed();
944
945 let r1 = <$type>::try_from(&ev1);
946 assert!(r1.is_ok());
947 }};
948 }
949
950 macro_rules! sch_from_entry_err {
951 (
952 $e:expr,
953 $type:ty
954 ) => {{
955 let ev1 = $e.into_sealed_committed();
956
957 let r1 = <$type>::try_from(&ev1);
958 assert!(r1.is_err());
959 }};
960 }
961
962 #[test]
963 fn test_schema_attribute_from_entry() {
964 sketching::test_init();
965
966 sch_from_entry_err!(
967 entry_init!(
968 (Attribute::Class, EntryClass::Object.to_value()),
969 (Attribute::Class, EntryClass::AttributeType.to_value()),
970 (
971 Attribute::AttributeName,
972 Value::new_iutf8("schema_attr_test")
973 ),
974 (
975 Attribute::Uuid,
976 Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
977 ),
978 (Attribute::Unique, Value::Bool(false))
979 ),
980 SchemaAttribute
981 );
982
983 sch_from_entry_err!(
984 entry_init!(
985 (Attribute::Class, EntryClass::Object.to_value()),
986 (Attribute::Class, EntryClass::AttributeType.to_value()),
987 (
988 Attribute::AttributeName,
989 Value::new_iutf8("schema_attr_test")
990 ),
991 (
992 Attribute::Uuid,
993 Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
994 ),
995 (Attribute::MultiValue, Value::Bool(false)),
996 (Attribute::Unique, Value::Bool(false)),
997 (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
998 ),
999 SchemaAttribute
1000 );
1001
1002 sch_from_entry_err!(
1003 entry_init!(
1004 (Attribute::Class, EntryClass::Object.to_value()),
1005 (Attribute::Class, EntryClass::AttributeType.to_value()),
1006 (
1007 Attribute::AttributeName,
1008 Value::new_iutf8("schema_attr_test")
1009 ),
1010 (
1011 Attribute::Uuid,
1012 Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1013 ),
1014 (
1015 Attribute::Description,
1016 Value::Utf8("Test attr parsing".to_string())
1017 ),
1018 (Attribute::MultiValue, Value::Utf8("htouaoeu".to_string())),
1019 (Attribute::Unique, Value::Bool(false)),
1020 (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
1021 ),
1022 SchemaAttribute
1023 );
1024
1025 sch_from_entry_err!(
1026 entry_init!(
1027 (Attribute::Class, EntryClass::Object.to_value()),
1028 (Attribute::Class, EntryClass::AttributeType.to_value()),
1029 (
1030 Attribute::AttributeName,
1031 Value::new_iutf8("schema_attr_test")
1032 ),
1033 (
1034 Attribute::Uuid,
1035 Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1036 ),
1037 (
1038 Attribute::Description,
1039 Value::Utf8("Test attr parsing".to_string())
1040 ),
1041 (Attribute::MultiValue, Value::Bool(false)),
1042 (Attribute::Unique, Value::Bool(false)),
1043 (Attribute::Syntax, Value::Utf8("TNEOUNTUH".to_string()))
1044 ),
1045 SchemaAttribute
1046 );
1047
1048 sch_from_entry_ok!(
1050 entry_init!(
1051 (Attribute::Class, EntryClass::Object.to_value()),
1052 (Attribute::Class, EntryClass::AttributeType.to_value()),
1053 (
1054 Attribute::AttributeName,
1055 Value::new_iutf8("schema_attr_test")
1056 ),
1057 (
1058 Attribute::Uuid,
1059 Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1060 ),
1061 (
1062 Attribute::Description,
1063 Value::Utf8("Test attr parsing".to_string())
1064 ),
1065 (Attribute::MultiValue, Value::Bool(false)),
1066 (Attribute::Unique, Value::Bool(false)),
1067 (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
1068 ),
1069 SchemaAttribute
1070 );
1071
1072 sch_from_entry_ok!(
1074 entry_init!(
1075 (Attribute::Class, EntryClass::Object.to_value()),
1076 (Attribute::Class, EntryClass::AttributeType.to_value()),
1077 (
1078 Attribute::AttributeName,
1079 Value::new_iutf8("schema_attr_test")
1080 ),
1081 (
1082 Attribute::Uuid,
1083 Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1084 ),
1085 (
1086 Attribute::Description,
1087 Value::Utf8("Test attr parsing".to_string())
1088 ),
1089 (Attribute::MultiValue, Value::Bool(false)),
1090 (Attribute::Unique, Value::Bool(false)),
1091 (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
1092 (Attribute::Index, Value::Bool(true))
1093 ),
1094 SchemaAttribute
1095 );
1096 }
1097
1098 #[test]
1099 fn test_schema_class_from_entry() {
1100 sch_from_entry_err!(
1101 entry_init!(
1102 (Attribute::Class, EntryClass::Object.to_value()),
1103 (Attribute::Class, EntryClass::ClassType.to_value()),
1104 (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
1105 (
1106 Attribute::Uuid,
1107 Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1108 )
1109 ),
1110 SchemaClass
1111 );
1112
1113 sch_from_entry_err!(
1114 entry_init!(
1115 (Attribute::Class, EntryClass::Object.to_value()),
1116 (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
1117 (
1118 Attribute::Uuid,
1119 Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1120 ),
1121 (
1122 Attribute::Description,
1123 Value::Utf8("class test".to_string())
1124 )
1125 ),
1126 SchemaClass
1127 );
1128
1129 sch_from_entry_ok!(
1131 entry_init!(
1132 (Attribute::Class, EntryClass::Object.to_value()),
1133 (Attribute::Class, EntryClass::ClassType.to_value()),
1134 (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
1135 (
1136 Attribute::Uuid,
1137 Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1138 ),
1139 (
1140 Attribute::Description,
1141 Value::Utf8("class test".to_string())
1142 )
1143 ),
1144 SchemaClass
1145 );
1146
1147 sch_from_entry_ok!(
1149 entry_init!(
1150 (Attribute::Class, EntryClass::Object.to_value()),
1151 (Attribute::Class, EntryClass::ClassType.to_value()),
1152 (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
1153 (
1154 Attribute::Uuid,
1155 Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1156 ),
1157 (
1158 Attribute::Description,
1159 Value::Utf8("class test".to_string())
1160 ),
1161 (Attribute::SystemMust, Value::new_iutf8("a"))
1162 ),
1163 SchemaClass
1164 );
1165
1166 sch_from_entry_ok!(
1167 entry_init!(
1168 (Attribute::Class, EntryClass::Object.to_value()),
1169 (Attribute::Class, EntryClass::ClassType.to_value()),
1170 (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
1171 (
1172 Attribute::Uuid,
1173 Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1174 ),
1175 (
1176 Attribute::Description,
1177 Value::Utf8("class test".to_string())
1178 ),
1179 (Attribute::SystemMay, Value::new_iutf8("a"))
1180 ),
1181 SchemaClass
1182 );
1183
1184 sch_from_entry_ok!(
1185 entry_init!(
1186 (Attribute::Class, EntryClass::Object.to_value()),
1187 (Attribute::Class, EntryClass::ClassType.to_value()),
1188 (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
1189 (
1190 Attribute::Uuid,
1191 Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1192 ),
1193 (
1194 Attribute::Description,
1195 Value::Utf8("class test".to_string())
1196 ),
1197 (Attribute::May, Value::new_iutf8("a")),
1198 (Attribute::Must, Value::new_iutf8("b"))
1199 ),
1200 SchemaClass
1201 );
1202
1203 sch_from_entry_ok!(
1204 entry_init!(
1205 (Attribute::Class, EntryClass::Object.to_value()),
1206 (Attribute::Class, EntryClass::ClassType.to_value()),
1207 (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
1208 (
1209 Attribute::Uuid,
1210 Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
1211 ),
1212 (
1213 Attribute::Description,
1214 Value::Utf8("class test".to_string())
1215 ),
1216 (Attribute::May, Value::new_iutf8("a")),
1217 (Attribute::Must, Value::new_iutf8("b")),
1218 (Attribute::SystemMay, Value::new_iutf8("c")),
1219 (Attribute::SystemMust, Value::new_iutf8("d"))
1220 ),
1221 SchemaClass
1222 );
1223 }
1224
1225 #[test]
1226 fn test_schema_attribute_simple() {
1227 let single_value_string = SchemaAttribute {
1231 name: Attribute::from("single_value"),
1232 uuid: Uuid::new_v4(),
1233 description: String::from(""),
1234 syntax: SyntaxType::Utf8StringInsensitive,
1235 ..Default::default()
1236 };
1237
1238 let r1 = single_value_string
1239 .validate_ava(&Attribute::from("single_value"), &(vs_iutf8!["test"] as _));
1240 assert_eq!(r1, Ok(()));
1241
1242 let rvs = vs_iutf8!["test1", "test2"] as _;
1243 let r2 = single_value_string.validate_ava(&Attribute::from("single_value"), &rvs);
1244 assert_eq!(
1245 r2,
1246 Err(SchemaError::InvalidAttributeSyntax(
1247 "single_value".to_string()
1248 ))
1249 );
1250
1251 let multi_value_string = SchemaAttribute {
1254 name: Attribute::from("mv_string"),
1255 uuid: Uuid::new_v4(),
1256 description: String::from(""),
1257 multivalue: true,
1258 syntax: SyntaxType::Utf8String,
1259 ..Default::default()
1260 };
1261
1262 let rvs = vs_utf8!["test1".to_string(), "test2".to_string()] as _;
1263 let r5 = multi_value_string.validate_ava(&Attribute::from("mv_string"), &rvs);
1264 assert_eq!(r5, Ok(()));
1265
1266 let multi_value_boolean = SchemaAttribute {
1267 name: Attribute::from("mv_bool"),
1268 uuid: Uuid::new_v4(),
1269 description: String::from(""),
1270 multivalue: true,
1271 syntax: SyntaxType::Boolean,
1272 ..Default::default()
1273 };
1274
1275 let rvs = vs_bool![true, false];
1292 let r4 = multi_value_boolean.validate_ava(&Attribute::from("mv_bool"), &(rvs as _));
1293 assert_eq!(r4, Ok(()));
1294
1295 let single_value_syntax = SchemaAttribute {
1297 name: Attribute::from("sv_syntax"),
1298 uuid: Uuid::new_v4(),
1299 description: String::from(""),
1300 syntax: SyntaxType::SyntaxId,
1301 ..Default::default()
1302 };
1303
1304 let rvs = vs_syntax![SyntaxType::try_from("UTF8STRING").unwrap()] as _;
1305 let r6 = single_value_syntax.validate_ava(&Attribute::from("sv_syntax"), &rvs);
1306 assert_eq!(r6, Ok(()));
1307
1308 let rvs = vs_utf8!["thaeountaheu".to_string()] as _;
1309 let r7 = single_value_syntax.validate_ava(&Attribute::from("sv_syntax"), &rvs);
1310 assert_eq!(
1311 r7,
1312 Err(SchemaError::InvalidAttributeSyntax("sv_syntax".to_string()))
1313 );
1314
1315 let single_value_index = SchemaAttribute {
1316 name: Attribute::from("sv_index"),
1317 uuid: Uuid::new_v4(),
1318 description: String::from(""),
1319 syntax: SyntaxType::IndexId,
1320 ..Default::default()
1321 };
1322
1323 let rvs = vs_utf8!["thaeountaheu".to_string()] as _;
1324 let r9 = single_value_index.validate_ava(&Attribute::from("sv_index"), &rvs);
1325 assert_eq!(
1326 r9,
1327 Err(SchemaError::InvalidAttributeSyntax("sv_index".to_string()))
1328 );
1329 }
1330
1331 #[test]
1332 fn test_schema_simple() {
1333 let schema = Schema::new().expect("failed to create schema");
1334 let schema_ro = schema.read();
1335 validate_schema!(schema_ro);
1336 }
1337
1338 #[test]
1339 fn test_schema_entries() {
1340 sketching::test_init();
1341 let schema_outer = Schema::new().expect("failed to create schema");
1344 let schema = schema_outer.read();
1345
1346 let e_no_uuid = entry_init!().into_invalid_new();
1347
1348 assert_eq!(
1349 e_no_uuid.validate(&schema),
1350 Err(SchemaError::MissingMustAttribute(vec![Attribute::Uuid]))
1351 );
1352
1353 let e_no_class = entry_init!((
1354 Attribute::Uuid,
1355 Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1356 ))
1357 .into_invalid_new();
1358
1359 assert_eq!(e_no_class.validate(&schema), Err(SchemaError::NoClassFound));
1360
1361 let e_bad_class = entry_init!(
1362 (
1363 Attribute::Uuid,
1364 Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1365 ),
1366 (Attribute::Class, Value::new_iutf8("zzzzzz"))
1367 )
1368 .into_invalid_new();
1369 assert_eq!(
1370 e_bad_class.validate(&schema),
1371 Err(SchemaError::InvalidClass(vec!["zzzzzz".to_string()]))
1372 );
1373
1374 let e_attr_invalid = entry_init!(
1375 (
1376 Attribute::Uuid,
1377 Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1378 ),
1379 (Attribute::Class, EntryClass::Object.to_value()),
1380 (Attribute::Class, EntryClass::AttributeType.to_value())
1381 )
1382 .into_invalid_new();
1383 let res = e_attr_invalid.validate(&schema);
1384 matches!(res, Err(SchemaError::MissingMustAttribute(_)));
1385
1386 let e_attr_invalid_may = entry_init!(
1387 (Attribute::Class, EntryClass::Object.to_value()),
1388 (Attribute::Class, EntryClass::AttributeType.to_value()),
1389 (Attribute::AttributeName, Value::new_iutf8("testattr")),
1390 (Attribute::Description, Value::Utf8("testattr".to_string())),
1391 (Attribute::MultiValue, Value::Bool(false)),
1392 (Attribute::Unique, Value::Bool(false)),
1393 (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
1394 (
1395 Attribute::Uuid,
1396 Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1397 ),
1398 (Attribute::TestAttr, Value::Utf8("zzzz".to_string()))
1399 )
1400 .into_invalid_new();
1401
1402 assert_eq!(
1403 e_attr_invalid_may.validate(&schema),
1404 Err(SchemaError::AttributeNotValidForClass(
1405 Attribute::TestAttr.to_string()
1406 ))
1407 );
1408
1409 let e_attr_invalid_syn = entry_init!(
1410 (Attribute::Class, EntryClass::Object.to_value()),
1411 (Attribute::Class, EntryClass::AttributeType.to_value()),
1412 (Attribute::AttributeName, Value::new_iutf8("testattr")),
1413 (Attribute::Description, Value::Utf8("testattr".to_string())),
1414 (Attribute::MultiValue, Value::Utf8("false".to_string())),
1415 (Attribute::Unique, Value::Bool(false)),
1416 (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
1417 (
1418 Attribute::Uuid,
1419 Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1420 )
1421 )
1422 .into_invalid_new();
1423
1424 assert_eq!(
1425 e_attr_invalid_syn.validate(&schema),
1426 Err(SchemaError::InvalidAttributeSyntax(
1427 "multivalue".to_string()
1428 ))
1429 );
1430
1431 let e_phantom = entry_init!(
1433 (Attribute::Class, EntryClass::Object.to_value()),
1434 (Attribute::Class, EntryClass::AttributeType.to_value()),
1435 (Attribute::AttributeName, Value::new_iutf8("testattr")),
1436 (Attribute::Description, Value::Utf8("testattr".to_string())),
1437 (Attribute::MultiValue, Value::Bool(false)),
1438 (Attribute::Unique, Value::Bool(false)),
1439 (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
1440 (
1441 Attribute::Uuid,
1442 Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1443 ),
1444 (
1445 Attribute::PasswordImport,
1446 Value::Utf8("password".to_string())
1447 )
1448 )
1449 .into_invalid_new();
1450 assert!(e_phantom.validate(&schema).is_err());
1451
1452 let e_ok = entry_init!(
1453 (Attribute::Class, EntryClass::Object.to_value()),
1454 (Attribute::Class, EntryClass::AttributeType.to_value()),
1455 (Attribute::AttributeName, Value::new_iutf8("testattr")),
1456 (Attribute::Description, Value::Utf8("testattr".to_string())),
1457 (Attribute::MultiValue, Value::Bool(true)),
1458 (Attribute::Unique, Value::Bool(false)),
1459 (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
1460 (
1461 Attribute::Uuid,
1462 Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1463 )
1464 )
1465 .into_invalid_new();
1466 assert!(e_ok.validate(&schema).is_ok());
1467 }
1468
1469 #[test]
1470 fn test_schema_extensible() {
1471 let schema_outer = Schema::new().expect("failed to create schema");
1472 let schema = schema_outer.read();
1473 let e_extensible_bad = entry_init!(
1475 (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
1476 (
1477 Attribute::Uuid,
1478 Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1479 ),
1480 (Attribute::MultiValue, Value::Utf8("zzzz".to_string()))
1481 )
1482 .into_invalid_new();
1483
1484 assert_eq!(
1485 e_extensible_bad.validate(&schema),
1486 Err(SchemaError::InvalidAttributeSyntax(
1487 "multivalue".to_string()
1488 ))
1489 );
1490
1491 let e_extensible_phantom = entry_init!(
1493 (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
1494 (
1495 Attribute::Uuid,
1496 Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1497 ),
1498 (Attribute::PasswordImport, Value::Utf8("zzzz".to_string()))
1499 )
1500 .into_invalid_new();
1501
1502 assert_eq!(
1503 e_extensible_phantom.validate(&schema),
1504 Err(SchemaError::PhantomAttribute(
1505 Attribute::PasswordImport.to_string()
1506 ))
1507 );
1508
1509 let e_extensible = entry_init!(
1510 (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
1511 (
1512 Attribute::Uuid,
1513 Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
1514 ),
1515 (Attribute::MultiValue, Value::Bool(true))
1516 )
1517 .into_invalid_new();
1518
1519 assert!(e_extensible.validate(&schema).is_ok());
1521 }
1522
1523 #[test]
1524 fn test_schema_filter_validation() {
1525 let schema_outer = Schema::new().expect("failed to create schema");
1526 let schema = schema_outer.read();
1527
1528 let f_bool = filter_all!(f_eq(Attribute::MultiValue, PartialValue::new_iutf8("zzzz")));
1530 assert_eq!(
1531 f_bool.validate(&schema),
1532 Err(SchemaError::InvalidAttributeSyntax(
1533 "multivalue".to_string()
1534 ))
1535 );
1536 let f_insense = filter_all!(f_eq(Attribute::Class, EntryClass::AttributeType.into()));
1538 assert_eq!(
1539 f_insense.validate(&schema),
1540 Ok(filter_valid!(f_eq(
1541 Attribute::Class,
1542 EntryClass::AttributeType.into()
1543 )))
1544 );
1545 let f_or_empty = filter_all!(f_or!([]));
1547 assert_eq!(f_or_empty.validate(&schema), Err(SchemaError::EmptyFilter));
1548 let f_or = filter_all!(f_or!([f_eq(
1549 Attribute::MultiValue,
1550 PartialValue::new_iutf8("zzzz")
1551 )]));
1552 assert_eq!(
1553 f_or.validate(&schema),
1554 Err(SchemaError::InvalidAttributeSyntax(
1555 "multivalue".to_string()
1556 ))
1557 );
1558 let f_or_mult = filter_all!(f_and!([
1559 f_eq(Attribute::Class, EntryClass::AttributeType.into()),
1560 f_eq(Attribute::MultiValue, PartialValue::new_iutf8("zzzzzzz")),
1561 ]));
1562 assert_eq!(
1563 f_or_mult.validate(&schema),
1564 Err(SchemaError::InvalidAttributeSyntax(
1565 "multivalue".to_string()
1566 ))
1567 );
1568 let f_or_ok = filter_all!(f_andnot(f_and!([
1570 f_eq(Attribute::Class, EntryClass::AttributeType.into()),
1571 f_sub(Attribute::Class, EntryClass::ClassType.into()),
1572 f_pres(Attribute::Class)
1573 ])));
1574 assert_eq!(
1575 f_or_ok.validate(&schema),
1576 Ok(filter_valid!(f_andnot(f_and!([
1577 f_eq(Attribute::Class, EntryClass::AttributeType.into()),
1578 f_sub(Attribute::Class, EntryClass::ClassType.into()),
1579 f_pres(Attribute::Class)
1580 ]))))
1581 );
1582 }
1583
1584 #[test]
1585 fn test_schema_class_phantom_reject() {
1586 let schema_outer = Schema::new().expect("failed to create schema");
1588 let mut schema = schema_outer.write_blocking();
1589
1590 assert!(schema.validate().is_empty());
1591
1592 let class = SchemaClass {
1594 name: AttrString::from("testobject"),
1595 uuid: Uuid::new_v4(),
1596 description: String::from("test object"),
1597 systemmay: vec![Attribute::Claim],
1598 ..Default::default()
1599 };
1600
1601 assert!(schema.update_classes(std::iter::once(class)).is_ok());
1602
1603 assert_eq!(schema.validate().len(), 1);
1604 }
1605
1606 #[test]
1607 fn test_schema_class_exclusion_requires() {
1608 sketching::test_init();
1609
1610 let schema_outer = Schema::new().expect("failed to create schema");
1611 let mut schema = schema_outer.write_blocking();
1612
1613 assert!(schema.validate().is_empty());
1614
1615 let class_account = SchemaClass {
1618 name: Attribute::Account.into(),
1619 uuid: Uuid::new_v4(),
1620 description: String::from("account object"),
1621 systemmust: vec![
1622 Attribute::Class,
1623 Attribute::Uuid,
1624 Attribute::LastModifiedCid,
1625 Attribute::CreatedAtCid,
1626 ],
1627 systemsupplements: vec![EntryClass::Service.into(), EntryClass::Person.into()],
1628 ..Default::default()
1629 };
1630
1631 let class_person = SchemaClass {
1632 name: EntryClass::Person.into(),
1633 uuid: Uuid::new_v4(),
1634 description: String::from("person object"),
1635 systemmust: vec![
1636 Attribute::Class,
1637 Attribute::Uuid,
1638 Attribute::LastModifiedCid,
1639 Attribute::CreatedAtCid,
1640 ],
1641 ..Default::default()
1642 };
1643
1644 let class_service = SchemaClass {
1645 name: EntryClass::Service.into(),
1646 uuid: Uuid::new_v4(),
1647 description: String::from("service object"),
1648 systemmust: vec![
1649 Attribute::Class,
1650 Attribute::Uuid,
1651 Attribute::LastModifiedCid,
1652 Attribute::CreatedAtCid,
1653 ],
1654 excludes: vec![EntryClass::Person.into()],
1655 ..Default::default()
1656 };
1657
1658 assert!(schema
1659 .update_classes([class_account, class_person, class_service].into_iter())
1660 .is_ok());
1661
1662 let e_account = entry_init!(
1664 (Attribute::Class, EntryClass::Account.to_value()),
1665 (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
1666 )
1667 .into_invalid_new();
1668
1669 assert_eq!(
1670 e_account.validate(&schema),
1671 Err(SchemaError::SupplementsNotSatisfied(vec![
1672 EntryClass::Service.into(),
1673 EntryClass::Person.into(),
1674 ]))
1675 );
1676
1677 let e_service_person = entry_init!(
1692 (Attribute::Class, EntryClass::Service.to_value()),
1693 (Attribute::Class, EntryClass::Account.to_value()),
1694 (Attribute::Class, EntryClass::Person.to_value()),
1695 (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
1696 )
1697 .into_invalid_new();
1698
1699 assert_eq!(
1700 e_service_person.validate(&schema),
1701 Err(SchemaError::ExcludesNotSatisfied(vec![
1702 EntryClass::Person.to_string()
1703 ]))
1704 );
1705
1706 let e_service_valid = entry_init!(
1708 (Attribute::Class, EntryClass::Service.to_value()),
1709 (Attribute::Class, EntryClass::Account.to_value()),
1710 (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
1711 )
1712 .into_invalid_new();
1713
1714 assert!(e_service_valid.validate(&schema).is_ok());
1715
1716 let e_person_valid = entry_init!(
1717 (Attribute::Class, EntryClass::Person.to_value()),
1718 (Attribute::Class, EntryClass::Account.to_value()),
1719 (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
1720 )
1721 .into_invalid_new();
1722
1723 assert!(e_person_valid.validate(&schema).is_ok());
1724
1725 let e_person_valid = entry_init!(
1726 (Attribute::Class, EntryClass::Person.to_value()),
1727 (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
1728 )
1729 .into_invalid_new();
1730
1731 assert!(e_person_valid.validate(&schema).is_ok());
1732 }
1733}