yerba 0.5.1

YAML Editing and Refactoring with Better Accuracy
use std::fs;
use std::path::Path;

use crate::error::YerbaError;

#[derive(Debug)]
pub struct ValidationError {
  pub path: String,
  pub message: String,
  pub item_label: Option<String>,
  pub line: Option<usize>,
}

impl std::fmt::Display for ValidationError {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    let location = match self.line {
      Some(line) => format!(" (line {})", line),
      None => String::new(),
    };

    match &self.item_label {
      Some(label) => write!(f, "{} at {} ({}){}", self.message, self.path, label, location),
      None => write!(f, "{} at {}{}", self.message, self.path, location),
    }
  }
}

pub fn validate_value(value: &serde_json::Value, schema: &serde_json::Value) -> Vec<ValidationError> {
  let compiled = match jsonschema::draft7::new(schema) {
    Ok(compiled) => compiled,
    Err(error) => {
      return vec![ValidationError {
        path: String::new(),
        message: format!("invalid schema: {}", error),
        item_label: None,
        line: None,
      }];
    }
  };

  compiled
    .iter_errors(value)
    .map(|error| ValidationError {
      path: error.instance_path.to_string(),
      message: error.to_string(),
      item_label: None,
      line: None,
    })
    .collect()
}

pub fn validate_array(items: &[serde_json::Value], schema: &serde_json::Value) -> Vec<ValidationError> {
  let compiled = match jsonschema::draft7::new(schema) {
    Ok(compiled) => compiled,
    Err(error) => {
      return vec![ValidationError {
        path: String::new(),
        message: format!("invalid schema: {}", error),
        item_label: None,
        line: None,
      }];
    }
  };

  let mut errors = Vec::new();

  for (index, item) in items.iter().enumerate() {
    let item_label = item
      .get("name")
      .or_else(|| item.get("title"))
      .or_else(|| item.get("id"))
      .and_then(|value| value.as_str())
      .map(|string| string.to_string())
      .unwrap_or_else(|| format!("index {}", index));

    for error in compiled.iter_errors(item) {
      errors.push(ValidationError {
        path: format!("/{}{}", index, error.instance_path),
        message: error.to_string(),
        item_label: Some(item_label.clone()),
        line: None,
      });
    }
  }

  errors
}

pub fn load_schema(path: impl AsRef<Path>) -> Result<serde_json::Value, YerbaError> {
  let content = fs::read_to_string(path.as_ref())?;

  serde_json::from_str(&content).map_err(|error| YerbaError::ParseError(format!("invalid JSON schema: {}", error)))
}