///|
/// Merge projected output shapes using the same structural cases as runtime merge.
fn merge_output_shapes(
items : Array[@core.Schema],
) -> Result[@core.Schema, String] {
if items.is_empty() {
return Ok(@core.unknown())
}
let mut result = items[0]
for i = 1; i < items.length(); i = i + 1 {
match merge_output_pair(result, items[i]) {
Ok(merged) => result = merged
Err(message) => return Err(message)
}
}
Ok(result)
}
///|
fn merge_output_pair(
left : @core.Schema,
right : @core.Schema,
) -> Result[@core.Schema, String] {
if @core.output_is_optional(left) || @core.output_is_optional(right) {
return Err(
"Intersection output with overlapping missing values requires an explicit output schema",
)
}
let merged = match (left.schema_type, right.schema_type) {
(ObjectType(a, mode_a), ObjectType(b, mode_b)) => {
let fields = a.copy()
for key, child in b {
match fields.get(key) {
None => fields.set(key, child)
Some(previous) =>
match merge_output_pair(previous, child) {
Ok(child) => fields.set(key, child)
Err(message) => return Err(message + " at field " + key)
}
}
}
let result = @core.object(fields)
if mode_a is Strict || mode_b is Strict {
result.strict()
} else if mode_a is Passthrough || mode_b is Passthrough {
result.passthrough()
} else {
result
}
}
(ArrayType(a), ArrayType(b)) =>
match merge_output_pair(a, b) {
Ok(element) => @core.array(element)
Err(message) => return Err(message)
}
(TupleType(a), TupleType(b)) => {
if a.length() != b.length() {
return Err(
"Intersection tuple output with different lengths requires an explicit output schema",
)
}
let items : Array[@core.Schema] = []
for i = 0; i < a.length(); i = i + 1 {
match merge_output_pair(a[i], b[i]) {
Ok(child) => items.push(child)
Err(message) => return Err(message)
}
}
@core.tuple(items)
}
_ => {
if output_scalar_only(left) && output_scalar_only(right) {
return Ok(@core.intersection([left, right]))
}
return Err(
"Intersection output involving conditional or mixed structural shapes requires an explicit output schema",
)
}
}
Ok({ ..merged, rules: left.rules + right.rules, })
}
///|
fn output_scalar_only(schema : @core.Schema) -> Bool {
match schema.schema_type {
StringType | NumberType | BooleanType | NullType | EnumType(_) => true
LiteralType(value) => !(value is Object(_) || value is Array(_))
UnionType(items) | IntersectionType(items) => items.all(output_scalar_only)
NullableType(inner) => output_scalar_only(inner)
_ => false
}
}