diff --git a/src/lib/be/mod.rs b/src/lib/be/mod.rs index eefcaa26b..edebf3aa7 100644 --- a/src/lib/be/mod.rs +++ b/src/lib/be/mod.rs @@ -156,7 +156,7 @@ impl Drop for BackendTransaction { impl BackendTransaction { pub fn new(conn: r2d2::PooledConnection) -> Self { // Start the transaction - println!("Starting txn ..."); + println!("Starting RO txn ..."); // TODO: Way to flag that this will be a read? conn.execute("BEGIN TRANSACTION", NO_PARAMS).unwrap(); BackendTransaction { @@ -196,7 +196,7 @@ impl BackendReadTransaction for BackendWriteTransaction { impl BackendWriteTransaction { pub fn new(conn: r2d2::PooledConnection) -> Self { // Start the transaction - println!("Starting txn ..."); + println!("Starting WR txn ..."); // TODO: Way to flag that this will be a write? conn.execute("BEGIN TRANSACTION", NO_PARAMS).unwrap(); BackendWriteTransaction { @@ -273,8 +273,51 @@ impl BackendWriteTransaction { }) } - pub fn modify() -> Result<(), BackendError> { - unimplemented!() + pub fn modify(&self, au: &mut AuditScope, entries: &Vec) -> Result<(), BackendError> { + if entries.is_empty() { + // TODO: Better error + return Err(BackendError::EmptyRequest); + } + + // Assert the Id's exist on the entry, and serialise them. + let ser_entries: Vec = entries + .iter() + .filter_map(|e| { + match e.id { + Some(id) => { + Some(IdEntry { + id: id, + // TODO: Should we do better than unwrap? + data: serde_json::to_string(&e).unwrap(), + }) + } + None => None + } + }) + .collect(); + + audit_log!(au, "serialising: {:?}", ser_entries); + + // Simple: If the list of id's is not the same as the input list, we are missing id's + // TODO: This check won't be needed once I rebuild the entry state types. + if entries.len() != ser_entries.len() { + return Err(BackendError::EntryMissingId); + } + + // Now, given the list of id's, update them + { + // TODO: ACTUALLY HANDLE THIS ERROR WILLIAM YOU LAZY SHIT. + let mut stmt = self.conn.prepare("UPDATE id2entry SET data = :data WHERE id = :id").unwrap(); + + ser_entries.iter().for_each(|ser_ent| { + stmt.execute_named(&[ + (":id", &ser_ent.id), + (":data", &ser_ent.data), + ]).unwrap(); + }); + } + + Ok(()) } pub fn delete(&self, au: &mut AuditScope, entries: &Vec) -> Result<(), BackendError> { @@ -282,7 +325,6 @@ impl BackendWriteTransaction { if entries.is_empty() { // TODO: Better error - // End the timer return Err(BackendError::EmptyRequest); } @@ -294,12 +336,11 @@ impl BackendWriteTransaction { .collect(); // Simple: If the list of id's is not the same as the input list, we are missing id's + // TODO: This check won't be needed once I rebuild the entry state types. if entries.len() != id_list.len() { return Err(BackendError::EntryMissingId); } - - // Now, given the list of id's, delete them. { // SQL doesn't say if the thing "does or does not exist anymore". As a result, @@ -521,6 +562,19 @@ mod tests { }} } + macro_rules! entry_attr_pres { + ($audit:expr, $be:expr, $ent:expr, $attr:expr) => {{ + let filt = $ent.filter_from_attrs(&vec![String::from("userid")]).unwrap(); + let entries = $be.search($audit, &filt).unwrap(); + match entries.first() { + Some(ent) => { + ent.attribute_pres($attr) + } + None => false + } + }} + } + #[test] fn test_simple_create() { run_test!(|audit: &mut AuditScope, be: &BackendWriteTransaction| { @@ -553,6 +607,44 @@ mod tests { fn test_simple_modify() { run_test!(|audit: &mut AuditScope, be: &BackendWriteTransaction| { audit_log!(audit, "Simple Modify"); + // First create some entries (3?) + let mut e1: Entry = Entry::new(); + e1.add_ava(String::from("userid"), String::from("william")); + + let mut e2: Entry = Entry::new(); + e2.add_ava(String::from("userid"), String::from("alice")); + + assert!(be.create(audit, &vec![e1.clone(), e2.clone()]).is_ok()); + assert!(entry_exists!(audit, be, e1)); + assert!(entry_exists!(audit, be, e2)); + + // You need to now retrieve the entries back out to get the entry id's + let mut results = be.search(audit, &Filter::Pres(String::from("userid"))).unwrap(); + + // Get these out to usable entries. + let mut r1 = results.remove(0); + let mut r2 = results.remove(0); + + // Modify no id (err) + assert!(be.modify(audit, &vec![e1.clone()]).is_err()); + // Modify none + assert!(be.modify(audit, &vec![]).is_err()); + + // Make some changes to r1, r2. + r1.add_ava(String::from("desc"), String::from("modified")); + r2.add_ava(String::from("desc"), String::from("modified")); + + // Modify single + assert!(be.modify(audit, &vec![r1.clone()]).is_ok()); + // Assert no other changes + assert!(entry_attr_pres!(audit, be, r1, "desc")); + assert!(! entry_attr_pres!(audit, be, r2, "desc")); + + // Modify both + assert!(be.modify(audit, &vec![r1.clone(), r2.clone()]).is_ok()); + + assert!(entry_attr_pres!(audit, be, r1, "desc")); + assert!(entry_attr_pres!(audit, be, r2, "desc")); }); } diff --git a/src/lib/entry.rs b/src/lib/entry.rs index 6be905485..71968d6da 100644 --- a/src/lib/entry.rs +++ b/src/lib/entry.rs @@ -4,6 +4,8 @@ use filter::Filter; use std::collections::btree_map::{Iter as BTreeIter, IterMut as BTreeIterMut}; use std::collections::BTreeMap; use std::slice::Iter as SliceIter; +use modify::{Modify, ModifyList}; +use schema::SchemaReadTransaction; // make a trait entry for everything to adhere to? // * How to get indexs out? @@ -94,9 +96,23 @@ impl<'a> Iterator for EntryAvasMut<'a> { } // This is a BE concept, so move it there! + +// 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. + #[derive(Serialize, Deserialize, Debug)] pub struct Entry { pub id: Option, + // Flag if we have been schema checked or not. + // pub schema_validated: bool, attrs: BTreeMap>, } @@ -105,6 +121,9 @@ impl Entry { Entry { // This means NEVER COMMITED id: None, + // TODO: Make this only on cfg(test/debug_assertions) builds. + // ALTERNATE: Convert to a different entry type on validate/normalise? + // CONVERT TO A TYPE attrs: BTreeMap::new(), } } @@ -123,6 +142,7 @@ impl Entry { // 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 already exists, done! Ok(_) => {} Err(idx) => { // This cloning is to fix a borrow issue with the or_insert below. @@ -134,6 +154,29 @@ impl Entry { .or_insert(vec![value]); } + 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); + } + // FIXME: Should this collect from iter instead? /// Overwrite the existing avas. pub fn set_avas(&mut self, attr: String, values: Vec) { @@ -250,6 +293,72 @@ impl Entry { attrs: self.attrs.clone(), } } + + pub fn gen_modlist_assert(&self, schema: &SchemaReadTransaction) -> Result + { + // 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 { + // 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(), + } + } } impl Clone for Entry { @@ -318,6 +427,7 @@ struct User { #[cfg(test)] mod tests { use super::{Entry, User}; + use modify::{Modify, ModifyList}; use serde_json; #[test] @@ -349,7 +459,6 @@ mod tests { #[test] fn test_entry_pres() { let mut e: Entry = Entry::new(); - e.add_ava(String::from("userid"), String::from("william")); assert!(e.attribute_pres("userid")); @@ -367,4 +476,24 @@ mod tests { assert!(!e.attribute_equality("nonexist", "william")); } + #[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 + } } diff --git a/src/lib/error.rs b/src/lib/error.rs index 5585ee83c..8d8b3b9fe 100644 --- a/src/lib/error.rs +++ b/src/lib/error.rs @@ -14,6 +14,7 @@ pub enum SchemaError { pub enum OperationError { EmptyRequest, Backend, + NoMatchingEntries, SchemaViolation, Plugin, FilterGeneration, diff --git a/src/lib/event.rs b/src/lib/event.rs index 533c5bb02..59974dfe0 100644 --- a/src/lib/event.rs +++ b/src/lib/event.rs @@ -4,6 +4,7 @@ use super::proto_v1::{CreateRequest, Response, SearchRequest, SearchResponse}; use actix::prelude::*; use entry::Entry; use error::OperationError; +use modify::ModifyList; // Should the event Result have the log items? // FIXME: Remove seralising here - each type should @@ -70,6 +71,14 @@ impl SearchEvent { class: (), } } + + pub fn new_internal(filter: Filter) -> Self { + SearchEvent { + internal: true, + filter: filter, + class: (), + } + } } // Represents the decoded entries from the protocol -> internal entry representation @@ -156,3 +165,24 @@ impl DeleteEvent { } } +#[derive(Debug)] +pub struct ModifyEvent { + pub filter: Filter, + pub modlist: ModifyList, + pub internal: bool, +} + +impl Message for ModifyEvent { + type Result = Result; +} + +impl ModifyEvent { + pub fn new_internal(filter: Filter, modlist: ModifyList) -> Self { + ModifyEvent { + filter: filter, + modlist: modlist, + internal: true, + } + } +} + diff --git a/src/lib/filter.rs b/src/lib/filter.rs index b07a4e3e1..2d021c9a9 100644 --- a/src/lib/filter.rs +++ b/src/lib/filter.rs @@ -20,6 +20,10 @@ pub enum Filter { Not(Box), } +// Change this so you have RawFilter and Filter. RawFilter is the "builder", and then +// given a "schema" you can emit a Filter. For us internally, we can create Filter +// directly still ... + impl Filter { // Does this need mut self? Aren't we returning // a new copied filter? diff --git a/src/lib/lib.rs b/src/lib/lib.rs index d78d308e1..05a1b6c06 100644 --- a/src/lib/lib.rs +++ b/src/lib/lib.rs @@ -42,6 +42,7 @@ mod identity; mod plugins; mod schema; mod server; +mod modify; pub mod config; pub mod core; diff --git a/src/lib/plugins/base.rs b/src/lib/plugins/base.rs index 4bbf2ffad..720e9f959 100644 --- a/src/lib/plugins/base.rs +++ b/src/lib/plugins/base.rs @@ -20,7 +20,7 @@ pub struct Base {} impl Plugin for Base { fn id() -> &'static str { - "Base" + "plugin_base" } // Need to be given the backend(for testing ease) // audit diff --git a/src/lib/schema.rs b/src/lib/schema.rs index c685f1892..b6e6ee6cf 100644 --- a/src/lib/schema.rs +++ b/src/lib/schema.rs @@ -9,13 +9,12 @@ use regex::Regex; use std::convert::TryFrom; use std::str::FromStr; use uuid::Uuid; +use modify::ModifyList; use concread::cowcell::{CowCell, CowCellReadTxn, CowCellWriteTxn}; // representations of schema that confines object types, classes // and attributes. This ties in deeply with "Entry". -// This only defines the types, and how they are represented. For -// application and validation of the schema, see "Entry". // // In the future this will parse/read it's schema from the db // but we have to bootstrap with some core types. @@ -32,6 +31,7 @@ use concread::cowcell::{CowCell, CowCellReadTxn, CowCellWriteTxn}; // probably just protection from delete and modify, except systemmay/systemmust/index? // TODO: Schema types -> Entry conversion +// TODO: Entry -> Schema given class. This is for loading from the db. // TODO: prefix on all schema types that are system? @@ -300,6 +300,34 @@ pub struct SchemaInner { attributes: HashMap, } +pub trait SchemaReadTransaction { + fn get_inner(&self) -> &SchemaInner; + + fn validate(&self, audit: &mut AuditScope) -> Result<(), ()> { + self.get_inner().validate(audit) + } + + fn validate_entry(&self, entry: &Entry) -> Result<(), SchemaError> { + self.get_inner().validate_entry(entry) + } + + fn validate_filter(&self, filt: &Filter) -> Result<(), SchemaError> { + self.get_inner().validate_filter(filt) + } + + fn normalise_entry(&self, entry: &Entry) -> Entry { + self.get_inner().normalise_entry(entry) + } + + fn normalise_modlist(&self, modlist: &ModifyList) -> ModifyList { + unimplemented!() + } + + fn is_multivalue(&self, attr: &str) -> Result { + self.get_inner().is_multivalue(attr) + } +} + impl SchemaInner { pub fn new(audit: &mut AuditScope) -> Result { let mut au = AuditScope::new("schema_new"); @@ -941,13 +969,13 @@ impl SchemaInner { Ok(()) } - // pub fn normalise_entry(&mut self, entry: &mut Entry) -> Result<(), SchemaError> { + // TODO: Restructure this when we change entry lifecycle types. pub fn normalise_entry(&self, entry: &Entry) -> Entry { // We duplicate the entry here, because we can't // modify what we got on the protocol level. It also // lets us extend and change things. - let mut entry_new: Entry = Entry::new(); + let mut entry_new: Entry = entry.clone_no_attrs(); // Better hope we have the attribute type ... let schema_attr_name = self.attributes.get("name").unwrap(); // For each ava @@ -972,7 +1000,10 @@ impl SchemaInner { // now push those to the new entry. entry_new.set_avas(attr_name_normal, avas_normal); } + // Mark it is valid + // entry_new.schema_validated = true; // Done! + // TODO: Convert the entry type here to a validated type. entry_new } @@ -1035,6 +1066,17 @@ impl SchemaInner { pub fn normalise_filter(&mut self) { unimplemented!() } + + fn is_multivalue(&self, attr_name: &str) -> Result { + match self.attributes.get(attr_name) { + Some(a_schema) => { + Ok(a_schema.multivalue) + } + None => { + return Err(SchemaError::InvalidAttribute); + } + } + } } // type Schema = CowCell; @@ -1052,14 +1094,6 @@ impl<'a> SchemaWriteTransaction<'a> { self.inner.bootstrap_core(audit) } - pub fn validate_entry(&self, entry: &Entry) -> Result<(), SchemaError> { - self.inner.validate_entry(entry) - } - - pub fn normalise_entry(&self, entry: &Entry) -> Entry { - self.inner.normalise_entry(entry) - } - // TODO: 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 @@ -1068,13 +1102,12 @@ impl<'a> SchemaWriteTransaction<'a> { pub fn commit(self) { self.inner.commit(); } +} - pub fn validate_filter(&self, filt: &Filter) -> Result<(), SchemaError> { - self.inner.validate_filter(filt) - } - - pub fn validate(&self, audit: &mut AuditScope) -> Result<(), ()> { - self.inner.validate(audit) +impl<'a> SchemaReadTransaction for SchemaWriteTransaction<'a> { + fn get_inner(&self) -> &SchemaInner { + // Does this deref the CowCell for us? + &self.inner } } @@ -1082,17 +1115,10 @@ pub struct SchemaTransaction { inner: CowCellReadTxn, } -impl SchemaTransaction { - pub fn validate(&self, audit: &mut AuditScope) -> Result<(), ()> { - self.inner.validate(audit) - } - - pub fn validate_entry(&self, entry: &Entry) -> Result<(), SchemaError> { - self.inner.validate_entry(entry) - } - - pub fn validate_filter(&self, filt: &Filter) -> Result<(), SchemaError> { - self.inner.validate_filter(filt) +impl SchemaReadTransaction for SchemaTransaction { + fn get_inner(&self) -> &SchemaInner { + // Does this deref the CowCell for us? + &self.inner } } @@ -1124,6 +1150,7 @@ mod tests { use super::super::error::SchemaError; use super::super::filter::Filter; use super::{IndexType, Schema, SchemaAttribute, SchemaClass, SyntaxType}; + use schema::SchemaReadTransaction; use serde_json; use std::convert::TryFrom; use uuid::Uuid; @@ -1373,10 +1400,11 @@ mod tests { ) .unwrap(); - assert_eq!( - schema.validate_entry(&e_attr_invalid), - Err(SchemaError::MissingMustAttribute(String::from("system"))) - ); + let res = schema.validate_entry(&e_attr_invalid); + assert!(match res { + Err(SchemaError::MissingMustAttribute(_)) => true, + _ => false, + }); let e_attr_invalid_may: Entry = serde_json::from_str( r#"{ diff --git a/src/lib/server.rs b/src/lib/server.rs index 3ac686740..67c9edc3b 100644 --- a/src/lib/server.rs +++ b/src/lib/server.rs @@ -12,11 +12,13 @@ use be::{ use constants::{JSON_ANONYMOUS_V1, JSON_SYSTEM_INFO_V1}; use entry::Entry; use error::OperationError; -use event::{CreateEvent, ExistsEvent, OpResult, SearchEvent, SearchResult, DeleteEvent}; +use event::{CreateEvent, ExistsEvent, OpResult, SearchEvent, SearchResult, DeleteEvent, ModifyEvent}; use filter::Filter; use log::EventLog; use plugins::Plugins; -use schema::{Schema, SchemaTransaction, SchemaWriteTransaction}; +use schema::{Schema, SchemaTransaction, SchemaReadTransaction, SchemaWriteTransaction}; +use modify::ModifyList; + pub fn start(log: actix::Addr, path: &str, threads: usize) -> actix::Addr { let mut audit = AuditScope::new("server_start"); @@ -139,8 +141,13 @@ pub trait QueryServerReadTransaction { res } - fn internal_search(&self, _au: &mut AuditScope, _filter: Filter) -> Result, OperationError> { - unimplemented!() + fn internal_search(&self, audit: &mut AuditScope, filter: Filter) -> Result, OperationError> { + + let mut audit_int = AuditScope::new("internal_search"); + let se = SearchEvent::new_internal(filter); + let res = self.search(&mut audit_int, &se); + audit.append_scope(audit_int); + res } } @@ -313,6 +320,90 @@ impl<'a> QueryServerWriteTransaction<'a> { unimplemented!() } + pub fn modify(&self, au: &mut AuditScope, me: &ModifyEvent) -> Result<(), OperationError> { + // Get the candidates. + // Modify applies a modlist to a filter, so we need to internal search + // then apply. + // WARNING! Check access controls here!!!! + // How can we do the search with the permissions of the caller? + + // TODO: Fix this filter clone .... + // Likely this will be fixed if search takes &filter, and then clone + // to normalise, instead of attempting to mut the filter on norm. + let pre_candidates = match self.internal_search(au, me.filter.clone()) { + Ok(results) => results, + Err(e) => return Err(e), + }; + + if pre_candidates.len() == 0 { + return Err(OperationError::NoMatchingEntries) + }; + + // Clone a set of writeables. + // Apply the modlist -> Remember, we have a set of origs + // and the new modified ents. + let mut candidates: Vec = pre_candidates.iter() + .map(|er| { + er.apply_modlist(&me.modlist).unwrap() + }) + .collect(); + + audit_log!(au, "modify: candidates -> {:?}", candidates); + + // Pre mod plugins + + // Schema validate + let r = candidates.iter().fold(Ok(()), |acc, e| { + if acc.is_ok() { + self.schema + .validate_entry(e) + .map_err(|err| { + audit_log!(au, "Schema Violation: {:?}", err); + OperationError::SchemaViolation + }) + } else { + acc + } + }); + if r.is_err() { + audit_log!(au, "Modify operation failed (schema), {:?}", r); + return r; + } + + // Normalise all the data now it's validated. + // FIXME: This normalisation COPIES everything, which may be + // slow. + let norm_cand: Vec = candidates + .iter() + .map(|e| self.schema.normalise_entry(&e)) + .collect(); + + // Backend Modify + let mut audit_be = AuditScope::new("backend_modify"); + + let res = self + .be_txn + .modify(&mut audit_be, &norm_cand) + .map(|_| ()) + .map_err(|e| match e { + BackendError::EmptyRequest => OperationError::EmptyRequest, + BackendError::EntryMissingId => OperationError::InvalidRequestState, + }); + au.append_scope(audit_be); + + if res.is_err() { + // be_txn is dropped, ie aborted here. + audit_log!(au, "Modify operation failed (backend), {:?}", r); + return res; + } + + // Post Plugins + + // return + audit_log!(au, "Modify operation success"); + res + } + // These are where searches and other actions are actually implemented. This // is the "internal" version, where we define the event as being internal // only, allowing certain plugin by passes etc. @@ -343,6 +434,19 @@ impl<'a> QueryServerWriteTransaction<'a> { res } + pub fn internal_modify( + &self, + audit: &mut AuditScope, + filter: Filter, + modlist: ModifyList + ) -> Result<(), OperationError> { + let mut audit_int = AuditScope::new("internal_modify"); + let me = ModifyEvent::new_internal(filter, modlist); + let res = self.modify(&mut audit_int, &me); + audit.append_scope(audit_int); + res + } + // internal server operation types. // These just wrap the fn create/search etc, but they allow // creating the needed create event with the correct internal flags @@ -370,20 +474,32 @@ impl<'a> QueryServerWriteTransaction<'a> { // few attributes. // // This will extra classes an attributes alone! - let filt = match e.filter_from_attrs(&vec![String::from("name"), String::from("uuid")]) { + let filt = match e.filter_from_attrs(&vec![String::from("uuid")]) { Some(f) => f, None => return Err(OperationError::FilterGeneration), }; - // Does it exist? (TODO: Should be search, not exists ...) - match self.internal_exists(audit, filt) { - Ok(true) => { - // it exists. We need to ensure the content now. - unimplemented!() - } - Ok(false) => { - // It does not exist. Create it. - self.internal_create(audit, vec![e]) + match self.internal_search(audit, filt.clone()) { + Ok(results) => { + if results.len() == 0 { + // It does not exist. Create it. + self.internal_create(audit, vec![e]) + } else if results.len() == 1 { + // If the thing is subset, pass + match e.gen_modlist_assert(&self.schema) { + Ok(modlist) => { + // Apply to &results[0] + audit_log!(audit, "Generated modlist -> {:?}", modlist); + self.internal_modify(audit, filt, modlist) + } + Err(e) => { + unimplemented!() + // No action required. + } + } + } else { + Err(OperationError::InvalidDBState) + } } Err(e) => { // An error occured. pass it back up. @@ -403,7 +519,7 @@ impl<'a> QueryServerWriteTransaction<'a> { // attributes and classes. // Create a filter from the entry for assertion. - let filt = match e.filter_from_attrs(&vec![String::from("name"), String::from("uuid")]) { + let filt = match e.filter_from_attrs(&vec![String::from("uuid")]) { Some(f) => f, None => return Err(OperationError::FilterGeneration), }; @@ -441,6 +557,7 @@ impl<'a> QueryServerWriteTransaction<'a> { let e: Entry = serde_json::from_str(JSON_SYSTEM_INFO_V1).unwrap(); self.internal_assert_or_create(audit, e) }); + audit_log!(audit_si, "start_system_info -> result {:?}", res); audit.append_scope(audit_si); assert!(res.is_ok()); if res.is_err() { @@ -453,6 +570,7 @@ impl<'a> QueryServerWriteTransaction<'a> { let e: Entry = serde_json::from_str(JSON_ANONYMOUS_V1).unwrap(); self.internal_migrate_or_create(audit, e) }); + audit_log!(audit_an, "start_anonymous -> result {:?}", res); audit.append_scope(audit_an); assert!(res.is_ok()); if res.is_err() {