diff --git a/fixtures/filter.json b/fixtures/filter.json index 2a2aa72..06bec76 100644 --- a/fixtures/filter.json +++ b/fixtures/filter.json @@ -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": { diff --git a/src/database/compile/filter.rs b/src/database/compile/filter.rs index 388c286..8653ae8 100644 --- a/src/database/compile/filter.rs +++ b/src/database/compile/filter.rs @@ -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