blob: d3044e9743c2c55910d4ebbe64d620b2c7c31c09 [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 {
fidl_fuchsia_sys2 as fsys,
lazy_static::lazy_static,
regex::Regex,
std::collections::{HashMap, HashSet},
std::error,
std::fmt,
};
lazy_static! {
static ref PATH: Identifier = Identifier::new(r"^(/[^/]+)+$", 1024);
static ref NAME: Identifier = Identifier::new(r"^[0-9a-z_\-\.]+$", 100);
static ref URI: Identifier = Identifier::new(r"^[0-9a-z\+\-\.]+://.+$", 4096);
}
/// Enum type that can represent any error encountered during validation.
#[derive(Debug)]
pub enum Error {
MissingField(String, String),
EmptyField(String, String),
DuplicateField(String, String, String),
InvalidField(String, String),
FieldTooLong(String, String),
OfferTargetEqualsSource(String),
InvalidChild(String, String),
}
impl Error {
pub fn missing_field(decl_type: impl Into<String>, keyword: impl Into<String>) -> Self {
Error::MissingField(decl_type.into(), keyword.into())
}
pub fn empty_field(decl_type: impl Into<String>, keyword: impl Into<String>) -> Self {
Error::EmptyField(decl_type.into(), keyword.into())
}
pub fn duplicate_field(
decl_type: impl Into<String>,
keyword: impl Into<String>,
value: impl Into<String>,
) -> Self {
Error::DuplicateField(decl_type.into(), keyword.into(), value.into())
}
pub fn invalid_field(decl_type: impl Into<String>, keyword: impl Into<String>) -> Self {
Error::InvalidField(decl_type.into(), keyword.into())
}
pub fn field_too_long(decl_type: impl Into<String>, keyword: impl Into<String>) -> Self {
Error::FieldTooLong(decl_type.into(), keyword.into())
}
pub fn offer_target_equals_source(decl_type: impl Into<String>) -> Self {
Error::OfferTargetEqualsSource(decl_type.into())
}
pub fn invalid_child(decl_type: impl Into<String>, child: impl Into<String>) -> Self {
Error::InvalidChild(decl_type.into(), child.into())
}
}
impl error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
Error::MissingField(d, k) => write!(f, "{} missing {}", d, k),
Error::EmptyField(d, k) => write!(f, "{} has empty {}", d, k),
Error::DuplicateField(d, k, v) => write!(f, "\"{}\" is a duplicate {} {}", v, d, k),
Error::InvalidField(d, k) => write!(f, "{} has invalid {}", d, k),
Error::FieldTooLong(d, k) => write!(f, "{}'s {} is too long", d, k),
Error::OfferTargetEqualsSource(d) => {
write!(f, "OfferTarget \"{}\" is same as source", d)
}
Error::InvalidChild(d, c) => {
write!(f, "\"{}\" is referenced in {} but it does not appear in children", c, d)
}
}
}
}
/// Represents a list of errors encountered during validation.
#[derive(Debug)]
pub struct ErrorList {
errs: Vec<Error>,
}
impl ErrorList {
fn new(errs: Vec<Error>) -> ErrorList {
ErrorList { errs }
}
}
impl error::Error for ErrorList {}
impl fmt::Display for ErrorList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let strs: Vec<String> = self.errs.iter().map(|e| format!("{}", e)).collect();
write!(f, "{}", strs.join(", "))
}
}
/// Validates a ComponentDecl.
/// The ComponentDecl may ultimately originate from a CM file, or be directly constructed by the
/// caller. Either way, a ComponentDecl should always be validated before it's used. Examples
/// of what is validated (which may evolve in the future):
/// - That all semantically required fields are present
/// - That a child_name referenced in a source actually exists in the list of children
/// - That there are no duplicate target ptahs
pub fn validate(decl: &fsys::ComponentDecl) -> Result<(), ErrorList> {
let ctx = ValidationContext { decl, all_children: HashSet::new(), errors: vec![] };
ctx.validate().map_err(|errs| ErrorList::new(errs))
}
struct ValidationContext<'a> {
decl: &'a fsys::ComponentDecl,
all_children: HashSet<&'a str>,
errors: Vec<Error>,
}
type PathMap<'a> = HashMap<String, HashSet<&'a str>>;
impl<'a> ValidationContext<'a> {
fn validate(mut self) -> Result<(), Vec<Error>> {
// Validate "children" and build the set of all children.
if let Some(children) = self.decl.children.as_ref() {
for child in children.iter() {
self.validate_child_decl(&child);
}
}
// Validate "uses".
if let Some(uses) = self.decl.uses.as_ref() {
for use_ in uses.iter() {
self.validate_use_decl(&use_);
}
}
// Validate "exposes".
if let Some(exposes) = self.decl.exposes.as_ref() {
let mut target_paths = HashSet::new();
for expose in exposes.iter() {
self.validate_expose_decl(&expose, &mut target_paths);
}
}
// Validate "offers".
if let Some(offers) = self.decl.offers.as_ref() {
let mut target_paths = HashMap::new();
for offer in offers.iter() {
self.validate_offer_decl(&offer, &mut target_paths);
}
}
if self.errors.is_empty() {
Ok(())
} else {
Err(self.errors)
}
}
fn validate_use_decl(&mut self, use_: &fsys::UseDecl) {
self.validate_capability(use_.capability.as_ref(), "UseDecl");
PATH.check(use_.target_path.as_ref(), "UseDecl", "target_path", &mut self.errors);
}
fn validate_capability(&mut self, capability: Option<&fsys::Capability>, decl_type: &str) {
match capability.as_ref() {
Some(c) => match c {
fsys::Capability::Service(s) => {
PATH.check(s.path.as_ref(), decl_type, "capability.path", &mut self.errors);
}
fsys::Capability::Directory(s) => {
PATH.check(s.path.as_ref(), decl_type, "capability.path", &mut self.errors);
}
fsys::Capability::__UnknownVariant { .. } => {
self.errors.push(Error::invalid_field(decl_type, "capability"));
}
},
None => {
self.errors.push(Error::missing_field(decl_type, "capability"));
}
}
}
fn validate_child_decl(&mut self, child: &'a fsys::ChildDecl) {
let name = child.name.as_ref();
if NAME.check(name, "ChildDecl", "name", &mut self.errors) {
let name: &str = name.unwrap();
if !self.all_children.insert(name) {
self.errors.push(Error::duplicate_field("ChildDecl", "name", name));
}
}
URI.check(child.uri.as_ref(), "ChildDecl", "uri", &mut self.errors);
if child.startup.is_none() {
self.errors.push(Error::missing_field("ChildDecl", "startup"));
}
}
fn validate_source_child(&mut self, child: &fsys::ChildId, decl_type: &str) {
if NAME.check(child.name.as_ref(), decl_type, "source.child.name", &mut self.errors) {
if let Some(child_name) = &child.name {
if !self.all_children.contains(child_name as &str) {
self.errors.push(Error::invalid_child(
format!("{} source", decl_type),
child_name as &str,
));
}
} else {
self.errors.push(Error::missing_field(decl_type, "source.child.name"));
}
}
}
fn validate_expose_decl(
&mut self,
expose: &'a fsys::ExposeDecl,
prev_target_paths: &mut HashSet<&'a str>,
) {
self.validate_capability(expose.capability.as_ref(), "ExposeDecl");
match expose.source.as_ref() {
Some(r) => match r {
fsys::ExposeSource::Myself(_) => {}
fsys::ExposeSource::Child(child) => {
self.validate_source_child(child, "ExposeDecl");
}
fsys::ExposeSource::__UnknownVariant { .. } => {
self.errors.push(Error::invalid_field("ExposeDecl", "source"));
}
},
None => {
self.errors.push(Error::missing_field("ExposeDecl", "source"));
}
}
let target_path = expose.target_path.as_ref();
if PATH.check(target_path, "ExposeDecl", "target_path", &mut self.errors) {
let target_path: &str = target_path.unwrap();
if !prev_target_paths.insert(target_path) {
self.errors.push(Error::duplicate_field("ExposeDecl", "target_path", target_path));
}
}
}
fn validate_offer_decl(
&mut self,
offer: &'a fsys::OfferDecl,
prev_target_paths: &mut PathMap<'a>,
) {
self.validate_capability(offer.capability.as_ref(), "OfferDecl");
match offer.source.as_ref() {
Some(r) => match r {
fsys::OfferSource::Realm(_) => {}
fsys::OfferSource::Myself(_) => {}
fsys::OfferSource::Child(child) => {
self.validate_source_child(child, "OfferDecl");
}
fsys::OfferSource::__UnknownVariant { .. } => {
self.errors.push(Error::invalid_field("OfferDecl", "source"));
}
},
None => {
self.errors.push(Error::missing_field("OfferDecl", "source"));
}
}
if let Some(targets) = &offer.targets {
self.validate_targets(offer.source.as_ref(), targets, prev_target_paths);
} else {
self.errors.push(Error::missing_field("OfferDecl", "targets"));
}
}
fn validate_targets(
&mut self,
source: Option<&fsys::OfferSource>,
targets: &'a Vec<fsys::OfferTarget>,
prev_target_paths: &mut PathMap<'a>,
) {
if targets.is_empty() {
self.errors.push(Error::empty_field("OfferDecl", "targets"));
}
for target in targets.iter() {
let mut valid = true;
valid &= PATH.check(
target.target_path.as_ref(),
"OfferTarget",
"target_path",
&mut self.errors,
);
valid &= NAME.check(
target.child_name.as_ref(),
"OfferTarget",
"child_name",
&mut self.errors,
);
if valid {
let target_path: &str = target.target_path.as_ref().unwrap();
let child_name: &str = target.child_name.as_ref().unwrap();
if !self.all_children.contains(child_name) {
self.errors.push(Error::invalid_child("OfferTarget", child_name));
}
let paths_for_target =
prev_target_paths.entry(child_name.to_string()).or_insert(HashSet::new());
if !paths_for_target.insert(target_path) {
self.errors.push(Error::duplicate_field(
"OfferDecl",
"target_path",
target_path,
));
}
if let Some(source) = source {
if let fsys::OfferSource::Child(source_child) = source {
if let Some(source_child_name) = &source_child.name {
if source_child_name == child_name {
self.errors.push(Error::offer_target_equals_source(
source_child_name as &str,
));
}
}
}
}
}
}
}
}
struct Identifier {
re: Regex,
max_len: usize,
}
impl Identifier {
fn new(regex: &str, max_len: usize) -> Identifier {
Identifier { re: Regex::new(regex).unwrap(), max_len }
}
fn check(
&self,
prop: Option<&String>,
decl_type: &str,
keyword: &str,
errors: &mut Vec<Error>,
) -> bool {
let mut valid = true;
if prop.is_none() {
errors.push(Error::missing_field(decl_type, keyword));
valid = false;
} else {
if !self.re.is_match(prop.unwrap()) {
errors.push(Error::invalid_field(decl_type, keyword));
valid = false;
}
if prop.unwrap().len() > self.max_len {
errors.push(Error::field_too_long(decl_type, keyword));
valid = false;
}
}
valid
}
}
#[cfg(test)]
mod tests {
use {
super::*,
fidl_fuchsia_sys2::{
Capability, ChildDecl, ChildId, ComponentDecl, ExposeDecl, ExposeSource, OfferDecl,
OfferSource, OfferTarget, RealmId, SelfId, ServiceCapability, StartupMode, UseDecl,
},
};
fn validate_test(input: ComponentDecl, expected_res: Result<(), ErrorList>) {
let res = validate(&input);
assert_eq!(format!("{:?}", res), format!("{:?}", expected_res));
}
fn identifier_test(identifier: &Identifier, input: &str, expected_res: Result<(), ErrorList>) {
let mut errors = vec![];
let res: Result<(), ErrorList> =
match identifier.check(Some(&input.to_string()), "FooDecl", "foo", &mut errors) {
true => Ok(()),
false => Err(ErrorList::new(errors)),
};
assert_eq!(format!("{:?}", res), format!("{:?}", expected_res));
}
fn new_component_decl() -> ComponentDecl {
ComponentDecl {
program: None,
uses: None,
exposes: None,
offers: None,
facets: None,
children: None,
}
}
#[test]
fn test_errors() {
assert_eq!(format!("{}", Error::missing_field("Decl", "keyword")), "Decl missing keyword");
assert_eq!(format!("{}", Error::empty_field("Decl", "keyword")), "Decl has empty keyword");
assert_eq!(
format!("{}", Error::duplicate_field("Decl", "keyword", "foo")),
"\"foo\" is a duplicate Decl keyword"
);
assert_eq!(
format!("{}", Error::invalid_field("Decl", "keyword")),
"Decl has invalid keyword"
);
assert_eq!(
format!("{}", Error::field_too_long("Decl", "keyword")),
"Decl's keyword is too long"
);
assert_eq!(
format!("{}", Error::invalid_child("Decl", "child")),
"\"child\" is referenced in Decl but it does not appear in children"
);
}
macro_rules! test_validate {
(
$(
$test_name:ident => {
input = $input:expr,
result = $result:expr,
},
)+
) => {
$(
#[test]
fn $test_name() {
validate_test($input, $result);
}
)+
}
}
macro_rules! test_identifier {
(
$(
$test_name:ident => {
identifier = $identifier:expr,
input = $input:expr,
result = $result:expr,
},
)+
) => {
$(
#[test]
fn $test_name() {
identifier_test($identifier, $input, $result);
}
)+
}
}
test_identifier! {
// path
test_identifier_path_valid => {
identifier = &PATH,
input = "/foo/bar",
result = Ok(()),
},
test_identifier_path_invalid_empty => {
identifier = &PATH,
input = "",
result = Err(ErrorList::new(vec![Error::invalid_field("FooDecl", "foo")])),
},
test_identifier_path_invalid_root => {
identifier = &PATH,
input = "/",
result = Err(ErrorList::new(vec![Error::invalid_field("FooDecl", "foo")])),
},
test_identifier_path_invalid_relative => {
identifier = &PATH,
input = "foo/bar",
result = Err(ErrorList::new(vec![Error::invalid_field("FooDecl", "foo")])),
},
test_identifier_path_invalid_trailing => {
identifier = &PATH,
input = "/foo/bar/",
result = Err(ErrorList::new(vec![Error::invalid_field("FooDecl", "foo")])),
},
test_identifier_path_too_long => {
identifier = &PATH,
input = &format!("/{}", "a".repeat(1024)),
result = Err(ErrorList::new(vec![Error::field_too_long("FooDecl", "foo")])),
},
// name
test_identifier_name_valid => {
identifier = &NAME,
input = "abcdefghijklmnopqrstuvwxyz0123456789_-.",
result = Ok(()),
},
test_identifier_name_invalid => {
identifier = &NAME,
input = "^bad",
result = Err(ErrorList::new(vec![Error::invalid_field("FooDecl", "foo")])),
},
test_identifier_name_too_long => {
identifier = &NAME,
input = &format!("{}", "a".repeat(101)),
result = Err(ErrorList::new(vec![Error::field_too_long("FooDecl", "foo")])),
},
// uri
test_identifier_uri_valid => {
identifier = &URI,
input = "my+awesome-scheme.2://abc123!@#$%.com",
result = Ok(()),
},
test_identifier_uri_invalid => {
identifier = &URI,
input = "fuchsia-pkg://",
result = Err(ErrorList::new(vec![Error::invalid_field("FooDecl", "foo")])),
},
test_identifier_uri_too_long => {
identifier = &URI,
input = &format!("fuchsia-pkg://{}", "a".repeat(4083)),
result = Err(ErrorList::new(vec![Error::field_too_long("FooDecl", "foo")])),
},
}
test_validate! {
// uses
test_validate_uses_empty => {
input = {
let mut decl = new_component_decl();
decl.uses = Some(vec![UseDecl{
capability: None,
target_path: None,
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("UseDecl", "capability"),
Error::missing_field("UseDecl", "target_path"),
])),
},
test_validate_uses_invalid_identifiers => {
input = {
let mut decl = new_component_decl();
decl.uses = Some(vec![UseDecl{
capability: Some(Capability::Service(ServiceCapability{
path: Some("foo/".to_string()),
})),
target_path: Some("/".to_string()),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_field("UseDecl", "capability.path"),
Error::invalid_field("UseDecl", "target_path"),
])),
},
test_validate_uses_long_identifiers => {
input = {
let mut decl = new_component_decl();
decl.uses = Some(vec![UseDecl{
capability: Some(Capability::Service(ServiceCapability{
path: Some(format!("/{}", "a".repeat(1024))),
})),
target_path: Some(format!("/{}", "b".repeat(1024))),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::field_too_long("UseDecl", "capability.path"),
Error::field_too_long("UseDecl", "target_path"),
])),
},
// exposes
test_validate_exposes_empty => {
input = {
let mut decl = new_component_decl();
decl.exposes = Some(vec![ExposeDecl{
capability: None,
source: None,
target_path: None,
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("ExposeDecl", "capability"),
Error::missing_field("ExposeDecl", "source"),
Error::missing_field("ExposeDecl", "target_path"),
])),
},
test_validate_exposes_invalid_identifiers => {
input = {
let mut decl = new_component_decl();
decl.exposes = Some(vec![ExposeDecl{
capability: Some(Capability::Service(ServiceCapability{
path: Some("foo/".to_string()),
})),
source: Some(ExposeSource::Child(ChildId{name: Some("^bad".to_string())})),
target_path: Some("/".to_string()),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_field("ExposeDecl", "capability.path"),
Error::invalid_field("ExposeDecl", "source.child.name"),
Error::invalid_field("ExposeDecl", "target_path"),
])),
},
test_validate_exposes_long_identifiers => {
input = {
let mut decl = new_component_decl();
decl.exposes = Some(vec![ExposeDecl{
capability: Some(Capability::Service(ServiceCapability{
path: Some(format!("/{}", "a".repeat(1024))),
})),
source: Some(ExposeSource::Child(ChildId{name: Some("b".repeat(101))})),
target_path: Some(format!("/{}", "b".repeat(1024))),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::field_too_long("ExposeDecl", "capability.path"),
Error::field_too_long("ExposeDecl", "source.child.name"),
Error::field_too_long("ExposeDecl", "target_path"),
])),
},
test_validate_exposes_invalid_child => {
input = {
let mut decl = new_component_decl();
decl.exposes = Some(vec![
ExposeDecl{
capability: Some(Capability::Service(ServiceCapability{
path: Some("/loggers/fuchsia.logger.Log".to_string()),
})),
source: Some(ExposeSource::Child(ChildId {
name: Some("netstack".to_string()),
})),
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
},
]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_child("ExposeDecl source", "netstack"),
])),
},
test_validate_exposes_duplicate_target => {
input = {
let mut decl = new_component_decl();
decl.exposes = Some(vec![
ExposeDecl{
capability: Some(Capability::Service(ServiceCapability{
path: Some("/svc/logger".to_string()),
})),
source: Some(ExposeSource::Myself(SelfId{})),
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
},
ExposeDecl{
capability: Some(Capability::Service(ServiceCapability{
path: Some("/svc/logger2".to_string()),
})),
source: Some(ExposeSource::Myself(SelfId{})),
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
},
]);
decl
},
result = Err(ErrorList::new(vec![
Error::duplicate_field("ExposeDecl", "target_path", "/svc/fuchsia.logger.Log"),
])),
},
// offers
test_validate_offers_empty => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![OfferDecl{
capability: None,
source: None,
targets: None,
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("OfferDecl", "capability"),
Error::missing_field("OfferDecl", "source"),
Error::missing_field("OfferDecl", "targets"),
])),
},
test_validate_offers_long_identifiers => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![OfferDecl{
capability: Some(Capability::Service(ServiceCapability{
path: Some(format!("/{}", "a".repeat(1024))),
})),
source: Some(OfferSource::Child(ChildId{name: Some("a".repeat(101))})),
targets: Some(vec![
OfferTarget{
target_path: Some(format!("/{}", "b".repeat(1024))),
child_name: Some("b".repeat(101)),
},
]),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::field_too_long("OfferDecl", "capability.path"),
Error::field_too_long("OfferDecl", "source.child.name"),
Error::field_too_long("OfferTarget", "target_path"),
Error::field_too_long("OfferTarget", "child_name"),
])),
},
test_validate_offers_invalid_child => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![
OfferDecl{
capability: Some(Capability::Service(ServiceCapability{
path: Some("/loggers/fuchsia.logger.Log".to_string()),
})),
source: Some(OfferSource::Child(ChildId{name: Some("logger".to_string())})),
targets: Some(vec![
OfferTarget{
target_path: Some("/data/realm_assets".to_string()),
child_name: Some("netstack".to_string()),
},
]),
},
]);
decl.children = Some(vec![
ChildDecl{
name: Some("netstack".to_string()),
uri: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
startup: Some(StartupMode::Lazy),
},
]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_child("OfferDecl source", "logger"),
])),
},
test_validate_offer_target_empty => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![OfferDecl{
capability: Some(Capability::Service(ServiceCapability{
path: Some("/svc/logger".to_string()),
})),
source: Some(OfferSource::Realm(RealmId{})),
targets: Some(vec![OfferTarget{target_path: None, child_name: None}]),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("OfferTarget", "target_path"),
Error::missing_field("OfferTarget", "child_name"),
])),
},
test_validate_offer_targets_empty => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![OfferDecl{
capability: Some(Capability::Service(ServiceCapability{
path: Some("/svc/logger".to_string()),
})),
source: Some(OfferSource::Myself(SelfId{})),
targets: Some(vec![]),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::empty_field("OfferDecl", "targets"),
])),
},
test_validate_offer_target_equals_from => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![OfferDecl{
capability: Some(Capability::Service(ServiceCapability{
path: Some("/svc/logger".to_string()),
})),
source: Some(OfferSource::Child(ChildId{name: Some("logger".to_string())})),
targets: Some(vec![OfferTarget{
target_path: Some("/svc/logger".to_string()),
child_name: Some("logger".to_string()),
}]),
}]);
decl.children = Some(vec![ChildDecl{
name: Some("logger".to_string()),
uri: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()),
startup: Some(StartupMode::Lazy),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::offer_target_equals_source("logger"),
])),
},
test_validate_offer_target_duplicate_path => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![OfferDecl{
capability: Some(Capability::Service(ServiceCapability{
path: Some("/svc/logger".to_string()),
})),
source: Some(OfferSource::Myself(SelfId{})),
targets: Some(vec![
OfferTarget{
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
child_name: Some("netstack".to_string()),
},
OfferTarget{
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
child_name: Some("netstack".to_string()),
},
]),
}]);
decl.children = Some(vec![
ChildDecl{
name: Some("netstack".to_string()),
uri: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
startup: Some(StartupMode::Eager),
},
]);
decl
},
result = Err(ErrorList::new(vec![
Error::duplicate_field("OfferDecl", "target_path", "/svc/fuchsia.logger.Log"),
])),
},
test_validate_offer_target_invalid_child => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![OfferDecl{
capability: Some(Capability::Service(ServiceCapability{
path: Some("/svc/logger".to_string()),
})),
source: Some(OfferSource::Myself(SelfId{})),
targets: Some(vec![
OfferTarget{
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
child_name: Some("netstack".to_string()),
},
]),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_child("OfferTarget", "netstack"),
])),
},
// children
test_validate_children_empty => {
input = {
let mut decl = new_component_decl();
decl.children = Some(vec![ChildDecl{
name: None,
uri: None,
startup: None,
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("ChildDecl", "name"),
Error::missing_field("ChildDecl", "uri"),
Error::missing_field("ChildDecl", "startup"),
])),
},
test_validate_children_invalid_identifiers => {
input = {
let mut decl = new_component_decl();
decl.children = Some(vec![ChildDecl{
name: Some("^bad".to_string()),
uri: Some("bad-scheme&://blah".to_string()),
startup: Some(StartupMode::Lazy),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_field("ChildDecl", "name"),
Error::invalid_field("ChildDecl", "uri"),
])),
},
test_validate_children_long_identifiers => {
input = {
let mut decl = new_component_decl();
decl.children = Some(vec![ChildDecl{
name: Some("a".repeat(1025)),
uri: Some(format!("fuchsia-pkg://{}", "a".repeat(4083))),
startup: Some(StartupMode::Lazy),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::field_too_long("ChildDecl", "name"),
Error::field_too_long("ChildDecl", "uri"),
])),
},
}
}