///|
/// Render a contract in EvoWitness's canonical, review-stable DSL form.
pub fn Contract::to_dsl(self : Contract) -> String {
let out = StringBuilder()
out.write_string("contract " + self.name + " " + self.version + "\n")
for object in self.objects {
out.write_string("\ntype " + object.name + " ")
out.write_string(if object.open { "open\n" } else { "closed\n" })
for field in object.fields {
out.write_string(
"field " + field.name + " " + field.type_expr.render() + " ",
)
out.write_string(if field.required { "required" } else { "optional" })
for token in field.constraints.to_tokens() {
out.write_string(" " + token)
}
out.write_string("\n")
}
out.write_string("end\n")
}
out.to_string()
}
///|
fn Constraints::to_tokens(self : Constraints) -> Array[String] {
let tokens : Array[String] = []
match self.min_int {
Some(value) => tokens.push("min=" + value.to_string())
None => ()
}
match self.max_int {
Some(value) => tokens.push("max=" + value.to_string())
None => ()
}
match self.min_len {
Some(value) => tokens.push("minlen=" + value.to_string())
None => ()
}
match self.max_len {
Some(value) => tokens.push("maxlen=" + value.to_string())
None => ()
}
match self.default_value {
Some(value) => tokens.push("default=" + value)
None => ()
}
tokens
}
///|
/// Parse and re-render a source document, removing comments and normalizing layout.
pub fn canonicalize(source : String) -> Result[String, ParseError] {
match parse_contract(source) {
Ok(contract) => Ok(contract.to_dsl())
Err(error) => Err(error)
}
}
///|
/// Analyze every adjacent transition in an ordered contract history.
pub fn analyze_series(
contracts : Array[Contract],
mode? : CompatibilityMode = Full,
) -> Array[AnalysisReport] {
let reports : Array[AnalysisReport] = []
if contracts.length() < 2 {
return reports
}
for index = 0; index < contracts.length() - 1; index = index + 1 {
reports.push(analyze(contracts[index], contracts[index + 1], mode~))
}
reports
}
///|
/// Analyze every older/newer pair in an ordered contract history.
/// This catches a sequence that is safe per-step but violates a longer support window.
pub fn analyze_support_window(
contracts : Array[Contract],
mode? : CompatibilityMode = Full,
) -> Array[AnalysisReport] {
let reports : Array[AnalysisReport] = []
for old_index = 0; old_index < contracts.length(); old_index = old_index + 1 {
for new_index = old_index + 1
new_index < contracts.length()
new_index = new_index + 1 {
reports.push(analyze(contracts[old_index], contracts[new_index], mode~))
}
}
reports
}
///|
/// Highest-risk verdict across a version series.
pub fn series_verdict(reports : Array[AnalysisReport]) -> String {
if reports.any(report => !report.is_compatible()) {
"incompatible"
} else {
"compatible"
}
}