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}