Skip to main content

typed_openapi/
plan.rs

1//! The gate: whether a built request may be sent, decided once.
2//!
3//! Every caller that has a request and a confirmation builds a [`Plan`] and
4//! executes what it gets back, rather than testing the flag again. That is what
5//! makes a dry run print the exact bytes a confirmed run sends — they are this
6//! one value.
7//!
8//! Nothing here knows about command lines. A CLI reaches it through
9//! the `tree` module (feature `clap`), and a Rust caller that wants the same gate over a typed
10//! wrapper calls [`Plan::decide`] directly, which is what the `finalize-voucher`
11//! verb in the example does.
12
13use http::{Request, Uri};
14use thiserror::Error;
15
16use crate::model::{Effect, Operation};
17use crate::request::{Invocation, ValueError};
18use crate::values::Values;
19
20/// What the gate decided, with the request already built.
21///
22/// This is the only place in the crate that decides whether something is sent.
23#[derive(Debug, Clone)]
24pub enum Plan {
25    /// A read, or a write the user confirmed.
26    Send(Request<Vec<u8>>),
27    /// A write without confirmation. Print it; send nothing.
28    DryRun(Request<Vec<u8>>),
29}
30
31impl Plan {
32    /// The gate: a read runs on sight, a write runs only once confirmed.
33    #[must_use]
34    pub fn decide(effect: Effect, confirmed: bool, request: Request<Vec<u8>>) -> Self {
35        match (effect, confirmed) {
36            (Effect::Read, _) | (Effect::Write, true) => Self::Send(request),
37            (Effect::Write, false) => Self::DryRun(request),
38        }
39    }
40
41    /// Values that satisfy an operation, rendered against a server and put to
42    /// the gate — validation, rendering and the verdict in one step, so no
43    /// caller can do two of the three and skip the last.
44    pub fn build(
45        op: &Operation,
46        base: &Uri,
47        values: Values,
48        confirmed: bool,
49    ) -> Result<Self, PlanError> {
50        let invocation = Invocation::new(op, values)?;
51        Ok(Self::decide(
52            op.effect(),
53            confirmed,
54            invocation.request(base)?,
55        ))
56    }
57
58    /// The request, whichever way the gate went — a dry run prints exactly the
59    /// bytes a confirmed run sends, because they are this one value.
60    #[must_use]
61    pub fn request(&self) -> &Request<Vec<u8>> {
62        match self {
63            Self::Send(request) | Self::DryRun(request) => request,
64        }
65    }
66}
67
68/// Why values that parsed did not turn into a request.
69#[derive(Debug, Error)]
70pub enum PlanError {
71    #[error(transparent)]
72    Value(#[from] ValueError),
73    #[error("cannot build the request: {0}")]
74    Request(#[from] http::Error),
75}