diff options
author | Cyborus <cyborus@cyborus.xyz> | 2024-01-30 03:56:20 +0100 |
---|---|---|
committer | Cyborus <cyborus@cyborus.xyz> | 2024-01-30 03:56:20 +0100 |
commit | 19a0dc3a60f41c40a49b58d901a2bc82c00f11cb (patch) | |
tree | 51d505eef691c7ac3c524e145efefca1500601b3 /generator | |
parent | split generator into modules (diff) | |
download | forgejo-api-19a0dc3a60f41c40a49b58d901a2bc82c00f11cb.tar.xz forgejo-api-19a0dc3a60f41c40a49b58d901a2bc82c00f11cb.zip |
improve openapi verification
Diffstat (limited to 'generator')
-rw-r--r-- | generator/src/main.rs | 1 | ||||
-rw-r--r-- | generator/src/methods.rs | 59 | ||||
-rw-r--r-- | generator/src/openapi.rs | 479 | ||||
-rw-r--r-- | generator/src/structs.rs | 25 |
4 files changed, 500 insertions, 64 deletions
diff --git a/generator/src/main.rs b/generator/src/main.rs index 4e4054c..5a59984 100644 --- a/generator/src/main.rs +++ b/generator/src/main.rs @@ -21,6 +21,7 @@ fn get_spec() -> eyre::Result<OpenApiV2> { .unwrap_or_else(|| OsString::from("./swagger.v1.json")); let file = std::fs::read(path)?; let spec = serde_json::from_slice::<OpenApiV2>(&file)?; + spec.validate()?; Ok(spec) } diff --git a/generator/src/methods.rs b/generator/src/methods.rs index aeebf30..3ad34c7 100644 --- a/generator/src/methods.rs +++ b/generator/src/methods.rs @@ -106,7 +106,7 @@ fn method_docs(op: &Operation) -> eyre::Result<String> { MaybeRef::Ref { _ref } => eyre::bail!("pipis"), }; match param._in { - ParameterIn::Path | ParameterIn::Body | ParameterIn::FormData => { + ParameterIn::Path { param: _ } | ParameterIn::Body { schema: _ } | ParameterIn::FormData { param: _ } => { write!(&mut out, "/// - `{}`", param.name)?; if let Some(description) = ¶m.description { write!(&mut out, ": {}", description)?; @@ -141,31 +141,30 @@ fn fn_args_from_op(spec: &OpenApiV2, op: &Operation) -> eyre::Result<String> { let mut has_form = false; if let Some(params) = &op.parameters { for param in params { - let param = match ¶m { + let full_param = match ¶m { MaybeRef::Value { value } => value, MaybeRef::Ref { _ref } => eyre::bail!("todo: add deref parameters"), }; - match param._in { - ParameterIn::Path => { + match &full_param._in { + ParameterIn::Path { param } => { let type_name = param_type(¶m, false)?; args.push_str(", "); - args.push_str(&crate::sanitize_ident(¶m.name)); + args.push_str(&crate::sanitize_ident(&full_param.name)); args.push_str(": "); args.push_str(&type_name); } - ParameterIn::Query => has_query = true, - ParameterIn::Header => has_headers = true, - ParameterIn::Body => { - let schema_ref = param.schema.as_ref().unwrap(); - let ty = crate::schema_ref_type_name(spec, &schema_ref)?; + ParameterIn::Query { param } => has_query = true, + ParameterIn::Header { param }=> has_headers = true, + ParameterIn::Body { schema } => { + let ty = crate::schema_ref_type_name(spec, schema)?; args.push_str(", "); - args.push_str(&crate::sanitize_ident(¶m.name)); + args.push_str(&crate::sanitize_ident(&full_param.name)); args.push_str(": "); args.push_str(&ty); } - ParameterIn::FormData => { + ParameterIn::FormData { param } => { args.push_str(", "); - args.push_str(&crate::sanitize_ident(¶m.name)); + args.push_str(&crate::sanitize_ident(&full_param.name)); args.push_str(": Vec<u8>"); } } @@ -179,12 +178,8 @@ fn fn_args_from_op(spec: &OpenApiV2, op: &Operation) -> eyre::Result<String> { Ok(args) } -pub fn param_type(param: &Parameter, owned: bool) -> eyre::Result<String> { - let _type = param - ._type - .as_ref() - .ok_or_else(|| eyre::eyre!("no type provided for path param"))?; - param_type_inner(_type, param.format.as_deref(), param.items.as_ref(), owned) +pub fn param_type(param: &NonBodyParameter, owned: bool) -> eyre::Result<String> { + param_type_inner(¶m._type, param.format.as_deref(), param.items.as_ref(), owned) } fn param_type_inner( @@ -328,11 +323,11 @@ fn create_method_request( MaybeRef::Ref { _ref } => eyre::bail!("todo: add deref parameters"), }; let name = crate::sanitize_ident(¶m.name); - match param._in { - ParameterIn::Path => (/* do nothing */), - ParameterIn::Query => has_query = true, - ParameterIn::Header => has_headers = true, - ParameterIn::Body => { + match ¶m._in { + ParameterIn::Path { param } => (/* do nothing */), + ParameterIn::Query { param } => has_query = true, + ParameterIn::Header { param } => has_headers = true, + ParameterIn::Body { schema } => { if !body_method.is_empty() { eyre::bail!("cannot have more than one body parameter"); } @@ -342,7 +337,7 @@ fn create_method_request( body_method = format!(".json(&{name})"); } } - ParameterIn::FormData => { + ParameterIn::FormData { param } => { if !body_method.is_empty() { eyre::bail!("cannot have more than one body parameter"); } @@ -368,17 +363,13 @@ fn create_method_request( } fn param_is_string(spec: &OpenApiV2, param: &Parameter) -> eyre::Result<bool> { - match param._in { - ParameterIn::Body => { - let schema_ref = param - .schema - .as_ref() - .ok_or_else(|| eyre::eyre!("body param did not have schema"))?; - crate::schema_is_string(spec, schema_ref) + match ¶m._in { + ParameterIn::Body { schema } => { + crate::schema_is_string(spec, schema) } - _ => { + ParameterIn::Path { param } | ParameterIn::Query { param } | ParameterIn::Header { param } | ParameterIn::FormData { param } => { let is_str = match param._type { - Some(ParameterType::String) => true, + ParameterType::String => true, _ => false, }; Ok(is_str) diff --git a/generator/src/openapi.rs b/generator/src/openapi.rs index 5f87f01..292ca5f 100644 --- a/generator/src/openapi.rs +++ b/generator/src/openapi.rs @@ -1,5 +1,6 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; +use eyre::WrapErr; use url::Url; #[derive(serde::Deserialize, Debug, PartialEq)] @@ -7,7 +8,7 @@ use url::Url; pub struct OpenApiV2 { pub swagger: String, pub info: SpecInfo, - pub host: Option<Url>, + pub host: Option<String>, pub base_path: Option<String>, pub schemes: Option<Vec<String>>, pub consumes: Option<Vec<String>>, @@ -22,6 +23,62 @@ pub struct OpenApiV2 { pub external_docs: Option<ExternalDocs>, } +impl OpenApiV2 { + pub fn validate(&self) -> eyre::Result<()> { + eyre::ensure!(self.swagger == "2.0", "swagger version must be 2.0"); + if let Some(host) = &self.host { + eyre::ensure!(!host.contains("://"), "openapi.host cannot contain scheme"); + eyre::ensure!(!host.contains("/"), "openapi.host cannot contain path"); + } + if let Some(base_path) = &self.base_path { + eyre::ensure!( + base_path.starts_with("/"), + "openapi.base_path must start with a forward slash" + ); + } + if let Some(schemes) = &self.schemes { + for scheme in schemes { + eyre::ensure!( + matches!(&**scheme, "http" | "https" | "ws" | "wss"), + "openapi.schemes must only be http, https, ws, or wss" + ); + } + } + for (path, path_item) in &self.paths { + eyre::ensure!( + path.starts_with("/"), + "members of openapi.paths must start with a forward slash; {path} does not" + ); + let mut operation_ids = BTreeSet::new(); + path_item + .validate(&mut operation_ids) + .wrap_err_with(|| format!("OpenApiV2.paths[\"{path}\"]"))?; + } + if let Some(definitions) = &self.definitions { + for (name, schema) in definitions { + schema + .validate() + .wrap_err_with(|| format!("OpenApiV2.definitions[\"{name}\"]"))?; + } + } + if let Some(params) = &self.parameters { + for (name, param) in params { + param + .validate() + .wrap_err_with(|| format!("OpenApiV2.parameters[\"{name}\"]"))?; + } + } + if let Some(responses) = &self.responses { + for (name, responses) in responses { + responses + .validate() + .wrap_err_with(|| format!("OpenApiV2.responses[\"{name}\"]"))?; + } + } + Ok(()) + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct SpecInfo { @@ -63,6 +120,40 @@ pub struct PathItem { pub parameters: Option<Vec<MaybeRef<Parameter>>>, } +impl PathItem { + fn validate<'a>(&'a self, ids: &mut BTreeSet<&'a str>) -> eyre::Result<()> { + if let Some(op) = &self.get { + op.validate(ids).wrap_err("PathItem.get")?; + } + if let Some(op) = &self.put { + op.validate(ids).wrap_err("PathItem.patch")?; + } + if let Some(op) = &self.post { + op.validate(ids).wrap_err("PathItem.patch")?; + } + if let Some(op) = &self.delete { + op.validate(ids).wrap_err("PathItem.patch")?; + } + if let Some(op) = &self.options { + op.validate(ids).wrap_err("PathItem.patch")?; + } + if let Some(op) = &self.head { + op.validate(ids).wrap_err("PathItem.patch")?; + } + if let Some(op) = &self.patch { + op.validate(ids).wrap_err("PathItem.patch")?; + } + if let Some(params) = &self.parameters { + for param in params { + if let MaybeRef::Value { value } = param { + value.validate().wrap_err("PathItem.parameters")?; + } + } + } + Ok(()) + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Operation { @@ -80,6 +171,32 @@ pub struct Operation { pub security: Option<Vec<BTreeMap<String, Vec<String>>>>, } +impl Operation { + fn validate<'a>(&'a self, ids: &mut BTreeSet<&'a str>) -> eyre::Result<()> { + if let Some(operation_id) = self.operation_id.as_deref() { + let is_new = ids.insert(operation_id); + eyre::ensure!(is_new, "duplicate operation id"); + } + if let Some(params) = &self.parameters { + for param in params { + if let MaybeRef::Value { value } = param { + value.validate().wrap_err("Operation.parameters")?; + } + } + } + self.responses.validate().wrap_err("operation response")?; + if let Some(schemes) = &self.schemes { + for scheme in schemes { + eyre::ensure!( + matches!(&**scheme, "http" | "https" | "ws" | "wss"), + "openapi.schemes must only be http, https, ws, or wss" + ); + } + } + Ok(()) + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct ExternalDocs { @@ -91,13 +208,71 @@ pub struct ExternalDocs { #[serde(rename_all(deserialize = "camelCase"))] pub struct Parameter { pub name: String, - #[serde(rename = "in")] - pub _in: ParameterIn, pub description: Option<String>, - pub required: Option<bool>, - pub schema: Option<MaybeRef<Schema>>, + #[serde(flatten)] + pub _in: ParameterIn, +} + +impl Parameter { + fn validate(&self) -> eyre::Result<()> { + self._in.validate() + } +} + +#[derive(serde::Deserialize, Debug, PartialEq)] +#[serde(rename_all(deserialize = "camelCase"))] +#[serde(tag = "in")] +pub enum ParameterIn { + Body { + schema: MaybeRef<Schema>, + }, + Path { + #[serde(flatten)] + param: NonBodyParameter, + }, + Query { + #[serde(flatten)] + param: NonBodyParameter, + }, + Header { + #[serde(flatten)] + param: NonBodyParameter, + }, + FormData { + #[serde(flatten)] + param: NonBodyParameter, + }, +} + +impl ParameterIn { + fn validate(&self) -> eyre::Result<()> { + match self { + ParameterIn::Path { param } => { + eyre::ensure!( + param.required, + "path parameters must be required" + ); + param.validate().wrap_err("path param") + }, + ParameterIn::Query { param } => param.validate().wrap_err("query param"), + ParameterIn::Header { param } => param.validate().wrap_err("header param"), + ParameterIn::Body { schema } => if let MaybeRef::Value { value } = schema { + value.validate().wrap_err("body param") + } else { + Ok(()) + }, + ParameterIn::FormData { param } => param.validate().wrap_err("form param"), + } + } +} + +#[derive(serde::Deserialize, Debug, PartialEq)] +#[serde(rename_all(deserialize = "camelCase"))] +pub struct NonBodyParameter { + #[serde(default)] + pub required: bool, #[serde(rename = "type")] - pub _type: Option<ParameterType>, + pub _type: ParameterType, pub format: Option<String>, pub allow_empty_value: Option<bool>, pub items: Option<Items>, @@ -118,14 +293,48 @@ pub struct Parameter { pub multiple_of: Option<u64>, } -#[derive(serde::Deserialize, Debug, PartialEq)] -#[serde(rename_all(deserialize = "camelCase"))] -pub enum ParameterIn { - Path, - Query, - Header, - Body, - FormData, +impl NonBodyParameter { + fn validate(&self) -> eyre::Result<()> { + if self._type == ParameterType::Array { + eyre::ensure!( + self.items.is_some(), + "array paramters must define their item types" + ); + } + if let Some(items) = &self.items { + items.validate()?; + } + if let Some(default) = &self.default { + eyre::ensure!( + self._type.matches_value(default), + "param's default must match its type" + ); + } + if let Some(_enum) = &self._enum { + for variant in _enum { + eyre::ensure!( + self._type.matches_value(variant), + "header enum variant must match its type" + ); + } + } + if self.exclusive_maximum.is_some() { + eyre::ensure!( + self.maximum.is_some(), + "presence of `exclusiveMaximum` requires `maximum` be there too" + ); + } + if self.exclusive_minimum.is_some() { + eyre::ensure!( + self.minimum.is_some(), + "presence of `exclusiveMinimum` requires `minimum` be there too" + ); + } + if let Some(multiple_of) = self.multiple_of { + eyre::ensure!(multiple_of > 0, "multipleOf must be greater than 0"); + } + Ok(()) + } } #[derive(serde::Deserialize, Debug, PartialEq)] @@ -139,6 +348,19 @@ pub enum ParameterType { File, } +impl ParameterType { + fn matches_value(&self, value: &serde_json::Value) -> bool { + match (self, value) { + (ParameterType::String, serde_json::Value::String(_)) + | (ParameterType::Number, serde_json::Value::Number(_)) + | (ParameterType::Integer, serde_json::Value::Number(_)) + | (ParameterType::Boolean, serde_json::Value::Bool(_)) + | (ParameterType::Array, serde_json::Value::Array(_)) => true, + _ => false, + } + } +} + #[derive(serde::Deserialize, Debug, PartialEq, Clone, Copy)] #[serde(rename_all(deserialize = "camelCase"))] pub enum CollectionFormat { @@ -173,6 +395,55 @@ pub struct Items { pub multiple_of: Option<u64>, } +impl Items { + fn validate(&self) -> eyre::Result<()> { + if self._type == ParameterType::Array { + eyre::ensure!( + self.items.is_some(), + "array paramters must define their item types" + ); + } + if let Some(items) = &self.items { + items.validate()?; + } + if let Some(default) = &self.default { + match (&self._type, default) { + (ParameterType::String, serde_json::Value::String(_)) + | (ParameterType::Number, serde_json::Value::Number(_)) + | (ParameterType::Integer, serde_json::Value::Number(_)) + | (ParameterType::Boolean, serde_json::Value::Bool(_)) + | (ParameterType::Array, serde_json::Value::Array(_)) => (), + (ParameterType::File, _) => eyre::bail!("file params cannot have default value"), + _ => eyre::bail!("param's default must match its type"), + }; + } + if let Some(_enum) = &self._enum { + for variant in _enum { + eyre::ensure!( + self._type.matches_value(variant), + "header enum variant must match its type" + ); + } + } + if self.exclusive_maximum.is_some() { + eyre::ensure!( + self.maximum.is_some(), + "presence of `exclusiveMaximum` requires `maximum` be there too" + ); + } + if self.exclusive_minimum.is_some() { + eyre::ensure!( + self.minimum.is_some(), + "presence of `exclusiveMinimum` requires `minimum` be there too" + ); + } + if let Some(multiple_of) = self.multiple_of { + eyre::ensure!(multiple_of > 0, "multipleOf must be greater than 0"); + } + Ok(()) + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Responses { @@ -181,6 +452,28 @@ pub struct Responses { pub http_codes: BTreeMap<String, MaybeRef<Response>>, } +impl Responses { + fn validate(&self) -> eyre::Result<()> { + if self.default.is_none() && self.http_codes.is_empty() { + eyre::bail!("must have at least one response"); + } + if let Some(MaybeRef::Value { value }) = &self.default { + value.validate().wrap_err("default response")?; + } + for (code, response) in &self.http_codes { + let code_int = code.parse::<u16>().wrap_err("http code must be a number")?; + eyre::ensure!( + code_int >= 100 && code_int < 1000, + "invalid http status code" + ); + if let MaybeRef::Value { value } = response { + value.validate().wrap_err_with(|| code.to_string())?; + } + } + Ok(()) + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Response { @@ -190,6 +483,20 @@ pub struct Response { pub examples: Option<BTreeMap<String, serde_json::Value>>, } +impl Response { + fn validate(&self) -> eyre::Result<()> { + if let Some(headers) = &self.headers { + for (_, value) in headers { + value.validate().wrap_err("response header")?; + } + } + if let Some(MaybeRef::Value { value }) = &self.schema { + value.validate().wrap_err("response")?; + } + Ok(()) + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Header { @@ -215,6 +522,52 @@ pub struct Header { pub multiple_of: Option<u64>, } +impl Header { + fn validate(&self) -> eyre::Result<()> { + if self._type == ParameterType::Array { + eyre::ensure!( + self.items.is_some(), + "array paramters must define their item types" + ); + } + if let Some(default) = &self.default { + match (&self._type, default) { + (ParameterType::String, serde_json::Value::String(_)) + | (ParameterType::Number, serde_json::Value::Number(_)) + | (ParameterType::Integer, serde_json::Value::Number(_)) + | (ParameterType::Boolean, serde_json::Value::Bool(_)) + | (ParameterType::Array, serde_json::Value::Array(_)) => (), + (ParameterType::File, _) => eyre::bail!("file params cannot have default value"), + _ => eyre::bail!("param's default must match its type"), + }; + } + if let Some(_enum) = &self._enum { + for variant in _enum { + eyre::ensure!( + self._type.matches_value(variant), + "header enum variant must match its type" + ); + } + } + if self.exclusive_maximum.is_some() { + eyre::ensure!( + self.maximum.is_some(), + "presence of `exclusiveMaximum` requires `maximum` be there too" + ); + } + if self.exclusive_minimum.is_some() { + eyre::ensure!( + self.minimum.is_some(), + "presence of `exclusiveMinimum` requires `minimum` be there too" + ); + } + if let Some(multiple_of) = self.multiple_of { + eyre::ensure!(multiple_of > 0, "multipleOf must be greater than 0"); + } + Ok(()) + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Tag { @@ -259,6 +612,89 @@ pub struct Schema { pub example: Option<serde_json::Value>, } +impl Schema { + fn validate(&self) -> eyre::Result<()> { + if let Some(_type) = &self._type { + match _type { + SchemaType::One(_type) => { + if _type == &Primitive::Array { + eyre::ensure!( + self.items.is_some(), + "array paramters must define their item types" + ); + } + if let Some(default) = &self.default { + eyre::ensure!( + _type.matches_value(default), + "param's default must match its type" + ); + } + if let Some(_enum) = &self._enum { + for variant in _enum { + eyre::ensure!( + _type.matches_value(variant), + "schema enum variant must match its type" + ); + } + } + } + SchemaType::List(_) => { + eyre::bail!("sum types not supported"); + } + } + } else { + eyre::ensure!( + self.default.is_none(), + "cannot have default when no type is specified" + ); + } + if let Some(items) = &self.items { + if let MaybeRef::Value { value } = &**items { + value.validate()?; + } + } + if let Some(required) = &self.required { + let properties = self.properties.as_ref().ok_or_else(|| { + eyre::eyre!("required properties listed but no properties present") + })?; + for i in required { + eyre::ensure!( + properties.contains_key(i), + "property \"{i}\" required, but is not defined" + ); + } + } + if let Some(properties) = &self.properties { + for (_, schema) in properties { + if let MaybeRef::Value { value } = schema { + value.validate().wrap_err("schema properties")?; + } + } + } + if let Some(additional_properties) = &self.additional_properties { + if let MaybeRef::Value { value } = &**additional_properties { + value.validate().wrap_err("schema additional properties")?; + } + } + if self.exclusive_maximum.is_some() { + eyre::ensure!( + self.maximum.is_some(), + "presence of `exclusiveMaximum` requires `maximum` be there too" + ); + } + if self.exclusive_minimum.is_some() { + eyre::ensure!( + self.minimum.is_some(), + "presence of `exclusiveMinimum` requires `minimum` be there too" + ); + } + if let Some(multiple_of) = self.multiple_of { + eyre::ensure!(multiple_of > 0, "multipleOf must be greater than 0"); + } + Ok(()) + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(untagged)] pub enum SchemaType { @@ -278,6 +714,19 @@ pub enum Primitive { String, } +impl Primitive { + fn matches_value(&self, value: &serde_json::Value) -> bool { + match (self, value) { + (Primitive::String, serde_json::Value::String(_)) + | (Primitive::Number, serde_json::Value::Number(_)) + | (Primitive::Integer, serde_json::Value::Number(_)) + | (Primitive::Boolean, serde_json::Value::Bool(_)) + | (Primitive::Array, serde_json::Value::Array(_)) => true, + _ => false, + } + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Xml { diff --git a/generator/src/structs.rs b/generator/src/structs.rs index 55483b4..ec4f3b3 100644 --- a/generator/src/structs.rs +++ b/generator/src/structs.rs @@ -151,14 +151,13 @@ fn create_query_struct(spec: &OpenApiV2, path: &str, op: &Operation) -> eyre::Re MaybeRef::Value { value } => value, MaybeRef::Ref { _ref } => eyre::bail!("todo: add deref parameters"), }; - if param._in == ParameterIn::Query { - let ty = crate::methods::param_type(param, true)?; + if let ParameterIn::Query { param: query_param} = ¶m._in { + let ty = crate::methods::param_type(query_param, true)?; let field_name = crate::sanitize_ident(¶m.name); - let required = param.required.unwrap_or_default(); fields.push_str("pub "); fields.push_str(&field_name); fields.push_str(": "); - if required { + if query_param.required { fields.push_str(&ty); } else { fields.push_str("Option<"); @@ -168,11 +167,7 @@ fn create_query_struct(spec: &OpenApiV2, path: &str, op: &Operation) -> eyre::Re fields.push_str(",\n"); let mut handler = String::new(); - let ty = param - ._type - .as_ref() - .ok_or_else(|| eyre::eyre!("no type provided for query field"))?; - if required { + if query_param.required { writeln!(&mut handler, "let {field_name} = &self.{field_name};")?; } else { writeln!( @@ -180,8 +175,8 @@ fn create_query_struct(spec: &OpenApiV2, path: &str, op: &Operation) -> eyre::Re "if let Some({field_name}) = &self.{field_name} {{" )?; } - match ty { - ParameterType::String => match param.format.as_deref() { + match &query_param._type { + ParameterType::String => match query_param.format.as_deref() { Some("date-time" | "date") => { writeln!( &mut handler, @@ -206,14 +201,14 @@ fn create_query_struct(spec: &OpenApiV2, path: &str, op: &Operation) -> eyre::Re )?; } ParameterType::Array => { - let format = param.collection_format.unwrap_or(CollectionFormat::Csv); - let item = param + let format = query_param.collection_format.unwrap_or(CollectionFormat::Csv); + let item = query_param .items .as_ref() .ok_or_else(|| eyre::eyre!("array must have item type defined"))?; let item_pusher = match item._type { ParameterType::String => { - match param.format.as_deref() { + match query_param.format.as_deref() { Some("date-time" | "date") => { "write!(f, \"{{date}}\", item.format(&time::format_description::well_known::Rfc3339).unwrap())?;" }, @@ -280,7 +275,7 @@ fn create_query_struct(spec: &OpenApiV2, path: &str, op: &Operation) -> eyre::Re } ParameterType::File => eyre::bail!("cannot send file in query"), } - if !required { + if !query_param.required { writeln!(&mut handler, "}}")?; } imp.push_str(&handler); |