CLI generation prototype (#349)

This commit is contained in:
Adam Leventhal 2023-03-28 14:54:05 -07:00 committed by GitHub
parent ca5f0674cf
commit 10dc4cafc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 14810 additions and 101 deletions

6
Cargo.lock generated
View File

@ -2103,7 +2103,7 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]]
name = "typify"
version = "0.0.12-dev"
source = "git+https://github.com/oxidecomputer/typify#3cda314def8e95043f45c172cfb8851e391ef7ad"
source = "git+https://github.com/oxidecomputer/typify#6d77f63b3dc5312cd36549507f941cb5d783600e"
dependencies = [
"typify-impl",
"typify-macro",
@ -2112,7 +2112,7 @@ dependencies = [
[[package]]
name = "typify-impl"
version = "0.0.12-dev"
source = "git+https://github.com/oxidecomputer/typify#3cda314def8e95043f45c172cfb8851e391ef7ad"
source = "git+https://github.com/oxidecomputer/typify#6d77f63b3dc5312cd36549507f941cb5d783600e"
dependencies = [
"heck",
"log",
@ -2129,7 +2129,7 @@ dependencies = [
[[package]]
name = "typify-macro"
version = "0.0.12-dev"
source = "git+https://github.com/oxidecomputer/typify#3cda314def8e95043f45c172cfb8851e391ef7ad"
source = "git+https://github.com/oxidecomputer/typify#6d77f63b3dc5312cd36549507f941cb5d783600e"
dependencies = [
"proc-macro2",
"quote",

View File

@ -32,3 +32,4 @@ expectorate = "1.0"
http = "0.2.9"
hyper = "0.14.25"
serde_yaml = "0.9"
serde_json = "1.0.91"

538
progenitor-impl/src/cli.rs Normal file
View File

@ -0,0 +1,538 @@
// Copyright 2023 Oxide Computer Company
use heck::ToKebabCase;
use openapiv3::OpenAPI;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use typify::{TypeSpaceImpl, TypeStructPropInfo};
use crate::{
method::{
OperationParameterKind, OperationParameterType, OperationResponseStatus,
},
space_out_items,
to_schema::ToSchema,
util::{sanitize, Case},
validate_openapi, Generator, Result,
};
struct CliOperation {
cli_fn: TokenStream,
execute_fn: TokenStream,
cli_trait: TokenStream,
execute_trait: TokenStream,
}
impl Generator {
pub fn cli_text(
&mut self,
spec: &OpenAPI,
crate_name: &str,
) -> Result<String> {
let output = self.cli(spec, crate_name)?;
let content = rustfmt_wrapper::rustfmt_config(
rustfmt_wrapper::config::Config {
format_strings: Some(true),
..Default::default()
},
output,
)
.unwrap();
space_out_items(content)
}
pub fn cli(
&mut self,
spec: &OpenAPI,
crate_name: &str,
) -> Result<TokenStream> {
validate_openapi(spec)?;
// Convert our components dictionary to schemars
let schemas = spec.components.iter().flat_map(|components| {
components.schemas.iter().map(|(name, ref_or_schema)| {
(name.clone(), ref_or_schema.to_schema())
})
});
self.type_space.add_ref_types(schemas)?;
let raw_methods = spec
.paths
.iter()
.flat_map(|(path, ref_or_item)| {
// Exclude externally defined path items.
let item = ref_or_item.as_item().unwrap();
item.iter().map(move |(method, operation)| {
(path.as_str(), method, operation, &item.parameters)
})
})
.map(|(path, method, operation, path_parameters)| {
self.process_operation(
operation,
&spec.components,
path,
method,
path_parameters,
)
})
.collect::<Result<Vec<_>>>()?;
let methods = raw_methods
.iter()
.map(|method| self.cli_method(method))
.collect::<Vec<_>>();
let ops = methods.iter().map(
|CliOperation {
cli_fn,
execute_fn,
cli_trait: _,
execute_trait: _,
}| {
quote! {
#cli_fn
#execute_fn
}
},
);
let trait_ops = methods.iter().map(
|CliOperation {
cli_fn: _,
execute_fn: _,
cli_trait,
execute_trait,
}| {
quote! {
#cli_trait
#execute_trait
}
},
);
let cli_fns = raw_methods
.iter()
.map(|method| {
format_ident!(
"cli_{}",
sanitize(&method.operation_id, Case::Snake)
)
})
.collect::<Vec<_>>();
let execute_fns = raw_methods
.iter()
.map(|method| {
format_ident!(
"execute_{}",
sanitize(&method.operation_id, Case::Snake)
)
})
.collect::<Vec<_>>();
let cli_variants = raw_methods
.iter()
.map(|method| {
format_ident!(
"{}",
sanitize(&method.operation_id, Case::Pascal)
)
})
.collect::<Vec<_>>();
let crate_ident = format_ident!("{}", crate_name);
let code = quote! {
pub struct Cli {
client: #crate_ident::Client,
}
impl Cli {
pub fn new(client: #crate_ident::Client) -> Self {
Self { client }
}
#(#ops)*
pub fn get_command(cmd: CliCommand) -> clap::Command {
match cmd {
#(
CliCommand::#cli_variants => Self::#cli_fns(),
)*
}
}
pub async fn execute(
&self,
cmd: CliCommand,
matches: &clap::ArgMatches,
) {
let _ = match cmd {
#(
CliCommand::#cli_variants => {
// TODO ... do something with output
self.#execute_fns(matches).await;
}
)*
};
}
}
pub trait CliOverride {
#(#trait_ops)*
}
#[derive(Copy, Clone, Debug)]
pub enum CliCommand {
#(#cli_variants,)*
}
impl CliCommand {
pub fn iter() -> impl Iterator<Item = CliCommand> {
vec![
#(
CliCommand::#cli_variants,
)*
].into_iter()
}
}
};
Ok(code)
}
fn cli_method(
&mut self,
method: &crate::method::OperationMethod,
) -> CliOperation {
let fn_name = format_ident!("cli_{}", &method.operation_id);
let args = method
.params
.iter()
.filter(|param| {
!matches!(&param.kind, OperationParameterKind::Body(_))
&& (method.dropshot_paginated.is_none()
|| (param.name.as_str() != "page_token"))
})
.map(|param| {
let arg_name = param.name.to_kebab_case();
let required = match &param.kind {
OperationParameterKind::Path => true,
OperationParameterKind::Query(required) => *required,
OperationParameterKind::Header(required) => *required,
OperationParameterKind::Body(_) => unreachable!(),
};
let OperationParameterType::Type(arg_type_id) = &param.typ else {
panic!()
};
let arg_type = self.type_space.get_type(arg_type_id).unwrap();
let arg_details = arg_type.details();
let arg_type_name = match &arg_details{
typify::TypeDetails::Option(opt_id) => {
let inner_type = self.type_space.get_type(opt_id).unwrap();
inner_type.ident()
}
_ => {
arg_type.ident()
}
};
let help = param.description.as_ref().map(|description| {
quote! {
.help(#description)
}
});
quote! {
clap::Arg::new(#arg_name)
.long(#arg_name)
.required(#required)
.value_parser(clap::value_parser!(#arg_type_name))
#help
}
});
let maybe_body_param = method.params.iter().find(|param| {
matches!(&param.kind, OperationParameterKind::Body(_))
// TODO not sure how to deal with raw bodies right now
&& matches!(&param.typ, OperationParameterType::Type(_))
});
let body_arg = maybe_body_param.map(|param| {
let OperationParameterType::Type(type_id) = &param.typ else {
unreachable!();
};
let body_args = self.type_space.get_type(type_id).unwrap();
let body_arg = match body_args.details() {
typify::TypeDetails::Struct(s) => {
s.properties_info()
.filter_map(|prop_info| {
let TypeStructPropInfo {
name: prop_name,
description,
required,
type_id: prop_type_id,
} = prop_info;
let prop_type = self
.type_space
.get_type(&prop_type_id)
.unwrap();
let prop_type_ident = prop_type.ident();
let good =
prop_type.has_impl(TypeSpaceImpl::FromStr);
let prop_name = prop_name.to_kebab_case();
// assert!(good || !required);
good.then(|| {
let help =
description.as_ref().map(|description| {
quote! {
.help(#description)
}
});
quote! {
clap::Arg::new(#prop_name)
.long(#prop_name)
.required(#required)
.value_parser(clap::value_parser!(
#prop_type_ident
))
#help
}
})
})
.collect::<Vec<_>>()
}
_ => Vec::new(),
};
quote! {
#(
.arg(#body_arg)
)*
}
});
// TODO parameter for body as input json (--body-input?)
// TODO parameter to output a body template (--body-template?)
// TODO deal with all parameters?
let about = method.summary.as_ref().map(|summary| {
let mut about_str = summary.clone();
if let Some(description) = &method.description {
about_str.push_str("\n\n");
about_str.push_str(description);
}
quote! {
.about(#about_str)
}
});
let cli_fn = quote! {
pub fn #fn_name() -> clap::Command
{
clap::Command::new("")
#(
.arg(#args)
)*
#body_arg
#about
}
};
let cli_trait = quote! {
fn #fn_name(cmd: clap::Command) -> clap::Command {
cmd
}
};
let op_name = format_ident!("{}", &method.operation_id);
let fn_name = format_ident!("execute_{}", &method.operation_id);
let args = method
.params
.iter()
.filter(|param| {
!matches!(&param.kind, OperationParameterKind::Body(_))
&& (method.dropshot_paginated.is_none()
|| (param.name.as_str() != "page_token"))
})
.map(|param| {
let arg_name = param.name.to_kebab_case();
let arg_fn_name = sanitize(&param.name, Case::Snake);
let arg_fn = format_ident!("{}", arg_fn_name);
let OperationParameterType::Type(arg_type_id) = &param.typ else {
panic!()
};
let arg_type = self.type_space.get_type(arg_type_id).unwrap();
// TODO this really should be simpler.
let arg_details = arg_type.details();
let arg_type_name = match &arg_details{
typify::TypeDetails::Option(opt_id) => {
let inner_type = self.type_space.get_type(opt_id).unwrap();
inner_type.ident()
}
_ => {
arg_type.ident()
}
};
quote! {
if let Some(value) =
matches.get_one::<#arg_type_name>(#arg_name)
{
// clone here in case the arg type doesn't impl
// From<&T>
request = request.#arg_fn(value.clone());
}
}
});
let body_arg = maybe_body_param.map(|param| {
let OperationParameterType::Type(type_id) = &param.typ else {
unreachable!();
};
let body_type = self.type_space.get_type(type_id).unwrap();
let body_type_ident = body_type.ident();
let maybe_body_args = match body_type.details() {
typify::TypeDetails::Struct(s) => {
let args = s
.properties_info()
.filter_map(|prop_info| {
let TypeStructPropInfo {
name: prop_name,
description: _,
required: _,
type_id: body_type_id,
} = prop_info;
let prop_type = self
.type_space
.get_type(&body_type_id)
.unwrap();
let prop_fn = format_ident!(
"{}",
sanitize(prop_name, Case::Snake)
);
let prop_name = prop_name.to_kebab_case();
let prop_type_ident = prop_type.ident();
let good =
prop_type.has_impl(TypeSpaceImpl::FromStr);
// assert!(good || !required);
good.then(|| {
quote! {
if let Some(value) =
matches.get_one::<#prop_type_ident>(
#prop_name,
)
{
// clone here in case the arg type
// doesn't impl From<&T>
body = body.#prop_fn(
value.clone(),
);
}
}
})
})
.collect::<Vec<_>>();
Some(args)
}
_ => None,
};
maybe_body_args.map(|body_args| {
quote! {
let request = request.body({
let mut body = #body_type_ident::builder();
#( #body_args )*
body
});
}
})
});
let (_, success_type) = self.extract_responses(
method,
OperationResponseStatus::is_success_or_default,
);
let (_, error_type) = self.extract_responses(
method,
OperationResponseStatus::is_error_or_default,
);
let success_output = match success_type {
crate::method::OperationResponseType::Type(_) => {
quote! { println!("success\n{:#?}", r) }
}
crate::method::OperationResponseType::None => {
quote! { println!("success\n{:#?}", r) }
}
crate::method::OperationResponseType::Raw => quote! { todo!() },
crate::method::OperationResponseType::Upgrade => quote! { todo!() },
};
let error_output = match error_type {
crate::method::OperationResponseType::Type(_) => {
quote! { println!("error\n{:#?}", r) }
}
crate::method::OperationResponseType::None => {
quote! { println!("success\n{:#?}", r) }
}
crate::method::OperationResponseType::Raw => quote! { todo!() },
crate::method::OperationResponseType::Upgrade => quote! { todo!() },
};
let execute_fn = quote! {
pub async fn #fn_name(&self, matches: &clap::ArgMatches)
// ->
// Result<ResponseValue<#success_type>, Error<#error_type>>
{
let mut request = self.client.#op_name();
#( #args )*
#body_arg
let result = request.send().await;
match result {
Ok(r) => {
#success_output
}
Err(r) => {
#error_output
}
}
}
};
let execute_trait = quote! {
fn #fn_name(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut()
) -> Result<(), String> {
Ok(())
}
};
CliOperation {
cli_fn,
execute_fn,
cli_trait,
execute_trait,
}
}
}

View File

@ -14,6 +14,7 @@ use crate::to_schema::ToSchema;
pub use typify::TypeSpaceImpl as TypeImpl;
pub use typify::TypeSpacePatch as TypePatch;
mod cli;
mod method;
mod template;
mod to_schema;
@ -212,15 +213,11 @@ impl Generator {
validate_openapi(spec)?;
// Convert our components dictionary to schemars
let schemas = spec
.components
.iter()
.flat_map(|components| {
components.schemas.iter().map(|(name, ref_or_schema)| {
(name.clone(), ref_or_schema.to_schema())
})
let schemas = spec.components.iter().flat_map(|components| {
components.schemas.iter().map(|(name, ref_or_schema)| {
(name.clone(), ref_or_schema.to_schema())
})
.collect::<Vec<(String, _)>>();
});
self.type_space.add_ref_types(schemas)?;
@ -521,14 +518,7 @@ impl Generator {
// Format the file with rustfmt.
let content = rustfmt_wrapper::rustfmt_config(config, output).unwrap();
// Add newlines after end-braces at <= two levels of indentation.
Ok(if cfg!(not(windows)) {
let regex = regex::Regex::new(r#"(})(\n\s{0,8}[^} ])"#).unwrap();
regex.replace_all(&content, "$1\n$2").to_string()
} else {
let regex = regex::Regex::new(r#"(})(\r\n\s{0,8}[^} ])"#).unwrap();
regex.replace_all(&content, "$1\r\n$2").to_string()
})
space_out_items(content)
}
// TODO deprecate?
@ -545,7 +535,18 @@ impl Generator {
}
}
fn validate_openapi(spec: &OpenAPI) -> Result<()> {
pub(crate) fn space_out_items(content: String) -> Result<String> {
// Add newlines after end-braces at <= two levels of indentation.
Ok(if cfg!(not(windows)) {
let regex = regex::Regex::new(r#"(})(\n\s{0,8}[^} ])"#).unwrap();
regex.replace_all(&content, "$1\n$2").to_string()
} else {
let regex = regex::Regex::new(r#"(})(\r\n\s{0,8}[^} ])"#).unwrap();
regex.replace_all(&content, "$1\r\n$2").to_string()
})
}
pub fn validate_openapi(spec: &OpenAPI) -> Result<()> {
match spec.openapi.as_str() {
"3.0.0" | "3.0.1" | "3.0.2" | "3.0.3" => (),
v => {

View File

@ -9,7 +9,7 @@ use std::{
use openapiv3::{Components, Parameter, ReferenceOr, Response, StatusCode};
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use typify::TypeId;
use typify::{TypeId, TypeSpace};
use crate::{
template::PathTemplate,
@ -20,15 +20,15 @@ use crate::{to_schema::ToSchema, util::ReferenceOrExt};
/// The intermediate representation of an operation that will become a method.
pub(crate) struct OperationMethod {
operation_id: String,
pub operation_id: String,
pub tags: Vec<String>,
method: HttpMethod,
path: PathTemplate,
summary: Option<String>,
description: Option<String>,
params: Vec<OperationParameter>,
pub summary: Option<String>,
pub description: Option<String>,
pub params: Vec<OperationParameter>,
responses: Vec<OperationResponse>,
dropshot_paginated: Option<DropshotPagination>,
pub dropshot_paginated: Option<DropshotPagination>,
dropshot_websocket: bool,
}
@ -87,28 +87,28 @@ struct BuilderImpl {
body: TokenStream,
}
struct DropshotPagination {
pub struct DropshotPagination {
item: TypeId,
}
struct OperationParameter {
pub struct OperationParameter {
/// Sanitized parameter name.
name: String,
pub name: String,
/// Original parameter name provided by the API.
api_name: String,
description: Option<String>,
typ: OperationParameterType,
kind: OperationParameterKind,
pub description: Option<String>,
pub typ: OperationParameterType,
pub kind: OperationParameterKind,
}
#[derive(Eq, PartialEq)]
enum OperationParameterType {
pub enum OperationParameterType {
Type(TypeId),
RawBody,
}
#[derive(Debug, PartialEq, Eq)]
enum OperationParameterKind {
pub enum OperationParameterKind {
Path,
Query(bool),
Header(bool),
@ -116,7 +116,7 @@ enum OperationParameterKind {
}
#[derive(Debug, PartialEq, Eq)]
enum BodyContentType {
pub enum BodyContentType {
OctetStream,
Json,
FormUrlencoded,
@ -139,7 +139,7 @@ impl FromStr for BodyContentType {
}
#[derive(Debug)]
struct OperationResponse {
pub(crate) struct OperationResponse {
status_code: OperationResponseStatus,
typ: OperationResponseType,
// TODO this isn't currently used because dropshot doesn't give us a
@ -166,7 +166,7 @@ impl PartialOrd for OperationResponse {
}
#[derive(Debug, Clone, Eq, PartialEq)]
enum OperationResponseStatus {
pub(crate) enum OperationResponseStatus {
Code(u16),
Range(u16),
Default,
@ -187,7 +187,7 @@ impl OperationResponseStatus {
}
}
fn is_success_or_default(&self) -> bool {
pub fn is_success_or_default(&self) -> bool {
matches!(
self,
OperationResponseStatus::Default
@ -197,7 +197,7 @@ impl OperationResponseStatus {
)
}
fn is_error_or_default(&self) -> bool {
pub fn is_error_or_default(&self) -> bool {
matches!(
self,
OperationResponseStatus::Default
@ -206,7 +206,7 @@ impl OperationResponseStatus {
)
}
fn is_default(&self) -> bool {
pub fn is_default(&self) -> bool {
matches!(self, OperationResponseStatus::Default)
}
}
@ -224,13 +224,33 @@ impl PartialOrd for OperationResponseStatus {
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
enum OperationResponseType {
pub(crate) enum OperationResponseType {
Type(TypeId),
None,
Raw,
Upgrade,
}
impl OperationResponseType {
pub fn into_tokens(self, type_space: &TypeSpace) -> TokenStream {
match self {
OperationResponseType::Type(ref type_id) => {
let type_name = type_space.get_type(type_id).unwrap().ident();
quote! { #type_name }
}
OperationResponseType::None => {
quote! { () }
}
OperationResponseType::Raw => {
quote! { ByteStream }
}
OperationResponseType::Upgrade => {
quote! { reqwest::Upgraded }
}
}
}
}
impl Generator {
pub(crate) fn process_operation(
&mut self,
@ -730,6 +750,9 @@ impl Generator {
Ok(all)
}
/// Common code generation between positional and builder interface-styles.
/// Returns a struct with the success and error types and the core body
/// implementation that marshals arguments and executes the request.
fn method_sig_body(
&self,
method: &OperationMethod,
@ -1070,17 +1093,21 @@ impl Generator {
};
Ok(MethodSigBody {
success: response_type,
error: error_type,
success: response_type.into_tokens(&self.type_space),
error: error_type.into_tokens(&self.type_space),
body: body_impl,
})
}
fn extract_responses<'a>(
/// Extract responses that match criteria specified by the `filter`. The
/// result is a `Vec<OperationResponse>` that enumerates the cases matching
/// the filter, and a `TokenStream` that respresents the generated type for
/// those cases.
pub(crate) fn extract_responses<'a>(
&self,
method: &'a OperationMethod,
filter: fn(&OperationResponseStatus) -> bool,
) -> (Vec<&'a OperationResponse>, TokenStream) {
) -> (Vec<&'a OperationResponse>, OperationResponseType) {
let mut response_items = method
.responses
.iter()
@ -1091,22 +1118,20 @@ impl Generator {
// If we have a success range and a default, we can pop off the default
// since it will never be hit. Note that this is a no-op for error
// responses.
{
let len = response_items.len();
if len >= 2 {
if let (
OperationResponse {
status_code: OperationResponseStatus::Range(2),
..
},
OperationResponse {
status_code: OperationResponseStatus::Default,
..
},
) = (&response_items[len - 2], &response_items[len - 1])
{
response_items.pop();
}
let len = response_items.len();
if len >= 2 {
if let (
OperationResponse {
status_code: OperationResponseStatus::Range(2),
..
},
OperationResponse {
status_code: OperationResponseStatus::Default,
..
},
) = (&response_items[len - 2], &response_items[len - 1])
{
response_items.pop();
}
}
@ -1119,26 +1144,10 @@ impl Generator {
// enum type with variants for each of the response types.
assert!(response_types.len() <= 1);
let response_type = response_types
.iter()
.into_iter()
.next()
.map(|typ| match typ {
OperationResponseType::Type(type_id) => {
let type_name =
self.type_space.get_type(type_id).unwrap().ident();
quote! { #type_name }
}
OperationResponseType::None => {
quote! { () }
}
OperationResponseType::Raw => {
quote! { ByteStream }
}
OperationResponseType::Upgrade => {
quote! { reqwest::Upgraded }
}
})
// TODO should this be a bytestream?
.unwrap_or_else(|| quote! { () });
// TODO should this be OperationResponseType::Raw?
.unwrap_or(OperationResponseType::None);
(response_items, response_type)
}
@ -1271,9 +1280,9 @@ impl Generator {
/// }
/// ```
///
/// All parameters are present and all their types are Result<T, String> or
/// Result<Option<T>, String> for optional parameters. Each parameter also
/// has a corresponding method:
/// All parameters are present and all their types are `Result<T, String>`
/// or `Result<Option<T>, String>` for optional parameters. Each parameter
/// also has a corresponding method:
/// ```ignore
/// impl<'a> OperationId<'a> {
/// pub fn param_1<V>(self, value: V)
@ -1295,7 +1304,8 @@ impl Generator {
/// ```
///
/// The Client's operation_id method simply invokes the builder's new
/// method:
/// method, which assigns an error value to mandatory field and a
/// `Ok(None)` value to optional ones:
/// ```ignore
/// impl<'a> OperationId<'a> {
/// pub fn new(client: &'a super::Client) -> Self {
@ -1309,7 +1319,7 @@ impl Generator {
/// ```
///
/// Finally, builders have methods to execute the operation. This simply
/// resolves each parameter with the ? (Try operator).
/// resolves each parameter with the ? (`Try` operator).
/// ```ignore
/// impl<'a> OperationId<'a> {
/// pub fn send(self) -> Result<

View File

@ -0,0 +1,900 @@
pub struct Cli {
client: sdk::Client,
}
impl Cli {
pub fn new(client: sdk::Client) -> Self {
Self { client }
}
pub fn cli_control_hold() -> clap::Command {
clap::Command::new("")
}
pub async fn execute_control_hold(&self, matches: &clap::ArgMatches) {
let mut request = self.client.control_hold();
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_control_resume() -> clap::Command {
clap::Command::new("")
}
pub async fn execute_control_resume(&self, matches: &clap::ArgMatches) {
let mut request = self.client.control_resume();
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_task_get() -> clap::Command {
clap::Command::new("").arg(
clap::Arg::new("task")
.long("task")
.required(true)
.value_parser(clap::value_parser!(String)),
)
}
pub async fn execute_task_get(&self, matches: &clap::ArgMatches) {
let mut request = self.client.task_get();
if let Some(value) = matches.get_one::<String>("task") {
request = request.task(value.clone());
}
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_tasks_get() -> clap::Command {
clap::Command::new("")
}
pub async fn execute_tasks_get(&self, matches: &clap::ArgMatches) {
let mut request = self.client.tasks_get();
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_task_submit() -> clap::Command {
clap::Command::new("")
.arg(
clap::Arg::new("name")
.long("name")
.required(true)
.value_parser(clap::value_parser!(String)),
)
.arg(
clap::Arg::new("script")
.long("script")
.required(true)
.value_parser(clap::value_parser!(String)),
)
}
pub async fn execute_task_submit(&self, matches: &clap::ArgMatches) {
let mut request = self.client.task_submit();
let request = request.body({
let mut body = types::TaskSubmit::builder();
if let Some(value) = matches.get_one::<String>("name") {
body = body.name(value.clone());
}
if let Some(value) = matches.get_one::<String>("script") {
body = body.script(value.clone());
}
body
});
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_task_events_get() -> clap::Command {
clap::Command::new("")
.arg(
clap::Arg::new("task")
.long("task")
.required(true)
.value_parser(clap::value_parser!(String)),
)
.arg(
clap::Arg::new("minseq")
.long("minseq")
.required(false)
.value_parser(clap::value_parser!(u32)),
)
}
pub async fn execute_task_events_get(&self, matches: &clap::ArgMatches) {
let mut request = self.client.task_events_get();
if let Some(value) = matches.get_one::<String>("task") {
request = request.task(value.clone());
}
if let Some(value) = matches.get_one::<u32>("minseq") {
request = request.minseq(value.clone());
}
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_task_outputs_get() -> clap::Command {
clap::Command::new("").arg(
clap::Arg::new("task")
.long("task")
.required(true)
.value_parser(clap::value_parser!(String)),
)
}
pub async fn execute_task_outputs_get(&self, matches: &clap::ArgMatches) {
let mut request = self.client.task_outputs_get();
if let Some(value) = matches.get_one::<String>("task") {
request = request.task(value.clone());
}
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_task_output_download() -> clap::Command {
clap::Command::new("")
.arg(
clap::Arg::new("task")
.long("task")
.required(true)
.value_parser(clap::value_parser!(String)),
)
.arg(
clap::Arg::new("output")
.long("output")
.required(true)
.value_parser(clap::value_parser!(String)),
)
}
pub async fn execute_task_output_download(&self, matches: &clap::ArgMatches) {
let mut request = self.client.task_output_download();
if let Some(value) = matches.get_one::<String>("task") {
request = request.task(value.clone());
}
if let Some(value) = matches.get_one::<String>("output") {
request = request.output(value.clone());
}
let result = request.send().await;
match result {
Ok(r) => {
todo!()
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_user_create() -> clap::Command {
clap::Command::new("").arg(
clap::Arg::new("name")
.long("name")
.required(true)
.value_parser(clap::value_parser!(String)),
)
}
pub async fn execute_user_create(&self, matches: &clap::ArgMatches) {
let mut request = self.client.user_create();
let request = request.body({
let mut body = types::UserCreate::builder();
if let Some(value) = matches.get_one::<String>("name") {
body = body.name(value.clone());
}
body
});
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_whoami() -> clap::Command {
clap::Command::new("")
}
pub async fn execute_whoami(&self, matches: &clap::ArgMatches) {
let mut request = self.client.whoami();
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_worker_bootstrap() -> clap::Command {
clap::Command::new("")
.arg(
clap::Arg::new("bootstrap")
.long("bootstrap")
.required(true)
.value_parser(clap::value_parser!(String)),
)
.arg(
clap::Arg::new("token")
.long("token")
.required(true)
.value_parser(clap::value_parser!(String)),
)
}
pub async fn execute_worker_bootstrap(&self, matches: &clap::ArgMatches) {
let mut request = self.client.worker_bootstrap();
let request = request.body({
let mut body = types::WorkerBootstrap::builder();
if let Some(value) = matches.get_one::<String>("bootstrap") {
body = body.bootstrap(value.clone());
}
if let Some(value) = matches.get_one::<String>("token") {
body = body.token(value.clone());
}
body
});
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_worker_ping() -> clap::Command {
clap::Command::new("")
}
pub async fn execute_worker_ping(&self, matches: &clap::ArgMatches) {
let mut request = self.client.worker_ping();
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_worker_task_append() -> clap::Command {
clap::Command::new("")
.arg(
clap::Arg::new("task")
.long("task")
.required(true)
.value_parser(clap::value_parser!(String)),
)
.arg(
clap::Arg::new("payload")
.long("payload")
.required(true)
.value_parser(clap::value_parser!(String)),
)
.arg(
clap::Arg::new("stream")
.long("stream")
.required(true)
.value_parser(clap::value_parser!(String)),
)
.arg(
clap::Arg::new("time")
.long("time")
.required(true)
.value_parser(clap::value_parser!(chrono::DateTime<chrono::offset::Utc>)),
)
}
pub async fn execute_worker_task_append(&self, matches: &clap::ArgMatches) {
let mut request = self.client.worker_task_append();
if let Some(value) = matches.get_one::<String>("task") {
request = request.task(value.clone());
}
let request = request.body({
let mut body = types::WorkerAppendTask::builder();
if let Some(value) = matches.get_one::<String>("payload") {
body = body.payload(value.clone());
}
if let Some(value) = matches.get_one::<String>("stream") {
body = body.stream(value.clone());
}
if let Some(value) = matches.get_one::<chrono::DateTime<chrono::offset::Utc>>("time") {
body = body.time(value.clone());
}
body
});
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_worker_task_upload_chunk() -> clap::Command {
clap::Command::new("").arg(
clap::Arg::new("task")
.long("task")
.required(true)
.value_parser(clap::value_parser!(String)),
)
}
pub async fn execute_worker_task_upload_chunk(&self, matches: &clap::ArgMatches) {
let mut request = self.client.worker_task_upload_chunk();
if let Some(value) = matches.get_one::<String>("task") {
request = request.task(value.clone());
}
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_worker_task_complete() -> clap::Command {
clap::Command::new("")
.arg(
clap::Arg::new("task")
.long("task")
.required(true)
.value_parser(clap::value_parser!(String)),
)
.arg(
clap::Arg::new("failed")
.long("failed")
.required(true)
.value_parser(clap::value_parser!(bool)),
)
}
pub async fn execute_worker_task_complete(&self, matches: &clap::ArgMatches) {
let mut request = self.client.worker_task_complete();
if let Some(value) = matches.get_one::<String>("task") {
request = request.task(value.clone());
}
let request = request.body({
let mut body = types::WorkerCompleteTask::builder();
if let Some(value) = matches.get_one::<bool>("failed") {
body = body.failed(value.clone());
}
body
});
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_worker_task_add_output() -> clap::Command {
clap::Command::new("")
.arg(
clap::Arg::new("task")
.long("task")
.required(true)
.value_parser(clap::value_parser!(String)),
)
.arg(
clap::Arg::new("path")
.long("path")
.required(true)
.value_parser(clap::value_parser!(String)),
)
.arg(
clap::Arg::new("size")
.long("size")
.required(true)
.value_parser(clap::value_parser!(i64)),
)
}
pub async fn execute_worker_task_add_output(&self, matches: &clap::ArgMatches) {
let mut request = self.client.worker_task_add_output();
if let Some(value) = matches.get_one::<String>("task") {
request = request.task(value.clone());
}
let request = request.body({
let mut body = types::WorkerAddOutput::builder();
if let Some(value) = matches.get_one::<String>("path") {
body = body.path(value.clone());
}
if let Some(value) = matches.get_one::<i64>("size") {
body = body.size(value.clone());
}
body
});
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_workers_list() -> clap::Command {
clap::Command::new("")
}
pub async fn execute_workers_list(&self, matches: &clap::ArgMatches) {
let mut request = self.client.workers_list();
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_workers_recycle() -> clap::Command {
clap::Command::new("")
}
pub async fn execute_workers_recycle(&self, matches: &clap::ArgMatches) {
let mut request = self.client.workers_recycle();
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn get_command(cmd: CliCommand) -> clap::Command {
match cmd {
CliCommand::ControlHold => Self::cli_control_hold(),
CliCommand::ControlResume => Self::cli_control_resume(),
CliCommand::TaskGet => Self::cli_task_get(),
CliCommand::TasksGet => Self::cli_tasks_get(),
CliCommand::TaskSubmit => Self::cli_task_submit(),
CliCommand::TaskEventsGet => Self::cli_task_events_get(),
CliCommand::TaskOutputsGet => Self::cli_task_outputs_get(),
CliCommand::TaskOutputDownload => Self::cli_task_output_download(),
CliCommand::UserCreate => Self::cli_user_create(),
CliCommand::Whoami => Self::cli_whoami(),
CliCommand::WorkerBootstrap => Self::cli_worker_bootstrap(),
CliCommand::WorkerPing => Self::cli_worker_ping(),
CliCommand::WorkerTaskAppend => Self::cli_worker_task_append(),
CliCommand::WorkerTaskUploadChunk => Self::cli_worker_task_upload_chunk(),
CliCommand::WorkerTaskComplete => Self::cli_worker_task_complete(),
CliCommand::WorkerTaskAddOutput => Self::cli_worker_task_add_output(),
CliCommand::WorkersList => Self::cli_workers_list(),
CliCommand::WorkersRecycle => Self::cli_workers_recycle(),
}
}
pub async fn execute(&self, cmd: CliCommand, matches: &clap::ArgMatches) {
let _ = match cmd {
CliCommand::ControlHold => {
self.execute_control_hold(matches).await;
}
CliCommand::ControlResume => {
self.execute_control_resume(matches).await;
}
CliCommand::TaskGet => {
self.execute_task_get(matches).await;
}
CliCommand::TasksGet => {
self.execute_tasks_get(matches).await;
}
CliCommand::TaskSubmit => {
self.execute_task_submit(matches).await;
}
CliCommand::TaskEventsGet => {
self.execute_task_events_get(matches).await;
}
CliCommand::TaskOutputsGet => {
self.execute_task_outputs_get(matches).await;
}
CliCommand::TaskOutputDownload => {
self.execute_task_output_download(matches).await;
}
CliCommand::UserCreate => {
self.execute_user_create(matches).await;
}
CliCommand::Whoami => {
self.execute_whoami(matches).await;
}
CliCommand::WorkerBootstrap => {
self.execute_worker_bootstrap(matches).await;
}
CliCommand::WorkerPing => {
self.execute_worker_ping(matches).await;
}
CliCommand::WorkerTaskAppend => {
self.execute_worker_task_append(matches).await;
}
CliCommand::WorkerTaskUploadChunk => {
self.execute_worker_task_upload_chunk(matches).await;
}
CliCommand::WorkerTaskComplete => {
self.execute_worker_task_complete(matches).await;
}
CliCommand::WorkerTaskAddOutput => {
self.execute_worker_task_add_output(matches).await;
}
CliCommand::WorkersList => {
self.execute_workers_list(matches).await;
}
CliCommand::WorkersRecycle => {
self.execute_workers_recycle(matches).await;
}
};
}
}
pub trait CliOverride {
fn cli_control_hold(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_control_hold(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_control_resume(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_control_resume(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_task_get(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_task_get(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_tasks_get(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_tasks_get(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_task_submit(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_task_submit(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_task_events_get(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_task_events_get(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_task_outputs_get(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_task_outputs_get(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_task_output_download(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_task_output_download(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_user_create(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_user_create(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_whoami(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_whoami(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_worker_bootstrap(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_worker_bootstrap(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_worker_ping(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_worker_ping(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_worker_task_append(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_worker_task_append(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_worker_task_upload_chunk(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_worker_task_upload_chunk(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_worker_task_complete(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_worker_task_complete(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_worker_task_add_output(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_worker_task_add_output(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_workers_list(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_workers_list(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_workers_recycle(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_workers_recycle(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
}
#[derive(Copy, Clone, Debug)]
pub enum CliCommand {
ControlHold,
ControlResume,
TaskGet,
TasksGet,
TaskSubmit,
TaskEventsGet,
TaskOutputsGet,
TaskOutputDownload,
UserCreate,
Whoami,
WorkerBootstrap,
WorkerPing,
WorkerTaskAppend,
WorkerTaskUploadChunk,
WorkerTaskComplete,
WorkerTaskAddOutput,
WorkersList,
WorkersRecycle,
}
impl CliCommand {
pub fn iter() -> impl Iterator<Item = CliCommand> {
vec![
CliCommand::ControlHold,
CliCommand::ControlResume,
CliCommand::TaskGet,
CliCommand::TasksGet,
CliCommand::TaskSubmit,
CliCommand::TaskEventsGet,
CliCommand::TaskOutputsGet,
CliCommand::TaskOutputDownload,
CliCommand::UserCreate,
CliCommand::Whoami,
CliCommand::WorkerBootstrap,
CliCommand::WorkerPing,
CliCommand::WorkerTaskAppend,
CliCommand::WorkerTaskUploadChunk,
CliCommand::WorkerTaskComplete,
CliCommand::WorkerTaskAddOutput,
CliCommand::WorkersList,
CliCommand::WorkersRecycle,
]
.into_iter()
}
}

View File

@ -0,0 +1,395 @@
pub struct Cli {
client: sdk::Client,
}
impl Cli {
pub fn new(client: sdk::Client) -> Self {
Self { client }
}
pub fn cli_enrol() -> clap::Command {
clap::Command::new("")
.arg(
clap::Arg::new("authorization")
.long("authorization")
.required(true)
.value_parser(clap::value_parser!(String))
.help("Authorization header (bearer token)"),
)
.arg(
clap::Arg::new("host")
.long("host")
.required(true)
.value_parser(clap::value_parser!(String)),
)
.arg(
clap::Arg::new("key")
.long("key")
.required(true)
.value_parser(clap::value_parser!(String)),
)
}
pub async fn execute_enrol(&self, matches: &clap::ArgMatches) {
let mut request = self.client.enrol();
if let Some(value) = matches.get_one::<String>("authorization") {
request = request.authorization(value.clone());
}
let request = request.body({
let mut body = types::EnrolBody::builder();
if let Some(value) = matches.get_one::<String>("host") {
body = body.host(value.clone());
}
if let Some(value) = matches.get_one::<String>("key") {
body = body.key(value.clone());
}
body
});
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_global_jobs() -> clap::Command {
clap::Command::new("").arg(
clap::Arg::new("authorization")
.long("authorization")
.required(true)
.value_parser(clap::value_parser!(String))
.help("Authorization header (bearer token)"),
)
}
pub async fn execute_global_jobs(&self, matches: &clap::ArgMatches) {
let mut request = self.client.global_jobs();
if let Some(value) = matches.get_one::<String>("authorization") {
request = request.authorization(value.clone());
}
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_ping() -> clap::Command {
clap::Command::new("").arg(
clap::Arg::new("authorization")
.long("authorization")
.required(true)
.value_parser(clap::value_parser!(String))
.help("Authorization header (bearer token)"),
)
}
pub async fn execute_ping(&self, matches: &clap::ArgMatches) {
let mut request = self.client.ping();
if let Some(value) = matches.get_one::<String>("authorization") {
request = request.authorization(value.clone());
}
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_report_finish() -> clap::Command {
clap::Command::new("")
.arg(
clap::Arg::new("authorization")
.long("authorization")
.required(true)
.value_parser(clap::value_parser!(String))
.help("Authorization header (bearer token)"),
)
.arg(
clap::Arg::new("duration-millis")
.long("duration-millis")
.required(true)
.value_parser(clap::value_parser!(i32)),
)
.arg(
clap::Arg::new("end-time")
.long("end-time")
.required(true)
.value_parser(clap::value_parser!(chrono::DateTime<chrono::offset::Utc>)),
)
.arg(
clap::Arg::new("exit-status")
.long("exit-status")
.required(true)
.value_parser(clap::value_parser!(i32)),
)
}
pub async fn execute_report_finish(&self, matches: &clap::ArgMatches) {
let mut request = self.client.report_finish();
if let Some(value) = matches.get_one::<String>("authorization") {
request = request.authorization(value.clone());
}
let request = request.body({
let mut body = types::ReportFinishBody::builder();
if let Some(value) = matches.get_one::<i32>("duration-millis") {
body = body.duration_millis(value.clone());
}
if let Some(value) =
matches.get_one::<chrono::DateTime<chrono::offset::Utc>>("end-time")
{
body = body.end_time(value.clone());
}
if let Some(value) = matches.get_one::<i32>("exit-status") {
body = body.exit_status(value.clone());
}
body
});
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_report_output() -> clap::Command {
clap::Command::new("").arg(
clap::Arg::new("authorization")
.long("authorization")
.required(true)
.value_parser(clap::value_parser!(String))
.help("Authorization header (bearer token)"),
)
}
pub async fn execute_report_output(&self, matches: &clap::ArgMatches) {
let mut request = self.client.report_output();
if let Some(value) = matches.get_one::<String>("authorization") {
request = request.authorization(value.clone());
}
let request = request.body({
let mut body = types::ReportOutputBody::builder();
body
});
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn cli_report_start() -> clap::Command {
clap::Command::new("")
.arg(
clap::Arg::new("authorization")
.long("authorization")
.required(true)
.value_parser(clap::value_parser!(String))
.help("Authorization header (bearer token)"),
)
.arg(
clap::Arg::new("script")
.long("script")
.required(true)
.value_parser(clap::value_parser!(String)),
)
.arg(
clap::Arg::new("start-time")
.long("start-time")
.required(true)
.value_parser(clap::value_parser!(chrono::DateTime<chrono::offset::Utc>)),
)
}
pub async fn execute_report_start(&self, matches: &clap::ArgMatches) {
let mut request = self.client.report_start();
if let Some(value) = matches.get_one::<String>("authorization") {
request = request.authorization(value.clone());
}
let request = request.body({
let mut body = types::ReportStartBody::builder();
if let Some(value) = matches.get_one::<String>("script") {
body = body.script(value.clone());
}
if let Some(value) =
matches.get_one::<chrono::DateTime<chrono::offset::Utc>>("start-time")
{
body = body.start_time(value.clone());
}
body
});
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn get_command(cmd: CliCommand) -> clap::Command {
match cmd {
CliCommand::Enrol => Self::cli_enrol(),
CliCommand::GlobalJobs => Self::cli_global_jobs(),
CliCommand::Ping => Self::cli_ping(),
CliCommand::ReportFinish => Self::cli_report_finish(),
CliCommand::ReportOutput => Self::cli_report_output(),
CliCommand::ReportStart => Self::cli_report_start(),
}
}
pub async fn execute(&self, cmd: CliCommand, matches: &clap::ArgMatches) {
let _ = match cmd {
CliCommand::Enrol => {
self.execute_enrol(matches).await;
}
CliCommand::GlobalJobs => {
self.execute_global_jobs(matches).await;
}
CliCommand::Ping => {
self.execute_ping(matches).await;
}
CliCommand::ReportFinish => {
self.execute_report_finish(matches).await;
}
CliCommand::ReportOutput => {
self.execute_report_output(matches).await;
}
CliCommand::ReportStart => {
self.execute_report_start(matches).await;
}
};
}
}
pub trait CliOverride {
fn cli_enrol(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_enrol(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_global_jobs(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_global_jobs(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_ping(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_ping(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_report_finish(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_report_finish(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_report_output(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_report_output(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_report_start(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_report_start(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
}
#[derive(Copy, Clone, Debug)]
pub enum CliCommand {
Enrol,
GlobalJobs,
Ping,
ReportFinish,
ReportOutput,
ReportStart,
}
impl CliCommand {
pub fn iter() -> impl Iterator<Item = CliCommand> {
vec![
CliCommand::Enrol,
CliCommand::GlobalJobs,
CliCommand::Ping,
CliCommand::ReportFinish,
CliCommand::ReportOutput,
CliCommand::ReportStart,
]
.into_iter()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,88 @@
pub struct Cli {
client: sdk::Client,
}
impl Cli {
pub fn new(client: sdk::Client) -> Self {
Self { client }
}
pub fn cli_key_get() -> clap::Command {
clap::Command::new("")
.arg(
clap::Arg::new("key")
.long("key")
.required(false)
.value_parser(clap::value_parser!(bool))
.help("The same key parameter that overlaps with the path level parameter"),
)
.arg(
clap::Arg::new("unique-key")
.long("unique-key")
.required(false)
.value_parser(clap::value_parser!(String))
.help("A key parameter that will not be overridden by the path spec"),
)
}
pub async fn execute_key_get(&self, matches: &clap::ArgMatches) {
let mut request = self.client.key_get();
if let Some(value) = matches.get_one::<bool>("key") {
request = request.key(value.clone());
}
if let Some(value) = matches.get_one::<String>("unique-key") {
request = request.unique_key(value.clone());
}
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("success\n{:#?}", r)
}
}
}
pub fn get_command(cmd: CliCommand) -> clap::Command {
match cmd {
CliCommand::KeyGet => Self::cli_key_get(),
}
}
pub async fn execute(&self, cmd: CliCommand, matches: &clap::ArgMatches) {
let _ = match cmd {
CliCommand::KeyGet => {
self.execute_key_get(matches).await;
}
};
}
}
pub trait CliOverride {
fn cli_key_get(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_key_get(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
}
#[derive(Copy, Clone, Debug)]
pub enum CliCommand {
KeyGet,
}
impl CliCommand {
pub fn iter() -> impl Iterator<Item = CliCommand> {
vec![CliCommand::KeyGet].into_iter()
}
}

View File

@ -0,0 +1,340 @@
pub struct Cli {
client: sdk::Client,
}
impl Cli {
pub fn new(client: sdk::Client) -> Self {
Self { client }
}
pub fn cli_instance_get() -> clap::Command {
clap::Command::new("")
}
pub async fn execute_instance_get(&self, matches: &clap::ArgMatches) {
let mut request = self.client.instance_get();
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("error\n{:#?}", r)
}
}
}
pub fn cli_instance_ensure() -> clap::Command {
clap::Command::new("")
}
pub async fn execute_instance_ensure(&self, matches: &clap::ArgMatches) {
let mut request = self.client.instance_ensure();
let request = request.body({
let mut body = types::InstanceEnsureRequest::builder();
body
});
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("error\n{:#?}", r)
}
}
}
pub fn cli_instance_issue_crucible_snapshot_request() -> clap::Command {
clap::Command::new("")
.arg(
clap::Arg::new("id")
.long("id")
.required(true)
.value_parser(clap::value_parser!(uuid::Uuid)),
)
.arg(
clap::Arg::new("snapshot-id")
.long("snapshot-id")
.required(true)
.value_parser(clap::value_parser!(uuid::Uuid)),
)
.about("Issue a snapshot request to a crucible backend")
}
pub async fn execute_instance_issue_crucible_snapshot_request(
&self,
matches: &clap::ArgMatches,
) {
let mut request = self.client.instance_issue_crucible_snapshot_request();
if let Some(value) = matches.get_one::<uuid::Uuid>("id") {
request = request.id(value.clone());
}
if let Some(value) = matches.get_one::<uuid::Uuid>("snapshot-id") {
request = request.snapshot_id(value.clone());
}
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("error\n{:#?}", r)
}
}
}
pub fn cli_instance_migrate_status() -> clap::Command {
clap::Command::new("").arg(
clap::Arg::new("migration-id")
.long("migration-id")
.required(true)
.value_parser(clap::value_parser!(uuid::Uuid)),
)
}
pub async fn execute_instance_migrate_status(&self, matches: &clap::ArgMatches) {
let mut request = self.client.instance_migrate_status();
let request = request.body({
let mut body = types::InstanceMigrateStatusRequest::builder();
if let Some(value) = matches.get_one::<uuid::Uuid>("migration-id") {
body = body.migration_id(value.clone());
}
body
});
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("error\n{:#?}", r)
}
}
}
pub fn cli_instance_serial() -> clap::Command {
clap::Command::new("")
}
pub async fn execute_instance_serial(&self, matches: &clap::ArgMatches) {
let mut request = self.client.instance_serial();
let result = request.send().await;
match result {
Ok(r) => {
todo!()
}
Err(r) => {
todo!()
}
}
}
pub fn cli_instance_state_put() -> clap::Command {
clap::Command::new("")
}
pub async fn execute_instance_state_put(&self, matches: &clap::ArgMatches) {
let mut request = self.client.instance_state_put();
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("error\n{:#?}", r)
}
}
}
pub fn cli_instance_state_monitor() -> clap::Command {
clap::Command::new("").arg(
clap::Arg::new("gen")
.long("gen")
.required(true)
.value_parser(clap::value_parser!(u64)),
)
}
pub async fn execute_instance_state_monitor(&self, matches: &clap::ArgMatches) {
let mut request = self.client.instance_state_monitor();
let request = request.body({
let mut body = types::InstanceStateMonitorRequest::builder();
if let Some(value) = matches.get_one::<u64>("gen") {
body = body.gen(value.clone());
}
body
});
let result = request.send().await;
match result {
Ok(r) => {
println!("success\n{:#?}", r)
}
Err(r) => {
println!("error\n{:#?}", r)
}
}
}
pub fn get_command(cmd: CliCommand) -> clap::Command {
match cmd {
CliCommand::InstanceGet => Self::cli_instance_get(),
CliCommand::InstanceEnsure => Self::cli_instance_ensure(),
CliCommand::InstanceIssueCrucibleSnapshotRequest => {
Self::cli_instance_issue_crucible_snapshot_request()
}
CliCommand::InstanceMigrateStatus => Self::cli_instance_migrate_status(),
CliCommand::InstanceSerial => Self::cli_instance_serial(),
CliCommand::InstanceStatePut => Self::cli_instance_state_put(),
CliCommand::InstanceStateMonitor => Self::cli_instance_state_monitor(),
}
}
pub async fn execute(&self, cmd: CliCommand, matches: &clap::ArgMatches) {
let _ = match cmd {
CliCommand::InstanceGet => {
self.execute_instance_get(matches).await;
}
CliCommand::InstanceEnsure => {
self.execute_instance_ensure(matches).await;
}
CliCommand::InstanceIssueCrucibleSnapshotRequest => {
self.execute_instance_issue_crucible_snapshot_request(matches)
.await;
}
CliCommand::InstanceMigrateStatus => {
self.execute_instance_migrate_status(matches).await;
}
CliCommand::InstanceSerial => {
self.execute_instance_serial(matches).await;
}
CliCommand::InstanceStatePut => {
self.execute_instance_state_put(matches).await;
}
CliCommand::InstanceStateMonitor => {
self.execute_instance_state_monitor(matches).await;
}
};
}
}
pub trait CliOverride {
fn cli_instance_get(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_instance_get(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_instance_ensure(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_instance_ensure(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_instance_issue_crucible_snapshot_request(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_instance_issue_crucible_snapshot_request(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_instance_migrate_status(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_instance_migrate_status(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_instance_serial(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_instance_serial(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_instance_state_put(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_instance_state_put(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
fn cli_instance_state_monitor(cmd: clap::Command) -> clap::Command {
cmd
}
fn execute_instance_state_monitor(
&self,
matches: &clap::ArgMatches,
request: &mut (),
body: &mut (),
) -> Result<(), String> {
Ok(())
}
}
#[derive(Copy, Clone, Debug)]
pub enum CliCommand {
InstanceGet,
InstanceEnsure,
InstanceIssueCrucibleSnapshotRequest,
InstanceMigrateStatus,
InstanceSerial,
InstanceStatePut,
InstanceStateMonitor,
}
impl CliCommand {
pub fn iter() -> impl Iterator<Item = CliCommand> {
vec![
CliCommand::InstanceGet,
CliCommand::InstanceEnsure,
CliCommand::InstanceIssueCrucibleSnapshotRequest,
CliCommand::InstanceMigrateStatus,
CliCommand::InstanceSerial,
CliCommand::InstanceStatePut,
CliCommand::InstanceStateMonitor,
]
.into_iter()
}
}

View File

@ -34,6 +34,7 @@ fn verify_apis(openapi_file: &str) {
let spec = load_api(in_path);
// Positional generation.
let mut generator = Generator::default();
let output = generator.generate_text_normalize_comments(&spec).unwrap();
expectorate::assert_contents(
@ -41,6 +42,7 @@ fn verify_apis(openapi_file: &str) {
&output,
);
// Builder generation with derives and patches.
let mut generator = Generator::new(
GenerationSettings::default()
.with_interface(InterfaceStyle::Builder)
@ -65,17 +67,24 @@ fn verify_apis(openapi_file: &str) {
&output,
);
// Builder generation with tags.
let mut generator = Generator::new(
GenerationSettings::default()
.with_interface(InterfaceStyle::Builder)
.with_tag(TagStyle::Separate),
);
let output = generator.generate_text_normalize_comments(&spec).unwrap();
println!("{output}");
expectorate::assert_contents(
format!("tests/output/{}-builder-tagged.out", openapi_stem),
&output,
);
// CLI generation.
let output = generator.cli_text(&spec, "sdk").unwrap();
expectorate::assert_contents(
format!("tests/output/{}-cli.out", openapi_stem),
&output,
);
}
#[test]

View File

@ -27,20 +27,17 @@ mod builder_untagged {
use futures::StreamExt;
mod nexus_client {
use std::convert::Infallible;
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct MyIpv4Net(pub String);
impl std::fmt::Display for MyIpv4Net {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
#[derive(Clone, serde::Serialize, serde::Deserialize, Debug)]
pub struct MyIpv4Net(String);
impl std::str::FromStr for MyIpv4Net {
type Err = std::convert::Infallible;
fn from_str(value: &str) -> Result<Self, Self::Err> {
Ok(Self(value.to_string()))
}
}
impl std::str::FromStr for MyIpv4Net {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_string()))
impl ToString for MyIpv4Net {
fn to_string(&self) -> String {
self.0.clone()
}
}
progenitor::generate_api!(
@ -62,9 +59,7 @@ mod builder_untagged {
pub fn _ignore() {
// Verify the replacement above.
let _ignore = nexus_client::types::IpNet::V4(nexus_client::MyIpv4Net(
String::new(),
));
let _ignore = nexus_client::types::IpNet::V4("".parse().unwrap());
let client = Client::new("");
let stream = client