blob: 959b39cb4d7b52a403f3e3921d7c0541b9866886 [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 {
directed_graph::DirectedGraph,
fidl_fuchsia_sys2 as fsys,
std::{
collections::{HashMap, HashSet},
error, fmt,
},
thiserror::Error,
};
const MAX_PATH_LENGTH: usize = 1024;
const MAX_NAME_LENGTH: usize = 100;
const MAX_URL_LENGTH: usize = 4096;
/// Enum type that can represent any error encountered durlng validation.
#[derive(Debug, Error)]
pub enum Error {
#[error("{} missing {}", .0.decl, .0.field)]
MissingField(DeclField),
#[error("{} has empty {}", .0.decl, .0.field)]
EmptyField(DeclField),
#[error("{} has extraneous {}", .0.decl, .0.field)]
ExtraneousField(DeclField),
#[error("\"{1}\" is a duplicate {} {}", .0.decl, .0.field)]
DuplicateField(DeclField, String),
#[error("{} has invalid {}", .0.decl, .0.field)]
InvalidField(DeclField),
#[error("{} has invalid {}, unexpected character '{1}'", .0.decl, .0.field)]
InvalidCharacterInField(DeclField, char),
#[error("{}'s {} is too long", .0.decl, .0.field)]
FieldTooLong(DeclField),
#[error("\"{0}\" target \"{1}\" is same as source")]
OfferTargetEqualsSource(String, String),
#[error("\"{1}\" is referenced in {0} but it does not appear in children")]
InvalidChild(DeclField, String),
#[error("\"{1}\" is referenced in {0} but it does not appear in collections")]
InvalidCollection(DeclField, String),
#[error("\"{1}\" is referenced in {0} but it does not appear in storage")]
InvalidStorage(DeclField, String),
#[error("\"{1}\" is referenced in {0} but it does not appear in environments")]
InvalidEnvironment(DeclField, String),
#[error("\"{1}\" is referenced in {0} but it does not appear in resolvers")]
InvalidResolver(DeclField, String),
#[error("{0} specifies multiple runners")]
MultipleRunnersSpecified(String),
#[error("a dependency cycle exists between resolver registrations")]
ResolverDependencyCycle,
#[error("a dependency cycle exists between offer declarations")]
OfferDependencyCycle,
}
impl Error {
pub fn missing_field(decl_type: impl Into<String>, keyword: impl Into<String>) -> Self {
Error::MissingField(DeclField { decl: decl_type.into(), field: keyword.into() })
}
pub fn empty_field(decl_type: impl Into<String>, keyword: impl Into<String>) -> Self {
Error::EmptyField(DeclField { decl: decl_type.into(), field: keyword.into() })
}
pub fn extraneous_field(decl_type: impl Into<String>, keyword: impl Into<String>) -> Self {
Error::ExtraneousField(DeclField { decl: decl_type.into(), field: keyword.into() })
}
pub fn duplicate_field(
decl_type: impl Into<String>,
keyword: impl Into<String>,
value: impl Into<String>,
) -> Self {
Error::DuplicateField(
DeclField { decl: decl_type.into(), field: keyword.into() },
value.into(),
)
}
pub fn invalid_field(decl_type: impl Into<String>, keyword: impl Into<String>) -> Self {
Error::InvalidField(DeclField { decl: decl_type.into(), field: keyword.into() })
}
pub fn invalid_character_in_field(
decl_type: impl Into<String>,
keyword: impl Into<String>,
character: char,
) -> Self {
Error::InvalidCharacterInField(
DeclField { decl: decl_type.into(), field: keyword.into() },
character,
)
}
pub fn field_too_long(decl_type: impl Into<String>, keyword: impl Into<String>) -> Self {
Error::FieldTooLong(DeclField { decl: decl_type.into(), field: keyword.into() })
}
pub fn offer_target_equals_source(decl: impl Into<String>, target: impl Into<String>) -> Self {
Error::OfferTargetEqualsSource(decl.into(), target.into())
}
pub fn invalid_child(
decl_type: impl Into<String>,
keyword: impl Into<String>,
child: impl Into<String>,
) -> Self {
Error::InvalidChild(
DeclField { decl: decl_type.into(), field: keyword.into() },
child.into(),
)
}
pub fn invalid_collection(
decl_type: impl Into<String>,
keyword: impl Into<String>,
collection: impl Into<String>,
) -> Self {
Error::InvalidCollection(
DeclField { decl: decl_type.into(), field: keyword.into() },
collection.into(),
)
}
pub fn invalid_storage(
decl_type: impl Into<String>,
keyword: impl Into<String>,
storage: impl Into<String>,
) -> Self {
Error::InvalidStorage(
DeclField { decl: decl_type.into(), field: keyword.into() },
storage.into(),
)
}
pub fn invalid_environment(
decl_type: impl Into<String>,
keyword: impl Into<String>,
environment: impl Into<String>,
) -> Self {
Error::InvalidEnvironment(
DeclField { decl: decl_type.into(), field: keyword.into() },
environment.into(),
)
}
pub fn invalid_resolver(
decl_type: impl Into<String>,
keyword: impl Into<String>,
resolver: impl Into<String>,
) -> Self {
Error::InvalidResolver(
DeclField { decl: decl_type.into(), field: keyword.into() },
resolver.into(),
)
}
pub fn multiple_runners_specified(decl_type: impl Into<String>) -> Self {
Error::MultipleRunnersSpecified(decl_type.into())
}
pub fn resolver_dependency_cycle() -> Self {
Error::ResolverDependencyCycle
}
pub fn offer_dependency_cycle() -> Self {
Error::OfferDependencyCycle
}
}
#[derive(Debug)]
pub struct DeclField {
pub decl: String,
pub field: String,
}
impl fmt::Display for DeclField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}", &self.decl, &self.field)
}
}
/// Represents a list of errors encountered durlng 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 paths.
/// - That a cap is not offered back to the child that exposed it.
///
/// All checks are local to this ComponentDecl.
pub fn validate(decl: &fsys::ComponentDecl) -> Result<(), ErrorList> {
let ctx = ValidationContext {
decl,
all_children: HashMap::new(),
all_collections: HashSet::new(),
all_storage_and_sources: HashMap::new(),
all_runners_and_sources: HashMap::new(),
all_resolvers: HashSet::new(),
all_environment_names: HashSet::new(),
strong_dependencies: DirectedGraph::new(),
target_paths: HashMap::new(),
offered_runner_names: HashMap::new(),
offered_resolver_names: HashMap::new(),
offered_event_names: HashMap::new(),
errors: vec![],
};
ctx.validate().map_err(|errs| ErrorList::new(errs))
}
/// Validates an independent ChildDecl. Performs the same validation on it as `validate`.
pub fn validate_child(child: &fsys::ChildDecl) -> Result<(), ErrorList> {
let mut errors = vec![];
check_name(child.name.as_ref(), "ChildDecl", "name", &mut errors);
check_url(child.url.as_ref(), "ChildDecl", "url", &mut errors);
if child.startup.is_none() {
errors.push(Error::missing_field("ChildDecl", "startup"));
}
if child.environment.is_some() {
check_name(child.environment.as_ref(), "ChildDecl", "environment", &mut errors);
}
if errors.is_empty() {
Ok(())
} else {
Err(ErrorList { errs: errors })
}
}
struct ValidationContext<'a> {
decl: &'a fsys::ComponentDecl,
all_children: HashMap<&'a str, &'a fsys::ChildDecl>,
all_collections: HashSet<&'a str>,
all_storage_and_sources: HashMap<&'a str, Option<&'a str>>,
all_runners_and_sources: HashMap<&'a str, Option<&'a str>>,
all_resolvers: HashSet<&'a str>,
all_environment_names: HashSet<&'a str>,
strong_dependencies: DirectedGraph<&'a str>,
target_paths: PathMap<'a>,
offered_runner_names: NameMap<'a>,
offered_resolver_names: NameMap<'a>,
offered_event_names: NameMap<'a>,
errors: Vec<Error>,
}
#[derive(Clone, Copy, PartialEq)]
enum AllowablePaths {
One,
Many,
}
#[derive(Debug, PartialEq, Eq, Hash)]
enum TargetId<'a> {
Component(&'a str),
Collection(&'a str),
}
type PathMap<'a> = HashMap<TargetId<'a>, HashMap<&'a str, AllowablePaths>>;
type NameMap<'a> = HashMap<TargetId<'a>, HashSet<&'a str>>;
impl<'a> ValidationContext<'a> {
fn validate(mut self) -> Result<(), Vec<Error>> {
// Collect all environment names first, so that references to them can be checked.
if let Some(envs) = &self.decl.environments {
self.collect_environment_names(&envs);
}
// Validate "children" and build the set of all children.
if let Some(children) = self.decl.children.as_ref() {
for child in children {
self.validate_child_decl(&child);
}
}
// Validate "collections" and build the set of all collections.
if let Some(collections) = self.decl.collections.as_ref() {
for collection in collections {
self.validate_collection_decl(&collection);
}
}
// Validate "storage" and build the set of all storage sections.
if let Some(storage) = self.decl.storage.as_ref() {
for storage in storage {
self.validate_storage_decl(&storage);
}
}
// Validate "runners" and build the set of all runners.
if let Some(runners) = self.decl.runners.as_ref() {
for runner in runners {
self.validate_runner_decl(&runner);
}
}
// Validate "resolvers" and build the set of all resolvers.
if let Some(resolvers) = self.decl.resolvers.as_ref() {
for resolver in resolvers {
self.validate_resolver_decl(&resolver);
}
}
// Validate "uses".
if let Some(uses) = self.decl.uses.as_ref() {
self.validate_use_decls(uses);
}
// Validate "exposes".
if let Some(exposes) = self.decl.exposes.as_ref() {
let mut target_paths = HashMap::new();
let mut runner_names = HashSet::new();
let mut resolver_names = HashSet::new();
for expose in exposes.iter() {
self.validate_expose_decl(
&expose,
&mut target_paths,
&mut runner_names,
&mut resolver_names,
);
}
}
// Validate "offers".
if let Some(offers) = self.decl.offers.as_ref() {
for offer in offers.iter() {
self.validate_offers_decl(&offer);
}
if let Err(_) = self.strong_dependencies.topological_sort() {
self.errors.push(Error::offer_dependency_cycle());
}
}
// Validate "environments" after all other declarations are processed.
if let Some(environment) = self.decl.environments.as_ref() {
for environment in environment {
self.validate_environment_decl(&environment);
}
}
if self.errors.is_empty() {
Ok(())
} else {
Err(self.errors)
}
}
/// Collects all the environment names, watching for duplicates.
fn collect_environment_names(&mut self, envs: &'a [fsys::EnvironmentDecl]) {
for env in envs {
if let Some(name) = env.name.as_ref() {
if !self.all_environment_names.insert(name) {
self.errors.push(Error::duplicate_field("EnvironmentDecl", "name", name));
}
}
}
}
fn validate_use_decls(&mut self, uses: &[fsys::UseDecl]) {
// Validate individual fields.
for use_ in uses.iter() {
self.validate_use_decl(&use_);
}
self.validate_use_has_single_runner(&uses);
self.validate_use_paths(&uses);
}
fn validate_use_decl(&mut self, use_: &fsys::UseDecl) {
match use_ {
fsys::UseDecl::Service(u) => {
self.validate_use_fields(
"UseServiceDecl",
u.source.as_ref(),
u.source_path.as_ref(),
u.target_path.as_ref(),
);
}
fsys::UseDecl::Protocol(u) => {
self.validate_use_fields(
"UseProtocolDecl",
u.source.as_ref(),
u.source_path.as_ref(),
u.target_path.as_ref(),
);
}
fsys::UseDecl::Directory(u) => {
self.validate_use_fields(
"UseDirectoryDecl",
u.source.as_ref(),
u.source_path.as_ref(),
u.target_path.as_ref(),
);
if u.rights.is_none() {
self.errors.push(Error::missing_field("UseDirectoryDecl", "rights"));
}
if let Some(subdir) = u.subdir.as_ref() {
check_relative_path(
Some(subdir),
"UseDirectoryDecl",
"subdir",
&mut self.errors,
);
}
}
fsys::UseDecl::Storage(u) => match u.type_ {
None => self.errors.push(Error::missing_field("UseStorageDecl", "type")),
Some(fsys::StorageType::Meta) => {
if u.target_path.is_some() {
self.errors.push(Error::invalid_field("UseStorageDecl", "target_path"));
}
}
_ => {
check_path(
u.target_path.as_ref(),
"UseStorageDecl",
"target_path",
&mut self.errors,
);
}
},
fsys::UseDecl::Runner(r) => {
check_name(
r.source_name.as_ref(),
"UseRunnerDecl",
"source_name",
&mut self.errors,
);
}
fsys::UseDecl::Event(e) => {
self.validate_source(e.source.as_ref(), "UseEventDecl", "source");
check_name(e.source_name.as_ref(), "UseEventDecl", "source_name", &mut self.errors);
check_name(e.target_name.as_ref(), "UseEventDecl", "target_name", &mut self.errors);
}
fsys::UseDecl::__UnknownVariant { .. } => {
self.errors.push(Error::invalid_field("ComponentDecl", "use"));
}
}
}
/// Ensures that no more than one runner is specified.
fn validate_use_has_single_runner(&mut self, uses: &[fsys::UseDecl]) {
let mut runners_count: i32 = 0;
for use_ in uses.iter() {
if let fsys::UseDecl::Runner(_) = use_ {
runners_count += 1;
}
}
if runners_count > 1 {
self.errors.push(Error::multiple_runners_specified("UseRunnerDecl"));
}
}
/// Validates that Service, Protocol and Directory target paths differ.
fn validate_use_paths(&mut self, uses: &[fsys::UseDecl]) {
let mut used_ids = HashMap::new();
for use_ in uses.iter() {
match use_ {
fsys::UseDecl::Service(fsys::UseServiceDecl {
target_path: Some(path), ..
})
| fsys::UseDecl::Protocol(fsys::UseProtocolDecl {
target_path: Some(path), ..
})
| fsys::UseDecl::Directory(fsys::UseDirectoryDecl {
target_path: Some(path),
..
}) => {
let decl = match use_ {
fsys::UseDecl::Service(_) => "UseServiceDecl",
fsys::UseDecl::Protocol(_) => "UseProtocolDecl",
fsys::UseDecl::Directory(_) => "UseDirectoryDecl",
_ => unreachable!(),
};
if used_ids.insert(path, use_).is_some() {
// Disallow multiple capabilities for the same path.
self.errors.push(Error::duplicate_field(decl, "path", path));
}
}
_ => {}
}
}
}
fn validate_use_fields(
&mut self,
decl: &str,
source: Option<&fsys::Ref>,
source_path: Option<&String>,
target_path: Option<&String>,
) {
self.validate_source(source, decl, "source");
check_path(source_path, decl, "source_path", &mut self.errors);
check_path(target_path, decl, "target_path", &mut self.errors);
}
fn validate_source(&mut self, source: Option<&fsys::Ref>, decl: &str, field: &str) {
match source {
Some(fsys::Ref::Realm(_)) => {}
Some(fsys::Ref::Framework(_)) => {}
Some(_) => {
self.errors.push(Error::invalid_field(decl, field));
}
None => {
self.errors.push(Error::missing_field(decl, field));
}
};
}
fn validate_child_decl(&mut self, child: &'a fsys::ChildDecl) {
if let Err(mut e) = validate_child(child) {
self.errors.append(&mut e.errs);
}
if let Some(name) = child.name.as_ref() {
let name: &str = name;
if self.all_children.insert(name, child).is_some() {
self.errors.push(Error::duplicate_field("ChildDecl", "name", name));
}
}
if let Some(environment) = child.environment.as_ref() {
if !self.all_environment_names.contains(environment.as_str()) {
self.errors.push(Error::invalid_environment(
"ChildDecl",
"environment",
environment,
));
}
}
}
fn validate_collection_decl(&mut self, collection: &'a fsys::CollectionDecl) {
let name = collection.name.as_ref();
if check_name(name, "CollectionDecl", "name", &mut self.errors) {
let name: &str = name.unwrap();
if !self.all_collections.insert(name) {
self.errors.push(Error::duplicate_field("CollectionDecl", "name", name));
}
}
if collection.durability.is_none() {
self.errors.push(Error::missing_field("CollectionDecl", "durability"));
}
}
fn validate_environment_decl(&mut self, environment: &'a fsys::EnvironmentDecl) {
let name = environment.name.as_ref();
check_name(name, "EnvironmentDecl", "name", &mut self.errors);
if environment.extends.is_none() {
self.errors.push(Error::missing_field("EnvironmentDecl", "extends"));
}
if let Some(resolvers) = environment.resolvers.as_ref() {
let mut registered_schemes = HashSet::new();
for resolver in resolvers {
self.validate_resolver_registration(
resolver,
name.clone(),
&mut registered_schemes,
);
}
}
match environment.extends.as_ref() {
Some(fsys::EnvironmentExtends::None) => {
if environment.stop_timeout_ms.is_none() {
self.errors.push(Error::missing_field("EnvironmentDecl", "stop_timeout_ms"));
}
}
None | Some(fsys::EnvironmentExtends::Realm) => {}
}
}
fn validate_resolver_registration(
&mut self,
resolver_registration: &'a fsys::ResolverRegistration,
environment_name: Option<&'a String>,
schemes: &mut HashSet<&'a str>,
) {
check_name(
resolver_registration.resolver.as_ref(),
"ResolverRegistration",
"resolver",
&mut self.errors,
);
match &resolver_registration.source {
Some(fsys::Ref::Realm(_)) => {}
Some(fsys::Ref::Child(child_ref)) => {
// Make sure the child is valid.
if self.validate_child_ref("ResolverRegistration", "source", &child_ref) {
// Ensure there are no cycles, eg:
// environment is assigned to a child, but the environment contains a resolver
// provided by the same child.
// TODO(fxb/48128): Replace with cycle detection algorithm using //src/lib/directed_graph.
let child_name = child_ref.name.as_str();
if let Some(child_decl) = self.all_children.get(child_name) {
match (environment_name, child_decl.environment.as_ref()) {
(Some(environment_name), Some(child_environment_name))
if environment_name == child_environment_name =>
{
self.errors.push(Error::resolver_dependency_cycle());
}
_ => {}
}
}
}
}
Some(_) => {
self.errors.push(Error::invalid_field("ResolverRegistration", "source"));
}
None => {
self.errors.push(Error::missing_field("ResolverRegistration", "source"));
}
};
check_url_scheme(
resolver_registration.scheme.as_ref(),
"ResolverRegistration",
"scheme",
&mut self.errors,
);
if let Some(scheme) = resolver_registration.scheme.as_ref() {
if !schemes.insert(scheme.as_str()) {
self.errors.push(Error::duplicate_field("ResolverRegistration", "scheme", scheme));
}
}
}
fn validate_storage_decl(&mut self, storage: &'a fsys::StorageDecl) {
check_path(storage.source_path.as_ref(), "StorageDecl", "source_path", &mut self.errors);
let source_child_name = match storage.source.as_ref() {
Some(fsys::Ref::Realm(_)) => None,
Some(fsys::Ref::Self_(_)) => None,
Some(fsys::Ref::Child(child)) => {
self.validate_source_child(child, "StorageDecl");
Some(&child.name as &str)
}
Some(_) => {
self.errors.push(Error::invalid_field("StorageDecl", "source"));
None
}
None => {
self.errors.push(Error::missing_field("StorageDecl", "source"));
None
}
};
if check_name(storage.name.as_ref(), "StorageDecl", "name", &mut self.errors) {
let name = storage.name.as_ref().unwrap();
if self.all_storage_and_sources.insert(name, source_child_name).is_some() {
self.errors.push(Error::duplicate_field("StorageDecl", "name", name.as_str()));
}
}
}
fn validate_runner_decl(&mut self, runner: &'a fsys::RunnerDecl) {
let runner_source = match runner.source.as_ref() {
Some(fsys::Ref::Self_(_)) => None,
Some(fsys::Ref::Child(child)) => {
self.validate_source_child(child, "RunnerDecl");
Some(&child.name as &str)
}
Some(_) => {
self.errors.push(Error::invalid_field("RunnerDecl", "source"));
None
}
None => {
self.errors.push(Error::missing_field("RunnerDecl", "source"));
None
}
};
if check_name(runner.name.as_ref(), "RunnerDecl", "name", &mut self.errors) {
let name = runner.name.as_ref().unwrap();
if self.all_runners_and_sources.insert(name, runner_source).is_some() {
self.errors.push(Error::duplicate_field("RunnerDecl", "name", name.as_str()));
}
}
check_path(runner.source_path.as_ref(), "RunnerDecl", "source_path", &mut self.errors);
}
fn validate_resolver_decl(&mut self, resolver: &'a fsys::ResolverDecl) {
if check_name(resolver.name.as_ref(), "ResolverDecl", "name", &mut self.errors) {
let name = resolver.name.as_ref().unwrap();
if !self.all_resolvers.insert(name) {
self.errors.push(Error::duplicate_field("ResolverDecl", "name", name.as_str()));
}
}
check_path(resolver.source_path.as_ref(), "ResolverDecl", "source_path", &mut self.errors);
}
fn validate_source_child(&mut self, child: &fsys::ChildRef, decl_type: &str) {
let mut valid = true;
valid &= check_name(Some(&child.name), decl_type, "source.child.name", &mut self.errors);
valid &= if child.collection.is_some() {
self.errors.push(Error::extraneous_field(decl_type, "source.child.collection"));
false
} else {
true
};
if !valid {
return;
}
if !self.all_children.contains_key(&child.name as &str) {
self.errors.push(Error::invalid_child(decl_type, "source", &child.name as &str));
}
}
fn validate_storage_source(&mut self, source: &fsys::StorageRef, decl_type: &str) {
if check_name(Some(&source.name), decl_type, "source.storage.name", &mut self.errors) {
if !self.all_storage_and_sources.contains_key(&source.name as &str) {
self.errors.push(Error::invalid_storage(decl_type, "source", &source.name as &str));
}
}
}
fn validate_expose_decl(
&mut self,
expose: &'a fsys::ExposeDecl,
prev_target_paths: &mut HashMap<&'a str, AllowablePaths>,
prev_runner_names: &mut HashSet<&'a str>,
prev_resolver_names: &mut HashSet<&'a str>,
) {
match expose {
fsys::ExposeDecl::Service(e) => {
self.validate_expose_fields(
"ExposeServiceDecl",
AllowablePaths::Many,
e.source.as_ref(),
e.source_path.as_ref(),
e.target_path.as_ref(),
e.target.as_ref(),
prev_target_paths,
);
}
fsys::ExposeDecl::Protocol(e) => {
self.validate_expose_fields(
"ExposeProtocolDecl",
AllowablePaths::One,
e.source.as_ref(),
e.source_path.as_ref(),
e.target_path.as_ref(),
e.target.as_ref(),
prev_target_paths,
);
}
fsys::ExposeDecl::Directory(e) => {
self.validate_expose_fields(
"ExposeDirectoryDecl",
AllowablePaths::One,
e.source.as_ref(),
e.source_path.as_ref(),
e.target_path.as_ref(),
e.target.as_ref(),
prev_target_paths,
);
match e.source.as_ref() {
Some(fsys::Ref::Self_(_)) => {
if e.rights.is_none() {
self.errors.push(Error::missing_field("ExposeDirectoryDecl", "rights"));
}
}
_ => {}
}
if let Some(subdir) = e.subdir.as_ref() {
check_relative_path(
Some(subdir),
"ExposeDirectoryDecl",
"subdir",
&mut self.errors,
);
}
}
fsys::ExposeDecl::Runner(e) => {
self.validate_expose_runner_fields(e, prev_runner_names);
}
fsys::ExposeDecl::Resolver(e) => {
self.validate_expose_resolver_fields(e, prev_resolver_names);
}
fsys::ExposeDecl::__UnknownVariant { .. } => {
self.errors.push(Error::invalid_field("ComponentDecl", "expose"));
}
}
}
fn validate_expose_fields(
&mut self,
decl: &str,
allowable_paths: AllowablePaths,
source: Option<&fsys::Ref>,
source_path: Option<&String>,
target_path: Option<&'a String>,
target: Option<&fsys::Ref>,
prev_child_target_paths: &mut HashMap<&'a str, AllowablePaths>,
) {
match source {
Some(r) => match r {
fsys::Ref::Self_(_) => {}
fsys::Ref::Framework(_) => {}
fsys::Ref::Child(child) => {
self.validate_source_child(child, decl);
}
_ => {
self.errors.push(Error::invalid_field(decl, "source"));
}
},
None => {
self.errors.push(Error::missing_field(decl, "source"));
}
}
match target {
Some(r) => match r {
fsys::Ref::Realm(_) => {}
fsys::Ref::Framework(_) => {
if source != Some(&fsys::Ref::Self_(fsys::SelfRef {})) {
self.errors.push(Error::invalid_field(decl, "source"));
}
}
_ => {
self.errors.push(Error::invalid_field(decl, "target"));
}
},
None => {
self.errors.push(Error::missing_field(decl, "target"));
}
}
check_path(source_path, decl, "source_path", &mut self.errors);
if check_path(target_path, decl, "target_path", &mut self.errors) {
let target_path = target_path.unwrap();
if let Some(prev_state) = prev_child_target_paths.insert(target_path, allowable_paths) {
if prev_state == AllowablePaths::One || prev_state != allowable_paths {
self.errors.push(Error::duplicate_field(decl, "target_path", target_path));
}
}
}
}
/// Validates that the expose source is from `self`, `framework`, or a valid child.
fn validate_expose_source(
&mut self,
source: &Option<fsys::Ref>,
decl_type: &str,
field_name: &str,
) {
match source.as_ref() {
Some(fsys::Ref::Self_(_)) | Some(fsys::Ref::Framework(_)) => {}
Some(fsys::Ref::Child(child)) => {
self.validate_source_child(child, decl_type);
}
Some(_) => {
self.errors.push(Error::invalid_field(decl_type, field_name));
}
None => {
self.errors.push(Error::missing_field(decl_type, field_name));
}
};
}
/// Validates that the expose target is to `realm` or `framework`.
fn validate_expose_target(
&mut self,
target: &Option<fsys::Ref>,
decl_type: &str,
field_name: &str,
) {
match target.as_ref() {
Some(fsys::Ref::Realm(_)) => {}
Some(_) => {
self.errors.push(Error::invalid_field(decl_type, field_name));
}
None => {
self.errors.push(Error::missing_field(decl_type, field_name));
}
};
}
fn validate_expose_resolver_fields(
&mut self,
resolver: &'a fsys::ExposeResolverDecl,
prev_resolver_names: &mut HashSet<&'a str>,
) {
let decl = "ExposeResolverDecl";
self.validate_expose_source(&resolver.source, decl, "source");
self.validate_expose_target(&resolver.target, decl, "target");
check_name(resolver.source_name.as_ref(), decl, "source_name", &mut self.errors);
if check_name(resolver.target_name.as_ref(), decl, "target_name", &mut self.errors) {
// Ensure that target_name hasn't already been exposed.
let target_name = resolver.target_name.as_ref().unwrap();
if !prev_resolver_names.insert(target_name) {
self.errors.push(Error::duplicate_field(decl, "target_name", target_name));
}
}
// If the expose source is `self`, ensure we have a corresponding ResolverDecl.
if let (Some(fsys::Ref::Self_(_)), Some(ref name)) =
(&resolver.source, &resolver.source_name)
{
if !self.all_resolvers.contains(name as &str) {
self.errors.push(Error::invalid_resolver(decl, "source", name));
}
}
}
fn validate_expose_runner_fields(
&mut self,
runner: &'a fsys::ExposeRunnerDecl,
prev_runner_names: &mut HashSet<&'a str>,
) {
let decl = "ExposeRunnerDecl";
self.validate_expose_source(&runner.source, decl, "source");
self.validate_expose_target(&runner.target, decl, "target");
check_name(runner.source_name.as_ref(), decl, "source_name", &mut self.errors);
if check_name(runner.target_name.as_ref(), decl, "target_name", &mut self.errors) {
// Ensure that target_name hasn't already been exposed.
let target_name = runner.target_name.as_ref().unwrap();
if !prev_runner_names.insert(target_name) {
self.errors.push(Error::duplicate_field(decl, "target_name", target_name));
}
}
// If the expose source is `self`, ensure we have a corresponding RunnerDecl.
if let (Some(fsys::Ref::Self_(_)), Some(ref name)) = (&runner.source, &runner.source_name) {
if !self.all_runners_and_sources.contains_key(&name as &str) {
self.errors.push(Error::invalid_field(decl, "source"));
}
}
}
fn add_strong_dep(&mut self, from: Option<&'a fsys::Ref>, to: Option<&'a fsys::Ref>) {
if let Some(fsys::Ref::Child(fsys::ChildRef { name: source, .. })) = from {
if let Some(fsys::Ref::Child(fsys::ChildRef { name: target, .. })) = to {
if source == target {
// This is already its own error, don't report this as a cycle.
} else {
self.strong_dependencies.add_edge(source.as_str(), target.as_str());
}
}
}
}
fn validate_offers_decl(&mut self, offer: &'a fsys::OfferDecl) {
match offer {
fsys::OfferDecl::Service(o) => {
self.validate_offers_fields(
"OfferServiceDecl",
AllowablePaths::Many,
o.source.as_ref(),
o.source_path.as_ref(),
o.target.as_ref(),
o.target_path.as_ref(),
);
self.add_strong_dep(o.source.as_ref(), o.target.as_ref());
}
fsys::OfferDecl::Protocol(o) => {
self.validate_offers_fields(
"OfferProtocolDecl",
AllowablePaths::One,
o.source.as_ref(),
o.source_path.as_ref(),
o.target.as_ref(),
o.target_path.as_ref(),
);
if o.dependency_type.is_none() {
self.errors.push(Error::missing_field("OfferProtocolDecl", "dependency_type"));
} else if o.dependency_type == Some(fsys::DependencyType::Strong) {
self.add_strong_dep(o.source.as_ref(), o.target.as_ref());
}
}
fsys::OfferDecl::Directory(o) => {
self.validate_offers_fields(
"OfferDirectoryDecl",
AllowablePaths::One,
o.source.as_ref(),
o.source_path.as_ref(),
o.target.as_ref(),
o.target_path.as_ref(),
);
if o.dependency_type.is_none() {
self.errors.push(Error::missing_field("OfferDirectoryDecl", "dependency_type"));
} else if o.dependency_type == Some(fsys::DependencyType::Strong) {
self.add_strong_dep(o.source.as_ref(), o.target.as_ref());
}
match o.source.as_ref() {
Some(fsys::Ref::Self_(_)) => {
if o.rights.is_none() {
self.errors.push(Error::missing_field("OfferDirectoryDecl", "rights"));
}
}
_ => {}
}
if let Some(subdir) = o.subdir.as_ref() {
check_relative_path(
Some(subdir),
"OfferDirectoryDecl",
"subdir",
&mut self.errors,
);
}
}
fsys::OfferDecl::Storage(o) => {
self.validate_storage_offer_fields(
"OfferStorageDecl",
o.type_.as_ref(),
o.source.as_ref(),
o.target.as_ref(),
);
self.add_strong_dep(o.source.as_ref(), o.target.as_ref());
}
fsys::OfferDecl::Runner(o) => {
self.validate_runner_offer_fields(o);
self.add_strong_dep(o.source.as_ref(), o.target.as_ref());
}
fsys::OfferDecl::Resolver(o) => {
self.validate_resolver_offer_fields(o);
self.add_strong_dep(o.source.as_ref(), o.target.as_ref());
}
fsys::OfferDecl::Event(e) => {
self.validate_event_offer_fields(e);
}
fsys::OfferDecl::__UnknownVariant { .. } => {
self.errors.push(Error::invalid_field("ComponentDecl", "offer"));
}
}
}
/// Validates that the offer source is from `self`, `framework`, `realm`, or a valid child.
fn validate_offer_source(
&mut self,
source: &Option<fsys::Ref>,
decl_type: &str,
field_name: &str,
) {
match source.as_ref() {
Some(fsys::Ref::Self_(_))
| Some(fsys::Ref::Framework(_))
| Some(fsys::Ref::Realm(_)) => {}
Some(fsys::Ref::Child(child)) => {
self.validate_source_child(child, decl_type);
}
Some(_) => {
self.errors.push(Error::invalid_field(decl_type, field_name));
}
None => {
self.errors.push(Error::missing_field(decl_type, field_name));
}
};
}
/// Validates that the offer target is to a valid child or collection.
fn validate_offer_target(
&mut self,
target: &'a Option<fsys::Ref>,
decl_type: &str,
field_name: &str,
) -> Option<TargetId<'a>> {
match target.as_ref() {
Some(fsys::Ref::Child(child)) => {
if self.validate_child_ref(decl_type, field_name, &child) {
Some(TargetId::Component(&child.name))
} else {
None
}
}
Some(fsys::Ref::Collection(collection)) => {
if self.validate_collection_ref(decl_type, field_name, &collection) {
Some(TargetId::Collection(&collection.name))
} else {
None
}
}
Some(_) => {
self.errors.push(Error::invalid_field(decl_type, field_name));
None
}
None => {
self.errors.push(Error::missing_field(decl_type, field_name));
None
}
}
}
fn validate_offers_fields(
&mut self,
decl: &str,
allowable_paths: AllowablePaths,
source: Option<&fsys::Ref>,
source_path: Option<&String>,
target: Option<&'a fsys::Ref>,
target_path: Option<&'a String>,
) {
match source {
Some(fsys::Ref::Realm(_)) => {}
Some(fsys::Ref::Self_(_)) => {}
Some(fsys::Ref::Framework(_)) => {}
Some(fsys::Ref::Child(child)) => self.validate_source_child(child, decl),
Some(_) => self.errors.push(Error::invalid_field(decl, "source")),
None => self.errors.push(Error::missing_field(decl, "source")),
}
check_path(source_path, decl, "source_path", &mut self.errors);
match target {
Some(fsys::Ref::Child(c)) => {
self.validate_target_child(decl, allowable_paths, c, source, target_path);
}
Some(fsys::Ref::Collection(c)) => {
self.validate_target_collection(decl, allowable_paths, c, target_path);
}
Some(_) => {
self.errors.push(Error::invalid_field(decl, "target"));
}
None => {
self.errors.push(Error::missing_field(decl, "target"));
}
}
check_path(target_path, decl, "target_path", &mut self.errors);
}
fn validate_storage_offer_fields(
&mut self,
decl: &str,
type_: Option<&fsys::StorageType>,
source: Option<&'a fsys::Ref>,
target: Option<&'a fsys::Ref>,
) {
if type_.is_none() {
self.errors.push(Error::missing_field(decl, "type"));
}
let storage_source_name = match source {
Some(fsys::Ref::Realm(_)) => None,
Some(fsys::Ref::Storage(s)) => {
self.validate_storage_source(s, decl);
Some(&s.name as &str)
}
Some(_) => {
self.errors.push(Error::invalid_field(decl, "source"));
None
}
None => {
self.errors.push(Error::missing_field(decl, "source"));
None
}
};
self.validate_storage_target(decl, storage_source_name, target);
}
fn validate_runner_offer_fields(&mut self, runner: &'a fsys::OfferRunnerDecl) {
let decl = "OfferRunnerDecl";
self.validate_offer_source(&runner.source, decl, "source");
check_name(runner.source_name.as_ref(), decl, "source_name", &mut self.errors);
// If the offer source is `self`, ensure we have a corresponding RunnerDecl.
if let (Some(fsys::Ref::Self_(_)), Some(ref name)) = (&runner.source, &runner.source_name) {
if !self.all_runners_and_sources.contains_key(&name as &str) {
self.errors.push(Error::invalid_field(decl, "source"));
}
}
let target_id = self.validate_offer_target(&runner.target, decl, "target");
check_name(runner.target_name.as_ref(), decl, "target_name", &mut self.errors);
if let (Some(target_id), Some(target_name)) = (target_id, runner.target_name.as_ref()) {
// Assuming the target_name is valid, ensure the target_name isn't already used.
if !self
.offered_runner_names
.entry(target_id)
.or_insert(HashSet::new())
.insert(target_name)
{
self.errors.push(Error::duplicate_field(decl, "target_name", target_name as &str));
}
}
check_offer_target_is_not_source(&runner.target, &runner.source, decl, &mut self.errors);
}
fn validate_resolver_offer_fields(&mut self, resolver: &'a fsys::OfferResolverDecl) {
let decl = "OfferResolverDecl";
self.validate_offer_source(&resolver.source, decl, "source");
check_name(resolver.source_name.as_ref(), decl, "source_name", &mut self.errors);
// If the offer source is `self`, ensure we have a corresponding ResolverDecl.
if let (Some(fsys::Ref::Self_(_)), Some(ref name)) =
(&resolver.source, &resolver.source_name)
{
if !self.all_resolvers.contains(&name as &str) {
self.errors.push(Error::invalid_resolver(decl, "source", name));
}
}
let target_id = self.validate_offer_target(&resolver.target, decl, "target");
check_name(resolver.target_name.as_ref(), decl, "target_name", &mut self.errors);
if let (Some(target_id), Some(target_name)) = (target_id, resolver.target_name.as_ref()) {
// Assuming the target_name is valid, ensure the target_name isn't already used.
if !self
.offered_resolver_names
.entry(target_id)
.or_insert(HashSet::new())
.insert(target_name)
{
self.errors.push(Error::duplicate_field(decl, "target_name", target_name as &str));
}
}
check_offer_target_is_not_source(
&resolver.target,
&resolver.source,
decl,
&mut self.errors,
);
}
fn validate_event_offer_fields(&mut self, event: &'a fsys::OfferEventDecl) {
let decl = "OfferEventDecl";
check_name(event.source_name.as_ref(), decl, "source_name", &mut self.errors);
// Only realm and framework are valid.
match event.source {
Some(fsys::Ref::Realm(_)) => {}
Some(fsys::Ref::Framework(_)) => {}
Some(_) => {
self.errors.push(Error::invalid_field(decl, "source"));
}
None => {
self.errors.push(Error::missing_field(decl, "source"));
}
};
let target_id = self.validate_offer_target(&event.target, decl, "target");
if let (Some(target_id), Some(target_name)) = (target_id, event.target_name.as_ref()) {
// Assuming the target_name is valid, ensure the target_name isn't already used.
if !self
.offered_event_names
.entry(target_id)
.or_insert(HashSet::new())
.insert(target_name)
{
self.errors.push(Error::duplicate_field(decl, "target_name", target_name as &str));
}
}
check_name(event.target_name.as_ref(), decl, "target_name", &mut self.errors);
}
/// Check a `ChildRef` contains a valid child that exists.
///
/// We ensure the target child is statically defined (i.e., not a dynamic child inside
/// a collection).
fn validate_child_ref(&mut self, decl: &str, field_name: &str, child: &fsys::ChildRef) -> bool {
// Ensure the name is valid, and the reference refers to a static child.
//
// We attempt to list all errors if possible.
let mut valid = true;
if !check_name(
Some(&child.name),
decl,
&format!("{}.child.name", field_name),
&mut self.errors,
) {
valid = false;
}
if child.collection.is_some() {
self.errors
.push(Error::extraneous_field(decl, format!("{}.child.collection", field_name)));
valid = false;
}
if !valid {
return false;
}
// Ensure the child exists.
let name: &str = &child.name;
if !self.all_children.contains_key(name) {
self.errors.push(Error::invalid_child(decl, field_name, name));
return false;
}
true
}
/// Check a `CollectionRef` is valid and refers to an existing collection.
fn validate_collection_ref(
&mut self,
decl: &str,
field_name: &str,
collection: &fsys::CollectionRef,
) -> bool {
// Ensure the name is valid.
if !check_name(
Some(&collection.name),
decl,
&format!("{}.collection.name", field_name),
&mut self.errors,
) {
return false;
}
// Ensure the collection exists.
if !self.all_collections.contains(&collection.name as &str) {
self.errors.push(Error::invalid_collection(decl, field_name, &collection.name as &str));
return false;
}
true
}
fn validate_target_child(
&mut self,
decl: &str,
allowable_paths: AllowablePaths,
child: &'a fsys::ChildRef,
source: Option<&fsys::Ref>,
target_path: Option<&'a String>,
) {
if !self.validate_child_ref(decl, "target", child) {
return;
}
if let Some(target_path) = target_path {
let paths_for_target =
self.target_paths.entry(TargetId::Component(&child.name)).or_insert(HashMap::new());
if let Some(prev_state) = paths_for_target.insert(target_path, allowable_paths) {
if prev_state == AllowablePaths::One || prev_state != allowable_paths {
self.errors.push(Error::duplicate_field(
decl,
"target_path",
target_path as &str,
));
}
}
if let Some(source) = source {
if let fsys::Ref::Child(source_child) = source {
if source_child.name == child.name {
self.errors
.push(Error::offer_target_equals_source(decl, &child.name as &str));
}
}
}
}
}
fn validate_target_collection(
&mut self,
decl: &str,
allowable_paths: AllowablePaths,
collection: &'a fsys::CollectionRef,
target_path: Option<&'a String>,
) {
if !self.validate_collection_ref(decl, "target", &collection) {
return;
}
if let Some(target_path) = target_path {
let paths_for_target = self
.target_paths
.entry(TargetId::Collection(&collection.name))
.or_insert(HashMap::new());
if let Some(prev_state) = paths_for_target.insert(target_path, allowable_paths) {
if prev_state == AllowablePaths::One || prev_state != allowable_paths {
self.errors.push(Error::duplicate_field(
decl,
"target_path",
target_path as &str,
));
}
}
}
}
fn validate_storage_target(
&mut self,
decl: &str,
storage_source_name: Option<&'a str>,
target: Option<&'a fsys::Ref>,
) {
match target {
Some(fsys::Ref::Child(c)) => {
if !self.validate_child_ref(decl, "target", &c) {
return;
}
let name = &c.name;
if let Some(source_name) = storage_source_name {
if self.all_storage_and_sources.get(source_name) == Some(&Some(name)) {
self.errors.push(Error::offer_target_equals_source(decl, name));
}
}
}
Some(fsys::Ref::Collection(c)) => {
self.validate_collection_ref(decl, "target", &c);
}
Some(_) => self.errors.push(Error::invalid_field(decl, "target")),
None => self.errors.push(Error::missing_field(decl, "target")),
}
}
}
fn check_presence_and_length(
max_len: usize,
prop: Option<&String>,
decl_type: &str,
keyword: &str,
errors: &mut Vec<Error>,
) {
match prop {
Some(prop) if prop.len() == 0 => errors.push(Error::empty_field(decl_type, keyword)),
Some(prop) if prop.len() > max_len => {
errors.push(Error::field_too_long(decl_type, keyword))
}
Some(_) => (),
None => errors.push(Error::missing_field(decl_type, keyword)),
}
}
fn check_path(
prop: Option<&String>,
decl_type: &str,
keyword: &str,
errors: &mut Vec<Error>,
) -> bool {
let start_err_len = errors.len();
check_presence_and_length(MAX_PATH_LENGTH, prop, decl_type, keyword, errors);
if let Some(path) = prop {
// Paths must be more than 1 character long
if path.len() < 2 {
errors.push(Error::invalid_field(decl_type, keyword));
return false;
}
// Paths must start with `/`
if !path.starts_with('/') {
errors.push(Error::invalid_field(decl_type, keyword));
return false;
}
// Paths cannot have two `/`s in a row
if path.contains("//") {
errors.push(Error::invalid_field(decl_type, keyword));
return false;
}
// Paths cannot end with `/`
if path.ends_with('/') {
errors.push(Error::invalid_field(decl_type, keyword));
return false;
}
}
start_err_len == errors.len()
}
fn check_relative_path(
prop: Option<&String>,
decl_type: &str,
keyword: &str,
errors: &mut Vec<Error>,
) -> bool {
let start_err_len = errors.len();
check_presence_and_length(MAX_PATH_LENGTH, prop, decl_type, keyword, errors);
if let Some(path) = prop {
// Relative paths must be nonempty
if path.is_empty() {
errors.push(Error::invalid_field(decl_type, keyword));
return false;
}
// Relative paths cannot start with `/`
if path.starts_with('/') {
errors.push(Error::invalid_field(decl_type, keyword));
return false;
}
// Relative paths cannot have two `/`s in a row
if path.contains("//") {
errors.push(Error::invalid_field(decl_type, keyword));
return false;
}
// Relative paths cannot end with `/`
if path.ends_with('/') {
errors.push(Error::invalid_field(decl_type, keyword));
return false;
}
}
start_err_len == errors.len()
}
fn check_name(
prop: Option<&String>,
decl_type: &str,
keyword: &str,
errors: &mut Vec<Error>,
) -> bool {
let start_err_len = errors.len();
check_presence_and_length(MAX_NAME_LENGTH, prop, decl_type, keyword, errors);
if let Some(name) = prop {
for b in name.bytes() {
match b as char {
'0'..='9' | 'a'..='z' | '_' | '-' | '.' => (),
c => {
errors.push(Error::invalid_character_in_field(decl_type, keyword, c));
return false;
}
}
}
}
start_err_len == errors.len()
}
fn check_url(
prop: Option<&String>,
decl_type: &str,
keyword: &str,
errors: &mut Vec<Error>,
) -> bool {
let start_err_len = errors.len();
check_presence_and_length(MAX_URL_LENGTH, prop, decl_type, keyword, errors);
if let Some(url) = prop {
let mut chars_iter = url.chars();
let mut first_char = true;
while let Some(c) = chars_iter.next() {
match c {
'0'..='9' | 'a'..='z' | '+' | '-' | '.' => first_char = false,
':' => {
if first_char {
// There must be at least one character in the schema
errors.push(Error::invalid_field(decl_type, keyword));
return false;
}
// Once a `:` character is found, it must be followed by two `/` characters and
// then at least one more character. Note that these sequential calls to
// `.next()` without checking the result won't panic because `Chars` implements
// `FusedIterator`.
match (chars_iter.next(), chars_iter.next(), chars_iter.next()) {
(Some('/'), Some('/'), Some(_)) => return start_err_len == errors.len(),
_ => {
errors.push(Error::invalid_field(decl_type, keyword));
return false;
}
}
}
c => {
errors.push(Error::invalid_character_in_field(decl_type, keyword, c));
return false;
}
}
}
// If we've reached here then the string terminated unexpectedly
errors.push(Error::invalid_field(decl_type, keyword));
}
start_err_len == errors.len()
}
fn check_url_scheme(
prop: Option<&String>,
decl_type: &str,
keyword: &str,
errors: &mut Vec<Error>,
) -> bool {
if let Some(scheme) = prop {
if let Err(err) = cm_types::UrlScheme::validate(scheme) {
errors.push(match err {
cm_types::UrlSchemeValidationError::InvalidLength(0) => {
Error::empty_field(decl_type, keyword)
}
cm_types::UrlSchemeValidationError::InvalidLength(_) => {
Error::field_too_long(decl_type, keyword)
}
cm_types::UrlSchemeValidationError::MalformedUrlScheme(c) => {
Error::invalid_character_in_field(decl_type, keyword, c)
}
});
return false;
}
} else {
errors.push(Error::missing_field(decl_type, keyword));
return false;
}
true
}
/// Checks that the offer target is not the same as the offer source.
fn check_offer_target_is_not_source(
target: &Option<fsys::Ref>,
source: &Option<fsys::Ref>,
decl_type: &str,
errors: &mut Vec<Error>,
) -> bool {
match (source, target) {
(Some(fsys::Ref::Child(ref source_child)), Some(fsys::Ref::Child(ref target_child))) => {
if source_child.name == target_child.name {
errors.push(Error::offer_target_equals_source(decl_type, &target_child.name));
return false;
}
}
_ => {}
};
true
}
#[cfg(test)]
mod tests {
use {
super::*,
fidl_fuchsia_data as fdata, fidl_fuchsia_io2 as fio2,
fidl_fuchsia_sys2::{
ChildDecl, ChildRef, CollectionDecl, CollectionRef, ComponentDecl, DependencyType,
Durability, EnvironmentDecl, EnvironmentExtends, ExposeDecl, ExposeDirectoryDecl,
ExposeProtocolDecl, ExposeResolverDecl, ExposeRunnerDecl, ExposeServiceDecl,
FrameworkRef, OfferDecl, OfferDirectoryDecl, OfferEventDecl, OfferProtocolDecl,
OfferResolverDecl, OfferRunnerDecl, OfferServiceDecl, OfferStorageDecl, RealmRef, Ref,
ResolverDecl, ResolverRegistration, RunnerDecl, SelfRef, StartupMode, StorageDecl,
StorageRef, StorageType, UseDecl, UseDirectoryDecl, UseEventDecl, UseProtocolDecl,
UseRunnerDecl, UseServiceDecl, UseStorageDecl,
},
lazy_static::lazy_static,
proptest::prelude::*,
regex::Regex,
};
const PATH_REGEX_STR: &str = r"(/[^/]+)+";
const NAME_REGEX_STR: &str = r"[0-9a-z_\-\.]+";
const URL_REGEX_STR: &str = r"[0-9a-z\+\-\.]+://.+";
lazy_static! {
static ref PATH_REGEX: Regex =
Regex::new(&("^".to_string() + PATH_REGEX_STR + "$")).unwrap();
static ref NAME_REGEX: Regex =
Regex::new(&("^".to_string() + NAME_REGEX_STR + "$")).unwrap();
static ref URL_REGEX: Regex = Regex::new(&("^".to_string() + URL_REGEX_STR + "$")).unwrap();
}
proptest! {
#[test]
fn check_path_matches_regex(s in PATH_REGEX_STR) {
if s.len() < MAX_PATH_LENGTH {
let mut errors = vec![];
prop_assert!(check_path(Some(&s), "", "", &mut errors));
prop_assert!(errors.is_empty());
}
}
#[test]
fn check_name_matches_regex(s in NAME_REGEX_STR) {
if s.len() < MAX_NAME_LENGTH {
let mut errors = vec![];
prop_assert!(check_name(Some(&s), "", "", &mut errors));
prop_assert!(errors.is_empty());
}
}
#[test]
fn check_url_matches_regex(s in URL_REGEX_STR) {
if s.len() < MAX_URL_LENGTH {
let mut errors = vec![];
prop_assert!(check_url(Some(&s), "", "", &mut errors));
prop_assert!(errors.is_empty());
}
}
#[test]
fn check_path_fails_invalid_input(s in ".*") {
if !PATH_REGEX.is_match(&s) {
let mut errors = vec![];
prop_assert!(!check_path(Some(&s), "", "", &mut errors));
prop_assert!(!errors.is_empty());
}
}
#[test]
fn check_name_fails_invalid_input(s in ".*") {
if !NAME_REGEX.is_match(&s) {
let mut errors = vec![];
prop_assert!(!check_name(Some(&s), "", "", &mut errors));
prop_assert!(!errors.is_empty());
}
}
#[test]
fn check_url_fails_invalid_input(s in ".*") {
if !URL_REGEX.is_match(&s) {
let mut errors = vec![];
prop_assert!(!check_url(Some(&s), "", "", &mut errors));
prop_assert!(!errors.is_empty());
}
}
}
fn validate_test(input: ComponentDecl, expected_res: Result<(), ErrorList>) {
let res = validate(&input);
assert_eq!(format!("{:?}", res), format!("{:?}", expected_res));
}
fn check_test<F>(check_fn: F, input: &str, expected_res: Result<(), ErrorList>)
where
F: FnOnce(Option<&String>, &str, &str, &mut Vec<Error>) -> bool,
{
let mut errors = vec![];
let res: Result<(), ErrorList> =
match check_fn(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,
storage: None,
children: None,
collections: None,
runners: None,
resolvers: None,
environments: 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", "source", "child")),
"\"child\" is referenced in Decl.source but it does not appear in children"
);
assert_eq!(
format!("{}", Error::invalid_collection("Decl", "source", "child")),
"\"child\" is referenced in Decl.source but it does not appear in collections"
);
assert_eq!(
format!("{}", Error::invalid_storage("Decl", "source", "name")),
"\"name\" is referenced in Decl.source but it does not appear in storage"
);
assert_eq!(
format!("{}", Error::multiple_runners_specified("Decl")),
"Decl specifies multiple runners"
);
}
macro_rules! test_validate {
(
$(
$test_name:ident => {
input = $input:expr,
result = $result:expr,
},
)+
) => {
$(
#[test]
fn $test_name() {
validate_test($input, $result);
}
)+
}
}
macro_rules! test_string_checks {
(
$(
$test_name:ident => {
check_fn = $check_fn:expr,
input = $input:expr,
result = $result:expr,
},
)+
) => {
$(
#[test]
fn $test_name() {
check_test($check_fn, $input, $result);
}
)+
}
}
macro_rules! test_dependency {
(
$(
$test_name:ident => {
ty = $ty:expr,
offer_decl = $offer_decl:expr,
},
)+
) => {
$(
#[test]
fn $test_name() {
let mut decl = new_component_decl();
let dependencies = vec![
("a", "b"),
("b", "a"),
];
let offers = dependencies.into_iter().map(|(from,to)| {
let mut offer_decl = $offer_decl;
offer_decl.source = Some(Ref::Child(
ChildRef { name: from.to_string(), collection: None },
));
offer_decl.target = Some(Ref::Child(
ChildRef { name: to.to_string(), collection: None },
));
$ty(offer_decl)
}).collect();
let children = ["a", "b"].iter().map(|name| {
ChildDecl {
name: Some(name.to_string()),
url: Some(format!("fuchsia-pkg://fuchsia.com/pkg#meta/{}.cm", name)),
startup: Some(StartupMode::Lazy),
environment: None,
}
}).collect();
decl.offers = Some(offers);
decl.children = Some(children);
let result = Err(ErrorList::new(vec![
Error::offer_dependency_cycle(),
]));
validate_test(decl, result);
}
)+
}
}
macro_rules! test_weak_dependency {
(
$(
$test_name:ident => {
ty = $ty:expr,
offer_decl = $offer_decl:expr,
},
)+
) => {
$(
#[test]
fn $test_name() {
let mut decl = new_component_decl();
let offers = vec![
{
let mut offer_decl = $offer_decl;
offer_decl.source = Some(Ref::Child(
ChildRef { name: "a".to_string(), collection: None },
));
offer_decl.target = Some(Ref::Child(
ChildRef { name: "b".to_string(), collection: None },
));
offer_decl.dependency_type = Some(DependencyType::Strong);
$ty(offer_decl)
},
{
let mut offer_decl = $offer_decl;
offer_decl.source = Some(Ref::Child(
ChildRef { name: "b".to_string(), collection: None },
));
offer_decl.target = Some(Ref::Child(
ChildRef { name: "a".to_string(), collection: None },
));
offer_decl.dependency_type = Some(DependencyType::WeakForMigration);
$ty(offer_decl)
},
];
let children = ["a", "b"].iter().map(|name| {
ChildDecl {
name: Some(name.to_string()),
url: Some(format!("fuchsia-pkg://fuchsia.com/pkg#meta/{}.cm", name)),
startup: Some(StartupMode::Lazy),
environment: None,
}
}).collect();
decl.offers = Some(offers);
decl.children = Some(children);
let result = Ok(());
validate_test(decl, result);
}
)+
}
}
test_string_checks! {
// path
test_identifier_path_valid => {
check_fn = check_path,
input = "/foo/bar",
result = Ok(()),
},
test_identifier_path_invalid_empty => {
check_fn = check_path,
input = "",
result = Err(ErrorList::new(vec![
Error::empty_field("FooDecl", "foo"),
Error::invalid_field("FooDecl", "foo"),
])),
},
test_identifier_path_invalid_root => {
check_fn = check_path,
input = "/",
result = Err(ErrorList::new(vec![Error::invalid_field("FooDecl", "foo")])),
},
test_identifier_path_invalid_relative => {
check_fn = check_path,
input = "foo/bar",
result = Err(ErrorList::new(vec![Error::invalid_field("FooDecl", "foo")])),
},
test_identifier_path_invalid_trailing => {
check_fn = check_path,
input = "/foo/bar/",
result = Err(ErrorList::new(vec![Error::invalid_field("FooDecl", "foo")])),
},
test_identifier_path_too_long => {
check_fn = check_path,
input = &format!("/{}", "a".repeat(1024)),
result = Err(ErrorList::new(vec![Error::field_too_long("FooDecl", "foo")])),
},
// name
test_identifier_name_valid => {
check_fn = check_name,
input = "abcdefghijklmnopqrstuvwxyz0123456789_-.",
result = Ok(()),
},
test_identifier_name_invalid => {
check_fn = check_name,
input = "^bad",
result = Err(ErrorList::new(vec![Error::invalid_character_in_field("FooDecl", "foo", '^')])),
},
test_identifier_name_too_long => {
check_fn = check_name,
input = &format!("{}", "a".repeat(101)),
result = Err(ErrorList::new(vec![Error::field_too_long("FooDecl", "foo")])),
},
// url
test_identifier_url_valid => {
check_fn = check_url,
input = "my+awesome-scheme.2://abc123!@#$%.com",
result = Ok(()),
},
test_identifier_url_invalid => {
check_fn = check_url,
input = "fuchsia-pkg://",
result = Err(ErrorList::new(vec![Error::invalid_field("FooDecl", "foo")])),
},
test_identifier_url_too_long => {
check_fn = check_url,
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::Service(UseServiceDecl {
source: None,
source_path: None,
target_path: None,
}),
UseDecl::Protocol(UseProtocolDecl {
source: None,
source_path: None,
target_path: None,
}),
UseDecl::Directory(UseDirectoryDecl {
source: None,
source_path: None,
target_path: None,
rights: None,
subdir: None,
}),
UseDecl::Storage(UseStorageDecl {
type_: None,
target_path: None,
}),
UseDecl::Storage(UseStorageDecl {
type_: Some(StorageType::Cache),
target_path: None,
}),
UseDecl::Runner(UseRunnerDecl {
source_name: None,
}),
UseDecl::Event(UseEventDecl {
source: None,
source_name: None,
target_name: None,
filter: None,
})
]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("UseServiceDecl", "source"),
Error::missing_field("UseServiceDecl", "source_path"),
Error::missing_field("UseServiceDecl", "target_path"),
Error::missing_field("UseProtocolDecl", "source"),
Error::missing_field("UseProtocolDecl", "source_path"),
Error::missing_field("UseProtocolDecl", "target_path"),
Error::missing_field("UseDirectoryDecl", "source"),
Error::missing_field("UseDirectoryDecl", "source_path"),
Error::missing_field("UseDirectoryDecl", "target_path"),
Error::missing_field("UseDirectoryDecl", "rights"),
Error::missing_field("UseStorageDecl", "type"),
Error::missing_field("UseStorageDecl", "target_path"),
Error::missing_field("UseRunnerDecl", "source_name"),
Error::missing_field("UseEventDecl", "source"),
Error::missing_field("UseEventDecl", "source_name"),
Error::missing_field("UseEventDecl", "target_name"),
])),
},
test_validate_uses_invalid_identifiers_service => {
input = {
let mut decl = new_component_decl();
decl.uses = Some(vec![
UseDecl::Service(UseServiceDecl {
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
source_path: Some("foo/".to_string()),
target_path: Some("/".to_string()),
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_field("UseServiceDecl", "source"),
Error::invalid_field("UseServiceDecl", "source_path"),
Error::invalid_field("UseServiceDecl", "target_path"),
])),
},
test_validate_uses_invalid_identifiers_protocol => {
input = {
let mut decl = new_component_decl();
decl.uses = Some(vec![
UseDecl::Protocol(UseProtocolDecl {
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
source_path: Some("foo/".to_string()),
target_path: Some("/".to_string()),
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_field("UseProtocolDecl", "source"),
Error::invalid_field("UseProtocolDecl", "source_path"),
Error::invalid_field("UseProtocolDecl", "target_path"),
])),
},
test_validate_uses_invalid_identifiers => {
input = {
let mut decl = new_component_decl();
decl.uses = Some(vec![
UseDecl::Directory(UseDirectoryDecl {
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
source_path: Some("foo/".to_string()),
target_path: Some("/".to_string()),
rights: Some(fio2::Operations::Connect),
subdir: Some("/foo".to_string()),
}),
UseDecl::Storage(UseStorageDecl {
type_: Some(StorageType::Cache),
target_path: Some("/".to_string()),
}),
UseDecl::Storage(UseStorageDecl {
type_: Some(StorageType::Meta),
target_path: Some("/meta".to_string()),
}),
UseDecl::Event(UseEventDecl {
source: Some(fsys::Ref::Self_(fsys::SelfRef {})),
source_name: Some("/foo".to_string()),
target_name: Some("/foo".to_string()),
filter: Some(fdata::Dictionary { entries: None }),
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_field("UseDirectoryDecl", "source"),
Error::invalid_field("UseDirectoryDecl", "source_path"),
Error::invalid_field("UseDirectoryDecl", "target_path"),
Error::invalid_field("UseDirectoryDecl", "subdir"),
Error::invalid_field("UseStorageDecl", "target_path"),
Error::invalid_field("UseStorageDecl", "target_path"),
Error::invalid_field("UseEventDecl", "source"),
Error::invalid_character_in_field("UseEventDecl", "source_name", '/'),
Error::invalid_character_in_field("UseEventDecl", "target_name", '/'),
])),
},
test_validate_uses_multiple_runners => {
input = {
let mut decl = new_component_decl();
decl.uses = Some(vec![
UseDecl::Runner(UseRunnerDecl {
source_name: Some("elf".to_string()),
}),
UseDecl::Runner(UseRunnerDecl {
source_name: Some("elf".to_string()),
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::multiple_runners_specified("UseRunnerDecl"),
])),
},
test_validate_uses_long_identifiers => {
input = {
let mut decl = new_component_decl();
decl.uses = Some(vec![
UseDecl::Service(UseServiceDecl {
source: Some(fsys::Ref::Realm(fsys::RealmRef {})),
source_path: Some(format!("/{}", "a".repeat(1024))),
target_path: Some(format!("/{}", "b".repeat(1024))),
}),
UseDecl::Protocol(UseProtocolDecl {
source: Some(fsys::Ref::Realm(fsys::RealmRef {})),
source_path: Some(format!("/{}", "a".repeat(1024))),
target_path: Some(format!("/{}", "c".repeat(1024))),
}),
UseDecl::Directory(UseDirectoryDecl {
source: Some(fsys::Ref::Realm(fsys::RealmRef {})),
source_path: Some(format!("/{}", "a".repeat(1024))),
target_path: Some(format!("/{}", "d".repeat(1024))),
rights: Some(fio2::Operations::Connect),
subdir: None,
}),
UseDecl::Storage(UseStorageDecl {
type_: Some(StorageType::Cache),
target_path: Some(format!("/{}", "e".repeat(1024))),
}),
UseDecl::Runner(UseRunnerDecl {
source_name: Some(format!("{}", "a".repeat(101))),
}),
UseDecl::Event(UseEventDecl {
source: Some(fsys::Ref::Realm(fsys::RealmRef {})),
source_name: Some(format!("{}", "a".repeat(101))),
target_name: Some(format!("{}", "a".repeat(101))),
filter: None,
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::field_too_long("UseServiceDecl", "source_path"),
Error::field_too_long("UseServiceDecl", "target_path"),
Error::field_too_long("UseProtocolDecl", "source_path"),
Error::field_too_long("UseProtocolDecl", "target_path"),
Error::field_too_long("UseDirectoryDecl", "source_path"),
Error::field_too_long("UseDirectoryDecl", "target_path"),
Error::field_too_long("UseStorageDecl", "target_path"),
Error::field_too_long("UseRunnerDecl", "source_name"),
Error::field_too_long("UseEventDecl", "source_name"),
Error::field_too_long("UseEventDecl", "target_name"),
])),
},
test_validate_conflicting_paths => {
input = {
let mut decl = new_component_decl();
decl.uses = Some(vec![
UseDecl::Service(UseServiceDecl {
source: Some(fsys::Ref::Realm(fsys::RealmRef {})),
source_path: Some("/foo".to_string()),
target_path: Some("/bar".to_string()),
}),
UseDecl::Service(UseServiceDecl {
source: Some(fsys::Ref::Realm(fsys::RealmRef {})),
source_path: Some("/space".to_string()),
target_path: Some("/bar".to_string()),
}),
UseDecl::Protocol(UseProtocolDecl {
source: Some(fsys::Ref::Realm(fsys::RealmRef {})),
source_path: Some("/space".to_string()),
target_path: Some("/bar".to_string()),
}),
UseDecl::Directory(UseDirectoryDecl {
source: Some(fsys::Ref::Realm(fsys::RealmRef {})),
source_path: Some("/crow".to_string()),
target_path: Some("/bar".to_string()),
rights: Some(fio2::Operations::Connect),
subdir: None,
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::duplicate_field("UseServiceDecl", "path", "/bar"),
Error::duplicate_field("UseProtocolDecl", "path", "/bar"),
Error::duplicate_field("UseDirectoryDecl", "path", "/bar"),
])),
},
// exposes
test_validate_exposes_empty => {
input = {
let mut decl = new_component_decl();
decl.exposes = Some(vec![
ExposeDecl::Service(ExposeServiceDecl {
source: None,
source_path: None,
target_path: None,
target: None,
}),
ExposeDecl::Protocol(ExposeProtocolDecl {
source: None,
source_path: None,
target_path: None,
target: None,
}),
ExposeDecl::Directory(ExposeDirectoryDecl {
source: None,
source_path: None,
target_path: None,
target: None,
rights: None,
subdir: None,
}),
ExposeDecl::Runner(ExposeRunnerDecl {
source: None,
source_name: None,
target: None,
target_name: None,
}),
ExposeDecl::Resolver(ExposeResolverDecl {
source: None,
source_name: None,
target: None,
target_name: None,
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("ExposeServiceDecl", "source"),
Error::missing_field("ExposeServiceDecl", "target"),
Error::missing_field("ExposeServiceDecl", "source_path"),
Error::missing_field("ExposeServiceDecl", "target_path"),
Error::missing_field("ExposeProtocolDecl", "source"),
Error::missing_field("ExposeProtocolDecl", "target"),
Error::missing_field("ExposeProtocolDecl", "source_path"),
Error::missing_field("ExposeProtocolDecl", "target_path"),
Error::missing_field("ExposeDirectoryDecl", "source"),
Error::missing_field("ExposeDirectoryDecl", "target"),
Error::missing_field("ExposeDirectoryDecl", "source_path"),
Error::missing_field("ExposeDirectoryDecl", "target_path"),
Error::missing_field("ExposeRunnerDecl", "source"),
Error::missing_field("ExposeRunnerDecl", "target"),
Error::missing_field("ExposeRunnerDecl", "source_name"),
Error::missing_field("ExposeRunnerDecl", "target_name"),
Error::missing_field("ExposeResolverDecl", "source"),
Error::missing_field("ExposeResolverDecl", "target"),
Error::missing_field("ExposeResolverDecl", "source_name"),
Error::missing_field("ExposeResolverDecl", "target_name"),
])),
},
test_validate_exposes_extraneous => {
input = {
let mut decl = new_component_decl();
decl.exposes = Some(vec![
ExposeDecl::Service(ExposeServiceDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: Some("modular".to_string()),
})),
source_path: Some("/svc/logger".to_string()),
target_path: Some("/svc/logger".to_string()),
target: Some(Ref::Realm(RealmRef {})),
}),
ExposeDecl::Protocol(ExposeProtocolDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: Some("modular".to_string()),
})),
source_path: Some("/svc/legacy_logger".to_string()),
target_path: Some("/svc/legacy_logger".to_string()),
target: Some(Ref::Realm(RealmRef {})),
}),
ExposeDecl::Directory(ExposeDirectoryDecl {
source: Some(Ref::Child(ChildRef {
name: "netstack".to_string(),
collection: Some("modular".to_string()),
})),
source_path: Some("/data".to_string()),
target_path: Some("/data".to_string()),
target: Some(Ref::Realm(RealmRef {})),
rights: Some(fio2::Operations::Connect),
subdir: None,
}),
ExposeDecl::Runner(ExposeRunnerDecl {
source: Some(Ref::Child(ChildRef {
name: "netstack".to_string(),
collection: Some("modular".to_string()),
})),
source_name: Some("elf".to_string()),
target: Some(Ref::Realm(RealmRef {})),
target_name: Some("elf".to_string()),
}),
ExposeDecl::Resolver(ExposeResolverDecl {
source: Some(Ref::Child(ChildRef {
name: "netstack".to_string(),
collection: Some("modular".to_string()),
})),
source_name: Some("pkg".to_string()),
target: Some(Ref::Realm(RealmRef {})),
target_name: Some("pkg".to_string()),
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::extraneous_field("ExposeServiceDecl", "source.child.collection"),
Error::extraneous_field("ExposeProtocolDecl", "source.child.collection"),
Error::extraneous_field("ExposeDirectoryDecl", "source.child.collection"),
Error::extraneous_field("ExposeRunnerDecl", "source.child.collection"),
Error::extraneous_field("ExposeResolverDecl", "source.child.collection"),
])),
},
test_validate_exposes_rights => {
input = {
let mut decl = new_component_decl();
decl.exposes = Some(vec![
ExposeDecl::Directory(ExposeDirectoryDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/data/a".to_string()),
target_path: Some("/data/b".to_string()),
target: Some(Ref::Framework(FrameworkRef {})),
rights: None,
subdir: None,
})
]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("ExposeDirectoryDecl", "rights"),
])),
},
test_validate_exposes_invalid_identifiers => {
input = {
let mut decl = new_component_decl();
decl.exposes = Some(vec![
ExposeDecl::Service(ExposeServiceDecl {
source: Some(Ref::Child(ChildRef {
name: "^bad".to_string(),
collection: None,
})),
source_path: Some("foo/".to_string()),
target_path: Some("/".to_string()),
target: Some(Ref::Realm(RealmRef {})),
}),
ExposeDecl::Protocol(ExposeProtocolDecl {
source: Some(Ref::Child(ChildRef {
name: "^bad".to_string(),
collection: None,
})),
source_path: Some("foo/".to_string()),
target_path: Some("/".to_string()),
target: Some(Ref::Realm(RealmRef {})),
}),
ExposeDecl::Directory(ExposeDirectoryDecl {
source: Some(Ref::Child(ChildRef {
name: "^bad".to_string(),
collection: None,
})),
source_path: Some("foo/".to_string()),
target_path: Some("/".to_string()),
target: Some(Ref::Realm(RealmRef {})),
rights: Some(fio2::Operations::Connect),
subdir: Some("/foo".to_string()),
}),
ExposeDecl::Runner(ExposeRunnerDecl {
source: Some(Ref::Child(ChildRef {
name: "^bad".to_string(),
collection: None,
})),
source_name: Some("/path".to_string()),
target: Some(Ref::Realm(RealmRef {})),
target_name: Some("elf!".to_string()),
}),
ExposeDecl::Resolver(ExposeResolverDecl {
source: Some(Ref::Child(ChildRef {
name: "^bad".to_string(),
collection: None,
})),
source_name: Some("/path".to_string()),
target: Some(Ref::Realm(RealmRef {})),
target_name: Some("pkg!".to_string()),
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_character_in_field("ExposeServiceDecl", "source.child.name", '^'),
Error::invalid_field("ExposeServiceDecl", "source_path"),
Error::invalid_field("ExposeServiceDecl", "target_path"),
Error::invalid_character_in_field("ExposeProtocolDecl", "source.child.name", '^'),
Error::invalid_field("ExposeProtocolDecl", "source_path"),
Error::invalid_field("ExposeProtocolDecl", "target_path"),
Error::invalid_character_in_field("ExposeDirectoryDecl", "source.child.name", '^'),
Error::invalid_field("ExposeDirectoryDecl", "source_path"),
Error::invalid_field("ExposeDirectoryDecl", "target_path"),
Error::invalid_field("ExposeDirectoryDecl", "subdir"),
Error::invalid_character_in_field("ExposeRunnerDecl", "source.child.name", '^'),
Error::invalid_character_in_field("ExposeRunnerDecl", "source_name", '/'),
Error::invalid_character_in_field("ExposeRunnerDecl", "target_name", '!'),
Error::invalid_character_in_field("ExposeResolverDecl", "source.child.name", '^'),
Error::invalid_character_in_field("ExposeResolverDecl", "source_name", '/'),
Error::invalid_character_in_field("ExposeResolverDecl", "target_name", '!'),
])),
},
test_validate_exposes_invalid_source_target => {
input = {
let mut decl = new_component_decl();
decl.children = Some(vec![ChildDecl{
name: Some("logger".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()),
startup: Some(StartupMode::Lazy),
environment: None,
}]);
decl.exposes = Some(vec![
ExposeDecl::Service(ExposeServiceDecl {
source: None,
source_path: Some("/a".to_string()),
target_path: Some("/b".to_string()),
target: None,
}),
ExposeDecl::Protocol(ExposeProtocolDecl {
source: Some(Ref::Realm(RealmRef {})),
source_path: Some("/c".to_string()),
target_path: Some("/d".to_string()),
target: Some(Ref::Self_(SelfRef {})),
}),
ExposeDecl::Directory(ExposeDirectoryDecl {
source: Some(Ref::Collection(CollectionRef {name: "z".to_string()})),
source_path: Some("/e".to_string()),
target_path: Some("/f".to_string()),
target: Some(Ref::Collection(CollectionRef {name: "z".to_string()})),
rights: Some(fio2::Operations::Connect),
subdir: None,
}),
ExposeDecl::Directory(ExposeDirectoryDecl {
source: Some(Ref::Storage(StorageRef {name: "a".to_string()})),
source_path: Some("/g".to_string()),
target_path: Some("/h".to_string()),
target: Some(Ref::Storage(StorageRef {name: "a".to_string()})),
rights: Some(fio2::Operations::Connect),
subdir: None,
}),
ExposeDecl::Runner(ExposeRunnerDecl {
source: Some(Ref::Storage(StorageRef {name: "a".to_string()})),
source_name: Some("a".to_string()),
target: Some(Ref::Framework(FrameworkRef {})),
target_name: Some("b".to_string()),
}),
ExposeDecl::Resolver(ExposeResolverDecl {
source: Some(Ref::Storage(StorageRef {name: "a".to_string()})),
source_name: Some("a".to_string()),
target: Some(Ref::Framework(FrameworkRef {})),
target_name: Some("b".to_string()),
}),
ExposeDecl::Directory(ExposeDirectoryDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_path: Some("/i".to_string()),
target_path: Some("/j".to_string()),
target: Some(Ref::Framework(FrameworkRef {})),
rights: Some(fio2::Operations::Connect),
subdir: None,
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("ExposeServiceDecl", "source"),
Error::missing_field("ExposeServiceDecl", "target"),
Error::invalid_field("ExposeProtocolDecl", "source"),
Error::invalid_field("ExposeProtocolDecl", "target"),
Error::invalid_field("ExposeDirectoryDecl", "source"),
Error::invalid_field("ExposeDirectoryDecl", "target"),
Error::invalid_field("ExposeDirectoryDecl", "source"),
Error::invalid_field("ExposeDirectoryDecl", "target"),
Error::invalid_field("ExposeRunnerDecl", "source"),
Error::invalid_field("ExposeRunnerDecl", "target"),
Error::invalid_field("ExposeResolverDecl", "source"),
Error::invalid_field("ExposeResolverDecl", "target"),
Error::invalid_field("ExposeDirectoryDecl", "source"),
])),
},
test_validate_exposes_long_identifiers => {
input = {
let mut decl = new_component_decl();
decl.exposes = Some(vec![
ExposeDecl::Service(ExposeServiceDecl {
source: Some(Ref::Child(ChildRef {
name: "b".repeat(101),
collection: None,
})),
source_path: Some(format!("/{}", "a".repeat(1024))),
target_path: Some(format!("/{}", "b".repeat(1024))),
target: Some(Ref::Realm(RealmRef {})),
}),
ExposeDecl::Protocol(ExposeProtocolDecl {
source: Some(Ref::Child(ChildRef {
name: "b".repeat(101),
collection: None,
})),
source_path: Some(format!("/{}", "a".repeat(1024))),
target_path: Some(format!("/{}", "b".repeat(1024))),
target: Some(Ref::Realm(RealmRef {})),
}),
ExposeDecl::Directory(ExposeDirectoryDecl {
source: Some(Ref::Child(ChildRef {
name: "b".repeat(101),
collection: None,
})),
source_path: Some(format!("/{}", "a".repeat(1024))),
target_path: Some(format!("/{}", "b".repeat(1024))),
target: Some(Ref::Realm(RealmRef {})),
rights: Some(fio2::Operations::Connect),
subdir: None,
}),
ExposeDecl::Runner(ExposeRunnerDecl {
source: Some(Ref::Child(ChildRef {
name: "b".repeat(101),
collection: None,
})),
source_name: Some("a".repeat(101)),
target: Some(Ref::Realm(RealmRef {})),
target_name: Some("b".repeat(101)),
}),
ExposeDecl::Resolver(ExposeResolverDecl {
source: Some(Ref::Child(ChildRef {
name: "b".repeat(101),
collection: None,
})),
source_name: Some("a".repeat(101)),
target: Some(Ref::Realm(RealmRef {})),
target_name: Some("b".repeat(101)),
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::field_too_long("ExposeServiceDecl", "source.child.name"),
Error::field_too_long("ExposeServiceDecl", "source_path"),
Error::field_too_long("ExposeServiceDecl", "target_path"),
Error::field_too_long("ExposeProtocolDecl", "source.child.name"),
Error::field_too_long("ExposeProtocolDecl", "source_path"),
Error::field_too_long("ExposeProtocolDecl", "target_path"),
Error::field_too_long("ExposeDirectoryDecl", "source.child.name"),
Error::field_too_long("ExposeDirectoryDecl", "source_path"),
Error::field_too_long("ExposeDirectoryDecl", "target_path"),
Error::field_too_long("ExposeRunnerDecl", "source.child.name"),
Error::field_too_long("ExposeRunnerDecl", "source_name"),
Error::field_too_long("ExposeRunnerDecl", "target_name"),
Error::field_too_long("ExposeResolverDecl", "source.child.name"),
Error::field_too_long("ExposeResolverDecl", "source_name"),
Error::field_too_long("ExposeResolverDecl", "target_name"),
])),
},
test_validate_exposes_invalid_child => {
input = {
let mut decl = new_component_decl();
decl.exposes = Some(vec![
ExposeDecl::Service(ExposeServiceDecl {
source: Some(Ref::Child(ChildRef {
name: "netstack".to_string(),
collection: None,
})),
source_path: Some("/loggers/fuchsia.logger.Log".to_string()),
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
target: Some(Ref::Realm(RealmRef {})),
}),
ExposeDecl::Protocol(ExposeProtocolDecl {
source: Some(Ref::Child(ChildRef {
name: "netstack".to_string(),
collection: None,
})),
source_path: Some("/loggers/fuchsia.logger.LegacyLog".to_string()),
target_path: Some("/svc/fuchsia.logger.LegacyLog".to_string()),
target: Some(Ref::Realm(RealmRef {})),
}),
ExposeDecl::Directory(ExposeDirectoryDecl {
source: Some(Ref::Child(ChildRef {
name: "netstack".to_string(),
collection: None,
})),
source_path: Some("/data/netstack".to_string()),
target_path: Some("/data".to_string()),
target: Some(Ref::Realm(RealmRef {})),
rights: Some(fio2::Operations::Connect),
subdir: None,
}),
ExposeDecl::Runner(ExposeRunnerDecl {
source: Some(Ref::Child(ChildRef {
name: "netstack".to_string(),
collection: None,
})),
source_name: Some("elf".to_string()),
target: Some(Ref::Realm(RealmRef {})),
target_name: Some("elf".to_string()),
}),
ExposeDecl::Resolver(ExposeResolverDecl {
source: Some(Ref::Child(ChildRef {
name: "netstack".to_string(),
collection: None,
})),
source_name: Some("pkg".to_string()),
target: Some(Ref::Realm(RealmRef {})),
target_name: Some("pkg".to_string()),
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_child("ExposeServiceDecl", "source", "netstack"),
Error::invalid_child("ExposeProtocolDecl", "source", "netstack"),
Error::invalid_child("ExposeDirectoryDecl", "source", "netstack"),
Error::invalid_child("ExposeRunnerDecl", "source", "netstack"),
Error::invalid_child("ExposeResolverDecl", "source", "netstack"),
])),
},
test_validate_exposes_duplicate_target => {
input = {
let mut decl = new_component_decl();
decl.runners = Some(vec![RunnerDecl{
name: Some("source_elf".to_string()),
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/path".to_string()),
}]);
decl.resolvers = Some(vec![ResolverDecl {
name: Some("source_pkg".to_string()),
source_path: Some("/path".to_string()),
}]);
decl.exposes = Some(vec![
ExposeDecl::Directory(ExposeDirectoryDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/svc/logger".to_string()),
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
target: Some(Ref::Realm(RealmRef {})),
rights: Some(fio2::Operations::Connect),
subdir: None,
}),
ExposeDecl::Service(ExposeServiceDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/svc/logger2".to_string()),
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
target: Some(Ref::Realm(RealmRef {})),
}),
ExposeDecl::Directory(ExposeDirectoryDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/svc/logger3".to_string()),
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
target: Some(Ref::Realm(RealmRef {})),
rights: Some(fio2::Operations::Connect),
subdir: None,
}),
ExposeDecl::Service(ExposeServiceDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/svc/netstack".to_string()),
target_path: Some("/svc/fuchsia.net.Stack".to_string()),
target: Some(Ref::Realm(RealmRef {})),
}),
ExposeDecl::Service(ExposeServiceDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/svc/netstack2".to_string()),
target_path: Some("/svc/fuchsia.net.Stack".to_string()),
target: Some(Ref::Realm(RealmRef {})),
}),
ExposeDecl::Runner(ExposeRunnerDecl {
source: Some(Ref::Self_(SelfRef{})),
source_name: Some("source_elf".to_string()),
target: Some(Ref::Realm(RealmRef {})),
target_name: Some("elf".to_string()),
}),
ExposeDecl::Runner(ExposeRunnerDecl {
source: Some(Ref::Self_(SelfRef{})),
source_name: Some("source_elf".to_string()),
target: Some(Ref::Realm(RealmRef {})),
target_name: Some("elf".to_string()),
}),
ExposeDecl::Resolver(ExposeResolverDecl {
source: Some(Ref::Self_(SelfRef{})),
source_name: Some("source_pkg".to_string()),
target: Some(Ref::Realm(RealmRef {})),
target_name: Some("pkg".to_string()),
}),
ExposeDecl::Resolver(ExposeResolverDecl {
source: Some(Ref::Self_(SelfRef{})),
source_name: Some("source_pkg".to_string()),
target: Some(Ref::Realm(RealmRef {})),
target_name: Some("pkg".to_string()),
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::duplicate_field("ExposeServiceDecl", "target_path",
"/svc/fuchsia.logger.Log"),
Error::duplicate_field("ExposeDirectoryDecl", "target_path",
"/svc/fuchsia.logger.Log"),
Error::duplicate_field("ExposeRunnerDecl", "target_name",
"elf"),
Error::duplicate_field("ExposeResolverDecl", "target_name", "pkg"),
])),
},
test_validate_exposes_invalid_runner_from_self => {
input = {
let mut decl = new_component_decl();
decl.exposes = Some(vec![
ExposeDecl::Runner(ExposeRunnerDecl {
source: Some(Ref::Self_(SelfRef{})),
source_name: Some("source_elf".to_string()),
target: Some(Ref::Realm(RealmRef {})),
target_name: Some("elf".to_string()),
}),
]);
decl
},
result = Err(ErrorList::new(vec![
// We are attempting to expose a runner from "self", but we don't
// acutally declare a runner.
Error::invalid_field("ExposeRunnerDecl", "source"),
])),
},
// offers
test_validate_offers_empty => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![
OfferDecl::Service(OfferServiceDecl {
source: None,
source_path: None,
target: None,
target_path: None,
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: None,
source_path: None,
target: None,
target_path: None,
dependency_type: None,
}),
OfferDecl::Directory(OfferDirectoryDecl {
source: None,
source_path: None,
target: None,
target_path: None,
rights: None,
subdir: None,
dependency_type: None,
}),
OfferDecl::Storage(OfferStorageDecl {
type_: None,
source: None,
target: None,
}),
OfferDecl::Runner(OfferRunnerDecl {
source: None,
source_name: None,
target: None,
target_name: None,
}),
OfferDecl::Event(OfferEventDecl {
source: None,
source_name: None,
target: None,
target_name: None,
filter: None,
})
]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("OfferServiceDecl", "source"),
Error::missing_field("OfferServiceDecl", "source_path"),
Error::missing_field("OfferServiceDecl", "target"),
Error::missing_field("OfferServiceDecl", "target_path"),
Error::missing_field("OfferProtocolDecl", "source"),
Error::missing_field("OfferProtocolDecl", "source_path"),
Error::missing_field("OfferProtocolDecl", "target"),
Error::missing_field("OfferProtocolDecl", "target_path"),
Error::missing_field("OfferProtocolDecl", "dependency_type"),
Error::missing_field("OfferDirectoryDecl", "source"),
Error::missing_field("OfferDirectoryDecl", "source_path"),
Error::missing_field("OfferDirectoryDecl", "target"),
Error::missing_field("OfferDirectoryDecl", "target_path"),
Error::missing_field("OfferDirectoryDecl", "dependency_type"),
Error::missing_field("OfferStorageDecl", "type"),
Error::missing_field("OfferStorageDecl", "source"),
Error::missing_field("OfferStorageDecl", "target"),
Error::missing_field("OfferRunnerDecl", "source"),
Error::missing_field("OfferRunnerDecl", "source_name"),
Error::missing_field("OfferRunnerDecl", "target"),
Error::missing_field("OfferRunnerDecl", "target_name"),
Error::missing_field("OfferEventDecl", "source_name"),
Error::missing_field("OfferEventDecl", "source"),
Error::missing_field("OfferEventDecl", "target"),
Error::missing_field("OfferEventDecl", "target_name"),
])),
},
test_validate_offers_long_identifiers => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![
OfferDecl::Service(OfferServiceDecl {
source: Some(Ref::Child(ChildRef {
name: "a".repeat(101),
collection: None,
})),
source_path: Some(format!("/{}", "a".repeat(1024))),
target: Some(Ref::Child(
ChildRef {
name: "b".repeat(101),
collection: None,
}
)),
target_path: Some(format!("/{}", "b".repeat(1024))),
}),
OfferDecl::Service(OfferServiceDecl {
source: Some(Ref::Self_(SelfRef {})),
source_path: Some("/a".to_string()),
target: Some(Ref::Collection(
CollectionRef {
name: "b".repeat(101),
}
)),
target_path: Some(format!("/{}", "b".repeat(1024))),
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: Some(Ref::Child(ChildRef {
name: "a".repeat(101),
collection: None,
})),
source_path: Some(format!("/{}", "a".repeat(1024))),
target: Some(Ref::Child(
ChildRef {
name: "b".repeat(101),
collection: None,
}
)),
target_path: Some(format!("/{}", "b".repeat(1024))),
dependency_type: Some(DependencyType::Strong),
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: Some(Ref::Self_(SelfRef {})),
source_path: Some("/a".to_string()),
target: Some(Ref::Collection(
CollectionRef {
name: "b".repeat(101),
}
)),
target_path: Some(format!("/{}", "b".repeat(1024))),
dependency_type: Some(DependencyType::WeakForMigration),
}),
OfferDecl::Directory(OfferDirectoryDecl {
source: Some(Ref::Child(ChildRef {
name: "a".repeat(101),
collection: None,
})),
source_path: Some(format!("/{}", "a".repeat(1024))),
target: Some(Ref::Child(
ChildRef {
name: "b".repeat(101),
collection: None,
}
)),
target_path: Some(format!("/{}", "b".repeat(1024))),
rights: Some(fio2::Operations::Connect),
subdir: None,
dependency_type: Some(DependencyType::Strong),
}),
OfferDecl::Directory(OfferDirectoryDecl {
source: Some(Ref::Self_(SelfRef {})),
source_path: Some("/a".to_string()),
target: Some(Ref::Collection(
CollectionRef {
name: "b".repeat(101),
}
)),
target_path: Some(format!("/{}", "b".repeat(1024))),
rights: Some(fio2::Operations::Connect),
subdir: None,
dependency_type: Some(DependencyType::WeakForMigration),
}),
OfferDecl::Storage(OfferStorageDecl {
type_: Some(StorageType::Data),
source: Some(Ref::Realm(RealmRef {})),
target: Some(Ref::Child(
ChildRef {
name: "b".repeat(101),
collection: None,
}
)),
}),
OfferDecl::Storage(OfferStorageDecl {
type_: Some(StorageType::Data),
source: Some(Ref::Realm(RealmRef {})),
target: Some(Ref::Collection(
CollectionRef { name: "b".repeat(101) }
)),
}),
OfferDecl::Runner(OfferRunnerDecl {
source: Some(Ref::Child(ChildRef {
name: "a".repeat(101),
collection: None,
})),
source_name: Some("b".repeat(101)),
target: Some(Ref::Collection(
CollectionRef {
name: "c".repeat(101),
}
)),
target_name: Some("d".repeat(101)),
}),
OfferDecl::Resolver(OfferResolverDecl {
source: Some(Ref::Child(ChildRef {
name: "a".repeat(101),
collection: None,
})),
source_name: Some("b".repeat(101)),
target: Some(Ref::Collection(
CollectionRef {
name: "c".repeat(101),
}
)),
target_name: Some("d".repeat(101)),
}),
OfferDecl::Event(OfferEventDecl {
source: Some(Ref::Realm(RealmRef {})),
source_name: Some(format!("{}", "a".repeat(101))),
target: Some(Ref::Child(ChildRef {
name: "a".repeat(101),
collection: None
})),
target_name: Some(format!("{}", "a".repeat(101))),
filter: Some(fdata::Dictionary { entries: None }),
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::field_too_long("OfferServiceDecl", "source.child.name"),
Error::field_too_long("OfferServiceDecl", "source_path"),
Error::field_too_long("OfferServiceDecl", "target.child.name"),
Error::field_too_long("OfferServiceDecl", "target_path"),
Error::field_too_long("OfferServiceDecl", "target.collection.name"),
Error::field_too_long("OfferServiceDecl", "target_path"),
Error::field_too_long("OfferProtocolDecl", "source.child.name"),
Error::field_too_long("OfferProtocolDecl", "source_path"),
Error::field_too_long("OfferProtocolDecl", "target.child.name"),
Error::field_too_long("OfferProtocolDecl", "target_path"),
Error::field_too_long("OfferProtocolDecl", "target.collection.name"),
Error::field_too_long("OfferProtocolDecl", "target_path"),
Error::field_too_long("OfferDirectoryDecl", "source.child.name"),
Error::field_too_long("OfferDirectoryDecl", "source_path"),
Error::field_too_long("OfferDirectoryDecl", "target.child.name"),
Error::field_too_long("OfferDirectoryDecl", "target_path"),
Error::field_too_long("OfferDirectoryDecl", "target.collection.name"),
Error::field_too_long("OfferDirectoryDecl", "target_path"),
Error::field_too_long("OfferStorageDecl", "target.child.name"),
Error::field_too_long("OfferStorageDecl", "target.collection.name"),
Error::field_too_long("OfferRunnerDecl", "source.child.name"),
Error::field_too_long("OfferRunnerDecl", "source_name"),
Error::field_too_long("OfferRunnerDecl", "target.collection.name"),
Error::field_too_long("OfferRunnerDecl", "target_name"),
Error::field_too_long("OfferResolverDecl", "source.child.name"),
Error::field_too_long("OfferResolverDecl", "source_name"),
Error::field_too_long("OfferResolverDecl", "target.collection.name"),
Error::field_too_long("OfferResolverDecl", "target_name"),
Error::field_too_long("OfferEventDecl", "source_name"),
Error::field_too_long("OfferEventDecl", "target.child.name"),
Error::field_too_long("OfferEventDecl", "target_name"),
])),
},
test_validate_offers_rights => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![OfferDecl::Directory(OfferDirectoryDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/data/assets".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "logger".to_string(),
collection: None,
}
)),
target_path: Some("/data".to_string()),
rights: None,
subdir: None,
dependency_type: Some(DependencyType::Strong),
})]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_child("OfferDirectoryDecl", "target", "logger"),
Error::missing_field("OfferDirectoryDecl", "rights"),
])),
},
test_validate_offers_extraneous => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![
OfferDecl::Service(OfferServiceDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: Some("modular".to_string()),
})),
source_path: Some("/loggers/fuchsia.logger.Log".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: Some("modular".to_string()),
}
)),
target_path: Some("/data/realm_assets".to_string()),
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: Some("modular".to_string()),
})),
source_path: Some("/loggers/fuchsia.logger.Log".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: Some("modular".to_string()),
}
)),
target_path: Some("/data/realm_assets".to_string()),
dependency_type: Some(DependencyType::Strong),
}),
OfferDecl::Directory(OfferDirectoryDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: Some("modular".to_string()),
})),
source_path: Some("/data/assets".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: Some("modular".to_string()),
}
)),
target_path: Some("/data".to_string()),
rights: Some(fio2::Operations::Connect),
subdir: None,
dependency_type: Some(DependencyType::WeakForMigration),
}),
OfferDecl::Storage(OfferStorageDecl {
type_: Some(StorageType::Data),
source: Some(Ref::Realm(RealmRef{ })),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: Some("modular".to_string()),
}
)),
}),
OfferDecl::Runner(OfferRunnerDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: Some("modular".to_string()),
})),
source_name: Some("elf".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: Some("modular".to_string()),
}
)),
target_name: Some("elf".to_string()),
}),
OfferDecl::Resolver(OfferResolverDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: Some("modular".to_string()),
})),
source_name: Some("pkg".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: Some("modular".to_string()),
}
)),
target_name: Some("pkg".to_string()),
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::extraneous_field("OfferServiceDecl", "source.child.collection"),
Error::extraneous_field("OfferServiceDecl", "target.child.collection"),
Error::extraneous_field("OfferProtocolDecl", "source.child.collection"),
Error::extraneous_field("OfferProtocolDecl", "target.child.collection"),
Error::extraneous_field("OfferDirectoryDecl", "source.child.collection"),
Error::extraneous_field("OfferDirectoryDecl", "target.child.collection"),
Error::extraneous_field("OfferStorageDecl", "target.child.collection"),
Error::extraneous_field("OfferRunnerDecl", "source.child.collection"),
Error::extraneous_field("OfferRunnerDecl", "target.child.collection"),
Error::extraneous_field("OfferResolverDecl", "source.child.collection"),
Error::extraneous_field("OfferResolverDecl", "target.child.collection"),
])),
},
test_validate_offers_invalid_identifiers => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![
OfferDecl::Service(OfferServiceDecl {
source: Some(Ref::Child(ChildRef {
name: "^bad".to_string(),
collection: None,
})),
source_path: Some("foo/".to_string()),
target: Some(Ref::Child(ChildRef {
name: "%bad".to_string(),
collection: None,
})),
target_path: Some("/".to_string()),
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: Some(Ref::Child(ChildRef {
name: "^bad".to_string(),
collection: None,
})),
source_path: Some("foo/".to_string()),
target: Some(Ref::Child(ChildRef {
name: "%bad".to_string(),
collection: None,
})),
target_path: Some("/".to_string()),
dependency_type: Some(DependencyType::Strong),
}),
OfferDecl::Directory(OfferDirectoryDecl {
source: Some(Ref::Child(ChildRef {
name: "^bad".to_string(),
collection: None,
})),
source_path: Some("foo/".to_string()),
target: Some(Ref::Child(ChildRef {
name: "%bad".to_string(),
collection: None,
})),
target_path: Some("/".to_string()),
rights: Some(fio2::Operations::Connect),
subdir: Some("/foo".to_string()),
dependency_type: Some(DependencyType::Strong),
}),
OfferDecl::Runner(OfferRunnerDecl {
source: Some(Ref::Child(ChildRef {
name: "^bad".to_string(),
collection: None,
})),
source_name: Some("/path".to_string()),
target: Some(Ref::Child(ChildRef {
name: "%bad".to_string(),
collection: None,
})),
target_name: Some("elf!".to_string()),
}),
OfferDecl::Resolver(OfferResolverDecl {
source: Some(Ref::Child(ChildRef {
name: "^bad".to_string(),
collection: None,
})),
source_name: Some("/path".to_string()),
target: Some(Ref::Child(ChildRef {
name: "%bad".to_string(),
collection: None,
})),
target_name: Some("pkg!".to_string()),
}),
OfferDecl::Event(OfferEventDecl {
source: Some(Ref::Realm(RealmRef {})),
source_name: Some("/path".to_string()),
target: Some(Ref::Child(ChildRef {
name: "%bad".to_string(),
collection: None,
})),
target_name: Some("/path".to_string()),
filter: Some(fdata::Dictionary { entries: None }),
})
]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_character_in_field("OfferServiceDecl", "source.child.name", '^'),
Error::invalid_field("OfferServiceDecl", "source_path"),
Error::invalid_character_in_field("OfferServiceDecl", "target.child.name", '%'),
Error::invalid_field("OfferServiceDecl", "target_path"),
Error::invalid_character_in_field("OfferProtocolDecl", "source.child.name", '^'),
Error::invalid_field("OfferProtocolDecl", "source_path"),
Error::invalid_character_in_field("OfferProtocolDecl", "target.child.name", '%'),
Error::invalid_field("OfferProtocolDecl", "target_path"),
Error::invalid_character_in_field("OfferDirectoryDecl", "source.child.name", '^'),
Error::invalid_field("OfferDirectoryDecl", "source_path"),
Error::invalid_character_in_field("OfferDirectoryDecl", "target.child.name", '%'),
Error::invalid_field("OfferDirectoryDecl", "target_path"),
Error::invalid_field("OfferDirectoryDecl", "subdir"),
Error::invalid_character_in_field("OfferRunnerDecl", "source.child.name", '^'),
Error::invalid_character_in_field("OfferRunnerDecl", "source_name", '/'),
Error::invalid_character_in_field("OfferRunnerDecl", "target.child.name", '%'),
Error::invalid_character_in_field("OfferRunnerDecl", "target_name", '!'),
Error::invalid_character_in_field("OfferResolverDecl", "source.child.name", '^'),
Error::invalid_character_in_field("OfferResolverDecl", "source_name", '/'),
Error::invalid_character_in_field("OfferResolverDecl", "target.child.name", '%'),
Error::invalid_character_in_field("OfferResolverDecl", "target_name", '!'),
Error::invalid_character_in_field("OfferEventDecl", "source_name", '/'),
Error::invalid_character_in_field("OfferEventDecl", "target.child.name", '%'),
Error::invalid_character_in_field("OfferEventDecl", "target_name", '/'),
])),
},
test_validate_offers_target_equals_source => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![
OfferDecl::Service(OfferServiceDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_path: Some("/svc/logger".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "logger".to_string(),
collection: None,
}
)),
target_path: Some("/svc/logger".to_string()),
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_path: Some("/svc/legacy_logger".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "logger".to_string(),
collection: None,
}
)),
target_path: Some("/svc/legacy_logger".to_string()),
dependency_type: Some(DependencyType::WeakForMigration),
}),
OfferDecl::Directory(OfferDirectoryDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_path: Some("/data/assets".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "logger".to_string(),
collection: None,
}
)),
target_path: Some("/data".to_string()),
rights: Some(fio2::Operations::Connect),
subdir: None,
dependency_type: Some(DependencyType::Strong),
}),
OfferDecl::Runner(OfferRunnerDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("web".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "logger".to_string(),
collection: None,
}
)),
target_name: Some("web".to_string()),
}),
OfferDecl::Resolver(OfferResolverDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_name: Some("pkg".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "logger".to_string(),
collection: None,
}
)),
target_name: Some("pkg".to_string()),
}),
]);
decl.children = Some(vec![ChildDecl{
name: Some("logger".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/logger#meta/logger.cm".to_string()),
startup: Some(StartupMode::Lazy),
environment: None,
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::offer_target_equals_source("OfferServiceDecl", "logger"),
Error::offer_target_equals_source("OfferProtocolDecl", "logger"),
Error::offer_target_equals_source("OfferDirectoryDecl", "logger"),
Error::offer_target_equals_source("OfferRunnerDecl", "logger"),
Error::offer_target_equals_source("OfferResolverDecl", "logger"),
])),
},
test_validate_offers_storage_target_equals_source => {
input = ComponentDecl {
offers: Some(vec![
OfferDecl::Storage(OfferStorageDecl {
type_: Some(StorageType::Data),
source: Some(Ref::Storage(StorageRef {
name: "minfs".to_string(),
})),
target: Some(Ref::Child(
ChildRef {
name: "logger".to_string(),
collection: None,
}
)),
})
]),
children: Some(vec![
ChildDecl {
name: Some("logger".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm".to_string()),
startup: Some(StartupMode::Lazy),
environment: None,
},
]),
storage: Some(vec![
StorageDecl {
name: Some("minfs".to_string()),
source_path: Some("/minfs".to_string()),
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: None,
})),
}
]),
..new_component_decl()
},
result = Err(ErrorList::new(vec![
Error::offer_target_equals_source("OfferStorageDecl", "logger"),
])),
},
test_validate_offers_invalid_child => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![
OfferDecl::Service(OfferServiceDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_path: Some("/loggers/fuchsia.logger.Log".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: None,
}
)),
target_path: Some("/data/realm_assets".to_string()),
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_path: Some("/loggers/fuchsia.logger.LegacyLog".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: None,
}
)),
target_path: Some("/data/legacy_realm_assets".to_string()),
dependency_type: Some(DependencyType::Strong),
}),
OfferDecl::Directory(OfferDirectoryDecl {
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: None,
})),
source_path: Some("/data/assets".to_string()),
target: Some(Ref::Collection(
CollectionRef { name: "modular".to_string() }
)),
target_path: Some("/data".to_string()),
rights: Some(fio2::Operations::Connect),
subdir: None,
dependency_type: Some(DependencyType::WeakForMigration),
}),
]);
decl.storage = Some(vec![
StorageDecl {
name: Some("memfs".to_string()),
source_path: Some("/memfs".to_string()),
source: Some(Ref::Child(ChildRef {
name: "logger".to_string(),
collection: None,
})),
},
]);
decl.children = Some(vec![
ChildDecl {
name: Some("netstack".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
startup: Some(StartupMode::Lazy),
environment: None,
},
]);
decl.collections = Some(vec![
CollectionDecl {
name: Some("modular".to_string()),
durability: Some(Durability::Persistent),
},
]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_child("StorageDecl", "source", "logger"),
Error::invalid_child("OfferServiceDecl", "source", "logger"),
Error::invalid_child("OfferProtocolDecl", "source", "logger"),
Error::invalid_child("OfferDirectoryDecl", "source", "logger"),
])),
},
test_validate_offers_target_duplicate_path => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![
OfferDecl::Service(OfferServiceDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/svc/logger".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: None,
}
)),
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
}),
OfferDecl::Service(OfferServiceDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/svc/logger".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: None,
}
)),
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
}),
OfferDecl::Directory(OfferDirectoryDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/data/assets".to_string()),
target: Some(Ref::Collection(
CollectionRef { name: "modular".to_string() }
)),
target_path: Some("/data".to_string()),
rights: Some(fio2::Operations::Connect),
subdir: None,
dependency_type: Some(DependencyType::Strong),
}),
OfferDecl::Service(OfferServiceDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/data/assets".to_string()),
target: Some(Ref::Collection(
CollectionRef { name: "modular".to_string() }
)),
target_path: Some("/data".to_string()),
}),
OfferDecl::Directory(OfferDirectoryDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/data/assets".to_string()),
target: Some(Ref::Collection(
CollectionRef { name: "modular".to_string() }
)),
target_path: Some("/data".to_string()),
rights: Some(fio2::Operations::Connect),
subdir: None,
dependency_type: Some(DependencyType::WeakForMigration),
}),
OfferDecl::Runner(OfferRunnerDecl {
source: Some(Ref::Realm(RealmRef{})),
source_name: Some("elf".to_string()),
target: Some(Ref::Collection(
CollectionRef { name: "modular".to_string() }
)),
target_name: Some("duplicated".to_string()),
}),
OfferDecl::Runner(OfferRunnerDecl {
source: Some(Ref::Realm(RealmRef{})),
source_name: Some("elf".to_string()),
target: Some(Ref::Collection(
CollectionRef { name: "modular".to_string() }
)),
target_name: Some("duplicated".to_string()),
}),
OfferDecl::Resolver(OfferResolverDecl {
source: Some(Ref::Realm(RealmRef{})),
source_name: Some("pkg".to_string()),
target: Some(Ref::Collection(
CollectionRef { name: "modular".to_string() }
)),
target_name: Some("duplicated".to_string()),
}),
OfferDecl::Resolver(OfferResolverDecl {
source: Some(Ref::Realm(RealmRef{})),
source_name: Some("pkg".to_string()),
target: Some(Ref::Collection(
CollectionRef { name: "modular".to_string() }
)),
target_name: Some("duplicated".to_string()),
}),
OfferDecl::Event(OfferEventDecl {
source: Some(Ref::Realm(RealmRef {})),
source_name: Some("stopped".to_string()),
target: Some(Ref::Child(ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("started".to_string()),
filter: None,
}),
OfferDecl::Event(OfferEventDecl {
source: Some(Ref::Realm(RealmRef {})),
source_name: Some("started_on_x".to_string()),
target: Some(Ref::Child(ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some("started".to_string()),
filter: None,
}),
]);
decl.children = Some(vec![
ChildDecl{
name: Some("netstack".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
startup: Some(StartupMode::Eager),
environment: None,
},
]);
decl.collections = Some(vec![
CollectionDecl{
name: Some("modular".to_string()),
durability: Some(Durability::Persistent),
},
]);
decl
},
result = Err(ErrorList::new(vec![
Error::duplicate_field("OfferServiceDecl", "target_path", "/data"),
Error::duplicate_field("OfferDirectoryDecl", "target_path", "/data"),
Error::duplicate_field("OfferRunnerDecl", "target_name", "duplicated"),
Error::duplicate_field("OfferResolverDecl", "target_name", "duplicated"),
Error::duplicate_field("OfferEventDecl", "target_name", "started"),
])),
},
test_validate_offers_target_invalid => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![
OfferDecl::Service(OfferServiceDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/svc/logger".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: None,
}
)),
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
}),
OfferDecl::Service(OfferServiceDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/svc/logger".to_string()),
target: Some(Ref::Collection(
CollectionRef { name: "modular".to_string(), }
)),
target_path: Some("/svc/fuchsia.logger.Log".to_string()),
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/svc/legacy_logger".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: None,
}
)),
target_path: Some("/svc/fuchsia.logger.LegacyLog".to_string()),
dependency_type: Some(DependencyType::WeakForMigration),
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/svc/legacy_logger".to_string()),
target: Some(Ref::Collection(
CollectionRef { name: "modular".to_string(), }
)),
target_path: Some("/svc/fuchsia.logger.LegacyLog".to_string()),
dependency_type: Some(DependencyType::Strong),
}),
OfferDecl::Directory(OfferDirectoryDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/data/assets".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: None,
}
)),
target_path: Some("/data".to_string()),
rights: Some(fio2::Operations::Connect),
subdir: None,
dependency_type: Some(DependencyType::Strong),
}),
OfferDecl::Directory(OfferDirectoryDecl {
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/data/assets".to_string()),
target: Some(Ref::Collection(
CollectionRef { name: "modular".to_string(), }
)),
target_path: Some("/data".to_string()),
rights: Some(fio2::Operations::Connect),
subdir: None,
dependency_type: Some(DependencyType::WeakForMigration),
}),
OfferDecl::Storage(OfferStorageDecl {
type_: Some(StorageType::Data),
source: Some(Ref::Realm(RealmRef{})),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: None,
}
)),
}),
OfferDecl::Storage(OfferStorageDecl {
type_: Some(StorageType::Data),
source: Some(Ref::Realm(RealmRef{})),
target: Some(Ref::Collection(
CollectionRef { name: "modular".to_string(), }
)),
}),
OfferDecl::Runner(OfferRunnerDecl {
source: Some(Ref::Realm(RealmRef{})),
source_name: Some("elf".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: None,
}
)),
target_name: Some("elf".to_string()),
}),
OfferDecl::Runner(OfferRunnerDecl {
source: Some(Ref::Realm(RealmRef{})),
source_name: Some("elf".to_string()),
target: Some(Ref::Collection(
CollectionRef { name: "modular".to_string(), }
)),
target_name: Some("elf".to_string()),
}),
OfferDecl::Resolver(OfferResolverDecl {
source: Some(Ref::Realm(RealmRef{})),
source_name: Some("pkg".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: None,
}
)),
target_name: Some("pkg".to_string()),
}),
OfferDecl::Resolver(OfferResolverDecl {
source: Some(Ref::Realm(RealmRef{})),
source_name: Some("pkg".to_string()),
target: Some(Ref::Collection(
CollectionRef { name: "modular".to_string(), }
)),
target_name: Some("pkg".to_string()),
}),
OfferDecl::Event(OfferEventDecl {
source_name: Some("started".to_string()),
source: Some(Ref::Realm(RealmRef {})),
target_name: Some("started".to_string()),
target: Some(Ref::Child(
ChildRef {
name: "netstack".to_string(),
collection: None,
}
)),
filter: None,
}),
OfferDecl::Event(OfferEventDecl {
source_name: Some("started".to_string()),
source: Some(Ref::Realm(RealmRef {})),
target_name: Some("started".to_string()),
target: Some(Ref::Collection(
CollectionRef { name: "modular".to_string(), }
)),
filter: None,
}),
]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_child("OfferServiceDecl", "target", "netstack"),
Error::invalid_collection("OfferServiceDecl", "target", "modular"),
Error::invalid_child("OfferProtocolDecl", "target", "netstack"),
Error::invalid_collection("OfferProtocolDecl", "target", "modular"),
Error::invalid_child("OfferDirectoryDecl", "target", "netstack"),
Error::invalid_collection("OfferDirectoryDecl", "target", "modular"),
Error::invalid_child("OfferStorageDecl", "target", "netstack"),
Error::invalid_collection("OfferStorageDecl", "target", "modular"),
Error::invalid_child("OfferRunnerDecl", "target", "netstack"),
Error::invalid_collection("OfferRunnerDecl", "target", "modular"),
Error::invalid_child("OfferResolverDecl", "target", "netstack"),
Error::invalid_collection("OfferResolverDecl", "target", "modular"),
Error::invalid_child("OfferEventDecl", "target", "netstack"),
Error::invalid_collection("OfferEventDecl", "target", "modular"),
])),
},
test_validate_offers_event_from_realm => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(
vec![
Ref::Self_(SelfRef {}),
Ref::Child(ChildRef {name: "netstack".to_string(), collection: None }),
Ref::Collection(CollectionRef {name: "modular".to_string() }),
Ref::Storage(StorageRef {name: "a".to_string()}),
]
.into_iter()
.enumerate()
.map(|(i, source)| {
OfferDecl::Event(OfferEventDecl {
source: Some(source),
source_name: Some("started".to_string()),
target: Some(Ref::Child(ChildRef {
name: "netstack".to_string(),
collection: None,
})),
target_name: Some(format!("started_{}", i)),
filter: Some(fdata::Dictionary { entries: None }),
})
})
.collect());
decl.children = Some(vec![
ChildDecl{
name: Some("netstack".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/netstack/stable#meta/netstack.cm".to_string()),
startup: Some(StartupMode::Eager),
environment: None,
},
]);
decl.collections = Some(vec![
CollectionDecl {
name: Some("modular".to_string()),
durability: Some(Durability::Persistent),
},
]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_field("OfferEventDecl", "source"),
Error::invalid_field("OfferEventDecl", "source"),
Error::invalid_field("OfferEventDecl", "source"),
Error::invalid_field("OfferEventDecl", "source"),
])),
},
test_validate_offers_long_dependency_cycle => {
input = {
let mut decl = new_component_decl();
let dependencies = vec![
("d", "b"),
("a", "b"),
("b", "c"),
("b", "d"),
("c", "a"),
];
let offers = dependencies.into_iter().map(|(from,to)|
OfferDecl::Protocol(OfferProtocolDecl {
source: Some(Ref::Child(
ChildRef { name: from.to_string(), collection: None },
)),
source_path: Some(format!("/svc/thing_{}", from)),
target: Some(Ref::Child(
ChildRef { name: to.to_string(), collection: None },
)),
target_path: Some(format!("/svc/thing_{}", from)),
dependency_type: Some(DependencyType::Strong),
})).collect();
let children = ["a", "b", "c", "d"].iter().map(|name| {
ChildDecl {
name: Some(name.to_string()),
url: Some(format!("fuchsia-pkg://fuchsia.com/pkg#meta/{}.cm", name)),
startup: Some(StartupMode::Lazy),
environment: None,
}
}).collect();
decl.offers = Some(offers);
decl.children = Some(children);
decl
},
result = Err(ErrorList::new(vec![
Error::offer_dependency_cycle(),
])),
},
// environments
test_validate_environment_empty => {
input = {
let mut decl = new_component_decl();
decl.environments = Some(vec![EnvironmentDecl {
name: None,
extends: None,
resolvers: None,
stop_timeout_ms: None,
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("EnvironmentDecl", "name"),
Error::missing_field("EnvironmentDecl", "extends"),
])),
},
test_validate_environment_no_stop_timeout => {
input = { let mut decl = new_component_decl();
decl.environments = Some(vec![EnvironmentDecl {
name: Some("env".to_string()),
extends: Some(EnvironmentExtends::None),
resolvers: None,
stop_timeout_ms: None,
}]);
decl
},
result = Err(ErrorList::new(vec![Error::missing_field("EnvironmentDecl", "stop_timeout_ms")])),
},
test_validate_environment_extends_stop_timeout => {
input = { let mut decl = new_component_decl();
decl.environments = Some(vec![EnvironmentDecl {
name: Some("env".to_string()),
extends: Some(EnvironmentExtends::Realm),
resolvers: None,
stop_timeout_ms: None,
}]);
decl
},
result = Ok(()),
},
test_validate_environment_long_identifiers => {
input = {
let mut decl = new_component_decl();
decl.environments = Some(vec![EnvironmentDecl {
name: Some("a".repeat(1025)),
extends: Some(EnvironmentExtends::None),
resolvers: None,
stop_timeout_ms: Some(1234),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::field_too_long("EnvironmentDecl", "name"),
])),
},
test_validate_environment_empty_resolver_fields => {
input = {
let mut decl = new_component_decl();
decl.environments = Some(vec![EnvironmentDecl {
name: Some("a".to_string()),
extends: Some(EnvironmentExtends::None),
resolvers: Some(vec![
ResolverRegistration {
resolver: None,
source: None,
scheme: None,
},
]),
stop_timeout_ms: Some(1234),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("ResolverRegistration", "resolver"),
Error::missing_field("ResolverRegistration", "source"),
Error::missing_field("ResolverRegistration", "scheme"),
])),
},
test_validate_environment_long_resolver_fields => {
input = {
let mut decl = new_component_decl();
decl.environments = Some(vec![EnvironmentDecl {
name: Some("a".to_string()),
extends: Some(EnvironmentExtends::None),
resolvers: Some(vec![
ResolverRegistration {
resolver: Some("a".repeat(101)),
source: Some(Ref::Realm(RealmRef{})),
scheme: Some("a".repeat(101)),
},
]),
stop_timeout_ms: Some(1234),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::field_too_long("ResolverRegistration", "resolver"),
Error::field_too_long("ResolverRegistration", "scheme"),
])),
},
test_validate_environment_invalid_resolver_fields => {
input = {
let mut decl = new_component_decl();
decl.environments = Some(vec![EnvironmentDecl {
name: Some("a".to_string()),
extends: Some(EnvironmentExtends::None),
resolvers: Some(vec![
ResolverRegistration {
resolver: Some("^a".to_string()),
source: Some(Ref::Framework(fsys::FrameworkRef{})),
scheme: Some("9scheme".to_string()),
},
]),
stop_timeout_ms: Some(1234),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_character_in_field("ResolverRegistration", "resolver", '^'),
Error::invalid_field("ResolverRegistration", "source"),
Error::invalid_character_in_field("ResolverRegistration", "scheme", '9'),
])),
},
test_validate_environment_duplicate_resolver_schemes => {
input = {
let mut decl = new_component_decl();
decl.environments = Some(vec![EnvironmentDecl {
name: Some("a".to_string()),
extends: Some(EnvironmentExtends::None),
resolvers: Some(vec![
ResolverRegistration {
resolver: Some("pkg_resolver".to_string()),
source: Some(Ref::Realm(RealmRef{})),
scheme: Some("fuchsia-pkg".to_string()),
},
ResolverRegistration {
resolver: Some("base_resolver".to_string()),
source: Some(Ref::Realm(RealmRef{})),
scheme: Some("fuchsia-pkg".to_string()),
},
]),
stop_timeout_ms: Some(1234),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::duplicate_field("ResolverRegistration", "scheme", "fuchsia-pkg"),
])),
},
test_validate_environment_resolver_from_missing_child => {
input = {
let mut decl = new_component_decl();
decl.environments = Some(vec![EnvironmentDecl {
name: Some("a".to_string()),
extends: Some(EnvironmentExtends::None),
resolvers: Some(vec![
ResolverRegistration {
resolver: Some("pkg_resolver".to_string()),
source: Some(Ref::Child(ChildRef{
name: "missing".to_string(),
collection: None,
})),
scheme: Some("fuchsia-pkg".to_string()),
},
]),
stop_timeout_ms: Some(1234),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_child("ResolverRegistration", "source", "missing"),
])),
},
test_validate_environment_resolver_child_cycle => {
input = {
let mut decl = new_component_decl();
decl.environments = Some(vec![EnvironmentDecl {
name: Some("env".to_string()),
extends: Some(EnvironmentExtends::None),
resolvers: Some(vec![
ResolverRegistration {
resolver: Some("pkg_resolver".to_string()),
source: Some(Ref::Child(ChildRef{
name: "child".to_string(),
collection: None,
})),
scheme: Some("fuchsia-pkg".to_string()),
},
]),
stop_timeout_ms: Some(1234),
}]);
decl.children = Some(vec![ChildDecl {
name: Some("child".to_string()),
startup: Some(StartupMode::Lazy),
url: Some("fuchsia-pkg://child".to_string()),
environment: Some("env".to_string()),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::resolver_dependency_cycle(),
])),
},
// children
test_validate_children_empty => {
input = {
let mut decl = new_component_decl();
decl.children = Some(vec![ChildDecl{
name: None,
url: None,
startup: None,
environment: None,
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("ChildDecl", "name"),
Error::missing_field("ChildDecl", "url"),
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()),
url: Some("bad-scheme&://blah".to_string()),
startup: Some(StartupMode::Lazy),
environment: None,
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_character_in_field("ChildDecl", "name", '^'),
Error::invalid_character_in_field("ChildDecl", "url", '&'),
])),
},
test_validate_children_long_identifiers => {
input = {
let mut decl = new_component_decl();
decl.children = Some(vec![ChildDecl{
name: Some("a".repeat(1025)),
url: Some(format!("fuchsia-pkg://{}", "a".repeat(4083))),
startup: Some(StartupMode::Lazy),
environment: Some("a".repeat(1025)),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::field_too_long("ChildDecl", "name"),
Error::field_too_long("ChildDecl", "url"),
Error::field_too_long("ChildDecl", "environment"),
Error::invalid_environment("ChildDecl", "environment", "a".repeat(1025)),
])),
},
test_validate_child_references_unknown_env => {
input = {
let mut decl = new_component_decl();
decl.children = Some(vec![ChildDecl{
name: Some("foo".to_string()),
url: Some("fuchsia-pkg://foo".to_string()),
startup: Some(StartupMode::Lazy),
environment: Some("test_env".to_string()),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_environment("ChildDecl", "environment", "test_env"),
])),
},
// collections
test_validate_collections_empty => {
input = {
let mut decl = new_component_decl();
decl.collections = Some(vec![CollectionDecl{
name: None,
durability: None,
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("CollectionDecl", "name"),
Error::missing_field("CollectionDecl", "durability"),
])),
},
test_validate_collections_invalid_identifiers => {
input = {
let mut decl = new_component_decl();
decl.collections = Some(vec![CollectionDecl{
name: Some("^bad".to_string()),
durability: Some(Durability::Persistent),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_character_in_field("CollectionDecl", "name", '^'),
])),
},
test_validate_collections_long_identifiers => {
input = {
let mut decl = new_component_decl();
decl.collections = Some(vec![CollectionDecl{
name: Some("a".repeat(1025)),
durability: Some(Durability::Transient),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::field_too_long("CollectionDecl", "name"),
])),
},
// storage
test_validate_storage_long_identifiers => {
input = {
let mut decl = new_component_decl();
decl.storage = Some(vec![StorageDecl{
name: Some("a".repeat(101)),
source_path: Some(format!("/{}", "a".repeat(1024))),
source: Some(Ref::Self_(SelfRef{})),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::field_too_long("StorageDecl", "source_path"),
Error::field_too_long("StorageDecl", "name"),
])),
},
// runners
test_validate_runners_empty => {
input = {
let mut decl = new_component_decl();
decl.runners = Some(vec![RunnerDecl{
name: None,
source: None,
source_path: None,
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("RunnerDecl", "source"),
Error::missing_field("RunnerDecl", "name"),
Error::missing_field("RunnerDecl", "source_path"),
])),
},
test_validate_runners_invalid_identifiers => {
input = {
let mut decl = new_component_decl();
decl.runners = Some(vec![RunnerDecl{
name: Some("^bad".to_string()),
source: Some(Ref::Collection(CollectionRef {
name: "/bad".to_string()
})),
source_path: Some("&bad".to_string()),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_field("RunnerDecl", "source"),
Error::invalid_character_in_field("RunnerDecl", "name", '^'),
Error::invalid_field("RunnerDecl", "source_path"),
])),
},
test_validate_runners_invalid_child => {
input = {
let mut decl = new_component_decl();
decl.runners = Some(vec![RunnerDecl{
name: Some("elf".to_string()),
source: Some(Ref::Collection(CollectionRef {
name: "invalid".to_string(),
})),
source_path: Some("/elf".to_string()),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_field("RunnerDecl", "source"),
])),
},
test_validate_runners_long_identifiers => {
input = {
let mut decl = new_component_decl();
decl.runners = Some(vec![
RunnerDecl{
name: Some("a".repeat(101)),
source: Some(Ref::Child(ChildRef {
name: "b".repeat(101),
collection: None,
})),
source_path: Some(format!("/{}", "c".repeat(1024))),
},
]);
decl
},
result = Err(ErrorList::new(vec![
Error::field_too_long("RunnerDecl", "source.child.name"),
Error::field_too_long("RunnerDecl", "name"),
Error::field_too_long("RunnerDecl", "source_path"),
])),
},
test_validate_runners_duplicate_name => {
input = {
let mut decl = new_component_decl();
decl.runners = Some(vec![
RunnerDecl {
name: Some("elf".to_string()),
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/elf".to_string()),
},
RunnerDecl {
name: Some("elf".to_string()),
source: Some(Ref::Self_(SelfRef{})),
source_path: Some("/elf2".to_string()),
},
]);
decl
},
result = Err(ErrorList::new(vec![
Error::duplicate_field("RunnerDecl", "name", "elf"),
])),
},
// Resolvers
test_validate_resolvers_empty => {
input = {
let mut decl = new_component_decl();
decl.resolvers = Some(vec![ResolverDecl{
name: None,
source_path: None,
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::missing_field("ResolverDecl", "name"),
Error::missing_field("ResolverDecl", "source_path")
])),
},
test_validate_resolvers_invalid_identifiers => {
input = {
let mut decl = new_component_decl();
decl.resolvers = Some(vec![ResolverDecl{
name: Some("^bad".to_string()),
source_path: Some("&bad".to_string()),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_character_in_field("ResolverDecl", "name", '^'),
Error::invalid_field("ResolverDecl", "source_path")
])),
},
test_validate_resolvers_long_identifiers => {
input = {
let mut decl = new_component_decl();
decl.resolvers = Some(vec![ResolverDecl{
name: Some("a".repeat(101)),
source_path: Some(format!("/{}", "f".repeat(1024))),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::field_too_long("ResolverDecl", "name"),
Error::field_too_long("ResolverDecl", "source_path")
])),
},
test_validate_resolvers_duplicate_name => {
input = {
let mut decl = new_component_decl();
decl.resolvers = Some(vec![ResolverDecl{
name: Some("a".to_string()),
source_path: Some("/foo".to_string()),
},
ResolverDecl {
name: Some("a".to_string()),
source_path: Some("/bar".to_string()),
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::duplicate_field("ResolverDecl", "name", "a"),
])),
},
test_validate_resolvers_missing_from_offer => {
input = {
let mut decl = new_component_decl();
decl.offers = Some(vec![OfferDecl::Resolver(OfferResolverDecl{
source: Some(Ref::Self_(SelfRef {})),
source_name: Some("a".to_string()),
target: Some(Ref::Child(ChildRef { name: "child".to_string(), collection: None })),
target_name: Some("a".to_string()),
})]);
decl.children = Some(vec![ChildDecl {
name: Some("child".to_string()),
url: Some("test:///child".to_string()),
startup: Some(StartupMode::Eager),
environment: None,
}]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_resolver("OfferResolverDecl", "source", "a"),
])),
},
test_validate_resolvers_missing_from_expose => {
input = {
let mut decl = new_component_decl();
decl.exposes = Some(vec![ExposeDecl::Resolver(ExposeResolverDecl{
source: Some(Ref::Self_(SelfRef {})),
source_name: Some("a".to_string()),
target: Some(Ref::Realm(RealmRef {})),
target_name: Some("a".to_string()),
})]);
decl
},
result = Err(ErrorList::new(vec![
Error::invalid_resolver("ExposeResolverDecl", "source", "a"),
])),
},
}
test_dependency! {
test_validate_offers_protocol_dependency_cycle => {
ty = OfferDecl::Protocol,
offer_decl = OfferProtocolDecl {
source: None, // Filled by macro
target: None, // Filled by macro
source_path: Some(format!("/thing")),
target_path: Some(format!("/thing")),
dependency_type: Some(DependencyType::Strong),
},
},
test_validate_offers_directory_dependency_cycle => {
ty = OfferDecl::Directory,
offer_decl = OfferDirectoryDecl {
source: None, // Filled by macro
target: None, // Filled by macro
source_path: Some(format!("/thing")),
target_path: Some(format!("/thing")),
rights: Some(fio2::Operations::Connect),
subdir: None,
dependency_type: Some(DependencyType::Strong),
},
},
test_validate_offers_service_dependency_cycle => {
ty = OfferDecl::Service,
offer_decl = OfferServiceDecl {
source: None, // Filled by macro
target: None, // Filled by macro
source_path: Some(format!("/thing")),
target_path: Some(format!("/thing")),
},
},
test_validate_offers_runner_dependency_cycle => {
ty = OfferDecl::Runner,
offer_decl = OfferRunnerDecl {
source: None, // Filled by macro
target: None, // Filled by macro
source_name: Some(format!("thing")),
target_name: Some(format!("thing")),
},
},
test_validate_offers_resolver_dependency_cycle => {
ty = OfferDecl::Resolver,
offer_decl = OfferResolverDecl {
source: None, // Filled by macro
target: None, // Filled by macro
source_name: Some(format!("thing")),
target_name: Some(format!("thing")),
},
},
}
test_weak_dependency! {
test_validate_offers_protocol_weak_dependency_cycle => {
ty = OfferDecl::Protocol,
offer_decl = OfferProtocolDecl {
source: None, // Filled by macro
target: None, // Filled by macro
source_path: Some(format!("/thing")),
target_path: Some(format!("/thing")),
dependency_type: None, // Filled by macro
},
},
test_validate_offers_directory_weak_dependency_cycle => {
ty = OfferDecl::Directory,
offer_decl = OfferDirectoryDecl {
source: None, // Filled by macro
target: None, // Filled by macro
source_path: Some(format!("/thing")),
target_path: Some(format!("/thing")),
rights: Some(fio2::Operations::Connect),
subdir: None,
dependency_type: None, // Filled by macro
},
},
}
}