Compare commits

..

6 Commits

Author SHA1 Message Date
e9b5c82809 version: 1.0.131 2026-04-24 10:49:44 -04:00
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
9ab3689808 version: 1.0.128 2026-04-21 10:51:08 -04:00
4 changed files with 124 additions and 60 deletions

View File

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

View File

@ -12,11 +12,11 @@ impl Schema {
) {
#[cfg(not(test))]
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 {
code: "INVALID_IDENTIFIER".to_string(),
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
),
details: crate::drop::ErrorDetails {

View File

@ -14,7 +14,35 @@ impl Schema {
if let Some(props) = self.obj.compiled_properties.get() {
let mut filter_props = BTreeMap::new();
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());
let mut child_obj = SchemaObject::default();
@ -31,47 +59,49 @@ impl Schema {
}
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();
and_obj.type_ = Some(SchemaTypeOrArray::Multiple(vec![
"array".to_string(),
"null".to_string(),
]));
and_obj.items = Some(Arc::new(Schema {
obj: SchemaObject {
type_: Some(SchemaTypeOrArray::Single(root_filter_type.clone())),
..Default::default()
},
always_fail: false,
}));
filter_props.insert(
"$and".to_string(),
Arc::new(Schema {
obj: and_obj,
let mut and_obj = SchemaObject::default();
and_obj.type_ = Some(SchemaTypeOrArray::Multiple(vec![
"array".to_string(),
"null".to_string(),
]));
and_obj.items = Some(Arc::new(Schema {
obj: SchemaObject {
type_: Some(SchemaTypeOrArray::Single(root_filter_type.clone())),
..Default::default()
},
always_fail: false,
}),
);
}));
filter_props.insert(
"$and".to_string(),
Arc::new(Schema {
obj: and_obj,
always_fail: false,
}),
);
let mut or_obj = SchemaObject::default();
or_obj.type_ = Some(SchemaTypeOrArray::Multiple(vec![
"array".to_string(),
"null".to_string(),
]));
or_obj.items = Some(Arc::new(Schema {
obj: SchemaObject {
type_: Some(SchemaTypeOrArray::Single(root_filter_type.clone())),
..Default::default()
},
always_fail: false,
}));
filter_props.insert(
"$or".to_string(),
Arc::new(Schema {
obj: or_obj,
let mut or_obj = SchemaObject::default();
or_obj.type_ = Some(SchemaTypeOrArray::Multiple(vec![
"array".to_string(),
"null".to_string(),
]));
or_obj.items = Some(Arc::new(Schema {
obj: SchemaObject {
type_: Some(SchemaTypeOrArray::Single(root_filter_type.clone())),
..Default::default()
},
always_fail: false,
}),
);
}));
filter_props.insert(
"$or".to_string(),
Arc::new(Schema {
obj: or_obj,
always_fail: false,
}),
);
}
let mut wrapper_obj = SchemaObject::default();
// Filters are just plain objects containing conditions, no inheritance required
@ -119,7 +149,12 @@ impl Schema {
"number" => Some(vec!["number.condition".to_string()]),
"boolean" => Some(vec!["boolean.condition".to_string()]),
"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,
custom => {
// Assume anything else is a Relational cross-boundary that already has its own .filter dynamically built

View File

@ -1 +1 @@
1.0.127
1.0.131