///|
fn identifier(text : StringView) -> Bool {
let body = text.trim_start(chars="_")
guard body.length() > 0 else { return false }
let first = body.iter().next().unwrap()
guard first is ('A'..='Z' | 'a'..='z') || (text.has_prefix("_") && first is ('0'..='9')) else { return false }
for c in text {
if !(c is ('A'..='Z' | 'a'..='z' | '0'..='9' | '_')) { return false }
}
true
}
///|
/// Smithy 2.0 shape ID ABNF: https://smithy.io/2.0/spec/model.html#shape-id-abnf
/// Smithy absolute shape IDs. Labels require a qualified shape_namespace to avoid
/// treating ordinary text such as `read/write` as a link.
fn shape_target(text : String, qualified~ : Bool) -> String? {
let parts = text.split("/").to_array()
guard parts is [shape_namespace, shape] else { return None }
let ns = shape_namespace.split(".").to_array()
guard (!qualified || ns.length() >= 2) && ns.iter().all(identifier) else { return None }
let names = shape.split("$").to_array()
guard names.length() <= 2 && names.iter().all(identifier) else { return None }
Some("\{shape_namespace}#\{shape}")
}
///|
fn url_link(url : String) -> @model.Link? {
if url.has_prefix("smithy:") {
shape_target(url.view(start_offset=7).to_owned(), qualified=false)
.map(target => @model.Link::new(Smithy, target))
} else if url.has_prefix("openrpc:") && url.length() > 8 {
Some(@model.Link::new(OpenRpc, url.view(start_offset=8).to_owned()))
} else {
None
}
}
///|
fn message_link(label : String) -> @model.Link? {
shape_target(label, qualified=true).map(target => @model.Link::new(Smithy, target))
}
///|
/// JSON objects keep their source order in `links`. Select the first known
/// scheme, just as a single `link` selects its URL. Unknown schemes stay in AST.
fn statement_link(s : @ast.LinkStatement) -> @model.Link? {
match s.kind {
Link => match s.text.find("@") {
Some(at) => url_link(s.text.view(start_offset=at + 1).trim().to_owned())
None => None
}
Links => {
let parsed = json_value(s.text)
guard parsed is Some(Object(fields)) else { return None }
for _, value in fields {
if value is String(url) && url_link(url) is Some(link) {
return Some(link)
}
}
None
}
Properties | Details => None
}
}
///|
fn json_value(text : String) -> Json? {
try Some(@json.parse(text)) catch { _ => None }
}