blob: 58282923edd0f9532ceffa632b886ff8c326d326 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use failure::Fail;
use serde::{Deserialize, Serialize};
use serde_json::{from_reader, from_str, to_string, to_value};
use std::io::Read;
use valico::json_schema;
/// The various types of errors raised by this module.
#[derive(Debug, Fail)]
enum Error {
#[fail(display = "invalid JSON schema: {:?}", _0)]
SchemaInvalid(valico::json_schema::schema::SchemaError),
#[fail(display = "could not validate file: {}", errors)]
#[allow(dead_code)] // The compiler complains about the variant not being constructed.
JsonFileInvalid { errors: String },
}
type Result<T> = std::result::Result<T, failure::Error>;
impl From<valico::json_schema::schema::SchemaError> for Error {
fn from(err: valico::json_schema::schema::SchemaError) -> Self {
Error::SchemaInvalid(err)
}
}
/// Augments metadata representations with utility methods to serialize/deserialize and validate
/// their contents.
pub trait JsonObject: for<'a> Deserialize<'a> + Serialize + Sized {
/// Creates a new instance from its raw data.
fn new<R: Read>(source: R) -> Result<Self> {
Ok(from_reader(source)?)
}
/// Returns the schema matching the object type.
fn get_schema() -> &'static str;
/// Checks whether the object satisfies its associated JSON schema.
fn validate(&self) -> Result<()> {
let schema = from_str(Self::get_schema())?;
let mut scope = json_schema::Scope::new();
// Add the schema including all the common definitions.
let common_schema = from_str(include_str!("../common.json"))?;
scope
.compile(common_schema, true)
.map_err(Error::SchemaInvalid)?;
let validator = scope
.compile_and_return(schema, true)
.map_err(Error::SchemaInvalid)?;
let value = to_value(self)?;
let result = validator.validate(&value);
if !result.is_valid() {
let mut error_messages: Vec<String> = result
.errors
.iter()
.map(|e| format!("{} at {}", e.get_title(), e.get_path()))
.collect();
error_messages.sort_unstable();
return Err(Error::JsonFileInvalid {
errors: error_messages.join(", "),
}
.into());
}
Ok(())
}
/// Serializes the object into its string representation.
fn to_string(&self) -> Result<String> {
Ok(to_string(self)?)
}
}
#[cfg(test)]
mod tests {
use serde_derive::{Deserialize, Serialize};
use super::*;
#[derive(Deserialize, Serialize)]
struct Metadata {
target: String,
}
impl JsonObject for Metadata {
fn get_schema() -> &'static str {
r#"{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://fuchsia.com/schemas/sdk/test_metadata.json",
"properties": {
"target": {
"$ref": "common.json#/definitions/target_arch"
}
}
}"#
}
}
#[test]
/// Checks that references to common.json are properly resolved.
fn test_common_reference() {
let metadata = Metadata {
target: "y128".to_string(), // Not a valid architecture.
};
let result = metadata.validate();
assert!(result.is_err(), "Validation did not respect common schema.");
}
}