Nested fields in optional fields should not be required (#4719)
This commit is contained in:
parent
2eff350c19
commit
6735ea96d8
3 changed files with 163 additions and 97 deletions
|
@ -6,7 +6,7 @@ use crate::err::Error;
|
|||
use crate::iam::Action;
|
||||
use crate::sql::permission::Permission;
|
||||
use crate::sql::value::Value;
|
||||
use crate::sql::Kind;
|
||||
use crate::sql::{Idiom, Kind};
|
||||
use reblessive::tree::Stk;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -26,8 +26,23 @@ impl Document {
|
|||
let rid = self.id.as_ref().unwrap();
|
||||
// Get the user applied input
|
||||
let inp = self.initial.doc.as_ref().changed(self.current.doc.as_ref());
|
||||
// If set, the loop will skip certain clauses as long
|
||||
// as the field name starts with the set idiom
|
||||
let mut skip: Option<Idiom> = None;
|
||||
// Loop through all field statements
|
||||
for fd in self.fd(ctx, opt).await?.iter() {
|
||||
let skipped = match skip {
|
||||
Some(ref inner) => {
|
||||
let skipped = fd.name.starts_with(inner);
|
||||
if !skipped {
|
||||
skip = None;
|
||||
}
|
||||
|
||||
skipped
|
||||
}
|
||||
None => false,
|
||||
};
|
||||
|
||||
// Loop over each field in document
|
||||
for (k, mut val) in self.current.doc.as_ref().walk(&fd.name).into_iter() {
|
||||
// Get the initial value
|
||||
|
@ -41,6 +56,7 @@ impl Document {
|
|||
thing: rid.to_string(),
|
||||
});
|
||||
}
|
||||
if !skipped {
|
||||
// Get the default value
|
||||
let def = match &fd.default {
|
||||
Some(v) => Some(v),
|
||||
|
@ -132,7 +148,10 @@ impl Document {
|
|||
ctx.add_value("before", old.clone());
|
||||
let ctx = ctx.freeze();
|
||||
// Process the ASSERT clause
|
||||
if !expr.compute(stk, &ctx, opt, Some(&self.current)).await?.is_truthy()
|
||||
if !expr
|
||||
.compute(stk, &ctx, opt, Some(&self.current))
|
||||
.await?
|
||||
.is_truthy()
|
||||
{
|
||||
return Err(Error::FieldValue {
|
||||
thing: rid.to_string(),
|
||||
|
@ -144,6 +163,7 @@ impl Document {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check for a PERMISSIONS clause
|
||||
if opt.check_perms(Action::Edit)? {
|
||||
// Get the permission clause
|
||||
|
@ -184,6 +204,12 @@ impl Document {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !skipped {
|
||||
if matches!(val, Value::None) && matches!(fd.kind, Some(Kind::Option(_))) {
|
||||
skip = Some(fd.name.to_owned());
|
||||
}
|
||||
|
||||
// Set the value of the field
|
||||
match val {
|
||||
Value::None => self.current.doc.to_mut().del(stk, ctx, opt, &k).await?,
|
||||
|
@ -191,6 +217,7 @@ impl Document {
|
|||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// Carry on
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -153,6 +153,10 @@ impl Idiom {
|
|||
self.0.truncate(self.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn starts_with(&self, other: &[Part]) -> bool {
|
||||
self.0.starts_with(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Idiom {
|
||||
|
|
|
@ -281,3 +281,38 @@ async fn strict_typing_none_null() -> Result<(), Error> {
|
|||
//
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn strict_typing_optional_object() -> Result<(), Error> {
|
||||
let sql = "
|
||||
DEFINE TABLE test SCHEMAFULL;
|
||||
DEFINE FIELD obj ON test TYPE option<object>;
|
||||
DEFINE FIELD obj.a ON test TYPE string;
|
||||
|
||||
CREATE ONLY test:1;
|
||||
CREATE ONLY test:2 SET obj = {};
|
||||
CREATE ONLY test:3 SET obj = { a: 'abc' };
|
||||
";
|
||||
let mut t = Test::new(sql).await?;
|
||||
//
|
||||
t.skip_ok(3)?;
|
||||
//
|
||||
t.expect_val(
|
||||
"{
|
||||
id: test:1,
|
||||
}",
|
||||
)?;
|
||||
//
|
||||
t.expect_error("Found NONE for field `obj.a`, with record `test:2`, but expected a string")?;
|
||||
//
|
||||
t.expect_val(
|
||||
"{
|
||||
id: test:3,
|
||||
obj: {
|
||||
a: 'abc',
|
||||
},
|
||||
}",
|
||||
)?;
|
||||
//
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue