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