availability/
lib.rs

1// Copyright 2024 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::Availability;
6use moniker::ExtendedMoniker;
7use router_error::Explain;
8use thiserror::Error;
9use zx_status as zx;
10
11/// Ensure that availability cannot decrease from target to source.
12pub fn advance(
13    moniker: &ExtendedMoniker,
14    current: Availability,
15    next: Availability,
16) -> Result<Availability, TargetHasStrongerAvailability> {
17    match (current, next) {
18        // `self` will be `SameAsTarget` when routing starts from an `Offer` or `Expose`. This
19        // is to verify as much as possible the correctness of routes involving `Offer` and
20        // `Expose` without full knowledge of the `use -> offer -> expose` chain.
21        //
22        // For the purpose of availability checking, we will skip any checks until we encounter
23        // a route declaration that has a known availability.
24        (Availability::SameAsTarget, _) => Ok(next),
25
26        // If our availability doesn't change, there's nothing to do.
27        (Availability::Required, Availability::Required)
28        | (Availability::Optional, Availability::Optional)
29        | (Availability::Transitional, Availability::Transitional)
30
31        // If the next availability is explicitly a pass-through, there's nothing to do.
32        | (Availability::Required, Availability::SameAsTarget)
33        | (Availability::Optional, Availability::SameAsTarget)
34        | (Availability::Transitional, Availability::SameAsTarget) => Ok(current),
35
36        // Increasing the strength of availability as we travel toward the source is allowed.
37        (Availability::Optional, Availability::Required)
38        | (Availability::Transitional, Availability::Required)
39        | (Availability::Transitional, Availability::Optional) =>
40            Ok(next),
41
42        // Decreasing the strength of availability is not allowed, as that could lead to
43        // unsanctioned broken routes.
44        (Availability::Optional, Availability::Transitional)
45        | (Availability::Required, Availability::Transitional)
46        | (Availability::Required, Availability::Optional) =>
47            Err(TargetHasStrongerAvailability { moniker: moniker.clone() }),
48    }
49}
50
51/// Availability requested by the target has stronger guarantees than what
52/// is being provided at the source.
53#[derive(Debug, Error, Clone, PartialEq)]
54#[error(
55    "Availability requested by the target has stronger guarantees than what \
56    is being provided at the source."
57)]
58pub struct TargetHasStrongerAvailability {
59    pub moniker: ExtendedMoniker,
60}
61
62impl Explain for TargetHasStrongerAvailability {
63    fn as_zx_status(&self) -> zx::Status {
64        zx::Status::NOT_FOUND
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use test_case::test_case;
72
73    #[test_case(Availability::Optional, Availability::Optional, Ok(Availability::Optional))]
74    #[test_case(Availability::Optional, Availability::Required, Ok(Availability::Required))]
75    #[test_case(Availability::Optional, Availability::SameAsTarget, Ok(Availability::Optional))]
76    #[test_case(
77        Availability::Optional,
78        Availability::Transitional,
79        Err(TargetHasStrongerAvailability { moniker: ExtendedMoniker::ComponentManager })
80    )]
81    #[test_case(Availability::Required, Availability::Optional, Err(TargetHasStrongerAvailability { moniker: ExtendedMoniker::ComponentManager }))]
82    #[test_case(Availability::Required, Availability::Required, Ok(Availability::Required))]
83    #[test_case(Availability::Required, Availability::SameAsTarget, Ok(Availability::Required))]
84    #[test_case(
85        Availability::Required,
86        Availability::Transitional,
87        Err(TargetHasStrongerAvailability { moniker: ExtendedMoniker::ComponentManager })
88    )]
89    #[test_case(Availability::Transitional, Availability::Optional, Ok(Availability::Optional))]
90    #[test_case(Availability::Transitional, Availability::Required, Ok(Availability::Required))]
91    #[test_case(
92        Availability::Transitional,
93        Availability::SameAsTarget,
94        Ok(Availability::Transitional)
95    )]
96    #[test_case(
97        Availability::Transitional,
98        Availability::Transitional,
99        Ok(Availability::Transitional)
100    )]
101    fn advance_tests(
102        current: Availability,
103        next: Availability,
104        expected: Result<Availability, TargetHasStrongerAvailability>,
105    ) {
106        let actual = advance(&ExtendedMoniker::ComponentManager, current, next);
107        assert_eq!(actual, expected);
108    }
109}