summaryrefslogtreecommitdiffstats
path: root/generator
diff options
context:
space:
mode:
authorCyborus <cyborus@cyborus.xyz>2024-01-30 03:56:20 +0100
committerCyborus <cyborus@cyborus.xyz>2024-01-30 03:56:20 +0100
commit19a0dc3a60f41c40a49b58d901a2bc82c00f11cb (patch)
tree51d505eef691c7ac3c524e145efefca1500601b3 /generator
parentsplit generator into modules (diff)
downloadforgejo-api-19a0dc3a60f41c40a49b58d901a2bc82c00f11cb.tar.xz
forgejo-api-19a0dc3a60f41c40a49b58d901a2bc82c00f11cb.zip
improve openapi verification
Diffstat (limited to 'generator')
-rw-r--r--generator/src/main.rs1
-rw-r--r--generator/src/methods.rs59
-rw-r--r--generator/src/openapi.rs479
-rw-r--r--generator/src/structs.rs25
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) = &param.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 &param {
+ let full_param = match &param {
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(&param, false)?;
args.push_str(", ");
- args.push_str(&crate::sanitize_ident(&param.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(&param.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(&param.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(&param._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(&param.name);
- match param._in {
- ParameterIn::Path => (/* do nothing */),
- ParameterIn::Query => has_query = true,
- ParameterIn::Header => has_headers = true,
- ParameterIn::Body => {
+ match &param._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 &param._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} = &param._in {
+ let ty = crate::methods::param_type(query_param, true)?;
let field_name = crate::sanitize_ident(&param.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);