Compare commits

..

4 Commits

Author SHA1 Message Date
628471e5d5 version: 1.0.130 2026-04-24 10:48:51 -04:00
0093aea790 fixed nested filters 2026-04-24 10:48:38 -04:00
45ebc57e0c version: 1.0.129 2026-04-21 11:54:03 -04:00
00319b570b fixed $ validation in schema ids 2026-04-21 11:53:51 -04:00
4 changed files with 124 additions and 60 deletions

View File

@ -172,53 +172,69 @@
"schemas": { "schemas": {
"person": {}, "person": {},
"person.filter": { "person.filter": {
"type": "object",
"compiledPropertyNames": [ "compiledPropertyNames": [
"$and", "$and",
"$or", "$or",
"ad_hoc",
"age", "age",
"billing_address", "billing_address",
"birth_date", "birth_date",
"first_name" "first_name",
"tags"
], ],
"properties": { "properties": {
"$and": { "$and": {
"type": [
"array",
"null"
],
"items": { "items": {
"compiledPropertyNames": [ "compiledPropertyNames": [
"$and", "$and",
"$or", "$or",
"ad_hoc",
"age", "age",
"billing_address", "billing_address",
"birth_date", "birth_date",
"first_name" "first_name",
"tags"
], ],
"type": "person.filter" "type": "person.filter"
} },
"type": [
"array",
"null"
]
}, },
"$or": { "$or": {
"type": [
"array",
"null"
],
"items": { "items": {
"compiledPropertyNames": [ "compiledPropertyNames": [
"$and", "$and",
"$or", "$or",
"ad_hoc",
"age", "age",
"billing_address", "billing_address",
"birth_date", "birth_date",
"first_name" "first_name",
"tags"
], ],
"type": "person.filter" "type": "person.filter"
} },
},
"first_name": {
"type": [ "type": [
"string.condition", "array",
"null"
]
},
"ad_hoc": {
"compiledPropertyNames": [
"foo"
],
"properties": {
"foo": {
"type": [
"string.condition",
"null"
]
}
},
"type": [
"object",
"null" "null"
] ]
}, },
@ -239,8 +255,21 @@
"date.condition", "date.condition",
"null" "null"
] ]
},
"first_name": {
"type": [
"string.condition",
"null"
]
},
"tags": {
"type": [
"string.condition",
"null"
]
} }
} },
"type": "object"
}, },
"address": {}, "address": {},
"address.filter": { "address.filter": {

View File

@ -12,11 +12,11 @@ impl Schema {
) { ) {
#[cfg(not(test))] #[cfg(not(test))]
for c in id.chars() { for c in id.chars() {
if !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != '_' && c != '.' { if !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != '_' && c != '.' && c != '$' {
errors.push(crate::drop::Error { errors.push(crate::drop::Error {
code: "INVALID_IDENTIFIER".to_string(), code: "INVALID_IDENTIFIER".to_string(),
message: format!( message: format!(
"Invalid character '{}' in JSON Schema '{}' property: '{}'. Identifiers must exclusively contain [a-z0-9_.]", "Invalid character '{}' in JSON Schema '{}' property: '{}'. Identifiers must exclusively contain [a-z0-9_.$]",
c, field_name, id c, field_name, id
), ),
details: crate::drop::ErrorDetails { details: crate::drop::ErrorDetails {

View File

@ -14,7 +14,35 @@ impl Schema {
if let Some(props) = self.obj.compiled_properties.get() { if let Some(props) = self.obj.compiled_properties.get() {
let mut filter_props = BTreeMap::new(); let mut filter_props = BTreeMap::new();
for (key, child) in props { for (key, child) in props {
if let Some(mut filter_type) = Self::resolve_filter_type(child) { let mut structural_filter = None;
let is_array = match &child.obj.type_ {
Some(SchemaTypeOrArray::Single(t)) => t == "array",
Some(SchemaTypeOrArray::Multiple(types)) => types.contains(&"array".to_string()),
None => false,
};
if is_array {
if let Some(items) = &child.obj.items {
if !items.is_proxy() {
structural_filter = items.compile_filter(_db, "", _errors);
}
}
} else if !child.is_proxy() {
structural_filter = child.compile_filter(_db, "", _errors);
}
if let Some(mut inline_schema) = structural_filter {
inline_schema.obj.type_ = Some(SchemaTypeOrArray::Multiple(vec![
"object".to_string(),
"null".to_string(),
]));
filter_props.insert(
key.clone(),
Arc::new(inline_schema),
);
} else if let Some(mut filter_type) = Self::resolve_filter_type(child) {
filter_type.push("null".to_string()); filter_type.push("null".to_string());
let mut child_obj = SchemaObject::default(); let mut child_obj = SchemaObject::default();
@ -31,47 +59,49 @@ impl Schema {
} }
if !filter_props.is_empty() { if !filter_props.is_empty() {
let root_filter_type = format!("{}.filter", root_id); if !root_id.is_empty() {
let root_filter_type = format!("{}.filter", root_id);
let mut and_obj = SchemaObject::default(); let mut and_obj = SchemaObject::default();
and_obj.type_ = Some(SchemaTypeOrArray::Multiple(vec![ and_obj.type_ = Some(SchemaTypeOrArray::Multiple(vec![
"array".to_string(), "array".to_string(),
"null".to_string(), "null".to_string(),
])); ]));
and_obj.items = Some(Arc::new(Schema { and_obj.items = Some(Arc::new(Schema {
obj: SchemaObject { obj: SchemaObject {
type_: Some(SchemaTypeOrArray::Single(root_filter_type.clone())), type_: Some(SchemaTypeOrArray::Single(root_filter_type.clone())),
..Default::default() ..Default::default()
}, },
always_fail: false,
}));
filter_props.insert(
"$and".to_string(),
Arc::new(Schema {
obj: and_obj,
always_fail: false, always_fail: false,
}), }));
); filter_props.insert(
"$and".to_string(),
Arc::new(Schema {
obj: and_obj,
always_fail: false,
}),
);
let mut or_obj = SchemaObject::default(); let mut or_obj = SchemaObject::default();
or_obj.type_ = Some(SchemaTypeOrArray::Multiple(vec![ or_obj.type_ = Some(SchemaTypeOrArray::Multiple(vec![
"array".to_string(), "array".to_string(),
"null".to_string(), "null".to_string(),
])); ]));
or_obj.items = Some(Arc::new(Schema { or_obj.items = Some(Arc::new(Schema {
obj: SchemaObject { obj: SchemaObject {
type_: Some(SchemaTypeOrArray::Single(root_filter_type.clone())), type_: Some(SchemaTypeOrArray::Single(root_filter_type.clone())),
..Default::default() ..Default::default()
}, },
always_fail: false,
}));
filter_props.insert(
"$or".to_string(),
Arc::new(Schema {
obj: or_obj,
always_fail: false, always_fail: false,
}), }));
); filter_props.insert(
"$or".to_string(),
Arc::new(Schema {
obj: or_obj,
always_fail: false,
}),
);
}
let mut wrapper_obj = SchemaObject::default(); let mut wrapper_obj = SchemaObject::default();
// Filters are just plain objects containing conditions, no inheritance required // Filters are just plain objects containing conditions, no inheritance required
@ -119,7 +149,12 @@ impl Schema {
"number" => Some(vec!["number.condition".to_string()]), "number" => Some(vec!["number.condition".to_string()]),
"boolean" => Some(vec!["boolean.condition".to_string()]), "boolean" => Some(vec!["boolean.condition".to_string()]),
"object" => None, // Inline structures are ignored in Composed References "object" => None, // Inline structures are ignored in Composed References
"array" => None, // We don't filter primitive arrays or map complex arrays yet "array" => {
if let Some(items) = &schema.obj.items {
return Self::resolve_filter_type(items);
}
None
},
"null" => None, "null" => None,
custom => { custom => {
// Assume anything else is a Relational cross-boundary that already has its own .filter dynamically built // Assume anything else is a Relational cross-boundary that already has its own .filter dynamically built

View File

@ -1 +1 @@
1.0.128 1.0.130