diff options
author | Cyborus <cyborus@cyborus.xyz> | 2024-01-30 22:45:53 +0100 |
---|---|---|
committer | Cyborus <cyborus@cyborus.xyz> | 2024-01-30 22:45:53 +0100 |
commit | 311e17e3ba978c8f5bbb87b5fd598929f80c3e25 (patch) | |
tree | 1c447fa7bb8c356859a031ff387f14cea9ed509f /generator | |
parent | format (diff) | |
download | forgejo-api-311e17e3ba978c8f5bbb87b5fd598929f80c3e25.tar.xz forgejo-api-311e17e3ba978c8f5bbb87b5fd598929f80c3e25.zip |
more general dereferencing
Diffstat (limited to 'generator')
-rw-r--r-- | generator/src/main.rs | 30 | ||||
-rw-r--r-- | generator/src/methods.rs | 30 | ||||
-rw-r--r-- | generator/src/openapi.rs | 648 |
3 files changed, 658 insertions, 50 deletions
diff --git a/generator/src/main.rs b/generator/src/main.rs index 6058807..6e43f2c 100644 --- a/generator/src/main.rs +++ b/generator/src/main.rs @@ -46,7 +46,12 @@ fn run_rustfmt_on(path: &OsStr) { } fn schema_ref_type_name(spec: &OpenApiV2, schema: &MaybeRef<Schema>) -> eyre::Result<String> { - let (name, schema) = deref_definition(spec, &schema)?; + let name = if let MaybeRef::Ref { _ref } = schema { + _ref.rsplit_once("/").map(|(_, b)| b) + } else { + None + }; + let schema = schema.deref(spec)?; schema_type_name(spec, name, schema) } @@ -105,29 +110,8 @@ fn schema_type_name( } } -fn deref_definition<'a>( - spec: &'a OpenApiV2, - r: &'a MaybeRef<Schema>, -) -> eyre::Result<(Option<&'a str>, &'a Schema)> { - let r = match r { - MaybeRef::Value { value } => return Ok((None, value)), - MaybeRef::Ref { _ref } => _ref, - }; - let name = r - .strip_prefix("#/definitions/") - .ok_or_else(|| eyre::eyre!("invalid definition reference"))?; - let global_definitions = spec - .definitions - .as_ref() - .ok_or_else(|| eyre::eyre!("no global definitions"))?; - let definition = global_definitions - .get(name) - .ok_or_else(|| eyre::eyre!("referenced definition does not exist"))?; - Ok((Some(name), definition)) -} - fn schema_is_string(spec: &OpenApiV2, schema: &MaybeRef<Schema>) -> eyre::Result<bool> { - let (_, schema) = deref_definition(spec, schema)?; + let schema = schema.deref(spec)?; let is_str = match schema._type { Some(SchemaType::One(Primitive::String)) => true, _ => false, diff --git a/generator/src/methods.rs b/generator/src/methods.rs index ad29e05..4a97e18 100644 --- a/generator/src/methods.rs +++ b/generator/src/methods.rs @@ -142,10 +142,7 @@ fn fn_args_from_op(spec: &OpenApiV2, op: &Operation) -> eyre::Result<String> { // let mut has_headers = false; if let Some(params) = &op.parameters { for param in params { - let full_param = match ¶m { - MaybeRef::Value { value } => value, - MaybeRef::Ref { _ref } => eyre::bail!("todo: add deref parameters"), - }; + let full_param = param.deref(spec)?; match &full_param._in { ParameterIn::Path { param } => { let type_name = param_type(¶m, false)?; @@ -270,7 +267,7 @@ fn response_ref_type_name( spec: &OpenApiV2, schema: &MaybeRef<Response>, ) -> eyre::Result<ResponseType> { - let (_, response) = deref_response(spec, schema)?; + let response = schema.deref(spec)?; let mut ty = ResponseType::default(); if response.headers.is_some() { ty.headers = Some("reqwest::header::HeaderMap".into()); @@ -281,27 +278,6 @@ fn response_ref_type_name( Ok(ty) } -fn deref_response<'a>( - spec: &'a OpenApiV2, - r: &'a MaybeRef<Response>, -) -> eyre::Result<(Option<&'a str>, &'a Response)> { - let r = match r { - MaybeRef::Value { value } => return Ok((None, value)), - MaybeRef::Ref { _ref } => _ref, - }; - let name = r - .strip_prefix("#/responses/") - .ok_or_else(|| eyre::eyre!("invalid response reference"))?; - let global_responses = spec - .responses - .as_ref() - .ok_or_else(|| eyre::eyre!("no global responses"))?; - let response = global_responses - .get(name) - .ok_or_else(|| eyre::eyre!("referenced response does not exist"))?; - Ok((Some(name), response)) -} - fn create_method_body( spec: &OpenApiV2, method: &str, @@ -431,7 +407,7 @@ fn create_method_response(spec: &OpenApiV2, op: &Operation) -> eyre::Result<Stri out.push_str("let response = self.execute(request).await?;\n"); out.push_str("match response.status().as_u16() {\n"); for (code, res) in &op.responses.http_codes { - let (_, res) = deref_response(spec, res)?; + let res = res.deref(spec)?; if !code.starts_with("2") { continue; } diff --git a/generator/src/openapi.rs b/generator/src/openapi.rs index 867220c..35dd9ba 100644 --- a/generator/src/openapi.rs +++ b/generator/src/openapi.rs @@ -3,6 +3,132 @@ use std::collections::{BTreeMap, BTreeSet}; use eyre::WrapErr; use url::Url; +trait JsonDeref: std::any::Any { + fn deref_any<'a>(&'a self, path: &str) -> eyre::Result<&'a dyn std::any::Any>; +} + +impl JsonDeref for bool { + fn deref_any<'a>(&'a self, path: &str) -> eyre::Result<&'a dyn std::any::Any> { + if path.is_empty() { + Ok(self) + } else { + Err(eyre::eyre!("not found")) + } + } +} + +impl JsonDeref for u64 { + fn deref_any<'a>(&'a self, path: &str) -> eyre::Result<&'a dyn std::any::Any> { + if path.is_empty() { + Ok(self) + } else { + Err(eyre::eyre!("not found")) + } + } +} + +impl JsonDeref for f64 { + fn deref_any<'a>(&'a self, path: &str) -> eyre::Result<&'a dyn std::any::Any> { + if path.is_empty() { + Ok(self) + } else { + Err(eyre::eyre!("not found")) + } + } +} + +impl JsonDeref for String { + fn deref_any<'a>(&'a self, path: &str) -> eyre::Result<&'a dyn std::any::Any> { + if path.is_empty() { + Ok(self) + } else { + Err(eyre::eyre!("not found")) + } + } +} + +impl JsonDeref for Url { + fn deref_any<'a>(&'a self, path: &str) -> eyre::Result<&'a dyn std::any::Any> { + if path.is_empty() { + Ok(self) + } else { + Err(eyre::eyre!("not found")) + } + } +} + +impl<T: JsonDeref> JsonDeref for Option<T> { + fn deref_any<'a>(&'a self, path: &str) -> eyre::Result<&'a dyn std::any::Any> { + match self { + Some(x) => x.deref_any(path), + None => Err(eyre::eyre!("not found")), + } + } +} + +impl<T: JsonDeref> JsonDeref for Box<T> { + fn deref_any<'a>(&'a self, path: &str) -> eyre::Result<&'a dyn std::any::Any> { + T::deref_any(&**self, path) + } +} + +impl<T: JsonDeref> JsonDeref for Vec<T> { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + let idx = head.parse::<usize>().wrap_err("not found")?; + let value = self.get(idx).ok_or_else(|| eyre::eyre!("not found"))?; + value.deref_any(tail) + } +} + +impl<T: JsonDeref> JsonDeref for BTreeMap<String, T> { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + let value = self.get(head).ok_or_else(|| eyre::eyre!("not found"))?; + value.deref_any(tail) + } +} + +impl JsonDeref for serde_json::Value { + fn deref_any<'a>(&'a self, path: &str) -> eyre::Result<&'a dyn std::any::Any> { + match self { + serde_json::Value::Null => eyre::bail!("not found"), + serde_json::Value::Bool(b) => b.deref_any(path), + serde_json::Value::Number(x) => x.deref_any(path), + serde_json::Value::String(s) => s.deref_any(path), + serde_json::Value::Array(list) => list.deref_any(path), + serde_json::Value::Object(map) => map.deref_any(path), + } + } +} + +impl JsonDeref for serde_json::Map<String, serde_json::Value> { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + let value = self.get(head).ok_or_else(|| eyre::eyre!("not found"))?; + value.deref_any(tail) + } +} + +impl JsonDeref for serde_json::Number { + fn deref_any<'a>(&'a self, path: &str) -> eyre::Result<&'a dyn std::any::Any> { + if path.is_empty() { + Ok(self) + } else { + eyre::bail!("not found") + } + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct OpenApiV2 { @@ -23,6 +149,36 @@ pub struct OpenApiV2 { pub external_docs: Option<ExternalDocs>, } +impl JsonDeref for OpenApiV2 { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + let path = path + .strip_prefix("#/") + .ok_or_else(|| eyre::eyre!("invalid ref prefix"))?; + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "swagger" => self.swagger.deref_any(tail), + "info" => self.info.deref_any(tail), + "host" => self.host.deref_any(tail), + "base_path" => self.base_path.deref_any(tail), + "schemes" => self.schemes.deref_any(tail), + "consumes" => self.consumes.deref_any(tail), + "produces" => self.produces.deref_any(tail), + "paths" => self.paths.deref_any(tail), + "definitions" => self.definitions.deref_any(tail), + "parameters" => self.parameters.deref_any(tail), + "responses" => self.responses.deref_any(tail), + "security_definitions" => self.security_definitions.deref_any(tail), + "security" => self.security.deref_any(tail), + "tags" => self.tags.deref_any(tail), + "external_docs" => self.external_docs.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + } + } +} + impl OpenApiV2 { pub fn validate(&self) -> eyre::Result<()> { eyre::ensure!(self.swagger == "2.0", "swagger version must be 2.0"); @@ -77,6 +233,13 @@ impl OpenApiV2 { } Ok(()) } + + pub fn deref<T: std::any::Any>(&self, path: &str) -> eyre::Result<&T> { + self.deref_any(path).and_then(|a| { + a.downcast_ref::<T>() + .ok_or_else(|| eyre::eyre!("incorrect type found at reference")) + }) + } } #[derive(serde::Deserialize, Debug, PartialEq)] @@ -90,6 +253,24 @@ pub struct SpecInfo { pub version: String, } +impl JsonDeref for SpecInfo { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "title" => self.title.deref_any(tail), + "description" => self.description.deref_any(tail), + "terms_of_service" => self.terms_of_service.deref_any(tail), + "contact" => self.contact.deref_any(tail), + "license" => self.license.deref_any(tail), + "version" => self.version.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + } + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Contact { @@ -98,6 +279,21 @@ pub struct Contact { pub email: Option<String>, } +impl JsonDeref for Contact { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "name" => self.name.deref_any(tail), + "url" => self.url.deref_any(tail), + "email" => self.email.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + } + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct License { @@ -105,6 +301,20 @@ pub struct License { pub url: Option<Url>, } +impl JsonDeref for License { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "name" => self.name.deref_any(tail), + "url" => self.url.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + } + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct PathItem { @@ -120,6 +330,27 @@ pub struct PathItem { pub parameters: Option<Vec<MaybeRef<Parameter>>>, } +impl JsonDeref for PathItem { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "$ref" => self._ref.deref_any(tail), + "get" => self.get.deref_any(tail), + "put" => self.put.deref_any(tail), + "post" => self.post.deref_any(tail), + "delete" => self.delete.deref_any(tail), + "options" => self.options.deref_any(tail), + "head" => self.head.deref_any(tail), + "patch" => self.patch.deref_any(tail), + "parameters" => self.parameters.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + } + } +} + impl PathItem { fn validate<'a>(&'a self, ids: &mut BTreeSet<&'a str>) -> eyre::Result<()> { if let Some(op) = &self.get { @@ -171,6 +402,30 @@ pub struct Operation { pub security: Option<Vec<BTreeMap<String, Vec<String>>>>, } +impl JsonDeref for Operation { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "tags" => self.tags.deref_any(tail), + "summary" => self.summary.deref_any(tail), + "description" => self.description.deref_any(tail), + "external_docs" => self.external_docs.deref_any(tail), + "operation_id" => self.operation_id.deref_any(tail), + "consumes" => self.consumes.deref_any(tail), + "produces" => self.produces.deref_any(tail), + "parameters" => self.parameters.deref_any(tail), + "responses" => self.responses.deref_any(tail), + "schemes" => self.schemes.deref_any(tail), + "deprecated" => self.deprecated.deref_any(tail), + "security" => self.security.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + } + } +} + impl Operation { fn validate<'a>(&'a self, ids: &mut BTreeSet<&'a str>) -> eyre::Result<()> { if let Some(operation_id) = self.operation_id.as_deref() { @@ -204,6 +459,20 @@ pub struct ExternalDocs { pub url: Url, } +impl JsonDeref for ExternalDocs { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "description" => self.description.deref_any(tail), + "url" => self.url.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + } + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Parameter { @@ -213,6 +482,33 @@ pub struct Parameter { pub _in: ParameterIn, } +impl JsonDeref for Parameter { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "name" => self.name.deref_any(tail), + "description" => self.description.deref_any(tail), + "in" => { + if tail.is_empty() { + Ok(match &self._in { + ParameterIn::Body { schema: _ } => &"body" as _, + ParameterIn::Path { param: _ } => &"path" as _, + ParameterIn::Query { param: _ } => &"query" as _, + ParameterIn::Header { param: _ } => &"header" as _, + ParameterIn::FormData { param: _ } => &"formData" as _, + }) + } else { + eyre::bail!("not found") + } + } + _ => self._in.deref_any(path), + } + } +} + impl Parameter { fn validate(&self) -> eyre::Result<()> { self._in.validate() @@ -244,6 +540,22 @@ pub enum ParameterIn { }, } +impl JsonDeref for ParameterIn { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match self { + ParameterIn::Body { schema } => match head { + "schema" => schema.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + }, + ParameterIn::Path { param } + | ParameterIn::Query { param } + | ParameterIn::Header { param } + | ParameterIn::FormData { param } => param.deref_any(path), + } + } +} + impl ParameterIn { fn validate(&self) -> eyre::Result<()> { match self { @@ -292,6 +604,37 @@ pub struct NonBodyParameter { pub multiple_of: Option<u64>, } +impl JsonDeref for NonBodyParameter { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "required" => self.required.deref_any(tail), + "type" => self._type.deref_any(tail), + "format" => self.format.deref_any(tail), + "allow_empty_value" => self.allow_empty_value.deref_any(tail), + "items" => self.items.deref_any(tail), + "collection_format" => self.collection_format.deref_any(tail), + "default" => self.default.deref_any(tail), + "maximum" => self.maximum.deref_any(tail), + "exclusive_maximum" => self.exclusive_maximum.deref_any(tail), + "minimum" => self.minimum.deref_any(tail), + "exclusive_minimum" => self.exclusive_minimum.deref_any(tail), + "max_length" => self.max_length.deref_any(tail), + "min_length" => self.min_length.deref_any(tail), + "pattern" => self.pattern.deref_any(tail), // should be regex + "max_items" => self.max_items.deref_any(tail), + "min_items" => self.min_items.deref_any(tail), + "unique_items" => self.unique_items.deref_any(tail), + "enum" => self._enum.deref_any(tail), + "multiple_of" => self.multiple_of.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + } + } +} + impl NonBodyParameter { fn validate(&self) -> eyre::Result<()> { if self._type == ParameterType::Array { @@ -347,6 +690,16 @@ pub enum ParameterType { File, } +impl JsonDeref for ParameterType { + fn deref_any<'a>(&'a self, path: &str) -> eyre::Result<&'a dyn std::any::Any> { + if path.is_empty() { + Ok(self) + } else { + Err(eyre::eyre!("not found")) + } + } +} + impl ParameterType { fn matches_value(&self, value: &serde_json::Value) -> bool { match (self, value) { @@ -370,6 +723,16 @@ pub enum CollectionFormat { Multi, } +impl JsonDeref for CollectionFormat { + fn deref_any<'a>(&'a self, path: &str) -> eyre::Result<&'a dyn std::any::Any> { + if path.is_empty() { + Ok(self) + } else { + Err(eyre::eyre!("not found")) + } + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Items { @@ -394,6 +757,35 @@ pub struct Items { pub multiple_of: Option<u64>, } +impl JsonDeref for Items { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "type" => self._type.deref_any(tail), + "format" => self.format.deref_any(tail), + "items" => self.items.deref_any(tail), + "collection_format" => self.collection_format.deref_any(tail), + "default" => self.default.deref_any(tail), + "maximum" => self.maximum.deref_any(tail), + "exclusive_maximum" => self.exclusive_maximum.deref_any(tail), + "minimum" => self.minimum.deref_any(tail), + "exclusive_minimum" => self.exclusive_minimum.deref_any(tail), + "max_length" => self.max_length.deref_any(tail), + "min_length" => self.min_length.deref_any(tail), + "pattern" => self.pattern.deref_any(tail), // should be regex + "max_items" => self.max_items.deref_any(tail), + "min_items" => self.min_items.deref_any(tail), + "unique_items" => self.unique_items.deref_any(tail), + "enum" => self._enum.deref_any(tail), + "multiple_of" => self.multiple_of.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + } + } +} + impl Items { fn validate(&self) -> eyre::Result<()> { if self._type == ParameterType::Array { @@ -451,6 +843,23 @@ pub struct Responses { pub http_codes: BTreeMap<String, MaybeRef<Response>>, } +impl JsonDeref for Responses { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "default" => self.default.deref_any(tail), + code => self + .http_codes + .get(code) + .map(|r| r as _) + .ok_or_else(|| eyre::eyre!("not found")), + } + } +} + impl Responses { fn validate(&self) -> eyre::Result<()> { if self.default.is_none() && self.http_codes.is_empty() { @@ -482,6 +891,22 @@ pub struct Response { pub examples: Option<BTreeMap<String, serde_json::Value>>, } +impl JsonDeref for Response { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "description" => self.description.deref_any(tail), + "schema" => self.schema.deref_any(tail), + "headers" => self.headers.deref_any(tail), + "examples" => self.examples.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + } + } +} + impl Response { fn validate(&self) -> eyre::Result<()> { if let Some(headers) = &self.headers { @@ -521,6 +946,36 @@ pub struct Header { pub multiple_of: Option<u64>, } +impl JsonDeref for Header { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "description" => self.description.deref_any(tail), + "type" => self._type.deref_any(tail), + "format" => self.format.deref_any(tail), + "items" => self.items.deref_any(tail), + "collection_format" => self.collection_format.deref_any(tail), + "default" => self.default.deref_any(tail), + "maximum" => self.maximum.deref_any(tail), + "exclusive_maximum" => self.exclusive_maximum.deref_any(tail), + "minimum" => self.minimum.deref_any(tail), + "exclusive_minimum" => self.exclusive_minimum.deref_any(tail), + "max_length" => self.max_length.deref_any(tail), + "min_length" => self.min_length.deref_any(tail), + "pattern" => self.pattern.deref_any(tail), // should be regex + "max_items" => self.max_items.deref_any(tail), + "min_items" => self.min_items.deref_any(tail), + "unique_items" => self.unique_items.deref_any(tail), + "enum" => self._enum.deref_any(tail), + "multiple_of" => self.multiple_of.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + } + } +} + impl Header { fn validate(&self) -> eyre::Result<()> { if self._type == ParameterType::Array { @@ -575,6 +1030,21 @@ pub struct Tag { pub external_docs: Option<ExternalDocs>, } +impl JsonDeref for Tag { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "name" => self.name.deref_any(tail), + "description" => self.description.deref_any(tail), + "external_docs" => self.external_docs.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + } + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct Schema { @@ -611,6 +1081,46 @@ pub struct Schema { pub example: Option<serde_json::Value>, } +impl JsonDeref for Schema { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "format" => self.format.deref_any(tail), + "title" => self.title.deref_any(tail), + "description" => self.description.deref_any(tail), + "default" => self.default.deref_any(tail), + "multiple_of" => self.multiple_of.deref_any(tail), + "maximum" => self.maximum.deref_any(tail), + "exclusive_maximum" => self.exclusive_maximum.deref_any(tail), + "minimum" => self.minimum.deref_any(tail), + "exclusive_minimum" => self.exclusive_minimum.deref_any(tail), + "max_length" => self.max_length.deref_any(tail), + "min_length" => self.min_length.deref_any(tail), + "pattern" => self.pattern.deref_any(tail), // should be regex + "max_items" => self.max_items.deref_any(tail), + "min_items" => self.min_items.deref_any(tail), + "unique_items" => self.unique_items.deref_any(tail), + "max_properties" => self.max_properties.deref_any(tail), + "min_properties" => self.min_properties.deref_any(tail), + "required" => self.required.deref_any(tail), + "enum" => self._enum.deref_any(tail), + "type" => self._type.deref_any(tail), + "properties" => self.properties.deref_any(tail), + "additional_properties" => self.additional_properties.deref_any(tail), + "items" => self.items.deref_any(tail), + "discriminator" => self.discriminator.deref_any(tail), + "read_only" => self.read_only.deref_any(tail), + "xml" => self.xml.deref_any(tail), + "external_docs" => self.external_docs.deref_any(tail), + "example" => self.example.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + } + } +} + impl Schema { fn validate(&self) -> eyre::Result<()> { if let Some(_type) = &self._type { @@ -701,6 +1211,15 @@ pub enum SchemaType { List(Vec<Primitive>), } +impl JsonDeref for SchemaType { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + match self { + SchemaType::One(i) => i.deref_any(path), + SchemaType::List(list) => list.deref_any(path), + } + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub enum Primitive { @@ -713,6 +1232,16 @@ pub enum Primitive { String, } +impl JsonDeref for Primitive { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + Ok(self) + } else { + Err(eyre::eyre!("not found")) + } + } +} + impl Primitive { fn matches_value(&self, value: &serde_json::Value) -> bool { match (self, value) { @@ -736,6 +1265,23 @@ pub struct Xml { pub wrapped: Option<bool>, } +impl JsonDeref for Xml { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "name" => self.name.deref_any(tail), + "namespace" => self.namespace.deref_any(tail), + "prefix" => self.prefix.deref_any(tail), + "attribute" => self.attribute.deref_any(tail), + "wrapped" => self.wrapped.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + } + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub struct SecurityScheme { @@ -744,6 +1290,20 @@ pub struct SecurityScheme { pub description: Option<String>, } +impl JsonDeref for SecurityScheme { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match head { + "type" => self._type.deref_any(tail), + "description" => self.description.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + } + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"), tag = "type")] pub enum SecurityType { @@ -760,6 +1320,28 @@ pub enum SecurityType { }, } +impl JsonDeref for SecurityType { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match self { + SecurityType::Basic => eyre::bail!("not found: {head}"), + SecurityType::ApiKey { name, _in } => match head { + "name" => name.deref_any(tail), + "in" => _in.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + }, + SecurityType::OAuth2 { flow, scopes } => match head { + "flow" => flow.deref_any(tail), + "scopes" => scopes.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + }, + } + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"))] pub enum KeyIn { @@ -767,6 +1349,16 @@ pub enum KeyIn { Header, } +impl JsonDeref for KeyIn { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + Ok(self) + } else { + eyre::bail!("not found") + } + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(rename_all(deserialize = "camelCase"), tag = "flow")] pub enum OAuth2Flow { @@ -785,6 +1377,37 @@ pub enum OAuth2Flow { }, } +impl JsonDeref for OAuth2Flow { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match self { + OAuth2Flow::Implicit { authorization_url } => match head { + "authorizationUrl" => authorization_url.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + }, + OAuth2Flow::Password { token_url } => match head { + "tokenUrl" => token_url.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + }, + OAuth2Flow::Application { token_url } => match head { + "tokenUrl" => token_url.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + }, + OAuth2Flow::AccessCode { + authorization_url, + token_url, + } => match head { + "authorizationUrl" => authorization_url.deref_any(tail), + "tokenUrl" => token_url.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + }, + } + } +} + #[derive(serde::Deserialize, Debug, PartialEq)] #[serde(untagged)] pub enum MaybeRef<T> { @@ -797,3 +1420,28 @@ pub enum MaybeRef<T> { value: T, }, } + +impl<T: JsonDeref> JsonDeref for MaybeRef<T> { + fn deref_any(&self, path: &str) -> eyre::Result<&dyn std::any::Any> { + if path.is_empty() { + return Ok(self); + } + let (head, tail) = path.split_once("/").unwrap_or((path, "")); + match self { + MaybeRef::Ref { _ref } => match head { + "$ref" => _ref.deref_any(tail), + _ => eyre::bail!("not found: {head}"), + }, + MaybeRef::Value { value } => value.deref_any(path), + } + } +} + +impl<T: std::any::Any> MaybeRef<T> { + pub fn deref<'a>(&'a self, spec: &'a OpenApiV2) -> eyre::Result<&'a T> { + match self { + MaybeRef::Ref { _ref } => spec.deref(_ref), + MaybeRef::Value { value } => Ok(value), + } + } +} |