filter synthesis: compile named non-table value types structurally
A property typed as a named value type (a schema-only config object like
an operating-hours schedule) previously got a dangling {type}.filter
reference — no filter is ever synthesized for a non-table-backed schema,
so the whole parent filter failed downstream (PROXY_TYPE_RESOLUTION_FAILED;
the punc generator emitted an empty filter type).
Naming a value type is a reuse choice, not a semantics choice: it now
compiles structurally into the parent filter, exactly like an inline
object, recursively (including array items). Table-backed boundaries keep
the lazy {type}.filter reference. A named type with no compilable
structure is omitted instead of dangling.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@ -87,6 +87,34 @@
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schedule": {
|
||||
"type": [
|
||||
"opening_hours",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"season": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"opening_hours": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"open": {
|
||||
"type": "string"
|
||||
},
|
||||
"seasons": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "season"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -262,6 +290,7 @@
|
||||
"uuid_field",
|
||||
"tags",
|
||||
"ad_hoc",
|
||||
"schedule",
|
||||
"$and",
|
||||
"$or"
|
||||
],
|
||||
@ -277,6 +306,7 @@
|
||||
"uuid_field",
|
||||
"tags",
|
||||
"ad_hoc",
|
||||
"schedule",
|
||||
"$and",
|
||||
"$or"
|
||||
],
|
||||
@ -298,6 +328,7 @@
|
||||
"uuid_field",
|
||||
"tags",
|
||||
"ad_hoc",
|
||||
"schedule",
|
||||
"$and",
|
||||
"$or"
|
||||
],
|
||||
@ -366,6 +397,41 @@
|
||||
"string.condition",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"schedule": {
|
||||
"type": [
|
||||
"filter",
|
||||
"null"
|
||||
],
|
||||
"compiledPropertyNames": [
|
||||
"open",
|
||||
"seasons"
|
||||
],
|
||||
"properties": {
|
||||
"open": {
|
||||
"type": [
|
||||
"string.condition",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"seasons": {
|
||||
"type": [
|
||||
"filter",
|
||||
"null"
|
||||
],
|
||||
"compiledPropertyNames": [
|
||||
"label"
|
||||
],
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": [
|
||||
"string.condition",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "filter"
|
||||
@ -483,7 +549,9 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"opening_hours": {},
|
||||
"season": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
flows
2
flows
Submodule flows updated: 89748a246e...0d9bd8644e
@ -26,10 +26,14 @@ impl Schema {
|
||||
if let Some(items) = &child.obj.items {
|
||||
if !items.is_proxy() {
|
||||
structural_filter = items.compile_filter(_db, "", _errors);
|
||||
} else if let Some(target) = Self::value_type_target(items, _db) {
|
||||
structural_filter = target.compile_filter(_db, "", _errors);
|
||||
}
|
||||
}
|
||||
} else if !child.is_proxy() {
|
||||
structural_filter = child.compile_filter(_db, "", _errors);
|
||||
} else if let Some(target) = Self::value_type_target(child, _db) {
|
||||
structural_filter = target.compile_filter(_db, "", _errors);
|
||||
}
|
||||
|
||||
if let Some(mut inline_schema) = structural_filter {
|
||||
@ -117,6 +121,37 @@ impl Schema {
|
||||
None
|
||||
}
|
||||
|
||||
/// Resolves a pure type-pointer schema to a named non-table value type's own schema —
|
||||
/// a reusable, schema-only object (e.g. an operating-hours config). Naming a value type
|
||||
/// is a reuse choice, not a semantics choice: it filters structurally, exactly like an
|
||||
/// inline object. Table-backed boundaries keep the lazy {type}.filter reference instead.
|
||||
fn value_type_target<'a>(schema: &Arc<Schema>, db: &'a Database) -> Option<&'a Arc<Schema>> {
|
||||
let t = match &schema.obj.type_ {
|
||||
Some(SchemaTypeOrArray::Single(t)) => Some(t.as_str()),
|
||||
Some(SchemaTypeOrArray::Multiple(types)) => {
|
||||
types.iter().find(|t| *t != "null").map(|s| s.as_str())
|
||||
}
|
||||
None => None,
|
||||
}?;
|
||||
if matches!(
|
||||
t,
|
||||
"string" | "integer" | "number" | "boolean" | "object" | "array" | "null"
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
if t.ends_with(".condition") || t.ends_with(".filter") {
|
||||
return None;
|
||||
}
|
||||
if db.enums.contains_key(t) {
|
||||
return None;
|
||||
}
|
||||
let base = t.split('.').next_back().unwrap_or(t);
|
||||
if db.types.contains_key(base) {
|
||||
return None;
|
||||
}
|
||||
db.schemas.get(t)
|
||||
}
|
||||
|
||||
fn resolve_filter_type(schema: &Arc<Schema>, db: &Database) -> Option<Vec<String>> {
|
||||
if let Some(type_) = &schema.obj.type_ {
|
||||
match type_ {
|
||||
@ -165,8 +200,16 @@ impl Schema {
|
||||
} else if db.enums.contains_key(custom) {
|
||||
Some(vec![format!("{}.condition", custom)])
|
||||
} else {
|
||||
// Assume anything else is a Relational cross-boundary that already has its own .filter dynamically built
|
||||
// A Relational cross-boundary gets a reference to the target's dynamically built
|
||||
// .filter — but only a table-backed boundary has one. A named non-table value type
|
||||
// compiles structurally upstream (value_type_target); reaching here means it had
|
||||
// no compilable structure — omit it rather than emit a dangling .filter reference.
|
||||
let base = custom.split('.').next_back().unwrap_or(custom);
|
||||
if db.types.contains_key(base) {
|
||||
Some(vec![format!("{}.filter", custom)])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user