Skip to content

Commit 13febcb

Browse files
authored
Merge pull request #5 from svix/onelson/optional-request-bodies
Optional request bodies
2 parents 2023a44 + 36fbdbe commit 13febcb

File tree

5 files changed

+77
-9
lines changed

5 files changed

+77
-9
lines changed

src/api.rs

+61-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ pub(crate) struct Api {
2020
}
2121

2222
impl Api {
23-
pub(crate) fn new(paths: openapi::Paths, with_deprecated: bool) -> anyhow::Result<Self> {
23+
pub(crate) fn new(
24+
paths: openapi::Paths,
25+
with_deprecated: bool,
26+
component_schemas: &IndexMap<String, openapi::SchemaObject>,
27+
) -> anyhow::Result<Self> {
2428
let mut resources = BTreeMap::new();
2529

2630
for (path, pi) in paths {
@@ -38,7 +42,9 @@ impl Api {
3842
continue;
3943
}
4044

41-
if let Some((res_path, op)) = Operation::from_openapi(&path, method, op) {
45+
if let Some((res_path, op)) =
46+
Operation::from_openapi(&path, method, op, component_schemas)
47+
{
4248
let resource = get_or_insert_resource(&mut resources, res_path);
4349
if op.method == "post" {
4450
resource.has_post_operation = true;
@@ -178,6 +184,10 @@ struct Operation {
178184
/// Name of the request body type, if any.
179185
#[serde(skip_serializing_if = "Option::is_none")]
180186
request_body_schema_name: Option<String>,
187+
/// Some request bodies are required, but all the fields are optional (i.e. the CLI can omit
188+
/// this from the argument list).
189+
/// Only useful when `request_body_schema_name` is `Some`.
190+
request_body_all_optional: bool,
181191
/// Name of the response body type, if any.
182192
#[serde(skip_serializing_if = "Option::is_none")]
183193
response_body_schema_name: Option<String>,
@@ -189,6 +199,7 @@ impl Operation {
189199
path: &str,
190200
method: &str,
191201
op: openapi::Operation,
202+
component_schemas: &IndexMap<String, aide::openapi::SchemaObject>,
192203
) -> Option<(Vec<String>, Self)> {
193204
let Some(op_id) = op.operation_id else {
194205
// ignore operations without an operationId
@@ -298,6 +309,51 @@ impl Operation {
298309
}
299310
}
300311

312+
let request_body_all_optional = op
313+
.request_body
314+
.as_ref()
315+
.map(|r| {
316+
match r {
317+
ReferenceOr::Reference { .. } => {
318+
unimplemented!("reference")
319+
}
320+
ReferenceOr::Item(body) => {
321+
if let Some(mt) = body.content.get("application/json") {
322+
match mt.schema.as_ref().map(|so| &so.json_schema) {
323+
Some(Schema::Object(schemars::schema::SchemaObject {
324+
object: Some(ov),
325+
..
326+
})) => {
327+
return ov.required.is_empty();
328+
}
329+
Some(Schema::Object(schemars::schema::SchemaObject {
330+
reference: Some(s),
331+
..
332+
})) => {
333+
match component_schemas
334+
.get(
335+
&get_schema_name(Some(s)).expect("schema should exist"),
336+
)
337+
.map(|so| &so.json_schema)
338+
{
339+
Some(Schema::Object(schemars::schema::SchemaObject {
340+
object: Some(ov),
341+
..
342+
})) => {
343+
return ov.required.is_empty();
344+
}
345+
_ => unimplemented!("double ref not supported"),
346+
}
347+
}
348+
_ => {}
349+
}
350+
}
351+
}
352+
}
353+
false
354+
})
355+
.unwrap_or_default();
356+
301357
let request_body_schema_name = op.request_body.and_then(|b| match b {
302358
ReferenceOr::Item(mut req_body) => {
303359
assert!(req_body.required);
@@ -317,7 +373,7 @@ impl Operation {
317373
if !obj.is_ref() {
318374
tracing::error!(?obj, "unexpected non-$ref json body schema");
319375
}
320-
get_schema_name(obj.reference)
376+
get_schema_name(obj.reference.as_deref())
321377
}
322378
}
323379
}
@@ -370,6 +426,7 @@ impl Operation {
370426
header_params,
371427
query_params,
372428
request_body_schema_name,
429+
request_body_all_optional,
373430
response_body_schema_name,
374431
};
375432
Some((res_path, op))
@@ -413,7 +470,7 @@ fn response_body_schema_name(resp: ReferenceOr<openapi::Response>) -> Option<Str
413470
if !obj.is_ref() {
414471
tracing::error!(?obj, "unexpected non-$ref json body schema");
415472
}
416-
get_schema_name(obj.reference)
473+
get_schema_name(obj.reference.as_deref())
417474
}
418475
}
419476
}

src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ fn main() -> anyhow::Result<()> {
6161
let mut components = spec.components.unwrap_or_default();
6262

6363
if let Some(paths) = spec.paths {
64-
let api = Api::new(paths, with_deprecated).unwrap();
64+
let api = Api::new(paths, with_deprecated, &components.schemas).unwrap();
6565
{
6666
let mut api_file = BufWriter::new(File::create("api.ron")?);
6767
writeln!(api_file, "{api:#?}")?;

src/types.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ impl FieldType {
7272
Some(SingleOrVec::Vec(types)) => {
7373
bail!("unsupported multi-typed parameter: `{types:?}`")
7474
}
75-
None => match get_schema_name(obj.reference) {
75+
None => match get_schema_name(obj.reference.as_deref()) {
7676
Some(name) => Self::SchemaRef(name),
7777
None => bail!("unsupported type-less parameter"),
7878
},

src/util.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use anyhow::Context as _;
55
use camino::Utf8Path;
66
use serde::de::DeserializeOwned;
77

8-
pub(crate) fn get_schema_name(maybe_ref: Option<String>) -> Option<String> {
8+
pub(crate) fn get_schema_name(maybe_ref: Option<&str>) -> Option<String> {
99
let r = maybe_ref?;
1010
let schema_name = r.strip_prefix("#/components/schemas/");
1111
if schema_name.is_none() {

templates/svix_cli_resource.rs.jinja

+13-2
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,12 @@ pub enum {{ resource_type_name }}Commands {
8686
{# body parameter struct -#}
8787
{% if op.request_body_schema_name is defined -%}
8888
{{ op.request_body_schema_name | to_snake_case }}:
89-
JsonOf<{{ op.request_body_schema_name }}>,
89+
{% if op.request_body_all_optional %}
90+
Option<JsonOf<{{ op.request_body_schema_name }}>>
91+
{% else %}
92+
JsonOf<{{ op.request_body_schema_name }}>
93+
{% endif %}
94+
,
9095
{% endif -%}
9196

9297
{# query parameters -#}
@@ -151,7 +156,13 @@ impl {{ resource_type_name }}Commands {
151156

152157
{# body parameter struct -#}
153158
{% if op.request_body_schema_name is defined -%}
154-
{{ op.request_body_schema_name | to_snake_case }}.into_inner(),
159+
{{ op.request_body_schema_name | to_snake_case }}
160+
{% if op.request_body_all_optional -%}
161+
.map(|x| x.into_inner()).unwrap_or_default()
162+
{% else -%}
163+
.into_inner()
164+
{% endif -%}
165+
,
155166
{% endif -%}
156167

157168
{# query parameters -#}

0 commit comments

Comments
 (0)