cm_fidl_validator/
error.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use cm_types::ParseError;
6use fidl_fuchsia_component_decl as fdecl;
7use std::fmt;
8use std::fmt::Display;
9use thiserror::Error;
10
11/// Enum type that can represent any error encountered during validation.
12#[derive(Debug, Error, PartialEq, Clone)]
13pub enum Error {
14    #[error("Field `{}` is missing for {}.", .0.field, .0.decl)]
15    MissingField(DeclField),
16
17    #[error("Field `{}` is empty for {}.", .0.field, .0.decl)]
18    EmptyField(DeclField),
19
20    #[error("{} has unnecessary field `{}`.", .0.decl, .0.field)]
21    ExtraneousField(DeclField),
22
23    #[error("\"{}\" is duplicated for field `{}` in {}.", .1, .0.field, .0.decl)]
24    DuplicateField(DeclField, String),
25
26    #[error("Field `{}` for {} is invalid.",  .0.field, .0.decl)]
27    InvalidField(DeclField),
28
29    #[error("Field {} for {} is invalid. {}.", .0.field, .0.decl, .1)]
30    InvalidUrl(DeclField, String),
31
32    #[error("Field `{}` for {} is too long.", .0.field, .0.decl)]
33    FieldTooLong(DeclField),
34
35    #[error("Field `{}` for {} has an invalid path segment.", .0.field, .0.decl)]
36    FieldInvalidSegment(DeclField),
37
38    #[error("\"{0}\" capabilities must be offered as a built-in capability.")]
39    CapabilityMustBeBuiltin(DeclType),
40
41    #[error("\"{0}\" capabilities are not currently allowed as built-ins.")]
42    CapabilityCannotBeBuiltin(DeclType),
43
44    #[error(
45        "Encountered an unknown capability declaration. This may happen due to ABI skew between the FIDL component declaration and the system."
46    )]
47    UnknownCapability,
48
49    #[error("\"{1}\" is referenced in {0} but it does not appear in children.")]
50    InvalidChild(DeclField, String),
51
52    #[error("\"{1}\" is referenced in {0} but it does not appear in collections.")]
53    InvalidCollection(DeclField, String),
54
55    #[error("\"{1}\" is referenced in {0} but it does not appear in storage.")]
56    InvalidStorage(DeclField, String),
57
58    #[error("\"{1}\" is referenced in {0} but it does not appear in environments.")]
59    InvalidEnvironment(DeclField, String),
60
61    #[error("\"{1}\" is referenced in {0} but it does not appear in capabilities.")]
62    InvalidCapability(DeclField, String),
63
64    #[error("\"{1}\" is referenced in {0} but it does not appear in runners.")]
65    InvalidRunner(DeclField, String),
66
67    #[error("There are dependency cycle(s): {0}.")]
68    DependencyCycle(String),
69
70    #[error(
71        "Path \"{path}\" from {decl} overlaps with \"{other_path}\" path from {other_decl}. Paths across decls must be unique in order to avoid namespace collisions."
72    )]
73    InvalidPathOverlap { decl: DeclField, path: String, other_decl: DeclField, other_path: String },
74
75    #[error("{} \"{}\" path overlaps with \"/pkg\", which is a protected path", decl, path)]
76    PkgPathOverlap { decl: DeclField, path: String },
77
78    #[error(
79        "Source path \"{1}\" provided to {0} decl is unnecessary. Built-in capabilities don't need this field as they originate from the framework."
80    )]
81    ExtraneousSourcePath(DeclField, String),
82
83    #[error(
84        "Configuration schema defines a vector nested inside another vector. Vector can only contain numbers, booleans, and strings."
85    )]
86    NestedVector,
87
88    #[error(
89        "The `availability` field in {0} for {1} must be set to \"optional\" because the source is \"void\"."
90    )]
91    AvailabilityMustBeOptional(DeclField, String),
92
93    #[error("Invalid aggregate offer: {0}")]
94    InvalidAggregateOffer(String),
95
96    #[error(
97        "All sources that feed into an aggregation operation should have the same availability. Got {0}."
98    )]
99    DifferentAvailabilityInAggregation(AvailabilityList),
100
101    #[error("Multiple runners used.")]
102    MultipleRunnersUsed,
103
104    #[error("Used runner conflicts with program runner.")]
105    ConflictingRunners,
106
107    #[error(
108        "Runner is missing for executable component. A runner must be specified in the \
109            `program` section or in the `use` section."
110    )]
111    MissingRunner,
112
113    #[error("Dynamic children cannot specify an environment.")]
114    DynamicChildWithEnvironment,
115}
116
117/// [AvailabilityList] is a newtype to provide a human friendly [Display] impl for a vector
118/// of availabilities.
119#[derive(Debug, PartialEq, Clone)]
120pub struct AvailabilityList(pub Vec<fdecl::Availability>);
121
122impl Display for AvailabilityList {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        let comma_separated =
125            self.0.iter().map(|s| format!("{:?}", s)).collect::<Vec<_>>().join(", ");
126        write!(f, "[ {comma_separated} ]")
127    }
128}
129
130impl Error {
131    pub fn missing_field(decl_type: DeclType, keyword: impl Into<String>) -> Self {
132        Error::MissingField(DeclField { decl: decl_type, field: keyword.into() })
133    }
134
135    pub fn empty_field(decl_type: DeclType, keyword: impl Into<String>) -> Self {
136        Error::EmptyField(DeclField { decl: decl_type, field: keyword.into() })
137    }
138
139    pub fn extraneous_field(decl_type: DeclType, keyword: impl Into<String>) -> Self {
140        Error::ExtraneousField(DeclField { decl: decl_type, field: keyword.into() })
141    }
142
143    pub fn duplicate_field(
144        decl_type: DeclType,
145        keyword: impl Into<String>,
146        value: impl Into<String>,
147    ) -> Self {
148        Error::DuplicateField(DeclField { decl: decl_type, field: keyword.into() }, value.into())
149    }
150
151    pub fn invalid_field(decl_type: DeclType, keyword: impl Into<String>) -> Self {
152        Error::InvalidField(DeclField { decl: decl_type, field: keyword.into() })
153    }
154
155    pub fn invalid_url(
156        decl_type: DeclType,
157        keyword: impl Into<String>,
158        message: impl Into<String>,
159    ) -> Self {
160        Error::InvalidUrl(DeclField { decl: decl_type, field: keyword.into() }, message.into())
161    }
162
163    pub fn field_too_long(decl_type: DeclType, keyword: impl Into<String>) -> Self {
164        Error::FieldTooLong(DeclField { decl: decl_type, field: keyword.into() })
165    }
166
167    pub fn field_invalid_segment(decl_type: DeclType, keyword: impl Into<String>) -> Self {
168        Error::FieldInvalidSegment(DeclField { decl: decl_type, field: keyword.into() })
169    }
170
171    pub fn invalid_child(
172        decl_type: DeclType,
173        keyword: impl Into<String>,
174        child: impl Into<String>,
175    ) -> Self {
176        Error::InvalidChild(DeclField { decl: decl_type, field: keyword.into() }, child.into())
177    }
178
179    pub fn invalid_collection(
180        decl_type: DeclType,
181        keyword: impl Into<String>,
182        collection: impl Into<String>,
183    ) -> Self {
184        Error::InvalidCollection(
185            DeclField { decl: decl_type, field: keyword.into() },
186            collection.into(),
187        )
188    }
189
190    pub fn invalid_environment(
191        decl_type: DeclType,
192        keyword: impl Into<String>,
193        environment: impl Into<String>,
194    ) -> Self {
195        Error::InvalidEnvironment(
196            DeclField { decl: decl_type, field: keyword.into() },
197            environment.into(),
198        )
199    }
200
201    // TODO: Replace with `invalid_capability`?
202    pub fn invalid_runner(
203        decl_type: DeclType,
204        keyword: impl Into<String>,
205        runner: impl Into<String>,
206    ) -> Self {
207        Error::InvalidRunner(DeclField { decl: decl_type, field: keyword.into() }, runner.into())
208    }
209
210    pub fn invalid_capability(
211        decl_type: DeclType,
212        keyword: impl Into<String>,
213        capability: impl Into<String>,
214    ) -> Self {
215        Error::InvalidCapability(
216            DeclField { decl: decl_type, field: keyword.into() },
217            capability.into(),
218        )
219    }
220
221    pub fn dependency_cycle(error: impl Into<String>) -> Self {
222        Error::DependencyCycle(error.into())
223    }
224
225    pub fn invalid_path_overlap(
226        decl: DeclType,
227        path: impl Into<String>,
228        other_decl: DeclType,
229        other_path: impl Into<String>,
230    ) -> Self {
231        Error::InvalidPathOverlap {
232            decl: DeclField { decl, field: "target_path".to_string() },
233            path: path.into(),
234            other_decl: DeclField { decl: other_decl, field: "target_path".to_string() },
235            other_path: other_path.into(),
236        }
237    }
238
239    pub fn pkg_path_overlap(decl: DeclType, path: impl Into<String>) -> Self {
240        Error::PkgPathOverlap {
241            decl: DeclField { decl, field: "target_path".to_string() },
242            path: path.into(),
243        }
244    }
245
246    pub fn extraneous_source_path(decl_type: DeclType, path: impl Into<String>) -> Self {
247        Error::ExtraneousSourcePath(
248            DeclField { decl: decl_type, field: "source_path".to_string() },
249            path.into(),
250        )
251    }
252
253    pub fn nested_vector() -> Self {
254        Error::NestedVector
255    }
256
257    pub fn availability_must_be_optional(
258        decl_type: DeclType,
259        keyword: impl Into<String>,
260        source_name: Option<&String>,
261    ) -> Self {
262        Error::AvailabilityMustBeOptional(
263            DeclField { decl: decl_type, field: keyword.into() },
264            source_name.cloned().unwrap_or_else(|| "<unnamed>".to_string()),
265        )
266    }
267
268    pub fn invalid_aggregate_offer(info: impl Into<String>) -> Self {
269        Error::InvalidAggregateOffer(info.into())
270    }
271
272    pub fn different_availability_in_aggregation(availability: Vec<fdecl::Availability>) -> Self {
273        Error::DifferentAvailabilityInAggregation(AvailabilityList(availability))
274    }
275
276    pub fn from_parse_error(
277        err: ParseError,
278        prop: &String,
279        decl_type: DeclType,
280        keyword: &str,
281    ) -> Self {
282        match err {
283            ParseError::Empty => Error::empty_field(decl_type, keyword),
284            ParseError::TooLong => Error::field_too_long(decl_type, keyword),
285            ParseError::InvalidComponentUrl { details } => {
286                Error::invalid_url(decl_type, keyword, format!(r#""{prop}": {details}"#))
287            }
288            ParseError::InvalidValue => Error::invalid_field(decl_type, keyword),
289            ParseError::InvalidSegment => Error::field_invalid_segment(decl_type, keyword),
290            ParseError::NoLeadingSlash => Error::invalid_field(decl_type, keyword),
291        }
292    }
293}
294
295// To regenerate:
296//
297// ```
298//     fx exec env | \
299//         grep FUCHSIA_BUILD_DIR | \
300//         xargs -I {} bash -c 'export {}; grep -E "pub (enum|struct)" $FUCHSIA_BUILD_DIR/fidling/gen/sdk/fidl/fuchsia.component.decl/fuchsia.component.decl/rust/fidl_fuchsia_component_decl.rs' | \
301//         awk '{print $3}' | \
302//         sed 's/[:;]$//' | \
303//         sort | uniq | sed 's/$/,/'
304// ```
305//
306/// The list of all declarations in fuchsia.component.decl, for error reporting purposes.
307#[derive(Debug, PartialEq, Clone, Copy)]
308pub enum DeclType {
309    AllowedOffers,
310    Availability,
311    Capability,
312    CapabilityRef,
313    Child,
314    ChildRef,
315    Collection,
316    CollectionRef,
317    Component,
318    Configuration,
319    ConfigChecksum,
320    ConfigField,
321    ConfigMutability,
322    ConfigOverride,
323    ConfigSchema,
324    ConfigSingleValue,
325    ConfigType,
326    ConfigTypeLayout,
327    ConfigValue,
328    ConfigValuesData,
329    ConfigValueSource,
330    ConfigValueSpec,
331    ConfigVectorValue,
332    DebugProtocolRegistration,
333    DebugRef,
334    DebugRegistration,
335    DependencyType,
336    Dictionary,
337    Directory,
338    Durability,
339    Environment,
340    EnvironmentExtends,
341    EventStream,
342    EventSubscription,
343    Expose,
344    ExposeConfig,
345    ExposeDictionary,
346    ExposeDirectory,
347    ExposeProtocol,
348    ExposeResolver,
349    ExposeRunner,
350    ExposeService,
351    FrameworkRef,
352    LayoutConstraint,
353    LayoutParameter,
354    NameMapping,
355    Offer,
356    OfferConfig,
357    OfferDictionary,
358    OfferDirectory,
359    OfferEventStream,
360    OfferProtocol,
361    OfferResolver,
362    OfferRunner,
363    OfferService,
364    OfferStorage,
365    OnTerminate,
366    ParentRef,
367    Program,
368    Protocol,
369    Ref,
370    ResolvedConfig,
371    ResolvedConfigField,
372    Resolver,
373    ResolverRegistration,
374    Runner,
375    RunnerRegistration,
376    SelfRef,
377    Service,
378    StartupMode,
379    Storage,
380    StorageId,
381    Use,
382    UseConfiguration,
383    UseDictionary,
384    UseDirectory,
385    UseEventStream,
386    UseProtocol,
387    UseRunner,
388    UseService,
389    UseStorage,
390    VoidRef,
391}
392
393impl fmt::Display for DeclType {
394    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395        let name = match *self {
396            // To regenerate:
397            //
398            // ```
399            //     fx exec env | \
400            //         grep FUCHSIA_BUILD_DIR | \
401            //         xargs -I {} bash -c 'export {}; grep -E "pub (enum|struct)" $FUCHSIA_BUILD_DIR/fidling/gen/sdk/fidl/fuchsia.component.decl/fuchsia.component.decl/rust/fidl_fuchsia_component_decl.rs' | \
402            //         awk '{print $3}' | \
403            //         sed 's/[:;]$//' | \
404            //         sort | uniq | sed 's/\(.*\)/DeclType::\1 => "\1",/'
405            // ```
406            DeclType::AllowedOffers => "AllowedOffers",
407            DeclType::Availability => "Availability",
408            DeclType::Capability => "Capability",
409            DeclType::CapabilityRef => "CapabilityRef",
410            DeclType::Child => "Child",
411            DeclType::ChildRef => "ChildRef",
412            DeclType::Collection => "Collection",
413            DeclType::CollectionRef => "CollectionRef",
414            DeclType::Component => "Component",
415            DeclType::Configuration => "Configuration",
416            DeclType::ConfigChecksum => "ConfigChecksum",
417            DeclType::ConfigField => "ConfigField",
418            DeclType::ConfigMutability => "ConfigMutability",
419            DeclType::ConfigOverride => "ConfigOverride",
420            DeclType::ConfigSchema => "ConfigSchema",
421            DeclType::ConfigSingleValue => "ConfigSingleValue",
422            DeclType::ConfigType => "ConfigType",
423            DeclType::ConfigTypeLayout => "ConfigTypeLayout",
424            DeclType::ConfigValue => "ConfigValue",
425            DeclType::ConfigValuesData => "ConfigValuesData",
426            DeclType::ConfigValueSource => "ConfigValueSource",
427            DeclType::ConfigValueSpec => "ConfigValueSpec",
428            DeclType::ConfigVectorValue => "ConfigVectorValue",
429            DeclType::DebugProtocolRegistration => "DebugProtocolRegistration",
430            DeclType::DebugRef => "DebugRef",
431            DeclType::DebugRegistration => "DebugRegistration",
432            DeclType::DependencyType => "DependencyType",
433            DeclType::Dictionary => "Dictionary",
434            DeclType::Directory => "Directory",
435            DeclType::Durability => "Durability",
436            DeclType::Environment => "Environment",
437            DeclType::EnvironmentExtends => "EnvironmentExtends",
438            DeclType::EventStream => "EventStream",
439            DeclType::EventSubscription => "EventSubscription",
440            DeclType::Expose => "Expose",
441            DeclType::ExposeConfig => "ExposeConfig",
442            DeclType::ExposeDictionary => "ExposeDictionary",
443            DeclType::ExposeDirectory => "ExposeDirectory",
444            DeclType::ExposeProtocol => "ExposeProtocol",
445            DeclType::ExposeResolver => "ExposeResolver",
446            DeclType::ExposeRunner => "ExposeRunner",
447            DeclType::ExposeService => "ExposeService",
448            DeclType::FrameworkRef => "FrameworkRef",
449            DeclType::LayoutConstraint => "LayoutConstraint",
450            DeclType::LayoutParameter => "LayoutParameter",
451            DeclType::NameMapping => "NameMapping",
452            DeclType::Offer => "Offer",
453            DeclType::OfferConfig => "OfferConfig",
454            DeclType::OfferDictionary => "OfferDictionary",
455            DeclType::OfferDirectory => "OfferDirectory",
456            DeclType::OfferEventStream => "OfferEventStream",
457            DeclType::OfferProtocol => "OfferProtocol",
458            DeclType::OfferResolver => "OfferResolver",
459            DeclType::OfferRunner => "OfferRunner",
460            DeclType::OfferService => "OfferService",
461            DeclType::OfferStorage => "OfferStorage",
462            DeclType::OnTerminate => "OnTerminate",
463            DeclType::ParentRef => "ParentRef",
464            DeclType::Program => "Program",
465            DeclType::Protocol => "Protocol",
466            DeclType::Ref => "Ref",
467            DeclType::ResolvedConfig => "ResolvedConfig",
468            DeclType::ResolvedConfigField => "ResolvedConfigField",
469            DeclType::Resolver => "Resolver",
470            DeclType::ResolverRegistration => "ResolverRegistration",
471            DeclType::Runner => "Runner",
472            DeclType::RunnerRegistration => "RunnerRegistration",
473            DeclType::SelfRef => "SelfRef",
474            DeclType::Service => "Service",
475            DeclType::StartupMode => "StartupMode",
476            DeclType::Storage => "Storage",
477            DeclType::StorageId => "StorageId",
478            DeclType::Use => "Use",
479            DeclType::UseConfiguration => "UseConfiguration",
480            DeclType::UseDictionary => "UseDictionary",
481            DeclType::UseDirectory => "UseDirectory",
482            DeclType::UseEventStream => "UseEventStream",
483            DeclType::UseProtocol => "UseProtocol",
484            DeclType::UseRunner => "UseRunner",
485            DeclType::UseService => "UseService",
486            DeclType::UseStorage => "UseStorage",
487            DeclType::VoidRef => "VoidRef",
488        };
489        write!(f, "{}", name)
490    }
491}
492
493#[derive(Debug, PartialEq, Clone)]
494pub struct DeclField {
495    pub decl: DeclType,
496    pub field: String,
497}
498
499impl fmt::Display for DeclField {
500    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501        write!(f, "{}.{}", &self.decl, &self.field)
502    }
503}
504
505/// Represents a list of errors encountered during validation.
506#[derive(Debug, Error, PartialEq, Clone)]
507pub struct ErrorList {
508    pub errs: Vec<Error>,
509}
510
511impl ErrorList {
512    pub(crate) fn new(errs: Vec<Error>) -> ErrorList {
513        ErrorList { errs }
514    }
515}
516
517impl fmt::Display for ErrorList {
518    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
519        let strs: Vec<String> = self.errs.iter().map(|e| format!("{}", e)).collect();
520        write!(f, "{}", strs.join(", "))
521    }
522}
523
524#[cfg(test)]
525mod tests {
526    use super::*;
527
528    #[test]
529    fn test_errors() {
530        assert_eq!(
531            format!("{}", Error::missing_field(DeclType::Child, "keyword")),
532            "Field `keyword` is missing for Child."
533        );
534        assert_eq!(
535            format!("{}", Error::empty_field(DeclType::Child, "keyword")),
536            "Field `keyword` is empty for Child."
537        );
538        assert_eq!(
539            format!("{}", Error::duplicate_field(DeclType::Child, "keyword", "foo")),
540            "\"foo\" is duplicated for field `keyword` in Child."
541        );
542        assert_eq!(
543            format!("{}", Error::invalid_field(DeclType::Child, "keyword")),
544            "Field `keyword` for Child is invalid."
545        );
546        assert_eq!(
547            format!("{}", Error::field_too_long(DeclType::Child, "keyword")),
548            "Field `keyword` for Child is too long."
549        );
550        assert_eq!(
551            format!("{}", Error::field_invalid_segment(DeclType::Child, "keyword")),
552            "Field `keyword` for Child has an invalid path segment."
553        );
554        assert_eq!(
555            format!("{}", Error::invalid_child(DeclType::Child, "source", "child")),
556            "\"child\" is referenced in Child.source but it does not appear in children."
557        );
558        assert_eq!(
559            format!("{}", Error::invalid_collection(DeclType::Child, "source", "child")),
560            "\"child\" is referenced in Child.source but it does not appear in collections."
561        );
562    }
563}