path parameters first, by position; then query parameters, by name

This commit is contained in:
Joshua M. Clulow 2021-09-17 21:17:21 -07:00
parent aab5061b56
commit 2a319675b1
2 changed files with 107 additions and 45 deletions

View File

@ -1171,56 +1171,90 @@ fn gen(api: &OpenAPI, ts: &mut TypeSpace) -> Result<String> {
}
a(" &self,");
for par in o.parameters.iter() {
match par.item()? {
openapiv3::Parameter::Path {
parameter_data,
style: openapiv3::PathStyle::Simple,
} => {
/*
* Path parameters MUST be required.
*/
assert!(parameter_data.required);
let nam = &parameter_data.name;
let tid = ts.select(None, parameter_data.schema()?)?;
let typ =
ts.render_type(&tid, UseContext::Parameter)?;
a(&format!(" {}: {},", nam, typ));
}
openapiv3::Parameter::Query {
parameter_data,
allow_reserved: _,
style: openapiv3::QueryStyle::Form,
allow_empty_value,
} => {
if let Some(aev) = allow_empty_value {
if *aev {
bail!("allow empty value is a no go");
}
}
let nam = &parameter_data.name;
let tid = ts.select(None, parameter_data.schema()?)?;
let tid = if parameter_data.required {
tid
} else {
/*
* The order of parameters in the specification is effectively
* arbitrary, and both path and query style parameters end up
* mingled in the same list.
*/
let mut parms = o
.parameters
.iter()
.map(|par| {
match par.item()? {
openapiv3::Parameter::Path {
parameter_data,
style: openapiv3::PathStyle::Simple,
} => {
/*
* If this is an optional parameter, we need an
* Option version of the type.
* Path parameters MUST be required.
*/
ts.id_for_optional(&tid)
};
let typ =
ts.render_type(&tid, UseContext::Parameter)?;
a(&format!(" {}: {},", nam, typ));
assert!(parameter_data.required);
query.push((nam.to_string(), !parameter_data.required));
let nam = &parameter_data.name;
let tid =
ts.select(None, parameter_data.schema()?)?;
let typ =
ts.render_type(&tid, UseContext::Parameter)?;
return Ok((true, nam, typ));
}
openapiv3::Parameter::Query {
parameter_data,
allow_reserved: _,
style: openapiv3::QueryStyle::Form,
allow_empty_value,
} => {
if let Some(aev) = allow_empty_value {
if *aev {
bail!("allow empty value is a no go");
}
}
let nam = &parameter_data.name;
let tid =
ts.select(None, parameter_data.schema()?)?;
let tid = if parameter_data.required {
tid
} else {
/*
* If this is an optional parameter, we need an
* Option version of the type.
*/
ts.id_for_optional(&tid)
};
let typ =
ts.render_type(&tid, UseContext::Parameter)?;
query.push((
nam.to_string(),
!parameter_data.required,
));
return Ok((false, nam, typ));
}
x => bail!("unhandled parameter type: {:#?}", x),
}
x => bail!("unhandled parameter type: {:#?}", x),
}
})
.collect::<Result<Vec<_>>>()?;
/*
* Deal first with path parameters, ordered by their position in the
* path template string.
*/
let tmp = template::parse(p)?;
for pn in tmp.names() {
let par = parms.iter().find(|p| p.0 && p.1 == &pn).unwrap();
a(&format!(" {}: {},", par.1, par.2));
}
/*
* Second, include query parameters, ordered by parameter name.
*/
parms.sort_by(|a, b| a.1.cmp(&b.1));
for par in parms.iter().filter(|p| !p.0) {
a(&format!(" {}: {},", par.1, par.2));
}
/*
* Include the body parameter, if there is one, last in the list:
*/
if let Some(bp) = &body_param {
a(&format!(" body: {},", bp));
}
@ -1289,7 +1323,6 @@ fn gen(api: &OpenAPI, ts: &mut TypeSpace) -> Result<String> {
/*
* Generate the URL for the request.
*/
let tmp = template::parse(p)?;
a(&tmp.compile());
/*

View File

@ -35,6 +35,16 @@ impl Template {
out.push_str(" );\n");
out
}
pub fn names(&self) -> Vec<String> {
self.components
.iter()
.filter_map(|c| match c {
Component::Parameter(name) => Some(name.to_string()),
Component::Constant(_) => None,
})
.collect()
}
}
pub fn parse(t: &str) -> Result<Template> {
@ -159,6 +169,25 @@ mod test {
Ok(())
}
#[test]
fn names() -> Result<()> {
let trials = vec![
("/info", vec![]),
("/measure/{number}", vec!["number".to_string()]),
(
"/measure/{one}/{two}/and/{three}/yeah",
vec!["one".to_string(), "two".to_string(), "three".to_string()],
),
];
for (path, want) in trials.iter() {
let t = parse(path).with_context(|| anyhow!("path {}", path))?;
assert_eq!(&t.names(), want);
}
Ok(())
}
#[test]
fn compile() -> Result<()> {
let t = parse("/measure/{number}")?;