Large progress on schema and entries

This commit is contained in:
William Brown 2018-11-13 20:14:26 +13:00
parent f0702c01ee
commit 56264b5b7b
4 changed files with 681 additions and 16 deletions

View file

@ -1,5 +1,8 @@
// use serde_json::{Error, Value};
use std::collections::btree_map::Iter as BTreeIter;
use std::collections::BTreeMap;
use std::marker::PhantomData;
use std::slice::Iter as SliceIter;
// make a trait entry for everything to adhere to?
// * How to get indexs out?
@ -28,6 +31,49 @@ use std::collections::BTreeMap;
// }
//
pub struct EntryClasses<'a> {
inner: Option<SliceIter<'a, String>>,
// _p: &'a PhantomData<()>,
}
impl<'a> Iterator for EntryClasses<'a> {
type Item = &'a String;
#[inline]
fn next(&mut self) -> Option<(&'a String)> {
match self.inner.iter_mut().next() {
Some(i) => i.next(),
None => None,
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
match self.inner.iter().next() {
Some(i) => i.size_hint(),
None => (0, None),
}
}
}
pub struct EntryAvas<'a> {
inner: BTreeIter<'a, String, Vec<String>>,
}
impl<'a> Iterator for EntryAvas<'a> {
type Item = (&'a String, &'a Vec<String>);
#[inline]
fn next(&mut self) -> Option<(&'a String, &'a Vec<String>)> {
self.inner.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Entry {
attrs: BTreeMap<String, Vec<String>>,
@ -52,6 +98,10 @@ impl Entry {
Ok(())
}
pub fn get_ava(&self, attr: &String) -> Option<&Vec<String>> {
self.attrs.get(attr)
}
pub fn validate(&self) -> bool {
// We need access to the current system schema here now ...
true
@ -60,6 +110,21 @@ impl Entry {
pub fn pres(&self, attr: &str) -> bool {
self.attrs.contains_key(attr)
}
pub fn classes(&self) -> EntryClasses {
// Get the class vec, if any?
// How do we indicate "empty?"
// FIXME: Actually handle this error ...
let c = self.attrs.get("class").map(|c| c.iter());
EntryClasses { inner: c }
}
pub fn avas(&self) -> EntryAvas {
// Get all attr:value pairs.
EntryAvas {
inner: self.attrs.iter(),
}
}
}
impl Clone for Entry {
@ -149,8 +214,10 @@ impl User {
// We have to sort vecs ...
// Is there a way to call this on serialise?
fn validate() -> Result<(), ()> {
Err(())
fn validate(&self) -> Result<(), ()> {
// Given a schema, validate our object is sane.
Ok(())
}
}

9
src/error.rs Normal file
View file

@ -0,0 +1,9 @@
#[derive(Debug, PartialEq)]
pub enum SchemaError {
NOT_IMPLEMENTED,
INVALID_CLASS,
// FIXME: Is there a way to say what we are missing on error?
MISSING_MUST_ATTRIBUTE,
INVALID_ATTRIBUTE,
INVALID_ATTRIBUTE_SYNTAX,
}

View file

@ -1,3 +1,7 @@
#![feature(try_from)]
extern crate serde;
extern crate serde_json;
#[macro_use]
@ -24,8 +28,9 @@ pub mod log;
mod audit;
mod be;
pub mod entry;
pub mod error;
pub mod event;
pub mod filter;
pub mod proto;
pub mod server;
pub mod schema;
pub mod server;

View file

@ -1,4 +1,8 @@
use super::entry::Entry;
use super::error::SchemaError;
use std::collections::HashMap;
// Apparently this is nightly only?
use std::convert::TryFrom;
// representations of schema that confines object types, classes
// and attributes. This ties in deeply with "Entry".
@ -8,34 +12,92 @@ use std::collections::HashMap;
// In the future this will parse/read it's schema from the db
// but we have to bootstrap with some core types.
#[derive(Debug, PartialEq)]
enum Ternary {
Empty,
True,
False,
}
#[derive(Debug, Clone, PartialEq)]
pub enum IndexType {
EQUALITY,
PRESENCE,
SUBSTRING,
}
pub enum SyntaxType {
UTF8STRING,
impl TryFrom<String> for IndexType {
type Error = ();
fn try_from(value: String) -> Result<IndexType, Self::Error> {
if value == "EQUALITY" {
Ok(IndexType::EQUALITY)
} else if value == "PRESENCE" {
Ok(IndexType::PRESENCE)
} else if value == "SUBSTRING" {
Ok(IndexType::SUBSTRING)
} else {
Err(())
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SyntaxType {
// We need an insensitive string type too ...
// We also need to "self host" a syntax type, and index type
UTF8STRING,
UTF8STRING_INSENSITIVE,
BOOLEAN,
SYNTAX_ID,
INDEX_ID,
}
impl TryFrom<String> for IndexType {
type Error = ();
fn try_from(value: String) -> Result<SyntaxType, Self::Error> {
}
}
#[derive(Debug, Clone)]
pub struct SchemaAttribute {
class: Vec<String>,
name: String,
// Perhaps later add aliases?
description: String,
system: bool,
secret: bool,
multivalue: bool,
index: Vec<IndexType>,
syntax: SyntaxType,
}
pub struct SchemaClass {
name: String,
descriptions: String,
systemmay: Vec<SchemaAttribute>,
may: Vec<SchemaAttribute>,
systemmust: Vec<SchemaAttribute>,
must: Vec<SchemaAttribute>,
impl SchemaAttribute {
// Implement Equality, PartialOrd, Normalisation,
// Validation.
}
#[derive(Debug)]
pub struct SchemaClass {
class: Vec<String>,
name: String,
description: String,
// This allows modification of system types to be extended in custom ways
systemmay: Vec<String>,
may: Vec<String>,
systemmust: Vec<String>,
must: Vec<String>,
}
impl SchemaClass {
// Implement Validation and Normalisation against entries
pub fn validate_entry(&self, entry: &Entry) -> Result<(), ()> {
Err(())
}
}
#[derive(Debug)]
pub struct Schema {
// We contain sets of classes and attributes.
classes: HashMap<String, SchemaClass>,
@ -45,26 +107,548 @@ pub struct Schema {
impl Schema {
pub fn new() -> Self {
//
// Bootstrap in definitions of our own schema types
Schema {
let mut s = Schema {
classes: HashMap::new(),
attributes: HashMap::new(),
};
// Bootstrap in definitions of our own schema types
// First, add all the needed core attributes for schema parsing
s.attributes.insert(
String::from("class"),
SchemaAttribute {
class: vec![String::from("attributetype")],
name: String::from("class"),
description: String::from("The set of classes defining an object"),
system: true,
secret: false,
multivalue: true,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
String::from("name"),
SchemaAttribute {
class: vec![String::from("attributetype")],
name: String::from("name"),
description: String::from("The shortform name of an object"),
system: true,
secret: false,
multivalue: false,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
String::from("description"),
SchemaAttribute {
class: vec![String::from("attributetype")],
name: String::from("description"),
description: String::from("A description of an attribute, object or class"),
system: true,
secret: false,
multivalue: false,
index: vec![],
syntax: SyntaxType::UTF8STRING,
},
);
s.attributes.insert(
String::from("system"),
SchemaAttribute {
class: vec![String::from("attributetype")],
name: String::from("system"),
description: String::from(
"Is this object or attribute provided from the core system?",
),
system: true,
secret: false,
multivalue: false,
index: vec![],
syntax: SyntaxType::BOOLEAN,
},
);
s.attributes.insert(String::from("secret"), SchemaAttribute {
class: vec![String::from("attributetype")],
name: String::from("secret"),
description: String::from("If true, this value is always hidden internally to the server, even beyond access controls."),
system: true,
secret: false,
multivalue: false,
index: vec![],
syntax: SyntaxType::BOOLEAN,
});
s.attributes.insert(String::from("multivalue"), SchemaAttribute {
class: vec![String::from("attributetype")],
name: String::from("multivalue"),
description: String::from("If true, this attribute is able to store multiple values rather than just a single value."),
system: true,
secret: false,
multivalue: false,
index: vec![],
syntax: SyntaxType::BOOLEAN,
});
s.attributes.insert(
String::from("index"),
SchemaAttribute {
class: vec![String::from("attributetype")],
name: String::from("index"),
description: String::from(
"Describe the indexes to apply to instances of this attribute.",
),
system: true,
secret: false,
multivalue: false,
index: vec![],
syntax: SyntaxType::INDEX_ID,
},
);
s.attributes.insert(
String::from("syntax"),
SchemaAttribute {
class: vec![String::from("attributetype")],
name: String::from("syntax"),
description: String::from(
"Describe the syntax of this attribute. This affects indexing and sorting.",
),
system: true,
secret: false,
multivalue: false,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::SYNTAX_ID,
},
);
s.attributes.insert(
String::from("systemmay"),
SchemaAttribute {
class: vec![String::from("attributetype")],
name: String::from("systemmay"),
description: String::from(
"A list of system provided optional attributes this class can store.",
),
system: true,
secret: false,
multivalue: true,
index: vec![],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
String::from("may"),
SchemaAttribute {
class: vec![String::from("attributetype")],
name: String::from("may"),
description: String::from(
"A user modifiable list of optional attributes this class can store.",
),
system: true,
secret: false,
multivalue: false,
index: vec![],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
String::from("systemmust"),
SchemaAttribute {
class: vec![String::from("attributetype")],
name: String::from("systemmust"),
description: String::from(
"A list of system provided required attributes this class must store.",
),
system: true,
secret: false,
multivalue: false,
index: vec![],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.attributes.insert(
String::from("must"),
SchemaAttribute {
class: vec![String::from("attributetype")],
name: String::from("must"),
description: String::from(
"A user modifiable list of required attributes this class must store.",
),
system: true,
secret: false,
multivalue: false,
index: vec![],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
},
);
s.classes.insert(
String::from("attributetype"),
SchemaClass {
class: vec![String::from("classtype")],
name: String::from("attributetype"),
description: String::from("Definition of a schema attribute"),
systemmay: vec![String::from("index"), String::from("description")],
may: vec![],
systemmust: vec![
String::from("class"),
String::from("name"),
String::from("system"),
String::from("secret"),
String::from("multivalue"),
String::from("syntax"),
],
must: vec![],
},
);
s.classes.insert(
String::from("classtype"),
SchemaClass {
class: vec![String::from("classtype")],
name: String::from("classtype"),
description: String::from("Definition of a schema classtype"),
systemmay: vec![
String::from("description"),
String::from("systemmay"),
String::from("may"),
String::from("systemmust"),
String::from("must"),
],
may: vec![],
systemmust: vec![String::from("class"), String::from("name")],
must: vec![],
},
);
s.classes.insert(
String::from("extensibleobject"),
SchemaClass {
class: vec![String::from("classtype")],
name: String::from("extensibleobject"),
description: String::from("A class type that turns off all rules ..."),
systemmay: vec![],
may: vec![],
systemmust: vec![],
must: vec![],
},
);
s
}
pub fn validate(&self) -> Result<(), ()> {
// FIXME: How can we make this return a proper result?
//
// Do we need some functional bullshit?
// Validate our schema content is sane
// For now we only have a few basic methods for this, such as
// checking all our classes must/may are correct.
for class in self.classes.values() {
for a in &class.systemmay {
assert!(self.attributes.contains_key(a));
}
for a in &class.may {
assert!(self.attributes.contains_key(a));
}
for a in &class.systemmust {
assert!(self.attributes.contains_key(a));
}
for a in &class.must {
assert!(self.attributes.contains_key(a));
}
}
Ok(())
}
pub fn validate_entry(&self, entry: &Entry) -> Result<(), SchemaError> {
// First look at the classes on the entry.
// Now, check they are valid classes
//
// FIXME: We could restructure this to be a map that gets Some(class)
// if found, then do a len/filter/check on the resulting class set?
let c_valid = entry.classes().fold(Ternary::Empty, |acc, c| {
if acc == Ternary::False {
// Begin shortcircuit
acc
} else {
// Test the value (Could be True or Valid on entry.
// We
match self.classes.contains_key(c) {
true => Ternary::True,
false => Ternary::False,
}
}
});
if c_valid != Ternary::True {
return Err(SchemaError::INVALID_CLASS);
};
let classes: HashMap<String, &SchemaClass> = entry
.classes()
.map(|c| (c.clone(), self.classes.get(c).unwrap()))
.collect();
let extensible = classes.contains_key("extensibleobject");
// What this is really doing is taking a set of classes, and building an
// "overall" class that describes this exact object for checking
// for each class
// add systemmust/must and systemmay/may to their lists
// add anything from must also into may
// Now from the set of valid classes make a list of must/may
// FIXME: This is clone on read, which may be really slow. It also may
// be inefficent on duplicates etc.
let must: HashMap<String, &SchemaAttribute> = classes
.iter()
// Join our class systemmmust + must into one iter
.flat_map(|(_, cls)| cls.systemmust.iter().chain(cls.must.iter()))
.map(|s| (s.clone(), self.attributes.get(s).unwrap()))
.collect();
let may: HashMap<String, &SchemaAttribute> = classes
.iter()
// Join our class systemmmust + must into one iter
.flat_map(|(_, cls)| {
cls.systemmust
.iter()
.chain(cls.must.iter())
.chain(cls.systemmay.iter())
.chain(cls.may.iter())
})
.map(|s| (s.clone(), self.attributes.get(s).unwrap()))
.collect();
// FIXME: Error needs to say what is missing
// We need to return *all* missing attributes.
// Check that all must are inplace
// for each attr in must, check it's present on our ent
for (attr_name, attr) in must {
let avas = entry.get_ava(&attr_name);
if avas.is_none() {
return Err(SchemaError::MISSING_MUST_ATTRIBUTE);
}
}
// Check that any other attributes are in may
// for each attr on the object, check it's in the may+must set
for (attr_name, avas) in entry.avas() {
println!("AVAS {:?} : {:?}", attr_name, avas);
match self.attributes.get(attr_name) {
Some(a_schema) => {
// Now, for each type we do a *full* check of the syntax
// and validity of the ava.
}
None => {
if !extensible {
return Err(SchemaError::INVALID_ATTRIBUTE);
}
}
}
}
// Well, we got here, so okay!
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{Schema, SchemaClass, SchemaAttribute};
use std::convert::TryFrom;
use super::super::entry::Entry;
use super::super::error::SchemaError;
use super::{IndexType, Schema, SchemaAttribute, SchemaClass, SyntaxType};
#[test]
fn test_schema_index_tryfrom() {
let r1 = IndexType::try_from(String::from("EQUALITY"));
assert_eq!(r1, Ok(IndexType::EQUALITY));
let r2 = IndexType::try_from(String::from("PRESENCE"));
assert_eq!(r2, Ok(IndexType::PRESENCE));
let r3 = IndexType::try_from(String::from("SUBSTRING"));
assert_eq!(r3, Ok(IndexType::SUBSTRING));
let r4 = IndexType::try_from(String::from("thaoeusaneuh"));
assert_eq!(r4, Err(()));
}
#[test]
fn test_schema_syntax_tryfrom() {
}
#[test]
fn test_schema_attribute_simple() {
let class_attribute = SchemaAttribute {
class: vec![String::from("attributetype")],
name: String::from("class"),
description: String::from("The set of classes defining an object"),
system: true,
secret: false,
multivalue: true,
index: vec![IndexType::EQUALITY],
syntax: SyntaxType::UTF8STRING_INSENSITIVE,
};
// Test basic functions of simple attributes
}
#[test]
fn test_schema_classes_simple() {
// Test basic functions of simple attributes
}
#[test]
fn test_schema_simple() {
let schema = Schema::new();
assert!(schema.validate().is_ok());
}
#[test]
fn test_schema_export_validate() {
// Test exporting schema to entries, then validate them
// as legitimate entries.
}
#[test]
fn test_schema_entries() {
// Given an entry, assert it's schema is valid
// We do
let schema = Schema::new();
let mut e_no_class: Entry = Entry::new();
assert_eq!(
schema.validate_entry(&e_no_class),
Err(SchemaError::INVALID_CLASS)
);
let mut e_bad_class: Entry = Entry::new();
e_bad_class
.add_ava(String::from("class"), String::from("zzzzzz"))
.unwrap();
assert_eq!(
schema.validate_entry(&e_bad_class),
Err(SchemaError::INVALID_CLASS)
);
let mut e_attr_invalid: Entry = Entry::new();
e_attr_invalid
.add_ava(String::from("class"), String::from("attributetype"))
.unwrap();
assert_eq!(
schema.validate_entry(&e_attr_invalid),
Err(SchemaError::MISSING_MUST_ATTRIBUTE)
);
let mut e_attr_invalid_may: Entry = Entry::new();
e_attr_invalid_may
.add_ava(String::from("class"), String::from("attributetype"))
.unwrap();
e_attr_invalid_may
.add_ava(String::from("name"), String::from("testattr"))
.unwrap();
e_attr_invalid_may
.add_ava(String::from("system"), String::from("false"))
.unwrap();
e_attr_invalid_may
.add_ava(String::from("secret"), String::from("false"))
.unwrap();
e_attr_invalid_may
.add_ava(String::from("multivalue"), String::from("false"))
.unwrap();
e_attr_invalid_may
.add_ava(String::from("syntax"), String::from("UTF8STRING"))
.unwrap();
// This is the invalid one
e_attr_invalid_may
.add_ava(String::from("zzzz"), String::from("zzzz"))
.unwrap();
assert_eq!(
schema.validate_entry(&e_attr_invalid_may),
Err(SchemaError::INVALID_ATTRIBUTE)
);
let mut e_attr_invalid_syn: Entry = Entry::new();
e_attr_invalid_syn
.add_ava(String::from("class"), String::from("attributetype"))
.unwrap();
e_attr_invalid_syn
.add_ava(String::from("name"), String::from("testattr"))
.unwrap();
e_attr_invalid_syn
.add_ava(String::from("system"), String::from("false"))
.unwrap();
e_attr_invalid_syn
.add_ava(String::from("secret"), String::from("false"))
.unwrap();
// This is the invalid one
e_attr_invalid_syn
.add_ava(String::from("multivalue"), String::from("zzzz"))
.unwrap();
e_attr_invalid_syn
.add_ava(String::from("syntax"), String::from("UTF8STRING"))
.unwrap();
assert_eq!(
schema.validate_entry(&e_attr_invalid_syn),
Err(SchemaError::INVALID_ATTRIBUTE_SYNTAX)
);
let mut e_ok: Entry = Entry::new();
e_ok.add_ava(String::from("class"), String::from("attributetype"))
.unwrap();
e_ok.add_ava(String::from("name"), String::from("testattr"))
.unwrap();
e_ok.add_ava(String::from("system"), String::from("false"))
.unwrap();
e_ok.add_ava(String::from("secret"), String::from("false"))
.unwrap();
e_ok.add_ava(String::from("multivalue"), String::from("true"))
.unwrap();
e_ok.add_ava(String::from("syntax"), String::from("UTF8STRING"))
.unwrap();
assert_eq!(schema.validate_entry(&e_ok), Ok(()));
}
#[test]
fn test_schema_extensible() {
let schema = Schema::new();
// Just because you are extensible, doesn't mean you can be lazy
let mut e_extensible_bad: Entry = Entry::new();
e_extensible_bad
.add_ava(String::from("class"), String::from("extensibleobject"))
.unwrap();
// Secret is a boolean type
e_extensible_bad
.add_ava(String::from("secret"), String::from("zzzz"))
.unwrap();
assert_eq!(
schema.validate_entry(&e_extensible_bad),
Err(SchemaError::INVALID_ATTRIBUTE_SYNTAX)
);
let mut e_extensible: Entry = Entry::new();
e_extensible
.add_ava(String::from("class"), String::from("extensibleobject"))
.unwrap();
e_extensible
.add_ava(String::from("zzzz"), String::from("zzzz"))
.unwrap();
/* Is okay because extensible! */
assert_eq!(schema.validate_entry(&e_extensible), Ok(()));
}
#[test]
fn test_schema_custom() {
// Validate custom schema entries
}
#[test]
fn test_schema_loading() {
// Validate loading schema from entries
}
}