a variety of improvements to support omicron clients (#12)
- add the start of a client support crate - add support for pre/post request hooks with consumer-specific data - suggest type names for parameter and response types in case those types are unnamed - handle more reference types by resolving them properly - improve optional parameter generation
This commit is contained in:
parent
97857c347c
commit
7934be90b9
|
@ -609,9 +609,9 @@ checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.30"
|
version = "1.0.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70"
|
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
@ -622,21 +622,27 @@ version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"getopts",
|
"getopts",
|
||||||
"indexmap",
|
|
||||||
"openapiv3",
|
"openapiv3",
|
||||||
"progenitor-impl",
|
"progenitor-impl",
|
||||||
"progenitor-macro",
|
"progenitor-macro",
|
||||||
"regex",
|
|
||||||
"rustfmt-wrapper",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "progenitor-client"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"reqwest",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "progenitor-impl"
|
name = "progenitor-impl"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"convert_case",
|
||||||
"expectorate",
|
"expectorate",
|
||||||
"getopts",
|
"getopts",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
|
@ -657,6 +663,7 @@ name = "progenitor-macro"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"openapiv3",
|
"openapiv3",
|
||||||
|
"proc-macro2",
|
||||||
"progenitor-impl",
|
"progenitor-impl",
|
||||||
"quote",
|
"quote",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -749,9 +756,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.11.5"
|
version = "0.11.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51c732d463dd300362ffb44b7b125f299c23d2990411a4253824630ebc7467fb"
|
checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -1123,7 +1130,7 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typify"
|
name = "typify"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
source = "git+https://github.com/oxidecomputer/typify#3cf8c1bb87b29cec4615d124362fb8f0872ffebd"
|
source = "git+https://github.com/oxidecomputer/typify#6de8074425c1c0090a85efc6115dfa4605d123e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typify-impl",
|
"typify-impl",
|
||||||
"typify-macro",
|
"typify-macro",
|
||||||
|
@ -1132,7 +1139,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typify-impl"
|
name = "typify-impl"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
source = "git+https://github.com/oxidecomputer/typify#3cf8c1bb87b29cec4615d124362fb8f0872ffebd"
|
source = "git+https://github.com/oxidecomputer/typify#6de8074425c1c0090a85efc6115dfa4605d123e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case",
|
"convert_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
@ -1146,7 +1153,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typify-macro"
|
name = "typify-macro"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
source = "git+https://github.com/oxidecomputer/typify#3cf8c1bb87b29cec4615d124362fb8f0872ffebd"
|
source = "git+https://github.com/oxidecomputer/typify#6de8074425c1c0090a85efc6115dfa4605d123e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"progenitor",
|
"progenitor",
|
||||||
"progenitor-macro",
|
"progenitor-client",
|
||||||
"progenitor-impl",
|
"progenitor-impl",
|
||||||
|
"progenitor-macro",
|
||||||
"example-build",
|
"example-build",
|
||||||
"example-macro",
|
"example-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
default-members = [
|
default-members = [
|
||||||
"progenitor",
|
"progenitor",
|
||||||
"progenitor-macro",
|
"progenitor-client",
|
||||||
"progenitor-impl",
|
"progenitor-impl",
|
||||||
|
"progenitor-macro",
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,13 +5,13 @@ authors = ["Adam H. Leventhal <ahl@oxidecomputer.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.44"
|
anyhow = "1.0"
|
||||||
percent-encoding = "2.1.0"
|
percent-encoding = "2.1"
|
||||||
serde = { version = "1.0.130", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
reqwest = { version = "0.11.5", features = ["json", "stream"] }
|
reqwest = { version = "0.11", features = ["json", "stream"] }
|
||||||
uuid = { version = "0.8.2", features = ["serde", "v4"] }
|
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
progenitor = { path = "../progenitor" }
|
progenitor = { path = "../progenitor" }
|
||||||
serde_json = "1.0.68"
|
serde_json = "1.0"
|
||||||
|
|
|
@ -6,9 +6,9 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
progenitor = { path = "../progenitor" }
|
progenitor = { path = "../progenitor" }
|
||||||
anyhow = "1.0.44"
|
anyhow = "1.0"
|
||||||
percent-encoding = "2.1.0"
|
percent-encoding = "2.1"
|
||||||
serde = { version = "1.0.130", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
reqwest = { version = "0.11.5", features = ["json", "stream"] }
|
reqwest = { version = "0.11", features = ["json", "stream"] }
|
||||||
uuid = { version = "0.8.2", features = ["serde", "v4"] }
|
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|
|
@ -2,6 +2,15 @@
|
||||||
|
|
||||||
use progenitor::generate_api;
|
use progenitor::generate_api;
|
||||||
|
|
||||||
generate_api!("../sample_openapi/keeper.json");
|
generate_api!(
|
||||||
|
"../sample_openapi/keeper.json",
|
||||||
|
(),
|
||||||
|
|_, request| {
|
||||||
|
println!("doing this {:?}", request);
|
||||||
|
},
|
||||||
|
crate::all_done
|
||||||
|
);
|
||||||
|
|
||||||
|
fn all_done(_: &(), _result: &reqwest::Result<reqwest::Response>) {}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "progenitor-client"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2018"
|
||||||
|
license = "MPL-2.0"
|
||||||
|
repository = "https://github.com/oxidecomputer/progenitor.git"
|
||||||
|
description = "An OpenAPI client generator - client support"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
reqwest = "0.11"
|
||||||
|
serde_json = "1.0"
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright 2021 Oxide Computer Company
|
||||||
|
|
||||||
|
//! Support code for generated clients.
|
||||||
|
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
/// Error produced by generated client methods.
|
||||||
|
pub enum Error<E> {
|
||||||
|
/// Indicates an error from the server, with the data, or with the
|
||||||
|
/// connection.
|
||||||
|
CommunicationError(reqwest::Error),
|
||||||
|
|
||||||
|
/// A documented error response.
|
||||||
|
ErrorResponse(ResponseValue<E>),
|
||||||
|
|
||||||
|
/// A response not listed in the API description. This may represent a
|
||||||
|
/// success or failure response; check `status()::is_success()`.
|
||||||
|
UnexpectedResponse(reqwest::Response),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ResponseValue<T> {
|
||||||
|
inner: T,
|
||||||
|
response: reqwest::Response,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ResponseValue<T> {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn new(inner: T, response: reqwest::Response) -> Self {
|
||||||
|
Self { inner, response }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request(&self) -> &reqwest::Response {
|
||||||
|
&self.response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for ResponseValue<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,19 +7,20 @@ repository = "https://github.com/oxidecomputer/progenitor.git"
|
||||||
description = "An OpenAPI client generator - core implementation"
|
description = "An OpenAPI client generator - core implementation"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1.0"
|
||||||
getopts = "0.2"
|
getopts = "0.2"
|
||||||
indexmap = "1.7.0"
|
indexmap = "1.7"
|
||||||
openapiv3 = "1.0.0-beta.2"
|
openapiv3 = "1.0.0-beta.2"
|
||||||
proc-macro2 = "1.0.29"
|
proc-macro2 = "1.0"
|
||||||
quote = "1.0.9"
|
quote = "1.0"
|
||||||
regex = "1.5.4"
|
regex = "1.5"
|
||||||
rustfmt-wrapper = "0.1.0"
|
rustfmt-wrapper = "0.1"
|
||||||
schemars = "0.8.5"
|
schemars = "0.8"
|
||||||
serde = { version = "1", features = [ "derive" ] }
|
serde = { version = "1.0", features = [ "derive" ] }
|
||||||
serde_json = "1.0.68"
|
serde_json = "1.0"
|
||||||
|
convert_case = "0.4"
|
||||||
typify = { git = "https://github.com/oxidecomputer/typify" }
|
typify = { git = "https://github.com/oxidecomputer/typify" }
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
expectorate = "1.0.4"
|
expectorate = "1.0"
|
||||||
|
|
|
@ -2,7 +2,11 @@
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use openapiv3::{OpenAPI, ReferenceOr};
|
use convert_case::{Case, Casing};
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use openapiv3::{
|
||||||
|
Components, OpenAPI, Parameter, ReferenceOr, RequestBody, Response, Schema,
|
||||||
|
};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
|
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
|
@ -33,6 +37,9 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Generator {
|
pub struct Generator {
|
||||||
type_space: TypeSpace,
|
type_space: TypeSpace,
|
||||||
|
inner_type: Option<TokenStream>,
|
||||||
|
pre_hook: Option<TokenStream>,
|
||||||
|
post_hook: Option<TokenStream>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Generator {
|
impl Generator {
|
||||||
|
@ -40,6 +47,21 @@ impl Generator {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_inner_type(&mut self, inner_type: TokenStream) -> &mut Self {
|
||||||
|
self.inner_type = Some(inner_type);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_pre_hook(&mut self, pre_hook: TokenStream) -> &mut Self {
|
||||||
|
self.pre_hook = Some(pre_hook);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_post_hook(&mut self, post_hook: TokenStream) -> &mut Self {
|
||||||
|
self.post_hook = Some(post_hook);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn generate_tokens(&mut self, spec: &OpenAPI) -> Result<TokenStream> {
|
pub fn generate_tokens(&mut self, spec: &OpenAPI) -> Result<TokenStream> {
|
||||||
// Convert our components dictionary to schemars
|
// Convert our components dictionary to schemars
|
||||||
let schemas = spec
|
let schemas = spec
|
||||||
|
@ -55,283 +77,23 @@ impl Generator {
|
||||||
self.type_space.set_type_mod("types");
|
self.type_space.set_type_mod("types");
|
||||||
self.type_space.add_ref_types(schemas)?;
|
self.type_space.add_ref_types(schemas)?;
|
||||||
|
|
||||||
enum ParamType {
|
|
||||||
Path,
|
|
||||||
Query,
|
|
||||||
Body,
|
|
||||||
}
|
|
||||||
|
|
||||||
let methods = spec
|
let methods = spec
|
||||||
.operations()
|
.paths
|
||||||
.map(|(path, method, operation)| {
|
|
||||||
let mut query: Vec<(String, bool)> = Vec::new();
|
|
||||||
let mut raw_params = operation
|
|
||||||
.parameters
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|parameter| {
|
.flat_map(|(path, ref_or_item)| {
|
||||||
match parameter.item()? {
|
let item = ref_or_item.as_item().unwrap();
|
||||||
openapiv3::Parameter::Path {
|
assert!(item.parameters.is_empty());
|
||||||
parameter_data,
|
item.iter().map(move |(method, operation)| {
|
||||||
style: openapiv3::PathStyle::Simple,
|
(path.as_str(), method, operation)
|
||||||
} => {
|
|
||||||
// Path parameters MUST be required.
|
|
||||||
assert!(parameter_data.required);
|
|
||||||
|
|
||||||
let nam = parameter_data.name.clone();
|
|
||||||
let schema =
|
|
||||||
parameter_data.schema()?.to_schema();
|
|
||||||
let typ = self
|
|
||||||
.type_space
|
|
||||||
.add_type_details(&schema)?
|
|
||||||
.parameter;
|
|
||||||
|
|
||||||
Ok((ParamType::Path, nam, typ))
|
|
||||||
}
|
|
||||||
openapiv3::Parameter::Query {
|
|
||||||
parameter_data,
|
|
||||||
allow_reserved: _,
|
|
||||||
style: openapiv3::QueryStyle::Form,
|
|
||||||
allow_empty_value,
|
|
||||||
} => {
|
|
||||||
if let Some(aev) = allow_empty_value {
|
|
||||||
if *aev {
|
|
||||||
todo!("allow empty value is a no go");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let nam = parameter_data.name.clone();
|
|
||||||
let schema =
|
|
||||||
parameter_data.schema()?.to_schema();
|
|
||||||
let mut typ = self
|
|
||||||
.type_space
|
|
||||||
.add_type_details(&schema)?
|
|
||||||
.parameter;
|
|
||||||
if !parameter_data.required {
|
|
||||||
typ = quote! { Option<#typ> };
|
|
||||||
}
|
|
||||||
query.push((
|
|
||||||
nam.to_string(),
|
|
||||||
!parameter_data.required,
|
|
||||||
));
|
|
||||||
Ok((ParamType::Query, nam, typ))
|
|
||||||
}
|
|
||||||
x => todo!("unhandled parameter type: {:#?}", x),
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
})
|
||||||
|
.map(|(path, method, operation)| {
|
||||||
let mut bounds = Vec::new();
|
self.process_operation(
|
||||||
|
operation,
|
||||||
let (body_param, body_func) = if let Some(b) =
|
&spec.components,
|
||||||
&operation.request_body
|
path,
|
||||||
{
|
method,
|
||||||
let b = b.item()?;
|
)
|
||||||
if b.is_binary()? {
|
|
||||||
bounds.push(quote! {B: Into<reqwest::Body>});
|
|
||||||
(Some(quote! {B}), Some(quote! { .body(body) }))
|
|
||||||
} else {
|
|
||||||
let mt = b.content_json()?;
|
|
||||||
if !mt.encoding.is_empty() {
|
|
||||||
todo!("media type encoding not empty: {:#?}", mt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(s) = &mt.schema {
|
|
||||||
let schema = s.to_schema();
|
|
||||||
let typ = self
|
|
||||||
.type_space
|
|
||||||
.add_type_details(&schema)?
|
|
||||||
.parameter;
|
|
||||||
(Some(typ), Some(quote! { .json(body) }))
|
|
||||||
} else {
|
|
||||||
todo!("media type encoding, no schema: {:#?}", mt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(None, None)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(body) = body_param {
|
|
||||||
raw_params.push((
|
|
||||||
ParamType::Body,
|
|
||||||
"body".to_string(),
|
|
||||||
body,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let tmp = template::parse(path)?;
|
|
||||||
let names = tmp.names();
|
|
||||||
let url_path = tmp.compile();
|
|
||||||
|
|
||||||
// Put parameters in a deterministic order.
|
|
||||||
raw_params.sort_by(|a, b| match (&a.0, &b.0) {
|
|
||||||
// Path params are first and are in positional order.
|
|
||||||
(ParamType::Path, ParamType::Path) => {
|
|
||||||
let aa = names.iter().position(|x| x == &a.1).unwrap();
|
|
||||||
let bb = names.iter().position(|x| x == &b.1).unwrap();
|
|
||||||
aa.cmp(&bb)
|
|
||||||
}
|
|
||||||
(ParamType::Path, ParamType::Query) => Ordering::Less,
|
|
||||||
(ParamType::Path, ParamType::Body) => Ordering::Less,
|
|
||||||
|
|
||||||
// Query params are in lexicographic order.
|
|
||||||
(ParamType::Query, ParamType::Body) => Ordering::Less,
|
|
||||||
(ParamType::Query, ParamType::Query) => a.1.cmp(&b.1),
|
|
||||||
(ParamType::Query, ParamType::Path) => Ordering::Greater,
|
|
||||||
|
|
||||||
// Body params are last and should be unique
|
|
||||||
(ParamType::Body, ParamType::Path) => Ordering::Greater,
|
|
||||||
(ParamType::Body, ParamType::Query) => Ordering::Greater,
|
|
||||||
(ParamType::Body, ParamType::Body) => {
|
|
||||||
panic!("should only be one body")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let (response_type, decode_response) = if operation
|
|
||||||
.responses
|
|
||||||
.responses
|
|
||||||
.len()
|
|
||||||
== 1
|
|
||||||
{
|
|
||||||
let only = operation.responses.responses.first().unwrap();
|
|
||||||
if !matches!(only.0, openapiv3::StatusCode::Code(200..=299))
|
|
||||||
{
|
|
||||||
todo!("code? {:#?}", only);
|
|
||||||
}
|
|
||||||
|
|
||||||
let i = only.1.item()?;
|
|
||||||
if !i.headers.is_empty() {
|
|
||||||
todo!("no response headers for now");
|
|
||||||
}
|
|
||||||
|
|
||||||
if !i.links.is_empty() {
|
|
||||||
todo!("no response links for now");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look at the response content. For now, support a
|
|
||||||
// single JSON-formatted response.
|
|
||||||
let typ = match (
|
|
||||||
i.content.len(),
|
|
||||||
i.content.get("application/json"),
|
|
||||||
) {
|
|
||||||
(0, _) => quote! { () },
|
|
||||||
(1, Some(mt)) => {
|
|
||||||
if !mt.encoding.is_empty() {
|
|
||||||
todo!(
|
|
||||||
"media type encoding not empty: {:#?}",
|
|
||||||
mt
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(schema) = &mt.schema {
|
|
||||||
let schema = schema.to_schema();
|
|
||||||
self.type_space.add_type_details(&schema)?.ident
|
|
||||||
} else {
|
|
||||||
todo!(
|
|
||||||
"media type encoding, no schema: {:#?}",
|
|
||||||
mt
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(1, None) => {
|
|
||||||
todo!(
|
|
||||||
"response content not JSON: {:#?}",
|
|
||||||
i.content
|
|
||||||
);
|
|
||||||
}
|
|
||||||
(_, _) => {
|
|
||||||
todo!(
|
|
||||||
"too many response contents: {:#?}",
|
|
||||||
i.content
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
(typ, quote! { res.json().await? })
|
|
||||||
} else if operation.responses.responses.is_empty() {
|
|
||||||
(quote! { reqwest::Response }, quote! { res })
|
|
||||||
} else {
|
|
||||||
todo!("responses? {:#?}", operation.responses);
|
|
||||||
};
|
|
||||||
|
|
||||||
let operation_id = format_ident!(
|
|
||||||
"{}",
|
|
||||||
operation.operation_id.as_deref().unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
let bounds = if bounds.is_empty() {
|
|
||||||
quote! {}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
< #(#bounds),* >
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let params = raw_params.into_iter().map(|(_, name, typ)| {
|
|
||||||
let name = format_ident!("{}", name);
|
|
||||||
quote! {
|
|
||||||
#name: #typ
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let (query_build, query_use) = if query.is_empty() {
|
|
||||||
(quote! {}, quote! {})
|
|
||||||
} else {
|
|
||||||
let query_items = query.iter().map(|(qn, opt)| {
|
|
||||||
if *opt {
|
|
||||||
let qn_ident = format_ident!("{}", qn);
|
|
||||||
quote! {
|
|
||||||
if let Some(v) = & #qn_ident {
|
|
||||||
query.push((#qn, v.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
query.push((#qn, #qn.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let query_build = quote! {
|
|
||||||
let mut query = Vec::new();
|
|
||||||
#(#query_items)*
|
|
||||||
};
|
|
||||||
let query_use = quote! {
|
|
||||||
.query(&query)
|
|
||||||
};
|
|
||||||
|
|
||||||
(query_build, query_use)
|
|
||||||
};
|
|
||||||
|
|
||||||
let doc_comment = format!(
|
|
||||||
"{}: {} {}",
|
|
||||||
operation.operation_id.as_deref().unwrap(),
|
|
||||||
method.to_ascii_uppercase(),
|
|
||||||
path
|
|
||||||
);
|
|
||||||
|
|
||||||
let method_func = format_ident!("{}", method);
|
|
||||||
|
|
||||||
let method = quote! {
|
|
||||||
#[doc = #doc_comment]
|
|
||||||
pub async fn #operation_id #bounds (
|
|
||||||
&self,
|
|
||||||
#(#params),*
|
|
||||||
) -> Result<#response_type> {
|
|
||||||
#url_path
|
|
||||||
#query_build
|
|
||||||
|
|
||||||
let res = self.client
|
|
||||||
. #method_func (url)
|
|
||||||
#body_func
|
|
||||||
#query_use
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
|
|
||||||
Ok(#decode_response)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(method)
|
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
@ -345,9 +107,20 @@ impl Generator {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
types.sort_by(|a, b| a.0.cmp(&b.0));
|
types.sort_by(|(a_name, _), (b_name, _)| a_name.cmp(b_name));
|
||||||
let types = types.into_iter().map(|(_, def)| def);
|
let types = types.into_iter().map(|(_, def)| def);
|
||||||
|
|
||||||
|
let inner_property = self.inner_type.as_ref().map(|inner| {
|
||||||
|
quote! {
|
||||||
|
inner: #inner,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let inner_value = self.inner_type.as_ref().map(|_| {
|
||||||
|
quote! {
|
||||||
|
inner
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let file = quote! {
|
let file = quote! {
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
@ -381,27 +154,32 @@ impl Generator {
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
baseurl: String,
|
baseurl: String,
|
||||||
client: reqwest::Client,
|
client: reqwest::Client,
|
||||||
|
#inner_property
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub fn new(baseurl: &str) -> Client {
|
pub fn new(
|
||||||
|
baseurl: &str,
|
||||||
|
#inner_property
|
||||||
|
) -> Self {
|
||||||
let dur = std::time::Duration::from_secs(15);
|
let dur = std::time::Duration::from_secs(15);
|
||||||
let client = reqwest::ClientBuilder::new()
|
let client = reqwest::ClientBuilder::new()
|
||||||
.connect_timeout(dur)
|
.connect_timeout(dur)
|
||||||
.timeout(dur)
|
.timeout(dur)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
Self::new_with_client(baseurl, client, #inner_value)
|
||||||
Client::new_with_client(baseurl, client)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_client(
|
pub fn new_with_client(
|
||||||
baseurl: &str,
|
baseurl: &str,
|
||||||
client: reqwest::Client,
|
client: reqwest::Client,
|
||||||
) -> Client {
|
#inner_property
|
||||||
Client {
|
) -> Self {
|
||||||
|
Self {
|
||||||
baseurl: baseurl.to_string(),
|
baseurl: baseurl.to_string(),
|
||||||
client,
|
client,
|
||||||
|
#inner_value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,6 +190,314 @@ impl Generator {
|
||||||
Ok(file)
|
Ok(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process_operation(
|
||||||
|
&mut self,
|
||||||
|
operation: &openapiv3::Operation,
|
||||||
|
components: &Option<Components>,
|
||||||
|
path: &str,
|
||||||
|
method: &str,
|
||||||
|
) -> Result<TokenStream> {
|
||||||
|
enum ParamType {
|
||||||
|
Path,
|
||||||
|
Query,
|
||||||
|
Body,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut query: Vec<(String, bool)> = Vec::new();
|
||||||
|
let mut raw_params = operation
|
||||||
|
.parameters
|
||||||
|
.iter()
|
||||||
|
.map(|parameter| {
|
||||||
|
match parameter.item(components)? {
|
||||||
|
openapiv3::Parameter::Path {
|
||||||
|
parameter_data,
|
||||||
|
style: openapiv3::PathStyle::Simple,
|
||||||
|
} => {
|
||||||
|
// Path parameters MUST be required.
|
||||||
|
assert!(parameter_data.required);
|
||||||
|
|
||||||
|
let nam = parameter_data.name.clone();
|
||||||
|
let schema = parameter_data.schema()?.to_schema();
|
||||||
|
let name = format!(
|
||||||
|
"{}{}",
|
||||||
|
sanitize(
|
||||||
|
operation.operation_id.as_ref().unwrap(),
|
||||||
|
Case::Pascal
|
||||||
|
),
|
||||||
|
sanitize(&nam, Case::Pascal),
|
||||||
|
);
|
||||||
|
let typ = self
|
||||||
|
.type_space
|
||||||
|
.add_type_details_with_name(&schema, Some(name))?
|
||||||
|
.parameter;
|
||||||
|
|
||||||
|
Ok((ParamType::Path, nam, typ))
|
||||||
|
}
|
||||||
|
openapiv3::Parameter::Query {
|
||||||
|
parameter_data,
|
||||||
|
allow_reserved: _,
|
||||||
|
style: openapiv3::QueryStyle::Form,
|
||||||
|
allow_empty_value,
|
||||||
|
} => {
|
||||||
|
if let Some(true) = allow_empty_value {
|
||||||
|
todo!("allow empty value is a no go");
|
||||||
|
}
|
||||||
|
|
||||||
|
let nam = parameter_data.name.clone();
|
||||||
|
let mut schema = parameter_data.schema()?.to_schema();
|
||||||
|
let name = format!(
|
||||||
|
"{}{}",
|
||||||
|
sanitize(
|
||||||
|
operation.operation_id.as_ref().unwrap(),
|
||||||
|
Case::Pascal
|
||||||
|
),
|
||||||
|
sanitize(&nam, Case::Pascal),
|
||||||
|
);
|
||||||
|
|
||||||
|
if !parameter_data.required {
|
||||||
|
schema = make_optional(schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
let typ = self
|
||||||
|
.type_space
|
||||||
|
.add_type_details_with_name(&schema, Some(name))?
|
||||||
|
.parameter;
|
||||||
|
|
||||||
|
query.push((nam.to_string(), !parameter_data.required));
|
||||||
|
Ok((ParamType::Query, nam, typ))
|
||||||
|
}
|
||||||
|
x => todo!("unhandled parameter type: {:#?}", x),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
let mut bounds = Vec::new();
|
||||||
|
let (body_param, body_func) = if let Some(b) = &operation.request_body {
|
||||||
|
let b = b.item(components)?;
|
||||||
|
if b.is_binary(components)? {
|
||||||
|
bounds.push(quote! {B: Into<reqwest::Body>});
|
||||||
|
(Some(quote! {B}), Some(quote! { .body(body) }))
|
||||||
|
} else {
|
||||||
|
let mt = b.content_json()?;
|
||||||
|
if !mt.encoding.is_empty() {
|
||||||
|
todo!("media type encoding not empty: {:#?}", mt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(s) = &mt.schema {
|
||||||
|
let schema = s.to_schema();
|
||||||
|
let name = format!(
|
||||||
|
"{}Body",
|
||||||
|
sanitize(
|
||||||
|
operation.operation_id.as_ref().unwrap(),
|
||||||
|
Case::Pascal
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let typ = self
|
||||||
|
.type_space
|
||||||
|
.add_type_details_with_name(&schema, Some(name))?
|
||||||
|
.parameter;
|
||||||
|
(Some(typ), Some(quote! { .json(body) }))
|
||||||
|
} else {
|
||||||
|
todo!("media type encoding, no schema: {:#?}", mt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(None, None)
|
||||||
|
};
|
||||||
|
if let Some(body) = body_param {
|
||||||
|
raw_params.push((ParamType::Body, "body".to_string(), body));
|
||||||
|
}
|
||||||
|
let tmp = template::parse(path)?;
|
||||||
|
let names = tmp.names();
|
||||||
|
let url_path = tmp.compile();
|
||||||
|
raw_params.sort_by(|a, b| match (&a.0, &b.0) {
|
||||||
|
// Path params are first and are in positional order.
|
||||||
|
(ParamType::Path, ParamType::Path) => {
|
||||||
|
let aa = names.iter().position(|x| x == &a.1).unwrap();
|
||||||
|
let bb = names.iter().position(|x| x == &b.1).unwrap();
|
||||||
|
aa.cmp(&bb)
|
||||||
|
}
|
||||||
|
(ParamType::Path, ParamType::Query) => Ordering::Less,
|
||||||
|
(ParamType::Path, ParamType::Body) => Ordering::Less,
|
||||||
|
|
||||||
|
// Query params are in lexicographic order.
|
||||||
|
(ParamType::Query, ParamType::Body) => Ordering::Less,
|
||||||
|
(ParamType::Query, ParamType::Query) => a.1.cmp(&b.1),
|
||||||
|
(ParamType::Query, ParamType::Path) => Ordering::Greater,
|
||||||
|
|
||||||
|
// Body params are last and should be unique
|
||||||
|
(ParamType::Body, ParamType::Path) => Ordering::Greater,
|
||||||
|
(ParamType::Body, ParamType::Query) => Ordering::Greater,
|
||||||
|
(ParamType::Body, ParamType::Body) => {
|
||||||
|
panic!("should only be one body")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let (response_type, decode_response) =
|
||||||
|
// TODO let's consider how we handle multiple responses
|
||||||
|
if operation.responses.responses.len() >= 1 {
|
||||||
|
let only =
|
||||||
|
operation.responses.responses.first().unwrap();
|
||||||
|
if !matches!(
|
||||||
|
only.0,
|
||||||
|
openapiv3::StatusCode::Code(200..=299)
|
||||||
|
) {
|
||||||
|
todo!("code? {:#?}", only);
|
||||||
|
}
|
||||||
|
|
||||||
|
let i = only.1.item(components)?;
|
||||||
|
// TODO handle response headers.
|
||||||
|
|
||||||
|
// Look at the response content. For now, support a
|
||||||
|
// single JSON-formatted response.
|
||||||
|
match (
|
||||||
|
i.content.len(),
|
||||||
|
i.content.get("application/json"),
|
||||||
|
) {
|
||||||
|
(0, _) => (quote! { () }, quote! { res.json().await? }),
|
||||||
|
(1, Some(mt)) => {
|
||||||
|
if !mt.encoding.is_empty() {
|
||||||
|
todo!(
|
||||||
|
"media type encoding not empty: {:#?}",
|
||||||
|
mt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let typ = if let Some(schema) = &mt.schema {
|
||||||
|
let schema = schema.to_schema();
|
||||||
|
let name = format!(
|
||||||
|
"{}Response",
|
||||||
|
sanitize(
|
||||||
|
operation
|
||||||
|
.operation_id
|
||||||
|
.as_ref()
|
||||||
|
.unwrap(),
|
||||||
|
Case::Pascal
|
||||||
|
)
|
||||||
|
);
|
||||||
|
self.type_space
|
||||||
|
.add_type_details_with_name(
|
||||||
|
&schema,
|
||||||
|
Some(name),
|
||||||
|
)?
|
||||||
|
.ident
|
||||||
|
} else {
|
||||||
|
todo!(
|
||||||
|
"media type encoding, no schema: {:#?}",
|
||||||
|
mt
|
||||||
|
);
|
||||||
|
};
|
||||||
|
(typ, quote! { res.json().await? })
|
||||||
|
}
|
||||||
|
(1, None) => {
|
||||||
|
// Non-JSON response.
|
||||||
|
(quote! { reqwest::Response }, quote! { res })
|
||||||
|
}
|
||||||
|
(_, _) => {
|
||||||
|
todo!(
|
||||||
|
"too many response contents: {:#?}",
|
||||||
|
i.content
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if operation.responses.responses.is_empty() {
|
||||||
|
(quote! { reqwest::Response }, quote! { res })
|
||||||
|
} else {
|
||||||
|
todo!("responses? {:#?}", operation.responses);
|
||||||
|
};
|
||||||
|
let operation_id = format_ident!(
|
||||||
|
"{}",
|
||||||
|
sanitize(operation.operation_id.as_deref().unwrap(), Case::Snake)
|
||||||
|
);
|
||||||
|
let bounds = if bounds.is_empty() {
|
||||||
|
quote! {}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
< #(#bounds),* >
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let params = raw_params.into_iter().map(|(_, name, typ)| {
|
||||||
|
let name = format_ident!("{}", name);
|
||||||
|
quote! {
|
||||||
|
#name: #typ
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let (query_build, query_use) = if query.is_empty() {
|
||||||
|
(quote! {}, quote! {})
|
||||||
|
} else {
|
||||||
|
let query_items = query.iter().map(|(qn, opt)| {
|
||||||
|
if *opt {
|
||||||
|
let qn_ident = format_ident!("{}", qn);
|
||||||
|
quote! {
|
||||||
|
if let Some(v) = & #qn_ident {
|
||||||
|
query.push((#qn, v.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
query.push((#qn, #qn.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let query_build = quote! {
|
||||||
|
let mut query = Vec::new();
|
||||||
|
#(#query_items)*
|
||||||
|
};
|
||||||
|
let query_use = quote! {
|
||||||
|
.query(&query)
|
||||||
|
};
|
||||||
|
|
||||||
|
(query_build, query_use)
|
||||||
|
};
|
||||||
|
let doc_comment = format!(
|
||||||
|
"{}: {} {}",
|
||||||
|
operation.operation_id.as_deref().unwrap(),
|
||||||
|
method.to_ascii_uppercase(),
|
||||||
|
path
|
||||||
|
);
|
||||||
|
|
||||||
|
let pre_hook = self.pre_hook.as_ref().map(|hook| {
|
||||||
|
quote! {
|
||||||
|
(#hook)(&self.inner, &request);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let post_hook = self.post_hook.as_ref().map(|hook| {
|
||||||
|
quote! {
|
||||||
|
(#hook)(&self.inner, &result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO validate that method is one of the expected methods.
|
||||||
|
let method_func = format_ident!("{}", method.to_lowercase());
|
||||||
|
let method = quote! {
|
||||||
|
#[doc = #doc_comment]
|
||||||
|
pub async fn #operation_id #bounds (
|
||||||
|
&self,
|
||||||
|
#(#params),*
|
||||||
|
) -> Result<#response_type> {
|
||||||
|
#url_path
|
||||||
|
#query_build
|
||||||
|
|
||||||
|
let request = self.client
|
||||||
|
. #method_func (url)
|
||||||
|
#body_func
|
||||||
|
#query_use
|
||||||
|
.build()?;
|
||||||
|
#pre_hook
|
||||||
|
let result = self.client
|
||||||
|
.execute(request)
|
||||||
|
.await;
|
||||||
|
#post_hook
|
||||||
|
|
||||||
|
// TODO we should do a match here for result?.status().as_u16()
|
||||||
|
let res = result?.error_for_status()?;
|
||||||
|
|
||||||
|
Ok(#decode_response)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(method)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn generate_text(&mut self, spec: &OpenAPI) -> Result<String> {
|
pub fn generate_text(&mut self, spec: &OpenAPI) -> Result<String> {
|
||||||
let output = self.generate_tokens(spec)?;
|
let output = self.generate_tokens(spec)?;
|
||||||
|
|
||||||
|
@ -453,6 +539,44 @@ impl Generator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Make the schema optional if it isn't already.
|
||||||
|
pub fn make_optional(
|
||||||
|
schema: schemars::schema::Schema,
|
||||||
|
) -> schemars::schema::Schema {
|
||||||
|
match &schema {
|
||||||
|
// If the instance_type already includes Null then this is already
|
||||||
|
// optional.
|
||||||
|
schemars::schema::Schema::Object(schemars::schema::SchemaObject {
|
||||||
|
instance_type: Some(schemars::schema::SingleOrVec::Vec(types)),
|
||||||
|
..
|
||||||
|
}) if types.contains(&schemars::schema::InstanceType::Null) => schema,
|
||||||
|
|
||||||
|
// Otherwise, create a oneOf where one of the branches is the null
|
||||||
|
// type. We could potentially check to see if the schema already
|
||||||
|
// conforms to this pattern as well, but it doesn't hurt as typify will
|
||||||
|
// already reduce nested Options to a single Option.
|
||||||
|
_ => {
|
||||||
|
let null_schema = schemars::schema::Schema::Object(
|
||||||
|
schemars::schema::SchemaObject {
|
||||||
|
instance_type: Some(schemars::schema::SingleOrVec::Single(
|
||||||
|
Box::new(schemars::schema::InstanceType::Null),
|
||||||
|
)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
schemars::schema::Schema::Object(schemars::schema::SchemaObject {
|
||||||
|
subschemas: Some(Box::new(
|
||||||
|
schemars::schema::SubschemaValidation {
|
||||||
|
one_of: Some(vec![schema, null_schema]),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trait ParameterDataExt {
|
trait ParameterDataExt {
|
||||||
fn schema(&self) -> Result<&openapiv3::ReferenceOr<openapiv3::Schema>>;
|
fn schema(&self) -> Result<&openapiv3::ReferenceOr<openapiv3::Schema>>;
|
||||||
}
|
}
|
||||||
|
@ -469,7 +593,7 @@ impl ParameterDataExt for openapiv3::ParameterData {
|
||||||
}
|
}
|
||||||
|
|
||||||
trait ExtractJsonMediaType {
|
trait ExtractJsonMediaType {
|
||||||
fn is_binary(&self) -> Result<bool>;
|
fn is_binary(&self, components: &Option<Components>) -> Result<bool>;
|
||||||
fn content_json(&self) -> Result<openapiv3::MediaType>;
|
fn content_json(&self) -> Result<openapiv3::MediaType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -489,7 +613,7 @@ impl ExtractJsonMediaType for openapiv3::Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_binary(&self) -> Result<bool> {
|
fn is_binary(&self, _components: &Option<Components>) -> Result<bool> {
|
||||||
if self.content.is_empty() {
|
if self.content.is_empty() {
|
||||||
/*
|
/*
|
||||||
* XXX If there are no content types, I guess it is not binary?
|
* XXX If there are no content types, I guess it is not binary?
|
||||||
|
@ -512,7 +636,7 @@ impl ExtractJsonMediaType for openapiv3::Response {
|
||||||
VariantOrUnknownOrEmpty::Item,
|
VariantOrUnknownOrEmpty::Item,
|
||||||
};
|
};
|
||||||
|
|
||||||
let s = s.item()?;
|
let s = s.item(&None)?;
|
||||||
if s.schema_data.nullable {
|
if s.schema_data.nullable {
|
||||||
todo!("XXX nullable binary?");
|
todo!("XXX nullable binary?");
|
||||||
}
|
}
|
||||||
|
@ -570,7 +694,7 @@ impl ExtractJsonMediaType for openapiv3::RequestBody {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_binary(&self) -> Result<bool> {
|
fn is_binary(&self, components: &Option<Components>) -> Result<bool> {
|
||||||
if self.content.is_empty() {
|
if self.content.is_empty() {
|
||||||
/*
|
/*
|
||||||
* XXX If there are no content types, I guess it is not binary?
|
* XXX If there are no content types, I guess it is not binary?
|
||||||
|
@ -593,7 +717,7 @@ impl ExtractJsonMediaType for openapiv3::RequestBody {
|
||||||
VariantOrUnknownOrEmpty::Item,
|
VariantOrUnknownOrEmpty::Item,
|
||||||
};
|
};
|
||||||
|
|
||||||
let s = s.item()?;
|
let s = s.item(components)?;
|
||||||
if s.schema_data.nullable {
|
if s.schema_data.nullable {
|
||||||
todo!("XXX nullable binary?");
|
todo!("XXX nullable binary?");
|
||||||
}
|
}
|
||||||
|
@ -635,17 +759,62 @@ impl ExtractJsonMediaType for openapiv3::RequestBody {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait ReferenceOrExt<T> {
|
trait ReferenceOrExt<T: ComponentLookup> {
|
||||||
fn item(&self) -> Result<&T>;
|
fn item<'a>(&'a self, components: &'a Option<Components>) -> Result<&'a T>;
|
||||||
|
}
|
||||||
|
trait ComponentLookup: Sized {
|
||||||
|
fn get_components(
|
||||||
|
components: &Components,
|
||||||
|
) -> &IndexMap<String, ReferenceOr<Self>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ReferenceOrExt<T> for openapiv3::ReferenceOr<T> {
|
impl<T: ComponentLookup> ReferenceOrExt<T> for openapiv3::ReferenceOr<T> {
|
||||||
fn item(&self) -> Result<&T> {
|
fn item<'a>(&'a self, components: &'a Option<Components>) -> Result<&'a T> {
|
||||||
match self {
|
match self {
|
||||||
ReferenceOr::Reference { .. } => {
|
|
||||||
Err(Error::BadConversion("unexpected reference".to_string()))
|
|
||||||
}
|
|
||||||
ReferenceOr::Item(item) => Ok(item),
|
ReferenceOr::Item(item) => Ok(item),
|
||||||
|
ReferenceOr::Reference { reference } => {
|
||||||
|
let idx = reference.rfind('/').unwrap();
|
||||||
|
let key = &reference[idx + 1..];
|
||||||
|
let parameters =
|
||||||
|
T::get_components(components.as_ref().unwrap());
|
||||||
|
parameters.get(key).unwrap().item(components)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentLookup for Parameter {
|
||||||
|
fn get_components(
|
||||||
|
components: &Components,
|
||||||
|
) -> &IndexMap<String, ReferenceOr<Self>> {
|
||||||
|
&components.parameters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentLookup for RequestBody {
|
||||||
|
fn get_components(
|
||||||
|
components: &Components,
|
||||||
|
) -> &IndexMap<String, ReferenceOr<Self>> {
|
||||||
|
&components.request_bodies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentLookup for Response {
|
||||||
|
fn get_components(
|
||||||
|
components: &Components,
|
||||||
|
) -> &IndexMap<String, ReferenceOr<Self>> {
|
||||||
|
&components.responses
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentLookup for Schema {
|
||||||
|
fn get_components(
|
||||||
|
components: &Components,
|
||||||
|
) -> &IndexMap<String, ReferenceOr<Self>> {
|
||||||
|
&components.schemas
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sanitize(input: &str, case: Case) -> String {
|
||||||
|
input.replace('/', "-").to_case(case)
|
||||||
|
}
|
||||||
|
|
|
@ -156,18 +156,18 @@ pub struct Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub fn new(baseurl: &str) -> Client {
|
pub fn new(baseurl: &str) -> Self {
|
||||||
let dur = std::time::Duration::from_secs(15);
|
let dur = std::time::Duration::from_secs(15);
|
||||||
let client = reqwest::ClientBuilder::new()
|
let client = reqwest::ClientBuilder::new()
|
||||||
.connect_timeout(dur)
|
.connect_timeout(dur)
|
||||||
.timeout(dur)
|
.timeout(dur)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Client::new_with_client(baseurl, client)
|
Self::new_with_client(baseurl, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Client {
|
pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self {
|
||||||
Client {
|
Self {
|
||||||
baseurl: baseurl.to_string(),
|
baseurl: baseurl.to_string(),
|
||||||
client,
|
client,
|
||||||
}
|
}
|
||||||
|
@ -176,14 +176,18 @@ impl Client {
|
||||||
#[doc = "control_hold: POST /v1/control/hold"]
|
#[doc = "control_hold: POST /v1/control/hold"]
|
||||||
pub async fn control_hold(&self) -> Result<()> {
|
pub async fn control_hold(&self) -> Result<()> {
|
||||||
let url = format!("{}/v1/control/hold", self.baseurl,);
|
let url = format!("{}/v1/control/hold", self.baseurl,);
|
||||||
let res = self.client.post(url).send().await?.error_for_status()?;
|
let request = self.client.post(url).build()?;
|
||||||
|
let result = self.client.execute(request).await;
|
||||||
|
let res = result?.error_for_status()?;
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "control_resume: POST /v1/control/resume"]
|
#[doc = "control_resume: POST /v1/control/resume"]
|
||||||
pub async fn control_resume(&self) -> Result<()> {
|
pub async fn control_resume(&self) -> Result<()> {
|
||||||
let url = format!("{}/v1/control/resume", self.baseurl,);
|
let url = format!("{}/v1/control/resume", self.baseurl,);
|
||||||
let res = self.client.post(url).send().await?.error_for_status()?;
|
let request = self.client.post(url).build()?;
|
||||||
|
let result = self.client.execute(request).await;
|
||||||
|
let res = result?.error_for_status()?;
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,27 +198,27 @@ impl Client {
|
||||||
self.baseurl,
|
self.baseurl,
|
||||||
progenitor_support::encode_path(&task.to_string()),
|
progenitor_support::encode_path(&task.to_string()),
|
||||||
);
|
);
|
||||||
let res = self.client.get(url).send().await?.error_for_status()?;
|
let request = self.client.get(url).build()?;
|
||||||
|
let result = self.client.execute(request).await;
|
||||||
|
let res = result?.error_for_status()?;
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "tasks_get: GET /v1/tasks"]
|
#[doc = "tasks_get: GET /v1/tasks"]
|
||||||
pub async fn tasks_get(&self) -> Result<Vec<types::Task>> {
|
pub async fn tasks_get(&self) -> Result<Vec<types::Task>> {
|
||||||
let url = format!("{}/v1/tasks", self.baseurl,);
|
let url = format!("{}/v1/tasks", self.baseurl,);
|
||||||
let res = self.client.get(url).send().await?.error_for_status()?;
|
let request = self.client.get(url).build()?;
|
||||||
|
let result = self.client.execute(request).await;
|
||||||
|
let res = result?.error_for_status()?;
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "task_submit: POST /v1/tasks"]
|
#[doc = "task_submit: POST /v1/tasks"]
|
||||||
pub async fn task_submit(&self, body: &types::TaskSubmit) -> Result<types::TaskSubmitResult> {
|
pub async fn task_submit(&self, body: &types::TaskSubmit) -> Result<types::TaskSubmitResult> {
|
||||||
let url = format!("{}/v1/tasks", self.baseurl,);
|
let url = format!("{}/v1/tasks", self.baseurl,);
|
||||||
let res = self
|
let request = self.client.post(url).json(body).build()?;
|
||||||
.client
|
let result = self.client.execute(request).await;
|
||||||
.post(url)
|
let res = result?.error_for_status()?;
|
||||||
.json(body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,13 +238,9 @@ impl Client {
|
||||||
query.push(("minseq", v.to_string()));
|
query.push(("minseq", v.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = self
|
let request = self.client.get(url).query(&query).build()?;
|
||||||
.client
|
let result = self.client.execute(request).await;
|
||||||
.get(url)
|
let res = result?.error_for_status()?;
|
||||||
.query(&query)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +251,9 @@ impl Client {
|
||||||
self.baseurl,
|
self.baseurl,
|
||||||
progenitor_support::encode_path(&task.to_string()),
|
progenitor_support::encode_path(&task.to_string()),
|
||||||
);
|
);
|
||||||
let res = self.client.get(url).send().await?.error_for_status()?;
|
let request = self.client.get(url).build()?;
|
||||||
|
let result = self.client.execute(request).await;
|
||||||
|
let res = result?.error_for_status()?;
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,27 +269,27 @@ impl Client {
|
||||||
progenitor_support::encode_path(&task.to_string()),
|
progenitor_support::encode_path(&task.to_string()),
|
||||||
progenitor_support::encode_path(&output.to_string()),
|
progenitor_support::encode_path(&output.to_string()),
|
||||||
);
|
);
|
||||||
let res = self.client.get(url).send().await?.error_for_status()?;
|
let request = self.client.get(url).build()?;
|
||||||
|
let result = self.client.execute(request).await;
|
||||||
|
let res = result?.error_for_status()?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "user_create: POST /v1/users"]
|
#[doc = "user_create: POST /v1/users"]
|
||||||
pub async fn user_create(&self, body: &types::UserCreate) -> Result<types::UserCreateResult> {
|
pub async fn user_create(&self, body: &types::UserCreate) -> Result<types::UserCreateResult> {
|
||||||
let url = format!("{}/v1/users", self.baseurl,);
|
let url = format!("{}/v1/users", self.baseurl,);
|
||||||
let res = self
|
let request = self.client.post(url).json(body).build()?;
|
||||||
.client
|
let result = self.client.execute(request).await;
|
||||||
.post(url)
|
let res = result?.error_for_status()?;
|
||||||
.json(body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "whoami: GET /v1/whoami"]
|
#[doc = "whoami: GET /v1/whoami"]
|
||||||
pub async fn whoami(&self) -> Result<types::WhoamiResult> {
|
pub async fn whoami(&self) -> Result<types::WhoamiResult> {
|
||||||
let url = format!("{}/v1/whoami", self.baseurl,);
|
let url = format!("{}/v1/whoami", self.baseurl,);
|
||||||
let res = self.client.get(url).send().await?.error_for_status()?;
|
let request = self.client.get(url).build()?;
|
||||||
|
let result = self.client.execute(request).await;
|
||||||
|
let res = result?.error_for_status()?;
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,20 +299,18 @@ impl Client {
|
||||||
body: &types::WorkerBootstrap,
|
body: &types::WorkerBootstrap,
|
||||||
) -> Result<types::WorkerBootstrapResult> {
|
) -> Result<types::WorkerBootstrapResult> {
|
||||||
let url = format!("{}/v1/worker/bootstrap", self.baseurl,);
|
let url = format!("{}/v1/worker/bootstrap", self.baseurl,);
|
||||||
let res = self
|
let request = self.client.post(url).json(body).build()?;
|
||||||
.client
|
let result = self.client.execute(request).await;
|
||||||
.post(url)
|
let res = result?.error_for_status()?;
|
||||||
.json(body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "worker_ping: GET /v1/worker/ping"]
|
#[doc = "worker_ping: GET /v1/worker/ping"]
|
||||||
pub async fn worker_ping(&self) -> Result<types::WorkerPingResult> {
|
pub async fn worker_ping(&self) -> Result<types::WorkerPingResult> {
|
||||||
let url = format!("{}/v1/worker/ping", self.baseurl,);
|
let url = format!("{}/v1/worker/ping", self.baseurl,);
|
||||||
let res = self.client.get(url).send().await?.error_for_status()?;
|
let request = self.client.get(url).build()?;
|
||||||
|
let result = self.client.execute(request).await;
|
||||||
|
let res = result?.error_for_status()?;
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,13 +325,9 @@ impl Client {
|
||||||
self.baseurl,
|
self.baseurl,
|
||||||
progenitor_support::encode_path(&task.to_string()),
|
progenitor_support::encode_path(&task.to_string()),
|
||||||
);
|
);
|
||||||
let res = self
|
let request = self.client.post(url).json(body).build()?;
|
||||||
.client
|
let result = self.client.execute(request).await;
|
||||||
.post(url)
|
let res = result?.error_for_status()?;
|
||||||
.json(body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,13 +342,9 @@ impl Client {
|
||||||
self.baseurl,
|
self.baseurl,
|
||||||
progenitor_support::encode_path(&task.to_string()),
|
progenitor_support::encode_path(&task.to_string()),
|
||||||
);
|
);
|
||||||
let res = self
|
let request = self.client.post(url).body(body).build()?;
|
||||||
.client
|
let result = self.client.execute(request).await;
|
||||||
.post(url)
|
let res = result?.error_for_status()?;
|
||||||
.body(body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,13 +359,9 @@ impl Client {
|
||||||
self.baseurl,
|
self.baseurl,
|
||||||
progenitor_support::encode_path(&task.to_string()),
|
progenitor_support::encode_path(&task.to_string()),
|
||||||
);
|
);
|
||||||
let res = self
|
let request = self.client.post(url).json(body).build()?;
|
||||||
.client
|
let result = self.client.execute(request).await;
|
||||||
.post(url)
|
let res = result?.error_for_status()?;
|
||||||
.json(body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,27 +376,27 @@ impl Client {
|
||||||
self.baseurl,
|
self.baseurl,
|
||||||
progenitor_support::encode_path(&task.to_string()),
|
progenitor_support::encode_path(&task.to_string()),
|
||||||
);
|
);
|
||||||
let res = self
|
let request = self.client.post(url).json(body).build()?;
|
||||||
.client
|
let result = self.client.execute(request).await;
|
||||||
.post(url)
|
let res = result?.error_for_status()?;
|
||||||
.json(body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "workers_list: GET /v1/workers"]
|
#[doc = "workers_list: GET /v1/workers"]
|
||||||
pub async fn workers_list(&self) -> Result<types::WorkersResult> {
|
pub async fn workers_list(&self) -> Result<types::WorkersResult> {
|
||||||
let url = format!("{}/v1/workers", self.baseurl,);
|
let url = format!("{}/v1/workers", self.baseurl,);
|
||||||
let res = self.client.get(url).send().await?.error_for_status()?;
|
let request = self.client.get(url).build()?;
|
||||||
|
let result = self.client.execute(request).await;
|
||||||
|
let res = result?.error_for_status()?;
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "workers_recycle: POST /v1/workers/recycle"]
|
#[doc = "workers_recycle: POST /v1/workers/recycle"]
|
||||||
pub async fn workers_recycle(&self) -> Result<()> {
|
pub async fn workers_recycle(&self) -> Result<()> {
|
||||||
let url = format!("{}/v1/workers/recycle", self.baseurl,);
|
let url = format!("{}/v1/workers/recycle", self.baseurl,);
|
||||||
let res = self.client.post(url).send().await?.error_for_status()?;
|
let request = self.client.post(url).build()?;
|
||||||
|
let result = self.client.execute(request).await;
|
||||||
|
let res = result?.error_for_status()?;
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,18 +97,18 @@ pub struct Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub fn new(baseurl: &str) -> Client {
|
pub fn new(baseurl: &str) -> Self {
|
||||||
let dur = std::time::Duration::from_secs(15);
|
let dur = std::time::Duration::from_secs(15);
|
||||||
let client = reqwest::ClientBuilder::new()
|
let client = reqwest::ClientBuilder::new()
|
||||||
.connect_timeout(dur)
|
.connect_timeout(dur)
|
||||||
.timeout(dur)
|
.timeout(dur)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Client::new_with_client(baseurl, client)
|
Self::new_with_client(baseurl, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Client {
|
pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self {
|
||||||
Client {
|
Self {
|
||||||
baseurl: baseurl.to_string(),
|
baseurl: baseurl.to_string(),
|
||||||
client,
|
client,
|
||||||
}
|
}
|
||||||
|
@ -117,27 +117,27 @@ impl Client {
|
||||||
#[doc = "enrol: POST /enrol"]
|
#[doc = "enrol: POST /enrol"]
|
||||||
pub async fn enrol(&self, body: &types::EnrolBody) -> Result<()> {
|
pub async fn enrol(&self, body: &types::EnrolBody) -> Result<()> {
|
||||||
let url = format!("{}/enrol", self.baseurl,);
|
let url = format!("{}/enrol", self.baseurl,);
|
||||||
let res = self
|
let request = self.client.post(url).json(body).build()?;
|
||||||
.client
|
let result = self.client.execute(request).await;
|
||||||
.post(url)
|
let res = result?.error_for_status()?;
|
||||||
.json(body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "global_jobs: GET /global/jobs"]
|
#[doc = "global_jobs: GET /global/jobs"]
|
||||||
pub async fn global_jobs(&self) -> Result<types::GlobalJobsResult> {
|
pub async fn global_jobs(&self) -> Result<types::GlobalJobsResult> {
|
||||||
let url = format!("{}/global/jobs", self.baseurl,);
|
let url = format!("{}/global/jobs", self.baseurl,);
|
||||||
let res = self.client.get(url).send().await?.error_for_status()?;
|
let request = self.client.get(url).build()?;
|
||||||
|
let result = self.client.execute(request).await;
|
||||||
|
let res = result?.error_for_status()?;
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "ping: GET /ping"]
|
#[doc = "ping: GET /ping"]
|
||||||
pub async fn ping(&self) -> Result<types::PingResult> {
|
pub async fn ping(&self) -> Result<types::PingResult> {
|
||||||
let url = format!("{}/ping", self.baseurl,);
|
let url = format!("{}/ping", self.baseurl,);
|
||||||
let res = self.client.get(url).send().await?.error_for_status()?;
|
let request = self.client.get(url).build()?;
|
||||||
|
let result = self.client.execute(request).await;
|
||||||
|
let res = result?.error_for_status()?;
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,13 +147,9 @@ impl Client {
|
||||||
body: &types::ReportFinishBody,
|
body: &types::ReportFinishBody,
|
||||||
) -> Result<types::ReportResult> {
|
) -> Result<types::ReportResult> {
|
||||||
let url = format!("{}/report/finish", self.baseurl,);
|
let url = format!("{}/report/finish", self.baseurl,);
|
||||||
let res = self
|
let request = self.client.post(url).json(body).build()?;
|
||||||
.client
|
let result = self.client.execute(request).await;
|
||||||
.post(url)
|
let res = result?.error_for_status()?;
|
||||||
.json(body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,26 +159,18 @@ impl Client {
|
||||||
body: &types::ReportOutputBody,
|
body: &types::ReportOutputBody,
|
||||||
) -> Result<types::ReportResult> {
|
) -> Result<types::ReportResult> {
|
||||||
let url = format!("{}/report/output", self.baseurl,);
|
let url = format!("{}/report/output", self.baseurl,);
|
||||||
let res = self
|
let request = self.client.post(url).json(body).build()?;
|
||||||
.client
|
let result = self.client.execute(request).await;
|
||||||
.post(url)
|
let res = result?.error_for_status()?;
|
||||||
.json(body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc = "report_start: POST /report/start"]
|
#[doc = "report_start: POST /report/start"]
|
||||||
pub async fn report_start(&self, body: &types::ReportStartBody) -> Result<types::ReportResult> {
|
pub async fn report_start(&self, body: &types::ReportStartBody) -> Result<types::ReportResult> {
|
||||||
let url = format!("{}/report/start", self.baseurl,);
|
let url = format!("{}/report/start", self.baseurl,);
|
||||||
let res = self
|
let request = self.client.post(url).json(body).build()?;
|
||||||
.client
|
let result = self.client.execute(request).await;
|
||||||
.post(url)
|
let res = result?.error_for_status()?;
|
||||||
.json(body)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?;
|
|
||||||
Ok(res.json().await?)
|
Ok(res.json().await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,10 @@ description = "An OpenAPI client generator - macros"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
openapiv3 = "1.0.0-beta.2"
|
openapiv3 = "1.0.0-beta.2"
|
||||||
progenitor-impl = { path = "../progenitor-impl" }
|
progenitor-impl = { path = "../progenitor-impl" }
|
||||||
quote = "1.0.10"
|
quote = "1.0"
|
||||||
serde_json = "1.0.68"
|
proc-macro2 = "1.0"
|
||||||
syn = "1.0.80"
|
serde_json = "1.0"
|
||||||
|
syn = "1.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
|
@ -5,7 +5,11 @@ use std::path::Path;
|
||||||
use openapiv3::OpenAPI;
|
use openapiv3::OpenAPI;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use progenitor_impl::Generator;
|
use progenitor_impl::Generator;
|
||||||
use syn::LitStr;
|
use quote::ToTokens;
|
||||||
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
ExprClosure, LitStr, Token,
|
||||||
|
};
|
||||||
|
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn generate_api(item: TokenStream) -> TokenStream {
|
pub fn generate_api(item: TokenStream) -> TokenStream {
|
||||||
|
@ -15,34 +19,100 @@ pub fn generate_api(item: TokenStream) -> TokenStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Settings {
|
||||||
|
file: LitStr,
|
||||||
|
inner: Option<proc_macro2::TokenStream>,
|
||||||
|
pre: Option<proc_macro2::TokenStream>,
|
||||||
|
post: Option<proc_macro2::TokenStream>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for Settings {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
|
||||||
|
let file = input.parse::<LitStr>()?;
|
||||||
|
let inner = parse_inner(input)?;
|
||||||
|
let pre = parse_hook(input)?;
|
||||||
|
let post = parse_hook(input)?;
|
||||||
|
|
||||||
|
// Optional trailing comma.
|
||||||
|
if input.peek(Token!(,)) {
|
||||||
|
let _ = input.parse::<Token!(,)>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Settings {
|
||||||
|
file,
|
||||||
|
inner,
|
||||||
|
pre,
|
||||||
|
post,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_inner(
|
||||||
|
input: ParseStream,
|
||||||
|
) -> Result<Option<proc_macro2::TokenStream>, syn::Error> {
|
||||||
|
if input.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let _: Token!(,) = input.parse()?;
|
||||||
|
if input.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Ok(Some(input.parse::<syn::Type>()?.to_token_stream()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_hook(
|
||||||
|
input: ParseStream,
|
||||||
|
) -> Result<Option<proc_macro2::TokenStream>, syn::Error> {
|
||||||
|
if input.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let _: Token!(,) = input.parse()?;
|
||||||
|
if input.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
if let Ok(closure) = input.parse::<ExprClosure>() {
|
||||||
|
Ok(Some(closure.to_token_stream()))
|
||||||
|
} else {
|
||||||
|
Ok(Some(input.parse::<syn::Path>()?.to_token_stream()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn do_generate_api(item: TokenStream) -> Result<TokenStream, syn::Error> {
|
fn do_generate_api(item: TokenStream) -> Result<TokenStream, syn::Error> {
|
||||||
let arg = syn::parse::<LitStr>(item)?;
|
let Settings {
|
||||||
|
file,
|
||||||
|
inner,
|
||||||
|
pre,
|
||||||
|
post,
|
||||||
|
} = syn::parse::<Settings>(item)?;
|
||||||
let dir = std::env::var("CARGO_MANIFEST_DIR").map_or_else(
|
let dir = std::env::var("CARGO_MANIFEST_DIR").map_or_else(
|
||||||
|_| std::env::current_dir().unwrap(),
|
|_| std::env::current_dir().unwrap(),
|
||||||
|s| Path::new(&s).to_path_buf(),
|
|s| Path::new(&s).to_path_buf(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let path = dir.join(arg.value());
|
let path = dir.join(file.value());
|
||||||
|
|
||||||
let content = std::fs::read_to_string(&path).map_err(|e| {
|
let content = std::fs::read_to_string(&path).map_err(|e| {
|
||||||
syn::Error::new(
|
syn::Error::new(
|
||||||
arg.span(),
|
file.span(),
|
||||||
format!("couldn't read file {}: {}", arg.value(), e.to_string()),
|
format!("couldn't read file {}: {}", file.value(), e.to_string()),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let spec = serde_json::from_str::<OpenAPI>(&content).map_err(|e| {
|
let spec = serde_json::from_str::<OpenAPI>(&content).map_err(|e| {
|
||||||
syn::Error::new(
|
syn::Error::new(
|
||||||
arg.span(),
|
file.span(),
|
||||||
format!("failed to parse {}: {}", arg.value(), e.to_string()),
|
format!("failed to parse {}: {}", file.value(), e.to_string()),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut builder = Generator::new();
|
let mut builder = Generator::new();
|
||||||
|
inner.map(|inner_type| builder.with_inner_type(inner_type));
|
||||||
|
pre.map(|pre_hook| builder.with_pre_hook(pre_hook));
|
||||||
|
post.map(|post_hook| builder.with_post_hook(post_hook));
|
||||||
let ret = builder.generate_tokens(&spec).map_err(|e| {
|
let ret = builder.generate_tokens(&spec).map_err(|e| {
|
||||||
syn::Error::new(
|
syn::Error::new(
|
||||||
arg.span(),
|
file.span(),
|
||||||
format!("generation error for {}: {}", arg.value(), e.to_string()),
|
format!("generation error for {}: {}", file.value(), e.to_string()),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
|
@ -9,15 +9,8 @@ description = "An OpenAPI client generator"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
progenitor-macro = { path = "../progenitor-macro" }
|
progenitor-macro = { path = "../progenitor-macro" }
|
||||||
progenitor-impl = { path = "../progenitor-impl" }
|
progenitor-impl = { path = "../progenitor-impl" }
|
||||||
rustfmt-wrapper = "0.1.0"
|
anyhow = "1.0"
|
||||||
anyhow = "1"
|
|
||||||
getopts = "0.2"
|
getopts = "0.2"
|
||||||
indexmap = "1.7.0"
|
|
||||||
openapiv3 = "1.0.0-beta.2"
|
openapiv3 = "1.0.0-beta.2"
|
||||||
#proc-macro2 = "1.0.29"
|
serde = { version = "1.0", features = [ "derive" ] }
|
||||||
#quote = "1.0.9"
|
serde_json = "1.0"
|
||||||
regex = "1.5.4"
|
|
||||||
#schemars = "0.8.5"
|
|
||||||
serde = { version = "1", features = [ "derive" ] }
|
|
||||||
serde_json = "1.0.68"
|
|
||||||
#typify = { git = "https://github.com/oxidecomputer/typify" }
|
|
||||||
|
|
Loading…
Reference in New Issue