///|
/// One request in a heterogeneous catalog batch.
pub(all) struct CatalogRequest {
name : String
variables : Variables
required : Array[String]
} derive(Debug)
///|
pub fn CatalogRequest::new(
name : String,
variables : Variables,
required~ : Array[String] = [],
) -> CatalogRequest {
{ name, variables, required, }
}
///|
/// index=-1 means a batch option/count error; otherwise identifies the request.
pub(all) struct CatalogBatchError {
index : Int32
name : String
error : TemplateError
} derive(Debug)
///|
/// Validate the entire plan without materializing any result URI.
/// Aggregate input counts variable names/values and catalog request names.
pub fn TemplateCatalog::measure_batch(
self : TemplateCatalog,
requests : Array[CatalogRequest],
reject_unused~ : Bool = false,
limits~ : ExpansionLimits = ExpansionLimits::new(),
batch_limits~ : BatchLimits = BatchLimits::new(),
) -> Result[Array[Int32], CatalogBatchError] {
if batch_limits.max_items < 0 ||
batch_limits.max_total_input_codepoints < 0 ||
batch_limits.max_total_output_bytes < 0 {
return Err({
index: -1,
name: "",
error: problem("InvalidLimit", "negative batch budget", 0, 0),
})
}
if requests.length() > batch_limits.max_items {
return Err({
index: -1,
name: "",
error: problem("BatchItemLimit", "batch count exceeds budget", 0, 0),
})
}
match check_input_budget(Variables::new(), limits) {
Err(error) => return Err({ index: -1, name: "", error, })
Ok(_) => ()
}
let sizes : Array[Int32] = []
let mut remaining_input = batch_limits.max_total_input_codepoints
let mut remaining_output = batch_limits.max_total_output_bytes
for index, request in requests {
let name = request.name
let template = match self.get(name) {
Err(e) => return Err({ index, name, error: e.error, })
Ok(t) => t
}
let input_size = match
template.validate_bindings(
request.variables,
request.required,
reject_unused,
limits,
) {
Err(error) => return Err({ index, name, error, })
Ok(left) => limits.max_total_value_codepoints - left
}
if input_size > remaining_input {
return Err({
index,
name,
error: problem(
"BatchInputLimit", "total input exceeds batch budget", 0, 0,
),
})
}
remaining_input = remaining_input - input_size
remaining_input = consume_input_text(name, remaining_input, remaining_input)
if remaining_input < 0 {
return Err({
index,
name,
error: problem(
"BatchInputLimit", "total input exceeds batch budget", 0, 0,
),
})
}
let per_item = {
..limits,
max_output_bytes: Int32::min(limits.max_output_bytes, remaining_output),
}
match
template.render_impl(request.variables, Some(per_item), false, false) {
Err(e) => {
let error = if e.code == "OutputLimit" &&
remaining_output < limits.max_output_bytes {
{
..e,
code: "BatchOutputLimit",
message: "total output exceeds batch budget",
}
} else {
e
}
return Err({ index, name, error, })
}
Ok(r) => {
sizes.push(r.bytes)
remaining_output = remaining_output - r.bytes
}
}
}
Ok(sizes)
}
///|
/// Atomic multi-template expansion: preflight all requests before producing URIs.
pub fn TemplateCatalog::expand_batch(
self : TemplateCatalog,
requests : Array[CatalogRequest],
reject_unused~ : Bool = false,
limits~ : ExpansionLimits = ExpansionLimits::new(),
batch_limits~ : BatchLimits = BatchLimits::new(),
) -> Result[Array[String], CatalogBatchError] {
match self.measure_batch(requests, reject_unused~, limits~, batch_limits~) {
Err(e) => return Err(e)
Ok(_) => ()
}
let results : Array[String] = []
for index, request in requests {
match
self
.get(request.name)
.unwrap()
.expand_impl(request.variables, Some(limits)) {
Err(error) => return Err({ index, name: request.name, error, })
Ok(uri) => results.push(uri)
}
}
Ok(results)
}
///|
pub extend CatalogRequest with Debug::{to_repr}
///|
pub extend CatalogBatchError with Debug::{to_repr}