Skip to main content

typed_openapi/
values.rs

1//! The arguments for one operation, keyed on the names the document uses.
2//!
3//! This is the seam that lets one request builder serve two callers. A CLI
4//! translates flags into a [`Values`]; a generated Rust wrapper builds one
5//! directly from typed arguments. Neither knows about the other, and neither
6//! re-spells a path template or a query string.
7
8/// What goes in the request body. Which variant an operation wants is a
9/// document fact, so the media type is not carried here — [`Body`] holds it.
10///
11/// [`Body`]: crate::Body
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum Payload {
14    /// JSON, either assembled from per-field flags or taken whole from a file.
15    Json(serde_json::Value),
16    /// Bytes the CLI does not interpret, sent under the document's media type.
17    Raw(Vec<u8>),
18    /// `multipart/form-data`, assembled part by part.
19    Multipart(Vec<Part>),
20}
21
22/// One part of a multipart body.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Part {
25    name: String,
26    filename: Option<String>,
27    bytes: Vec<u8>,
28}
29
30impl Part {
31    /// A text part: `--field name=value`.
32    #[must_use]
33    pub fn text(name: impl Into<String>, value: impl Into<String>) -> Self {
34        Self {
35            name: name.into(),
36            filename: None,
37            bytes: value.into().into_bytes(),
38        }
39    }
40
41    /// A file part: `--file name=@path`. `filename` is what the server sees.
42    #[must_use]
43    pub fn file(name: impl Into<String>, filename: impl Into<String>, bytes: Vec<u8>) -> Self {
44        Self {
45            name: name.into(),
46            filename: Some(filename.into()),
47            bytes,
48        }
49    }
50
51    #[must_use]
52    pub fn name(&self) -> &str {
53        &self.name
54    }
55
56    #[must_use]
57    pub fn filename(&self) -> Option<&str> {
58        self.filename.as_deref()
59    }
60
61    #[must_use]
62    pub fn bytes(&self) -> &[u8] {
63        &self.bytes
64    }
65}
66
67/// Parameters and a body for one operation, keyed on wire names.
68///
69/// Nothing here is checked against a document — that happens once, in
70/// [`Invocation::new`], so there is exactly one place that decides what
71/// satisfies an operation.
72///
73/// [`Invocation::new`]: crate::Invocation::new
74#[derive(Debug, Clone, Default, PartialEq, Eq)]
75pub struct Values {
76    params: Vec<(String, String)>,
77    body: Option<Payload>,
78}
79
80impl Values {
81    #[must_use]
82    pub fn new() -> Self {
83        Self::default()
84    }
85
86    /// One parameter, under the name the document gives it.
87    #[must_use]
88    #[expect(
89        clippy::needless_pass_by_value,
90        reason = "taking a reference would put `&` in front of every argument \
91                  at every generated call site, for values that cost nothing to move"
92    )]
93    pub fn param(mut self, name: impl Into<String>, value: impl ToString) -> Self {
94        self.params.push((name.into(), value.to_string()));
95        self
96    }
97
98    /// The same, for an optional parameter: `None` adds nothing.
99    #[must_use]
100    pub fn maybe(self, name: impl Into<String>, value: Option<impl ToString>) -> Self {
101        match value {
102            Some(value) => self.param(name, value),
103            None => self,
104        }
105    }
106
107    #[must_use]
108    pub fn json(mut self, value: serde_json::Value) -> Self {
109        self.body = Some(Payload::Json(value));
110        self
111    }
112
113    #[must_use]
114    pub fn raw(mut self, bytes: Vec<u8>) -> Self {
115        self.body = Some(Payload::Raw(bytes));
116        self
117    }
118
119    #[must_use]
120    pub fn multipart(mut self, parts: Vec<Part>) -> Self {
121        self.body = Some(Payload::Multipart(parts));
122        self
123    }
124
125    /// Attach `body` when there is one; leave the body alone when there is not.
126    #[must_use]
127    pub fn body(mut self, body: Option<Payload>) -> Self {
128        if let Some(body) = body {
129            self.body = Some(body);
130        }
131        self
132    }
133
134    #[must_use]
135    pub fn params(&self) -> &[(String, String)] {
136        &self.params
137    }
138
139    #[must_use]
140    pub fn payload(&self) -> Option<&Payload> {
141        self.body.as_ref()
142    }
143}