//! [`Schema`] is one of the foundational concepts of the server. It provides a
//! set of rules to enforce that [`Entries`] ava's must be compliant to, to be
//! considered valid for commit to the database. This allows us to provide
//! requirements and structure as to what an [`Entry`] must have and may contain
//! which enables many other parts to function.
//!
//! To define this structure we define [`Attributes`] that provide rules for how
//! and ava should be structured. We also define [`Classes`] that define
//! the rules of which [`Attributes`] may or must exist on an [`Entry`] for it
//! to be considered valid. An [`Entry`] must have at least 1 to infinite
//! [`Classes`]. [`Classes'] are additive.
//!
//! [`Schema`]: struct.Schema.html
//! [`Entries`]: ../entry/index.html
//! [`Entry`]: ../entry/index.html
//! [`Attributes`]: struct.SchemaAttribute.html
//! [`Classes`]: struct.SchemaClass.html

use crate::be::IdxKey;
use crate::prelude::*;
use crate::valueset::ValueSet;
use concread::cowcell::*;
use hashbrown::{HashMap, HashSet};
use std::collections::BTreeSet;
use tracing::trace;
use uuid::Uuid;

// representations of schema that confines object types, classes
// and attributes. This ties in deeply with "Entry".
//
// In the future this will parse/read it's schema from the db
// but we have to bootstrap with some core types.

/// Schema stores the set of [`Classes`] and [`Attributes`] that the server will
/// use to validate [`Entries`], [`Filters`] and [`Modifications`]. Additionally the
/// schema stores an extracted copy of the current attribute indexing metadata that
/// is used by the backend during queries.
///
/// [`Filters`]: ../filter/index.html
/// [`Modifications`]: ../modify/index.html
/// [`Entries`]: ../entry/index.html
/// [`Attributes`]: struct.SchemaAttribute.html
/// [`Classes`]: struct.SchemaClass.html
pub struct Schema {
    classes: CowCell<HashMap<AttrString, SchemaClass>>,
    attributes: CowCell<HashMap<Attribute, SchemaAttribute>>,
    unique_cache: CowCell<Vec<Attribute>>,
    ref_cache: CowCell<HashMap<Attribute, SchemaAttribute>>,
}

/// A writable transaction of the working schema set. You should not change this directly,
/// the writability is for the server internally to allow reloading of the schema. Changes
/// you make will be lost when the server re-reads the schema from disk.
pub struct SchemaWriteTransaction<'a> {
    classes: CowCellWriteTxn<'a, HashMap<AttrString, SchemaClass>>,
    attributes: CowCellWriteTxn<'a, HashMap<Attribute, SchemaAttribute>>,

    unique_cache: CowCellWriteTxn<'a, Vec<Attribute>>,
    ref_cache: CowCellWriteTxn<'a, HashMap<Attribute, SchemaAttribute>>,
}

/// A readonly transaction of the working schema set.
pub struct SchemaReadTransaction {
    classes: CowCellReadTxn<HashMap<AttrString, SchemaClass>>,
    attributes: CowCellReadTxn<HashMap<Attribute, SchemaAttribute>>,

    unique_cache: CowCellReadTxn<Vec<Attribute>>,
    ref_cache: CowCellReadTxn<HashMap<Attribute, SchemaAttribute>>,
}

#[derive(Debug, Clone, Copy, Default)]
pub enum Replicated {
    #[default]
    True,
    False,
}

impl From<Replicated> for bool {
    fn from(value: Replicated) -> bool {
        match value {
            Replicated::True => true,
            Replicated::False => false,
        }
    }
}

impl From<bool> for Replicated {
    fn from(value: bool) -> Self {
        match value {
            true => Replicated::True,
            false => Replicated::False,
        }
    }
}

/// An item representing an attribute and the rules that enforce it. These rules enforce if an
/// attribute on an [`Entry`] may be single or multi value, must be unique amongst all other types
/// of this attribute, if the attribute should be [`indexed`], and what type of data [`syntax`] it may hold.
///
/// [`Entry`]: ../entry/index.html
/// [`indexed`]: ../value/enum.IndexType.html
/// [`syntax`]: ../value/enum.SyntaxType.html
#[derive(Debug, Clone, Default)]
pub struct SchemaAttribute {
    pub name: Attribute,
    pub uuid: Uuid,
    pub description: String,
    /// Defines if the attribute may have one or multiple values associated to it.
    pub multivalue: bool,
    /// If this flag is set, all instances of this attribute must be a unique value in the database.
    pub unique: bool,
    /// This defines that the value is a phantom - it is "not real", can never "be real". It
    /// is synthesised in memory, and will never be written to the database. This can exist for
    /// placeholders like cn/uid in ldap.
    pub phantom: bool,
    /// This boolean defines if this attribute may be altered by an external IDP sync
    /// agreement.
    pub sync_allowed: bool,

    /// If set the value of this attribute get replicated to other servers
    pub replicated: Replicated,
    /// Define if this attribute is indexed or not according to its syntax type rule
    pub indexed: bool,
    /// THe type of data that this attribute may hold.
    pub syntax: SyntaxType,
}

impl SchemaAttribute {
    pub fn try_from(value: &Entry<EntrySealed, EntryCommitted>) -> Result<Self, OperationError> {
        // Convert entry to a schema attribute.

        // uuid
        let uuid = value.get_uuid();

        // class
        if !value.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into()) {
            admin_error!(
                "class {} not present - {:?}",
                EntryClass::AttributeType,
                uuid
            );
            return Err(OperationError::InvalidSchemaState(format!(
                "missing {}",
                EntryClass::AttributeType
            )));
        }

        // name
        let name = value
            .get_ava_single_iutf8(Attribute::AttributeName)
            .map(|s| s.into())
            .ok_or_else(|| {
                admin_error!("missing {} - {:?}", Attribute::AttributeName, uuid);
                OperationError::InvalidSchemaState("missing attributename".to_string())
            })?;
        // description
        let description = value
            .get_ava_single_utf8(Attribute::Description)
            .map(|s| s.to_string())
            .ok_or_else(|| {
                admin_error!("missing {} - {}", Attribute::Description, name);
                OperationError::InvalidSchemaState("missing description".to_string())
            })?;

        // multivalue
        let multivalue = value
            .get_ava_single_bool(Attribute::MultiValue)
            .ok_or_else(|| {
                admin_error!("missing {} - {}", Attribute::MultiValue, name);
                OperationError::InvalidSchemaState("missing multivalue".to_string())
            })?;

        let unique = value
            .get_ava_single_bool(Attribute::Unique)
            .ok_or_else(|| {
                admin_error!("missing {} - {}", Attribute::Unique, name);
                OperationError::InvalidSchemaState("missing unique".to_string())
            })?;

        let phantom = value
            .get_ava_single_bool(Attribute::Phantom)
            .unwrap_or_default();

        let sync_allowed = value
            .get_ava_single_bool(Attribute::SyncAllowed)
            .unwrap_or_default();

        // Default, all attributes are replicated unless you opt in for them to NOT be.
        // Generally this is internal to the server only, so we don't advertise it.
        let replicated = value
            .get_ava_single_bool(Attribute::Replicated)
            .map(Replicated::from)
            .unwrap_or_default();

        let indexed = value
            .get_ava_single_bool(Attribute::Indexed)
            .unwrap_or_default();

        // syntax type
        let syntax = value
            .get_ava_single_syntax(Attribute::Syntax)
            .ok_or_else(|| {
                admin_error!("missing {} - {}", Attribute::Syntax, name);
                OperationError::InvalidSchemaState(format!("missing {}", Attribute::Syntax))
            })?;

        trace!(?name, ?indexed);

