| // 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, |
| itertools::Itertools, |
| std::{ |
| collections::{HashMap, HashSet}, |
| fmt, |
| path::Path, |
| }, |
| 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 during validation. |
| #[derive(Debug, Error, PartialEq)] |
| 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("{}'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 capabilities")] |
| InvalidCapability(DeclField, String), |
| #[error("\"{1}\" is referenced in {0} but it does not appear in runners")] |
| InvalidRunner(DeclField, String), |
| #[error("\"{1}\" is referenced in {0} but it does not appear in events")] |
| EventStreamEventNotFound(DeclField, String), |
| #[error("Event \"{1}\" is referenced in {0} with unsupported mode \"{2}\"")] |
| EventStreamUnsupportedMode(DeclField, String, String), |
| #[error("{0} specifies multiple runners")] |
| MultipleRunnersSpecified(String), |
| #[error("dependency cycle(s) exist: {0}")] |
| DependencyCycle(String), |
| #[error("{} \"{}\" path overlaps with {} \"{}\"", decl, path, other_decl, other_path)] |
| InvalidPathOverlap { decl: DeclField, path: String, other_decl: DeclField, other_path: String }, |
| } |
| |
| 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 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(), |
| ) |
| } |
| |
| // TODO: Replace with `invalid_capability`? |
| pub fn invalid_runner( |
| decl_type: impl Into<String>, |
| keyword: impl Into<String>, |
| runner: impl Into<String>, |
| ) -> Self { |
| Error::InvalidRunner( |
| DeclField { decl: decl_type.into(), field: keyword.into() }, |
| runner.into(), |
| ) |
| } |
| |
| pub fn invalid_capability( |
| decl_type: impl Into<String>, |
| keyword: impl Into<String>, |
| capability: impl Into<String>, |
| ) -> Self { |
| Error::InvalidCapability( |
| DeclField { decl: decl_type.into(), field: keyword.into() }, |
| capability.into(), |
| ) |
| } |
| |
| pub fn event_stream_event_not_found( |
| decl_type: impl Into<String>, |
| keyword: impl Into<String>, |
| event_name: impl Into<String>, |
| ) -> Self { |
| Error::EventStreamEventNotFound( |
| DeclField { decl: decl_type.into(), field: keyword.into() }, |
| event_name.into(), |
| ) |
| } |
| |
| pub fn event_stream_unsupported_mode( |
| decl_type: impl Into<String>, |
| keyword: impl Into<String>, |
| event_name: impl Into<String>, |
| event_mode: impl Into<String>, |
| ) -> Self { |
| Error::EventStreamUnsupportedMode( |
| DeclField { decl: decl_type.into(), field: keyword.into() }, |
| event_name.into(), |
| event_mode.into(), |
| ) |
| } |
| |
| pub fn multiple_runners_specified(decl_type: impl Into<String>) -> Self { |
| Error::MultipleRunnersSpecified(decl_type.into()) |
| } |
| |
| pub fn dependency_cycle(error: String) -> Self { |
| Error::DependencyCycle(error) |
| } |
| |
| pub fn invalid_path_overlap( |
| decl: impl Into<String>, |
| path: impl Into<String>, |
| other_decl: impl Into<String>, |
| other_path: impl Into<String>, |
| ) -> Self { |
| Error::InvalidPathOverlap { |
| decl: DeclField { decl: decl.into(), field: "target_path".to_string() }, |
| path: path.into(), |
| other_decl: DeclField { decl: other_decl.into(), field: "target_path".to_string() }, |
| other_path: other_path.into(), |
| } |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| 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, Error, PartialEq)] |
| pub struct ErrorList { |
| errs: Vec<Error>, |
| } |
| |
| impl ErrorList { |
| fn new(errs: Vec<Error>) -> ErrorList { |
| ErrorList { errs } |
| } |
| } |
| |
| 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::default(); |
| ctx.validate(decl).map_err(|errs| ErrorList::new(errs)) |
| } |
| |
| /// Validates a list of CapabilityDecls independently. |
| pub fn validate_capabilities(capabilities: &Vec<fsys::CapabilityDecl>) -> Result<(), ErrorList> { |
| let mut ctx = ValidationContext::default(); |
| for capability in capabilities { |
| ctx.validate_capability_decl(capability); |
| } |
| if ctx.errors.is_empty() { |
| Ok(()) |
| } else { |
| Err(ErrorList::new(ctx.errors)) |
| } |
| } |
| |
| /// 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 }) |
| } |
| } |
| |
| #[derive(Default)] |
| struct ValidationContext<'a> { |
| all_children: HashMap<&'a str, &'a fsys::ChildDecl>, |
| all_collections: HashSet<&'a str>, |
| all_capability_ids: HashSet<&'a str>, |
| all_storage_and_sources: HashMap<&'a str, Option<&'a str>>, |
| all_services: HashSet<&'a str>, |
| all_protocols: HashSet<&'a str>, |
| all_directories: HashSet<&'a str>, |
| all_runners: HashSet<&'a str>, |
| all_resolvers: HashSet<&'a str>, |
| all_environment_names: HashSet<&'a str>, |
| all_events: HashMap<&'a str, fsys::EventMode>, |
| all_event_streams: HashSet<&'a str>, |
| strong_dependencies: DirectedGraph<DependencyNode<'a>>, |
| target_ids: IdMap<'a>, |
| errors: Vec<Error>, |
| } |
| |
| /// A node in the DependencyGraph. The first string describes the type of node and the second |
| /// string is the name of the node. |
| #[derive(Copy, Clone, Hash, Ord, Debug, PartialOrd, PartialEq, Eq)] |
| enum DependencyNode<'a> { |
| Child(&'a str), |
| Environment(&'a str), |
| } |
| |
| impl<'a> fmt::Display for DependencyNode<'a> { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match self { |
| DependencyNode::Child(name) => write!(f, "child {}", name), |
| DependencyNode::Environment(name) => write!(f, "environment {}", name), |
| } |
| } |
| } |
| |
| #[derive(Clone, Copy, PartialEq)] |
| enum AllowableIds { |
| One, |
| Many, |
| } |
| |
| #[derive(Debug, PartialEq, Eq, Hash)] |
| enum TargetId<'a> { |
| Component(&'a str), |
| Collection(&'a str), |
| } |
| |
| type IdMap<'a> = HashMap<TargetId<'a>, HashMap<&'a str, AllowableIds>>; |
| |
| impl<'a> ValidationContext<'a> { |
| fn validate(mut self, decl: &'a fsys::ComponentDecl) -> Result<(), Vec<Error>> { |
| // Collect all environment names first, so that references to them can be checked. |
| if let Some(envs) = &decl.environments { |
| self.collect_environment_names(&envs); |
| } |
| |
| // Validate "children" and build the set of all children. |
| if let Some(children) = 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) = decl.collections.as_ref() { |
| for collection in collections { |
| self.validate_collection_decl(&collection); |
| } |
| } |
| |
| // Validate "capabilities" and build the set of all capabilities. |
| if let Some(capabilities) = decl.capabilities.as_ref() { |
| for capability in capabilities { |
| self.validate_capability_decl(capability); |
| } |
| } |
| |
| // Validate "uses". |
| if let Some(uses) = decl.uses.as_ref() { |
| self.validate_use_decls(uses); |
| } |
| |
| // Validate "exposes". |
| if let Some(exposes) = decl.exposes.as_ref() { |
| let mut target_ids = HashMap::new(); |
| for expose in exposes.iter() { |
| self.validate_expose_decl(&expose, &mut target_ids); |
| } |
| } |
| |
| // Validate "offers". |
| if let Some(offers) = decl.offers.as_ref() { |
| for offer in offers.iter() { |
| self.validate_offers_decl(&offer); |
| } |
| } |
| |
| // Validate "environments" after all other declarations are processed. |
| if let Some(environment) = decl.environments.as_ref() { |
| for environment in environment { |
| self.validate_environment_decl(&environment); |
| } |
| } |
| |
| // Check that there are no strong cyclical dependencies between children and environments |
| if let Err(e) = self.strong_dependencies.topological_sort() { |
| self.errors.push(Error::dependency_cycle(e.format_cycle())); |
| } |
| |
| 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_capability_decl(&mut self, capability: &'a fsys::CapabilityDecl) { |
| match capability { |
| fsys::CapabilityDecl::Service(service) => self.validate_service_decl(&service), |
| fsys::CapabilityDecl::Protocol(protocol) => self.validate_protocol_decl(&protocol), |
| fsys::CapabilityDecl::Directory(directory) => self.validate_directory_decl(&directory), |
| fsys::CapabilityDecl::Storage(storage) => self.validate_storage_decl(&storage), |
| fsys::CapabilityDecl::Runner(runner) => self.validate_runner_decl(&runner), |
| fsys::CapabilityDecl::Resolver(resolver) => self.validate_resolver_decl(&resolver), |
| fsys::CapabilityDeclUnknown!() => { |
| self.errors.push(Error::invalid_field("ComponentDecl", "capability")); |
| } |
| } |
| } |
| |
| fn validate_use_decls(&mut self, uses: &'a [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_: &'a fsys::UseDecl) { |
| match use_ { |
| fsys::UseDecl::Service(u) => { |
| self.validate_use_source(u.source.as_ref(), "UseServiceDecl", "source"); |
| check_name( |
| u.source_name.as_ref(), |
| "UseServiceDecl", |
| "source_name", |
| &mut self.errors, |
| ); |
| check_path( |
| u.target_path.as_ref(), |
| "UseServiceDecl", |
| "target_path", |
| &mut self.errors, |
| ); |
| } |
| fsys::UseDecl::Protocol(u) => { |
| self.validate_use_source(u.source.as_ref(), "UseProtocolDecl", "source"); |
| check_name( |
| u.source_name.as_ref(), |
| "UseProtocolDecl", |
| "source_name", |
| &mut self.errors, |
| ); |
| check_path( |
| u.target_path.as_ref(), |
| "UseProtocolDecl", |
| "target_path", |
| &mut self.errors, |
| ); |
| } |
| fsys::UseDecl::Directory(u) => { |
| self.validate_use_source(u.source.as_ref(), "UseDirectoryDecl", "source"); |
| check_name( |
| u.source_name.as_ref(), |
| "UseDirectoryDecl", |
| "source_name", |
| &mut self.errors, |
| ); |
| check_path( |
| u.target_path.as_ref(), |
| "UseDirectoryDecl", |
| "target_path", |
| &mut self.errors, |
| ); |
| 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) => { |
| check_name( |
| u.source_name.as_ref(), |
| "UseStorageDecl", |
| "source_name", |
| &mut self.errors, |
| ); |
| 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_event(e); |
| } |
| fsys::UseDecl::EventStream(e) => { |
| self.validate_event_stream(e); |
| } |
| fsys::UseDeclUnknown!() => { |
| 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 paths-based capabilities (service, directory, protocol) |
| /// are different and not prefixes of each other. |
| fn validate_use_paths(&mut self, uses: &[fsys::UseDecl]) { |
| #[derive(Debug, PartialEq, Clone, Copy)] |
| struct PathCapability<'a> { |
| decl: &'a str, |
| dir: &'a Path, |
| use_: &'a fsys::UseDecl, |
| } |
| let mut used_paths = 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 capability = match use_ { |
| fsys::UseDecl::Service(_) => { |
| let dir = match Path::new(path).parent() { |
| Some(p) => p, |
| None => continue, // Invalid path, validated elsewhere |
| }; |
| PathCapability { decl: "UseServiceDecl", dir, use_ } |
| } |
| fsys::UseDecl::Protocol(_) => { |
| let dir = match Path::new(path).parent() { |
| Some(p) => p, |
| None => continue, // Invalid path, validated elsewhere |
| }; |
| PathCapability { decl: "UseProtocolDecl", dir, use_ } |
| } |
| fsys::UseDecl::Directory(_) => { |
| PathCapability { decl: "UseDirectoryDecl", dir: Path::new(path), use_ } |
| } |
| _ => unreachable!(), |
| }; |
| if used_paths.insert(path, capability).is_some() { |
| // Disallow multiple capabilities for the same path. |
| self.errors.push(Error::duplicate_field(capability.decl, "path", path)); |
| } |
| } |
| _ => {} |
| } |
| } |
| for ((&path_a, capability_a), (&path_b, capability_b)) in |
| used_paths.iter().tuple_combinations() |
| { |
| if match (capability_a.use_, capability_b.use_) { |
| // Directories can't be the same or partially overlap. |
| (fsys::UseDecl::Directory(_), fsys::UseDecl::Directory(_)) => { |
| capability_b.dir == capability_a.dir |
| || capability_b.dir.starts_with(capability_a.dir) |
| || capability_a.dir.starts_with(capability_b.dir) |
| } |
| |
| // Protocols and Services can't overlap with Directories. |
| (_, fsys::UseDecl::Directory(_)) | (fsys::UseDecl::Directory(_), _) => { |
| capability_b.dir == capability_a.dir |
| || capability_b.dir.starts_with(capability_a.dir) |
| || capability_a.dir.starts_with(capability_b.dir) |
| } |
| |
| // Protocols and Services containing directories may be same, but |
| // partial overlap is disallowed. |
| (_, _) => { |
| capability_b.dir != capability_a.dir |
| && (capability_b.dir.starts_with(capability_a.dir) |
| || capability_a.dir.starts_with(capability_b.dir)) |
| } |
| } { |
| self.errors.push(Error::invalid_path_overlap( |
| capability_a.decl, |
| path_a, |
| capability_b.decl, |
| path_b, |
| )); |
| } |
| } |
| } |
| |
| fn validate_event(&mut self, event: &'a fsys::UseEventDecl) { |
| self.validate_use_source(event.source.as_ref(), "UseEventDecl", "source"); |
| check_name(event.source_name.as_ref(), "UseEventDecl", "source_name", &mut self.errors); |
| check_name(event.target_name.as_ref(), "UseEventDecl", "target_name", &mut self.errors); |
| check_events_mode(&event.mode, "UseEventDecl", "mode", &mut self.errors); |
| if let Some(target_name) = event.target_name.as_ref() { |
| if self |
| .all_events |
| .insert(target_name, event.mode.unwrap_or(fsys::EventMode::Async)) |
| .is_some() |
| { |
| self.errors.push(Error::duplicate_field( |
| "UseEventDecl", |
| "target_name", |
| target_name, |
| )); |
| } |
| } |
| } |
| |
| fn validate_event_stream(&mut self, event_stream: &'a fsys::UseEventStreamDecl) { |
| check_path( |
| event_stream.target_path.as_ref(), |
| "UseEventStreamDecl", |
| "target_path", |
| &mut self.errors, |
| ); |
| if let Some(target_path) = event_stream.target_path.as_ref() { |
| if !self.all_event_streams.insert(target_path) { |
| self.errors.push(Error::duplicate_field( |
| "UseEventStreamDecl", |
| "target_path", |
| target_path, |
| )); |
| } |
| } |
| match event_stream.events.as_ref() { |
| None => { |
| self.errors.push(Error::missing_field("UseEventStreamDecl", "events")); |
| } |
| Some(events) if events.is_empty() => { |
| self.errors.push(Error::empty_field("UseEventStreamDecl", "events")); |
| } |
| Some(subscriptions) => { |
| for subscription in subscriptions { |
| check_name( |
| subscription.event_name.as_ref(), |
| "UseEventStreamDecl", |
| "event_name", |
| &mut self.errors, |
| ); |
| let event_name = subscription.event_name.clone().unwrap_or_default(); |
| let event_mode = subscription.mode.unwrap_or(fsys::EventMode::Async); |
| match self.all_events.get(event_name.as_str()) { |
| Some(mode) => { |
| if *mode != fsys::EventMode::Sync && event_mode == fsys::EventMode::Sync |
| { |
| self.errors.push(Error::event_stream_unsupported_mode( |
| "UseEventStreamDecl", |
| "events", |
| event_name, |
| format!("{:?}", event_mode), |
| )); |
| } |
| } |
| None => { |
| self.errors.push(Error::event_stream_event_not_found( |
| "UseEventStreamDecl", |
| "events", |
| event_name, |
| )); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| fn validate_use_source(&mut self, source: Option<&fsys::Ref>, decl: &str, field: &str) { |
| match source { |
| Some(fsys::Ref::Parent(_)) => {} |
| Some(fsys::Ref::Framework(_)) => {} |
| Some(fsys::Ref::Debug(_)) => {} |
| Some(fsys::Ref::Capability(capability)) => { |
| if !self.all_capability_ids.contains(capability.name.as_str()) { |
| self.errors.push(Error::invalid_capability(decl, field, &capability.name)); |
| } |
| } |
| 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(env) = child.environment.as_ref() { |
| let source = DependencyNode::Environment(env.as_str()); |
| let target = DependencyNode::Child(name); |
| self.strong_dependencies.add_edge(source, target); |
| } |
| } |
| 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 there is an environment, we don't need to account for it in the dependency |
| // graph because a collection is always a sink node. |
| } |
| if collection.durability.is_none() { |
| self.errors.push(Error::missing_field("CollectionDecl", "durability")); |
| } |
| if let Some(environment) = collection.environment.as_ref() { |
| if !self.all_environment_names.contains(environment.as_str()) { |
| self.errors.push(Error::invalid_environment( |
| "CollectionDecl", |
| "environment", |
| environment, |
| )); |
| } |
| } |
| } |
| |
| 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(runners) = environment.runners.as_ref() { |
| let mut registered_runners = HashSet::new(); |
| for runner in runners { |
| self.validate_runner_registration(runner, name.clone(), &mut registered_runners); |
| } |
| } |
| 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) => {} |
| } |
| |
| if let Some(debugs) = environment.debug_capabilities.as_ref() { |
| for debug in debugs { |
| self.validate_environment_debug_registration(debug); |
| } |
| } |
| } |
| |
| fn validate_runner_registration( |
| &mut self, |
| runner_registration: &'a fsys::RunnerRegistration, |
| environment_name: Option<&'a String>, |
| runner_names: &mut HashSet<&'a str>, |
| ) { |
| check_name( |
| runner_registration.source_name.as_ref(), |
| "RunnerRegistration", |
| "source_name", |
| &mut self.errors, |
| ); |
| self.validate_registration_source( |
| environment_name, |
| runner_registration.source.as_ref(), |
| "RunnerRegistration", |
| ); |
| // If the source is `self`, ensure we have a corresponding RunnerDecl. |
| if let (Some(fsys::Ref::Self_(_)), Some(ref name)) = |
| (&runner_registration.source, &runner_registration.source_name) |
| { |
| if !self.all_runners.contains(name as &str) { |
| self.errors.push(Error::invalid_runner("RunnerRegistration", "source_name", name)); |
| } |
| } |
| |
| check_name( |
| runner_registration.target_name.as_ref(), |
| "RunnerRegistration", |
| "target_name", |
| &mut self.errors, |
| ); |
| if let Some(name) = runner_registration.target_name.as_ref() { |
| if !runner_names.insert(name.as_str()) { |
| self.errors.push(Error::duplicate_field("RunnerRegistration", "target_name", name)); |
| } |
| } |
| } |
| |
| 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, |
| ); |
| self.validate_registration_source( |
| environment_name, |
| resolver_registration.source.as_ref(), |
| "ResolverRegistration", |
| ); |
| 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_registration_source( |
| &mut self, |
| environment_name: Option<&'a String>, |
| source: Option<&'a fsys::Ref>, |
| ty: &str, |
| ) { |
| match source { |
| Some(fsys::Ref::Parent(_)) => {} |
| Some(fsys::Ref::Self_(_)) => {} |
| Some(fsys::Ref::Child(child_ref)) => { |
| // Make sure the child is valid. |
| if self.validate_child_ref(ty, "source", &child_ref) { |
| // Ensure there are no cycles, such as a resolver in an environment being |
| // assigned to a child which the resolver depends on. |
| let source = DependencyNode::Child(child_ref.name.as_str()); |
| let target = DependencyNode::Environment(environment_name.unwrap().as_str()); |
| self.strong_dependencies.add_edge(source, target); |
| } |
| } |
| Some(_) => { |
| self.errors.push(Error::invalid_field(ty, "source")); |
| } |
| None => { |
| self.errors.push(Error::missing_field(ty, "source")); |
| } |
| } |
| } |
| |
| fn validate_service_decl(&mut self, service: &'a fsys::ServiceDecl) { |
| if check_name(service.name.as_ref(), "ServiceDecl", "name", &mut self.errors) { |
| let name = service.name.as_ref().unwrap(); |
| if !self.all_capability_ids.insert(name) { |
| self.errors.push(Error::duplicate_field("ServiceDecl", "name", name.as_str())); |
| } |
| self.all_services.insert(name); |
| } |
| check_path(service.source_path.as_ref(), "ServiceDecl", "source_path", &mut self.errors); |
| } |
| |
| fn validate_protocol_decl(&mut self, protocol: &'a fsys::ProtocolDecl) { |
| if check_name(protocol.name.as_ref(), "ProtocolDecl", "name", &mut self.errors) { |
| let name = protocol.name.as_ref().unwrap(); |
| if !self.all_capability_ids.insert(name) { |
| self.errors.push(Error::duplicate_field("ProtocolDecl", "name", name.as_str())); |
| } |
| self.all_protocols.insert(name); |
| } |
| check_path(protocol.source_path.as_ref(), "ProtocolDecl", "source_path", &mut self.errors); |
| } |
| |
| fn validate_directory_decl(&mut self, directory: &'a fsys::DirectoryDecl) { |
| if check_name(directory.name.as_ref(), "DirectoryDecl", "name", &mut self.errors) { |
| let name = directory.name.as_ref().unwrap(); |
| if !self.all_capability_ids.insert(name) { |
| self.errors.push(Error::duplicate_field("DirectoryDecl", "name", name.as_str())); |
| } |
| self.all_directories.insert(name); |
| } |
| check_path( |
| directory.source_path.as_ref(), |
| "DirectoryDecl", |
| "source_path", |
| &mut self.errors, |
| ); |
| if directory.rights.is_none() { |
| self.errors.push(Error::missing_field("DirectoryDecl", "rights")); |
| } |
| } |
| |
| fn validate_storage_decl(&mut self, storage: &'a fsys::StorageDecl) { |
| let source_child_name = match storage.source.as_ref() { |
| Some(fsys::Ref::Parent(_)) => 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_capability_ids.insert(name) { |
| self.errors.push(Error::duplicate_field("StorageDecl", "name", name.as_str())); |
| } |
| self.all_storage_and_sources.insert(name, source_child_name); |
| } |
| check_name(storage.backing_dir.as_ref(), "StorageDecl", "backing_dir", &mut self.errors); |
| } |
| |
| fn validate_runner_decl(&mut self, runner: &'a fsys::RunnerDecl) { |
| 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_capability_ids.insert(name) { |
| self.errors.push(Error::duplicate_field("RunnerDecl", "name", name.as_str())); |
| } |
| self.all_runners.insert(name); |
| } |
| 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_capability_ids.insert(name) { |
| self.errors.push(Error::duplicate_field("ResolverDecl", "name", name.as_str())); |
| } |
| self.all_resolvers.insert(name); |
| } |
| 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_source_capability( |
| &mut self, |
| capability: &fsys::CapabilityRef, |
| decl_type: &str, |
| field: &str, |
| ) { |
| if !self.all_capability_ids.contains(capability.name.as_str()) { |
| self.errors.push(Error::invalid_capability(decl_type, field, &capability.name)); |
| } |
| } |
| |
| fn validate_storage_source(&mut self, source_name: &String, 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)); |
| } |
| } |
| } |
| |
| fn validate_expose_decl( |
| &mut self, |
| expose: &'a fsys::ExposeDecl, |
| prev_target_ids: &mut HashMap<&'a str, AllowableIds>, |
| ) { |
| match expose { |
| fsys::ExposeDecl::Service(e) => { |
| let decl = "ExposeServiceDecl"; |
| self.validate_expose_fields( |
| decl, |
| AllowableIds::Many, |
| e.source.as_ref(), |
| e.source_name.as_ref(), |
| e.target_name.as_ref(), |
| e.target.as_ref(), |
| prev_target_ids, |
| ); |
| // If the expose source is `self`, ensure we have a corresponding ServiceDecl. |
| // TODO: Consider bringing this bit into validate_expose_fields. |
| if let (Some(fsys::Ref::Self_(_)), Some(ref name)) = (&e.source, &e.source_name) { |
| if !self.all_services.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| } |
| fsys::ExposeDecl::Protocol(e) => { |
| let decl = "ExposeProtocolDecl"; |
| self.validate_expose_fields( |
| decl, |
| AllowableIds::One, |
| e.source.as_ref(), |
| e.source_name.as_ref(), |
| e.target_name.as_ref(), |
| e.target.as_ref(), |
| prev_target_ids, |
| ); |
| // If the expose source is `self`, ensure we have a corresponding ProtocolDecl. |
| // TODO: Consider bringing this bit into validate_expose_fields. |
| if let (Some(fsys::Ref::Self_(_)), Some(ref name)) = (&e.source, &e.source_name) { |
| if !self.all_protocols.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| } |
| fsys::ExposeDecl::Directory(e) => { |
| let decl = "ExposeDirectoryDecl"; |
| self.validate_expose_fields( |
| decl, |
| AllowableIds::One, |
| e.source.as_ref(), |
| e.source_name.as_ref(), |
| e.target_name.as_ref(), |
| e.target.as_ref(), |
| prev_target_ids, |
| ); |
| // If the expose source is `self`, ensure we have a corresponding DirectoryDecl. |
| // TODO: Consider bringing this bit into validate_expose_fields. |
| if let (Some(fsys::Ref::Self_(_)), Some(ref name)) = (&e.source, &e.source_name) { |
| if !self.all_directories.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| if name.starts_with('/') && e.rights.is_none() { |
| self.errors.push(Error::missing_field(decl, "rights")); |
| } |
| } |
| |
| // Subdir makes sense when routing, but when exposing to framework the subdirectory |
| // can be exposed directly. |
| match e.target.as_ref() { |
| Some(fsys::Ref::Framework(_)) => { |
| if e.subdir.is_some() { |
| self.errors.push(Error::invalid_field(decl, "subdir")); |
| } |
| } |
| _ => {} |
| } |
| |
| if let Some(subdir) = e.subdir.as_ref() { |
| check_relative_path(Some(subdir), decl, "subdir", &mut self.errors); |
| } |
| } |
| fsys::ExposeDecl::Runner(e) => { |
| let decl = "ExposeRunnerDecl"; |
| self.validate_expose_fields( |
| decl, |
| AllowableIds::One, |
| e.source.as_ref(), |
| e.source_name.as_ref(), |
| e.target_name.as_ref(), |
| e.target.as_ref(), |
| prev_target_ids, |
| ); |
| // If the expose source is `self`, ensure we have a corresponding RunnerDecl. |
| if let (Some(fsys::Ref::Self_(_)), Some(ref name)) = (&e.source, &e.source_name) { |
| if !self.all_runners.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| } |
| fsys::ExposeDecl::Resolver(e) => { |
| let decl = "ExposeResolverDecl"; |
| self.validate_expose_fields( |
| decl, |
| AllowableIds::One, |
| e.source.as_ref(), |
| e.source_name.as_ref(), |
| e.target_name.as_ref(), |
| e.target.as_ref(), |
| prev_target_ids, |
| ); |
| // If the expose source is `self`, ensure we have a corresponding ResolverDecl. |
| if let (Some(fsys::Ref::Self_(_)), Some(ref name)) = (&e.source, &e.source_name) { |
| if !self.all_resolvers.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| } |
| fsys::ExposeDeclUnknown!() => { |
| self.errors.push(Error::invalid_field("ComponentDecl", "expose")); |
| } |
| } |
| } |
| |
| fn validate_expose_fields( |
| &mut self, |
| decl: &str, |
| allowable_ids: AllowableIds, |
| source: Option<&fsys::Ref>, |
| source_name: Option<&String>, |
| target_name: Option<&'a String>, |
| target: Option<&fsys::Ref>, |
| prev_child_target_ids: &mut HashMap<&'a str, AllowableIds>, |
| ) { |
| match source { |
| Some(r) => match r { |
| fsys::Ref::Self_(_) => {} |
| fsys::Ref::Framework(_) => {} |
| fsys::Ref::Child(child) => { |
| self.validate_source_child(child, decl); |
| } |
| fsys::Ref::Capability(c) => { |
| self.validate_source_capability(c, decl, "source"); |
| } |
| _ => { |
| 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::Parent(_) => {} |
| fsys::Ref::Framework(_) => { |
| if source != Some(&fsys::Ref::Self_(fsys::SelfRef {})) { |
| self.errors.push(Error::invalid_field(decl, "target")); |
| } |
| } |
| _ => { |
| self.errors.push(Error::invalid_field(decl, "target")); |
| } |
| }, |
| None => { |
| self.errors.push(Error::missing_field(decl, "target")); |
| } |
| } |
| check_name(source_name, decl, "source_name", &mut self.errors); |
| if check_name(target_name, decl, "target_name", &mut self.errors) { |
| // TODO: This logic needs to pair the target name with the target before concluding |
| // there's a duplicate. |
| let target_name = target_name.unwrap(); |
| if let Some(prev_state) = prev_child_target_ids.insert(target_name, allowable_ids) { |
| if prev_state == AllowableIds::One || prev_state != allowable_ids { |
| self.errors.push(Error::duplicate_field(decl, "target_name", target_name)); |
| } |
| } |
| } |
| } |
| |
| 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 { |
| let source = DependencyNode::Child(source.as_str()); |
| let target = DependencyNode::Child(target.as_str()); |
| self.strong_dependencies.add_edge(source, target); |
| } |
| } |
| } |
| } |
| |
| fn validate_offers_decl(&mut self, offer: &'a fsys::OfferDecl) { |
| match offer { |
| fsys::OfferDecl::Service(o) => { |
| let decl = "OfferServiceDecl"; |
| self.validate_offer_fields( |
| decl, |
| AllowableIds::Many, |
| o.source.as_ref(), |
| o.source_name.as_ref(), |
| o.target.as_ref(), |
| o.target_name.as_ref(), |
| ); |
| // If the offer source is `self`, ensure we have a corresponding ServiceDecl. |
| // TODO: Consider bringing this bit into validate_offer_fields |
| if let (Some(fsys::Ref::Self_(_)), Some(ref name)) = (&o.source, &o.source_name) { |
| if !self.all_services.contains(&name as &str) { |
| self.errors.push(Error::invalid_field(decl, "source")); |
| } |
| } |
| self.add_strong_dep(o.source.as_ref(), o.target.as_ref()); |
| } |
| fsys::OfferDecl::Protocol(o) => { |
| let decl = "OfferProtocolDecl"; |
| self.validate_offer_fields( |
| decl, |
| AllowableIds::One, |
| o.source.as_ref(), |
| o.source_name.as_ref(), |
| o.target.as_ref(), |
| o.target_name.as_ref(), |
| ); |
| if o.dependency_type.is_none() { |
| self.errors.push(Error::missing_field(decl, "dependency_type")); |
| } else if o.dependency_type == Some(fsys::DependencyType::Strong) { |
| self.add_strong_dep(o.source.as_ref(), o.target.as_ref()); |
| } |
| // If the offer source is `self`, ensure we have a corresponding ProtocolDecl. |
| // TODO: Consider bringing this bit into validate_offer_fields. |
| if let (Some(fsys::Ref::Self_(_)), Some(ref name)) = (&o.source, &o.source_name) { |
| if !self.all_protocols.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| } |
| fsys::OfferDecl::Directory(o) => { |
| let decl = "OfferDirectoryDecl"; |
| self.validate_offer_fields( |
| decl, |
| AllowableIds::One, |
| o.source.as_ref(), |
| o.source_name.as_ref(), |
| o.target.as_ref(), |
| o.target_name.as_ref(), |
| ); |
| if o.dependency_type.is_none() { |
| self.errors.push(Error::missing_field(decl, "dependency_type")); |
| } else if o.dependency_type == Some(fsys::DependencyType::Strong) { |
| self.add_strong_dep(o.source.as_ref(), o.target.as_ref()); |
| } |
| // If the offer source is `self`, ensure we have a corresponding DirectoryDecl. |
| // TODO: Consider bringing this bit into validate_offer_fields. |
| if let (Some(fsys::Ref::Self_(_)), Some(ref name)) = (&o.source, &o.source_name) { |
| if !self.all_directories.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| 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.source_name.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) => { |
| let decl = "OfferRunnerDecl"; |
| self.validate_offer_fields( |
| decl, |
| AllowableIds::One, |
| o.source.as_ref(), |
| o.source_name.as_ref(), |
| o.target.as_ref(), |
| o.target_name.as_ref(), |
| ); |
| // If the offer source is `self`, ensure we have a corresponding RunnerDecl. |
| if let (Some(fsys::Ref::Self_(_)), Some(ref name)) = (&o.source, &o.source_name) { |
| if !self.all_runners.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| self.add_strong_dep(o.source.as_ref(), o.target.as_ref()); |
| } |
| fsys::OfferDecl::Resolver(o) => { |
| let decl = "OfferResolverDecl"; |
| self.validate_offer_fields( |
| decl, |
| AllowableIds::One, |
| o.source.as_ref(), |
| o.source_name.as_ref(), |
| o.target.as_ref(), |
| o.target_name.as_ref(), |
| ); |
| // If the offer source is `self`, ensure we have a corresponding ResolverDecl. |
| if let (Some(fsys::Ref::Self_(_)), Some(ref name)) = (&o.source, &o.source_name) { |
| if !self.all_resolvers.contains(&name as &str) { |
| self.errors.push(Error::invalid_capability(decl, "source", name)); |
| } |
| } |
| self.add_strong_dep(o.source.as_ref(), o.target.as_ref()); |
| } |
| fsys::OfferDecl::Event(e) => { |
| self.validate_event_offer_fields(e); |
| } |
| fsys::OfferDeclUnknown!() => { |
| self.errors.push(Error::invalid_field("ComponentDecl", "offer")); |
| } |
| } |
| } |
| |
| /// 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_offer_fields( |
| &mut self, |
| decl: &str, |
| allowable_names: AllowableIds, |
| source: Option<&fsys::Ref>, |
| source_name: Option<&String>, |
| target: Option<&'a fsys::Ref>, |
| target_name: Option<&'a String>, |
| ) { |
| match source { |
| Some(fsys::Ref::Parent(_)) => {} |
| Some(fsys::Ref::Self_(_)) => {} |
| Some(fsys::Ref::Framework(_)) => {} |
| Some(fsys::Ref::Child(child)) => self.validate_source_child(child, decl), |
| Some(fsys::Ref::Capability(c)) => self.validate_source_capability(c, decl, "source"), |
| Some(_) => self.errors.push(Error::invalid_field(decl, "source")), |
| None => self.errors.push(Error::missing_field(decl, "source")), |
| } |
| check_name(source_name, decl, "source_name", &mut self.errors); |
| match target { |
| Some(fsys::Ref::Child(c)) => { |
| self.validate_target_child(decl, allowable_names, c, source, target_name); |
| } |
| Some(fsys::Ref::Collection(c)) => { |
| self.validate_target_collection(decl, allowable_names, c, target_name); |
| } |
| Some(_) => { |
| self.errors.push(Error::invalid_field(decl, "target")); |
| } |
| None => { |
| self.errors.push(Error::missing_field(decl, "target")); |
| } |
| } |
| check_name(target_name, decl, "target_name", &mut self.errors); |
| } |
| |
| fn validate_storage_offer_fields( |
| &mut self, |
| decl: &str, |
| source_name: Option<&'a String>, |
| source: Option<&'a fsys::Ref>, |
| target: Option<&'a fsys::Ref>, |
| ) { |
| if source_name.is_none() { |
| self.errors.push(Error::missing_field(decl, "source_name")); |
| } |
| match source { |
| Some(fsys::Ref::Parent(_)) => (), |
| Some(fsys::Ref::Self_(_)) => { |
| self.validate_storage_source(source_name.unwrap(), decl); |
| } |
| Some(_) => { |
| self.errors.push(Error::invalid_field(decl, "source")); |
| } |
| None => { |
| self.errors.push(Error::missing_field(decl, "source")); |
| } |
| } |
| self.validate_storage_target(decl, source_name.map(|n| n.as_str()), target); |
| } |
| |
| 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 parent and framework are valid. |
| match event.source { |
| Some(fsys::Ref::Parent(_)) => {} |
| 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 let Some(_) = self |
| .target_ids |
| .entry(target_id) |
| .or_insert(HashMap::new()) |
| .insert(target_name, AllowableIds::One) |
| { |
| 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_events_mode(&event.mode, "OfferEventDecl", "mode", &mut self.errors); |
| } |
| |
| fn validate_environment_debug_registration(&mut self, debug: &'a fsys::DebugRegistration) { |
| match debug { |
| fsys::DebugRegistration::Protocol(o) => { |
| let decl = "DebugProtocolRegistration"; |
| self.validate_environment_debug_fields( |
| decl, |
| o.source.as_ref(), |
| o.source_name.as_ref(), |
| o.target_name.as_ref(), |
| ); |
| |
| if let (Some(fsys::Ref::Self_(_)), Some(ref name)) = (&o.source, &o.source_name) { |
| if !self.all_protocols.contains(&name as &str) { |
| self.errors.push(Error::invalid_field(decl, "source")); |
| } |
| } |
| } |
| fsys::DebugRegistrationUnknown!() => { |
| self.errors.push(Error::invalid_field("EnvironmentDecl", "debug")); |
| } |
| } |
| } |
| |
| fn validate_environment_debug_fields( |
| &mut self, |
| decl: &str, |
| source: Option<&fsys::Ref>, |
| source_name: Option<&String>, |
| target_name: Option<&'a String>, |
| ) { |
| // We don't support "source" from "capability" for now. |
| match source { |
| Some(fsys::Ref::Parent(_)) => {} |
| 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_name(source_name, decl, "source_name", &mut self.errors); |
| check_name(target_name, 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_names: AllowableIds, |
| child: &'a fsys::ChildRef, |
| source: Option<&fsys::Ref>, |
| target_name: Option<&'a String>, |
| ) { |
| if !self.validate_child_ref(decl, "target", child) { |
| return; |
| } |
| if let Some(target_name) = target_name { |
| let names_for_target = |
| self.target_ids.entry(TargetId::Component(&child.name)).or_insert(HashMap::new()); |
| if let Some(prev_state) = names_for_target.insert(target_name, allowable_names) { |
| if prev_state == AllowableIds::One || prev_state != allowable_names { |
| self.errors.push(Error::duplicate_field( |
| decl, |
| "target_name", |
| target_name 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_names: AllowableIds, |
| collection: &'a fsys::CollectionRef, |
| target_name: Option<&'a String>, |
| ) { |
| if !self.validate_collection_ref(decl, "target", &collection) { |
| return; |
| } |
| if let Some(target_name) = target_name { |
| let names_for_target = self |
| .target_ids |
| .entry(TargetId::Collection(&collection.name)) |
| .or_insert(HashMap::new()); |
| if let Some(prev_state) = names_for_target.insert(target_name, allowable_names) { |
| if prev_state == AllowableIds::One || prev_state != allowable_names { |
| self.errors.push(Error::duplicate_field( |
| decl, |
| "target_name", |
| target_name 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() { |
| let b = b as char; |
| if b.is_ascii_alphanumeric() || b == '_' || b == '-' || b == '.' { |
| // Ok |
| } else { |
| errors.push(Error::invalid_field(decl_type, keyword)); |
| } |
| } |
| } |
| start_err_len == errors.len() |
| } |
| |
| // TODO: This should probably be checking with the `url` crate |
| 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; |
| } |
| } |
| } |
| _ => { |
| errors.push(Error::invalid_field(decl_type, keyword)); |
| 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::ParseError::InvalidLength => { |
| if scheme.is_empty() { |
| Error::empty_field(decl_type, keyword) |
| } else { |
| Error::field_too_long(decl_type, keyword) |
| } |
| } |
| cm_types::ParseError::InvalidValue => Error::invalid_field(decl_type, keyword), |
| e => { |
| panic!("unexpected parse error: {:?}", e); |
| } |
| }); |
| return false; |
| } |
| } else { |
| errors.push(Error::missing_field(decl_type, keyword)); |
| return false; |
| } |
| true |
| } |
| |
| /// Events mode should be always present. |
| fn check_events_mode( |
| mode: &Option<fsys::EventMode>, |
| decl_type: &str, |
| field_name: &str, |
| errors: &mut Vec<Error>, |
| ) { |
| if mode.is_none() { |
| errors.push(Error::missing_field(decl_type, field_name)); |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, fidl_fuchsia_data as fdata, fidl_fuchsia_io2 as fio2, fidl_fuchsia_sys2::*, |
| lazy_static::lazy_static, proptest::prelude::*, regex::Regex, |
| }; |
| |
| const PATH_REGEX_STR: &str = r"(/[^/]+)+"; |
| const NAME_REGEX_STR: &str = r"[0-9a-zA-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!(res, expected_res); |
| } |
| |
| fn validate_test_any_result(input: ComponentDecl, expected_res: Vec<Result<(), ErrorList>>) { |
| let res = format!("{:?}", validate(&input)); |
| let expected_res_debug = format!("{:?}", expected_res); |
| |
| let matched_exp = |
| expected_res.into_iter().find(|expected| res == format!("{:?}", expected)); |
| |
| assert!( |
| matched_exp.is_some(), |
| "assertion failed: Expected one of:\n{:?}\nActual:\n{:?}", |
| expected_res_debug, |
| res |
| ); |
| } |
| |
| fn validate_capabilities_test(input: Vec<CapabilityDecl>, expected_res: Result<(), ErrorList>) { |
| let res = validate_capabilities(&input); |
| assert_eq!(res, 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, |
| capabilities: None, |
| children: None, |
| collections: None, |
| environments: None, |
| ..ComponentDecl::EMPTY |
| } |
| } |
| |
| #[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_validate_any_result { |
| ( |
| $( |
| $test_name:ident => { |
| input = $input:expr, |
| results = $results:expr, |
| }, |
| )+ |
| ) => { |
| $( |
| #[test] |
| fn $test_name() { |
| validate_test_any_result($input, $results); |
| } |
| )+ |
| } |
| } |
| |
| macro_rules! test_validate_capabilities { |
| ( |
| $( |
| $test_name:ident => { |
| input = $input:expr, |
| result = $result:expr, |
| }, |
| )+ |
| ) => { |
| $( |
| #[test] |
| fn $test_name() { |
| validate_capabilities_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, |
| ..ChildDecl::EMPTY |
| } |
| }).collect(); |
| decl.offers = Some(offers); |
| decl.children = Some(children); |
| let result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle( |
| directed_graph::Error::CyclesDetected([vec!["child a", "child b", "child a"]].iter().cloned().collect()).format_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, |
| ..ChildDecl::EMPTY |
| } |
| }).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_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_any_result! { |
| test_validate_use_disallows_nested_dirs => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| UseDecl::Directory(UseDirectoryDecl { |
| source: Some(fsys::Ref::Parent(fsys::ParentRef {})), |
| source_name: Some("abc".to_string()), |
| target_path: Some("/foo/bar".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| ..UseDirectoryDecl::EMPTY |
| }), |
| UseDecl::Directory(UseDirectoryDecl { |
| source: Some(fsys::Ref::Parent(fsys::ParentRef {})), |
| source_name: Some("abc".to_string()), |
| target_path: Some("/foo/bar/baz".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| ..UseDirectoryDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| results = vec![ |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseDirectoryDecl", "/foo/bar/baz", "UseDirectoryDecl", "/foo/bar"), |
| ])), |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseDirectoryDecl", "/foo/bar", "UseDirectoryDecl", "/foo/bar/baz"), |
| ])), |
| ], |
| }, |
| test_validate_use_disallows_common_prefixes_protocol => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| UseDecl::Directory(UseDirectoryDecl { |
| source: Some(fsys::Ref::Parent(fsys::ParentRef {})), |
| source_name: Some("abc".to_string()), |
| target_path: Some("/foo/bar".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| ..UseDirectoryDecl::EMPTY |
| }), |
| UseDecl::Protocol(UseProtocolDecl { |
| source: Some(fsys::Ref::Parent(fsys::ParentRef {})), |
| source_name: Some("crow".to_string()), |
| target_path: Some("/foo/bar/fuchsia.2".to_string()), |
| ..UseProtocolDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| results = vec![ |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseProtocolDecl", "/foo/bar/fuchsia.2", "UseDirectoryDecl", "/foo/bar"), |
| ])), |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseDirectoryDecl", "/foo/bar", "UseProtocolDecl", "/foo/bar/fuchsia.2"), |
| ])), |
| ], |
| }, |
| test_validate_use_disallows_common_prefixes_service => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| UseDecl::Directory(UseDirectoryDecl { |
| source: Some(fsys::Ref::Parent(fsys::ParentRef {})), |
| source_name: Some("abc".to_string()), |
| target_path: Some("/foo/bar".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| ..UseDirectoryDecl::EMPTY |
| }), |
| UseDecl::Service(UseServiceDecl { |
| source: Some(fsys::Ref::Parent(fsys::ParentRef {})), |
| source_name: Some("space".to_string()), |
| target_path: Some("/foo/bar/baz/fuchsia.logger.Log".to_string()), |
| ..UseServiceDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| results = vec![ |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseServiceDecl", "/foo/bar/baz/fuchsia.logger.Log", "UseDirectoryDecl", "/foo/bar"), |
| ])), |
| Err(ErrorList::new(vec![ |
| Error::invalid_path_overlap( |
| "UseDirectoryDecl", "/foo/bar", "UseServiceDecl", "/foo/bar/baz/fuchsia.logger.Log"), |
| ])), |
| ], |
| }, |
| } |
| |
| test_validate! { |
| // uses |
| test_validate_uses_empty => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| UseDecl::Service(UseServiceDecl { |
| source: None, |
| source_name: None, |
| target_path: None, |
| ..UseServiceDecl::EMPTY |
| }), |
| UseDecl::Protocol(UseProtocolDecl { |
| source: None, |
| source_name: None, |
| target_path: None, |
| ..UseProtocolDecl::EMPTY |
| }), |
| UseDecl::Directory(UseDirectoryDecl { |
| source: None, |
| source_name: None, |
| target_path: None, |
| rights: None, |
| subdir: None, |
| ..UseDirectoryDecl::EMPTY |
| }), |
| UseDecl::Storage(UseStorageDecl { |
| source_name: None, |
| target_path: None, |
| ..UseStorageDecl::EMPTY |
| }), |
| UseDecl::Storage(UseStorageDecl { |
| source_name: Some("cache".to_string()), |
| target_path: None, |
| ..UseStorageDecl::EMPTY |
| }), |
| UseDecl::Runner(UseRunnerDecl { |
| source_name: None, |
| ..UseRunnerDecl::EMPTY |
| }), |
| UseDecl::Event(UseEventDecl { |
| source: None, |
| source_name: None, |
| target_name: None, |
| filter: None, |
| mode: None, |
| ..UseEventDecl::EMPTY |
| }), |
| UseDecl::EventStream(UseEventStreamDecl { |
| target_path: None, |
| events: None, |
| ..UseEventStreamDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("UseServiceDecl", "source"), |
| Error::missing_field("UseServiceDecl", "source_name"), |
| Error::missing_field("UseServiceDecl", "target_path"), |
| Error::missing_field("UseProtocolDecl", "source"), |
| Error::missing_field("UseProtocolDecl", "source_name"), |
| Error::missing_field("UseProtocolDecl", "target_path"), |
| Error::missing_field("UseDirectoryDecl", "source"), |
| Error::missing_field("UseDirectoryDecl", "source_name"), |
| Error::missing_field("UseDirectoryDecl", "target_path"), |
| Error::missing_field("UseDirectoryDecl", "rights"), |
| Error::missing_field("UseStorageDecl", "source_name"), |
| Error::missing_field("UseStorageDecl", "target_path"), |
| 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"), |
| Error::missing_field("UseEventDecl", "mode"), |
| Error::missing_field("UseEventStreamDecl", "target_path"), |
| Error::missing_field("UseEventStreamDecl", "events"), |
| ])), |
| }, |
| 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_name: Some("foo/".to_string()), |
| target_path: Some("/".to_string()), |
| ..UseServiceDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("UseServiceDecl", "source"), |
| Error::invalid_field("UseServiceDecl", "source_name"), |
| 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_name: Some("foo/".to_string()), |
| target_path: Some("/".to_string()), |
| ..UseProtocolDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("UseProtocolDecl", "source"), |
| Error::invalid_field("UseProtocolDecl", "source_name"), |
| 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_name: Some("foo/".to_string()), |
| target_path: Some("/".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| subdir: Some("/foo".to_string()), |
| ..UseDirectoryDecl::EMPTY |
| }), |
| UseDecl::Storage(UseStorageDecl { |
| source_name: Some("/cache".to_string()), |
| target_path: Some("/".to_string()), |
| ..UseStorageDecl::EMPTY |
| }), |
| UseDecl::Storage(UseStorageDecl { |
| source_name: Some("temp".to_string()), |
| target_path: Some("tmp".to_string()), |
| ..UseStorageDecl::EMPTY |
| }), |
| 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, ..fdata::Dictionary::EMPTY }), |
| mode: Some(EventMode::Async), |
| ..UseEventDecl::EMPTY |
| }), |
| UseDecl::Event(UseEventDecl { |
| source: Some(fsys::Ref::Framework(fsys::FrameworkRef {})), |
| source_name: Some("started".to_string()), |
| target_name: Some("started".to_string()), |
| filter: Some(fdata::Dictionary { entries: None, ..fdata::Dictionary::EMPTY }), |
| mode: Some(EventMode::Async), |
| ..UseEventDecl::EMPTY |
| }), |
| UseDecl::EventStream(UseEventStreamDecl { |
| target_path: Some("/bar".to_string()), |
| events: Some(vec!["/a".to_string(), "/b".to_string()].into_iter().map(|name| fsys::EventSubscription { |
| event_name: Some(name), |
| mode: Some(fsys::EventMode::Async), |
| ..fsys::EventSubscription::EMPTY |
| }).collect()), |
| ..UseEventStreamDecl::EMPTY |
| }), |
| UseDecl::EventStream(UseEventStreamDecl { |
| target_path: Some("/bleep".to_string()), |
| events: Some(vec![fsys::EventSubscription { |
| event_name: Some("started".to_string()), |
| mode: Some(fsys::EventMode::Sync), |
| ..fsys::EventSubscription::EMPTY |
| }]), |
| ..UseEventStreamDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("UseDirectoryDecl", "source"), |
| Error::invalid_field("UseDirectoryDecl", "source_name"), |
| Error::invalid_field("UseDirectoryDecl", "target_path"), |
| Error::invalid_field("UseDirectoryDecl", "subdir"), |
| Error::invalid_field("UseStorageDecl", "source_name"), |
| Error::invalid_field("UseStorageDecl", "target_path"), |
| Error::invalid_field("UseStorageDecl", "target_path"), |
| Error::invalid_field("UseEventDecl", "source"), |
| Error::invalid_field("UseEventDecl", "source_name"), |
| Error::invalid_field("UseEventDecl", "target_name"), |
| Error::invalid_field("UseEventStreamDecl", "event_name"), |
| Error::event_stream_event_not_found("UseEventStreamDecl", "events", "/a".to_string()), |
| Error::invalid_field("UseEventStreamDecl", "event_name"), |
| Error::event_stream_event_not_found("UseEventStreamDecl", "events", "/b".to_string()), |
| Error::event_stream_unsupported_mode("UseEventStreamDecl", "events", "started".to_string(), "Sync".to_string()), |
| ])), |
| }, |
| test_validate_uses_missing_source => { |
| input = { |
| ComponentDecl { |
| uses: Some(vec![ |
| UseDecl::Protocol(UseProtocolDecl { |
| source: Some(fsys::Ref::Capability(fsys::CapabilityRef { |
| name: "this-storage-doesnt-exist".to_string(), |
| })), |
| source_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| target_path: Some("/svc/fuchsia.sys2.StorageAdmin".to_string()), |
| ..UseProtocolDecl::EMPTY |
| }) |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_capability("UseProtocolDecl", "source", "this-storage-doesnt-exist"), |
| ])), |
| }, |
| test_validate_has_events_in_event_stream => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.uses = Some(vec![ |
| UseDecl::EventStream(UseEventStreamDecl { |
| target_path: Some("/bar".to_string()), |
| events: None, |
| ..UseEventStreamDecl::EMPTY |
| }), |
| UseDecl::EventStream(UseEventStreamDecl { |
| target_path: Some("/barbar".to_string()), |
| events: Some(vec![]), |
| ..UseEventStreamDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("UseEventStreamDecl", "events"), |
| Error::empty_field("UseEventStreamDecl", "events"), |
| ])), |
| }, |
| 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()), |
| ..UseRunnerDecl::EMPTY |
| }), |
| UseDecl::Runner(UseRunnerDecl { |
| source_name: Some("elf".to_string()), |
| ..UseRunnerDecl::EMPTY |
| }), |
| ]); |
| 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::Parent(fsys::ParentRef {})), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target_path: Some(format!("/s/{}", "b".repeat(1024))), |
| ..UseServiceDecl::EMPTY |
| }), |
| UseDecl::Protocol(UseProtocolDecl { |
| source: Some(fsys::Ref::Parent(fsys::ParentRef {})), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target_path: Some(format!("/p/{}", "c".repeat(1024))), |
| ..UseProtocolDecl::EMPTY |
| }), |
| UseDecl::Directory(UseDirectoryDecl { |
| source: Some(fsys::Ref::Parent(fsys::ParentRef {})), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target_path: Some(format!("/d/{}", "d".repeat(1024))), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| ..UseDirectoryDecl::EMPTY |
| }), |
| UseDecl::Storage(UseStorageDecl { |
| source_name: Some("cache".to_string()), |
| target_path: Some(format!("/{}", "e".repeat(1024))), |
| ..UseStorageDecl::EMPTY |
| }), |
| UseDecl::Runner(UseRunnerDecl { |
| source_name: Some(format!("{}", "a".repeat(101))), |
| ..UseRunnerDecl::EMPTY |
| }), |
| UseDecl::Event(UseEventDecl { |
| source: Some(fsys::Ref::Parent(fsys::ParentRef {})), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target_name: Some(format!("{}", "a".repeat(101))), |
| filter: None, |
| mode: Some(EventMode::Sync), |
| ..UseEventDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::field_too_long("UseServiceDecl", "source_name"), |
| Error::field_too_long("UseServiceDecl", "target_path"), |
| Error::field_too_long("UseProtocolDecl", "source_name"), |
| Error::field_too_long("UseProtocolDecl", "target_path"), |
| Error::field_too_long("UseDirectoryDecl", "source_name"), |
| 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::Parent(fsys::ParentRef {})), |
| source_name: Some("foo".to_string()), |
| target_path: Some("/bar".to_string()), |
| ..UseServiceDecl::EMPTY |
| }), |
| UseDecl::Protocol(UseProtocolDecl { |
| source: Some(fsys::Ref::Parent(fsys::ParentRef {})), |
| source_name: Some("space".to_string()), |
| target_path: Some("/bar".to_string()), |
| ..UseProtocolDecl::EMPTY |
| }), |
| UseDecl::Directory(UseDirectoryDecl { |
| source: Some(fsys::Ref::Parent(fsys::ParentRef {})), |
| source_name: Some("crow".to_string()), |
| target_path: Some("/bar".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| ..UseDirectoryDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| 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_name: None, |
| target_name: None, |
| target: None, |
| ..ExposeServiceDecl::EMPTY |
| }), |
| ExposeDecl::Protocol(ExposeProtocolDecl { |
| source: None, |
| source_name: None, |
| target_name: None, |
| target: None, |
| ..ExposeProtocolDecl::EMPTY |
| }), |
| ExposeDecl::Directory(ExposeDirectoryDecl { |
| source: None, |
| source_name: None, |
| target_name: None, |
| target: None, |
| rights: None, |
| subdir: None, |
| ..ExposeDirectoryDecl::EMPTY |
| }), |
| ExposeDecl::Runner(ExposeRunnerDecl { |
| source: None, |
| source_name: None, |
| target: None, |
| target_name: None, |
| ..ExposeRunnerDecl::EMPTY |
| }), |
| ExposeDecl::Resolver(ExposeResolverDecl { |
| source: None, |
| source_name: None, |
| target: None, |
| target_name: None, |
| ..ExposeResolverDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("ExposeServiceDecl", "source"), |
| Error::missing_field("ExposeServiceDecl", "target"), |
| Error::missing_field("ExposeServiceDecl", "source_name"), |
| Error::missing_field("ExposeServiceDecl", "target_name"), |
| Error::missing_field("ExposeProtocolDecl", "source"), |
| Error::missing_field("ExposeProtocolDecl", "target"), |
| Error::missing_field("ExposeProtocolDecl", "source_name"), |
| Error::missing_field("ExposeProtocolDecl", "target_name"), |
| Error::missing_field("ExposeDirectoryDecl", "source"), |
| Error::missing_field("ExposeDirectoryDecl", "target"), |
| Error::missing_field("ExposeDirectoryDecl", "source_name"), |
| Error::missing_field("ExposeDirectoryDecl", "target_name"), |
| 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_name: Some("logger".to_string()), |
| target_name: Some("logger".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| ..ExposeServiceDecl::EMPTY |
| }), |
| ExposeDecl::Protocol(ExposeProtocolDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "logger".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("legacy_logger".to_string()), |
| target_name: Some("legacy_logger".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| ..ExposeProtocolDecl::EMPTY |
| }), |
| ExposeDecl::Directory(ExposeDirectoryDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "netstack".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("data".to_string()), |
| target_name: Some("data".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| ..ExposeDirectoryDecl::EMPTY |
| }), |
| 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::Parent(ParentRef {})), |
| target_name: Some("elf".to_string()), |
| ..ExposeRunnerDecl::EMPTY |
| }), |
| 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::Parent(ParentRef {})), |
| target_name: Some("pkg".to_string()), |
| ..ExposeResolverDecl::EMPTY |
| }), |
| ]); |
| 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_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_name: Some("foo/".to_string()), |
| target_name: Some("/".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| ..ExposeServiceDecl::EMPTY |
| }), |
| ExposeDecl::Protocol(ExposeProtocolDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("foo/".to_string()), |
| target_name: Some("/".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| ..ExposeProtocolDecl::EMPTY |
| }), |
| ExposeDecl::Directory(ExposeDirectoryDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("foo/".to_string()), |
| target_name: Some("/".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| rights: Some(fio2::Operations::Connect), |
| subdir: Some("/foo".to_string()), |
| ..ExposeDirectoryDecl::EMPTY |
| }), |
| ExposeDecl::Runner(ExposeRunnerDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("/path".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("elf!".to_string()), |
| ..ExposeRunnerDecl::EMPTY |
| }), |
| ExposeDecl::Resolver(ExposeResolverDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("/path".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("pkg!".to_string()), |
| ..ExposeResolverDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("ExposeServiceDecl", "source.child.name"), |
| Error::invalid_field("ExposeServiceDecl", "source_name"), |
| Error::invalid_field("ExposeServiceDecl", "target_name"), |
| Error::invalid_field("ExposeProtocolDecl", "source.child.name"), |
| Error::invalid_field("ExposeProtocolDecl", "source_name"), |
| Error::invalid_field("ExposeProtocolDecl", "target_name"), |
| Error::invalid_field("ExposeDirectoryDecl", "source.child.name"), |
| Error::invalid_field("ExposeDirectoryDecl", "source_name"), |
| Error::invalid_field("ExposeDirectoryDecl", "target_name"), |
| Error::invalid_field("ExposeDirectoryDecl", "subdir"), |
| Error::invalid_field("ExposeRunnerDecl", "source.child.name"), |
| Error::invalid_field("ExposeRunnerDecl", "source_name"), |
| Error::invalid_field("ExposeRunnerDecl", "target_name"), |
| Error::invalid_field("ExposeResolverDecl", "source.child.name"), |
| Error::invalid_field("ExposeResolverDecl", "source_name"), |
| Error::invalid_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, |
| ..ChildDecl::EMPTY |
| }]); |
| decl.exposes = Some(vec![ |
| ExposeDecl::Service(ExposeServiceDecl { |
| source: None, |
| source_name: Some("a".to_string()), |
| target_name: Some("b".to_string()), |
| target: None, |
| ..ExposeServiceDecl::EMPTY |
| }), |
| ExposeDecl::Protocol(ExposeProtocolDecl { |
| source: Some(Ref::Parent(ParentRef {})), |
| source_name: Some("c".to_string()), |
| target_name: Some("d".to_string()), |
| target: Some(Ref::Self_(SelfRef {})), |
| ..ExposeProtocolDecl::EMPTY |
| }), |
| ExposeDecl::Directory(ExposeDirectoryDecl { |
| source: Some(Ref::Collection(CollectionRef {name: "z".to_string()})), |
| source_name: Some("e".to_string()), |
| target_name: Some("f".to_string()), |
| target: Some(Ref::Collection(CollectionRef {name: "z".to_string()})), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| ..ExposeDirectoryDecl::EMPTY |
| }), |
| ExposeDecl::Directory(ExposeDirectoryDecl { |
| source: Some(Ref::Parent(ParentRef {})), |
| source_name: Some("g".to_string()), |
| target_name: Some("h".to_string()), |
| target: Some(Ref::Framework(FrameworkRef {})), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| ..ExposeDirectoryDecl::EMPTY |
| }), |
| ExposeDecl::Runner(ExposeRunnerDecl { |
| source: Some(Ref::Parent(ParentRef {})), |
| source_name: Some("i".to_string()), |
| target: Some(Ref::Framework(FrameworkRef {})), |
| target_name: Some("j".to_string()), |
| ..ExposeRunnerDecl::EMPTY |
| }), |
| ExposeDecl::Resolver(ExposeResolverDecl { |
| source: Some(Ref::Parent(ParentRef {})), |
| source_name: Some("k".to_string()), |
| target: Some(Ref::Framework(FrameworkRef {})), |
| target_name: Some("l".to_string()), |
| ..ExposeResolverDecl::EMPTY |
| }), |
| ExposeDecl::Directory(ExposeDirectoryDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("m".to_string()), |
| target_name: Some("n".to_string()), |
| target: Some(Ref::Framework(FrameworkRef {})), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| ..ExposeDirectoryDecl::EMPTY |
| }), |
| ]); |
| 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", "target"), |
| ])), |
| }, |
| 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_name: Some(format!("{}", "a".repeat(1025))), |
| target_name: Some(format!("{}", "b".repeat(1025))), |
| target: Some(Ref::Parent(ParentRef {})), |
| ..ExposeServiceDecl::EMPTY |
| }), |
| ExposeDecl::Protocol(ExposeProtocolDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| })), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| target: Some(Ref::Parent(ParentRef {})), |
| ..ExposeProtocolDecl::EMPTY |
| }), |
| ExposeDecl::Directory(ExposeDirectoryDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| })), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| target: Some(Ref::Parent(ParentRef {})), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| ..ExposeDirectoryDecl::EMPTY |
| }), |
| ExposeDecl::Runner(ExposeRunnerDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| })), |
| source_name: Some("a".repeat(101)), |
| target: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("b".repeat(101)), |
| ..ExposeRunnerDecl::EMPTY |
| }), |
| ExposeDecl::Resolver(ExposeResolverDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| })), |
| source_name: Some("a".repeat(101)), |
| target: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("b".repeat(101)), |
| ..ExposeResolverDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::field_too_long("ExposeServiceDecl", "source.child.name"), |
| Error::field_too_long("ExposeServiceDecl", "source_name"), |
| Error::field_too_long("ExposeServiceDecl", "target_name"), |
| Error::field_too_long("ExposeProtocolDecl", "source.child.name"), |
| Error::field_too_long("ExposeProtocolDecl", "source_name"), |
| Error::field_too_long("ExposeProtocolDecl", "target_name"), |
| Error::field_too_long("ExposeDirectoryDecl", "source.child.name"), |
| Error::field_too_long("ExposeDirectoryDecl", "source_name"), |
| Error::field_too_long("ExposeDirectoryDecl", "target_name"), |
| 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_name: Some("fuchsia.logger.Log".to_string()), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| ..ExposeServiceDecl::EMPTY |
| }), |
| ExposeDecl::Protocol(ExposeProtocolDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| })), |
| source_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| target_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| ..ExposeProtocolDecl::EMPTY |
| }), |
| ExposeDecl::Directory(ExposeDirectoryDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| })), |
| source_name: Some("data".to_string()), |
| target_name: Some("data".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| ..ExposeDirectoryDecl::EMPTY |
| }), |
| ExposeDecl::Runner(ExposeRunnerDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| })), |
| source_name: Some("elf".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("elf".to_string()), |
| ..ExposeRunnerDecl::EMPTY |
| }), |
| ExposeDecl::Resolver(ExposeResolverDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| })), |
| source_name: Some("pkg".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("pkg".to_string()), |
| ..ExposeResolverDecl::EMPTY |
| }), |
| ]); |
| 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_invalid_source_capability => { |
| input = { |
| ComponentDecl { |
| exposes: Some(vec![ |
| ExposeDecl::Protocol(ExposeProtocolDecl { |
| source: Some(Ref::Capability(CapabilityRef { |
| name: "this-storage-doesnt-exist".to_string(), |
| })), |
| source_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| target_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| ..ExposeProtocolDecl::EMPTY |
| }), |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_capability("ExposeProtocolDecl", "source", "this-storage-doesnt-exist"), |
| ])), |
| }, |
| test_validate_exposes_duplicate_target => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.exposes = Some(vec![ |
| ExposeDecl::Service(ExposeServiceDecl { |
| source: Some(Ref::Self_(SelfRef{})), |
| source_name: Some("netstack".to_string()), |
| target_name: Some("fuchsia.net.Stack".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| ..ExposeServiceDecl::EMPTY |
| }), |
| ExposeDecl::Service(ExposeServiceDecl { |
| source: Some(Ref::Self_(SelfRef{})), |
| source_name: Some("netstack2".to_string()), |
| target_name: Some("fuchsia.net.Stack".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| ..ExposeServiceDecl::EMPTY |
| }), |
| ExposeDecl::Protocol(ExposeProtocolDecl { |
| source: Some(Ref::Self_(SelfRef{})), |
| source_name: Some("fonts".to_string()), |
| target_name: Some("fuchsia.fonts.Provider".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| ..ExposeProtocolDecl::EMPTY |
| }), |
| ExposeDecl::Protocol(ExposeProtocolDecl { |
| source: Some(Ref::Self_(SelfRef{})), |
| source_name: Some("fonts2".to_string()), |
| target_name: Some("fuchsia.fonts.Provider".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| ..ExposeProtocolDecl::EMPTY |
| }), |
| ExposeDecl::Directory(ExposeDirectoryDecl { |
| source: Some(Ref::Self_(SelfRef{})), |
| source_name: Some("assets".to_string()), |
| target_name: Some("stuff".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| rights: None, |
| subdir: None, |
| ..ExposeDirectoryDecl::EMPTY |
| }), |
| ExposeDecl::Directory(ExposeDirectoryDecl { |
| source: Some(Ref::Self_(SelfRef{})), |
| source_name: Some("assets2".to_string()), |
| target_name: Some("stuff".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| rights: None, |
| subdir: None, |
| ..ExposeDirectoryDecl::EMPTY |
| }), |
| ExposeDecl::Runner(ExposeRunnerDecl { |
| source: Some(Ref::Self_(SelfRef{})), |
| source_name: Some("source_elf".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("elf".to_string()), |
| ..ExposeRunnerDecl::EMPTY |
| }), |
| ExposeDecl::Runner(ExposeRunnerDecl { |
| source: Some(Ref::Self_(SelfRef{})), |
| source_name: Some("source_elf".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("elf".to_string()), |
| ..ExposeRunnerDecl::EMPTY |
| }), |
| ExposeDecl::Resolver(ExposeResolverDecl { |
| source: Some(Ref::Self_(SelfRef{})), |
| source_name: Some("source_pkg".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("pkg".to_string()), |
| ..ExposeResolverDecl::EMPTY |
| }), |
| ExposeDecl::Resolver(ExposeResolverDecl { |
| source: Some(Ref::Self_(SelfRef{})), |
| source_name: Some("source_pkg".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("pkg".to_string()), |
| ..ExposeResolverDecl::EMPTY |
| }), |
| ]); |
| decl.capabilities = Some(vec![ |
| CapabilityDecl::Service(ServiceDecl { |
| name: Some("netstack".to_string()), |
| source_path: Some("/path".to_string()), |
| ..ServiceDecl::EMPTY |
| }), |
| CapabilityDecl::Service(ServiceDecl { |
| name: Some("netstack2".to_string()), |
| source_path: Some("/path".to_string()), |
| ..ServiceDecl::EMPTY |
| }), |
| CapabilityDecl::Protocol(ProtocolDecl { |
| name: Some("fonts".to_string()), |
| source_path: Some("/path".to_string()), |
| ..ProtocolDecl::EMPTY |
| }), |
| CapabilityDecl::Protocol(ProtocolDecl { |
| name: Some("fonts2".to_string()), |
| source_path: Some("/path".to_string()), |
| ..ProtocolDecl::EMPTY |
| }), |
| CapabilityDecl::Directory(DirectoryDecl { |
| name: Some("assets".to_string()), |
| source_path: Some("/path".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| ..DirectoryDecl::EMPTY |
| }), |
| CapabilityDecl::Directory(DirectoryDecl { |
| name: Some("assets2".to_string()), |
| source_path: Some("/path".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| ..DirectoryDecl::EMPTY |
| }), |
| CapabilityDecl::Runner(RunnerDecl { |
| name: Some("source_elf".to_string()), |
| source: Some(Ref::Self_(SelfRef{})), |
| source_path: Some("/path".to_string()), |
| ..RunnerDecl::EMPTY |
| }), |
| CapabilityDecl::Resolver(ResolverDecl { |
| name: Some("source_pkg".to_string()), |
| source_path: Some("/path".to_string()), |
| ..ResolverDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| // Duplicate services are allowed. |
| Error::duplicate_field("ExposeProtocolDecl", "target_name", |
| "fuchsia.fonts.Provider"), |
| Error::duplicate_field("ExposeDirectoryDecl", "target_name", |
| "stuff"), |
| Error::duplicate_field("ExposeRunnerDecl", "target_name", |
| "elf"), |
| Error::duplicate_field("ExposeResolverDecl", "target_name", "pkg"), |
| ])), |
| }, |
| // TODO: Add analogous test for offer |
| test_validate_exposes_invalid_capability_from_self => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.exposes = Some(vec![ |
| ExposeDecl::Service(ExposeServiceDecl { |
| source: Some(Ref::Self_(SelfRef{})), |
| source_name: Some("fuchsia.netstack.Netstack".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("foo".to_string()), |
| ..ExposeServiceDecl::EMPTY |
| }), |
| ExposeDecl::Protocol(ExposeProtocolDecl { |
| source: Some(Ref::Self_(SelfRef{})), |
| source_name: Some("fuchsia.netstack.Netstack".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("bar".to_string()), |
| ..ExposeProtocolDecl::EMPTY |
| }), |
| ExposeDecl::Directory(ExposeDirectoryDecl { |
| source: Some(Ref::Self_(SelfRef{})), |
| source_name: Some("dir".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("assets".to_string()), |
| rights: None, |
| subdir: None, |
| ..ExposeDirectoryDecl::EMPTY |
| }), |
| ExposeDecl::Runner(ExposeRunnerDecl { |
| source: Some(Ref::Self_(SelfRef{})), |
| source_name: Some("source_elf".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("elf".to_string()), |
| ..ExposeRunnerDecl::EMPTY |
| }), |
| ExposeDecl::Resolver(ExposeResolverDecl { |
| source: Some(Ref::Self_(SelfRef{})), |
| source_name: Some("source_pkg".to_string()), |
| target: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("pkg".to_string()), |
| ..ExposeResolverDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_capability("ExposeServiceDecl", "source", "fuchsia.netstack.Netstack"), |
| Error::invalid_capability("ExposeProtocolDecl", "source", "fuchsia.netstack.Netstack"), |
| Error::invalid_capability("ExposeDirectoryDecl", "source", "dir"), |
| Error::invalid_capability("ExposeRunnerDecl", "source", "source_elf"), |
| Error::invalid_capability("ExposeResolverDecl", "source", "source_pkg"), |
| ])), |
| }, |
| |
| // offers |
| test_validate_offers_empty => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| OfferDecl::Service(OfferServiceDecl { |
| source: None, |
| source_name: None, |
| target: None, |
| target_name: None, |
| ..OfferServiceDecl::EMPTY |
| }), |
| OfferDecl::Protocol(OfferProtocolDecl { |
| source: None, |
| source_name: None, |
| target: None, |
| target_name: None, |
| dependency_type: None, |
| ..OfferProtocolDecl::EMPTY |
| }), |
| OfferDecl::Directory(OfferDirectoryDecl { |
| source: None, |
| source_name: None, |
| target: None, |
| target_name: None, |
| rights: None, |
| subdir: None, |
| dependency_type: None, |
| ..OfferDirectoryDecl::EMPTY |
| }), |
| OfferDecl::Storage(OfferStorageDecl { |
| source_name: None, |
| source: None, |
| target: None, |
| target_name: None, |
| ..OfferStorageDecl::EMPTY |
| }), |
| OfferDecl::Runner(OfferRunnerDecl { |
| source: None, |
| source_name: None, |
| target: None, |
| target_name: None, |
| ..OfferRunnerDecl::EMPTY |
| }), |
| OfferDecl::Event(OfferEventDecl { |
| source: None, |
| source_name: None, |
| target: None, |
| target_name: None, |
| filter: None, |
| mode: None, |
| ..OfferEventDecl::EMPTY |
| }) |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("OfferServiceDecl", "source"), |
| Error::missing_field("OfferServiceDecl", "source_name"), |
| Error::missing_field("OfferServiceDecl", "target"), |
| Error::missing_field("OfferServiceDecl", "target_name"), |
| Error::missing_field("OfferProtocolDecl", "source"), |
| Error::missing_field("OfferProtocolDecl", "source_name"), |
| Error::missing_field("OfferProtocolDecl", "target"), |
| Error::missing_field("OfferProtocolDecl", "target_name"), |
| Error::missing_field("OfferProtocolDecl", "dependency_type"), |
| Error::missing_field("OfferDirectoryDecl", "source"), |
| Error::missing_field("OfferDirectoryDecl", "source_name"), |
| Error::missing_field("OfferDirectoryDecl", "target"), |
| Error::missing_field("OfferDirectoryDecl", "target_name"), |
| Error::missing_field("OfferDirectoryDecl", "dependency_type"), |
| Error::missing_field("OfferStorageDecl", "source_name"), |
| 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"), |
| Error::missing_field("OfferEventDecl", "mode"), |
| ])), |
| }, |
| 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_name: Some(format!("{}", "a".repeat(101))), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| } |
| )), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| ..OfferServiceDecl::EMPTY |
| }), |
| OfferDecl::Service(OfferServiceDecl { |
| source: Some(Ref::Parent(ParentRef {})), |
| source_name: Some("a".to_string()), |
| target: Some(Ref::Collection( |
| CollectionRef { |
| name: "b".repeat(101), |
| } |
| )), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| ..OfferServiceDecl::EMPTY |
| }), |
| OfferDecl::Protocol(OfferProtocolDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "a".repeat(101), |
| collection: None, |
| })), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| } |
| )), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferProtocolDecl::EMPTY |
| }), |
| OfferDecl::Protocol(OfferProtocolDecl { |
| source: Some(Ref::Parent(ParentRef {})), |
| source_name: Some("a".to_string()), |
| target: Some(Ref::Collection( |
| CollectionRef { |
| name: "b".repeat(101), |
| } |
| )), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| dependency_type: Some(DependencyType::WeakForMigration), |
| ..OfferProtocolDecl::EMPTY |
| }), |
| OfferDecl::Directory(OfferDirectoryDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "a".repeat(101), |
| collection: None, |
| })), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| } |
| )), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferDirectoryDecl::EMPTY |
| }), |
| OfferDecl::Directory(OfferDirectoryDecl { |
| source: Some(Ref::Parent(ParentRef {})), |
| source_name: Some("a".to_string()), |
| target: Some(Ref::Collection( |
| CollectionRef { |
| name: "b".repeat(101), |
| } |
| )), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| dependency_type: Some(DependencyType::WeakForMigration), |
| ..OfferDirectoryDecl::EMPTY |
| }), |
| OfferDecl::Storage(OfferStorageDecl { |
| source_name: Some("data".to_string()), |
| source: Some(Ref::Parent(ParentRef {})), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| } |
| )), |
| target_name: Some("data".to_string()), |
| ..OfferStorageDecl::EMPTY |
| }), |
| OfferDecl::Storage(OfferStorageDecl { |
| source_name: Some("data".to_string()), |
| source: Some(Ref::Parent(ParentRef {})), |
| target: Some(Ref::Collection( |
| CollectionRef { name: "b".repeat(101) } |
| )), |
| target_name: Some("data".to_string()), |
| ..OfferStorageDecl::EMPTY |
| }), |
| 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)), |
| ..OfferRunnerDecl::EMPTY |
| }), |
| 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)), |
| ..OfferResolverDecl::EMPTY |
| }), |
| OfferDecl::Event(OfferEventDecl { |
| source: Some(Ref::Parent(ParentRef {})), |
| 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, ..fdata::Dictionary::EMPTY }), |
| mode: Some(EventMode::Async), |
| ..OfferEventDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::field_too_long("OfferServiceDecl", "source.child.name"), |
| Error::field_too_long("OfferServiceDecl", "source_name"), |
| Error::field_too_long("OfferServiceDecl", "target.child.name"), |
| Error::field_too_long("OfferServiceDecl", "target_name"), |
| Error::field_too_long("OfferServiceDecl", "target.collection.name"), |
| Error::field_too_long("OfferServiceDecl", "target_name"), |
| Error::field_too_long("OfferProtocolDecl", "source.child.name"), |
| Error::field_too_long("OfferProtocolDecl", "source_name"), |
| Error::field_too_long("OfferProtocolDecl", "target.child.name"), |
| Error::field_too_long("OfferProtocolDecl", "target_name"), |
| Error::field_too_long("OfferProtocolDecl", "target.collection.name"), |
| Error::field_too_long("OfferProtocolDecl", "target_name"), |
| Error::field_too_long("OfferDirectoryDecl", "source.child.name"), |
| Error::field_too_long("OfferDirectoryDecl", "source_name"), |
| Error::field_too_long("OfferDirectoryDecl", "target.child.name"), |
| Error::field_too_long("OfferDirectoryDecl", "target_name"), |
| Error::field_too_long("OfferDirectoryDecl", "target.collection.name"), |
| Error::field_too_long("OfferDirectoryDecl", "target_name"), |
| 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_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_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: Some("modular".to_string()), |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| ..OfferServiceDecl::EMPTY |
| }), |
| OfferDecl::Protocol(OfferProtocolDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "logger".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: Some("modular".to_string()), |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferProtocolDecl::EMPTY |
| }), |
| OfferDecl::Directory(OfferDirectoryDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "logger".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("assets".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: Some("modular".to_string()), |
| } |
| )), |
| target_name: Some("assets".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| dependency_type: Some(DependencyType::WeakForMigration), |
| ..OfferDirectoryDecl::EMPTY |
| }), |
| OfferDecl::Storage(OfferStorageDecl { |
| source_name: Some("data".to_string()), |
| source: Some(Ref::Parent(ParentRef{ })), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: Some("modular".to_string()), |
| } |
| )), |
| target_name: Some("data".to_string()), |
| ..OfferStorageDecl::EMPTY |
| }), |
| 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()), |
| ..OfferRunnerDecl::EMPTY |
| }), |
| 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()), |
| ..OfferResolverDecl::EMPTY |
| }), |
| ]); |
| decl.capabilities = Some(vec![ |
| CapabilityDecl::Protocol(ProtocolDecl { |
| name: Some("fuchsia.logger.Log".to_string()), |
| source_path: Some("/svc/logger".to_string()), |
| ..ProtocolDecl::EMPTY |
| }), |
| CapabilityDecl::Directory(DirectoryDecl { |
| name: Some("assets".to_string()), |
| source_path: Some("/data/assets".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| ..DirectoryDecl::EMPTY |
| }), |
| ]); |
| 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_name: Some("foo/".to_string()), |
| target: Some(Ref::Child(ChildRef { |
| name: "%bad".to_string(), |
| collection: None, |
| })), |
| target_name: Some("/".to_string()), |
| ..OfferServiceDecl::EMPTY |
| }), |
| OfferDecl::Protocol(OfferProtocolDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("foo/".to_string()), |
| target: Some(Ref::Child(ChildRef { |
| name: "%bad".to_string(), |
| collection: None, |
| })), |
| target_name: Some("/".to_string()), |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferProtocolDecl::EMPTY |
| }), |
| OfferDecl::Directory(OfferDirectoryDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("foo/".to_string()), |
| target: Some(Ref::Child(ChildRef { |
| name: "%bad".to_string(), |
| collection: None, |
| })), |
| target_name: Some("/".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| subdir: Some("/foo".to_string()), |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferDirectoryDecl::EMPTY |
| }), |
| 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()), |
| ..OfferRunnerDecl::EMPTY |
| }), |
| 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()), |
| ..OfferResolverDecl::EMPTY |
| }), |
| OfferDecl::Event(OfferEventDecl { |
| source: Some(Ref::Parent(ParentRef {})), |
| 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, ..fdata::Dictionary::EMPTY }), |
| mode: Some(fsys::EventMode::Sync), |
| ..OfferEventDecl::EMPTY |
| }) |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("OfferServiceDecl", "source.child.name"), |
| Error::invalid_field("OfferServiceDecl", "source_name"), |
| Error::invalid_field("OfferServiceDecl", "target.child.name"), |
| Error::invalid_field("OfferServiceDecl", "target_name"), |
| Error::invalid_field("OfferProtocolDecl", "source.child.name"), |
| Error::invalid_field("OfferProtocolDecl", "source_name"), |
| Error::invalid_field("OfferProtocolDecl", "target.child.name"), |
| Error::invalid_field("OfferProtocolDecl", "target_name"), |
| Error::invalid_field("OfferDirectoryDecl", "source.child.name"), |
| Error::invalid_field("OfferDirectoryDecl", "source_name"), |
| Error::invalid_field("OfferDirectoryDecl", "target.child.name"), |
| Error::invalid_field("OfferDirectoryDecl", "target_name"), |
| Error::invalid_field("OfferDirectoryDecl", "subdir"), |
| Error::invalid_field("OfferRunnerDecl", "source.child.name"), |
| Error::invalid_field("OfferRunnerDecl", "source_name"), |
| Error::invalid_field("OfferRunnerDecl", "target.child.name"), |
| Error::invalid_field("OfferRunnerDecl", "target_name"), |
| Error::invalid_field("OfferResolverDecl", "source.child.name"), |
| Error::invalid_field("OfferResolverDecl", "source_name"), |
| Error::invalid_field("OfferResolverDecl", "target.child.name"), |
| Error::invalid_field("OfferResolverDecl", "target_name"), |
| Error::invalid_field("OfferEventDecl", "source_name"), |
| Error::invalid_field("OfferEventDecl", "target.child.name"), |
| Error::invalid_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_name: Some("logger".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("logger".to_string()), |
| ..OfferServiceDecl::EMPTY |
| }), |
| OfferDecl::Protocol(OfferProtocolDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("legacy_logger".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("legacy_logger".to_string()), |
| dependency_type: Some(DependencyType::WeakForMigration), |
| ..OfferProtocolDecl::EMPTY |
| }), |
| OfferDecl::Directory(OfferDirectoryDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("assets".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("assets".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferDirectoryDecl::EMPTY |
| }), |
| 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()), |
| ..OfferRunnerDecl::EMPTY |
| }), |
| 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()), |
| ..OfferResolverDecl::EMPTY |
| }), |
| ]); |
| 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, |
| ..ChildDecl::EMPTY |
| }]); |
| 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 { |
| source_name: Some("data".to_string()), |
| source: Some(Ref::Self_(SelfRef { })), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("data".to_string()), |
| ..OfferStorageDecl::EMPTY |
| }) |
| ]), |
| capabilities: Some(vec![ |
| CapabilityDecl::Storage(StorageDecl { |
| name: Some("data".to_string()), |
| backing_dir: Some("minfs".to_string()), |
| source: Some(Ref::Child(ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| subdir: None, |
| ..StorageDecl::EMPTY |
| }), |
| ]), |
| 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, |
| ..ChildDecl::EMPTY |
| }, |
| ]), |
| ..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_name: Some("fuchsia.logger.Log".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| ..OfferServiceDecl::EMPTY |
| }), |
| OfferDecl::Protocol(OfferProtocolDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferProtocolDecl::EMPTY |
| }), |
| OfferDecl::Directory(OfferDirectoryDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("assets".to_string()), |
| target: Some(Ref::Collection( |
| CollectionRef { name: "modular".to_string() } |
| )), |
| target_name: Some("assets".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| dependency_type: Some(DependencyType::WeakForMigration), |
| ..OfferDirectoryDecl::EMPTY |
| }), |
| ]); |
| decl.capabilities = Some(vec![ |
| CapabilityDecl::Storage(StorageDecl { |
| name: Some("memfs".to_string()), |
| backing_dir: Some("memfs".to_string()), |
| source: Some(Ref::Child(ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| subdir: None, |
| ..StorageDecl::EMPTY |
| }), |
| ]); |
| 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, |
| ..ChildDecl::EMPTY |
| }, |
| ]); |
| decl.collections = Some(vec![ |
| CollectionDecl { |
| name: Some("modular".to_string()), |
| durability: Some(Durability::Persistent), |
| environment: None, |
| ..CollectionDecl::EMPTY |
| }, |
| ]); |
| 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_invalid_source_capability => { |
| input = { |
| ComponentDecl { |
| offers: Some(vec![ |
| OfferDecl::Protocol(OfferProtocolDecl { |
| source: Some(Ref::Capability(CapabilityRef { |
| name: "this-storage-doesnt-exist".to_string(), |
| })), |
| source_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferProtocolDecl::EMPTY |
| }), |
| ]), |
| ..new_component_decl() |
| } |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_capability("OfferProtocolDecl", "source", "this-storage-doesnt-exist"), |
| Error::invalid_child("OfferProtocolDecl", "target", "netstack"), |
| ])), |
| }, |
| test_validate_offers_target => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.offers = Some(vec![ |
| OfferDecl::Service(OfferServiceDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("logger".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| ..OfferServiceDecl::EMPTY |
| }), |
| OfferDecl::Service(OfferServiceDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("logger2".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| ..OfferServiceDecl::EMPTY |
| }), |
| OfferDecl::Protocol(OfferProtocolDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferProtocolDecl::EMPTY |
| }), |
| OfferDecl::Protocol(OfferProtocolDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferProtocolDecl::EMPTY |
| }), |
| OfferDecl::Directory(OfferDirectoryDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("assets".to_string()), |
| target: Some(Ref::Collection( |
| CollectionRef { name: "modular".to_string() } |
| )), |
| target_name: Some("assets".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferDirectoryDecl::EMPTY |
| }), |
| OfferDecl::Directory(OfferDirectoryDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("assets".to_string()), |
| target: Some(Ref::Collection( |
| CollectionRef { name: "modular".to_string() } |
| )), |
| target_name: Some("assets".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| dependency_type: Some(DependencyType::WeakForMigration), |
| ..OfferDirectoryDecl::EMPTY |
| }), |
| OfferDecl::Runner(OfferRunnerDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("elf".to_string()), |
| target: Some(Ref::Collection( |
| CollectionRef { name: "modular".to_string() } |
| )), |
| target_name: Some("duplicated".to_string()), |
| ..OfferRunnerDecl::EMPTY |
| }), |
| OfferDecl::Runner(OfferRunnerDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("elf".to_string()), |
| target: Some(Ref::Collection( |
| CollectionRef { name: "modular".to_string() } |
| )), |
| target_name: Some("duplicated".to_string()), |
| ..OfferRunnerDecl::EMPTY |
| }), |
| OfferDecl::Resolver(OfferResolverDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("pkg".to_string()), |
| target: Some(Ref::Collection( |
| CollectionRef { name: "modular".to_string() } |
| )), |
| target_name: Some("duplicated".to_string()), |
| ..OfferResolverDecl::EMPTY |
| }), |
| OfferDecl::Event(OfferEventDecl { |
| source: Some(Ref::Parent(ParentRef {})), |
| 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, |
| mode: Some(EventMode::Async), |
| ..OfferEventDecl::EMPTY |
| }), |
| OfferDecl::Event(OfferEventDecl { |
| source: Some(Ref::Parent(ParentRef {})), |
| 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, |
| mode: Some(EventMode::Async), |
| ..OfferEventDecl::EMPTY |
| }), |
| ]); |
| 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, |
| ..ChildDecl::EMPTY |
| }, |
| ]); |
| decl.collections = Some(vec![ |
| CollectionDecl{ |
| name: Some("modular".to_string()), |
| durability: Some(Durability::Persistent), |
| environment: None, |
| ..CollectionDecl::EMPTY |
| }, |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| // Duplicate services are allowed. |
| Error::duplicate_field("OfferProtocolDecl", "target_name", "fuchsia.logger.LegacyLog"), |
| Error::duplicate_field("OfferDirectoryDecl", "target_name", "assets"), |
| 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::Parent(ParentRef{})), |
| source_name: Some("logger".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| ..OfferServiceDecl::EMPTY |
| }), |
| OfferDecl::Service(OfferServiceDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("logger".to_string()), |
| target: Some(Ref::Collection( |
| CollectionRef { name: "modular".to_string(), } |
| )), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| ..OfferServiceDecl::EMPTY |
| }), |
| OfferDecl::Protocol(OfferProtocolDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("legacy_logger".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| dependency_type: Some(DependencyType::WeakForMigration), |
| ..OfferProtocolDecl::EMPTY |
| }), |
| OfferDecl::Protocol(OfferProtocolDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("legacy_logger".to_string()), |
| target: Some(Ref::Collection( |
| CollectionRef { name: "modular".to_string(), } |
| )), |
| target_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferProtocolDecl::EMPTY |
| }), |
| OfferDecl::Directory(OfferDirectoryDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("assets".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("data".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferDirectoryDecl::EMPTY |
| }), |
| OfferDecl::Directory(OfferDirectoryDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("assets".to_string()), |
| target: Some(Ref::Collection( |
| CollectionRef { name: "modular".to_string(), } |
| )), |
| target_name: Some("data".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| dependency_type: Some(DependencyType::WeakForMigration), |
| ..OfferDirectoryDecl::EMPTY |
| }), |
| OfferDecl::Storage(OfferStorageDecl { |
| source_name: Some("data".to_string()), |
| source: Some(Ref::Parent(ParentRef{})), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("data".to_string()), |
| ..OfferStorageDecl::EMPTY |
| }), |
| OfferDecl::Storage(OfferStorageDecl { |
| source_name: Some("data".to_string()), |
| source: Some(Ref::Parent(ParentRef{})), |
| target: Some(Ref::Collection( |
| CollectionRef { name: "modular".to_string(), } |
| )), |
| target_name: Some("data".to_string()), |
| ..OfferStorageDecl::EMPTY |
| }), |
| OfferDecl::Runner(OfferRunnerDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("elf".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("elf".to_string()), |
| ..OfferRunnerDecl::EMPTY |
| }), |
| OfferDecl::Runner(OfferRunnerDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("elf".to_string()), |
| target: Some(Ref::Collection( |
| CollectionRef { name: "modular".to_string(), } |
| )), |
| target_name: Some("elf".to_string()), |
| ..OfferRunnerDecl::EMPTY |
| }), |
| OfferDecl::Resolver(OfferResolverDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("pkg".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("pkg".to_string()), |
| ..OfferResolverDecl::EMPTY |
| }), |
| OfferDecl::Resolver(OfferResolverDecl { |
| source: Some(Ref::Parent(ParentRef{})), |
| source_name: Some("pkg".to_string()), |
| target: Some(Ref::Collection( |
| CollectionRef { name: "modular".to_string(), } |
| )), |
| target_name: Some("pkg".to_string()), |
| ..OfferResolverDecl::EMPTY |
| }), |
| OfferDecl::Event(OfferEventDecl { |
| source_name: Some("started".to_string()), |
| source: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("started".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "netstack".to_string(), |
| collection: None, |
| } |
| )), |
| filter: None, |
| mode: Some(EventMode::Async), |
| ..OfferEventDecl::EMPTY |
| }), |
| OfferDecl::Event(OfferEventDecl { |
| source_name: Some("started".to_string()), |
| source: Some(Ref::Parent(ParentRef {})), |
| target_name: Some("started".to_string()), |
| target: Some(Ref::Collection( |
| CollectionRef { name: "modular".to_string(), } |
| )), |
| filter: None, |
| mode: Some(EventMode::Async), |
| ..OfferEventDecl::EMPTY |
| }), |
| ]); |
| 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() }), |
| ] |
| .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, ..fdata::Dictionary::EMPTY }), |
| mode: Some(EventMode::Sync), |
| ..OfferEventDecl::EMPTY |
| }) |
| }) |
| .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, |
| ..ChildDecl::EMPTY |
| }, |
| ]); |
| decl.collections = Some(vec![ |
| CollectionDecl { |
| name: Some("modular".to_string()), |
| durability: Some(Durability::Persistent), |
| environment: None, |
| ..CollectionDecl::EMPTY |
| }, |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| 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_name: Some(format!("thing_{}", from)), |
| target: Some(Ref::Child( |
| ChildRef { name: to.to_string(), collection: None }, |
| )), |
| target_name: Some(format!("thing_{}", from)), |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferProtocolDecl::EMPTY |
| })).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, |
| ..ChildDecl::EMPTY |
| } |
| }).collect(); |
| decl.offers = Some(offers); |
| decl.children = Some(children); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle(directed_graph::Error::CyclesDetected([vec!["child a", "child b", "child c", "child a"], vec!["child b", "child d", "child b"]].iter().cloned().collect()).format_cycle()), |
| ])), |
| }, |
| |
| // environments |
| test_validate_environment_empty => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![EnvironmentDecl { |
| name: None, |
| extends: None, |
| runners: None, |
| resolvers: None, |
| stop_timeout_ms: None, |
| debug_capabilities: None, |
| ..EnvironmentDecl::EMPTY |
| }]); |
| 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), |
| runners: None, |
| resolvers: None, |
| stop_timeout_ms: None, |
| ..EnvironmentDecl::EMPTY |
| }]); |
| 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), |
| runners: None, |
| resolvers: None, |
| stop_timeout_ms: None, |
| ..EnvironmentDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Ok(()), |
| }, |
| test_validate_environment_long_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![EnvironmentDecl { |
| name: Some("a".repeat(101)), |
| extends: Some(EnvironmentExtends::None), |
| runners: Some(vec![ |
| RunnerRegistration { |
| source_name: Some("a".repeat(101)), |
| source: Some(Ref::Parent(ParentRef{})), |
| target_name: Some("a".repeat(101)), |
| ..RunnerRegistration::EMPTY |
| }, |
| ]), |
| resolvers: Some(vec![ |
| ResolverRegistration { |
| resolver: Some("a".repeat(101)), |
| source: Some(Ref::Parent(ParentRef{})), |
| scheme: Some("a".repeat(101)), |
| ..ResolverRegistration::EMPTY |
| }, |
| ]), |
| stop_timeout_ms: Some(1234), |
| ..EnvironmentDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::field_too_long("EnvironmentDecl", "name"), |
| Error::field_too_long("RunnerRegistration", "source_name"), |
| Error::field_too_long("RunnerRegistration", "target_name"), |
| Error::field_too_long("ResolverRegistration", "resolver"), |
| Error::field_too_long("ResolverRegistration", "scheme"), |
| ])), |
| }, |
| test_validate_environment_empty_runner_resolver_fields => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![EnvironmentDecl { |
| name: Some("a".to_string()), |
| extends: Some(EnvironmentExtends::None), |
| runners: Some(vec![ |
| RunnerRegistration { |
| source_name: None, |
| source: None, |
| target_name: None, |
| ..RunnerRegistration::EMPTY |
| }, |
| ]), |
| resolvers: Some(vec![ |
| ResolverRegistration { |
| resolver: None, |
| source: None, |
| scheme: None, |
| ..ResolverRegistration::EMPTY |
| }, |
| ]), |
| stop_timeout_ms: Some(1234), |
| ..EnvironmentDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("RunnerRegistration", "source_name"), |
| Error::missing_field("RunnerRegistration", "source"), |
| Error::missing_field("RunnerRegistration", "target_name"), |
| Error::missing_field("ResolverRegistration", "resolver"), |
| Error::missing_field("ResolverRegistration", "source"), |
| Error::missing_field("ResolverRegistration", "scheme"), |
| ])), |
| }, |
| test_validate_environment_invalid_fields => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![EnvironmentDecl { |
| name: Some("a".to_string()), |
| extends: Some(EnvironmentExtends::None), |
| runners: Some(vec![ |
| RunnerRegistration { |
| source_name: Some("^a".to_string()), |
| source: Some(Ref::Framework(fsys::FrameworkRef{})), |
| target_name: Some("%a".to_string()), |
| ..RunnerRegistration::EMPTY |
| }, |
| ]), |
| resolvers: Some(vec![ |
| ResolverRegistration { |
| resolver: Some("^a".to_string()), |
| source: Some(Ref::Framework(fsys::FrameworkRef{})), |
| scheme: Some("9scheme".to_string()), |
| ..ResolverRegistration::EMPTY |
| }, |
| ]), |
| stop_timeout_ms: Some(1234), |
| ..EnvironmentDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("RunnerRegistration", "source_name"), |
| Error::invalid_field("RunnerRegistration", "source"), |
| Error::invalid_field("RunnerRegistration", "target_name"), |
| Error::invalid_field("ResolverRegistration", "resolver"), |
| Error::invalid_field("ResolverRegistration", "source"), |
| Error::invalid_field("ResolverRegistration", "scheme"), |
| ])), |
| }, |
| test_validate_environment_missing_runner => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![EnvironmentDecl { |
| name: Some("a".to_string()), |
| extends: Some(EnvironmentExtends::None), |
| runners: Some(vec![ |
| RunnerRegistration { |
| source_name: Some("dart".to_string()), |
| source: Some(Ref::Self_(SelfRef{})), |
| target_name: Some("dart".to_string()), |
| ..RunnerRegistration::EMPTY |
| }, |
| ]), |
| resolvers: None, |
| stop_timeout_ms: Some(1234), |
| ..EnvironmentDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_runner("RunnerRegistration", "source_name", "dart"), |
| ])), |
| }, |
| test_validate_environment_duplicate_registrations => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![EnvironmentDecl { |
| name: Some("a".to_string()), |
| extends: Some(EnvironmentExtends::None), |
| runners: Some(vec![ |
| RunnerRegistration { |
| source_name: Some("dart".to_string()), |
| source: Some(Ref::Parent(ParentRef{})), |
| target_name: Some("dart".to_string()), |
| ..RunnerRegistration::EMPTY |
| }, |
| RunnerRegistration { |
| source_name: Some("other-dart".to_string()), |
| source: Some(Ref::Parent(ParentRef{})), |
| target_name: Some("dart".to_string()), |
| ..RunnerRegistration::EMPTY |
| }, |
| ]), |
| resolvers: Some(vec![ |
| ResolverRegistration { |
| resolver: Some("pkg_resolver".to_string()), |
| source: Some(Ref::Parent(ParentRef{})), |
| scheme: Some("fuchsia-pkg".to_string()), |
| ..ResolverRegistration::EMPTY |
| }, |
| ResolverRegistration { |
| resolver: Some("base_resolver".to_string()), |
| source: Some(Ref::Parent(ParentRef{})), |
| scheme: Some("fuchsia-pkg".to_string()), |
| ..ResolverRegistration::EMPTY |
| }, |
| ]), |
| stop_timeout_ms: Some(1234), |
| ..EnvironmentDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::duplicate_field("RunnerRegistration", "target_name", "dart"), |
| Error::duplicate_field("ResolverRegistration", "scheme", "fuchsia-pkg"), |
| ])), |
| }, |
| test_validate_environment_from_missing_child => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![EnvironmentDecl { |
| name: Some("a".to_string()), |
| extends: Some(EnvironmentExtends::None), |
| runners: Some(vec![ |
| RunnerRegistration { |
| source_name: Some("elf".to_string()), |
| source: Some(Ref::Child(ChildRef{ |
| name: "missing".to_string(), |
| collection: None, |
| })), |
| target_name: Some("elf".to_string()), |
| ..RunnerRegistration::EMPTY |
| }, |
| ]), |
| 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()), |
| ..ResolverRegistration::EMPTY |
| }, |
| ]), |
| stop_timeout_ms: Some(1234), |
| ..EnvironmentDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_child("RunnerRegistration", "source", "missing"), |
| Error::invalid_child("ResolverRegistration", "source", "missing"), |
| ])), |
| }, |
| test_validate_environment_runner_child_cycle => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![EnvironmentDecl { |
| name: Some("env".to_string()), |
| extends: Some(EnvironmentExtends::None), |
| runners: Some(vec![ |
| RunnerRegistration { |
| source_name: Some("elf".to_string()), |
| source: Some(Ref::Child(ChildRef{ |
| name: "child".to_string(), |
| collection: None, |
| })), |
| target_name: Some("elf".to_string()), |
| ..RunnerRegistration::EMPTY |
| }, |
| ]), |
| resolvers: None, |
| stop_timeout_ms: Some(1234), |
| ..EnvironmentDecl::EMPTY |
| }]); |
| 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()), |
| ..ChildDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle( |
| directed_graph::Error::CyclesDetected([vec!["child child", "environment env", "child child"]].iter().cloned().collect()).format_cycle() |
| ), |
| ])), |
| }, |
| 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), |
| runners: 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()), |
| ..ResolverRegistration::EMPTY |
| }, |
| ]), |
| stop_timeout_ms: Some(1234), |
| ..EnvironmentDecl::EMPTY |
| }]); |
| 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()), |
| ..ChildDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle( |
| directed_graph::Error::CyclesDetected([vec!["child child", "environment env", "child child"]].iter().cloned().collect()).format_cycle() |
| ), |
| ])), |
| }, |
| test_validate_environment_resolver_multiple_children_cycle => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![EnvironmentDecl { |
| name: Some("env".to_string()), |
| extends: Some(EnvironmentExtends::None), |
| runners: None, |
| resolvers: Some(vec![ |
| ResolverRegistration { |
| resolver: Some("pkg_resolver".to_string()), |
| source: Some(Ref::Child(ChildRef{ |
| name: "a".to_string(), |
| collection: None, |
| })), |
| scheme: Some("fuchsia-pkg".to_string()), |
| ..ResolverRegistration::EMPTY |
| }, |
| ]), |
| stop_timeout_ms: Some(1234), |
| ..EnvironmentDecl::EMPTY |
| }]); |
| decl.children = Some(vec![ |
| ChildDecl { |
| name: Some("a".to_string()), |
| startup: Some(StartupMode::Lazy), |
| url: Some("fuchsia-pkg://child-a".to_string()), |
| environment: None, |
| ..ChildDecl::EMPTY |
| }, |
| ChildDecl { |
| name: Some("b".to_string()), |
| startup: Some(StartupMode::Lazy), |
| url: Some("fuchsia-pkg://child-b".to_string()), |
| environment: Some("env".to_string()), |
| ..ChildDecl::EMPTY |
| }, |
| ]); |
| decl.offers = Some(vec![OfferDecl::Service(OfferServiceDecl { |
| source: Some(Ref::Child(ChildRef { |
| name: "b".to_string(), |
| collection: None, |
| })), |
| source_name: Some("thing".to_string()), |
| target: Some(Ref::Child( |
| ChildRef { |
| name: "a".to_string(), |
| collection: None, |
| } |
| )), |
| target_name: Some("thing".to_string()), |
| ..OfferServiceDecl::EMPTY |
| })]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::dependency_cycle( |
| directed_graph::Error::CyclesDetected([vec!["child a", "environment env", "child b", "child a"]].iter().cloned().collect()).format_cycle() |
| ), |
| ])), |
| }, |
| test_validate_environment_debug_empty => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![ |
| EnvironmentDecl { |
| name: Some("a".to_string()), |
| extends: Some(EnvironmentExtends::None), |
| stop_timeout_ms: Some(2), |
| debug_capabilities:Some(vec![ |
| DebugRegistration::Protocol(DebugProtocolRegistration { |
| source: None, |
| source_name: None, |
| target_name: None, |
| ..DebugProtocolRegistration::EMPTY |
| }), |
| ]), |
| ..EnvironmentDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("DebugProtocolRegistration", "source"), |
| Error::missing_field("DebugProtocolRegistration", "source_name"), |
| Error::missing_field("DebugProtocolRegistration", "target_name"), |
| ])), |
| }, |
| test_validate_environment_debug_log_identifier => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![ |
| EnvironmentDecl { |
| name: Some("a".to_string()), |
| extends: Some(EnvironmentExtends::None), |
| stop_timeout_ms: Some(2), |
| debug_capabilities:Some(vec![ |
| DebugRegistration::Protocol(DebugProtocolRegistration { |
| source: Some(Ref::Child(ChildRef { |
| name: "a".repeat(101), |
| collection: None, |
| })), |
| source_name: Some(format!("{}", "a".repeat(101))), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| ..DebugProtocolRegistration::EMPTY |
| }), |
| DebugRegistration::Protocol(DebugProtocolRegistration { |
| source: Some(Ref::Parent(ParentRef {})), |
| source_name: Some("a".to_string()), |
| target_name: Some(format!("{}", "b".repeat(101))), |
| ..DebugProtocolRegistration::EMPTY |
| }), |
| ]), |
| ..EnvironmentDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::field_too_long("DebugProtocolRegistration", "source.child.name"), |
| Error::field_too_long("DebugProtocolRegistration", "source_name"), |
| Error::field_too_long("DebugProtocolRegistration", "target_name"), |
| Error::field_too_long("DebugProtocolRegistration", "target_name"), |
| ])), |
| }, |
| test_validate_environment_debug_log_extraneous => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![ |
| EnvironmentDecl { |
| name: Some("a".to_string()), |
| extends: Some(EnvironmentExtends::None), |
| stop_timeout_ms: Some(2), |
| debug_capabilities:Some(vec![ |
| DebugRegistration::Protocol(DebugProtocolRegistration { |
| source: Some(Ref::Child(ChildRef { |
| name: "logger".to_string(), |
| collection: Some("modular".to_string()), |
| })), |
| source_name: Some("fuchsia.logger.Log".to_string()), |
| target_name: Some("fuchsia.logger.Log".to_string()), |
| ..DebugProtocolRegistration::EMPTY |
| }), |
| ]), |
| ..EnvironmentDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::extraneous_field("DebugProtocolRegistration", "source.child.collection"), |
| ])), |
| }, |
| test_validate_environment_debug_log_invalid_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![ |
| EnvironmentDecl { |
| name: Some("a".to_string()), |
| extends: Some(EnvironmentExtends::None), |
| stop_timeout_ms: Some(2), |
| debug_capabilities:Some(vec![ |
| DebugRegistration::Protocol(DebugProtocolRegistration { |
| source: Some(Ref::Child(ChildRef { |
| name: "^bad".to_string(), |
| collection: None, |
| })), |
| source_name: Some("foo/".to_string()), |
| target_name: Some("/".to_string()), |
| ..DebugProtocolRegistration::EMPTY |
| }), |
| ]), |
| ..EnvironmentDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("DebugProtocolRegistration", "source.child.name"), |
| Error::invalid_field("DebugProtocolRegistration", "source_name"), |
| Error::invalid_field("DebugProtocolRegistration", "target_name"), |
| ])), |
| }, |
| test_validate_environment_debug_log_invalid_child => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![ |
| EnvironmentDecl { |
| name: Some("a".to_string()), |
| extends: Some(EnvironmentExtends::None), |
| stop_timeout_ms: Some(2), |
| debug_capabilities:Some(vec![ |
| DebugRegistration::Protocol(DebugProtocolRegistration { |
| source: Some(Ref::Child(ChildRef { |
| name: "logger".to_string(), |
| collection: None, |
| })), |
| source_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| target_name: Some("fuchsia.logger.LegacyLog".to_string()), |
| ..DebugProtocolRegistration::EMPTY |
| }), |
| ]), |
| ..EnvironmentDecl::EMPTY |
| }]); |
| 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, |
| ..ChildDecl::EMPTY |
| }, |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_child("DebugProtocolRegistration", "source", "logger"), |
| |
| ])), |
| }, |
| test_validate_environment_debug_source_capability => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.environments = Some(vec![ |
| EnvironmentDecl { |
| name: Some("a".to_string()), |
| extends: Some(EnvironmentExtends::None), |
| stop_timeout_ms: Some(2), |
| debug_capabilities:Some(vec![ |
| DebugRegistration::Protocol(DebugProtocolRegistration { |
| source: Some(Ref::Capability(CapabilityRef { |
| name: "storage".to_string(), |
| })), |
| source_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| target_name: Some("fuchsia.sys2.StorageAdmin".to_string()), |
| ..DebugProtocolRegistration::EMPTY |
| }), |
| ]), |
| ..EnvironmentDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("DebugProtocolRegistration", "source"), |
| ])), |
| }, |
| |
| |
| // 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, |
| ..ChildDecl::EMPTY |
| }]); |
| 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, |
| ..ChildDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("ChildDecl", "name"), |
| Error::invalid_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)), |
| ..ChildDecl::EMPTY |
| }]); |
| 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()), |
| ..ChildDecl::EMPTY |
| }]); |
| 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, |
| environment: None, |
| ..CollectionDecl::EMPTY |
| }]); |
| 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), |
| environment: None, |
| ..CollectionDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_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), |
| environment: None, |
| ..CollectionDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::field_too_long("CollectionDecl", "name"), |
| ])), |
| }, |
| test_validate_collection_references_unknown_env => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.collections = Some(vec![CollectionDecl { |
| name: Some("foo".to_string()), |
| durability: Some(Durability::Transient), |
| environment: Some("test_env".to_string()), |
| ..CollectionDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_environment("CollectionDecl", "environment", "test_env"), |
| ])), |
| }, |
| |
| // capabilities |
| test_validate_capabilities_empty => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.capabilities = Some(vec![ |
| CapabilityDecl::Service(ServiceDecl { |
| name: None, |
| source_path: None, |
| ..ServiceDecl::EMPTY |
| }), |
| CapabilityDecl::Protocol(ProtocolDecl { |
| name: None, |
| source_path: None, |
| ..ProtocolDecl::EMPTY |
| }), |
| CapabilityDecl::Directory(DirectoryDecl { |
| name: None, |
| source_path: None, |
| rights: None, |
| ..DirectoryDecl::EMPTY |
| }), |
| CapabilityDecl::Storage(StorageDecl { |
| name: None, |
| source: None, |
| backing_dir: None, |
| subdir: None, |
| ..StorageDecl::EMPTY |
| }), |
| CapabilityDecl::Runner(RunnerDecl { |
| name: None, |
| source: None, |
| source_path: None, |
| ..RunnerDecl::EMPTY |
| }), |
| CapabilityDecl::Resolver(ResolverDecl { |
| name: None, |
| source_path: None, |
| ..ResolverDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("ServiceDecl", "name"), |
| Error::missing_field("ServiceDecl", "source_path"), |
| Error::missing_field("ProtocolDecl", "name"), |
| Error::missing_field("ProtocolDecl", "source_path"), |
| Error::missing_field("DirectoryDecl", "name"), |
| Error::missing_field("DirectoryDecl", "source_path"), |
| Error::missing_field("DirectoryDecl", "rights"), |
| Error::missing_field("StorageDecl", "source"), |
| Error::missing_field("StorageDecl", "name"), |
| Error::missing_field("StorageDecl", "backing_dir"), |
| Error::missing_field("RunnerDecl", "source"), |
| Error::missing_field("RunnerDecl", "name"), |
| Error::missing_field("RunnerDecl", "source_path"), |
| Error::missing_field("ResolverDecl", "name"), |
| Error::missing_field("ResolverDecl", "source_path"), |
| ])), |
| }, |
| test_validate_capabilities_invalid_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.capabilities = Some(vec![ |
| CapabilityDecl::Service(ServiceDecl { |
| name: Some("^bad".to_string()), |
| source_path: Some("&bad".to_string()), |
| ..ServiceDecl::EMPTY |
| }), |
| CapabilityDecl::Protocol(ProtocolDecl { |
| name: Some("^bad".to_string()), |
| source_path: Some("&bad".to_string()), |
| ..ProtocolDecl::EMPTY |
| }), |
| CapabilityDecl::Directory(DirectoryDecl { |
| name: Some("^bad".to_string()), |
| source_path: Some("&bad".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| ..DirectoryDecl::EMPTY |
| }), |
| CapabilityDecl::Storage(StorageDecl { |
| name: Some("^bad".to_string()), |
| source: Some(Ref::Collection(CollectionRef { |
| name: "/bad".to_string() |
| })), |
| backing_dir: Some("&bad".to_string()), |
| subdir: None, |
| ..StorageDecl::EMPTY |
| }), |
| CapabilityDecl::Runner(RunnerDecl { |
| name: Some("^bad".to_string()), |
| source: Some(Ref::Collection(CollectionRef { |
| name: "/bad".to_string() |
| })), |
| source_path: Some("&bad".to_string()), |
| ..RunnerDecl::EMPTY |
| }), |
| CapabilityDecl::Resolver(ResolverDecl { |
| name: Some("^bad".to_string()), |
| source_path: Some("&bad".to_string()), |
| ..ResolverDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("ServiceDecl", "name"), |
| Error::invalid_field("ServiceDecl", "source_path"), |
| Error::invalid_field("ProtocolDecl", "name"), |
| Error::invalid_field("ProtocolDecl", "source_path"), |
| Error::invalid_field("DirectoryDecl", "name"), |
| Error::invalid_field("DirectoryDecl", "source_path"), |
| Error::invalid_field("StorageDecl", "source"), |
| Error::invalid_field("StorageDecl", "name"), |
| Error::invalid_field("StorageDecl", "backing_dir"), |
| Error::invalid_field("RunnerDecl", "source"), |
| Error::invalid_field("RunnerDecl", "name"), |
| Error::invalid_field("RunnerDecl", "source_path"), |
| Error::invalid_field("ResolverDecl", "name"), |
| Error::invalid_field("ResolverDecl", "source_path"), |
| ])), |
| }, |
| test_validate_capabilities_invalid_child => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.capabilities = Some(vec![ |
| CapabilityDecl::Storage(StorageDecl { |
| name: Some("foo".to_string()), |
| source: Some(Ref::Collection(CollectionRef { |
| name: "invalid".to_string(), |
| })), |
| backing_dir: Some("foo".to_string()), |
| subdir: None, |
| ..StorageDecl::EMPTY |
| }), |
| CapabilityDecl::Runner(RunnerDecl { |
| name: Some("bar".to_string()), |
| source: Some(Ref::Collection(CollectionRef { |
| name: "invalid".to_string(), |
| })), |
| source_path: Some("/foo".to_string()), |
| ..RunnerDecl::EMPTY |
| }), |
| CapabilityDecl::Resolver(ResolverDecl { |
| name: Some("baz".to_string()), |
| source_path: Some("/foo".to_string()), |
| ..ResolverDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_field("StorageDecl", "source"), |
| Error::invalid_field("RunnerDecl", "source"), |
| ])), |
| }, |
| test_validate_capabilities_long_identifiers => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.capabilities = Some(vec![ |
| CapabilityDecl::Service(ServiceDecl { |
| name: Some("a".repeat(101)), |
| source_path: Some(format!("/{}", "c".repeat(1024))), |
| ..ServiceDecl::EMPTY |
| }), |
| CapabilityDecl::Protocol(ProtocolDecl { |
| name: Some("a".repeat(101)), |
| source_path: Some(format!("/{}", "c".repeat(1024))), |
| ..ProtocolDecl::EMPTY |
| }), |
| CapabilityDecl::Directory(DirectoryDecl { |
| name: Some("a".repeat(101)), |
| source_path: Some(format!("/{}", "c".repeat(1024))), |
| rights: Some(fio2::Operations::Connect), |
| ..DirectoryDecl::EMPTY |
| }), |
| CapabilityDecl::Storage(StorageDecl { |
| name: Some("a".repeat(101)), |
| source: Some(Ref::Child(ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| })), |
| backing_dir: Some(format!("{}", "c".repeat(101))), |
| subdir: None, |
| ..StorageDecl::EMPTY |
| }), |
| CapabilityDecl::Runner(RunnerDecl { |
| name: Some("a".repeat(101)), |
| source: Some(Ref::Child(ChildRef { |
| name: "b".repeat(101), |
| collection: None, |
| })), |
| source_path: Some(format!("/{}", "c".repeat(1024))), |
| ..RunnerDecl::EMPTY |
| }), |
| CapabilityDecl::Resolver(ResolverDecl { |
| name: Some("a".repeat(101)), |
| source_path: Some(format!("/{}", "b".repeat(1024))), |
| ..ResolverDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::field_too_long("ServiceDecl", "name"), |
| Error::field_too_long("ServiceDecl", "source_path"), |
| Error::field_too_long("ProtocolDecl", "name"), |
| Error::field_too_long("ProtocolDecl", "source_path"), |
| Error::field_too_long("DirectoryDecl", "name"), |
| Error::field_too_long("DirectoryDecl", "source_path"), |
| Error::field_too_long("StorageDecl", "source.child.name"), |
| Error::field_too_long("StorageDecl", "name"), |
| Error::field_too_long("StorageDecl", "backing_dir"), |
| Error::field_too_long("RunnerDecl", "source.child.name"), |
| Error::field_too_long("RunnerDecl", "name"), |
| Error::field_too_long("RunnerDecl", "source_path"), |
| Error::field_too_long("ResolverDecl", "name"), |
| Error::field_too_long("ResolverDecl", "source_path"), |
| ])), |
| }, |
| test_validate_capabilities_duplicate_name => { |
| input = { |
| let mut decl = new_component_decl(); |
| decl.capabilities = Some(vec![ |
| CapabilityDecl::Service(ServiceDecl { |
| name: Some("service".to_string()), |
| source_path: Some("/service".to_string()), |
| ..ServiceDecl::EMPTY |
| }), |
| CapabilityDecl::Service(ServiceDecl { |
| name: Some("service".to_string()), |
| source_path: Some("/service".to_string()), |
| ..ServiceDecl::EMPTY |
| }), |
| CapabilityDecl::Protocol(ProtocolDecl { |
| name: Some("protocol".to_string()), |
| source_path: Some("/protocol".to_string()), |
| ..ProtocolDecl::EMPTY |
| }), |
| CapabilityDecl::Protocol(ProtocolDecl { |
| name: Some("protocol".to_string()), |
| source_path: Some("/protocol".to_string()), |
| ..ProtocolDecl::EMPTY |
| }), |
| CapabilityDecl::Directory(DirectoryDecl { |
| name: Some("directory".to_string()), |
| source_path: Some("/directory".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| ..DirectoryDecl::EMPTY |
| }), |
| CapabilityDecl::Directory(DirectoryDecl { |
| name: Some("directory".to_string()), |
| source_path: Some("/directory".to_string()), |
| rights: Some(fio2::Operations::Connect), |
| ..DirectoryDecl::EMPTY |
| }), |
| CapabilityDecl::Storage(StorageDecl { |
| name: Some("storage".to_string()), |
| source: Some(Ref::Self_(SelfRef{})), |
| backing_dir: Some("directory".to_string()), |
| subdir: None, |
| ..StorageDecl::EMPTY |
| }), |
| CapabilityDecl::Storage(StorageDecl { |
| name: Some("storage".to_string()), |
| source: Some(Ref::Self_(SelfRef{})), |
| backing_dir: Some("directory".to_string()), |
| subdir: None, |
| ..StorageDecl::EMPTY |
| }), |
| CapabilityDecl::Runner(RunnerDecl { |
| name: Some("runner".to_string()), |
| source: Some(Ref::Self_(SelfRef{})), |
| source_path: Some("/runner".to_string()), |
| ..RunnerDecl::EMPTY |
| }), |
| CapabilityDecl::Runner(RunnerDecl { |
| name: Some("runner".to_string()), |
| source: Some(Ref::Self_(SelfRef{})), |
| source_path: Some("/runner".to_string()), |
| ..RunnerDecl::EMPTY |
| }), |
| CapabilityDecl::Resolver(ResolverDecl { |
| name: Some("resolver".to_string()), |
| source_path: Some("/resolver".to_string()), |
| ..ResolverDecl::EMPTY |
| }), |
| CapabilityDecl::Resolver(ResolverDecl { |
| name: Some("resolver".to_string()), |
| source_path: Some("/resolver".to_string()), |
| ..ResolverDecl::EMPTY |
| }), |
| ]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::duplicate_field("ServiceDecl", "name", "service"), |
| Error::duplicate_field("ProtocolDecl", "name", "protocol"), |
| Error::duplicate_field("DirectoryDecl", "name", "directory"), |
| Error::duplicate_field("StorageDecl", "name", "storage"), |
| Error::duplicate_field("RunnerDecl", "name", "runner"), |
| Error::duplicate_field("ResolverDecl", "name", "resolver"), |
| ])), |
| }, |
| |
| 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()), |
| ..OfferResolverDecl::EMPTY |
| })]); |
| decl.children = Some(vec![ChildDecl { |
| name: Some("child".to_string()), |
| url: Some("test:///child".to_string()), |
| startup: Some(StartupMode::Eager), |
| environment: None, |
| ..ChildDecl::EMPTY |
| }]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_capability("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::Parent(ParentRef {})), |
| target_name: Some("a".to_string()), |
| ..ExposeResolverDecl::EMPTY |
| })]); |
| decl |
| }, |
| result = Err(ErrorList::new(vec![ |
| Error::invalid_capability("ExposeResolverDecl", "source", "a"), |
| ])), |
| }, |
| } |
| |
| test_validate_capabilities! { |
| test_validate_capabilities_individually_ok => { |
| input = vec![ |
| CapabilityDecl::Protocol(ProtocolDecl { |
| name: Some("foo_svc".into()), |
| source_path: Some("/svc/foo".into()), |
| ..ProtocolDecl::EMPTY |
| }), |
| CapabilityDecl::Directory(DirectoryDecl { |
| name: Some("foo_dir".into()), |
| source_path: Some("/foo".into()), |
| rights: Some(fio2::Operations::Connect), |
| ..DirectoryDecl::EMPTY |
| }), |
| ], |
| result = Ok(()), |
| }, |
| test_validate_capabilities_individually_err => { |
| input = vec![ |
| CapabilityDecl::Protocol(ProtocolDecl { |
| name: None, |
| source_path: None, |
| ..ProtocolDecl::EMPTY |
| }), |
| CapabilityDecl::Directory(DirectoryDecl { |
| name: None, |
| source_path: None, |
| rights: None, |
| ..DirectoryDecl::EMPTY |
| }), |
| ], |
| result = Err(ErrorList::new(vec![ |
| Error::missing_field("ProtocolDecl", "name"), |
| Error::missing_field("ProtocolDecl", "source_path"), |
| Error::missing_field("DirectoryDecl", "name"), |
| Error::missing_field("DirectoryDecl", "source_path"), |
| Error::missing_field("DirectoryDecl", "rights"), |
| ])), |
| }, |
| } |
| |
| 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_name: Some(format!("thing")), |
| target_name: Some(format!("thing")), |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferProtocolDecl::EMPTY |
| }, |
| }, |
| test_validate_offers_directory_dependency_cycle => { |
| ty = OfferDecl::Directory, |
| offer_decl = OfferDirectoryDecl { |
| source: None, // Filled by macro |
| target: None, // Filled by macro |
| source_name: Some(format!("thing")), |
| target_name: Some(format!("thing")), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| dependency_type: Some(DependencyType::Strong), |
| ..OfferDirectoryDecl::EMPTY |
| }, |
| }, |
| test_validate_offers_service_dependency_cycle => { |
| ty = OfferDecl::Service, |
| offer_decl = OfferServiceDecl { |
| source: None, // Filled by macro |
| target: None, // Filled by macro |
| source_name: Some(format!("thing")), |
| target_name: Some(format!("thing")), |
| ..OfferServiceDecl::EMPTY |
| }, |
| }, |
| 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")), |
| ..OfferRunnerDecl::EMPTY |
| }, |
| }, |
| 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")), |
| ..OfferResolverDecl::EMPTY |
| }, |
| }, |
| } |
| 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_name: Some(format!("thing")), |
| target_name: Some(format!("thing")), |
| dependency_type: None, // Filled by macro |
| ..OfferProtocolDecl::EMPTY |
| }, |
| }, |
| test_validate_offers_directory_weak_dependency_cycle => { |
| ty = OfferDecl::Directory, |
| offer_decl = OfferDirectoryDecl { |
| source: None, // Filled by macro |
| target: None, // Filled by macro |
| source_name: Some(format!("thing")), |
| target_name: Some(format!("thing")), |
| rights: Some(fio2::Operations::Connect), |
| subdir: None, |
| dependency_type: None, // Filled by macro |
| ..OfferDirectoryDecl::EMPTY |
| }, |
| }, |
| } |
| } |