Skip to main content

network_device/
operation.rs

1//! Typed imperative operations and their embedded DSL composition.
2
3use crate::{InterfaceName, StaticRoute};
4use std::error::Error;
5use std::future::Future;
6
7/// A device driver capable of executing one or more typed operations.
8pub trait Driver: Send + Sync {
9    /// Error type returned by this driver's operations.
10    type Error: Error + Send + Sync + 'static;
11}
12
13/// Executes a specific operation on a [`Driver`].
14///
15/// Implement this trait for each operation a driver supports. External crates
16/// can introduce their own operation types and implementations.
17pub trait Execute<Operation: Send>: Driver {
18    /// Value produced by this operation.
19    type Output: Send;
20
21    /// Executes the owned operation.
22    #[allow(clippy::manual_async_fn)]
23    fn execute<'a>(
24        &'a self,
25        operation: Operation,
26    ) -> impl Future<Output = Result<Self::Output, Self::Error>> + Send + 'a;
27}
28
29/// A typed step in a direct-operation program.
30#[derive(Clone, Debug, Eq, PartialEq)]
31pub struct OperationStep<Operation> {
32    operation: Operation,
33}
34
35/// Starts a typed operation step.
36pub const fn operation<Operation>(operation: Operation) -> OperationStep<Operation> {
37    OperationStep { operation }
38}
39
40impl<Operation> OperationStep<Operation> {
41    /// Sequences this step before `next`; the sequence returns the next step's output.
42    pub fn then<Next>(self, next: Next) -> Sequence<Self, Next> {
43        Sequence {
44            first: self,
45            second: next,
46        }
47    }
48}
49
50/// Two operation steps executed in order.
51#[derive(Clone, Debug, Eq, PartialEq)]
52pub struct Sequence<First, Second> {
53    first: First,
54    second: Second,
55}
56
57impl<First, Second> Sequence<First, Second> {
58    /// Appends another step to this sequence.
59    pub fn then<Next>(self, next: Next) -> Sequence<Self, Next> {
60        Sequence {
61            first: self,
62            second: next,
63        }
64    }
65}
66
67/// Runs a DSL value against a statically selected driver.
68pub trait Run<DriverType: Driver>: Send {
69    /// Output of the final operation in this runnable value.
70    type Output: Send;
71
72    /// Runs the value, returning the first error or the final operation's output.
73    #[allow(clippy::manual_async_fn)]
74    fn run<'a>(
75        self,
76        driver: &'a DriverType,
77    ) -> impl Future<Output = Result<Self::Output, DriverType::Error>> + Send + 'a
78    where
79        Self: 'a,
80        DriverType: 'a;
81}
82
83impl<DriverType, Operation> Run<DriverType> for OperationStep<Operation>
84where
85    DriverType: Execute<Operation>,
86    Operation: Send,
87{
88    type Output = <DriverType as Execute<Operation>>::Output;
89
90    #[allow(clippy::manual_async_fn)]
91    fn run<'a>(
92        self,
93        driver: &'a DriverType,
94    ) -> impl Future<Output = Result<Self::Output, DriverType::Error>> + Send + 'a
95    where
96        Self: 'a,
97        DriverType: 'a,
98    {
99        async move { driver.execute(self.operation).await }
100    }
101}
102
103impl<DriverType, First, Second> Run<DriverType> for Sequence<First, Second>
104where
105    DriverType: Driver,
106    First: Run<DriverType>,
107    Second: Run<DriverType>,
108{
109    type Output = Second::Output;
110
111    #[allow(clippy::manual_async_fn)]
112    fn run<'a>(
113        self,
114        driver: &'a DriverType,
115    ) -> impl Future<Output = Result<Self::Output, DriverType::Error>> + Send + 'a
116    where
117        Self: 'a,
118        DriverType: 'a,
119    {
120        async move {
121            self.first.run(driver).await?;
122            self.second.run(driver).await
123        }
124    }
125}
126
127/// Desired administrative state for an interface.
128#[derive(Clone, Copy, Debug, Eq, PartialEq)]
129pub enum AdministrativeState {
130    /// The interface should be administratively enabled.
131    Enabled,
132    /// The interface should be administratively disabled.
133    Disabled,
134}
135
136/// Imperative operation to set an interface's administrative state.
137#[derive(Clone, Debug, Eq, PartialEq)]
138pub struct SetInterfaceState {
139    /// Interface to modify.
140    pub interface: InterfaceName,
141    /// Requested state.
142    pub state: AdministrativeState,
143}
144
145impl SetInterfaceState {
146    /// Creates the operation for one interface and state.
147    pub fn new(interface: InterfaceName, state: AdministrativeState) -> Self {
148        Self { interface, state }
149    }
150}
151
152/// Imperative operation to set an interface description.
153#[derive(Clone, Debug, Eq, PartialEq)]
154pub struct SetInterfaceDescription {
155    /// Interface to modify.
156    pub interface: InterfaceName,
157    /// New description.
158    pub description: String,
159}
160
161impl SetInterfaceDescription {
162    /// Creates the operation for one interface and description.
163    pub fn new(interface: InterfaceName, description: impl Into<String>) -> Self {
164        Self {
165            interface,
166            description: description.into(),
167        }
168    }
169}
170
171/// Imperative operation to ensure a static route exists.
172#[derive(Clone, Copy, Debug, Eq, PartialEq)]
173pub struct EnsureStaticRoute {
174    /// Route to ensure.
175    pub route: StaticRoute,
176}
177
178impl EnsureStaticRoute {
179    /// Creates the operation for one static route.
180    pub const fn new(route: StaticRoute) -> Self {
181        Self { route }
182    }
183}
184
185/// Imperative operation to remove a static route.
186#[derive(Clone, Copy, Debug, Eq, PartialEq)]
187pub struct RemoveStaticRoute {
188    /// Route to remove.
189    pub route: StaticRoute,
190}
191
192impl RemoveStaticRoute {
193    /// Creates the operation for one static route.
194    pub const fn new(route: StaticRoute) -> Self {
195        Self { route }
196    }
197}