        Ok(SchemaAttribute {
            name,
            uuid,
            description,
            multivalue,
            unique,
            phantom,
            sync_allowed,
            replicated,
            indexed,
            syntax,
        })
    }

    // There may be a difference between a value and a filter value on complex
    // types - IE a complex type may have multiple parts that are secret, but a filter
    // on that may only use a single tagged attribute for example.
    pub fn validate_partialvalue(
        &self,
        a: &Attribute,
        v: &PartialValue,
    ) -> Result<(), SchemaError> {
        let r = match self.syntax {
            SyntaxType::Boolean => matches!(v, PartialValue::Bool(_)),
            SyntaxType::SyntaxId => matches!(v, PartialValue::Syntax(_)),
            SyntaxType::IndexId => matches!(v, PartialValue::Index(_)),
            SyntaxType::Uuid => matches!(v, PartialValue::Uuid(_)),
            SyntaxType::ReferenceUuid => matches!(v, PartialValue::Refer(_)),
            SyntaxType::Utf8StringInsensitive => matches!(v, PartialValue::Iutf8(_)),
            SyntaxType::Utf8StringIname => matches!(v, PartialValue::Iname(_)),
            SyntaxType::Utf8String => matches!(v, PartialValue::Utf8(_)),
            SyntaxType::JsonFilter => matches!(v, PartialValue::JsonFilt(_)),
            SyntaxType::Credential => matches!(v, PartialValue::Cred(_)),
            SyntaxType::SecretUtf8String => matches!(v, PartialValue::SecretValue),
            SyntaxType::SshKey => matches!(v, PartialValue::SshKey(_)),
            SyntaxType::SecurityPrincipalName => matches!(v, PartialValue::Spn(_, _)),
            SyntaxType::Uint32 => matches!(v, PartialValue::Uint32(_)),
            SyntaxType::Cid => matches!(v, PartialValue::Cid(_)),
            SyntaxType::NsUniqueId => matches!(v, PartialValue::Nsuniqueid(_)),
            SyntaxType::DateTime => matches!(v, PartialValue::DateTime(_)),
            SyntaxType::EmailAddress => matches!(v, PartialValue::EmailAddress(_)),
            SyntaxType::Url => matches!(v, PartialValue::Url(_)),
            SyntaxType::OauthScope => matches!(v, PartialValue::OauthScope(_)),
            SyntaxType::OauthScopeMap => matches!(v, PartialValue::Refer(_)),
            SyntaxType::OauthClaimMap => {
                matches!(v, PartialValue::Iutf8(_))
                    || matches!(v, PartialValue::Refer(_))
                    || matches!(v, PartialValue::OauthClaimValue(_, _, _))
                    || matches!(v, PartialValue::OauthClaim(_, _))
            }
            SyntaxType::PrivateBinary => matches!(v, PartialValue::PrivateBinary),
            SyntaxType::IntentToken => matches!(v, PartialValue::IntentToken(_)),
            SyntaxType::Passkey => matches!(v, PartialValue::Passkey(_)),
            SyntaxType::AttestedPasskey => matches!(v, PartialValue::AttestedPasskey(_)),
            // Allow refer types.
            SyntaxType::Session => matches!(v, PartialValue::Refer(_)),
            SyntaxType::ApiToken => matches!(v, PartialValue::Refer(_)),
            SyntaxType::Oauth2Session => matches!(v, PartialValue::Refer(_)),
            // These are just insensitive string lookups on the hex-ified kid.
            SyntaxType::JwsKeyEs256 => matches!(v, PartialValue::Iutf8(_)),
            SyntaxType::JwsKeyRs256 => matches!(v, PartialValue::Iutf8(_)),
            SyntaxType::UiHint => matches!(v, PartialValue::UiHint(_)),
            SyntaxType::EcKeyPrivate => matches!(v, PartialValue::SecretValue),
            // Comparing on the label.
            SyntaxType::TotpSecret => matches!(v, PartialValue::Utf8(_)),
            SyntaxType::AuditLogString => matches!(v, PartialValue::Utf8(_)),
            SyntaxType::Image => matches!(v, PartialValue::Utf8(_)),
            SyntaxType::CredentialType => matches!(v, PartialValue::CredentialType(_)),

            SyntaxType::HexString | SyntaxType::Certificate | SyntaxType::KeyInternal => {
                matches!(v, PartialValue::HexString(_))
            }

            SyntaxType::WebauthnAttestationCaList => false,
            SyntaxType::ApplicationPassword => {
                matches!(v, PartialValue::Uuid(_)) || matches!(v, PartialValue::Refer(_))
            }
        };
        if r {
            Ok(())
        } else {
            error!(
                ?a,
                ?self,
                ?v,
                "validate_partialvalue InvalidAttributeSyntax"
            );
            Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
        }
    }

    pub fn validate_value(&self, a: &Attribute, v: &Value) -> Result<(), SchemaError> {
        let r = v.validate()
            && match self.syntax {
                SyntaxType::Boolean => matches!(v, Value::Bool(_)),
                SyntaxType::SyntaxId => matches!(v, Value::Syntax(_)),
                SyntaxType::IndexId => matches!(v, Value::Index(_)),
                SyntaxType::Uuid => matches!(v, Value::Uuid(_)),
                SyntaxType::ReferenceUuid => matches!(v, Value::Refer(_)),
                SyntaxType::Utf8StringInsensitive => matches!(v, Value::Iutf8(_)),
                SyntaxType::Utf8StringIname => matches!(v, Value::Iname(_)),
                SyntaxType::Utf8String => matches!(v, Value::Utf8(_)),
                SyntaxType::JsonFilter => matches!(v, Value::JsonFilt(_)),
                SyntaxType::Credential => matches!(v, Value::Cred(_, _)),
                SyntaxType::SecretUtf8String => matches!(v, Value::SecretValue(_)),
                SyntaxType::SshKey => matches!(v, Value::SshKey(_, _)),
                SyntaxType::SecurityPrincipalName => matches!(v, Value::Spn(_, _)),
                SyntaxType::Uint32 => matches!(v, Value::Uint32(_)),
                SyntaxType::Cid => matches!(v, Value::Cid(_)),
                SyntaxType::NsUniqueId => matches!(v, Value::Nsuniqueid(_)),
                SyntaxType::DateTime => matches!(v, Value::DateTime(_)),
                SyntaxType::EmailAddress => matches!(v, Value::EmailAddress(_, _)),
                SyntaxType::Url => matches!(v, Value::Url(_)),
                SyntaxType::OauthScope => matches!(v, Value::OauthScope(_)),
                SyntaxType::OauthScopeMap => matches!(v, Value::OauthScopeMap(_, _)),
                SyntaxType::OauthClaimMap => {
                    matches!(v, Value::OauthClaimValue(_, _, _))
                        || matches!(v, Value::OauthClaimMap(_, _))
                }
                SyntaxType::PrivateBinary => matches!(v, Value::PrivateBinary(_)),
                SyntaxType::IntentToken => matches!(v, Value::IntentToken(_, _)),
                SyntaxType::Passkey => matches!(v, Value::Passkey(_, _, _)),
                SyntaxType::AttestedPasskey => matches!(v, Value::AttestedPasskey(_, _, _)),
                SyntaxType::Session => matches!(v, Value::Session(_, _)),
                SyntaxType::ApiToken => matches!(v, Value::ApiToken(_, _)),
                SyntaxType::Oauth2Session => matches!(v, Value::Oauth2Session(_, _)),
                SyntaxType::JwsKeyEs256 => matches!(v, Value::JwsKeyEs256(_)),
                SyntaxType::JwsKeyRs256 => matches!(v, Value::JwsKeyRs256(_)),
                SyntaxType::UiHint => matches!(v, Value::UiHint(_)),
                SyntaxType::TotpSecret => matches!(v, Value::TotpSecret(_, _)),
                SyntaxType::AuditLogString => matches!(v, Value::Utf8(_)),
                SyntaxType::EcKeyPrivate => matches!(v, Value::EcKeyPrivate(_)),
                SyntaxType::Image => matches!(v, Value::Image(_)),
                SyntaxType::CredentialType => matches!(v, Value::CredentialType(_)),
                SyntaxType::WebauthnAttestationCaList => {
                    matches!(v, Value::WebauthnAttestationCaList(_))
                }
                SyntaxType::KeyInternal => matches!(v, Value::KeyInternal { .. }),
                SyntaxType::HexString => matches!(v, Value::HexString(_)),
                SyntaxType::Certificate => matches!(v, Value::Certificate(_)),
                SyntaxType::ApplicationPassword => matches!(v, Value::ApplicationPassword(..)),
            };
        if r {
            Ok(())
        } else {
            error!(
                ?a,
                ?self,
                ?v,
                "validate_value failure - InvalidAttributeSyntax"
            );
            Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
        }
    }

    pub fn validate_ava(&self, a: &Attribute, ava: &ValueSet) -> Result<(), SchemaError> {
        trace!("Checking for valid {:?} -> {:?}", self.name, ava);
        // Check multivalue
        if !self.multivalue && ava.len() > 1 {
            // lrequest_error!("Ava len > 1 on single value attribute!");
            admin_error!("Ava len > 1 on single value attribute!");
            return Err(SchemaError::InvalidAttributeSyntax(a.to_string()));
        };
        // If syntax, check the type is correct
        let valid = self.syntax == ava.syntax();
        if valid && ava.validate(self) {
            Ok(())
        } else {
            error!(
                ?a,
                "validate_ava - InvalidAttributeSyntax for {:?}", self.syntax
            );
            Err(SchemaError::InvalidAttributeSyntax(a.to_string()))
        }
    }
}

/// An item representing a class and the rules for that class. These rules enforce that an
/// [`Entry`]'s avas conform to a set of requirements, giving structure to an entry about
/// what avas must or may exist. The kanidm project provides attributes in `systemmust` and
/// `systemmay`, which can not be altered. An administrator may extend these in the `must`
/// and `may` attributes.
///
/// Classes are additive, meaning that if there are two classes, the `may` rules of both union,
/// and that if an attribute is `must` on one class, and `may` in another, the `must` rule
/// takes precedence. It is not possible to combine classes in an incompatible way due to these
/// rules.
///
/// That in mind, an entry that has one of every possible class would probably be nonsensical,
/// but the addition rules make it easy to construct and understand with concepts like [`access`]
/// controls or accounts and posix extensions.
///
/// [`Entry`]: ../entry/index.html
/// [`access`]: ../access/index.html
#[derive(Debug, Clone, Default)]
pub struct SchemaClass {
    pub name: AttrString,
    pub uuid: Uuid,
    pub description: String,
    pub sync_allowed: bool,
    /// This allows modification of system types to be extended in custom ways
    pub systemmay: Vec<Attribute>,
    pub may: Vec<Attribute>,
    pub systemmust: Vec<Attribute>,
    pub must: Vec<Attribute>,
    /// A list of classes that this extends. These are an "or", as at least one
    /// of the supplementing classes must also be present. Think of this as
    /// "inherits toward" or "provides". This is just as "strict" as requires but
    /// operates in the opposite direction allowing a tree structure.
    pub systemsupplements: Vec<AttrString>,
    pub supplements: Vec<AttrString>,
    /// A list of classes that can not co-exist with this item at the same time.
    pub systemexcludes: Vec<AttrString>,
    pub excludes: Vec<AttrString>,
}

impl SchemaClass {
    pub fn try_from(value: &Entry<EntrySealed, EntryCommitted>) -> Result<Self, OperationError> {
        // uuid
        let uuid = value.get_uuid();
        // Convert entry to a schema class.
        if !value.attribute_equality(Attribute::Class, &EntryClass::ClassType.into()) {
            error!("class classtype not present - {:?}", uuid);
            return Err(OperationError::InvalidSchemaState(
                "missing classtype".to_string(),
            ));
        }

        // name
        let name = value
            .get_ava_single_iutf8(Attribute::ClassName)
            .map(AttrString::from)
            .ok_or_else(|| {
                error!("missing {} - {:?}", Attribute::ClassName, uuid);
                OperationError::InvalidSchemaState(format!("missing {}", Attribute::ClassName))
            })?;

        // description
        let description = value
            .get_ava_single_utf8(Attribute::Description)
            .map(String::from)
            .ok_or_else(|| {
                error!("missing {} - {}", Attribute::Description, name);
                OperationError::InvalidSchemaState(format!("missing {}", Attribute::Description))
            })?;

        let sync_allowed = value
            .get_ava_single_bool(Attribute::SyncAllowed)
            .unwrap_or(false);

        // These are all "optional" lists of strings.
        let systemmay = value
            .get_ava_iter_iutf8(Attribute::SystemMay)
            .into_iter()
            .flat_map(|iter| iter.map(Attribute::from))
            .collect();
        let systemmust = value
            .get_ava_iter_iutf8(Attribute::SystemMust)
            .into_iter()
            .flat_map(|iter| iter.map(Attribute::from))
            .collect();
        let may = value
            .get_ava_iter_iutf8(Attribute::May)
            .into_iter()
            .flat_map(|iter| iter.map(Attribute::from))
            .collect();
        let must = value
            .get_ava_iter_iutf8(Attribute::Must)
            .into_iter()
            .flat_map(|iter| iter.map(Attribute::from))
            .collect();

        let systemsupplements = value
            .get_ava_iter_iutf8(Attribute::SystemSupplements)
            .map(|i| i.map(|v| v.into()).collect())
            .unwrap_or_default();
        let supplements = value
            .get_ava_iter_iutf8(Attribute::Supplements)
            .map(|i| i.map(|v| v.into()).collect())
            .unwrap_or_default();
        let systemexcludes = value
            .get_ava_iter_iutf8(Attribute::SystemExcludes)
            .map(|i| i.map(|v| v.into()).collect())
            .unwrap_or_default();
        let excludes = value
            .get_ava_iter_iutf8(Attribute::Excludes)
            .map(|i| i.map(|v| v.into()).collect())
            .unwrap_or_default();

        Ok(SchemaClass {
            name,
            uuid,
            description,
            sync_allowed,
            systemmay,
            may,
            systemmust,
            must,
            systemsupplements,
            supplements,
            systemexcludes,
            excludes,
        })
    }

    /// An iterator over the full set of attrs that may or must exist
    /// on this class.
    pub fn may_iter(&self) -> impl Iterator<Item = &Attribute> {
        self.systemmay
            .iter()
            .chain(self.may.iter())
            .chain(self.systemmust.iter())
            .chain(self.must.iter())
    }
}

pub trait SchemaTransaction {
    fn get_classes(&self) -> &HashMap<AttrString, SchemaClass>;
    fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute>;

    fn get_attributes_unique(&self) -> &Vec<Attribute>;
    fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute>;

