diff --git a/lib/src/dbs/auth.rs b/lib/src/dbs/auth.rs index a2097c8e..51ef1d03 100644 --- a/lib/src/dbs/auth.rs +++ b/lib/src/dbs/auth.rs @@ -23,6 +23,15 @@ impl Default for Auth { } impl Auth { + pub fn perms(&self) -> bool { + match self { + Auth::No => true, + Auth::Sc(_, _, _) => true, + Auth::Db(_, _) => false, + Auth::Ns(_) => false, + Auth::Kv => false, + } + } pub fn check(&self, level: Level) -> bool { match self { Auth::No => matches!(level, Level::No), diff --git a/lib/src/doc/create.rs b/lib/src/doc/create.rs index 77139264..62662027 100644 --- a/lib/src/doc/create.rs +++ b/lib/src/doc/create.rs @@ -20,6 +20,8 @@ impl<'a> Document<'a> { self.exist(ctx, opt, txn, stm).await?; // Merge record data self.merge(ctx, opt, txn, stm).await?; + // Merge fields data + self.field(ctx, opt, txn, stm).await?; // Check if allowed self.allow(ctx, opt, txn, stm).await?; // Store index data diff --git a/lib/src/doc/field.rs b/lib/src/doc/field.rs new file mode 100644 index 00000000..c02d7388 --- /dev/null +++ b/lib/src/doc/field.rs @@ -0,0 +1,124 @@ +use crate::ctx::Context; +use crate::dbs::Options; +use crate::dbs::Runtime; +use crate::dbs::Statement; +use crate::dbs::Transaction; +use crate::doc::Document; +use crate::err::Error; +use crate::sql::kind::Kind; +use crate::sql::permission::Permission; +use crate::sql::value::Value; + +impl<'a> Document<'a> { + pub async fn field( + &mut self, + ctx: &Runtime, + opt: &Options, + txn: &Transaction, + _stm: &Statement, + ) -> Result<(), Error> { + // Get the record id + let rid = self.id.as_ref().unwrap(); + // Get all field statements + let fds = txn.clone().lock().await.all_fd(opt.ns(), opt.db(), &rid.tb).await?; + // Loop through all field statements + for fd in fds.iter() { + // Get the initial value + let old = self.initial.get(ctx, opt, txn, &fd.name).await?; + // Get the current value + let mut val = self.current.get(ctx, opt, txn, &fd.name).await?; + // Check for a VALUE clause + if let Some(expr) = &fd.value { + // Configure the context + let mut ctx = Context::new(ctx); + ctx.add_value("value".into(), val.clone()); + ctx.add_value("after".into(), val.clone()); + ctx.add_value("before".into(), old.clone()); + let ctx = ctx.freeze(); + // Process the VALUE clause + val = expr.compute(&ctx, opt, txn, Some(&self.current)).await?; + } + // Check for a TYPE clause + if let Some(kind) = &fd.kind { + val = match kind { + Kind::Any => val, + Kind::Bool => val.make_bool(), + Kind::Int => val.make_int(), + Kind::Float => val.make_float(), + Kind::Decimal => val.make_decimal(), + Kind::Number => val.make_number(), + Kind::String => val.make_strand(), + Kind::Datetime => val.make_datetime(), + Kind::Duration => val.make_duration(), + Kind::Array => match val { + Value::Array(_) => val, + _ => Value::None, + }, + Kind::Object => match val { + Value::Object(_) => val, + _ => Value::None, + }, + Kind::Record(t) => match val.is_type_record(t) { + true => val, + _ => Value::None, + }, + Kind::Geometry(t) => match val.is_type_geometry(t) { + true => val, + _ => Value::None, + }, + } + } + // Check for a ASSERT clause + if let Some(expr) = &fd.assert { + // Configure the context + let mut ctx = Context::new(ctx); + ctx.add_value("value".into(), val.clone()); + ctx.add_value("after".into(), val.clone()); + ctx.add_value("before".into(), old.clone()); + let ctx = ctx.freeze(); + // Process the ASSERT clause + if !expr.compute(&ctx, opt, txn, Some(&self.current)).await?.is_truthy() { + return Err(Error::FieldValue { + value: val.clone(), + field: fd.name.clone(), + check: expr.clone(), + }); + } + } + // Check for a PERMISSIONS clause + if opt.perms && opt.auth.perms() { + let perms = if self.initial.is_none() { + &fd.permissions.create + } else if self.current.is_none() { + &fd.permissions.delete + } else { + &fd.permissions.update + }; + match perms { + Permission::Full => (), + Permission::None => val = old, + Permission::Specific(e) => { + // Configure the context + let mut ctx = Context::new(ctx); + ctx.add_value("value".into(), val.clone()); + ctx.add_value("after".into(), val.clone()); + ctx.add_value("before".into(), old.clone()); + let ctx = ctx.freeze(); + // Process the PERMISSION clause + if !e.compute(&ctx, opt, txn, Some(&self.current)).await?.is_truthy() { + val = old + } + } + } + } + // Set the value of the field + match val { + Value::None => self.current.to_mut().del(ctx, opt, txn, &fd.name).await?, + Value::Void => self.current.to_mut().del(ctx, opt, txn, &fd.name).await?, + _ => self.current.to_mut().set(ctx, opt, txn, &fd.name, val).await?, + }; + } + // Carry on + Ok(()) + } +} diff --git a/lib/src/doc/mod.rs b/lib/src/doc/mod.rs index 23a0023f..42ea168b 100644 --- a/lib/src/doc/mod.rs +++ b/lib/src/doc/mod.rs @@ -13,6 +13,7 @@ mod empty; mod erase; mod event; mod exist; +mod field; mod grant; mod index; mod insert; diff --git a/lib/src/doc/relate.rs b/lib/src/doc/relate.rs index 1affcd9e..30c1eefc 100644 --- a/lib/src/doc/relate.rs +++ b/lib/src/doc/relate.rs @@ -20,6 +20,8 @@ impl<'a> Document<'a> { self.allow(ctx, opt, txn, stm).await?; // Merge record data self.merge(ctx, opt, txn, stm).await?; + // Merge fields data + self.field(ctx, opt, txn, stm).await?; // Check if allowed self.allow(ctx, opt, txn, stm).await?; // Store index data diff --git a/lib/src/doc/update.rs b/lib/src/doc/update.rs index 20299cd5..caabcb05 100644 --- a/lib/src/doc/update.rs +++ b/lib/src/doc/update.rs @@ -22,6 +22,8 @@ impl<'a> Document<'a> { self.allow(ctx, opt, txn, stm).await?; // Merge record data self.merge(ctx, opt, txn, stm).await?; + // Merge fields data + self.field(ctx, opt, txn, stm).await?; // Check if allowed self.allow(ctx, opt, txn, stm).await?; // Store index data diff --git a/lib/src/err/mod.rs b/lib/src/err/mod.rs index b7537777..eb67e5c6 100644 --- a/lib/src/err/mod.rs +++ b/lib/src/err/mod.rs @@ -1,3 +1,4 @@ +use crate::sql::idiom::Idiom; use crate::sql::thing::Thing; use crate::sql::value::Value; use msgpack::encode::Error as SerdeError; @@ -173,6 +174,13 @@ pub enum Error { thing: Thing, }, + #[error("Found '{value}' for field '{field}' but field must conform to: {check}")] + FieldValue { + value: Value, + field: Idiom, + check: Value, + }, + #[error("Serde error: {0}")] Serde(#[from] SerdeError), diff --git a/lib/src/sql/value/value.rs b/lib/src/sql/value/value.rs index 3b67cba6..f051ad5c 100644 --- a/lib/src/sql/value/value.rs +++ b/lib/src/sql/value/value.rs @@ -507,6 +507,40 @@ impl Value { } } + pub fn is_type_record(&self, types: &[Table]) -> bool { + match self { + Value::Thing(v) => types.iter().any(|t| t.name == v.tb), + _ => false, + } + } + + pub fn is_type_geometry(&self, types: &[String]) -> bool { + match self { + Value::Geometry(Geometry::Point(_)) => { + types.iter().any(|t| &t[..] == "feature" || &t[..] == "point") + } + Value::Geometry(Geometry::Line(_)) => { + types.iter().any(|t| &t[..] == "feature" || &t[..] == "line") + } + Value::Geometry(Geometry::Polygon(_)) => { + types.iter().any(|t| &t[..] == "feature" || &t[..] == "polygon") + } + Value::Geometry(Geometry::MultiPoint(_)) => { + types.iter().any(|t| &t[..] == "feature" || &t[..] == "multipoint") + } + Value::Geometry(Geometry::MultiLine(_)) => { + types.iter().any(|t| &t[..] == "feature" || &t[..] == "multiline") + } + Value::Geometry(Geometry::MultiPolygon(_)) => { + types.iter().any(|t| &t[..] == "feature" || &t[..] == "multipolygon") + } + Value::Geometry(Geometry::Collection(_)) => { + types.iter().any(|t| &t[..] == "feature" || &t[..] == "collection") + } + _ => false, + } + } + // ----------------------------------- // Simple conversion of value // -----------------------------------