io_vdir/coroutine.rs
1//! # Generator-shape coroutine driver
2//!
3//! Mirrors the shape of `core::ops::Coroutine`: a `Yield` associated
4//! type for intermediate progress, a `Return` associated type for
5//! terminal output, and a two-variant [`VdirCoroutineState`]
6//! (`Yielded` / `Complete`).
7//!
8//! io-vdir is filesystem-flavoured, so every coroutine in the crate
9//! picks the standard [`VdirYield`] directly: it gathers every
10//! filesystem primitive the crate emits, namely directory create /
11//! remove / read / exists, file create / read / exists / remove, path
12//! rename / copy, plus the random bytes input needed to mint new item
13//! identifiers.
14//!
15//! [`VdirClient::run`] drives any standard-Yield coroutine to
16//! completion against the local filesystem.
17//!
18//! [`VdirClient::run`]: crate::client::VdirClient::run
19
20use alloc::{
21 collections::{BTreeMap, BTreeSet},
22 vec::Vec,
23};
24
25use crate::path::VdirPath;
26
27/// State yielded by a [`VdirCoroutine::resume`] step.
28///
29/// Two-variant by design (matches std's `core::ops::CoroutineState`):
30/// any further variation lives inside the per-coroutine `Yield` type.
31#[derive(Debug)]
32pub enum VdirCoroutineState<Y, R> {
33 /// Intermediate yield. The driver reacts to `Y` (perform the
34 /// requested filesystem op, supply random bytes) and resumes the
35 /// coroutine again.
36 Yielded(Y),
37 /// Terminal yield. By convention `R = Result<Output, Error>`.
38 Complete(R),
39}
40
41/// Standard-shape Vdir coroutine.
42///
43/// Implementors own their internal state machine and declare their
44/// per-step `Yield` plus a terminal `Return`. The driver reacts to
45/// each `Yield` variant and resumes until `Complete`.
46pub trait VdirCoroutine {
47 /// Intermediate value handed back on every step. Per-coroutine:
48 /// each implementor picks exactly the variants it needs. In
49 /// io-vdir every coroutine picks [`VdirYield`].
50 type Yield;
51 /// Terminal value. By convention `Result<Output, Error>`; the "ok"
52 /// arm carries the operation's final output, the "error" arm
53 /// carries the cause.
54 type Return;
55
56 /// Advances the coroutine one step.
57 ///
58 /// Pass [`None`] on the initial call. Pass `Some(arg)` carrying the
59 /// value matching the previous `Yielded` variant.
60 fn resume(&mut self, arg: Option<VdirReply>) -> VdirCoroutineState<Self::Yield, Self::Return>;
61}
62
63/// Standard filesystem Yield. Every io-vdir coroutine picks `type Yield
64/// = VdirYield`.
65///
66/// Each variant is paired with the matching [`VdirReply`] variant the
67/// driver feeds back on the next `resume`.
68#[derive(Debug)]
69pub enum VdirYield {
70 /// Driver must supply `len` random bytes and feed back
71 /// [`VdirReply::Random`].
72 WantsRandom {
73 /// Number of random bytes the driver must supply.
74 len: usize,
75 },
76
77 /// Driver must check each path for existence as a regular file and
78 /// feed back [`VdirReply::FileExists`].
79 WantsFileExists(BTreeSet<VdirPath>),
80
81 /// Driver must check each path for existence as a directory and
82 /// feed back [`VdirReply::DirExists`].
83 WantsDirExists(BTreeSet<VdirPath>),
84
85 /// Driver must list each directory's entries and feed back
86 /// [`VdirReply::DirRead`].
87 WantsDirRead(BTreeSet<VdirPath>),
88
89 /// Driver must read each file's bytes and feed back
90 /// [`VdirReply::FileRead`].
91 WantsFileRead(BTreeSet<VdirPath>),
92
93 /// Driver must write each `(path, bytes)` pair and feed back
94 /// [`VdirReply::FileCreate`].
95 WantsFileCreate(BTreeMap<VdirPath, Vec<u8>>),
96
97 /// Driver must create each directory (with parents) and feed back
98 /// [`VdirReply::DirCreate`].
99 WantsDirCreate(BTreeSet<VdirPath>),
100
101 /// Driver must recursively remove each directory and feed back
102 /// [`VdirReply::DirRemove`].
103 WantsDirRemove(BTreeSet<VdirPath>),
104
105 /// Driver must remove each file and feed back
106 /// [`VdirReply::FileRemove`].
107 WantsFileRemove(BTreeSet<VdirPath>),
108
109 /// Driver must rename each `(from, to)` pair and feed back
110 /// [`VdirReply::Rename`].
111 WantsRename(Vec<(VdirPath, VdirPath)>),
112
113 /// Driver must copy each `(from, to)` pair and feed back
114 /// [`VdirReply::Copy`].
115 WantsCopy(Vec<(VdirPath, VdirPath)>),
116}
117
118/// Reply fed back into [`VdirCoroutine::resume`] by the driver.
119///
120/// One variant per [`VdirYield`] request; the coroutine asserts the
121/// variant it expects and ignores the rest.
122#[derive(Clone, Debug)]
123pub enum VdirReply {
124 /// Reply to [`VdirYield::WantsRandom`].
125 Random(Vec<u8>),
126
127 /// Reply to [`VdirYield::WantsFileExists`].
128 FileExists(BTreeMap<VdirPath, bool>),
129
130 /// Reply to [`VdirYield::WantsDirExists`].
131 DirExists(BTreeMap<VdirPath, bool>),
132
133 /// Reply to [`VdirYield::WantsDirRead`].
134 DirRead(BTreeMap<VdirPath, BTreeSet<VdirPath>>),
135
136 /// Reply to [`VdirYield::WantsFileRead`].
137 FileRead(BTreeMap<VdirPath, Vec<u8>>),
138
139 /// Acknowledgement of [`VdirYield::WantsFileCreate`].
140 FileCreate,
141
142 /// Acknowledgement of [`VdirYield::WantsDirCreate`].
143 DirCreate,
144
145 /// Acknowledgement of [`VdirYield::WantsDirRemove`].
146 DirRemove,
147
148 /// Acknowledgement of [`VdirYield::WantsFileRemove`].
149 FileRemove,
150
151 /// Acknowledgement of [`VdirYield::WantsRename`].
152 Rename,
153
154 /// Acknowledgement of [`VdirYield::WantsCopy`].
155 Copy,
156}
157
158/// Coroutine `?`: forwards `Yielded` (via `Into`), short-circuits on
159/// `Err`, evaluates to the inner `Ok` value.
160#[macro_export]
161macro_rules! vdir_try {
162 ($coroutine:expr, $arg:expr $(,)?) => {
163 match $crate::coroutine::VdirCoroutine::resume($coroutine, $arg) {
164 $crate::coroutine::VdirCoroutineState::Yielded(y) => {
165 return $crate::coroutine::VdirCoroutineState::Yielded(y.into());
166 }
167 $crate::coroutine::VdirCoroutineState::Complete(Err(err)) => {
168 return $crate::coroutine::VdirCoroutineState::Complete(Err(err.into()));
169 }
170 $crate::coroutine::VdirCoroutineState::Complete(Ok(value)) => value,
171 }
172 };
173}