    fn validate(&self) -> Vec<Result<(), ConsistencyError>> {
        let mut res = Vec::with_capacity(0);

        let class_snapshot = self.get_classes();
        let attribute_snapshot = self.get_attributes();

        // We need to check that every uuid is unique because during tests we aren't doing
        // a disk reload, which means we were missing this and causing potential migration
        // failures on upgrade.

        let mut unique_uuid_set = HashSet::new();
        class_snapshot
            .values()
            .map(|class| &class.uuid)
            .chain(attribute_snapshot.values().map(|attr| &attr.uuid))
            .for_each(|uuid| {
                // If the set did not have this value present, true is returned.
                if !unique_uuid_set.insert(uuid) {
                    res.push(Err(ConsistencyError::SchemaUuidNotUnique(*uuid)))
                }
            });

        class_snapshot.values().for_each(|class| {
            // report the class we are checking
            class
                .systemmay
                .iter()
                .chain(class.may.iter())
                .chain(class.systemmust.iter())
                .chain(class.must.iter())
                .for_each(|a| {
                    match attribute_snapshot.get(a) {
                        Some(attr) => {
                            // We have the attribute, ensure it's not a phantom.
                            if attr.phantom {
                                res.push(Err(ConsistencyError::SchemaClassPhantomAttribute(
                                    class.name.to_string(),
                                    a.to_string(),
                                )))
                            }
                        }
                        None => {
                            // No such attr, something is missing!
                            res.push(Err(ConsistencyError::SchemaClassMissingAttribute(
                                class.name.to_string(),
                                a.to_string(),
                            )))
                        }
                    }
                })
        }); // end for
        res
    }

    fn is_replicated(&self, attr: &Attribute) -> bool {
        match self.get_attributes().get(attr) {
            Some(a_schema) => {
                // We'll likely add more conditions here later.
                // Allow items that are replicated and not phantoms
                a_schema.replicated.into() && !a_schema.phantom
            }
            None => {
                warn!(
                    "Attribute {} was not found in schema during replication request",
                    attr
                );
                false
            }
        }
    }

    fn is_multivalue(&self, attr: &Attribute) -> Result<bool, SchemaError> {
        match self.get_attributes().get(attr) {
            Some(a_schema) => Ok(a_schema.multivalue),
            None => {
                // ladmin_error!("Attribute does not exist?!");
                Err(SchemaError::InvalidAttribute(attr.to_string()))
            }
        }
    }

    fn normalise_attr_if_exists(&self, an: &str) -> Option<Attribute> {
        let attr = Attribute::from(an);
        if self.get_attributes().contains_key(&attr) {
            Some(attr)
        } else {
            None
        }
    }

    fn query_attrs_difference(
        &self,
        prev_class: &BTreeSet<&str>,
        new_class: &BTreeSet<&str>,
    ) -> Result<(BTreeSet<&str>, BTreeSet<&str>), SchemaError> {
        let schema_classes = self.get_classes();

        let mut invalid_classes = Vec::with_capacity(0);

        let prev_attrs: BTreeSet<&str> = prev_class
            .iter()
            .filter_map(|cls| match schema_classes.get(*cls) {
                Some(x) => Some(x.may_iter()),
                None => {
                    admin_debug!("invalid class: {:?}", cls);
                    invalid_classes.push(cls.to_string());
                    None
                }
            })
            // flatten all the inner iters.
            .flatten()
            .map(|s| s.as_str())
            .collect();

        if !invalid_classes.is_empty() {
            return Err(SchemaError::InvalidClass(invalid_classes));
        };

        let new_attrs: BTreeSet<&str> = new_class
            .iter()
            .filter_map(|cls| match schema_classes.get(*cls) {
                Some(x) => Some(x.may_iter()),
                None => {
                    admin_debug!("invalid class: {:?}", cls);
                    invalid_classes.push(cls.to_string());
                    None
                }
            })
            // flatten all the inner iters.
            .flatten()
            .map(|s| s.as_str())
            .collect();

        if !invalid_classes.is_empty() {
            return Err(SchemaError::InvalidClass(invalid_classes));
        };

        let removed = prev_attrs.difference(&new_attrs).copied().collect();
        let added = new_attrs.difference(&prev_attrs).copied().collect();

        Ok((added, removed))
    }
}

