pub enum Document {
Single(Request),
Collection(Collection),
}Expand description
What one Sendra YAML file can hold: a single request, or a collection.
The two shapes are told apart by the presence of a top-level requests
key. A mapping with requests is a Collection; anything else is
parsed as a single Request. The discriminator is in the file itself, so
no new extension and no CLI flag are needed, and it cannot be ambiguous:
Request rejects unknown top-level keys, so a single-request file could
never have carried a requests key to begin with.
Detection is a separate pass over the YAML rather than a
#[serde(untagged)] enum on purpose. An untagged enum collapses every
failure into “data did not match any variant” with no position; picking the
target first and then deserializing the original text keeps serde’s real
error message, line and column included.
The Single variant is not boxed, though it is several times the size of
Collection. A Document is built once per invocation and read from where
it sits — the requests are borrowed out of it, never moved through it — so
the indirection would buy nothing and would cost every caller a deref to
reach a request that is right there.
Variants§
Single(Request)
Collection(Collection)
Implementations§
Source§impl Document
impl Document
Sourcepub fn from_yaml_str(yaml: &str) -> Result<Self, SendraError>
pub fn from_yaml_str(yaml: &str) -> Result<Self, SendraError>
Parse a request or a collection from a YAML string.
Sourcepub fn from_path(path: impl AsRef<Path>) -> Result<Self, SendraError>
pub fn from_path(path: impl AsRef<Path>) -> Result<Self, SendraError>
Read and parse a request or a collection from a YAML file on disk.
Sourcepub fn requests(&self) -> &[Request]
pub fn requests(&self) -> &[Request]
Every request the document holds, in file order — one for a single
request, all of them for a collection. This is what sendra run <file>
with no name sends.
Sourcepub fn get(&self, name: &str) -> Result<&Request, SendraError>
pub fn get(&self, name: &str) -> Result<&Request, SendraError>
Look up one request by name.
Asking a single-request file for a name is its own error rather than a “not found”: the file has no names to choose between, and saying so is more useful than listing an empty set.
Sourcepub fn validate(&self) -> Result<(), SendraError>
pub fn validate(&self) -> Result<(), SendraError>
Every rule Deserialize cannot express, checked directly rather than
only ever at parse time: a single request’s own Request::validate
(at most one body source, auth exclusivity, …), or, for a
collection, Collection::validate (non-empty, every request named,
no name used twice) plus that same per-request check for each one.
from_yaml_str/from_path already run this before ever handing a
Document back, so a Document that came from a real file is always
already valid — this exists for the other direction: a Document
built or mutated in memory (a front-end applying an edit, say) can
check before save_to_path writes it, rather
than only discovering it was invalid the next time something tries to
load it back. save_to_path calls this itself for exactly that
reason — this is exposed as its own method mainly so a caller can ask
the question earlier, e.g. to show a validation message before ever
attempting a write.
Sourcepub fn to_yaml_string(&self) -> Result<String, SendraError>
pub fn to_yaml_string(&self) -> Result<String, SendraError>
Serializes this document back to YAML, exactly the shape
from_yaml_str/from_path
parse: a bare Request for Single, a Collection for
Collection.
Not a derived Serialize impl on Document itself. Document
deliberately has no #[derive(Serialize)] (nor a hand-written
externally-tagged one): serde’s default representation for an enum
like this one wraps the output in a Single:/Collection: key
(!Single ... in YAML’s own tag syntax, depending on the
representation), which is not a shape from_yaml_str’s own shape
detection — “a top-level requests key means a collection, anything
else is a single request” (see this type’s own doc comment) — was ever
written to expect. Serializing whichever variant is actually held,
unwrapped, is what keeps
Document::from_yaml_str(&doc.to_yaml_string()?) equal to doc for
every real collection or request file — round-tripping through the
same shape a hand-written file already has, not a new one only this
method would produce.
Sourcepub fn save_to_path(&self, path: impl AsRef<Path>) -> Result<(), SendraError>
pub fn save_to_path(&self, path: impl AsRef<Path>) -> Result<(), SendraError>
Writes this document back to path, atomically: the new content is
written to a sibling temp file in the same directory first, then
std::fs::renamed over path — never written in place — so a
crash or a killed process mid-write can never leave path holding a
truncated or half-written file. A rename onto an existing file is
atomic on the same volume on both POSIX (rename(2)) and Windows
(std::fs::rename there is implemented as MoveFileExW with
MOVEFILE_REPLACE_EXISTING) — the two platforms sendra-tui ships
on — so path is always either its old content in full or its new
content in full, never a mix of both, no matter when the process is
interrupted.
The temp file is created in the same directory as path, not the
system temp directory: a rename across filesystems/mount points is not
atomic (POSIX rename(2) fails outright with EXDEV), so the temp
file has to already live on whatever volume path is on for the final
rename to be the one atomic operation this whole guarantee rests on.
If either the initial write or the rename fails, path is left
completely untouched (the failure can only ever happen to the temp
file, before path itself is touched at all) and the temp file is
removed on a best-effort basis rather than left behind as a stray
dotfile — the original error is what gets returned either way, not
whatever the cleanup did.
Refuses to write an invalid document at all — validate
is checked first, before the temp file is even created. Without this,
an in-memory edit that left the document invalid (a collection request
edited down to an empty name, say) would still write out a file that
parses back as YAML but fails Collection::validate the very next
time anything loads it — a real file that looks saved but is
silently broken. Catching it here means the caller learns about it
immediately, through the same Result a disk-level failure already
comes back through, rather than the next Document::from_path call
discovering it days later.