2018-11-11 01:39:11 +01:00
|
|
|
// use serde_json::{Error, Value};
|
2018-11-27 11:48:21 +01:00
|
|
|
use super::proto_v1::Entry as ProtoEntry;
|
2018-12-30 03:17:09 +01:00
|
|
|
use filter::Filter;
|
2018-11-15 04:49:08 +01:00
|
|
|
use std::collections::btree_map::{Iter as BTreeIter, IterMut as BTreeIterMut};
|
2018-10-03 13:21:21 +02:00
|
|
|
use std::collections::BTreeMap;
|
2018-11-13 08:14:26 +01:00
|
|
|
use std::slice::Iter as SliceIter;
|
2019-01-28 04:54:17 +01:00
|
|
|
use modify::{Modify, ModifyList};
|
|
|
|
use schema::SchemaReadTransaction;
|
2018-09-29 09:54:16 +02:00
|
|
|
|
|
|
|
// make a trait entry for everything to adhere to?
|
|
|
|
// * How to get indexs out?
|
|
|
|
// * How to track pending diffs?
|
|
|
|
|
2018-10-03 13:21:21 +02:00
|
|
|
// Entry is really similar to serde Value, but limits the possibility
|
|
|
|
// of what certain types could be.
|
|
|
|
//
|
|
|
|
// The idea of an entry is that we have
|
|
|
|
// an entry that looks like:
|
|
|
|
//
|
|
|
|
// {
|
|
|
|
// 'class': ['object', ...],
|
|
|
|
// 'attr': ['value', ...],
|
|
|
|
// 'attr': ['value', ...],
|
|
|
|
// ...
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// When we send this as a result to clients, we could embed other objects as:
|
|
|
|
//
|
|
|
|
// {
|
|
|
|
// 'attr': [
|
|
|
|
// 'value': {
|
|
|
|
// },
|
|
|
|
// ],
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
|
2018-11-13 08:14:26 +01:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-15 04:49:08 +01:00
|
|
|
pub struct EntryAvasMut<'a> {
|
|
|
|
inner: BTreeIterMut<'a, String, Vec<String>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Iterator for EntryAvasMut<'a> {
|
|
|
|
type Item = (&'a String, &'a mut Vec<String>);
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn next(&mut self) -> Option<(&'a String, &'a mut Vec<String>)> {
|
|
|
|
self.inner.next()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
|
|
self.inner.size_hint()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-22 02:39:56 +01:00
|
|
|
// This is a BE concept, so move it there!
|
2019-01-28 04:54:17 +01:00
|
|
|
|
|
|
|
// Entry should have a lifecycle of types. THis is Raw (modifiable) and Entry (verified).
|
|
|
|
// This way, we can move between them, but only certain actions are possible on either
|
|
|
|
// This means modifications happen on Raw, but to move to Entry, you schema normalise.
|
|
|
|
// Vice versa, you can for free, move to Raw, but you lose the validation.
|
|
|
|
|
|
|
|
// Because this is type system it's "free" in the end, and means we force validation
|
|
|
|
// at the correct and required points of the entries life.
|
|
|
|
|
|
|
|
// This is specifically important for the commit to the backend, as we only want to
|
|
|
|
// commit validated types.
|
|
|
|
|
2018-09-29 09:54:16 +02:00
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct Entry {
|
2019-01-22 02:39:56 +01:00
|
|
|
pub id: Option<i64>,
|
2019-01-28 04:54:17 +01:00
|
|
|
// Flag if we have been schema checked or not.
|
|
|
|
// pub schema_validated: bool,
|
2018-10-03 13:21:21 +02:00
|
|
|
attrs: BTreeMap<String, Vec<String>>,
|
|
|
|
}
|
2018-09-29 09:54:16 +02:00
|
|
|
|
2018-10-03 13:21:21 +02:00
|
|
|
impl Entry {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Entry {
|
2019-01-22 02:39:56 +01:00
|
|
|
// This means NEVER COMMITED
|
|
|
|
id: None,
|
2019-01-28 04:54:17 +01:00
|
|
|
// TODO: Make this only on cfg(test/debug_assertions) builds.
|
|
|
|
// ALTERNATE: Convert to a different entry type on validate/normalise?
|
|
|
|
// CONVERT TO A TYPE
|
2018-11-07 07:35:25 +01:00
|
|
|
attrs: BTreeMap::new(),
|
2018-10-03 13:21:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This should always work? It's only on validate that we'll build
|
|
|
|
// a list of syntax violations ...
|
2018-11-14 02:54:59 +01:00
|
|
|
// If this already exists, we silently drop the event? Is that an
|
|
|
|
// acceptable interface?
|
|
|
|
// Should value here actually be a &str?
|
|
|
|
pub fn add_ava(&mut self, attr: String, value: String) {
|
2018-10-03 13:21:21 +02:00
|
|
|
// get_mut to access value
|
2018-11-14 02:54:59 +01:00
|
|
|
// How do we make this turn into an ok / err?
|
2018-11-07 07:35:25 +01:00
|
|
|
self.attrs
|
|
|
|
.entry(attr)
|
2018-11-14 02:54:59 +01:00
|
|
|
.and_modify(|v| {
|
|
|
|
// Here we need to actually do a check/binary search ...
|
2018-11-20 07:51:10 +01:00
|
|
|
// FIXME: Because map_err is lazy, this won't do anything on release
|
|
|
|
match v.binary_search(&value) {
|
2019-01-28 04:54:17 +01:00
|
|
|
// It already exists, done!
|
2018-11-20 07:51:10 +01:00
|
|
|
Ok(_) => {}
|
|
|
|
Err(idx) => {
|
|
|
|
// This cloning is to fix a borrow issue with the or_insert below.
|
|
|
|
// Is there a better way?
|
|
|
|
v.insert(idx, value.clone())
|
|
|
|
}
|
|
|
|
}
|
2018-11-14 02:54:59 +01:00
|
|
|
})
|
2018-11-07 07:35:25 +01:00
|
|
|
.or_insert(vec![value]);
|
2018-10-03 13:21:21 +02:00
|
|
|
}
|
|
|
|
|
2019-01-28 04:54:17 +01:00
|
|
|
pub fn remove_ava(&mut self, attr: &String, value: &String) {
|
|
|
|
self.attrs
|
|
|
|
// TODO: Fix this clone ...
|
|
|
|
.entry(attr.clone())
|
|
|
|
.and_modify(|v| {
|
|
|
|
// Here we need to actually do a check/binary search ...
|
|
|
|
// FIXME: Because map_err is lazy, this won't do anything on release
|
|
|
|
match v.binary_search(&value) {
|
|
|
|
// It exists, rm it.
|
|
|
|
Ok(idx) => {
|
|
|
|
v.remove(idx);
|
|
|
|
}
|
|
|
|
// It does not exist, move on.
|
|
|
|
Err(_) => {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn purge_ava(&mut self, attr: &String) {
|
|
|
|
self.attrs.remove(attr);
|
|
|
|
}
|
|
|
|
|
2018-11-15 04:49:08 +01:00
|
|
|
// FIXME: Should this collect from iter instead?
|
2018-12-29 03:29:10 +01:00
|
|
|
/// Overwrite the existing avas.
|
|
|
|
pub fn set_avas(&mut self, attr: String, values: Vec<String>) {
|
2018-11-15 04:49:08 +01:00
|
|
|
// Overwrite the existing value
|
|
|
|
let _ = self.attrs.insert(attr, values);
|
|
|
|
}
|
|
|
|
|
2018-11-13 08:14:26 +01:00
|
|
|
pub fn get_ava(&self, attr: &String) -> Option<&Vec<String>> {
|
|
|
|
self.attrs.get(attr)
|
|
|
|
}
|
|
|
|
|
2018-11-14 02:54:59 +01:00
|
|
|
pub fn attribute_pres(&self, attr: &str) -> bool {
|
|
|
|
// FIXME: Do we need to normalise attr name?
|
|
|
|
self.attrs.contains_key(attr)
|
2018-10-03 13:21:21 +02:00
|
|
|
}
|
2018-11-07 07:54:02 +01:00
|
|
|
|
2018-11-14 02:54:59 +01:00
|
|
|
pub fn attribute_equality(&self, attr: &str, value: &str) -> bool {
|
2018-12-29 10:56:03 +01:00
|
|
|
// we assume based on schema normalisation on the way in
|
2018-11-14 02:54:59 +01:00
|
|
|
// that the equality here of the raw values MUST be correct.
|
2018-12-29 10:56:03 +01:00
|
|
|
// We also normalise filters, to ensure that their values are
|
|
|
|
// syntax valid and will correctly match here with our indexes.
|
2018-11-14 02:54:59 +01:00
|
|
|
|
|
|
|
// FIXME: Make this binary_search
|
|
|
|
|
|
|
|
self.attrs.get(attr).map_or(false, |v| {
|
|
|
|
v.iter()
|
|
|
|
.fold(false, |acc, av| if acc { acc } else { value == av })
|
|
|
|
})
|
2018-11-07 07:54:02 +01:00
|
|
|
}
|
2018-11-13 08:14:26 +01:00
|
|
|
|
2018-12-30 03:17:09 +01:00
|
|
|
pub fn attribute_substring(&self, _attr: &str, _subvalue: &str) -> bool {
|
2018-12-29 10:56:03 +01:00
|
|
|
unimplemented!();
|
|
|
|
}
|
|
|
|
|
2018-11-13 08:14:26 +01:00
|
|
|
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 {
|
|
|
|
EntryAvas {
|
|
|
|
inner: self.attrs.iter(),
|
|
|
|
}
|
|
|
|
}
|
2018-11-15 04:49:08 +01:00
|
|
|
|
|
|
|
pub fn avas_mut(&mut self) -> EntryAvasMut {
|
|
|
|
EntryAvasMut {
|
|
|
|
inner: self.attrs.iter_mut(),
|
|
|
|
}
|
|
|
|
}
|
2018-11-27 11:48:21 +01:00
|
|
|
|
2019-01-20 00:51:22 +01:00
|
|
|
pub fn filter_from_attrs(&self, attrs: &Vec<String>) -> Option<Filter> {
|
|
|
|
// Generate a filter from the attributes requested and defined.
|
|
|
|
// Basically, this is a series of nested and's (which will be
|
|
|
|
// optimised down later: but if someone wants to solve flatten() ...)
|
|
|
|
|
|
|
|
// Take name: (a, b), name: (c, d) -> (name, a), (name, b), (name, c), (name, d)
|
|
|
|
|
|
|
|
let mut pairs: Vec<(String, String)> = Vec::new();
|
|
|
|
|
|
|
|
for attr in attrs {
|
|
|
|
match self.attrs.get(attr) {
|
|
|
|
Some(values) => {
|
|
|
|
for v in values {
|
2019-01-20 01:46:17 +01:00
|
|
|
pairs.push((attr.clone(), v.clone()))
|
2019-01-20 00:51:22 +01:00
|
|
|
}
|
|
|
|
}
|
2019-01-20 01:46:17 +01:00
|
|
|
None => return None,
|
2019-01-20 00:51:22 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now make this a filter?
|
|
|
|
|
2019-01-20 01:46:17 +01:00
|
|
|
let eq_filters = pairs
|
|
|
|
.into_iter()
|
|
|
|
.map(|(attr, value)| Filter::Eq(attr, value))
|
2019-01-20 00:51:22 +01:00
|
|
|
.collect();
|
|
|
|
|
|
|
|
Some(Filter::And(eq_filters))
|
2018-12-30 03:17:09 +01:00
|
|
|
}
|
|
|
|
|
2018-11-27 11:48:21 +01:00
|
|
|
// FIXME: Can we consume protoentry?
|
|
|
|
pub fn from(e: &ProtoEntry) -> Self {
|
|
|
|
// Why not the trait? In the future we may want to extend
|
|
|
|
// this with server aware functions for changes of the
|
|
|
|
// incoming data.
|
|
|
|
Entry {
|
2018-12-29 13:05:54 +01:00
|
|
|
// For now, we do a straight move, and we sort the incoming data
|
|
|
|
// sets so that BST works.
|
2019-01-22 02:39:56 +01:00
|
|
|
id: None,
|
2018-12-29 13:06:15 +01:00
|
|
|
attrs: e
|
|
|
|
.attrs
|
|
|
|
.iter()
|
2018-12-29 13:05:54 +01:00
|
|
|
.map(|(k, v)| {
|
|
|
|
let mut nv = v.clone();
|
|
|
|
nv.sort_unstable();
|
2018-12-29 13:06:15 +01:00
|
|
|
(k.clone(), nv)
|
|
|
|
})
|
|
|
|
.collect(),
|
2018-11-27 11:48:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn into(&self) -> ProtoEntry {
|
|
|
|
// It's very likely that at this stage we'll need to apply
|
|
|
|
// access controls, dynamic attributes or more.
|
|
|
|
// As a result, this may not even be the right place
|
|
|
|
// for the conversion as algorithmically it may be
|
|
|
|
// better to do this from the outside view. This can
|
|
|
|
// of course be identified and changed ...
|
|
|
|
ProtoEntry {
|
|
|
|
attrs: self.attrs.clone(),
|
|
|
|
}
|
|
|
|
}
|
2019-01-28 04:54:17 +01:00
|
|
|
|
|
|
|
pub fn gen_modlist_assert(&self, schema: &SchemaReadTransaction) -> Result<ModifyList, ()>
|
|
|
|
{
|
|
|
|
// Create a modlist from this entry. We make this assuming we want the entry
|
|
|
|
// to have this one as a subset of values. This means if we have single
|
|
|
|
// values, we'll replace, if they are multivalue, we present them.
|
|
|
|
//
|
|
|
|
// We assume the schema validaty of the entry is already checked, and
|
|
|
|
// normalisation performed.
|
|
|
|
let mut mods = ModifyList::new();
|
|
|
|
|
|
|
|
for (k, vs) in self.attrs.iter() {
|
|
|
|
// Get the schema attribute type out.
|
|
|
|
match schema.is_multivalue(k) {
|
|
|
|
Ok(r) => {
|
|
|
|
if !r {
|
|
|
|
// As this is single value, purge then present to maintain this
|
|
|
|
// invariant
|
|
|
|
mods.push_mod(Modify::Purged(k.clone()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
return Err(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for v in vs {
|
|
|
|
mods.push_mod(Modify::Present(k.clone(), v.clone()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(mods)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should this be schemaless, relying on checks of the modlist, and the entry validate after?
|
|
|
|
pub fn apply_modlist(&self, modlist: &ModifyList) -> Result<Entry, ()> {
|
|
|
|
// Apply a modlist, generating a new entry that conforms to the changes.
|
|
|
|
// This is effectively clone-and-transform
|
|
|
|
|
|
|
|
// clone the entry
|
|
|
|
let mut ne = self.clone();
|
|
|
|
|
|
|
|
// mutate
|
|
|
|
for modify in modlist.mods.iter() {
|
|
|
|
match modify {
|
|
|
|
Modify::Present(a, v) => {
|
|
|
|
ne.add_ava(a.clone(), v.clone())
|
|
|
|
}
|
|
|
|
Modify::Removed(a, v) => {
|
|
|
|
ne.remove_ava(a, v)
|
|
|
|
}
|
|
|
|
Modify::Purged(a) => {
|
|
|
|
ne.purge_ava(a)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// return it
|
|
|
|
Ok(ne)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn clone_no_attrs(&self) -> Entry {
|
|
|
|
Entry {
|
|
|
|
id: self.id,
|
|
|
|
attrs: BTreeMap::new(),
|
|
|
|
}
|
|
|
|
}
|
2018-09-29 09:54:16 +02:00
|
|
|
}
|
|
|
|
|
2018-11-11 01:39:11 +01:00
|
|
|
impl Clone for Entry {
|
|
|
|
fn clone(&self) -> Entry {
|
|
|
|
Entry {
|
2019-01-22 02:39:56 +01:00
|
|
|
id: self.id,
|
2018-11-11 01:39:11 +01:00
|
|
|
attrs: self.attrs.clone(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialEq for Entry {
|
|
|
|
fn eq(&self, rhs: &Entry) -> bool {
|
|
|
|
// FIXME: This is naive. Later it should be schema
|
|
|
|
// aware checking.
|
|
|
|
self.attrs == rhs.attrs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-29 09:54:16 +02:00
|
|
|
// pub trait Entry {
|
2018-10-03 13:21:21 +02:00
|
|
|
//fn to_json_str(&self) -> String;
|
|
|
|
// fn to_index_diff -> ???
|
|
|
|
// from_json_str() -> Self;
|
|
|
|
//
|
|
|
|
// Does this match a filter or not?a
|
|
|
|
// fn apply_filter -> Result<bool, ()>
|
2018-09-29 09:54:16 +02:00
|
|
|
// }
|
|
|
|
|
|
|
|
//enum Credential {
|
2018-10-03 13:21:21 +02:00
|
|
|
//?
|
2018-09-29 09:54:16 +02:00
|
|
|
//}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
enum Credential {
|
|
|
|
Password {
|
|
|
|
name: String,
|
|
|
|
hash: String,
|
|
|
|
},
|
|
|
|
TOTPPassword {
|
|
|
|
name: String,
|
|
|
|
hash: String,
|
|
|
|
totp_secret: String,
|
|
|
|
},
|
|
|
|
SshPublicKey {
|
|
|
|
name: String,
|
|
|
|
data: String,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
struct User {
|
|
|
|
username: String,
|
|
|
|
// Could this be derived from self? Do we even need schema?
|
|
|
|
class: Vec<String>,
|
|
|
|
displayname: String,
|
|
|
|
legalname: Option<String>,
|
|
|
|
email: Vec<String>,
|
|
|
|
// uuid?
|
|
|
|
// need to support deref later ...
|
|
|
|
memberof: Vec<String>,
|
|
|
|
sshpublickey: Vec<String>,
|
|
|
|
|
|
|
|
credentials: Vec<Credential>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2018-11-07 07:35:25 +01:00
|
|
|
use super::{Entry, User};
|
2019-01-28 04:54:17 +01:00
|
|
|
use modify::{Modify, ModifyList};
|
2018-09-29 09:54:16 +02:00
|
|
|
use serde_json;
|
|
|
|
|
2018-10-03 13:21:21 +02:00
|
|
|
#[test]
|
|
|
|
fn test_entry_basic() {
|
|
|
|
let mut e: Entry = Entry::new();
|
2018-09-29 09:54:16 +02:00
|
|
|
|
2018-11-14 02:54:59 +01:00
|
|
|
e.add_ava(String::from("userid"), String::from("william"));
|
2018-10-03 13:21:21 +02:00
|
|
|
|
2018-11-20 07:51:10 +01:00
|
|
|
let _d = serde_json::to_string_pretty(&e).unwrap();
|
2018-11-14 02:54:59 +01:00
|
|
|
}
|
2018-10-03 13:21:21 +02:00
|
|
|
|
2018-11-14 02:54:59 +01:00
|
|
|
#[test]
|
|
|
|
fn test_entry_dup_value() {
|
|
|
|
// Schema doesn't matter here because we are duplicating a value
|
|
|
|
// it should fail!
|
|
|
|
|
|
|
|
// We still probably need schema here anyway to validate what we
|
|
|
|
// are adding ... Or do we validate after the changes are made in
|
|
|
|
// total?
|
|
|
|
let mut e: Entry = Entry::new();
|
|
|
|
e.add_ava(String::from("userid"), String::from("william"));
|
|
|
|
e.add_ava(String::from("userid"), String::from("william"));
|
|
|
|
|
|
|
|
let values = e.get_ava(&String::from("userid")).unwrap();
|
|
|
|
// Should only be one value!
|
|
|
|
assert_eq!(values.len(), 1)
|
2018-10-03 13:21:21 +02:00
|
|
|
}
|
2018-11-07 07:54:02 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_entry_pres() {
|
|
|
|
let mut e: Entry = Entry::new();
|
2018-11-14 02:54:59 +01:00
|
|
|
e.add_ava(String::from("userid"), String::from("william"));
|
|
|
|
|
|
|
|
assert!(e.attribute_pres("userid"));
|
|
|
|
assert!(!e.attribute_pres("name"));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_entry_equality() {
|
|
|
|
let mut e: Entry = Entry::new();
|
2018-11-07 07:54:02 +01:00
|
|
|
|
2018-11-14 02:54:59 +01:00
|
|
|
e.add_ava(String::from("userid"), String::from("william"));
|
2018-11-07 07:54:02 +01:00
|
|
|
|
2018-11-14 02:54:59 +01:00
|
|
|
assert!(e.attribute_equality("userid", "william"));
|
|
|
|
assert!(!e.attribute_equality("userid", "test"));
|
|
|
|
assert!(!e.attribute_equality("nonexist", "william"));
|
2018-11-07 07:54:02 +01:00
|
|
|
}
|
2019-01-21 03:08:56 +01:00
|
|
|
|
2019-01-28 04:54:17 +01:00
|
|
|
#[test]
|
|
|
|
fn test_entry_apply_modlist() {
|
|
|
|
// Test application of changes to an entry.
|
|
|
|
let mut e: Entry = Entry::new();
|
|
|
|
e.add_ava(String::from("userid"), String::from("william"));
|
|
|
|
|
|
|
|
let mods = ModifyList::new_list(vec![
|
|
|
|
Modify::Present(String::from("attr"), String::from("value")),
|
|
|
|
]);
|
|
|
|
|
|
|
|
let ne = e.apply_modlist(&mods).unwrap();
|
|
|
|
|
|
|
|
// Assert the changes are there
|
|
|
|
assert!(ne.attribute_equality("attr", "value"));
|
|
|
|
|
|
|
|
|
|
|
|
// Assert present for multivalue
|
|
|
|
// Assert purge on single/multi/empty value
|
|
|
|
// Assert removed on value that exists and doesn't exist
|
|
|
|
}
|
2018-10-03 13:21:21 +02:00
|
|
|
}
|