impl SchemaWriteTransaction<'_> {
    // Schema probably needs to be part of the backend, so that commits are wholly atomic
    // but in the current design, we need to open be first, then schema, but we have to commit be
    // first, then schema to ensure that the be content matches our schema. Saying this, if your
    // schema commit fails we need to roll back still .... How great are transactions.
    // At the least, this is what validation is for!
    pub fn commit(self) -> Result<(), OperationError> {
        let SchemaWriteTransaction {
            classes,
            attributes,
            unique_cache,
            ref_cache,
        } = self;

        unique_cache.commit();
        ref_cache.commit();
        classes.commit();
        attributes.commit();
        Ok(())
    }

    pub fn update_attributes(
        &mut self,
        attributetypes: Vec<SchemaAttribute>,
    ) -> Result<(), OperationError> {
        // purge all old attributes.
        self.attributes.clear();

        self.unique_cache.clear();
        self.ref_cache.clear();
        // Update with new ones.
        // Do we need to check for dups?
        // No, they'll over-write each other ... but we do need name uniqueness.
        attributetypes.into_iter().for_each(|a| {
            // Update the unique and ref caches.
            if a.syntax == SyntaxType::ReferenceUuid ||
                a.syntax == SyntaxType::OauthScopeMap ||
                a.syntax == SyntaxType::OauthClaimMap ||
                // So that when an rs is removed we trigger removal of the sessions.
                a.syntax == SyntaxType::Oauth2Session ||
                // When an application is removed we trigger removal of passwords
                a.syntax == SyntaxType::ApplicationPassword
            // May not need to be a ref type since it doesn't have external links/impact?
            // || a.syntax == SyntaxType::Session
            {
                self.ref_cache.insert(a.name.clone(), a.clone());
            }
            if a.unique {
                self.unique_cache.push(a.name.clone());
            }
            // Finally insert.
            self.attributes.insert(a.name.clone(), a);
        });

        Ok(())
    }

    pub fn update_classes(&mut self, classtypes: Vec<SchemaClass>) -> Result<(), OperationError> {
        // purge all old attributes.
        self.classes.clear();
        // Update with new ones.
        // Do we need to check for dups?
        // No, they'll over-write each other ... but we do need name uniqueness.
        classtypes.into_iter().for_each(|a| {
            self.classes.insert(a.name.clone(), a);
        });
        Ok(())
    }

    pub fn to_entries(&self) -> Vec<Entry<EntryInit, EntryNew>> {
        let r: Vec<_> = self
            .attributes
            .values()
            .map(Entry::<EntryInit, EntryNew>::from)
            .chain(
                self.classes
                    .values()
                    .map(Entry::<EntryInit, EntryNew>::from),
            )
            .collect();
        r
    }

    pub fn reload_idxmeta(&self) -> Vec<IdxKey> {
        self.get_attributes()
            .values()
            .flat_map(|a| {
                // Unique values must be indexed
                if a.indexed || a.unique {
                    a.syntax.index_types()
                } else {
                    &[]
                }
                .iter()
                .map(move |itype: &IndexType| IdxKey {
                    attr: a.name.clone(),
                    itype: *itype,
                })
            })
            .collect()
    }

    #[instrument(level = "debug", name = "schema::generate_in_memory", skip_all)]
    pub fn generate_in_memory(&mut self) -> Result<(), OperationError> {
        //
        self.classes.clear();
        self.attributes.clear();
        // Bootstrap in definitions of our own schema types
        // First, add all the needed core attributes for schema parsing
        self.attributes.insert(
            Attribute::Class,
            SchemaAttribute {
                name: Attribute::Class,
                uuid: UUID_SCHEMA_ATTR_CLASS,
                description: String::from("The set of classes defining an object"),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::Uuid,
            SchemaAttribute {
                name: Attribute::Uuid,
                uuid: UUID_SCHEMA_ATTR_UUID,
                description: String::from("The universal unique id of the object"),
                multivalue: false,
                // Uniqueness is handled by base.rs, not attrunique here due to
                // needing to check recycled objects too.
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::Uuid,
            },
        );
        self.attributes.insert(
            Attribute::SourceUuid,
            SchemaAttribute {
                name: Attribute::SourceUuid,
                uuid: UUID_SCHEMA_ATTR_SOURCE_UUID,
                description: String::from(
                    "The universal unique id of the source object(s) which conflicted with this entry",
                ),
                multivalue: true,
                // Uniqueness is handled by base.rs, not attrunique here due to
                // needing to check recycled objects too.
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::Uuid,
            },
        );
        self.attributes.insert(
            Attribute::CreatedAtCid,
            SchemaAttribute {
                name: Attribute::CreatedAtCid,
                uuid: UUID_SCHEMA_ATTR_CREATED_AT_CID,
                description: String::from("The cid when this entry was created"),
                multivalue: false,
                // Uniqueness is handled by base.rs, not attrunique here due to
                // needing to check recycled objects too.
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::Cid,
            },
        );
        self.attributes.insert(
            Attribute::LastModifiedCid,
            SchemaAttribute {
                name: Attribute::LastModifiedCid,
                uuid: UUID_SCHEMA_ATTR_LAST_MOD_CID,
                description: String::from("The cid of the last change to this object"),
                multivalue: false,
                // Uniqueness is handled by base.rs, not attrunique here due to
                // needing to check recycled objects too.
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::Cid,
            },
        );
        self.attributes.insert(
            Attribute::Name,
            SchemaAttribute {
                name: Attribute::Name,
                uuid: UUID_SCHEMA_ATTR_NAME,
                description: String::from("The shortform name of an object"),
                multivalue: false,
                unique: true,
                phantom: false,
                sync_allowed: true,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::Utf8StringIname,
            },
        );
        self.attributes.insert(
            Attribute::Spn,
            SchemaAttribute {
                name: Attribute::Spn,
                uuid: UUID_SCHEMA_ATTR_SPN,
                description: String::from(
                    "The Security Principal Name of an object, unique across all domain trusts",
                ),
                multivalue: false,
                unique: true,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::SecurityPrincipalName,
            },
        );
        self.attributes.insert(
            Attribute::AttributeName,
            SchemaAttribute {
                name: Attribute::AttributeName,
                uuid: UUID_SCHEMA_ATTR_ATTRIBUTENAME,
                description: String::from("The name of a schema attribute"),
                multivalue: false,
                unique: true,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::ClassName,
            SchemaAttribute {
                name: Attribute::ClassName,
                uuid: UUID_SCHEMA_ATTR_CLASSNAME,
                description: String::from("The name of a schema class"),
                multivalue: false,
                unique: true,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::Description,
            SchemaAttribute {
                name: Attribute::Description,
                uuid: UUID_SCHEMA_ATTR_DESCRIPTION,
                description: String::from("A description of an attribute, object or class"),
                multivalue: false,
                unique: false,
                phantom: false,
                sync_allowed: true,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Utf8String,
            },
        );
        self.attributes.insert(Attribute::MultiValue, SchemaAttribute {
                name: Attribute::MultiValue,
                uuid: UUID_SCHEMA_ATTR_MULTIVALUE,
                description: String::from("If true, this attribute is able to store multiple values rather than just a single value."),
                multivalue: false,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Boolean,
            });
        self.attributes.insert(Attribute::Phantom, SchemaAttribute {
                name: Attribute::Phantom,
                uuid: UUID_SCHEMA_ATTR_PHANTOM,
                description: String::from("If true, this attribute must NOT be present in any may/must sets of a class as. This represents generated attributes."),
                multivalue: false,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Boolean,
            });
        self.attributes.insert(Attribute::SyncAllowed, SchemaAttribute {
                name: Attribute::SyncAllowed,
                uuid: UUID_SCHEMA_ATTR_SYNC_ALLOWED,
                description: String::from("If true, this attribute or class can by synchronised by an external scim import"),
                multivalue: false,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Boolean,
            });
        self.attributes.insert(Attribute::Replicated, SchemaAttribute {
                name: Attribute::Replicated,
                uuid: UUID_SCHEMA_ATTR_REPLICATED,
                description: String::from("If true, this attribute or class can by replicated between nodes in the topology"),
                multivalue: false,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Boolean,
            });
        self.attributes.insert(
            Attribute::Unique,
            SchemaAttribute {
                name: Attribute::Unique,
                uuid: UUID_SCHEMA_ATTR_UNIQUE,
                description: String::from(
                    "If true, this attribute must store a unique value through out the database.",
                ),
                multivalue: false,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Boolean,
            },
        );
        self.attributes.insert(
            Attribute::Index,
            SchemaAttribute {
                name: Attribute::Index,
                uuid: UUID_SCHEMA_ATTR_INDEX,
                description: String::from(
                    "Describe the indexes to apply to instances of this attribute.",
                ),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::IndexId,
            },
        );
        self.attributes.insert(
            Attribute::Indexed,
            SchemaAttribute {
                name: Attribute::Indexed,
                uuid: UUID_SCHEMA_ATTR_INDEXED,
                description: String::from(
                    "A boolean stating if this attribute will be indexed according to its syntax rules."
                ),
                multivalue: false,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Boolean,
            },
        );
        self.attributes.insert(
            Attribute::Syntax,
            SchemaAttribute {
                name: Attribute::Syntax,
                uuid: UUID_SCHEMA_ATTR_SYNTAX,
                description: String::from(
                    "Describe the syntax of this attribute. This affects indexing and sorting.",
                ),
                multivalue: false,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::SyntaxId,
            },
        );
        self.attributes.insert(
            Attribute::SystemMay,
            SchemaAttribute {
                name: Attribute::SystemMay,
                uuid: UUID_SCHEMA_ATTR_SYSTEMMAY,
                description: String::from(
                    "A list of system provided optional attributes this class can store.",
                ),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::May,
            SchemaAttribute {
                name: Attribute::May,
                uuid: UUID_SCHEMA_ATTR_MAY,
                description: String::from(
                    "A user modifiable list of optional attributes this class can store.",
                ),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::SystemMust,
            SchemaAttribute {
                name: Attribute::SystemMust,
                uuid: UUID_SCHEMA_ATTR_SYSTEMMUST,
                description: String::from(
                    "A list of system provided required attributes this class must store.",
                ),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::Must,
            SchemaAttribute {
                name: Attribute::Must,
                uuid: UUID_SCHEMA_ATTR_MUST,
                description: String::from(
                    "A user modifiable list of required attributes this class must store.",
                ),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::SystemSupplements,
            SchemaAttribute {
                name: Attribute::SystemSupplements,
                uuid: UUID_SCHEMA_ATTR_SYSTEMSUPPLEMENTS,
                description: String::from(
                    "A set of classes that this type supplements, where this class can't exist without their presence.",
                ),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::Supplements,
            SchemaAttribute {
                name: Attribute::Supplements,
                uuid: UUID_SCHEMA_ATTR_SUPPLEMENTS,
                description: String::from(
                    "A set of user modifiable classes, where this determines that at least one other type must supplement this type",
                ),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::SystemExcludes,
            SchemaAttribute {
                name: Attribute::SystemExcludes,
                uuid: UUID_SCHEMA_ATTR_SYSTEMEXCLUDES,
                description: String::from(
                    "A set of classes that are denied presence in connection to this class",
                ),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::Excludes,
            SchemaAttribute {
                name: Attribute::Excludes,
                uuid: UUID_SCHEMA_ATTR_EXCLUDES,
                description: String::from(
                    "A set of user modifiable classes that are denied presence in connection to this class",
                ),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );

        // SYSINFO attrs
        // ACP attributes.
        self.attributes.insert(
            Attribute::AcpEnable,
            SchemaAttribute {
                name: Attribute::AcpEnable,
                uuid: UUID_SCHEMA_ATTR_ACP_ENABLE,
                description: String::from("A flag to determine if this ACP is active for application. True is enabled, and enforced. False is checked but not enforced."),
                multivalue: false,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::Boolean,
            },
        );

        self.attributes.insert(
            Attribute::AcpReceiver,
            SchemaAttribute {
                name: Attribute::AcpReceiver,
                uuid: UUID_SCHEMA_ATTR_ACP_RECEIVER,
                description: String::from(
                    "Who the ACP applies to, constraining or allowing operations.",
                ),
                multivalue: false,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::JsonFilter,
            },
        );
        self.attributes.insert(
            Attribute::AcpReceiverGroup,
            SchemaAttribute {
                name: Attribute::AcpReceiverGroup,
                uuid: UUID_SCHEMA_ATTR_ACP_RECEIVER_GROUP,
                description: String::from(
                    "The group that receives this access control to allow access",
                ),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::ReferenceUuid,
            },
        );

        self.attributes.insert(
            Attribute::AcpTargetScope,
            SchemaAttribute {
                name: Attribute::AcpTargetScope,
                uuid: UUID_SCHEMA_ATTR_ACP_TARGETSCOPE,
                description: String::from(
                    "The effective targets of the ACP, e.g. what will be acted upon.",
                ),
                multivalue: false,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::JsonFilter,
            },
        );
        self.attributes.insert(
            Attribute::AcpSearchAttr,
            SchemaAttribute {
                name: Attribute::AcpSearchAttr,
                uuid: UUID_SCHEMA_ATTR_ACP_SEARCH_ATTR,
                description: String::from(
                    "The attributes that may be viewed or searched by the receiver on targetscope.",
                ),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::AcpCreateClass,
            SchemaAttribute {
                name: Attribute::AcpCreateClass,
                uuid: UUID_SCHEMA_ATTR_ACP_CREATE_CLASS,
                description: String::from("The set of classes that can be created on a new entry."),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::AcpCreateAttr,
            SchemaAttribute {
                name: Attribute::AcpCreateAttr,
                uuid: UUID_SCHEMA_ATTR_ACP_CREATE_ATTR,
                description: String::from(
                    "The set of attribute types that can be created on an entry.",
                ),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );

        self.attributes.insert(
            Attribute::AcpModifyRemovedAttr,
            SchemaAttribute {
                name: Attribute::AcpModifyRemovedAttr,
                uuid: UUID_SCHEMA_ATTR_ACP_MODIFY_REMOVEDATTR,
                description: String::from(
                    "The set of attribute types that could be removed or purged in a modification.",
                ),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::AcpModifyPresentAttr,
            SchemaAttribute {
                name: Attribute::AcpModifyPresentAttr,
                uuid: UUID_SCHEMA_ATTR_ACP_MODIFY_PRESENTATTR,
                description: String::from(
                    "The set of attribute types that could be added or asserted in a modification.",
                ),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::AcpModifyClass,
            SchemaAttribute {
                name: Attribute::AcpModifyClass,
                uuid: UUID_SCHEMA_ATTR_ACP_MODIFY_CLASS,
                description: String::from("The set of class values that could be asserted or added to an entry. Only applies to modify::present operations on class."),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
                Attribute::AcpModifyPresentClass,
                SchemaAttribute {
                    name: Attribute::AcpModifyPresentClass,
                    uuid: UUID_SCHEMA_ATTR_ACP_MODIFY_PRESENT_CLASS,
                    description: String::from("The set of class values that could be asserted or added to an entry. Only applies to modify::present operations on class."),
                    multivalue: true,
                    unique: false,
                    phantom: false,
                    sync_allowed: false,
                    replicated: Replicated::True,
                    indexed: false,
                    syntax: SyntaxType::Utf8StringInsensitive,
                },
            );
        self.attributes.insert(
                Attribute::AcpModifyRemoveClass,
                SchemaAttribute {
                    name: Attribute::AcpModifyRemoveClass,
                    uuid: UUID_SCHEMA_ATTR_ACP_MODIFY_REMOVE_CLASS,
                    description: String::from("The set of class values that could be asserted or added to an entry. Only applies to modify::remove operations on class."),
                    multivalue: true,
                    unique: false,
                    phantom: false,
                    sync_allowed: false,
                    replicated: Replicated::True,
                    indexed: false,
                    syntax: SyntaxType::Utf8StringInsensitive,
                },
            );
        self.attributes.insert(
            Attribute::EntryManagedBy,
            SchemaAttribute {
                name: Attribute::EntryManagedBy,
                uuid: UUID_SCHEMA_ATTR_ENTRY_MANAGED_BY,
                description: String::from(
                    "A reference to a group that has access to manage the content of this entry.",
                ),
                multivalue: false,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::ReferenceUuid,
            },
        );
        // MO/Member
        self.attributes.insert(
            Attribute::MemberOf,
            SchemaAttribute {
                name: Attribute::MemberOf,
                uuid: UUID_SCHEMA_ATTR_MEMBEROF,
                description: String::from("reverse group membership of the object"),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: true,
                syntax: SyntaxType::ReferenceUuid,
            },
        );
        self.attributes.insert(
            Attribute::DirectMemberOf,
            SchemaAttribute {
                name: Attribute::DirectMemberOf,
                uuid: UUID_SCHEMA_ATTR_DIRECTMEMBEROF,
                description: String::from("reverse direct group membership of the object"),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: true,
                syntax: SyntaxType::ReferenceUuid,
            },
        );
        self.attributes.insert(
            Attribute::RecycledDirectMemberOf,
            SchemaAttribute {
                name: Attribute::RecycledDirectMemberOf,
                uuid: UUID_SCHEMA_ATTR_RECYCLEDDIRECTMEMBEROF,
                description: String::from("recycled reverse direct group membership of the object to assist in revive operations."),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                // Unlike DMO this must be replicated so that on a recycle event, these groups
                //  "at delete" are replicated to partners. This avoids us having to replicate
                // DMO which is very costly, while still retaining our ability to revive entries
                // and their group memberships as a best effort.
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::ReferenceUuid,
            },
        );
        self.attributes.insert(
            Attribute::Member,
            SchemaAttribute {
                name: Attribute::Member,
                uuid: UUID_SCHEMA_ATTR_MEMBER,
                description: String::from("List of members of the group"),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: true,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::ReferenceUuid,
            },
        );
        self.attributes.insert(
            Attribute::DynMember,
            SchemaAttribute {
                name: Attribute::DynMember,
                uuid: UUID_SCHEMA_ATTR_DYNMEMBER,
                description: String::from("List of dynamic members of the group"),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: true,
                replicated: Replicated::False,
                indexed: true,
                syntax: SyntaxType::ReferenceUuid,
            },
        );
        // Migration related
        self.attributes.insert(
            Attribute::Version,
            SchemaAttribute {
                name: Attribute::Version,
                uuid: UUID_SCHEMA_ATTR_VERSION,
                description: String::from(
                    "The systems internal migration version for provided objects",
                ),
                multivalue: false,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Uint32,
            },
        );
        // Domain for sysinfo
        self.attributes.insert(
            Attribute::Domain,
            SchemaAttribute {
                name: Attribute::Domain,
                uuid: UUID_SCHEMA_ATTR_DOMAIN,
                description: String::from("A DNS Domain name entry."),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::Utf8StringIname,
            },
        );
        self.attributes.insert(
            Attribute::Claim,
            SchemaAttribute {
                name: Attribute::Claim,
                uuid: UUID_SCHEMA_ATTR_CLAIM,
                description: String::from(
                    "The string identifier of an extracted claim that can be filtered",
                ),
                multivalue: true,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::Scope,
            SchemaAttribute {
                name: Attribute::Scope,
                uuid: UUID_SCHEMA_ATTR_SCOPE,
                description: String::from(
                    "The string identifier of a permission scope in a session",
                ),
                multivalue: true,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );

        // External Scim Sync
        self.attributes.insert(
            Attribute::SyncExternalId,
            SchemaAttribute {
                name: Attribute::SyncExternalId,
                uuid: UUID_SCHEMA_ATTR_SYNC_EXTERNAL_ID,
                description: String::from(
                    "An external string ID of an entry imported from a sync agreement",
                ),
                multivalue: false,
                unique: true,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::SyncParentUuid,
            SchemaAttribute {
                name: Attribute::SyncParentUuid,
                uuid: UUID_SCHEMA_ATTR_SYNC_PARENT_UUID,
                description: String::from(
                    "The UUID of the parent sync agreement that created this entry.",
                ),
                multivalue: false,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: true,
                syntax: SyntaxType::ReferenceUuid,
            },
        );
        self.attributes.insert(
            Attribute::SyncClass,
            SchemaAttribute {
                name: Attribute::SyncClass,
                uuid: UUID_SCHEMA_ATTR_SYNC_CLASS,
                description: String::from("The set of classes requested by the sync client."),
                multivalue: true,
                unique: false,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );

        self.attributes.insert(
            Attribute::PasswordImport,
            SchemaAttribute {
                name: Attribute::PasswordImport,
                uuid: UUID_SCHEMA_ATTR_PASSWORD_IMPORT,
                description: String::from("An imported password hash from an external system."),
                multivalue: false,
                unique: false,
                phantom: true,
                sync_allowed: true,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::Utf8String,
            },
        );

        self.attributes.insert(
            Attribute::UnixPasswordImport,
            SchemaAttribute {
                name: Attribute::UnixPasswordImport,
                uuid: UUID_SCHEMA_ATTR_UNIX_PASSWORD_IMPORT,
                description: String::from(
                    "An imported unix password hash from an external system.",
                ),
                multivalue: false,
                unique: false,
                phantom: true,
                sync_allowed: true,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::Utf8String,
            },
        );

        self.attributes.insert(
            Attribute::TotpImport,
            SchemaAttribute {
                name: Attribute::TotpImport,
                uuid: UUID_SCHEMA_ATTR_TOTP_IMPORT,
                description: String::from("An imported totp secret from an external system."),
                multivalue: true,
                unique: false,
                phantom: true,
                sync_allowed: true,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::TotpSecret,
            },
        );

        // LDAP Masking Phantoms
        self.attributes.insert(
            Attribute::Dn,
            SchemaAttribute {
                name: Attribute::Dn,
                uuid: UUID_SCHEMA_ATTR_DN,
                description: String::from("An LDAP Compatible DN"),
                multivalue: false,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::EntryDn,
            SchemaAttribute {
                name: Attribute::EntryDn,
                uuid: UUID_SCHEMA_ATTR_ENTRYDN,
                description: String::from("An LDAP Compatible EntryDN"),
                multivalue: false,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::EntryUuid,
            SchemaAttribute {
                name: Attribute::EntryUuid,
                uuid: UUID_SCHEMA_ATTR_ENTRYUUID,
                description: String::from("An LDAP Compatible entryUUID"),
                multivalue: false,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::Uuid,
            },
        );
        self.attributes.insert(
            Attribute::ObjectClass,
            SchemaAttribute {
                name: Attribute::ObjectClass,
                uuid: UUID_SCHEMA_ATTR_OBJECTCLASS,
                description: String::from("An LDAP Compatible objectClass"),
                multivalue: true,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::Utf8StringInsensitive,
            },
        );
        self.attributes.insert(
            Attribute::Cn,
            SchemaAttribute {
                name: Attribute::Cn,
                uuid: UUID_SCHEMA_ATTR_CN,
                description: String::from("An LDAP Compatible objectClass"),
                multivalue: false,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::Utf8StringIname,
            },
        );
        self.attributes.insert(
            Attribute::LdapKeys, // keys
            SchemaAttribute {
                name: Attribute::LdapKeys, // keys
                uuid: UUID_SCHEMA_ATTR_KEYS,
                description: String::from("An LDAP Compatible keys (ssh)"),
                multivalue: true,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::SshKey,
            },
        );
        self.attributes.insert(
            Attribute::LdapSshPublicKey,
            SchemaAttribute {
                name: Attribute::LdapSshPublicKey,
                uuid: UUID_SCHEMA_ATTR_SSHPUBLICKEY,
                description: String::from("An LDAP Compatible sshPublicKey"),
                multivalue: true,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::SshKey,
            },
        );
        self.attributes.insert(
            Attribute::Email,
            SchemaAttribute {
                name: Attribute::Email,
                uuid: UUID_SCHEMA_ATTR_EMAIL,
                description: String::from("An LDAP Compatible email"),
                multivalue: true,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::EmailAddress,
            },
        );
        self.attributes.insert(
            Attribute::EmailPrimary,
            SchemaAttribute {
                name: Attribute::EmailPrimary,
                uuid: UUID_SCHEMA_ATTR_EMAILPRIMARY,
                description: String::from("An LDAP Compatible primary email"),
                multivalue: false,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::EmailAddress,
            },
        );
        self.attributes.insert(
            Attribute::EmailAlternative,
            SchemaAttribute {
                name: Attribute::EmailAlternative,
                uuid: UUID_SCHEMA_ATTR_EMAILALTERNATIVE,
                description: String::from("An LDAP Compatible alternative email"),
                multivalue: false,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::EmailAddress,
            },
        );
        self.attributes.insert(
            Attribute::LdapEmailAddress,
            SchemaAttribute {
                name: Attribute::LdapEmailAddress,
                uuid: UUID_SCHEMA_ATTR_EMAILADDRESS,
                description: String::from("An LDAP Compatible emailAddress"),
                multivalue: true,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::EmailAddress,
            },
        );
        self.attributes.insert(
            Attribute::Gecos,
            SchemaAttribute {
                name: Attribute::Gecos,
                uuid: UUID_SCHEMA_ATTR_GECOS,
                description: String::from("An LDAP Compatible gecos."),
                multivalue: false,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::Utf8String,
            },
        );
        self.attributes.insert(
            Attribute::Uid,
            SchemaAttribute {
                name: Attribute::Uid,
                uuid: UUID_SCHEMA_ATTR_UID,
                description: String::from("An LDAP Compatible uid."),
                multivalue: false,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::Utf8String,
            },
        );
        self.attributes.insert(
            Attribute::UidNumber,
            SchemaAttribute {
                name: Attribute::UidNumber,
                uuid: UUID_SCHEMA_ATTR_UIDNUMBER,
                description: String::from("An LDAP Compatible uidNumber."),
                multivalue: false,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::Uint32,
            },
        );
        self.attributes.insert(
            Attribute::SudoHost,
            SchemaAttribute {
                name: Attribute::SudoHost,
                uuid: UUID_SCHEMA_ATTR_SUDOHOST,
                description: String::from("An LDAP Compatible sudohost."),
                multivalue: false,
                unique: false,
                phantom: true,
                sync_allowed: false,
                replicated: Replicated::False,
                indexed: false,
                syntax: SyntaxType::Utf8String,
            },
        );
        // end LDAP masking phantoms
        self.attributes.insert(
            Attribute::Image,
            SchemaAttribute {
                name: Attribute::Image,
                uuid: UUID_SCHEMA_ATTR_IMAGE,
                description: String::from("An image for display to end users."),
                multivalue: false,
                unique: false,
                phantom: false,
                sync_allowed: true,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Image,
            },
        );

        self.attributes.insert(
            Attribute::OAuth2DeviceFlowEnable,
            SchemaAttribute {
                name: Attribute::OAuth2DeviceFlowEnable,
                uuid: UUID_SCHEMA_ATTR_OAUTH2_DEVICE_FLOW_ENABLE,
                description: String::from("Enable the OAuth2 Device Flow for this client."),
                multivalue: false,
                unique: true,
                phantom: false,
                sync_allowed: false,
                replicated: Replicated::True,
                indexed: false,
                syntax: SyntaxType::Boolean,
            },
        );

        self.classes.insert(
            EntryClass::AttributeType.into(),
            SchemaClass {
                name: EntryClass::AttributeType.into(),
                uuid: UUID_SCHEMA_CLASS_ATTRIBUTETYPE,
                description: String::from("Definition of a schema attribute"),
                systemmay: vec![
                    Attribute::Replicated,
                    Attribute::Phantom,
                    Attribute::SyncAllowed,
                    Attribute::Index,
                    Attribute::Indexed,
                ],
                systemmust: vec![
                    Attribute::Class,
                    Attribute::AttributeName,
                    Attribute::MultiValue,
                    Attribute::Unique,
                    Attribute::Syntax,
                    Attribute::Description,
                ],
                systemexcludes: vec![EntryClass::ClassType.into()],
                ..Default::default()
            },
        );
        self.classes.insert(
            EntryClass::ClassType.into(),
            SchemaClass {
                name: EntryClass::ClassType.into(),
                uuid: UUID_SCHEMA_CLASS_CLASSTYPE,
                description: String::from("Definition of a schema classtype"),
                systemmay: vec![
                    Attribute::SyncAllowed,
                    Attribute::SystemMay,
                    Attribute::May,
                    Attribute::SystemMust,
                    Attribute::Must,
                    Attribute::SystemSupplements,
                    Attribute::Supplements,
                    Attribute::SystemExcludes,
                    Attribute::Excludes,
                ],
                systemmust: vec![
                    Attribute::Class,
                    Attribute::ClassName,
                    Attribute::Description,
                ],
                systemexcludes: vec![Attribute::AttributeType.into()],
                ..Default::default()
            },
        );
        self.classes.insert(
            EntryClass::Object.into(),
            SchemaClass {
                name: EntryClass::Object.into(),
                uuid: UUID_SCHEMA_CLASS_OBJECT,
                description: String::from("A system created class that all objects must contain"),
                systemmay: vec![Attribute::Description, Attribute::EntryManagedBy],
                systemmust: vec![
                    Attribute::Class,
                    Attribute::Uuid,
                    Attribute::LastModifiedCid,
                    Attribute::CreatedAtCid,
                ],
                ..Default::default()
            },
        );
        self.classes.insert(
            EntryClass::Builtin.into(),
            SchemaClass {
                name: EntryClass::Builtin.into(),
                uuid: UUID_SCHEMA_CLASS_BUILTIN,
                description: String::from("A marker class denoting builtin entries"),
                ..Default::default()
            },
        );
        self.classes.insert(
            EntryClass::MemberOf.into(),
            SchemaClass {
                name: EntryClass::MemberOf.into(),
                uuid: UUID_SCHEMA_CLASS_MEMBEROF,
                description: String::from(
                    "Class that is dynamically added to recipients of memberof or directmemberof",
                ),
                systemmay: vec![Attribute::MemberOf, Attribute::DirectMemberOf],
                ..Default::default()
            },
        );
        self.classes.insert(
            EntryClass::ExtensibleObject.into(),
            SchemaClass {
                name: EntryClass::ExtensibleObject.into(),
                uuid: UUID_SCHEMA_CLASS_EXTENSIBLEOBJECT,
                description: String::from(
                    "A class type that has green hair and turns off all rules ...",
                ),
                ..Default::default()
            },
        );
        /* These two classes are core to the entry lifecycle for recycling and tombstoning */
        self.classes.insert(
                EntryClass::Recycled.into(),
                SchemaClass {
                    name: EntryClass::Recycled.into(),
                    uuid: UUID_SCHEMA_CLASS_RECYCLED,
                    description: String::from("An object that has been deleted, but still recoverable via the revive operation. Recycled objects are not modifiable, only revivable."),
                    systemmay: vec![Attribute::RecycledDirectMemberOf],
                    .. Default::default()
                },
            );
        self.classes.insert(
                EntryClass::Tombstone.into(),
                SchemaClass {
                    name: EntryClass::Tombstone.into(),
                    uuid: UUID_SCHEMA_CLASS_TOMBSTONE,
                    description: String::from("An object that is purged from the recycle bin. This is a system internal state. Tombstones have no attributes beside UUID."),
                    systemmust: vec![
                        Attribute::Class,
                        Attribute::Uuid,
                    ],
                    .. Default::default()
                },
            );
        self.classes.insert(
            EntryClass::Conflict.into(),
            SchemaClass {
                name: EntryClass::Conflict.into(),
                uuid: UUID_SCHEMA_CLASS_CONFLICT,
                description: String::from(
                    "An entry representing conflicts that occurred during replication",
                ),
                systemmust: vec![Attribute::SourceUuid],
                systemsupplements: vec![EntryClass::Recycled.into()],
                ..Default::default()
            },
        );
        // sysinfo
        self.classes.insert(
            EntryClass::SystemInfo.into(),
            SchemaClass {
                name: EntryClass::SystemInfo.into(),
                uuid: UUID_SCHEMA_CLASS_SYSTEM_INFO,
                description: String::from("System metadata object class"),
                systemmust: vec![Attribute::Version],
                ..Default::default()
            },
        );
        // ACP
        self.classes.insert(
            EntryClass::AccessControlSearch.into(),
            SchemaClass {
                name: EntryClass::AccessControlSearch.into(),
                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_SEARCH,
                description: String::from("System Access Control Search Class"),
                systemmust: vec![Attribute::AcpSearchAttr],
                ..Default::default()
            },
        );
        self.classes.insert(
            EntryClass::AccessControlDelete.into(),
            SchemaClass {
                name: EntryClass::AccessControlDelete.into(),
                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_DELETE,
                description: String::from("System Access Control DELETE Class"),
                ..Default::default()
            },
        );
        self.classes.insert(
            EntryClass::AccessControlModify.into(),
            SchemaClass {
                name: EntryClass::AccessControlModify.into(),
                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_MODIFY,
                description: String::from("System Access Control Modify Class"),
                systemmay: vec![
                    Attribute::AcpModifyRemovedAttr,
                    Attribute::AcpModifyPresentAttr,
                    Attribute::AcpModifyClass,
                    Attribute::AcpModifyPresentClass,
                    Attribute::AcpModifyRemoveClass,
                ],
                ..Default::default()
            },
        );
        self.classes.insert(
            EntryClass::AccessControlCreate.into(),
            SchemaClass {
                name: EntryClass::AccessControlCreate.into(),
                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_CREATE,
                description: String::from("System Access Control Create Class"),
                systemmay: vec![Attribute::AcpCreateClass, Attribute::AcpCreateAttr],
                ..Default::default()
            },
        );
        self.classes.insert(
            EntryClass::AccessControlProfile.into(),
            SchemaClass {
                name: EntryClass::AccessControlProfile.into(),
                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_PROFILE,
                description: String::from("System Access Control Profile Class"),
                systemmay: vec![Attribute::AcpEnable, Attribute::Description],
                systemmust: vec![Attribute::Name],
                systemsupplements: vec![
                    EntryClass::AccessControlSearch.into(),
                    EntryClass::AccessControlDelete.into(),
                    EntryClass::AccessControlModify.into(),
                    EntryClass::AccessControlCreate.into(),
                ],
                ..Default::default()
            },
        );
        self.classes.insert(
            EntryClass::AccessControlReceiverEntryManager.into(),
            SchemaClass {
                name: EntryClass::AccessControlReceiverEntryManager.into(),
                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_RECEIVER_ENTRY_MANAGER,
                description: String::from("System Access Control Profile Receiver - Entry Manager"),
                systemexcludes: vec![EntryClass::AccessControlReceiverGroup.into()],
                systemsupplements: vec![EntryClass::AccessControlProfile.into()],
                ..Default::default()
            },
        );
        self.classes.insert(
            EntryClass::AccessControlReceiverGroup.into(),
            SchemaClass {
                name: EntryClass::AccessControlReceiverGroup.into(),
                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_RECEIVER_GROUP,
                description: String::from("System Access Control Profile Receiver - Group"),
                systemmay: vec![Attribute::AcpReceiver],
                systemmust: vec![Attribute::AcpReceiverGroup],
                systemsupplements: vec![EntryClass::AccessControlProfile.into()],
                systemexcludes: vec![EntryClass::AccessControlReceiverEntryManager.into()],
                ..Default::default()
            },
        );
        self.classes.insert(
            EntryClass::AccessControlTargetScope.into(),
            SchemaClass {
                name: EntryClass::AccessControlTargetScope.into(),
                uuid: UUID_SCHEMA_CLASS_ACCESS_CONTROL_TARGET_SCOPE,
                description: String::from("System Access Control Profile Target - Scope"),
                systemmust: vec![Attribute::AcpTargetScope],
                systemsupplements: vec![EntryClass::AccessControlProfile.into()],
                ..Default::default()
            },
        );

        // System attrs
        self.classes.insert(
            EntryClass::System.into(),
            SchemaClass {
                name: EntryClass::System.into(),
                uuid: UUID_SCHEMA_CLASS_SYSTEM,
                description: String::from("A class denoting that a type is system generated and protected. It has special internal behaviour."),
                .. Default::default()
            },
        );
        self.classes.insert(
            EntryClass::SyncObject.into(),
            SchemaClass {
                name: EntryClass::SyncObject.into(),
                uuid: UUID_SCHEMA_CLASS_SYNC_OBJECT,
                description: String::from("A class denoting that an entry is synchronised from an external source. This entry may not be modifiable."),
                systemmust: vec![
                    Attribute::SyncParentUuid
                ],
                systemmay: vec![
                    Attribute::SyncExternalId,
                    Attribute::SyncClass,
                ],
                .. Default::default()
            },
        );

        let r = self.validate();
        if r.is_empty() {
            admin_debug!("schema validate -> passed");
            Ok(())
        } else {
            admin_error!(err = ?r, "schema validate -> errors");
            Err(OperationError::ConsistencyError(
                r.into_iter().filter_map(|v| v.err()).collect(),
            ))
        }
    }
}

impl SchemaTransaction for SchemaWriteTransaction<'_> {
    fn get_attributes_unique(&self) -> &Vec<Attribute> {
        &self.unique_cache
    }

    fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute> {
        &self.ref_cache
    }

    fn get_classes(&self) -> &HashMap<AttrString, SchemaClass> {
        &self.classes
    }

    fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute> {
        &self.attributes
    }
}

impl SchemaTransaction for SchemaReadTransaction {
    fn get_attributes_unique(&self) -> &Vec<Attribute> {
        &self.unique_cache
    }

    fn get_reference_types(&self) -> &HashMap<Attribute, SchemaAttribute> {
        &self.ref_cache
    }

    fn get_classes(&self) -> &HashMap<AttrString, SchemaClass> {
        &self.classes
    }

    fn get_attributes(&self) -> &HashMap<Attribute, SchemaAttribute> {
        &self.attributes
    }
}

impl Schema {
    pub fn new() -> Result<Self, OperationError> {
        let s = Schema {
            classes: CowCell::new(HashMap::with_capacity(128)),
            attributes: CowCell::new(HashMap::with_capacity(128)),
            unique_cache: CowCell::new(Vec::with_capacity(0)),
            ref_cache: CowCell::new(HashMap::with_capacity(64)),
        };
        // let mut sw = task::block_on(s.write());
        let mut sw = s.write();
        let r1 = sw.generate_in_memory();
        debug_assert!(r1.is_ok());
        r1?;
        let r2 = sw.commit().map(|_| s);
        debug_assert!(r2.is_ok());
        r2
    }

    pub fn read(&self) -> SchemaReadTransaction {
        SchemaReadTransaction {
            classes: self.classes.read(),
            attributes: self.attributes.read(),
            unique_cache: self.unique_cache.read(),
            ref_cache: self.ref_cache.read(),
        }
    }

    pub fn write(&self) -> SchemaWriteTransaction<'_> {
        SchemaWriteTransaction {
            classes: self.classes.write(),
            attributes: self.attributes.write(),
            unique_cache: self.unique_cache.write(),
            ref_cache: self.ref_cache.write(),
        }
    }

    #[cfg(test)]
    pub(crate) fn write_blocking(&self) -> SchemaWriteTransaction<'_> {
        self.write()
    }
}

#[cfg(test)]
mod tests {
    use crate::prelude::*;
    use crate::schema::{Schema, SchemaAttribute, SchemaClass, SchemaTransaction, SyntaxType};
    use uuid::Uuid;

    // use crate::proto_v1::Filter as ProtoFilter;

    macro_rules! validate_schema {
        ($sch:ident) => {{
            // Turns into a result type
            let r: Result<Vec<()>, ConsistencyError> = $sch.validate().into_iter().collect();
            assert!(r.is_ok());
        }};
    }

    macro_rules! sch_from_entry_ok {
        (
            $e:expr,
            $type:ty
        ) => {{
            let ev1 = $e.into_sealed_committed();

            let r1 = <$type>::try_from(&ev1);
            assert!(r1.is_ok());
        }};
    }

    macro_rules! sch_from_entry_err {
        (
            $e:expr,
            $type:ty
        ) => {{
            let ev1 = $e.into_sealed_committed();

            let r1 = <$type>::try_from(&ev1);
            assert!(r1.is_err());
        }};
    }

    #[test]
    fn test_schema_attribute_from_entry() {
        sketching::test_init();

        sch_from_entry_err!(
            entry_init!(
                (Attribute::Class, EntryClass::Object.to_value()),
                (Attribute::Class, EntryClass::AttributeType.to_value()),
                (
                    Attribute::AttributeName,
                    Value::new_iutf8("schema_attr_test")
                ),
                (
                    Attribute::Uuid,
                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
                ),
                (Attribute::Unique, Value::Bool(false))
            ),
            SchemaAttribute
        );

        sch_from_entry_err!(
            entry_init!(
                (Attribute::Class, EntryClass::Object.to_value()),
                (Attribute::Class, EntryClass::AttributeType.to_value()),
                (
                    Attribute::AttributeName,
                    Value::new_iutf8("schema_attr_test")
                ),
                (
                    Attribute::Uuid,
                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
                ),
                (Attribute::MultiValue, Value::Bool(false)),
                (Attribute::Unique, Value::Bool(false)),
                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
            ),
            SchemaAttribute
        );

        sch_from_entry_err!(
            entry_init!(
                (Attribute::Class, EntryClass::Object.to_value()),
                (Attribute::Class, EntryClass::AttributeType.to_value()),
                (
                    Attribute::AttributeName,
                    Value::new_iutf8("schema_attr_test")
                ),
                (
                    Attribute::Uuid,
                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
                ),
                (
                    Attribute::Description,
                    Value::Utf8("Test attr parsing".to_string())
                ),
                (Attribute::MultiValue, Value::Utf8("htouaoeu".to_string())),
                (Attribute::Unique, Value::Bool(false)),
                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
            ),
            SchemaAttribute
        );

        sch_from_entry_err!(
            entry_init!(
                (Attribute::Class, EntryClass::Object.to_value()),
                (Attribute::Class, EntryClass::AttributeType.to_value()),
                (
                    Attribute::AttributeName,
                    Value::new_iutf8("schema_attr_test")
                ),
                (
                    Attribute::Uuid,
                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
                ),
                (
                    Attribute::Description,
                    Value::Utf8("Test attr parsing".to_string())
                ),
                (Attribute::MultiValue, Value::Bool(false)),
                (Attribute::Unique, Value::Bool(false)),
                (Attribute::Syntax, Value::Utf8("TNEOUNTUH".to_string()))
            ),
            SchemaAttribute
        );

        // Index is allowed to be empty
        sch_from_entry_ok!(
            entry_init!(
                (Attribute::Class, EntryClass::Object.to_value()),
                (Attribute::Class, EntryClass::AttributeType.to_value()),
                (
                    Attribute::AttributeName,
                    Value::new_iutf8("schema_attr_test")
                ),
                (
                    Attribute::Uuid,
                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
                ),
                (
                    Attribute::Description,
                    Value::Utf8("Test attr parsing".to_string())
                ),
                (Attribute::MultiValue, Value::Bool(false)),
                (Attribute::Unique, Value::Bool(false)),
                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String))
            ),
            SchemaAttribute
        );

        // Index present
        sch_from_entry_ok!(
            entry_init!(
                (Attribute::Class, EntryClass::Object.to_value()),
                (Attribute::Class, EntryClass::AttributeType.to_value()),
                (
                    Attribute::AttributeName,
                    Value::new_iutf8("schema_attr_test")
                ),
                (
                    Attribute::Uuid,
                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
                ),
                (
                    Attribute::Description,
                    Value::Utf8("Test attr parsing".to_string())
                ),
                (Attribute::MultiValue, Value::Bool(false)),
                (Attribute::Unique, Value::Bool(false)),
                (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
                (Attribute::Index, Value::Bool(true))
            ),
            SchemaAttribute
        );
    }

    #[test]
    fn test_schema_class_from_entry() {
        sch_from_entry_err!(
            entry_init!(
                (Attribute::Class, EntryClass::Object.to_value()),
                (Attribute::Class, EntryClass::ClassType.to_value()),
                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
                (
                    Attribute::Uuid,
                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
                )
            ),
            SchemaClass
        );

        sch_from_entry_err!(
            entry_init!(
                (Attribute::Class, EntryClass::Object.to_value()),
                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
                (
                    Attribute::Uuid,
                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
                ),
                (
                    Attribute::Description,
                    Value::Utf8("class test".to_string())
                )
            ),
            SchemaClass
        );

        // Classes can be valid with no attributes provided.
        sch_from_entry_ok!(
            entry_init!(
                (Attribute::Class, EntryClass::Object.to_value()),
                (Attribute::Class, EntryClass::ClassType.to_value()),
                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
                (
                    Attribute::Uuid,
                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
                ),
                (
                    Attribute::Description,
                    Value::Utf8("class test".to_string())
                )
            ),
            SchemaClass
        );

        // Classes with various may/must
        sch_from_entry_ok!(
            entry_init!(
                (Attribute::Class, EntryClass::Object.to_value()),
                (Attribute::Class, EntryClass::ClassType.to_value()),
                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
                (
                    Attribute::Uuid,
                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
                ),
                (
                    Attribute::Description,
                    Value::Utf8("class test".to_string())
                ),
                (Attribute::SystemMust, Value::new_iutf8("a"))
            ),
            SchemaClass
        );

        sch_from_entry_ok!(
            entry_init!(
                (Attribute::Class, EntryClass::Object.to_value()),
                (Attribute::Class, EntryClass::ClassType.to_value()),
                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
                (
                    Attribute::Uuid,
                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
                ),
                (
                    Attribute::Description,
                    Value::Utf8("class test".to_string())
                ),
                (Attribute::SystemMay, Value::new_iutf8("a"))
            ),
            SchemaClass
        );

        sch_from_entry_ok!(
            entry_init!(
                (Attribute::Class, EntryClass::Object.to_value()),
                (Attribute::Class, EntryClass::ClassType.to_value()),
                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
                (
                    Attribute::Uuid,
                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
                ),
                (
                    Attribute::Description,
                    Value::Utf8("class test".to_string())
                ),
                (Attribute::May, Value::new_iutf8("a")),
                (Attribute::Must, Value::new_iutf8("b"))
            ),
            SchemaClass
        );

        sch_from_entry_ok!(
            entry_init!(
                (Attribute::Class, EntryClass::Object.to_value()),
                (Attribute::Class, EntryClass::ClassType.to_value()),
                (Attribute::ClassName, Value::new_iutf8("schema_class_test")),
                (
                    Attribute::Uuid,
                    Value::Uuid(uuid::uuid!("66c68b2f-d02c-4243-8013-7946e40fe321"))
                ),
                (
                    Attribute::Description,
                    Value::Utf8("class test".to_string())
                ),
                (Attribute::May, Value::new_iutf8("a")),
                (Attribute::Must, Value::new_iutf8("b")),
                (Attribute::SystemMay, Value::new_iutf8("c")),
                (Attribute::SystemMust, Value::new_iutf8("d"))
            ),
            SchemaClass
        );
    }

    #[test]
    fn test_schema_attribute_simple() {
        // Test schemaAttribute validation of types.

        // Test single value string
        let single_value_string = SchemaAttribute {
            name: Attribute::from("single_value"),
            uuid: Uuid::new_v4(),
            description: String::from(""),
            syntax: SyntaxType::Utf8StringInsensitive,
            ..Default::default()
        };

        let r1 = single_value_string
            .validate_ava(&Attribute::from("single_value"), &(vs_iutf8!["test"] as _));
        assert_eq!(r1, Ok(()));

        let rvs = vs_iutf8!["test1", "test2"] as _;
        let r2 = single_value_string.validate_ava(&Attribute::from("single_value"), &rvs);
        assert_eq!(
            r2,
            Err(SchemaError::InvalidAttributeSyntax(
                "single_value".to_string()
            ))
        );

        // test multivalue string, boolean

        let multi_value_string = SchemaAttribute {
            name: Attribute::from("mv_string"),
            uuid: Uuid::new_v4(),
            description: String::from(""),
            multivalue: true,
            syntax: SyntaxType::Utf8String,
            ..Default::default()
        };

        let rvs = vs_utf8!["test1".to_string(), "test2".to_string()] as _;
        let r5 = multi_value_string.validate_ava(&Attribute::from("mv_string"), &rvs);
        assert_eq!(r5, Ok(()));

        let multi_value_boolean = SchemaAttribute {
            name: Attribute::from("mv_bool"),
            uuid: Uuid::new_v4(),
            description: String::from(""),
            multivalue: true,
            syntax: SyntaxType::Boolean,
            ..Default::default()
        };

        // Since valueset now disallows such shenanigans at a type level, this can't occur
        /*
        let rvs = unsafe {
            valueset![
                Value::new_bool(true),
                Value::new_iutf8("test1"),
                Value::new_iutf8("test2")
            ]
        };
        let r3 = multi_value_boolean.validate_ava("mv_bool", &rvs);
        assert_eq!(
            r3,
            Err(SchemaError::InvalidAttributeSyntax("mv_bool".to_string()))
        );
        */

        let rvs = vs_bool![true, false];
        let r4 = multi_value_boolean.validate_ava(&Attribute::from("mv_bool"), &(rvs as _));
        assert_eq!(r4, Ok(()));

        // syntax_id and index_type values
        let single_value_syntax = SchemaAttribute {
            name: Attribute::from("sv_syntax"),
            uuid: Uuid::new_v4(),
            description: String::from(""),
            syntax: SyntaxType::SyntaxId,
            ..Default::default()
        };

        let rvs = vs_syntax![SyntaxType::try_from("UTF8STRING").unwrap()] as _;
        let r6 = single_value_syntax.validate_ava(&Attribute::from("sv_syntax"), &rvs);
        assert_eq!(r6, Ok(()));

        let rvs = vs_utf8!["thaeountaheu".to_string()] as _;
        let r7 = single_value_syntax.validate_ava(&Attribute::from("sv_syntax"), &rvs);
        assert_eq!(
            r7,
            Err(SchemaError::InvalidAttributeSyntax("sv_syntax".to_string()))
        );

        let single_value_index = SchemaAttribute {
            name: Attribute::from("sv_index"),
            uuid: Uuid::new_v4(),
            description: String::from(""),
            syntax: SyntaxType::IndexId,
            ..Default::default()
        };

        let rvs = vs_utf8!["thaeountaheu".to_string()] as _;
        let r9 = single_value_index.validate_ava(&Attribute::from("sv_index"), &rvs);
        assert_eq!(
            r9,
            Err(SchemaError::InvalidAttributeSyntax("sv_index".to_string()))
        );
    }

    #[test]
    fn test_schema_simple() {
        let schema = Schema::new().expect("failed to create schema");
        let schema_ro = schema.read();
        validate_schema!(schema_ro);
    }

    #[test]
    fn test_schema_entries() {
        sketching::test_init();
        // Given an entry, assert it's schema is valid
        // We do
        let schema_outer = Schema::new().expect("failed to create schema");
        let schema = schema_outer.read();

        let e_no_uuid = entry_init!().into_invalid_new();

        assert_eq!(
            e_no_uuid.validate(&schema),
            Err(SchemaError::MissingMustAttribute(vec![Attribute::Uuid]))
        );

        let e_no_class = entry_init!((
            Attribute::Uuid,
            Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
        ))
        .into_invalid_new();

        assert_eq!(e_no_class.validate(&schema), Err(SchemaError::NoClassFound));

        let e_bad_class = entry_init!(
            (
                Attribute::Uuid,
                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
            ),
            (Attribute::Class, Value::new_class("zzzzzz"))
        )
        .into_invalid_new();
        assert_eq!(
            e_bad_class.validate(&schema),
            Err(SchemaError::InvalidClass(vec!["zzzzzz".to_string()]))
        );

        let e_attr_invalid = entry_init!(
            (
                Attribute::Uuid,
                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
            ),
            (Attribute::Class, EntryClass::Object.to_value()),
            (Attribute::Class, EntryClass::AttributeType.to_value())
        )
        .into_invalid_new();
        let res = e_attr_invalid.validate(&schema);
        matches!(res, Err(SchemaError::MissingMustAttribute(_)));

        let e_attr_invalid_may = entry_init!(
            (Attribute::Class, EntryClass::Object.to_value()),
            (Attribute::Class, EntryClass::AttributeType.to_value()),
            (Attribute::AttributeName, Value::new_iutf8("testattr")),
            (Attribute::Description, Value::Utf8("testattr".to_string())),
            (Attribute::MultiValue, Value::Bool(false)),
            (Attribute::Unique, Value::Bool(false)),
            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
            (
                Attribute::Uuid,
                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
            ),
            (Attribute::TestAttr, Value::Utf8("zzzz".to_string()))
        )
        .into_invalid_new();

        assert_eq!(
            e_attr_invalid_may.validate(&schema),
            Err(SchemaError::AttributeNotValidForClass(
                Attribute::TestAttr.to_string()
            ))
        );

        let e_attr_invalid_syn = entry_init!(
            (Attribute::Class, EntryClass::Object.to_value()),
            (Attribute::Class, EntryClass::AttributeType.to_value()),
            (Attribute::AttributeName, Value::new_iutf8("testattr")),
            (Attribute::Description, Value::Utf8("testattr".to_string())),
            (Attribute::MultiValue, Value::Utf8("false".to_string())),
            (Attribute::Unique, Value::Bool(false)),
            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
            (
                Attribute::Uuid,
                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
            )
        )
        .into_invalid_new();

        assert_eq!(
            e_attr_invalid_syn.validate(&schema),
            Err(SchemaError::InvalidAttributeSyntax(
                "multivalue".to_string()
            ))
        );

        // You may not have the phantom.
        let e_phantom = entry_init!(
            (Attribute::Class, EntryClass::Object.to_value()),
            (Attribute::Class, EntryClass::AttributeType.to_value()),
            (Attribute::AttributeName, Value::new_iutf8("testattr")),
            (Attribute::Description, Value::Utf8("testattr".to_string())),
            (Attribute::MultiValue, Value::Bool(false)),
            (Attribute::Unique, Value::Bool(false)),
            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
            (
                Attribute::Uuid,
                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
            ),
            (
                Attribute::PasswordImport,
                Value::Utf8("password".to_string())
            )
        )
        .into_invalid_new();
        assert!(e_phantom.validate(&schema).is_err());

        let e_ok = entry_init!(
            (Attribute::Class, EntryClass::Object.to_value()),
            (Attribute::Class, EntryClass::AttributeType.to_value()),
            (Attribute::AttributeName, Value::new_iutf8("testattr")),
            (Attribute::Description, Value::Utf8("testattr".to_string())),
            (Attribute::MultiValue, Value::Bool(true)),
            (Attribute::Unique, Value::Bool(false)),
            (Attribute::Syntax, Value::Syntax(SyntaxType::Utf8String)),
            (
                Attribute::Uuid,
                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
            )
        )
        .into_invalid_new();
        assert!(e_ok.validate(&schema).is_ok());
    }

    #[test]
    fn test_schema_extensible() {
        let schema_outer = Schema::new().expect("failed to create schema");
        let schema = schema_outer.read();
        // Just because you are extensible, doesn't mean you can be lazy
        let e_extensible_bad = entry_init!(
            (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
            (
                Attribute::Uuid,
                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
            ),
            (Attribute::MultiValue, Value::Utf8("zzzz".to_string()))
        )
        .into_invalid_new();

        assert_eq!(
            e_extensible_bad.validate(&schema),
            Err(SchemaError::InvalidAttributeSyntax(
                "multivalue".to_string()
            ))
        );

        // Extensible doesn't mean you can have the phantoms
        let e_extensible_phantom = entry_init!(
            (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
            (
                Attribute::Uuid,
                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
            ),
            (Attribute::PasswordImport, Value::Utf8("zzzz".to_string()))
        )
        .into_invalid_new();

        assert_eq!(
            e_extensible_phantom.validate(&schema),
            Err(SchemaError::PhantomAttribute(
                Attribute::PasswordImport.to_string()
            ))
        );

        let e_extensible = entry_init!(
            (Attribute::Class, EntryClass::ExtensibleObject.to_value()),
            (
                Attribute::Uuid,
                Value::Uuid(uuid::uuid!("db237e8a-0079-4b8c-8a56-593b22aa44d1"))
            ),
            (Attribute::MultiValue, Value::Bool(true))
        )
        .into_invalid_new();

        /* Is okay because extensible! */
        assert!(e_extensible.validate(&schema).is_ok());
    }

    #[test]
    fn test_schema_filter_validation() {
        let schema_outer = Schema::new().expect("failed to create schema");
        let schema = schema_outer.read();

        // test syntax of bool
        let f_bool = filter_all!(f_eq(Attribute::MultiValue, PartialValue::new_iutf8("zzzz")));
        assert_eq!(
            f_bool.validate(&schema),
            Err(SchemaError::InvalidAttributeSyntax(
                "multivalue".to_string()
            ))
        );
        // test insensitive values
        let f_insense = filter_all!(f_eq(Attribute::Class, EntryClass::AttributeType.into()));
        assert_eq!(
            f_insense.validate(&schema),
            Ok(filter_valid!(f_eq(
                Attribute::Class,
                EntryClass::AttributeType.into()
            )))
        );
        // Test the recursive structures validate
        let f_or_empty = filter_all!(f_or!([]));
        assert_eq!(f_or_empty.validate(&schema), Err(SchemaError::EmptyFilter));
        let f_or = filter_all!(f_or!([f_eq(
            Attribute::MultiValue,
            PartialValue::new_iutf8("zzzz")
        )]));
        assert_eq!(
            f_or.validate(&schema),
            Err(SchemaError::InvalidAttributeSyntax(
                "multivalue".to_string()
            ))
        );
        let f_or_mult = filter_all!(f_and!([
            f_eq(Attribute::Class, EntryClass::AttributeType.into()),
            f_eq(Attribute::MultiValue, PartialValue::new_iutf8("zzzzzzz")),
        ]));
        assert_eq!(
            f_or_mult.validate(&schema),
            Err(SchemaError::InvalidAttributeSyntax(
                "multivalue".to_string()
            ))
        );
        // Test mixed case attr name - this is a pass, due to normalisation
        let f_or_ok = filter_all!(f_andnot(f_and!([
            f_eq(Attribute::Class, EntryClass::AttributeType.into()),
            f_sub(Attribute::Class, EntryClass::ClassType.into()),
            f_pres(Attribute::Class)
        ])));
        assert_eq!(
            f_or_ok.validate(&schema),
            Ok(filter_valid!(f_andnot(f_and!([
                f_eq(Attribute::Class, EntryClass::AttributeType.into()),
                f_sub(Attribute::Class, EntryClass::ClassType.into()),
                f_pres(Attribute::Class)
            ]))))
        );
    }

    #[test]
    fn test_schema_class_phantom_reject() {
        // Check that entries can be normalised and validated sanely
        let schema_outer = Schema::new().expect("failed to create schema");
        let mut schema = schema_outer.write_blocking();

        assert!(schema.validate().is_empty());

        // Attempt to create a class with a phantom attribute, should be refused.
        let class = SchemaClass {
            name: AttrString::from("testobject"),
            uuid: Uuid::new_v4(),
            description: String::from("test object"),
            systemmay: vec![Attribute::Claim],
            ..Default::default()
        };

        assert!(schema.update_classes(vec![class]).is_ok());

        assert_eq!(schema.validate().len(), 1);
    }

    #[test]
    fn test_schema_class_exclusion_requires() {
        sketching::test_init();

        let schema_outer = Schema::new().expect("failed to create schema");
        let mut schema = schema_outer.write_blocking();

        assert!(schema.validate().is_empty());

        // We setup some classes that have requires and excludes and check that they
        // are enforced correctly.
        let class_account = SchemaClass {
            name: Attribute::Account.into(),
            uuid: Uuid::new_v4(),
            description: String::from("account object"),
            systemmust: vec![
                Attribute::Class,
                Attribute::Uuid,
                Attribute::LastModifiedCid,
                Attribute::CreatedAtCid,
            ],
            systemsupplements: vec![EntryClass::Service.into(), EntryClass::Person.into()],
            ..Default::default()
        };

        let class_person = SchemaClass {
            name: EntryClass::Person.into(),
            uuid: Uuid::new_v4(),
            description: String::from("person object"),
            systemmust: vec![
                Attribute::Class,
                Attribute::Uuid,
                Attribute::LastModifiedCid,
                Attribute::CreatedAtCid,
            ],
            ..Default::default()
        };

        let class_service = SchemaClass {
            name: EntryClass::Service.into(),
            uuid: Uuid::new_v4(),
            description: String::from("service object"),
            systemmust: vec![
                Attribute::Class,
                Attribute::Uuid,
                Attribute::LastModifiedCid,
                Attribute::CreatedAtCid,
            ],
            excludes: vec![EntryClass::Person.into()],
            ..Default::default()
        };

        assert!(schema
            .update_classes(vec![class_account, class_person, class_service])
            .is_ok());

        // Missing person or service account.
        let e_account = entry_init!(
            (Attribute::Class, EntryClass::Account.to_value()),
            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
        )
        .into_invalid_new();

        assert_eq!(
            e_account.validate(&schema),
            Err(SchemaError::SupplementsNotSatisfied(vec![
                EntryClass::Service.into(),
                EntryClass::Person.into(),
            ]))
        );

        // Service account missing account
        /*
        let e_service = unsafe { entry_init!(
            (Attribute::Class, EntryClass::Service.to_value()),
            (Attribute::Uuid, Value::new_uuid(Uuid::new_v4()))
        ).into_invalid_new() };

        assert_eq!(
            e_service.validate(&schema),
            Err(SchemaError::RequiresNotSatisfied(vec![Attribute::Account.to_string()]))
        );
        */

        // Service can't have person
        let e_service_person = entry_init!(
            (Attribute::Class, EntryClass::Service.to_value()),
            (Attribute::Class, EntryClass::Account.to_value()),
            (Attribute::Class, EntryClass::Person.to_value()),
            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
        )
        .into_invalid_new();

        assert_eq!(
            e_service_person.validate(&schema),
            Err(SchemaError::ExcludesNotSatisfied(vec![
                EntryClass::Person.to_string()
            ]))
        );

        // These are valid configurations.
        let e_service_valid = entry_init!(
            (Attribute::Class, EntryClass::Service.to_value()),
            (Attribute::Class, EntryClass::Account.to_value()),
            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
        )
        .into_invalid_new();

        assert!(e_service_valid.validate(&schema).is_ok());

        let e_person_valid = entry_init!(
            (Attribute::Class, EntryClass::Person.to_value()),
            (Attribute::Class, EntryClass::Account.to_value()),
            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
        )
        .into_invalid_new();

        assert!(e_person_valid.validate(&schema).is_ok());

        let e_person_valid = entry_init!(
            (Attribute::Class, EntryClass::Person.to_value()),
            (Attribute::Uuid, Value::Uuid(Uuid::new_v4()))
        )
        .into_invalid_new();

        assert!(e_person_valid.validate(&schema).is_ok());
    }
}