use serde_json::Value;
use crate::error::{Result, ValidationError, ValidationInfo, ValidationWarning, XarfError};
use crate::model::Report;
use crate::v3_compat;
use crate::validator::{ValidateOptions, validate};
#[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();
value = v3_compat::convert_v3_to_v4(value, &mut conversion_msgs)?;
warnings.push(ValidationWarning::new("", v3_compat::deprecation_warning()));
warnings.extend(
conversion_msgs
.into_iter()
.map(|m| ValidationWarning::new("", m)),
);
}
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",
}
}