Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c1c16bc814 | |||
| 5885552192 | |||
| dc033296d7 | |||
| 03a871bc1a |
@ -400,38 +400,9 @@
|
|||||||
},
|
},
|
||||||
"schedule": {
|
"schedule": {
|
||||||
"type": [
|
"type": [
|
||||||
"filter",
|
"opening_hours.filter",
|
||||||
"null"
|
|
||||||
],
|
|
||||||
"compiledPropertyNames": [
|
|
||||||
"open",
|
|
||||||
"seasons"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"open": {
|
|
||||||
"type": [
|
|
||||||
"string.condition",
|
|
||||||
"null"
|
"null"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"seasons": {
|
|
||||||
"type": [
|
|
||||||
"filter",
|
|
||||||
"null"
|
|
||||||
],
|
|
||||||
"compiledPropertyNames": [
|
|
||||||
"label"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"label": {
|
|
||||||
"type": [
|
|
||||||
"string.condition",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "filter"
|
"type": "filter"
|
||||||
@ -551,7 +522,61 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"opening_hours": {},
|
"opening_hours": {},
|
||||||
"season": {}
|
"season": {},
|
||||||
|
"opening_hours.filter": {
|
||||||
|
"type": "filter",
|
||||||
|
"compiledPropertyNames": [
|
||||||
|
"open",
|
||||||
|
"seasons",
|
||||||
|
"$and",
|
||||||
|
"$or"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"open": {
|
||||||
|
"type": [
|
||||||
|
"string.condition",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"seasons": {
|
||||||
|
"type": [
|
||||||
|
"season.filter",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"$and": {
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "opening_hours.filter",
|
||||||
|
"compiledPropertyNames": [
|
||||||
|
"open",
|
||||||
|
"seasons",
|
||||||
|
"$and",
|
||||||
|
"$or"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$or": {
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "opening_hours.filter",
|
||||||
|
"compiledPropertyNames": [
|
||||||
|
"open",
|
||||||
|
"seasons",
|
||||||
|
"$and",
|
||||||
|
"$or"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"season.filter": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,14 +26,10 @@ impl Schema {
|
|||||||
if let Some(items) = &child.obj.items {
|
if let Some(items) = &child.obj.items {
|
||||||
if !items.is_proxy() {
|
if !items.is_proxy() {
|
||||||
structural_filter = items.compile_filter(_db, "", _errors);
|
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() {
|
} else if !child.is_proxy() {
|
||||||
structural_filter = child.compile_filter(_db, "", _errors);
|
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 {
|
if let Some(mut inline_schema) = structural_filter {
|
||||||
@ -121,37 +117,6 @@ impl Schema {
|
|||||||
None
|
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>> {
|
fn resolve_filter_type(schema: &Arc<Schema>, db: &Database) -> Option<Vec<String>> {
|
||||||
if let Some(type_) = &schema.obj.type_ {
|
if let Some(type_) = &schema.obj.type_ {
|
||||||
match type_ {
|
match type_ {
|
||||||
@ -200,12 +165,16 @@ impl Schema {
|
|||||||
} else if db.enums.contains_key(custom) {
|
} else if db.enums.contains_key(custom) {
|
||||||
Some(vec![format!("{}.condition", custom)])
|
Some(vec![format!("{}.condition", custom)])
|
||||||
} else {
|
} else {
|
||||||
// A Relational cross-boundary gets a reference to the target's dynamically built
|
// A named type gets a reference to its dynamically built .filter — either a
|
||||||
// .filter — but only a table-backed boundary has one. A named non-table value type
|
// Relational cross-boundary (table-backed) or a named value type, whose filter
|
||||||
// compiles structurally upstream (value_type_target); reaching here means it had
|
// is likewise synthesized (see Database::compile_filters). A named type with no
|
||||||
// no compilable structure — omit it rather than emit a dangling .filter reference.
|
// compilable structure gets no filter — omit it rather than dangle a reference.
|
||||||
let base = custom.split('.').next_back().unwrap_or(custom);
|
let base = custom.split('.').next_back().unwrap_or(custom);
|
||||||
if db.types.contains_key(base) {
|
let has_value_filter = db
|
||||||
|
.schemas
|
||||||
|
.get(custom)
|
||||||
|
.map_or(false, |s| Database::is_value_filter_candidate(custom, s));
|
||||||
|
if db.types.contains_key(base) || has_value_filter {
|
||||||
Some(vec![format!("{}.filter", custom)])
|
Some(vec![format!("{}.filter", custom)])
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|||||||
@ -262,15 +262,23 @@ impl Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Synthesizes Composed Filter References for all table-backed boundaries.
|
/// Synthesizes Composed Filter References for all table-backed boundaries — and for
|
||||||
|
/// named non-table value types (schema-only objects, e.g. an operating-hours config),
|
||||||
|
/// so a property reference resolves to ONE named filter instead of inlining anonymous
|
||||||
|
/// per-path copies (which duplicate identical leaf type names for downstream codegen).
|
||||||
fn compile_filters(&mut self, errors: &mut Vec<crate::drop::Error>) -> Vec<(String, String)> {
|
fn compile_filters(&mut self, errors: &mut Vec<crate::drop::Error>) -> Vec<(String, String)> {
|
||||||
let mut filter_schemas = Vec::new();
|
let mut filter_schemas = Vec::new();
|
||||||
|
let mut seen_value_ids = std::collections::HashSet::new();
|
||||||
for (type_name, type_def) in &self.types {
|
for (type_name, type_def) in &self.types {
|
||||||
for (id, schema_arc) in &type_def.schemas {
|
for (id, schema_arc) in &type_def.schemas {
|
||||||
// Only run synthesis on actual structured, table-backed boundaries. Exclude subschemas!
|
// Run synthesis on structured table-backed boundaries and named value types.
|
||||||
|
// Exclude subschemas!
|
||||||
let base_name = id.split('.').last().unwrap_or(id);
|
let base_name = id.split('.').last().unwrap_or(id);
|
||||||
let is_table_backed = base_name == type_def.name;
|
let is_table_backed = base_name == type_def.name;
|
||||||
if is_table_backed && !id.contains('/') {
|
let is_value_type = !is_table_backed
|
||||||
|
&& Self::is_value_filter_candidate(id, schema_arc)
|
||||||
|
&& seen_value_ids.insert(id.clone());
|
||||||
|
if (is_table_backed || is_value_type) && !id.contains('/') {
|
||||||
if let Some(filter_schema) = schema_arc.compile_filter(self, id, errors) {
|
if let Some(filter_schema) = schema_arc.compile_filter(self, id, errors) {
|
||||||
filter_schemas.push((
|
filter_schemas.push((
|
||||||
type_name.clone(),
|
type_name.clone(),
|
||||||
@ -293,6 +301,20 @@ impl Database {
|
|||||||
filter_ids
|
filter_ids
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A named non-table value type that earns its own synthesized filter: a bare-named
|
||||||
|
/// (dotless) schema-only object with compiled properties. The base `filter`/`condition`
|
||||||
|
/// schemas are infrastructure, not value types.
|
||||||
|
pub fn is_value_filter_candidate(id: &str, schema: &Arc<Schema>) -> bool {
|
||||||
|
!id.contains('.')
|
||||||
|
&& id != "filter"
|
||||||
|
&& id != "condition"
|
||||||
|
&& schema
|
||||||
|
.obj
|
||||||
|
.compiled_properties
|
||||||
|
.get()
|
||||||
|
.map_or(false, |props| !props.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
/// Synthesizes strong Enum Conditions mirroring the string.condition capabilities.
|
/// Synthesizes strong Enum Conditions mirroring the string.condition capabilities.
|
||||||
fn compile_conditions(&mut self) -> Vec<(String, String)> {
|
fn compile_conditions(&mut self) -> Vec<(String, String)> {
|
||||||
let mut enum_conditions = Vec::new();
|
let mut enum_conditions = Vec::new();
|
||||||
|
|||||||
@ -13,6 +13,10 @@ pub struct Page {
|
|||||||
pub sidebar: Option<Sidebar>,
|
pub sidebar: Option<Sidebar>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub actions: Option<IndexMap<String, Action>>,
|
pub actions: Option<IndexMap<String, Action>>,
|
||||||
|
/// The personal-scope sibling punc for this page ("one surface, two scopes"):
|
||||||
|
/// the caller-scoped read the page issues when the app is in personal scope.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub personal: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
@ -22,4 +26,6 @@ pub struct Sidebar {
|
|||||||
pub category: Option<String>,
|
pub category: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub priority: Option<i32>,
|
pub priority: Option<i32>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub icon: Option<String>,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user