use std::fmt::Display;
use loftspec::check::Placed;
use loftspec::codec::{ReadError, Site};
use loftspec::lint::{Finding, Severity};
use loftspec::yaml::Location;
use serde_json::{Map, Value, json};
use crate::finish::Finished;
use crate::out::outln;
pub fn site(site: &Site) -> Map<String, Value> {
site_at(&site.path, site.location)
}
pub fn site_at(path: &impl Display, location: Option<Location>) -> Map<String, Value> {
let mut fields = Map::new();
fields.insert("path".to_owned(), json!(path.to_string()));
match location {
Some(Location::At(position)) => {
fields.insert("line".to_owned(), json!(position.line));
fields.insert("column".to_owned(), json!(position.column));
}
Some(Location::Byte(offset)) => {
fields.insert("byte".to_owned(), json!(offset));
}
None => {}
}
fields
}
pub fn error(kind: &str, message: &str, sites: &[Site]) -> Value {
error_at(kind, message, sites.iter().map(site).collect())
}
pub fn error_at(kind: &str, message: &str, sites: Vec<Map<String, Value>>) -> Value {
let mut fields = sites.first().cloned().unwrap_or_default();
fields.insert("kind".to_owned(), json!(kind));
fields.insert("message".to_owned(), json!(message));
let sites = sites.into_iter().map(Value::Object).collect();
fields.insert("sites".to_owned(), Value::Array(sites));
Value::Object(fields)
}
pub fn read_error(kind: &str, error: &ReadError) -> Value {
self::error(kind, &error.problem.to_string(), &error.sites)
}
pub fn read_errors(errors: &[ReadError]) -> Vec<Value> {
errors
.iter()
.map(|error| read_error("read", error))
.collect()
}
pub fn validation_errors(errors: &[Placed]) -> Vec<Value> {
errors
.iter()
.map(|placed| self::error("validation", &placed.error.to_string(), &placed.sites))
.collect()
}
pub fn finding(finding: &Finding) -> Value {
let mut fields = finding
.subjects
.first()
.and_then(|subject| subject.site.as_ref())
.map(site)
.unwrap_or_default();
fields.insert("rule".to_owned(), json!(finding.rule.as_str()));
fields.insert("severity".to_owned(), json!(finding.severity.as_str()));
fields.insert("message".to_owned(), json!(finding.message));
if let Some(term) = &finding.term {
fields.insert("term".to_owned(), json!(term));
}
if let Some(line) = finding.body_line {
fields.insert("bodyLine".to_owned(), json!(line));
}
let subjects = finding.subjects.iter().map(|subject| {
let mut fields = subject.site.as_ref().map(site).unwrap_or_default();
fields.insert("key".to_owned(), json!(subject.key.to_string()));
Value::Object(fields)
});
fields.insert("subjects".to_owned(), Value::Array(subjects.collect()));
Value::Object(fields)
}
pub fn result(schema: &str, outcome: &str, mut fields: Value, errors: Vec<Value>) -> Value {
fields["schema"] = json!(schema);
fields["outcome"] = json!(outcome);
fields["errors"] = Value::Array(errors);
fields
}
pub fn failure(schema: &str, errors: Vec<Value>) -> Value {
result(schema, "error", json!({}), errors)
}
pub fn fails_on(
threshold: Option<Severity>,
severities: impl IntoIterator<Item = Severity>,
) -> bool {
threshold.is_some_and(|threshold| severities.into_iter().any(|severity| severity >= threshold))
}
pub fn errors_of(document: &Value) -> &[Value] {
document["errors"].as_array().map_or(&[], Vec::as_slice)
}
pub fn print(document: &Value) {
outln!(
"{}",
serde_json::to_string(document).expect("a JSON value serializes")
);
}
pub fn stop(schema: &str, errors: Vec<Value>, message: &impl Display, as_json: bool) -> Finished {
let lines = vec![message.to_string()];
crate::stop::Stop { errors, lines }.exit(schema, as_json)
}