| // Copyright 2018 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 crate::common::{Error, CM_SCHEMA, CML_SCHEMA, CMX_SCHEMA, JsonSchemaStr}; |
| use serde_json::Value; |
| use std::fs; |
| use std::io::Read; |
| use std::path::PathBuf; |
| use valico::json_schema; |
| |
| /// Read in and parse a list of files, and return an Error if any of the given files are not valid |
| /// cmx. One of the JSON schemas located at ../*_schema.json, selected based on the file extension, |
| /// is used to determine the validity of each input file. |
| pub fn validate(files: Vec<PathBuf>) -> Result<(), Error> { |
| const BAD_EXTENSION: &str = "Input file does not have a component manifest extension \ |
| (.cm, .cml, or .cmx)"; |
| if files.is_empty() { |
| return Err(Error::invalid_args("No files provided")); |
| } |
| |
| for filename in files { |
| let mut buffer = String::new(); |
| fs::File::open(&filename)?.read_to_string(&mut buffer)?; |
| let v: Value = serde_json::from_str(&buffer) |
| .map_err(|e| Error::parse(format!("Couldn't read input as JSON: {}", e)))?; |
| match filename.extension() { |
| Some(ext) => match ext.to_str() { |
| Some("cm") => validate_json(&v, CM_SCHEMA), |
| Some("cml") => validate_cml(&v), |
| Some("cmx") => validate_json(&v, CMX_SCHEMA), |
| _ => Err(Error::invalid_args(BAD_EXTENSION)), |
| }, |
| None => Err(Error::invalid_args(BAD_EXTENSION)), |
| }?; |
| } |
| Ok(()) |
| } |
| |
| /// Validate a JSON document according to the given schema. |
| fn validate_json(json: &Value, schema: JsonSchemaStr) -> Result<(), Error> { |
| // Parse the schema |
| let cmx_schema_json = serde_json::from_str(schema) |
| .map_err(|e| Error::internal(format!("Couldn't read schema as JSON: {}", e)))?; |
| let mut scope = json_schema::Scope::new(); |
| let schema = scope |
| .compile_and_return(cmx_schema_json, false) |
| .map_err(|e| Error::internal(format!("Couldn't parse schema: {:?}", e)))?; |
| |
| // Validate the json |
| let res = schema.validate(json); |
| if !res.is_strictly_valid() { |
| let mut err_msgs = Vec::new(); |
| for e in &res.errors { |
| err_msgs.push(format!("{} at {}", e.get_title(), e.get_path()).into_boxed_str()); |
| } |
| // The ordering in which valico emits these errors is unstable. |
| // Sort error messages so that the resulting message is predictable. |
| err_msgs.sort_unstable(); |
| return Err(Error::parse(err_msgs.join(", "))); |
| } |
| Ok(()) |
| } |
| |
| /// Validates CML JSON document according to the schema. |
| /// TODO: Allow JSON5 input |
| /// TODO: Perform extra validation beyond what the schema provides. |
| pub fn validate_cml(json: &Value) -> Result<(), Error> { |
| validate_json(&json, CML_SCHEMA) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use serde_json::json; |
| use std::fs::File; |
| use std::io::Write; |
| use tempfile::TempDir; |
| |
| macro_rules! test_validate_cm { |
| ( |
| $( |
| $test_name:ident => { |
| input = $input:expr, |
| result = $result:expr, |
| }, |
| )+ |
| ) => { |
| $( |
| #[test] |
| fn $test_name() { |
| validate_test("test.cm", $input, $result); |
| } |
| )+ |
| } |
| } |
| |
| macro_rules! test_validate_cml { |
| ( |
| $( |
| $test_name:ident => { |
| input = $input:expr, |
| result = $result:expr, |
| }, |
| )+ |
| ) => { |
| $( |
| #[test] |
| fn $test_name() { |
| validate_test("test.cml", $input, $result); |
| } |
| )+ |
| } |
| } |
| |
| macro_rules! test_validate_cmx { |
| ( |
| $( |
| $test_name:ident => { |
| input = $input:expr, |
| result = $result:expr, |
| }, |
| )+ |
| ) => { |
| $( |
| #[test] |
| fn $test_name() { |
| validate_test("test.cmx", $input, $result); |
| } |
| )+ |
| } |
| } |
| |
| fn validate_test( |
| filename: &str, input: serde_json::value::Value, expected_result: Result<(), Error>, |
| ) { |
| let tmp_dir = TempDir::new().unwrap(); |
| let tmp_file_path = tmp_dir.path().join(filename); |
| |
| File::create(&tmp_file_path) |
| .unwrap() |
| .write_all(format!("{}", input).as_bytes()) |
| .unwrap(); |
| |
| let result = validate(vec![tmp_file_path]); |
| assert_eq!(format!("{:?}", result), format!("{:?}", expected_result)); |
| } |
| |
| // TODO: Consider converting these tests to a golden test |
| |
| test_validate_cm! { |
| // program |
| test_cm_empty_json => { |
| input = json!({}), |
| result = Ok(()), |
| }, |
| test_cm_program => { |
| input = json!({"program": { "foo": 55 }}), |
| result = Ok(()), |
| }, |
| |
| // uses |
| test_cm_uses => { |
| input = json!({ |
| "uses": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.boot.Log", |
| "target_path": "/svc/fuchsia.logger.Log", |
| }, |
| { |
| "type": "directory", |
| "source_path": "/data/assets", |
| "target_path": "/data/kitten_assets", |
| } |
| ] |
| }), |
| result = Ok(()), |
| }, |
| test_cm_uses_missing_props => { |
| input = json!({ |
| "uses": [ {} ] |
| }), |
| result = Err(Error::parse("This property is required at /uses/0/source_path, This property is required at /uses/0/target_path, This property is required at /uses/0/type")), |
| }, |
| test_cm_uses_bad_type => { |
| input = json!({ |
| "uses": [ |
| { |
| "type": "bad", |
| "source_path": "/svc/fuchsia.logger.Log", |
| "target_path": "/svc/fuchsia.logger.Log", |
| } |
| ] |
| }), |
| result = Err(Error::parse("Pattern condition is not met at /uses/0/type")), |
| }, |
| |
| // exposes |
| test_cm_exposes => { |
| input = json!({ |
| "exposes": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": { |
| "relation": "self" |
| }, |
| "target_path": "/svc/fuchsia.ui.Scenic" |
| }, |
| { |
| "type": "directory", |
| "source_path": "/data/assets", |
| "source": { |
| "relation": "child", |
| "child_name": "cat_viewer" |
| }, |
| "target_path": "/data/kitten_assets" |
| } |
| ] |
| }), |
| result = Ok(()), |
| }, |
| test_cm_exposes_all_valid_chars => { |
| input = json!({ |
| "exposes": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": { |
| "relation": "child", |
| "child_name": "ABCDEFGHIJILMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-" |
| }, |
| "target_path": "/svc/fuchsia.ui.Scenic" |
| }, |
| ] |
| }), |
| result = Ok(()), |
| }, |
| test_cm_exposes_missing_props => { |
| input = json!({ |
| "exposes": [ {} ] |
| }), |
| result = Err(Error::parse("This property is required at /exposes/0/source, This property is required at /exposes/0/source_path, This property is required at /exposes/0/target_path, This property is required at /exposes/0/type")), |
| }, |
| test_cm_exposes_bad_type => { |
| input = json!({ |
| "exposes": [ |
| { |
| "type": "bad", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": { |
| "relation": "self" |
| }, |
| "target_path": "/svc/fuchsia.ui.Scenic" |
| } |
| ] |
| }), |
| result = Err(Error::parse("Pattern condition is not met at /exposes/0/type")), |
| }, |
| test_cm_exposes_source_missing_props => { |
| input = json!({ |
| "exposes": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": {}, |
| "target_path": "/svc/fuchsia.ui.Scenic" |
| } |
| ] |
| }), |
| result = Err(Error::parse("This property is required at /exposes/0/source/relation")), |
| }, |
| test_cm_exposes_source_extraneous_child => { |
| input = json!({ |
| "exposes": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": { "relation": "self", "child_name": "scenic" }, |
| "target_path": "/svc/fuchsia.ui.Scenic" |
| } |
| ] |
| }), |
| result = Err(Error::parse("OneOf conditions are not met at /exposes/0/source")), |
| }, |
| test_cm_exposes_source_missing_child => { |
| input = json!({ |
| "exposes": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": { "relation": "child" }, |
| "target_path": "/svc/fuchsia.ui.Scenic" |
| } |
| ] |
| }), |
| result = Err(Error::parse("OneOf conditions are not met at /exposes/0/source")), |
| }, |
| test_cm_exposes_source_bad_relation => { |
| input = json!({ |
| "exposes": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": { |
| "relation": "realm" |
| }, |
| "target_path": "/svc/fuchsia.ui.Scenic" |
| } |
| ] |
| }), |
| result = Err(Error::parse("Pattern condition is not met at /exposes/0/source/relation")), |
| }, |
| test_cm_exposes_source_bad_child_name => { |
| input = json!({ |
| "exposes": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": { |
| "relation": "child", |
| "child_name": "bad^" |
| }, |
| "target_path": "/svc/fuchsia.ui.Scenic" |
| } |
| ] |
| }), |
| result = Err(Error::parse("Pattern condition is not met at /exposes/0/source/child_name")), |
| }, |
| |
| // offers |
| test_cm_offers => { |
| input = json!({ |
| "offers": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.logger.LogSink", |
| "source": { |
| "relation": "realm" |
| }, |
| "targets": [ |
| { |
| "target_path": "/svc/fuchsia.logger.SysLog", |
| "child_name": "viewer" |
| } |
| ] |
| }, |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": { |
| "relation": "self" |
| }, |
| "targets": [ |
| { |
| "target_path": "/svc/fuchsia.ui.Scenic", |
| "child_name": "user_shell" |
| }, |
| { |
| "target_path": "/services/fuchsia.ui.Scenic", |
| "child_name": "viewer" |
| } |
| ] |
| }, |
| { |
| "type": "directory", |
| "source_path": "/data/assets", |
| "source": { |
| "relation": "child", |
| "child_name": "cat_provider" |
| }, |
| "targets": [ |
| { |
| "target_path": "/data/kitten_assets", |
| "child_name": "cat_viewer" |
| } |
| ] |
| } |
| ] |
| }), |
| result = Ok(()), |
| }, |
| test_cm_offers_all_valid_chars => { |
| input = json!({ |
| "offers": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.logger.LogSink", |
| "source": { |
| "relation": "child", |
| "child_name": "ABCDEFGHIJILMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-" |
| }, |
| "targets": [ |
| { |
| "target_path": "/svc/fuchsia.logger.SysLog", |
| "child_name": "ABCDEFGHIJILMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-" |
| } |
| ] |
| } |
| ] |
| }), |
| result = Ok(()), |
| }, |
| test_cm_offers_missing_props => { |
| input = json!({ |
| "offers": [ {} ] |
| }), |
| result = Err(Error::parse("This property is required at /offers/0/source, This property is required at /offers/0/source_path, This property is required at /offers/0/targets, This property is required at /offers/0/type")), |
| }, |
| test_cm_offers_bad_type => { |
| input = json!({ |
| "offers": [ |
| { |
| "type": "bad", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": { |
| "relation": "self" |
| }, |
| "targets": [ |
| { |
| "target_path": "/svc/fuchsia.ui.Scenic", |
| "child_name": "user_shell" |
| } |
| ] |
| } |
| ] |
| }), |
| result = Err(Error::parse("Pattern condition is not met at /offers/0/type")), |
| }, |
| test_cm_offers_source_missing_props => { |
| input = json!({ |
| "offers": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": {}, |
| "targets": [ |
| { |
| "target_path": "/svc/fuchsia.ui.Scenic", |
| "child_name": "user_shell" |
| } |
| ] |
| } |
| ] |
| }), |
| result = Err(Error::parse("This property is required at /offers/0/source/relation")), |
| }, |
| test_cm_offers_source_extraneous_child => { |
| input = json!({ |
| "offers": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": { "relation": "self", "child_name": "scenic" }, |
| "targets": [ |
| { |
| "target_path": "/svc/fuchsia.ui.Scenic", |
| "child_name": "user_shell" |
| } |
| ] |
| } |
| ] |
| }), |
| result = Err(Error::parse("OneOf conditions are not met at /offers/0/source")), |
| }, |
| test_cm_offers_source_missing_child => { |
| input = json!({ |
| "offers": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": { "relation": "child" }, |
| "targets": [ |
| { |
| "target_path": "/svc/fuchsia.ui.Scenic", |
| "child_name": "user_shell" |
| } |
| ] |
| } |
| ] |
| }), |
| result = Err(Error::parse("OneOf conditions are not met at /offers/0/source")), |
| }, |
| test_cm_offers_source_bad_relation => { |
| input = json!({ |
| "offers": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": { |
| "relation": "bad" |
| }, |
| "targets": [ |
| { |
| "target_path": "/svc/fuchsia.ui.Scenic", |
| "child_name": "user_shell" |
| } |
| ] |
| } |
| ] |
| }), |
| result = Err(Error::parse("Pattern condition is not met at /offers/0/source/relation")), |
| }, |
| test_cm_offers_source_bad_child_name => { |
| input = json!({ |
| "offers": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": { |
| "relation": "child", |
| "child_name": "bad^" |
| }, |
| "targets": [ |
| { |
| "target_path": "/svc/fuchsia.ui.Scenic", |
| "child_name": "user_shell" |
| } |
| ] |
| } |
| ] |
| }), |
| result = Err(Error::parse("Pattern condition is not met at /offers/0/source/child_name")), |
| }, |
| test_cm_offers_target_missing_props => { |
| input = json!({ |
| "offers": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": { |
| "relation": "child", |
| "child_name": "cat_viewer" |
| }, |
| "targets": [ {} ] |
| } |
| ] |
| }), |
| result = Err(Error::parse("This property is required at /offers/0/targets/0/child_name, This property is required at /offers/0/targets/0/target_path")), |
| }, |
| test_cm_offers_target_bad_child_name => { |
| input = json!({ |
| "offers": [ |
| { |
| "type": "service", |
| "source_path": "/svc/fuchsia.ui.Scenic", |
| "source": { |
| "relation": "self" |
| }, |
| "targets": [ |
| { |
| "target_path": "/svc/fuchsia.ui.Scenic", |
| "child_name": "bad^" |
| } |
| ] |
| } |
| ] |
| }), |
| result = Err(Error::parse("Pattern condition is not met at /offers/0/targets/0/child_name")), |
| }, |
| |
| // children |
| test_cm_children => { |
| input = json!({ |
| "children": [ |
| { |
| "name": "System-logger2", |
| "uri": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm" |
| }, |
| { |
| "name": "ABCabc123_-", |
| "uri": "https://www.google.com/gmail" |
| } |
| ] |
| }), |
| result = Ok(()), |
| }, |
| test_cm_children_all_valid_chars => { |
| input = json!({ |
| "children": [ |
| { |
| "name": "ABCDEFGHIJILMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-", |
| "uri": "https://www.google.com/gmail" |
| } |
| ] |
| }), |
| result = Ok(()), |
| }, |
| test_cm_children_missing_props => { |
| input = json!({ |
| "children": [ {} ] |
| }), |
| result = Err(Error::parse("This property is required at /children/0/name, This property is required at /children/0/uri")), |
| }, |
| test_cm_children_bad_name => { |
| input = json!({ |
| "children": [ |
| { |
| "name": "bad^", |
| "uri": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm" |
| } |
| ] |
| }), |
| result = Err(Error::parse("Pattern condition is not met at /children/0/name")), |
| }, |
| |
| // facets |
| test_cm_facets => { |
| input = json!({ |
| "facets": { |
| "metadata": { |
| "title": "foo", |
| "authors": [ "me", "you" ], |
| "year": 2018 |
| } |
| } |
| }), |
| result = Ok(()), |
| }, |
| test_cm_facets_wrong_type => { |
| input = json!({ |
| "facets": 55 |
| }), |
| result = Err(Error::parse("Type of the value is wrong at /facets")), |
| }, |
| } |
| |
| test_validate_cml! { |
| // program |
| test_cml_empty_json => { |
| input = json!({}), |
| result = Ok(()), |
| }, |
| test_cml_program => { |
| input = json!({"program": { "binary": "bin/app" }}), |
| result = Ok(()), |
| }, |
| test_cml_program_no_binary => { |
| input = json!({"program": {}}), |
| result = Err(Error::parse("This property is required at /program/binary")), |
| }, |
| |
| // use |
| test_cml_use => { |
| input = json!({ |
| "use": [ |
| { "service": "/fonts/CoolFonts", "as": "/svc/fuchsia.fonts.Provider" }, |
| { "directory": "/data/assets" } |
| ] |
| }), |
| result = Ok(()), |
| }, |
| test_cml_use_missing_props => { |
| input = json!({ |
| "use": [ { "as": "/svc/fuchsia.logger.Log" } ] |
| }), |
| result = Err(Error::parse("OneOf conditions are not met at /use/0")), |
| }, |
| |
| // expose |
| test_cml_expose => { |
| input = json!({ |
| "expose": [ |
| { "service": "/loggers/fuchsia.logger.Log", "from": "#logger" }, |
| { "directory": "/volumes/blobfs", "from": "self" } |
| ], |
| "children": [ |
| { |
| "name": "logger", |
| "uri": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm" |
| } |
| ] |
| }), |
| result = Ok(()), |
| }, |
| test_cml_expose_all_valid_chars => { |
| input = json!({ |
| "expose": [ |
| { "service": "/loggers/fuchsia.logger.Log", "from": "#ABCDEFGHIJILMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-" } |
| ], |
| "children": [ |
| { |
| "name": "ABCDEFGHIJILMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-", |
| "uri": "https://www.google.com/gmail" |
| } |
| ] |
| }), |
| result = Ok(()), |
| }, |
| test_cml_expose_missing_props => { |
| input = json!({ |
| "expose": [ {} ] |
| }), |
| result = Err(Error::parse("OneOf conditions are not met at /expose/0, This property is required at /expose/0/from")), |
| }, |
| test_cml_expose_bad_from => { |
| input = json!({ |
| "expose": [ { |
| "service": "/loggers/fuchsia.logger.Log", "from": "realm" |
| } ] |
| }), |
| result = Err(Error::parse("Pattern condition is not met at /expose/0/from")), |
| }, |
| |
| // offer |
| test_cml_offer => { |
| input = json!({ |
| "offer": [ |
| { |
| "service": "/svc/fuchsia.logger.Log", |
| "from": "#logger", |
| "targets": [ |
| { "to": "#echo2_server" }, |
| { "to": "#scenic", "as": "/svc/fuchsia.logger.SysLog" } |
| ] |
| }, |
| { |
| "service": "/svc/fuchsia.fonts.Provider", |
| "from": "realm", |
| "targets": [ |
| { "to": "#echo2_server" }, |
| ] |
| }, |
| { |
| "directory": "/data/assets", |
| "from": "self", |
| "targets": [ |
| { "to": "#echo2_server" }, |
| ] |
| } |
| ], |
| "children": [ |
| { |
| "name": "logger", |
| "uri": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm" |
| }, |
| { |
| "name": "scenic", |
| "uri": "fuchsia-pkg://fuchsia.com/scenic/stable#meta/scenic.cm" |
| }, |
| { |
| "name": "echo2_server", |
| "uri": "fuchsia-pkg://fuchsia.com/echo2/stable#meta/echo2_server.cm" |
| } |
| ] |
| }), |
| result = Ok(()), |
| }, |
| test_cml_offer_all_valid_chars => { |
| input = json!({ |
| "offer": [ |
| { |
| "service": "/svc/fuchsia.logger.Log", |
| "from": "#ABCDEFGHIJILMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-", |
| "targets": [ |
| { |
| "to": "#ABCDEFGHIJILMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-" |
| } |
| ] |
| } |
| ], |
| "children": [ |
| { |
| "name": "ABCDEFGHIJILMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-", |
| "uri": "https://www.google.com/gmail" |
| } |
| ] |
| }), |
| result = Ok(()), |
| }, |
| test_cml_offer_missing_props => { |
| input = json!({ |
| "offer": [ {} ] |
| }), |
| result = Err(Error::parse("OneOf conditions are not met at /offer/0, This property is required at /offer/0/from, This property is required at /offer/0/targets")), |
| }, |
| test_cml_offer_bad_from => { |
| input = json!({ |
| "offer": [ { |
| "service": "/svc/fuchsia.logger.Log", |
| "from": "#invalid@", |
| "targets": [ |
| { "to": "#echo2_server" }, |
| ] |
| } ] |
| }), |
| result = Err(Error::parse("Pattern condition is not met at /offer/0/from")), |
| }, |
| test_cml_offer_empty_targets => { |
| input = json!({ |
| "offer": [ { |
| "service": "/svc/fuchsia.logger.Log", |
| "from": "#logger", |
| "targets": [] |
| } ] |
| }), |
| result = Err(Error::parse("MinItems condition is not met at /offer/0/targets")), |
| }, |
| test_cml_offer_target_missing_props => { |
| input = json!({ |
| "offer": [ { |
| "service": "/svc/fuchsia.logger.Log", |
| "from": "#logger", |
| "targets": [ |
| { "as": "/svc/fuchsia.logger.SysLog" } |
| ] |
| } ] |
| }), |
| result = Err(Error::parse("This property is required at /offer/0/targets/0/to")), |
| }, |
| test_cml_offer_target_bad_to => { |
| input = json!({ |
| "offer": [ { |
| "service": "/svc/fuchsia.logger.Log", |
| "from": "#logger", |
| "targets": [ |
| { "to": "self", "as": "/svc/fuchsia.logger.SysLog" } |
| ] |
| } ] |
| }), |
| result = Err(Error::parse("Pattern condition is not met at /offer/0/targets/0/to")), |
| }, |
| |
| // children |
| test_cml_children => { |
| input = json!({ |
| "children": [ |
| { |
| "name": "logger", |
| "uri": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm" |
| }, |
| { |
| "name": "gmail", |
| "uri": "https://www.google.com/gmail" |
| } |
| ] |
| }), |
| result = Ok(()), |
| }, |
| test_cml_children_missing_props => { |
| input = json!({ |
| "children": [ {} ] |
| }), |
| result = Err(Error::parse("This property is required at /children/0/name, This property is required at /children/0/uri")), |
| }, |
| test_cml_children_bad_name => { |
| input = json!({ |
| "children": [ |
| { |
| "name": "#bad", |
| "uri": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm" |
| } |
| ] |
| }), |
| result = Err(Error::parse("Pattern condition is not met at /children/0/name")), |
| }, |
| |
| // facets |
| test_cml_facets => { |
| input = json!({ |
| "facets": { |
| "metadata": { |
| "title": "foo", |
| "authors": [ "me", "you" ], |
| "year": 2018 |
| } |
| } |
| }), |
| result = Ok(()), |
| }, |
| test_cml_facets_wrong_type => { |
| input = json!({ |
| "facets": 55 |
| }), |
| result = Err(Error::parse("Type of the value is wrong at /facets")), |
| }, |
| } |
| |
| test_validate_cmx! { |
| test_cmx_err_empty_json => { |
| input = json!({}), |
| result = Err(Error::parse("This property is required at /program")), |
| }, |
| test_cmx_program => { |
| input = json!({"program": { "binary": "bin/app" }}), |
| result = Ok(()), |
| }, |
| test_cmx_program_no_binary => { |
| input = json!({ "program": {}}), |
| result = Err(Error::parse("OneOf conditions are not met at /program")), |
| }, |
| test_cmx_bad_program => { |
| input = json!({"prigram": { "binary": "bin/app" }}), |
| result = Err(Error::parse("Property conditions are not met at , \ |
| This property is required at /program")), |
| }, |
| test_cmx_sandbox => { |
| input = json!({ |
| "program": { "binary": "bin/app" }, |
| "sandbox": { "dev": [ "class/camera" ] } |
| }), |
| result = Ok(()), |
| }, |
| test_cmx_facets => { |
| input = json!({ |
| "program": { "binary": "bin/app" }, |
| "facets": { |
| "fuchsia.test": { |
| "system-services": [ "fuchsia.logger.LogSink" ] |
| } |
| } |
| }), |
| result = Ok(()), |
| }, |
| } |
| } |