Skip to main content

Document

Enum Document 

Source
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

Source

pub fn from_yaml_str(yaml: &str) -> Result<Self, SendraError>

Parse a request or a collection from a YAML string.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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 allvalidate 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.

Trait Implementations§

Source§

impl Clone for Document

Source§

fn clone(&self) -> Document

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Document

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for Document

Source§

fn eq(&self, other: &Document) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Document

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more