///|
/// A JSON Pointer at the use site and at the resolved definition.
pub(all) struct Location {
pointer : String
definition_pointer : String
} derive(Eq, ToJson)
///|
/// A supported breaking change, attributed to an affected operation.
pub(all) struct Finding {
rule_id : String
http_method : String
path : String
old_location : Location?
new_location : Location?
reason : String
} derive(Eq, ToJson)
///|
/// An input error or a reason why analysis could not be completed.
pub(all) struct Diagnostic {
code : String
severity : String
side : String
http_method : String
path : String
location : Location
reason : String
} derive(Eq, ToJson)
///|
/// status is complete, incomplete, or invalid; it does not mean full OAS compatibility.
pub(all) struct Report {
status : String
findings : Array[Finding]
diagnostics : Array[Diagnostic]
} derive(Eq, ToJson)
///|
/// Exit code 2 takes precedence over breaking changes (1).
pub fn Report::exit_code(self : Report) -> Int {
if self.status != "complete" {
2
} else if self.findings.is_empty() {
0
} else {
1
}
}
///|
priv struct Node {
raw : Json
location : Location
refs : Array[String]
}
///|
priv struct Context {
root : Json
side : String
http_method : String
path : String
diagnostics : Array[Diagnostic]
budget : WorkBudget
}
///|
priv struct WorkBudget {
mut remaining : Int
mut exhausted : Bool
}
///|
fn new_budget() -> WorkBudget {
{ remaining: 20000, exhausted: false, }
}
///|
fn Context::visit(self : Context, value : Node) -> Bool {
if self.budget.remaining > 0 {
self.budget.remaining -= 1
true
} else {
if !self.budget.exhausted {
self.budget.exhausted = true
self.diagnose(
"analysis-work-limit", "unsupported", value, "Analysis exceeds the shared 20000-node traversal budget; manual review is required.",
)
}
false
}
}
///|
fn Context::diagnose(
self : Context,
code : String,
severity : String,
node : Node,
reason : String,
) -> Unit {
self.diagnostics.push({
code,
severity,
side: self.side,
http_method: self.http_method,
path: self.path,
location: node.location,
reason,
})
}
///|
fn node(raw : Json, pointer : String) -> Node {
{ raw, location: { pointer, definition_pointer: pointer, }, refs: [], }
}
///|
fn field(value : Json, key : String) -> Json? {
match value {
Object(fields) => fields.get(key)
_ => None
}
}
///|
fn string_value(value : Json?) -> String? {
match value {
Some(String(s)) => Some(s)
_ => None
}
}
///|
fn pointer_token(value : String) -> String {
value.replace_all(old="~", new="~0").replace_all(old="/", new="~1")
}
///|
fn Node::child(self : Node, key : String, value : Json) -> Node {
let token = "/" + pointer_token(key)
{
raw: value,
location: {
pointer: self.location.pointer + token,
definition_pointer: self.location.definition_pointer + token,
},
refs: self.refs,
}
}
///|
fn keys(value : Json) -> Array[String] {
let result : Array[String] = []
if value is Object(fields) {
for key, _ in fields {
result.push(key)
}
}
result.sort()
result
}
///|
fn is_true(value : Json?) -> Bool {
match value {
Some(True) => true
_ => false
}
}