summaryrefslogtreecommitdiffstats
path: root/generator
diff options
context:
space:
mode:
authorCyborus <cyborus@cyborus.xyz>2024-01-30 22:45:53 +0100
committerCyborus <cyborus@cyborus.xyz>2024-01-30 22:45:53 +0100
commit311e17e3ba978c8f5bbb87b5fd598929f80c3e25 (patch)
tree1c447fa7bb8c356859a031ff387f14cea9ed509f /generator
parentformat (diff)
downloadforgejo-api-311e17e3ba978c8f5bbb87b5fd598929f80c3e25.tar.xz
forgejo-api-311e17e3ba978c8f5bbb87b5fd598929f80c3e25.zip
more general dereferencing
Diffstat (limited to 'generator')
-rw-r--r--generator/src/main.rs30
-rw-r--r--generator/src/methods.rs30
-rw-r--r--generator/src/openapi.rs648
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 &param {
- 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(&param, 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),
+ }
+ }
+}