add an optional builder pattern as well as extension traits for tags when using the builder interface (#86)

This commit is contained in:
Adam Leventhal 2022-07-02 19:09:38 -07:00 committed by GitHub
parent 9e84bde032
commit 9b28ac87c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 32268 additions and 1094 deletions

View File

@ -14,10 +14,26 @@ jobs:
runs-on: ubuntu-18.04 runs-on: ubuntu-18.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install nightly
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: rustfmt
default: false
- name: Install stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt
default: true
- name: Report cargo version - name: Report cargo version
run: cargo --version run: cargo --version
- name: Report rustfmt version - name: Report rustfmt version
run: cargo fmt -- --version run: cargo fmt -- --version
- name: Report nightly cargo version
run: cargo +nightly --version
- name: Report nightly rustfmt version
run: cargo +nightly fmt -- --version
- name: Check style - name: Check style
run: cargo fmt -- --check run: cargo fmt -- --check
@ -28,6 +44,18 @@ jobs:
os: [ ubuntu-18.04, windows-2019, macos-10.15 ] os: [ ubuntu-18.04, windows-2019, macos-10.15 ]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install nightly
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: rustfmt
default: false
- name: Install stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt
default: true
- name: Build - name: Build
run: cargo build --tests --verbose run: cargo build --tests --verbose
- name: Run tests - name: Run tests

View File

@ -3,7 +3,7 @@
:icons: font :icons: font
:toclevels: 1 :toclevels: 1
= Typify Changelog = Progenitor Changelog
// WARNING: This file is modified programmatically by `cargo release` as // WARNING: This file is modified programmatically by `cargo release` as
// configured in release.toml. DO NOT change the format of the headers or the // configured in release.toml. DO NOT change the format of the headers or the
@ -15,6 +15,8 @@
https://github.com/oxidecomputer/progenitor/compare/v0.1.1\...HEAD[Full list of commits] https://github.com/oxidecomputer/progenitor/compare/v0.1.1\...HEAD[Full list of commits]
* Add support for a builder-style generation in addition to the positional style (#86)
== 0.1.1 (released 2022-05-13) == 0.1.1 (released 2022-05-13)
First published version First published version

120
Cargo.lock generated
View File

@ -116,6 +116,45 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "clap"
version = "3.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
"once_cell",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_derive"
version = "3.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]] [[package]]
name = "console" name = "console"
version = "0.15.0" version = "0.15.0"
@ -776,9 +815,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.9.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
[[package]] [[package]]
name = "openapiv3" name = "openapiv3"
@ -824,6 +863,12 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "os_str_bytes"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.0" version = "0.12.0"
@ -886,6 +931,30 @@ version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.40" version = "1.0.40"
@ -901,8 +970,8 @@ version = "0.1.2-dev"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
"clap",
"futures", "futures",
"getopts",
"openapiv3", "openapiv3",
"percent-encoding", "percent-encoding",
"progenitor-client", "progenitor-client",
@ -1394,6 +1463,12 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.98" version = "1.0.98"
@ -1436,6 +1511,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "terminal_size" name = "terminal_size"
version = "0.1.17" version = "0.1.17"
@ -1446,6 +1530,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.31" version = "1.0.31"
@ -1640,9 +1730,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]] [[package]]
name = "typify" name = "typify"
version = "0.0.8" version = "0.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d94783d3e464d8b2b8f78e826521e9f11c7df83523dafd544c5e642bf31df5a4" checksum = "505f18fc847efc93b45a763f36f8099d3415dbfc6c0d6c3857a3012e20db92c7"
dependencies = [ dependencies = [
"typify-impl", "typify-impl",
"typify-macro", "typify-macro",
@ -1650,9 +1740,9 @@ dependencies = [
[[package]] [[package]]
name = "typify-impl" name = "typify-impl"
version = "0.0.8" version = "0.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b27f7e4f8a3b375daabcd3721bc067920a59ea9a33c1f01b9b2b98d17c3b5497" checksum = "b572369a55be8402a5a7e24c721e8d895dbd58bf3d9dccb4b3619fcb926e7bde"
dependencies = [ dependencies = [
"heck", "heck",
"log", "log",
@ -1663,14 +1753,14 @@ dependencies = [
"serde_json", "serde_json",
"syn", "syn",
"thiserror", "thiserror",
"unicode-xid", "unicode-ident",
] ]
[[package]] [[package]]
name = "typify-macro" name = "typify-macro"
version = "0.0.8" version = "0.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4211d794d8e4e6fed99bdcd263529ee05af52d9794004cc235123216f1fef42" checksum = "a615bfbbcf929b1733898d2afe00c83dbf8756f8028aa42c156c3c2713b0f03b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1696,9 +1786,9 @@ checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.0" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
@ -1715,12 +1805,6 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
[[package]] [[package]]
name = "untrusted" name = "untrusted"
version = "0.7.1" version = "0.7.1"

View File

@ -8,7 +8,9 @@ members = [
"progenitor-macro", "progenitor-macro",
] ]
#[patch."https://github.com/oxidecomputer/typify"]
#typify = { path = "../typify/typify" }
#[patch."https://github.com/oxidecomputer/dropshot"] #[patch."https://github.com/oxidecomputer/dropshot"]
#dropshot = { path = "../dropshot/dropshot" } #dropshot = { path = "../dropshot/dropshot" }
#[patch.crates-io]
#typify = { path = "../typify/typify" }
#rustfmt-wrapper = { path = "../rustfmt-wrapper" }

View File

@ -52,6 +52,8 @@ The macro has some additional fancy options to control the generated code:
```rust ```rust
generate_api!( generate_api!(
spec = "path/to/openapi_document.json", // The OpenAPI document spec = "path/to/openapi_document.json", // The OpenAPI document
interface = Builder, // Choose positional (default) or builder style
tags = Separate, // Tags may be Merged or Separate (default)
inner_type = my_client::InnerType, // Client inner type available to pre and post hooks inner_type = my_client::InnerType, // Client inner type available to pre and post hooks
pre_hook = closure::or::path::to::function, // Hook invoked before issuing the HTTP request pre_hook = closure::or::path::to::function, // Hook invoked before issuing the HTTP request
post_hook = closure::or::path::to::function, // Hook invoked prior to receiving the HTTP response post_hook = closure::or::path::to::function, // Hook invoked prior to receiving the HTTP response
@ -76,7 +78,7 @@ fn main() {
println!("cargo:rerun-if-changed={}", src); println!("cargo:rerun-if-changed={}", src);
let file = File::open(src).unwrap(); let file = File::open(src).unwrap();
let spec = serde_json::from_reader(file).unwrap(); let spec = serde_json::from_reader(file).unwrap();
let mut generator = progenitor::Generator::new(); let mut generator = progenitor::Generator::default();
let content = generator.generate_text(&spec).unwrap(); let content = generator.generate_text(&spec).unwrap();

View File

@ -2,7 +2,7 @@
name = "example-build" name = "example-build"
version = "0.0.1" version = "0.0.1"
authors = ["Adam H. Leventhal <ahl@oxidecomputer.com>"] authors = ["Adam H. Leventhal <ahl@oxidecomputer.com>"]
edition = "2018" edition = "2021"
[dependencies] [dependencies]
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }

View File

@ -11,7 +11,7 @@ fn main() {
println!("cargo:rerun-if-changed={}", src); println!("cargo:rerun-if-changed={}", src);
let file = File::open(src).unwrap(); let file = File::open(src).unwrap();
let spec = serde_json::from_reader(file).unwrap(); let spec = serde_json::from_reader(file).unwrap();
let mut generator = progenitor::Generator::new(); let mut generator = progenitor::Generator::default();
let content = generator.generate_text(&spec).unwrap(); let content = generator.generate_text(&spec).unwrap();

View File

@ -2,7 +2,7 @@
name = "example-macro" name = "example-macro"
version = "0.0.1" version = "0.0.1"
authors = ["Adam H. Leventhal <ahl@oxidecomputer.com>"] authors = ["Adam H. Leventhal <ahl@oxidecomputer.com>"]
edition = "2018" edition = "2021"
[dependencies] [dependencies]
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }

View File

@ -1,7 +1,7 @@
[package] [package]
name = "progenitor-client" name = "progenitor-client"
version = "0.1.2-dev" version = "0.1.2-dev"
edition = "2018" edition = "2021"
license = "MPL-2.0" license = "MPL-2.0"
repository = "https://github.com/oxidecomputer/progenitor.git" repository = "https://github.com/oxidecomputer/progenitor.git"
description = "An OpenAPI client generator - client support" description = "An OpenAPI client generator - client support"

View File

@ -1,5 +1,7 @@
// Copyright 2022 Oxide Computer Company // Copyright 2022 Oxide Computer Company
#![allow(dead_code)]
//! Support code for generated clients. //! Support code for generated clients.
use std::{ use std::{

View File

@ -1,7 +1,7 @@
[package] [package]
name = "progenitor-impl" name = "progenitor-impl"
version = "0.1.2-dev" version = "0.1.2-dev"
edition = "2018" edition = "2021"
license = "MPL-2.0" license = "MPL-2.0"
repository = "https://github.com/oxidecomputer/progenitor.git" repository = "https://github.com/oxidecomputer/progenitor.git"
description = "An OpenAPI client generator - core implementation" description = "An OpenAPI client generator - core implementation"
@ -14,13 +14,13 @@ openapiv3 = "1.0.0"
proc-macro2 = "1.0" proc-macro2 = "1.0"
quote = "1.0" quote = "1.0"
regex = "1.5" regex = "1.5"
rustfmt-wrapper = "0.2" rustfmt-wrapper = "0.2.0"
schemars = { version = "0.8.10", features = ["chrono", "uuid1"] } schemars = { version = "0.8.10", features = ["chrono", "uuid1"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
syn = { version = "1.0", features = ["parsing"] } syn = { version = "1.0", features = ["parsing"] }
thiserror = "1.0" thiserror = "1.0"
typify = "0.0.8" typify = "0.0.9"
unicode-ident = "1.0.0" unicode-ident = "1.0.0"
[dev-dependencies] [dev-dependencies]

View File

@ -3,6 +3,7 @@
use openapiv3::OpenAPI; use openapiv3::OpenAPI;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use serde::Deserialize;
use thiserror::Error; use thiserror::Error;
use typify::TypeSpace; use typify::TypeSpace;
@ -32,17 +33,59 @@ 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>, settings: GenerationSettings,
pre_hook: Option<TokenStream>,
post_hook: Option<TokenStream>,
uses_futures: bool, uses_futures: bool,
} }
impl Generator { #[derive(Default, Clone)]
pub struct GenerationSettings {
interface: InterfaceStyle,
tag: TagStyle,
inner_type: Option<TokenStream>,
pre_hook: Option<TokenStream>,
post_hook: Option<TokenStream>,
extra_derives: Vec<TokenStream>,
}
#[derive(Clone, Deserialize)]
pub enum InterfaceStyle {
Positional,
Builder,
}
impl Default for InterfaceStyle {
fn default() -> Self {
Self::Positional
}
}
#[derive(Clone, Deserialize)]
pub enum TagStyle {
Merged,
Separate,
}
impl Default for TagStyle {
fn default() -> Self {
Self::Merged
}
}
impl GenerationSettings {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
pub fn with_interface(&mut self, interface: InterfaceStyle) -> &mut Self {
self.interface = interface;
self
}
pub fn with_tag(&mut self, tag: TagStyle) -> &mut Self {
self.tag = tag;
self
}
pub fn with_inner_type(&mut self, inner_type: TokenStream) -> &mut Self { pub fn with_inner_type(&mut self, inner_type: TokenStream) -> &mut Self {
self.inner_type = Some(inner_type); self.inner_type = Some(inner_type);
self self
@ -58,10 +101,21 @@ impl Generator {
self self
} }
// TODO maybe change to a typify::Settings or something
pub fn with_derive(&mut self, derive: TokenStream) -> &mut Self { pub fn with_derive(&mut self, derive: TokenStream) -> &mut Self {
self.type_space.add_derive(derive); self.extra_derives.push(derive);
self self
} }
}
impl Generator {
pub fn new(settings: &GenerationSettings) -> Self {
Self {
type_space: TypeSpace::default(),
settings: settings.clone(),
uses_futures: false,
}
}
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
@ -100,10 +154,23 @@ impl Generator {
}) })
.collect::<Result<Vec<_>>>()?; .collect::<Result<Vec<_>>>()?;
let methods = raw_methods let operation_code = match (
.iter() &self.settings.interface,
.map(|method| self.positional_method(method)) &self.settings.tag,
.collect::<Result<Vec<_>>>()?; ) {
(InterfaceStyle::Positional, TagStyle::Merged) => {
self.generate_tokens_positional_merged(&raw_methods)
}
(InterfaceStyle::Positional, TagStyle::Separate) => {
unimplemented!("positional arguments with separate tags are currently unsupported")
}
(InterfaceStyle::Builder, TagStyle::Merged) => {
self.generate_tokens_builder_merged(&raw_methods)
}
(InterfaceStyle::Builder, TagStyle::Separate) => {
self.generate_tokens_builder_separate(&raw_methods)
}
}?;
let mut types = self let mut types = self
.type_space .type_space
@ -114,12 +181,17 @@ impl Generator {
let types = types.into_iter().map(|(_, def)| def); let types = types.into_iter().map(|(_, def)| def);
let shared = self.type_space.common_code(); let shared = self.type_space.common_code();
let inner_property = self.inner_type.as_ref().map(|inner| { let inner_property = self.settings.inner_type.as_ref().map(|inner| {
quote! {
pub (crate) inner: #inner,
}
});
let inner_parameter = self.settings.inner_type.as_ref().map(|inner| {
quote! { quote! {
inner: #inner, inner: #inner,
} }
}); });
let inner_value = self.inner_type.as_ref().map(|_| { let inner_value = self.settings.inner_type.as_ref().map(|_| {
quote! { quote! {
inner inner
} }
@ -129,6 +201,8 @@ impl Generator {
// Re-export ResponseValue and Error since those are used by the // Re-export ResponseValue and Error since those are used by the
// public interface of Client. // public interface of Client.
pub use progenitor_client::{ByteStream, Error, ResponseValue}; pub use progenitor_client::{ByteStream, Error, ResponseValue};
#[allow(unused_imports)]
use progenitor_client::encode_path;
pub mod types { pub mod types {
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -138,15 +212,15 @@ impl Generator {
#[derive(Clone)] #[derive(Clone)]
pub struct Client { pub struct Client {
baseurl: String, pub(crate) baseurl: String,
client: reqwest::Client, pub(crate) client: reqwest::Client,
#inner_property #inner_property
} }
impl Client { impl Client {
pub fn new( pub fn new(
baseurl: &str, baseurl: &str,
#inner_property #inner_parameter
) -> Self { ) -> 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()
@ -160,7 +234,7 @@ impl Generator {
pub fn new_with_client( pub fn new_with_client(
baseurl: &str, baseurl: &str,
client: reqwest::Client, client: reqwest::Client,
#inner_property #inner_parameter
) -> Self { ) -> Self {
Self { Self {
baseurl: baseurl.to_string(), baseurl: baseurl.to_string(),
@ -177,18 +251,126 @@ impl Generator {
&self.client &self.client
} }
#(#methods)*
} }
#operation_code
}; };
Ok(file) Ok(file)
} }
fn generate_tokens_positional_merged(
&mut self,
input_methods: &[method::OperationMethod],
) -> Result<TokenStream> {
let methods = input_methods
.iter()
.map(|method| self.positional_method(method))
.collect::<Result<Vec<_>>>()?;
let out = quote! {
impl Client {
#(#methods)*
}
};
Ok(out)
}
fn generate_tokens_builder_merged(
&mut self,
input_methods: &[method::OperationMethod],
) -> Result<TokenStream> {
let builder_struct = input_methods
.iter()
.map(|method| self.builder_struct(method, TagStyle::Merged))
.collect::<Result<Vec<_>>>()?;
let builder_methods = input_methods
.iter()
.map(|method| self.builder_impl(method))
.collect::<Vec<_>>();
let out = quote! {
impl Client {
#(#builder_methods)*
}
pub mod builder {
use super::types;
#[allow(unused_imports)]
use super::{ByteStream, Error, ResponseValue};
#[allow(unused_imports)]
use super::encode_path;
#(#builder_struct)*
}
};
Ok(out)
}
fn generate_tokens_builder_separate(
&mut self,
input_methods: &[method::OperationMethod],
) -> Result<TokenStream> {
let builder_struct = input_methods
.iter()
.map(|method| self.builder_struct(method, TagStyle::Separate))
.collect::<Result<Vec<_>>>()?;
let traits_and_impls = self.builder_tags(input_methods);
let out = quote! {
#traits_and_impls
pub mod builder {
use super::types;
#[allow(unused_imports)]
use super::{ByteStream, Error, ResponseValue};
#[allow(unused_imports)]
use super::encode_path;
#(#builder_struct)*
}
};
Ok(out)
}
/// Render text output.
pub fn generate_text(&mut self, spec: &OpenAPI) -> Result<String> { pub fn generate_text(&mut self, spec: &OpenAPI) -> Result<String> {
self.generate_text_impl(
spec,
rustfmt_wrapper::config::Config::default(),
)
}
/// Render text output and normalize doc comments
///
/// Requires a nightly install of `rustfmt` (even if the target project is
/// not using nightly).
pub fn generate_text_normalize_comments(
&mut self,
spec: &OpenAPI,
) -> Result<String> {
self.generate_text_impl(
spec,
rustfmt_wrapper::config::Config {
normalize_doc_attributes: Some(true),
wrap_comments: Some(true),
..Default::default()
},
)
}
fn generate_text_impl(
&mut self,
spec: &OpenAPI,
config: rustfmt_wrapper::config::Config,
) -> Result<String> {
let output = self.generate_tokens(spec)?; let output = self.generate_tokens(spec)?;
// Format the file with rustfmt. // Format the file with rustfmt.
let content = rustfmt_wrapper::rustfmt(output).unwrap(); let content = rustfmt_wrapper::rustfmt_config(config, output).unwrap();
// Add newlines after end-braces at <= two levels of indentation. // Add newlines after end-braces at <= two levels of indentation.
Ok(if cfg!(not(windows)) { Ok(if cfg!(not(windows)) {

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,11 @@ pub struct PathTemplate {
} }
impl PathTemplate { impl PathTemplate {
pub fn compile(&self, rename: HashMap<&String, &String>) -> TokenStream { pub fn compile(
&self,
rename: HashMap<&String, &String>,
client: TokenStream,
) -> TokenStream {
let mut fmt = String::new(); let mut fmt = String::new();
fmt.push_str("{}"); fmt.push_str("{}");
for c in self.components.iter() { for c in self.components.iter() {
@ -39,7 +43,7 @@ impl PathTemplate {
.expect(&format!("missing path name mapping {}", n)), .expect(&format!("missing path name mapping {}", n)),
); );
Some(quote! { Some(quote! {
progenitor_client::encode_path(&#param.to_string()) encode_path(&#param.to_string())
}) })
} else { } else {
None None
@ -47,7 +51,7 @@ impl PathTemplate {
}); });
quote! { quote! {
let url = format!(#fmt, self.baseurl, #(#components,)*); let url = format!(#fmt, #client.baseurl, #(#components,)*);
} }
} }
@ -233,11 +237,11 @@ mod test {
let number = "number".to_string(); let number = "number".to_string();
rename.insert(&number, &number); rename.insert(&number, &number);
let t = parse("/measure/{number}").unwrap(); let t = parse("/measure/{number}").unwrap();
let out = t.compile(rename); let out = t.compile(rename, quote::quote! { self });
let want = quote::quote! { let want = quote::quote! {
let url = format!("{}/measure/{}", let url = format!("{}/measure/{}",
self.baseurl, self.baseurl,
progenitor_client::encode_path(&number.to_string()), encode_path(&number.to_string()),
); );
}; };
assert_eq!(want.to_string(), out.to_string()); assert_eq!(want.to_string(), out.to_string());

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
#[allow(unused_imports)]
use progenitor_client::encode_path;
pub use progenitor_client::{ByteStream, Error, ResponseValue}; pub use progenitor_client::{ByteStream, Error, ResponseValue};
pub mod types { pub mod types {
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -132,8 +134,8 @@ pub mod types {
#[derive(Clone)] #[derive(Clone)]
pub struct Client { pub struct Client {
baseurl: String, pub(crate) baseurl: String,
client: reqwest::Client, pub(crate) client: reqwest::Client,
} }
impl Client { impl Client {
@ -161,8 +163,10 @@ impl Client {
pub fn client(&self) -> &reqwest::Client { pub fn client(&self) -> &reqwest::Client {
&self.client &self.client
} }
}
#[doc = "Sends a `POST` request to `/v1/control/hold`"] impl Client {
///Sends a `POST` request to `/v1/control/hold`
pub async fn control_hold<'a>(&'a self) -> Result<ResponseValue<()>, Error<()>> { pub async fn control_hold<'a>(&'a self) -> Result<ResponseValue<()>, Error<()>> {
let url = format!("{}/v1/control/hold", self.baseurl,); let url = format!("{}/v1/control/hold", self.baseurl,);
let request = self.client.post(url).build()?; let request = self.client.post(url).build()?;
@ -174,7 +178,7 @@ impl Client {
} }
} }
#[doc = "Sends a `POST` request to `/v1/control/resume`"] ///Sends a `POST` request to `/v1/control/resume`
pub async fn control_resume<'a>(&'a self) -> Result<ResponseValue<()>, Error<()>> { pub async fn control_resume<'a>(&'a self) -> Result<ResponseValue<()>, Error<()>> {
let url = format!("{}/v1/control/resume", self.baseurl,); let url = format!("{}/v1/control/resume", self.baseurl,);
let request = self.client.post(url).build()?; let request = self.client.post(url).build()?;
@ -186,7 +190,7 @@ impl Client {
} }
} }
#[doc = "Sends a `GET` request to `/v1/task/{task}`"] ///Sends a `GET` request to `/v1/task/{task}`
pub async fn task_get<'a>( pub async fn task_get<'a>(
&'a self, &'a self,
task: &'a str, task: &'a str,
@ -194,7 +198,7 @@ impl Client {
let url = format!( let url = format!(
"{}/v1/task/{}", "{}/v1/task/{}",
self.baseurl, self.baseurl,
progenitor_client::encode_path(&task.to_string()), encode_path(&task.to_string()),
); );
let request = self.client.get(url).build()?; let request = self.client.get(url).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
@ -205,7 +209,7 @@ impl Client {
} }
} }
#[doc = "Sends a `GET` request to `/v1/tasks`"] ///Sends a `GET` request to `/v1/tasks`
pub async fn tasks_get<'a>(&'a self) -> Result<ResponseValue<Vec<types::Task>>, Error<()>> { pub async fn tasks_get<'a>(&'a self) -> Result<ResponseValue<Vec<types::Task>>, Error<()>> {
let url = format!("{}/v1/tasks", self.baseurl,); let url = format!("{}/v1/tasks", self.baseurl,);
let request = self.client.get(url).build()?; let request = self.client.get(url).build()?;
@ -217,13 +221,13 @@ impl Client {
} }
} }
#[doc = "Sends a `POST` request to `/v1/tasks`"] ///Sends a `POST` request to `/v1/tasks`
pub async fn task_submit<'a>( pub async fn task_submit<'a>(
&'a self, &'a self,
body: &'a types::TaskSubmit, body: &'a types::TaskSubmit,
) -> Result<ResponseValue<types::TaskSubmitResult>, Error<()>> { ) -> Result<ResponseValue<types::TaskSubmitResult>, Error<()>> {
let url = format!("{}/v1/tasks", self.baseurl,); let url = format!("{}/v1/tasks", self.baseurl,);
let request = self.client.post(url).json(body).build()?; let request = self.client.post(url).json(&body).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
let response = result?; let response = result?;
match response.status().as_u16() { match response.status().as_u16() {
@ -232,7 +236,7 @@ impl Client {
} }
} }
#[doc = "Sends a `GET` request to `/v1/tasks/{task}/events`"] ///Sends a `GET` request to `/v1/tasks/{task}/events`
pub async fn task_events_get<'a>( pub async fn task_events_get<'a>(
&'a self, &'a self,
task: &'a str, task: &'a str,
@ -241,7 +245,7 @@ impl Client {
let url = format!( let url = format!(
"{}/v1/tasks/{}/events", "{}/v1/tasks/{}/events",
self.baseurl, self.baseurl,
progenitor_client::encode_path(&task.to_string()), encode_path(&task.to_string()),
); );
let mut query = Vec::new(); let mut query = Vec::new();
if let Some(v) = &minseq { if let Some(v) = &minseq {
@ -257,7 +261,7 @@ impl Client {
} }
} }
#[doc = "Sends a `GET` request to `/v1/tasks/{task}/outputs`"] ///Sends a `GET` request to `/v1/tasks/{task}/outputs`
pub async fn task_outputs_get<'a>( pub async fn task_outputs_get<'a>(
&'a self, &'a self,
task: &'a str, task: &'a str,
@ -265,7 +269,7 @@ impl Client {
let url = format!( let url = format!(
"{}/v1/tasks/{}/outputs", "{}/v1/tasks/{}/outputs",
self.baseurl, self.baseurl,
progenitor_client::encode_path(&task.to_string()), encode_path(&task.to_string()),
); );
let request = self.client.get(url).build()?; let request = self.client.get(url).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
@ -276,7 +280,7 @@ impl Client {
} }
} }
#[doc = "Sends a `GET` request to `/v1/tasks/{task}/outputs/{output}`"] ///Sends a `GET` request to `/v1/tasks/{task}/outputs/{output}`
pub async fn task_output_download<'a>( pub async fn task_output_download<'a>(
&'a self, &'a self,
task: &'a str, task: &'a str,
@ -285,8 +289,8 @@ impl Client {
let url = format!( let url = format!(
"{}/v1/tasks/{}/outputs/{}", "{}/v1/tasks/{}/outputs/{}",
self.baseurl, self.baseurl,
progenitor_client::encode_path(&task.to_string()), encode_path(&task.to_string()),
progenitor_client::encode_path(&output.to_string()), encode_path(&output.to_string()),
); );
let request = self.client.get(url).build()?; let request = self.client.get(url).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
@ -297,13 +301,13 @@ impl Client {
} }
} }
#[doc = "Sends a `POST` request to `/v1/users`"] ///Sends a `POST` request to `/v1/users`
pub async fn user_create<'a>( pub async fn user_create<'a>(
&'a self, &'a self,
body: &'a types::UserCreate, body: &'a types::UserCreate,
) -> Result<ResponseValue<types::UserCreateResult>, Error<()>> { ) -> Result<ResponseValue<types::UserCreateResult>, Error<()>> {
let url = format!("{}/v1/users", self.baseurl,); let url = format!("{}/v1/users", self.baseurl,);
let request = self.client.post(url).json(body).build()?; let request = self.client.post(url).json(&body).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
let response = result?; let response = result?;
match response.status().as_u16() { match response.status().as_u16() {
@ -312,7 +316,7 @@ impl Client {
} }
} }
#[doc = "Sends a `GET` request to `/v1/whoami`"] ///Sends a `GET` request to `/v1/whoami`
pub async fn whoami<'a>(&'a self) -> Result<ResponseValue<types::WhoamiResult>, Error<()>> { pub async fn whoami<'a>(&'a self) -> Result<ResponseValue<types::WhoamiResult>, Error<()>> {
let url = format!("{}/v1/whoami", self.baseurl,); let url = format!("{}/v1/whoami", self.baseurl,);
let request = self.client.get(url).build()?; let request = self.client.get(url).build()?;
@ -324,13 +328,13 @@ impl Client {
} }
} }
#[doc = "Sends a `POST` request to `/v1/worker/bootstrap`"] ///Sends a `POST` request to `/v1/worker/bootstrap`
pub async fn worker_bootstrap<'a>( pub async fn worker_bootstrap<'a>(
&'a self, &'a self,
body: &'a types::WorkerBootstrap, body: &'a types::WorkerBootstrap,
) -> Result<ResponseValue<types::WorkerBootstrapResult>, Error<()>> { ) -> Result<ResponseValue<types::WorkerBootstrapResult>, Error<()>> {
let url = format!("{}/v1/worker/bootstrap", self.baseurl,); let url = format!("{}/v1/worker/bootstrap", self.baseurl,);
let request = self.client.post(url).json(body).build()?; let request = self.client.post(url).json(&body).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
let response = result?; let response = result?;
match response.status().as_u16() { match response.status().as_u16() {
@ -339,7 +343,7 @@ impl Client {
} }
} }
#[doc = "Sends a `GET` request to `/v1/worker/ping`"] ///Sends a `GET` request to `/v1/worker/ping`
pub async fn worker_ping<'a>( pub async fn worker_ping<'a>(
&'a self, &'a self,
) -> Result<ResponseValue<types::WorkerPingResult>, Error<()>> { ) -> Result<ResponseValue<types::WorkerPingResult>, Error<()>> {
@ -353,7 +357,7 @@ impl Client {
} }
} }
#[doc = "Sends a `POST` request to `/v1/worker/task/{task}/append`"] ///Sends a `POST` request to `/v1/worker/task/{task}/append`
pub async fn worker_task_append<'a>( pub async fn worker_task_append<'a>(
&'a self, &'a self,
task: &'a str, task: &'a str,
@ -362,9 +366,9 @@ impl Client {
let url = format!( let url = format!(
"{}/v1/worker/task/{}/append", "{}/v1/worker/task/{}/append",
self.baseurl, self.baseurl,
progenitor_client::encode_path(&task.to_string()), encode_path(&task.to_string()),
); );
let request = self.client.post(url).json(body).build()?; let request = self.client.post(url).json(&body).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
let response = result?; let response = result?;
match response.status().as_u16() { match response.status().as_u16() {
@ -373,7 +377,7 @@ impl Client {
} }
} }
#[doc = "Sends a `POST` request to `/v1/worker/task/{task}/chunk`"] ///Sends a `POST` request to `/v1/worker/task/{task}/chunk`
pub async fn worker_task_upload_chunk<'a, B: Into<reqwest::Body>>( pub async fn worker_task_upload_chunk<'a, B: Into<reqwest::Body>>(
&'a self, &'a self,
task: &'a str, task: &'a str,
@ -382,7 +386,7 @@ impl Client {
let url = format!( let url = format!(
"{}/v1/worker/task/{}/chunk", "{}/v1/worker/task/{}/chunk",
self.baseurl, self.baseurl,
progenitor_client::encode_path(&task.to_string()), encode_path(&task.to_string()),
); );
let request = self.client.post(url).body(body).build()?; let request = self.client.post(url).body(body).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
@ -393,7 +397,7 @@ impl Client {
} }
} }
#[doc = "Sends a `POST` request to `/v1/worker/task/{task}/complete`"] ///Sends a `POST` request to `/v1/worker/task/{task}/complete`
pub async fn worker_task_complete<'a>( pub async fn worker_task_complete<'a>(
&'a self, &'a self,
task: &'a str, task: &'a str,
@ -402,9 +406,9 @@ impl Client {
let url = format!( let url = format!(
"{}/v1/worker/task/{}/complete", "{}/v1/worker/task/{}/complete",
self.baseurl, self.baseurl,
progenitor_client::encode_path(&task.to_string()), encode_path(&task.to_string()),
); );
let request = self.client.post(url).json(body).build()?; let request = self.client.post(url).json(&body).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
let response = result?; let response = result?;
match response.status().as_u16() { match response.status().as_u16() {
@ -413,7 +417,7 @@ impl Client {
} }
} }
#[doc = "Sends a `POST` request to `/v1/worker/task/{task}/output`"] ///Sends a `POST` request to `/v1/worker/task/{task}/output`
pub async fn worker_task_add_output<'a>( pub async fn worker_task_add_output<'a>(
&'a self, &'a self,
task: &'a str, task: &'a str,
@ -422,9 +426,9 @@ impl Client {
let url = format!( let url = format!(
"{}/v1/worker/task/{}/output", "{}/v1/worker/task/{}/output",
self.baseurl, self.baseurl,
progenitor_client::encode_path(&task.to_string()), encode_path(&task.to_string()),
); );
let request = self.client.post(url).json(body).build()?; let request = self.client.post(url).json(&body).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
let response = result?; let response = result?;
match response.status().as_u16() { match response.status().as_u16() {
@ -433,7 +437,7 @@ impl Client {
} }
} }
#[doc = "Sends a `GET` request to `/v1/workers`"] ///Sends a `GET` request to `/v1/workers`
pub async fn workers_list<'a>( pub async fn workers_list<'a>(
&'a self, &'a self,
) -> Result<ResponseValue<types::WorkersResult>, Error<()>> { ) -> Result<ResponseValue<types::WorkersResult>, Error<()>> {
@ -447,7 +451,7 @@ impl Client {
} }
} }
#[doc = "Sends a `POST` request to `/v1/workers/recycle`"] ///Sends a `POST` request to `/v1/workers/recycle`
pub async fn workers_recycle<'a>(&'a self) -> Result<ResponseValue<()>, Error<()>> { pub async fn workers_recycle<'a>(&'a self) -> Result<ResponseValue<()>, Error<()>> {
let url = format!("{}/v1/workers/recycle", self.baseurl,); let url = format!("{}/v1/workers/recycle", self.baseurl,);
let request = self.client.post(url).build()?; let request = self.client.post(url).build()?;

View File

@ -0,0 +1,418 @@
#[allow(unused_imports)]
use progenitor_client::encode_path;
pub use progenitor_client::{ByteStream, Error, ResponseValue};
pub mod types {
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EnrolBody {
pub host: String,
pub key: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GlobalJobsResult {
pub summary: Vec<ReportSummary>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct OutputRecord {
pub msg: String,
pub stream: String,
pub time: chrono::DateTime<chrono::offset::Utc>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PingResult {
pub host: String,
pub ok: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ReportFinishBody {
pub duration_millis: i32,
pub end_time: chrono::DateTime<chrono::offset::Utc>,
pub exit_status: i32,
pub id: ReportId,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ReportId {
pub host: String,
pub job: String,
pub pid: u64,
pub time: chrono::DateTime<chrono::offset::Utc>,
pub uuid: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ReportOutputBody {
pub id: ReportId,
pub record: OutputRecord,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ReportResult {
pub existed_already: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ReportStartBody {
pub id: ReportId,
pub script: String,
pub start_time: chrono::DateTime<chrono::offset::Utc>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ReportSummary {
pub age_seconds: i32,
pub duration_seconds: i32,
pub host: String,
pub job: String,
pub status: i32,
pub when: chrono::DateTime<chrono::offset::Utc>,
}
}
#[derive(Clone)]
pub struct Client {
pub(crate) baseurl: String,
pub(crate) client: reqwest::Client,
}
impl Client {
pub fn new(baseurl: &str) -> Self {
let dur = std::time::Duration::from_secs(15);
let client = reqwest::ClientBuilder::new()
.connect_timeout(dur)
.timeout(dur)
.build()
.unwrap();
Self::new_with_client(baseurl, client)
}
pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self {
Self {
baseurl: baseurl.to_string(),
client,
}
}
pub fn baseurl(&self) -> &String {
&self.baseurl
}
pub fn client(&self) -> &reqwest::Client {
&self.client
}
}
impl Client {
///Sends a `POST` request to `/enrol`
///```ignore
/// let response = client.enrol()
/// .body(body)
/// .send()
/// .await;
/// ```
pub fn enrol(&self) -> builder::Enrol {
builder::Enrol::new(self)
}
///Sends a `GET` request to `/global/jobs`
///```ignore
/// let response = client.global_jobs()
/// .send()
/// .await;
/// ```
pub fn global_jobs(&self) -> builder::GlobalJobs {
builder::GlobalJobs::new(self)
}
///Sends a `GET` request to `/ping`
///```ignore
/// let response = client.ping()
/// .send()
/// .await;
/// ```
pub fn ping(&self) -> builder::Ping {
builder::Ping::new(self)
}
///Sends a `POST` request to `/report/finish`
///```ignore
/// let response = client.report_finish()
/// .body(body)
/// .send()
/// .await;
/// ```
pub fn report_finish(&self) -> builder::ReportFinish {
builder::ReportFinish::new(self)
}
///Sends a `POST` request to `/report/output`
///```ignore
/// let response = client.report_output()
/// .body(body)
/// .send()
/// .await;
/// ```
pub fn report_output(&self) -> builder::ReportOutput {
builder::ReportOutput::new(self)
}
///Sends a `POST` request to `/report/start`
///```ignore
/// let response = client.report_start()
/// .body(body)
/// .send()
/// .await;
/// ```
pub fn report_start(&self) -> builder::ReportStart {
builder::ReportStart::new(self)
}
}
pub mod builder {
#[allow(unused_imports)]
use super::encode_path;
use super::types;
#[allow(unused_imports)]
use super::{ByteStream, Error, ResponseValue};
///Builder for [`Client::enrol`]
///
///[`Client::enrol`]: super::Client::enrol
#[derive(Clone)]
pub struct Enrol<'a> {
client: &'a super::Client,
body: Option<types::EnrolBody>,
}
impl<'a> Enrol<'a> {
pub fn new(client: &'a super::Client) -> Self {
Self { client, body: None }
}
pub fn body(mut self, value: types::EnrolBody) -> Self {
self.body = Some(value);
self
}
///Sends a `POST` request to `/enrol`
pub async fn send(self) -> Result<ResponseValue<()>, Error<()>> {
let Self { client, body } = self;
let (body,) = match (body,) {
(Some(body),) => (body,),
(body,) => {
let mut missing = Vec::new();
if body.is_none() {
missing.push(stringify!(body));
}
return Err(super::Error::InvalidRequest(format!(
"the following parameters are required: {}",
missing.join(", "),
)));
}
};
let url = format!("{}/enrol", client.baseurl,);
let request = client.client.post(url).json(&body).build()?;
let result = client.client.execute(request).await;
let response = result?;
match response.status().as_u16() {
201u16 => Ok(ResponseValue::empty(response)),
_ => Err(Error::UnexpectedResponse(response)),
}
}
}
///Builder for [`Client::global_jobs`]
///
///[`Client::global_jobs`]: super::Client::global_jobs
#[derive(Clone)]
pub struct GlobalJobs<'a> {
client: &'a super::Client,
}
impl<'a> GlobalJobs<'a> {
pub fn new(client: &'a super::Client) -> Self {
Self { client }
}
///Sends a `GET` request to `/global/jobs`
pub async fn send(self) -> Result<ResponseValue<types::GlobalJobsResult>, Error<()>> {
let Self { client } = self;
let url = format!("{}/global/jobs", client.baseurl,);
let request = client.client.get(url).build()?;
let result = client.client.execute(request).await;
let response = result?;
match response.status().as_u16() {
201u16 => ResponseValue::from_response(response).await,
_ => Err(Error::UnexpectedResponse(response)),
}
}
}
///Builder for [`Client::ping`]
///
///[`Client::ping`]: super::Client::ping
#[derive(Clone)]
pub struct Ping<'a> {
client: &'a super::Client,
}
impl<'a> Ping<'a> {
pub fn new(client: &'a super::Client) -> Self {
Self { client }
}
///Sends a `GET` request to `/ping`
pub async fn send(self) -> Result<ResponseValue<types::PingResult>, Error<()>> {
let Self { client } = self;
let url = format!("{}/ping", client.baseurl,);
let request = client.client.get(url).build()?;
let result = client.client.execute(request).await;
let response = result?;
match response.status().as_u16() {
201u16 => ResponseValue::from_response(response).await,
_ => Err(Error::UnexpectedResponse(response)),
}
}
}
///Builder for [`Client::report_finish`]
///
///[`Client::report_finish`]: super::Client::report_finish
#[derive(Clone)]
pub struct ReportFinish<'a> {
client: &'a super::Client,
body: Option<types::ReportFinishBody>,
}
impl<'a> ReportFinish<'a> {
pub fn new(client: &'a super::Client) -> Self {
Self { client, body: None }
}
pub fn body(mut self, value: types::ReportFinishBody) -> Self {
self.body = Some(value);
self
}
///Sends a `POST` request to `/report/finish`
pub async fn send(self) -> Result<ResponseValue<types::ReportResult>, Error<()>> {
let Self { client, body } = self;
let (body,) = match (body,) {
(Some(body),) => (body,),
(body,) => {
let mut missing = Vec::new();
if body.is_none() {
missing.push(stringify!(body));
}
return Err(super::Error::InvalidRequest(format!(
"the following parameters are required: {}",
missing.join(", "),
)));
}
};
let url = format!("{}/report/finish", client.baseurl,);
let request = client.client.post(url).json(&body).build()?;
let result = client.client.execute(request).await;
let response = result?;
match response.status().as_u16() {
201u16 => ResponseValue::from_response(response).await,
_ => Err(Error::UnexpectedResponse(response)),
}
}
}
///Builder for [`Client::report_output`]
///
///[`Client::report_output`]: super::Client::report_output
#[derive(Clone)]
pub struct ReportOutput<'a> {
client: &'a super::Client,
body: Option<types::ReportOutputBody>,
}
impl<'a> ReportOutput<'a> {
pub fn new(client: &'a super::Client) -> Self {
Self { client, body: None }
}
pub fn body(mut self, value: types::ReportOutputBody) -> Self {
self.body = Some(value);
self
}
///Sends a `POST` request to `/report/output`
pub async fn send(self) -> Result<ResponseValue<types::ReportResult>, Error<()>> {
let Self { client, body } = self;
let (body,) = match (body,) {
(Some(body),) => (body,),
(body,) => {
let mut missing = Vec::new();
if body.is_none() {
missing.push(stringify!(body));
}
return Err(super::Error::InvalidRequest(format!(
"the following parameters are required: {}",
missing.join(", "),
)));
}
};
let url = format!("{}/report/output", client.baseurl,);
let request = client.client.post(url).json(&body).build()?;
let result = client.client.execute(request).await;
let response = result?;
match response.status().as_u16() {
201u16 => ResponseValue::from_response(response).await,
_ => Err(Error::UnexpectedResponse(response)),
}
}
}
///Builder for [`Client::report_start`]
///
///[`Client::report_start`]: super::Client::report_start
#[derive(Clone)]
pub struct ReportStart<'a> {
client: &'a super::Client,
body: Option<types::ReportStartBody>,
}
impl<'a> ReportStart<'a> {
pub fn new(client: &'a super::Client) -> Self {
Self { client, body: None }
}
pub fn body(mut self, value: types::ReportStartBody) -> Self {
self.body = Some(value);
self
}
///Sends a `POST` request to `/report/start`
pub async fn send(self) -> Result<ResponseValue<types::ReportResult>, Error<()>> {
let Self { client, body } = self;
let (body,) = match (body,) {
(Some(body),) => (body,),
(body,) => {
let mut missing = Vec::new();
if body.is_none() {
missing.push(stringify!(body));
}
return Err(super::Error::InvalidRequest(format!(
"the following parameters are required: {}",
missing.join(", "),
)));
}
};
let url = format!("{}/report/start", client.baseurl,);
let request = client.client.post(url).json(&body).build()?;
let result = client.client.execute(request).await;
let response = result?;
match response.status().as_u16() {
201u16 => ResponseValue::from_response(response).await,
_ => Err(Error::UnexpectedResponse(response)),
}
}
}
}

View File

@ -0,0 +1,418 @@
#[allow(unused_imports)]
use progenitor_client::encode_path;
pub use progenitor_client::{ByteStream, Error, ResponseValue};
pub mod types {
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EnrolBody {
pub host: String,
pub key: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GlobalJobsResult {
pub summary: Vec<ReportSummary>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct OutputRecord {
pub msg: String,
pub stream: String,
pub time: chrono::DateTime<chrono::offset::Utc>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PingResult {
pub host: String,
pub ok: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ReportFinishBody {
pub duration_millis: i32,
pub end_time: chrono::DateTime<chrono::offset::Utc>,
pub exit_status: i32,
pub id: ReportId,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ReportId {
pub host: String,
pub job: String,
pub pid: u64,
pub time: chrono::DateTime<chrono::offset::Utc>,
pub uuid: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ReportOutputBody {
pub id: ReportId,
pub record: OutputRecord,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ReportResult {
pub existed_already: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ReportStartBody {
pub id: ReportId,
pub script: String,
pub start_time: chrono::DateTime<chrono::offset::Utc>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ReportSummary {
pub age_seconds: i32,
pub duration_seconds: i32,
pub host: String,
pub job: String,
pub status: i32,
pub when: chrono::DateTime<chrono::offset::Utc>,
}
}
#[derive(Clone)]
pub struct Client {
pub(crate) baseurl: String,
pub(crate) client: reqwest::Client,
}
impl Client {
pub fn new(baseurl: &str) -> Self {
let dur = std::time::Duration::from_secs(15);
let client = reqwest::ClientBuilder::new()
.connect_timeout(dur)
.timeout(dur)
.build()
.unwrap();
Self::new_with_client(baseurl, client)
}
pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self {
Self {
baseurl: baseurl.to_string(),
client,
}
}
pub fn baseurl(&self) -> &String {
&self.baseurl
}
pub fn client(&self) -> &reqwest::Client {
&self.client
}
}
impl Client {
///Sends a `POST` request to `/enrol`
///```ignore
/// let response = client.enrol()
/// .body(body)
/// .send()
/// .await;
/// ```
pub fn enrol(&self) -> builder::Enrol {
builder::Enrol::new(self)
}
///Sends a `GET` request to `/global/jobs`
///```ignore
/// let response = client.global_jobs()
/// .send()
/// .await;
/// ```
pub fn global_jobs(&self) -> builder::GlobalJobs {
builder::GlobalJobs::new(self)
}
///Sends a `GET` request to `/ping`
///```ignore
/// let response = client.ping()
/// .send()
/// .await;
/// ```
pub fn ping(&self) -> builder::Ping {
builder::Ping::new(self)
}
///Sends a `POST` request to `/report/finish`
///```ignore
/// let response = client.report_finish()
/// .body(body)
/// .send()
/// .await;
/// ```
pub fn report_finish(&self) -> builder::ReportFinish {
builder::ReportFinish::new(self)
}
///Sends a `POST` request to `/report/output`
///```ignore
/// let response = client.report_output()
/// .body(body)
/// .send()
/// .await;
/// ```
pub fn report_output(&self) -> builder::ReportOutput {
builder::ReportOutput::new(self)
}
///Sends a `POST` request to `/report/start`
///```ignore
/// let response = client.report_start()
/// .body(body)
/// .send()
/// .await;
/// ```
pub fn report_start(&self) -> builder::ReportStart {
builder::ReportStart::new(self)
}
}
pub mod builder {
#[allow(unused_imports)]
use super::encode_path;
use super::types;
#[allow(unused_imports)]
use super::{ByteStream, Error, ResponseValue};
///Builder for [`Client::enrol`]
///
///[`Client::enrol`]: super::Client::enrol
#[derive(Clone)]
pub struct Enrol<'a> {
client: &'a super::Client,
body: Option<types::EnrolBody>,
}
impl<'a> Enrol<'a> {
pub fn new(client: &'a super::Client) -> Self {
Self { client, body: None }
}
pub fn body(mut self, value: types::EnrolBody) -> Self {
self.body = Some(value);
self
}
///Sends a `POST` request to `/enrol`
pub async fn send(self) -> Result<ResponseValue<()>, Error<()>> {
let Self { client, body } = self;
let (body,) = match (body,) {
(Some(body),) => (body,),
(body,) => {
let mut missing = Vec::new();
if body.is_none() {
missing.push(stringify!(body));
}
return Err(super::Error::InvalidRequest(format!(
"the following parameters are required: {}",
missing.join(", "),
)));
}
};
let url = format!("{}/enrol", client.baseurl,);
let request = client.client.post(url).json(&body).build()?;
let result = client.client.execute(request).await;
let response = result?;
match response.status().as_u16() {
201u16 => Ok(ResponseValue::empty(response)),
_ => Err(Error::UnexpectedResponse(response)),
}
}
}
///Builder for [`Client::global_jobs`]
///
///[`Client::global_jobs`]: super::Client::global_jobs
#[derive(Clone)]
pub struct GlobalJobs<'a> {
client: &'a super::Client,
}
impl<'a> GlobalJobs<'a> {
pub fn new(client: &'a super::Client) -> Self {
Self { client }
}
///Sends a `GET` request to `/global/jobs`
pub async fn send(self) -> Result<ResponseValue<types::GlobalJobsResult>, Error<()>> {
let Self { client } = self;
let url = format!("{}/global/jobs", client.baseurl,);
let request = client.client.get(url).build()?;
let result = client.client.execute(request).await;
let response = result?;
match response.status().as_u16() {
201u16 => ResponseValue::from_response(response).await,
_ => Err(Error::UnexpectedResponse(response)),
}
}
}
///Builder for [`Client::ping`]
///
///[`Client::ping`]: super::Client::ping
#[derive(Clone)]
pub struct Ping<'a> {
client: &'a super::Client,
}
impl<'a> Ping<'a> {
pub fn new(client: &'a super::Client) -> Self {
Self { client }
}
///Sends a `GET` request to `/ping`
pub async fn send(self) -> Result<ResponseValue<types::PingResult>, Error<()>> {
let Self { client } = self;
let url = format!("{}/ping", client.baseurl,);
let request = client.client.get(url).build()?;
let result = client.client.execute(request).await;
let response = result?;
match response.status().as_u16() {
201u16 => ResponseValue::from_response(response).await,
_ => Err(Error::UnexpectedResponse(response)),
}
}
}
///Builder for [`Client::report_finish`]
///
///[`Client::report_finish`]: super::Client::report_finish
#[derive(Clone)]
pub struct ReportFinish<'a> {
client: &'a super::Client,
body: Option<types::ReportFinishBody>,
}
impl<'a> ReportFinish<'a> {
pub fn new(client: &'a super::Client) -> Self {
Self { client, body: None }
}
pub fn body(mut self, value: types::ReportFinishBody) -> Self {
self.body = Some(value);
self
}
///Sends a `POST` request to `/report/finish`
pub async fn send(self) -> Result<ResponseValue<types::ReportResult>, Error<()>> {
let Self { client, body } = self;
let (body,) = match (body,) {
(Some(body),) => (body,),
(body,) => {
let mut missing = Vec::new();
if body.is_none() {
missing.push(stringify!(body));
}
return Err(super::Error::InvalidRequest(format!(
"the following parameters are required: {}",
missing.join(", "),
)));
}
};
let url = format!("{}/report/finish", client.baseurl,);
let request = client.client.post(url).json(&body).build()?;
let result = client.client.execute(request).await;
let response = result?;
match response.status().as_u16() {
201u16 => ResponseValue::from_response(response).await,
_ => Err(Error::UnexpectedResponse(response)),
}
}
}
///Builder for [`Client::report_output`]
///
///[`Client::report_output`]: super::Client::report_output
#[derive(Clone)]
pub struct ReportOutput<'a> {
client: &'a super::Client,
body: Option<types::ReportOutputBody>,
}
impl<'a> ReportOutput<'a> {
pub fn new(client: &'a super::Client) -> Self {
Self { client, body: None }
}
pub fn body(mut self, value: types::ReportOutputBody) -> Self {
self.body = Some(value);
self
}
///Sends a `POST` request to `/report/output`
pub async fn send(self) -> Result<ResponseValue<types::ReportResult>, Error<()>> {
let Self { client, body } = self;
let (body,) = match (body,) {
(Some(body),) => (body,),
(body,) => {
let mut missing = Vec::new();
if body.is_none() {
missing.push(stringify!(body));
}
return Err(super::Error::InvalidRequest(format!(
"the following parameters are required: {}",
missing.join(", "),
)));
}
};
let url = format!("{}/report/output", client.baseurl,);
let request = client.client.post(url).json(&body).build()?;
let result = client.client.execute(request).await;
let response = result?;
match response.status().as_u16() {
201u16 => ResponseValue::from_response(response).await,
_ => Err(Error::UnexpectedResponse(response)),
}
}
}
///Builder for [`Client::report_start`]
///
///[`Client::report_start`]: super::Client::report_start
#[derive(Clone)]
pub struct ReportStart<'a> {
client: &'a super::Client,
body: Option<types::ReportStartBody>,
}
impl<'a> ReportStart<'a> {
pub fn new(client: &'a super::Client) -> Self {
Self { client, body: None }
}
pub fn body(mut self, value: types::ReportStartBody) -> Self {
self.body = Some(value);
self
}
///Sends a `POST` request to `/report/start`
pub async fn send(self) -> Result<ResponseValue<types::ReportResult>, Error<()>> {
let Self { client, body } = self;
let (body,) = match (body,) {
(Some(body),) => (body,),
(body,) => {
let mut missing = Vec::new();
if body.is_none() {
missing.push(stringify!(body));
}
return Err(super::Error::InvalidRequest(format!(
"the following parameters are required: {}",
missing.join(", "),
)));
}
};
let url = format!("{}/report/start", client.baseurl,);
let request = client.client.post(url).json(&body).build()?;
let result = client.client.execute(request).await;
let response = result?;
match response.status().as_u16() {
201u16 => ResponseValue::from_response(response).await,
_ => Err(Error::UnexpectedResponse(response)),
}
}
}
}

View File

@ -1,3 +1,5 @@
#[allow(unused_imports)]
use progenitor_client::encode_path;
pub use progenitor_client::{ByteStream, Error, ResponseValue}; pub use progenitor_client::{ByteStream, Error, ResponseValue};
pub mod types { pub mod types {
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -73,8 +75,8 @@ pub mod types {
#[derive(Clone)] #[derive(Clone)]
pub struct Client { pub struct Client {
baseurl: String, pub(crate) baseurl: String,
client: reqwest::Client, pub(crate) client: reqwest::Client,
} }
impl Client { impl Client {
@ -102,14 +104,16 @@ impl Client {
pub fn client(&self) -> &reqwest::Client { pub fn client(&self) -> &reqwest::Client {
&self.client &self.client
} }
}
#[doc = "Sends a `POST` request to `/enrol`"] impl Client {
///Sends a `POST` request to `/enrol`
pub async fn enrol<'a>( pub async fn enrol<'a>(
&'a self, &'a self,
body: &'a types::EnrolBody, body: &'a types::EnrolBody,
) -> Result<ResponseValue<()>, Error<()>> { ) -> Result<ResponseValue<()>, Error<()>> {
let url = format!("{}/enrol", self.baseurl,); let url = format!("{}/enrol", self.baseurl,);
let request = self.client.post(url).json(body).build()?; let request = self.client.post(url).json(&body).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
let response = result?; let response = result?;
match response.status().as_u16() { match response.status().as_u16() {
@ -118,7 +122,7 @@ impl Client {
} }
} }
#[doc = "Sends a `GET` request to `/global/jobs`"] ///Sends a `GET` request to `/global/jobs`
pub async fn global_jobs<'a>( pub async fn global_jobs<'a>(
&'a self, &'a self,
) -> Result<ResponseValue<types::GlobalJobsResult>, Error<()>> { ) -> Result<ResponseValue<types::GlobalJobsResult>, Error<()>> {
@ -132,7 +136,7 @@ impl Client {
} }
} }
#[doc = "Sends a `GET` request to `/ping`"] ///Sends a `GET` request to `/ping`
pub async fn ping<'a>(&'a self) -> Result<ResponseValue<types::PingResult>, Error<()>> { pub async fn ping<'a>(&'a self) -> Result<ResponseValue<types::PingResult>, Error<()>> {
let url = format!("{}/ping", self.baseurl,); let url = format!("{}/ping", self.baseurl,);
let request = self.client.get(url).build()?; let request = self.client.get(url).build()?;
@ -144,13 +148,13 @@ impl Client {
} }
} }
#[doc = "Sends a `POST` request to `/report/finish`"] ///Sends a `POST` request to `/report/finish`
pub async fn report_finish<'a>( pub async fn report_finish<'a>(
&'a self, &'a self,
body: &'a types::ReportFinishBody, body: &'a types::ReportFinishBody,
) -> Result<ResponseValue<types::ReportResult>, Error<()>> { ) -> Result<ResponseValue<types::ReportResult>, Error<()>> {
let url = format!("{}/report/finish", self.baseurl,); let url = format!("{}/report/finish", self.baseurl,);
let request = self.client.post(url).json(body).build()?; let request = self.client.post(url).json(&body).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
let response = result?; let response = result?;
match response.status().as_u16() { match response.status().as_u16() {
@ -159,13 +163,13 @@ impl Client {
} }
} }
#[doc = "Sends a `POST` request to `/report/output`"] ///Sends a `POST` request to `/report/output`
pub async fn report_output<'a>( pub async fn report_output<'a>(
&'a self, &'a self,
body: &'a types::ReportOutputBody, body: &'a types::ReportOutputBody,
) -> Result<ResponseValue<types::ReportResult>, Error<()>> { ) -> Result<ResponseValue<types::ReportResult>, Error<()>> {
let url = format!("{}/report/output", self.baseurl,); let url = format!("{}/report/output", self.baseurl,);
let request = self.client.post(url).json(body).build()?; let request = self.client.post(url).json(&body).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
let response = result?; let response = result?;
match response.status().as_u16() { match response.status().as_u16() {
@ -174,13 +178,13 @@ impl Client {
} }
} }
#[doc = "Sends a `POST` request to `/report/start`"] ///Sends a `POST` request to `/report/start`
pub async fn report_start<'a>( pub async fn report_start<'a>(
&'a self, &'a self,
body: &'a types::ReportStartBody, body: &'a types::ReportStartBody,
) -> Result<ResponseValue<types::ReportResult>, Error<()>> { ) -> Result<ResponseValue<types::ReportResult>, Error<()>> {
let url = format!("{}/report/start", self.baseurl,); let url = format!("{}/report/start", self.baseurl,);
let request = self.client.post(url).json(body).build()?; let request = self.client.post(url).json(&body).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
let response = result?; let response = result?;
match response.status().as_u16() { match response.status().as_u16() {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
#[allow(unused_imports)]
use progenitor_client::encode_path;
pub use progenitor_client::{ByteStream, Error, ResponseValue}; pub use progenitor_client::{ByteStream, Error, ResponseValue};
pub mod types { pub mod types {
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -20,7 +22,7 @@ pub mod types {
pub yes: bool, pub yes: bool,
} }
#[doc = "Error information from a response."] ///Error information from a response.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Error { pub struct Error {
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
@ -32,8 +34,8 @@ pub mod types {
#[derive(Clone)] #[derive(Clone)]
pub struct Client { pub struct Client {
baseurl: String, pub(crate) baseurl: String,
client: reqwest::Client, pub(crate) client: reqwest::Client,
} }
impl Client { impl Client {
@ -61,14 +63,16 @@ impl Client {
pub fn client(&self) -> &reqwest::Client { pub fn client(&self) -> &reqwest::Client {
&self.client &self.client
} }
}
#[doc = "Sends a `POST` request to ``"] impl Client {
///Sends a `POST` request to ``
pub async fn default_params<'a>( pub async fn default_params<'a>(
&'a self, &'a self,
body: &'a types::BodyWithDefaults, body: &'a types::BodyWithDefaults,
) -> Result<ResponseValue<ByteStream>, Error<ByteStream>> { ) -> Result<ResponseValue<ByteStream>, Error<ByteStream>> {
let url = format!("{}", self.baseurl,); let url = format!("{}", self.baseurl,);
let request = self.client.post(url).json(body).build()?; let request = self.client.post(url).json(&body).build()?;
let result = self.client.execute(request).await; let result = self.client.execute(request).await;
let response = result?; let response = result?;
match response.status().as_u16() { match response.status().as_u16() {

View File

@ -1,7 +1,9 @@
#[allow(unused_imports)]
use progenitor_client::encode_path;
pub use progenitor_client::{ByteStream, Error, ResponseValue}; pub use progenitor_client::{ByteStream, Error, ResponseValue};
pub mod types { pub mod types {
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[doc = "Error information from a response."] ///Error information from a response.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Error { pub struct Error {
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
@ -13,8 +15,8 @@ pub mod types {
#[derive(Clone)] #[derive(Clone)]
pub struct Client { pub struct Client {
baseurl: String, pub(crate) baseurl: String,
client: reqwest::Client, pub(crate) client: reqwest::Client,
} }
impl Client { impl Client {
@ -42,8 +44,10 @@ impl Client {
pub fn client(&self) -> &reqwest::Client { pub fn client(&self) -> &reqwest::Client {
&self.client &self.client
} }
}
#[doc = "Sends a `GET` request to ``"] impl Client {
///Sends a `GET` request to ``
pub async fn freeform_response<'a>( pub async fn freeform_response<'a>(
&'a self, &'a self,
) -> Result<ResponseValue<ByteStream>, Error<ByteStream>> { ) -> Result<ResponseValue<ByteStream>, Error<ByteStream>> {

View File

@ -1,7 +1,9 @@
#[allow(unused_imports)]
use progenitor_client::encode_path;
pub use progenitor_client::{ByteStream, Error, ResponseValue}; pub use progenitor_client::{ByteStream, Error, ResponseValue};
pub mod types { pub mod types {
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[doc = "Error information from a response."] ///Error information from a response.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Error { pub struct Error {
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
@ -13,8 +15,8 @@ pub mod types {
#[derive(Clone)] #[derive(Clone)]
pub struct Client { pub struct Client {
baseurl: String, pub(crate) baseurl: String,
client: reqwest::Client, pub(crate) client: reqwest::Client,
} }
impl Client { impl Client {
@ -42,8 +44,10 @@ impl Client {
pub fn client(&self) -> &reqwest::Client { pub fn client(&self) -> &reqwest::Client {
&self.client &self.client
} }
}
#[doc = "Sends a `GET` request to `/{ref}/{type}/{trait}`"] impl Client {
///Sends a `GET` request to `/{ref}/{type}/{trait}`
pub async fn renamed_parameters<'a>( pub async fn renamed_parameters<'a>(
&'a self, &'a self,
ref_: &'a str, ref_: &'a str,
@ -56,9 +60,9 @@ impl Client {
let url = format!( let url = format!(
"{}/{}/{}/{}", "{}/{}/{}/{}",
self.baseurl, self.baseurl,
progenitor_client::encode_path(&ref_.to_string()), encode_path(&ref_.to_string()),
progenitor_client::encode_path(&type_.to_string()), encode_path(&type_.to_string()),
progenitor_client::encode_path(&trait_.to_string()), encode_path(&trait_.to_string()),
); );
let mut query = Vec::new(); let mut query = Vec::new();
query.push(("if", if_.to_string())); query.push(("if", if_.to_string()));

View File

@ -2,36 +2,61 @@
use std::{fs::File, path::PathBuf}; use std::{fs::File, path::PathBuf};
use progenitor_impl::Generator; use progenitor_impl::{
GenerationSettings, Generator, InterfaceStyle, TagStyle,
};
#[track_caller] #[track_caller]
fn verify_file(openapi_file: &str) { fn verify_apis(openapi_file: &str) {
let mut in_path = PathBuf::from("../sample_openapi"); let mut in_path = PathBuf::from("../sample_openapi");
in_path.push(format!("{}.json", openapi_file)); in_path.push(format!("{}.json", openapi_file));
let file = File::open(in_path).unwrap(); let file = File::open(in_path).unwrap();
let spec = serde_json::from_reader(file).unwrap(); let spec = serde_json::from_reader(file).unwrap();
let mut generator = Generator::new();
let output = generator.generate_text(&spec).unwrap(); let mut generator = Generator::default();
let output = generator.generate_text_normalize_comments(&spec).unwrap();
expectorate::assert_contents( expectorate::assert_contents(
format!("tests/output/{}.out", openapi_file), format!("tests/output/{}-positional.out", openapi_file),
&output, &output,
) );
let mut generator = Generator::new(
GenerationSettings::default()
.with_interface(InterfaceStyle::Builder)
.with_tag(TagStyle::Merged),
);
let output = generator.generate_text_normalize_comments(&spec).unwrap();
expectorate::assert_contents(
format!("tests/output/{}-builder.out", openapi_file),
&output,
);
let mut generator = Generator::new(
GenerationSettings::default()
.with_interface(InterfaceStyle::Builder)
.with_tag(TagStyle::Separate),
);
let output = generator.generate_text_normalize_comments(&spec).unwrap();
expectorate::assert_contents(
format!("tests/output/{}-builder-tagged.out", openapi_file),
&output,
);
} }
#[test] #[test]
fn test_keeper() { fn test_keeper() {
verify_file("keeper"); verify_apis("keeper");
} }
#[test] #[test]
fn test_buildomat() { fn test_buildomat() {
verify_file("buildomat"); verify_apis("buildomat");
} }
#[test] #[test]
fn test_nexus() { fn test_nexus() {
verify_file("nexus"); verify_apis("nexus");
} }
// TODO this file is full of inconsistencies and incorrectly specified types. // TODO this file is full of inconsistencies and incorrectly specified types.
@ -40,5 +65,5 @@ fn test_nexus() {
#[ignore] #[ignore]
#[test] #[test]
fn test_github() { fn test_github() {
verify_file("api.github.com"); verify_apis("api.github.com");
} }

View File

@ -64,8 +64,8 @@ fn test_renamed_parameters() {
let spec = serde_json::from_str::<OpenAPI>(out).unwrap(); let spec = serde_json::from_str::<OpenAPI>(out).unwrap();
let mut generator = Generator::new(); let mut generator = Generator::default();
let output = generator.generate_text(&spec).unwrap(); let output = generator.generate_text_normalize_comments(&spec).unwrap();
expectorate::assert_contents( expectorate::assert_contents(
format!("tests/output/{}.out", "test_renamed_parameters"), format!("tests/output/{}.out", "test_renamed_parameters"),
&output, &output,
@ -97,8 +97,8 @@ fn test_freeform_response() {
let out = from_utf8(&out).unwrap(); let out = from_utf8(&out).unwrap();
let spec = serde_json::from_str::<OpenAPI>(out).unwrap(); let spec = serde_json::from_str::<OpenAPI>(out).unwrap();
let mut generator = Generator::new(); let mut generator = Generator::default();
let output = generator.generate_text(&spec).unwrap(); let output = generator.generate_text_normalize_comments(&spec).unwrap();
expectorate::assert_contents( expectorate::assert_contents(
format!("tests/output/{}.out", "test_freeform_response"), format!("tests/output/{}.out", "test_freeform_response"),
&output, &output,
@ -145,8 +145,8 @@ fn test_default_params() {
let out = from_utf8(&out).unwrap(); let out = from_utf8(&out).unwrap();
let spec = serde_json::from_str::<OpenAPI>(out).unwrap(); let spec = serde_json::from_str::<OpenAPI>(out).unwrap();
let mut generator = Generator::new(); let mut generator = Generator::default();
let output = generator.generate_text(&spec).unwrap(); let output = generator.generate_text_normalize_comments(&spec).unwrap();
expectorate::assert_contents( expectorate::assert_contents(
format!("tests/output/{}.out", "test_default_params"), format!("tests/output/{}.out", "test_default_params"),
&output, &output,

View File

@ -1,7 +1,7 @@
[package] [package]
name = "progenitor-macro" name = "progenitor-macro"
version = "0.1.2-dev" version = "0.1.2-dev"
edition = "2018" edition = "2021"
license = "MPL-2.0" license = "MPL-2.0"
repository = "https://github.com/oxidecomputer/progenitor.git" repository = "https://github.com/oxidecomputer/progenitor.git"
description = "An OpenAPI client generator - macros" description = "An OpenAPI client generator - macros"

View File

@ -4,7 +4,9 @@ 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::{
GenerationSettings, Generator, InterfaceStyle, TagStyle,
};
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use serde::Deserialize; use serde::Deserialize;
use serde_tokenstream::ParseWrapper; use serde_tokenstream::ParseWrapper;
@ -22,7 +24,8 @@ use syn::LitStr;
/// ```ignore /// ```ignore
/// generate_api!( /// generate_api!(
/// spec = "path/to/spec.json", /// spec = "path/to/spec.json",
/// [ inner_type = path::to:Type, ] /// [ interface = ( Positional | Builder ), ]
/// [ tags = ( Merged | Separate ), ]
/// [ pre_hook = closure::or::path::to::function, ] /// [ pre_hook = closure::or::path::to::function, ]
/// [ post_hook = closure::or::path::to::function, ] /// [ post_hook = closure::or::path::to::function, ]
/// [ derives = [ path::to::DeriveMacro ], ] /// [ derives = [ path::to::DeriveMacro ], ]
@ -32,6 +35,14 @@ use syn::LitStr;
/// The `spec` key is required; it is the OpenAPI document from which the /// The `spec` key is required; it is the OpenAPI document from which the
/// client is derived. /// client is derived.
/// ///
/// The optional `interface` lets you specify either a `Positional` argument or
/// `Builder` argument style; `Positional` is the default.
///
/// The optional `tags` may be `Merged` in which case all operations are
/// methods on the `Client` struct or `Separate` in which case each tag is
/// represented by an "extension trait" that `Client` implements. The default
/// is `Merged`.
///
/// The optional `inner_type` is for ancillary data, stored with the generated /// The optional `inner_type` is for ancillary data, stored with the generated
/// client that can be usd by the pre and post hooks. /// client that can be usd by the pre and post hooks.
/// ///
@ -58,8 +69,12 @@ pub fn generate_api(item: TokenStream) -> TokenStream {
} }
#[derive(Deserialize)] #[derive(Deserialize)]
struct Settings { struct MacroSettings {
spec: ParseWrapper<LitStr>, spec: ParseWrapper<LitStr>,
#[serde(default)]
interface: InterfaceStyle,
#[serde(default)]
tags: TagStyle,
inner_type: Option<ParseWrapper<syn::Type>>, inner_type: Option<ParseWrapper<syn::Type>>,
pre_hook: Option<ParseWrapper<ClosureOrPath>>, pre_hook: Option<ParseWrapper<ClosureOrPath>>,
post_hook: Option<ParseWrapper<ClosureOrPath>>, post_hook: Option<ParseWrapper<ClosureOrPath>>,
@ -67,6 +82,18 @@ struct Settings {
derives: Vec<ParseWrapper<syn::Path>>, derives: Vec<ParseWrapper<syn::Path>>,
} }
#[derive(Deserialize)]
enum GenerationStyle {
Positional,
Builder,
}
impl Default for GenerationStyle {
fn default() -> Self {
Self::Positional
}
}
#[derive(Debug)] #[derive(Debug)]
struct ClosureOrPath(proc_macro2::TokenStream); struct ClosureOrPath(proc_macro2::TokenStream);
@ -90,24 +117,34 @@ impl syn::parse::Parse for ClosureOrPath {
} }
fn do_generate_api(item: TokenStream) -> Result<TokenStream, syn::Error> { fn do_generate_api(item: TokenStream) -> Result<TokenStream, syn::Error> {
let (spec, inner_type, pre_hook, post_hook, derives) = let (spec, settings) = if let Ok(spec) = syn::parse::<LitStr>(item.clone())
if let Ok(spec) = syn::parse::<LitStr>(item.clone()) { {
(spec, None, None, None, Vec::new()) (spec, GenerationSettings::default())
} else { } else {
let Settings { let MacroSettings {
spec, spec,
interface,
tags,
inner_type, inner_type,
pre_hook, pre_hook,
post_hook, post_hook,
derives, derives,
} = serde_tokenstream::from_tokenstream(&item.into())?; } = serde_tokenstream::from_tokenstream(&item.into())?;
( let mut settings = GenerationSettings::default();
spec.into_inner(), settings.with_interface(interface);
inner_type.map(|x| x.into_inner()), settings.with_tag(tags);
pre_hook.map(|x| x.into_inner()), inner_type.map(|inner_type| {
post_hook.map(|x| x.into_inner()), settings.with_inner_type(inner_type.to_token_stream())
derives.into_iter().map(ParseWrapper::into_inner).collect(), });
) pre_hook
.map(|pre_hook| settings.with_pre_hook(pre_hook.into_inner().0));
post_hook
.map(|post_hook| settings.with_post_hook(post_hook.into_inner().0));
derives.into_iter().for_each(|derive| {
settings.with_derive(derive.to_token_stream());
});
(spec.into_inner(), settings)
}; };
let dir = std::env::var("CARGO_MANIFEST_DIR").map_or_else( let dir = std::env::var("CARGO_MANIFEST_DIR").map_or_else(
@ -122,31 +159,22 @@ fn do_generate_api(item: TokenStream) -> Result<TokenStream, syn::Error> {
serde_json::from_reader(std::fs::File::open(&path).map_err(|e| { serde_json::from_reader(std::fs::File::open(&path).map_err(|e| {
syn::Error::new( syn::Error::new(
spec.span(), spec.span(),
format!("couldn't read file {}: {}", path_str, e.to_string()), format!("couldn't read file {}: {}", path_str, e),
) )
})?) })?)
.map_err(|e| { .map_err(|e| {
syn::Error::new( syn::Error::new(
spec.span(), spec.span(),
format!("failed to parse {}: {}", path_str, e.to_string()), format!("failed to parse {}: {}", path_str, e),
) )
})?; })?;
let mut builder = Generator::new(); let mut builder = Generator::new(&settings);
inner_type.map(|inner_type| {
builder.with_inner_type(inner_type.to_token_stream())
});
pre_hook.map(|pre_hook| builder.with_pre_hook(pre_hook.0));
post_hook.map(|post_hook| builder.with_post_hook(post_hook.0));
derives.into_iter().for_each(|derive| {
builder.with_derive(derive.to_token_stream());
});
let code = builder.generate_tokens(&oapi).map_err(|e| { let code = builder.generate_tokens(&oapi).map_err(|e| {
syn::Error::new( syn::Error::new(
spec.span(), spec.span(),
format!("generation error for {}: {}", spec.value(), e.to_string()), format!("generation error for {}: {}", spec.value(), e),
) )
})?; })?;

View File

@ -1,7 +1,7 @@
[package] [package]
name = "progenitor" name = "progenitor"
version = "0.1.2-dev" version = "0.1.2-dev"
edition = "2018" edition = "2021"
license = "MPL-2.0" license = "MPL-2.0"
repository = "https://github.com/oxidecomputer/progenitor.git" repository = "https://github.com/oxidecomputer/progenitor.git"
description = "An OpenAPI client generator" description = "An OpenAPI client generator"
@ -11,10 +11,10 @@ progenitor-client = { version = "0.1.2-dev", path = "../progenitor-client" }
progenitor-impl = { version = "0.1.2-dev", path = "../progenitor-impl" } progenitor-impl = { version = "0.1.2-dev", path = "../progenitor-impl" }
progenitor-macro = { version = "0.1.2-dev", path = "../progenitor-macro" } progenitor-macro = { version = "0.1.2-dev", path = "../progenitor-macro" }
anyhow = "1.0" anyhow = "1.0"
getopts = "0.2"
openapiv3 = "1.0.0" openapiv3 = "1.0.0"
serde = { version = "1.0", features = [ "derive" ] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
clap = { version = "3.2.8", features = ["derive"] }
[dev-dependencies] [dev-dependencies]
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }

View File

@ -12,5 +12,8 @@
pub use progenitor_client; pub use progenitor_client;
pub use progenitor_impl::Error; pub use progenitor_impl::Error;
pub use progenitor_impl::GenerationSettings;
pub use progenitor_impl::Generator; pub use progenitor_impl::Generator;
pub use progenitor_impl::InterfaceStyle;
pub use progenitor_impl::TagStyle;
pub use progenitor_macro::generate_api; pub use progenitor_macro::generate_api;

View File

@ -8,10 +8,64 @@ use std::{
}; };
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use clap::{Parser, ValueEnum};
use openapiv3::OpenAPI; use openapiv3::OpenAPI;
use progenitor::Generator; use progenitor::{GenerationSettings, Generator, InterfaceStyle, TagStyle};
use serde::Deserialize; use serde::Deserialize;
#[derive(Parser)]
struct Args {
/// OpenAPI definition document (JSON)
#[clap(short = 'i', long)]
input: String,
/// Output directory for Rust crate
#[clap(short = 'o', long)]
output: String,
/// Target Rust crate name
#[clap(short = 'n', long)]
name: String,
/// Target Rust crate version
#[clap(short = 'v', long)]
version: String,
/// SDK interface style
#[clap(value_enum, long, default_value_t = InterfaceArg::Positional)]
interface: InterfaceArg,
/// SDK tag style
#[clap(value_enum, long, default_value_t = TagArg::Merged)]
tags: TagArg,
}
#[derive(Copy, Clone, ValueEnum)]
enum InterfaceArg {
Positional,
Builder,
}
impl From<InterfaceArg> for InterfaceStyle {
fn from(arg: InterfaceArg) -> Self {
match arg {
InterfaceArg::Positional => InterfaceStyle::Positional,
InterfaceArg::Builder => InterfaceStyle::Builder,
}
}
}
#[derive(Copy, Clone, ValueEnum)]
enum TagArg {
Merged,
Separate,
}
impl From<TagArg> for TagStyle {
fn from(arg: TagArg) -> Self {
match arg {
TagArg::Merged => TagStyle::Merged,
TagArg::Separate => TagStyle::Separate,
}
}
}
fn save<P>(p: P, data: &str) -> Result<()> fn save<P>(p: P, data: &str) -> Result<()>
where where
P: AsRef<Path>, P: AsRef<Path>,
@ -28,30 +82,15 @@ where
} }
fn main() -> Result<()> { fn main() -> Result<()> {
let mut opts = getopts::Options::new(); let args = Args::parse();
opts.parsing_style(getopts::ParsingStyle::StopAtFirstFree); let api = load_api(&args.input)?;
opts.reqopt("i", "", "OpenAPI definition document (JSON)", "INPUT");
opts.reqopt("o", "", "Generated Rust crate directory", "OUTPUT");
opts.reqopt("n", "", "Target Rust crate name", "CRATE");
opts.reqopt("v", "", "Target Rust crate version", "VERSION");
let args = match opts.parse(std::env::args().skip(1)) { //let mut builder = Generator::default();
Ok(args) => { let mut builder = Generator::new(
if !args.free.is_empty() { GenerationSettings::default()
eprintln!("{}", opts.usage("progenitor")); .with_interface(args.interface.into())
bail!("unexpected positional arguments"); .with_tag(args.tags.into()),
} );
args
}
Err(e) => {
eprintln!("{}", opts.usage("progenitor"));
bail!(e);
}
};
let api = load_api(&args.opt_str("i").unwrap())?;
let mut builder = Generator::new();
match builder.generate_text(&api) { match builder.generate_text(&api) {
Ok(api_code) => { Ok(api_code) => {
@ -67,13 +106,13 @@ fn main() -> Result<()> {
println!("-----------------------------------------------------"); println!("-----------------------------------------------------");
println!(); println!();
let name = args.opt_str("n").unwrap(); let name = &args.name;
let version = args.opt_str("v").unwrap(); let version = &args.version;
/* /*
* Create the top-level crate directory: * Create the top-level crate directory:
*/ */
let root = PathBuf::from(args.opt_str("o").unwrap()); let root = PathBuf::from(&args.output);
std::fs::create_dir_all(&root)?; std::fs::create_dir_all(&root)?;
/* /*

View File

@ -1,3 +1,41 @@
// Copyright 2021 Oxide Computer Company // Copyright 2022 Oxide Computer Company
progenitor::generate_api!("../sample_openapi/buildomat.json"); mod positional {
progenitor::generate_api!("../sample_openapi/buildomat.json");
fn _ignore() {
let _ = Client::new("").worker_task_upload_chunk("task", vec![0]);
}
}
mod builder_untagged {
progenitor::generate_api!(
spec = "../sample_openapi/buildomat.json",
interface = Builder,
tags = Merged,
);
fn _ignore() {
let _ = Client::new("")
.worker_task_upload_chunk()
.task("task")
.body(vec![0])
.send();
}
}
mod builder_tagged {
progenitor::generate_api!(
spec = "../sample_openapi/buildomat.json",
interface = Builder,
tags = Separate,
);
fn _ignore() {
let _ = Client::new("")
.worker_task_upload_chunk()
.task("task")
.body(vec![0])
.send();
}
}

View File

@ -1,3 +1,48 @@
// Copyright 2021 Oxide Computer Company // Copyright 2022 Oxide Computer Company
progenitor::generate_api!("../sample_openapi/keeper.json"); mod positional {
progenitor::generate_api!("../sample_openapi/keeper.json");
fn _ignore() {
let _ = Client::new("").enrol(&types::EnrolBody {
host: "".to_string(),
key: "".to_string(),
});
}
}
mod builder_untagged {
progenitor::generate_api!(
spec = "../sample_openapi/keeper.json",
interface = Builder,
tags = Merged,
);
fn _ignore() {
let _ = Client::new("")
.enrol()
.body(types::EnrolBody {
host: "".to_string(),
key: "".to_string(),
})
.send();
}
}
mod builder_tagged {
progenitor::generate_api!(
spec = "../sample_openapi/keeper.json",
interface = Builder,
tags = Separate,
);
fn _ignore() {
let _ = Client::new("")
.enrol()
.body(types::EnrolBody {
host: "".to_string(),
key: "".to_string(),
})
.send();
}
}

View File

@ -1,23 +1,74 @@
// Copyright 2021 Oxide Computer Company // Copyright 2021 Oxide Computer Company
progenitor::generate_api!("../sample_openapi/nexus.json"); mod positional {
use futures::StreamExt;
pub async fn iteration_example() { mod nexus_client {
let client = Client::new("xxx"); progenitor::generate_api!("../sample_openapi/nexus.json");
let bod = types::LoginParams {
username: "ahl".to_string(),
};
let mut stream = client.spoof_login(&bod).await.unwrap();
loop {
use futures::TryStreamExt;
match stream.try_next().await {
Ok(Some(bytes)) => println!("bytes: {:?}", bytes),
Ok(None) => break,
Err(e) => panic!("{}", e),
} }
use nexus_client::{types, Client};
fn _ignore() {
let _ = async {
let client = Client::new("");
let org = types::Name("org".to_string());
let project = types::Name("project".to_string());
let instance = types::Name("instance".to_string());
let stream = client.instance_disks_get_stream(
&org, &project, &instance, None, None,
);
let _ = stream.collect::<Vec<_>>();
};
}
}
mod builder_untagged {
use futures::StreamExt;
mod nexus_client {
progenitor::generate_api!(
spec = "../sample_openapi/nexus.json",
interface = Builder,
tags = Merged,
);
}
use nexus_client::{types, Client};
pub fn _ignore() {
let client = Client::new("");
let stream = client
.instance_disks_get()
.organization_name(types::Name("org".to_string()))
.project_name(types::Name("project".to_string()))
.instance_name(types::Name("instance".to_string()))
.stream();
let _ = stream.collect::<Vec<_>>();
}
}
mod builder_tagged {
use futures::StreamExt;
mod nexus_client {
progenitor::generate_api!(
spec = "../sample_openapi/nexus.json",
interface = Builder,
tags = Separate,
);
}
use nexus_client::{types, Client, ClientInstancesExt};
fn _ignore() {
let client = Client::new("");
let stream = client
.instance_disks_get()
.organization_name(types::Name("org".to_string()))
.project_name(types::Name("project".to_string()))
.instance_name(types::Name("instance".to_string()))
.stream();
let _ = stream.collect::<Vec<_>>();
} }
} }