///|
fn json_integer(value : Int) -> Json {
Json::number(value.to_double(), repr=value.to_string())
}
///|
fn coveralls_branch_identifier_json(value : String) -> Json {
let parsed : Int? = Some(@strconv.from_str(value)) catch { _ => None }
match parsed {
Some(number) => json_integer(number)
None => Json::string(value)
}
}
///|
fn coveralls_line_array(file : FileCoverage) -> Array[Json] raise CoverageError {
let counts : Map[Int, Int] = Map([])
let mut max_line = 0
for item in file.lines {
if item.line <= 0 {
raise InvalidModel("line number must be positive")
}
if item.hits < 0 {
raise InvalidModel("line hit count must not be negative")
}
counts.update_or_default(item.line, item.hits, old => old + item.hits)
if item.line > max_line {
max_line = item.line
}
}
Array::makei(max_line, index => {
match counts.get(index + 1) {
Some(hits) => json_integer(hits)
None => Json::null()
}
})
}
///|
fn coveralls_branch_array(
file : FileCoverage,
) -> Array[Json] raise CoverageError {
let flattened : Array[Json] = []
let branches = [ for branch in file.branches => branch ]
branches.sort_by(compare_branches)
for branch in branches {
if branch.line <= 0 {
raise InvalidModel("branch line must be positive")
}
flattened.push(json_integer(branch.line))
flattened.push(coveralls_branch_identifier_json(branch.block))
flattened.push(coveralls_branch_identifier_json(branch.branch))
flattened.push(
match branch.taken {
Some(count) =>
if count < 0 {
raise InvalidModel("branch hit count must not be negative")
} else {
json_integer(count)
}
None => Json::null()
},
)
}
flattened
}
///|
fn coveralls_file_json(file : FileCoverage) -> Json raise CoverageError {
let object : Map[String, Json] = {
"name": Json::string(file.path),
"coverage": Json::array(coveralls_line_array(file)),
}
if !file.branches.is_empty() {
object["branches"] = Json::array(coveralls_branch_array(file))
}
Json::object(object)
}
///|
/// Encode the line and branch portions of a report as Coveralls JSON.
///
/// Coveralls' source-file schema has no standard function-coverage field, so
/// function entries are intentionally not serialized. Numeric-looking branch
/// identifiers stay numeric; other identifiers are emitted as strings and are
/// accepted by mooncov's decoder.
pub fn to_coveralls_json(
report : CoverageReport,
service_name? : String = "mooncov",
service_job_id? : String,
) -> String raise CoverageError {
let source_files : Array[Json] = []
let canonical = canonicalize_report(report)
for file in canonical.files {
source_files.push(coveralls_file_json(file))
}
let root : Map[String, Json] = {
"service_name": Json::string(service_name),
"source_files": Json::array(source_files),
}
match service_job_id {
Some(value) => root["service_job_id"] = Json::string(value)
None => ()
}
Json::object(root).stringify(indent=2)
}