use serde_json::Value;
use crate::error::{Result, ValidationError, ValidationInfo, ValidationWarning, XarfError};
use crate::model::Report;
use crate::v3_compat;
use crate::validator::{validate, ValidateOptions};
#[derive(Debug, Clone)]
pub struct ParseResult {
pub report: Option<Report>,
pub errors: Vec<ValidationError>,
pub warnings: Vec<ValidationWarning>,
pub info: Option<Vec<ValidationInfo>>,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ParseOptions {
pub strict: bool,
pub show_missing_optional: bool,
}
impl From<ParseOptions> for ValidateOptions {
fn from(o: ParseOptions) -> Self {
Self {
strict: o.strict,
show_missing_optional: o.show_missing_optional,
}
}
}
pub fn parse(json: &str) -> Result<ParseResult> {
parse_with_options(json, ParseOptions::default())
}
pub fn parse_with_options(json: &str, options: ParseOptions) -> Result<ParseResult> {
let value: Value = serde_json::from_str(json)
.map_err(|e| XarfError::InvalidJson(format!("{e}")))?;
if !value.is_object() {
return Err(XarfError::InvalidJson(format!(
"expected a JSON object, got {}",
value_type_name(&value)
)));
}
parse_value(value, options)
}
pub fn parse_value(mut value: Value, options: ParseOptions) -> Result<ParseResult> {
let mut warnings: Vec<ValidationWarning> = Vec::new();
if v3_compat::is_v3_report(&value) {
let mut conversion_msgs: Vec<String> = Vec::new();
match v3_compat::convert_v3_to_v4(value.clone(), &mut conversion_msgs) {
Ok(converted) => {
value = converted;
warnings.push(ValidationWarning::new(
"",
v3_compat::deprecation_warning(),
));
for msg in conversion_msgs {
warnings.push(ValidationWarning::new("", msg));
}
}
Err(err) => {
return Err(err);
}
}
}
let validation = validate(&value, options.into())?;
let mut errors = validation.errors;
warnings.extend(validation.warnings);
if options.strict && !errors.is_empty() {
return Ok(ParseResult {
report: None,
errors,
warnings,
info: validation.info,
});
}
let report = match serde_json::from_value::<Report>(value) {
Ok(r) => Some(r),
Err(e) => {
errors.push(ValidationError::new("", format!("deserialize: {e}")));
None
}
};
Ok(ParseResult {
report,
errors,
warnings,
info: validation.info,
})
}
fn value_type_name(v: &Value) -> &'static str {
match v {
Value::Null => "null",
Value::Bool(_) => "boolean",
Value::Number(_) => "number",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}