From 9d8a7898a875a09d31d0e01b3b32f2a1ad3add3a Mon Sep 17 00:00:00 2001 From: ahirner Date: Thu, 19 Oct 2023 07:57:53 +0200 Subject: [PATCH 01/38] un-require unstable cargo fmt for now --- cargo-progenitor/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cargo-progenitor/src/main.rs b/cargo-progenitor/src/main.rs index 264d06d1..e7b50eae 100644 --- a/cargo-progenitor/src/main.rs +++ b/cargo-progenitor/src/main.rs @@ -94,8 +94,8 @@ impl From for TagStyle { fn reformat_code(input: String) -> String { let config = rustfmt_wrapper::config::Config { - normalize_doc_attributes: Some(true), - wrap_comments: Some(true), + //normalize_doc_attributes: Some(true), + //wrap_comments: Some(true), ..Default::default() }; space_out_items(rustfmt_wrapper::rustfmt_config(config, input).unwrap()) From 95850e31da58bcbadebbdfd7e47103d07a58c021 Mon Sep 17 00:00:00 2001 From: ahirner Date: Thu, 19 Oct 2023 07:58:33 +0200 Subject: [PATCH 02/38] enable reqwest's multipart --- Cargo.lock | 20 ++++++++++++++++++++ progenitor-client/Cargo.toml | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 0b594b40..6dbe4bec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1145,6 +1145,16 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -1602,6 +1612,7 @@ dependencies = [ "js-sys", "log", "mime", + "mime_guess", "native-tls", "once_cell", "percent-encoding", @@ -2433,6 +2444,15 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.7" diff --git a/progenitor-client/Cargo.toml b/progenitor-client/Cargo.toml index 3eb1bb5b..2faf8b4a 100644 --- a/progenitor-client/Cargo.toml +++ b/progenitor-client/Cargo.toml @@ -10,7 +10,7 @@ description = "An OpenAPI client generator - client support" bytes = "1.5.0" futures-core = "0.3.28" percent-encoding = "2.3" -reqwest = { version = "0.11.22", default-features = false, features = ["json", "stream"] } +reqwest = { version = "0.11.22", default-features = false, features = ["json", "stream", "multipart"] } serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7.1" From 92a32a294050072dde5fb4f0fb2baf443dcf1b72 Mon Sep 17 00:00:00 2001 From: ahirner Date: Thu, 19 Oct 2023 08:03:52 +0200 Subject: [PATCH 03/38] intro BodyContentType::FormData WIP --- cargo-progenitor/src/main.rs | 2 +- progenitor-client/src/progenitor_client.rs | 22 ++++++++++++++++++++ progenitor-impl/src/method.rs | 24 +++++++++++++++++++++- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/cargo-progenitor/src/main.rs b/cargo-progenitor/src/main.rs index e7b50eae..c5a7298a 100644 --- a/cargo-progenitor/src/main.rs +++ b/cargo-progenitor/src/main.rs @@ -234,7 +234,7 @@ pub fn dependencies(builder: Generator, include_client: bool) -> Vec { let mut deps = vec![ format!("bytes = \"{}\"", dependency_versions.get("bytes").unwrap()), format!("futures-core = \"{}\"", dependency_versions.get("futures-core").unwrap()), - format!("reqwest = {{ version = \"{}\", default-features=false, features = [\"json\", \"stream\"] }}", dependency_versions.get("reqwest").unwrap()), + format!("reqwest = {{ version = \"{}\", default-features=false, features = [\"json\", \"stream\", \"multipart\"] }}", dependency_versions.get("reqwest").unwrap()), format!("serde = {{ version = \"{}\", features = [\"derive\"] }}", dependency_versions.get("serde").unwrap()), format!("serde_urlencoded = \"{}\"", dependency_versions.get("serde_urlencoded").unwrap()), ]; diff --git a/progenitor-client/src/progenitor_client.rs b/progenitor-client/src/progenitor_client.rs index a412f7b3..fae24445 100644 --- a/progenitor-client/src/progenitor_client.rs +++ b/progenitor-client/src/progenitor_client.rs @@ -397,6 +397,10 @@ pub trait RequestBuilderExt { self, body: &T, ) -> Result>; + fn form_multipart( + self, + body: &T, + ) -> Result>; } impl RequestBuilderExt for RequestBuilder { @@ -415,4 +419,22 @@ impl RequestBuilderExt for RequestBuilder { Error::InvalidRequest("failed to serialize body".to_string()) })?)) } + + fn form_multipart( + self, + body: &T, + ) -> Result> { + let mut local_var_form = reqwest::multipart::Form::new(); + // todo: fill form and self.multipart it + Ok(self + .header( + reqwest::header::CONTENT_TYPE, + reqwest::header::HeaderValue::from_static( + "application/x-www-form-urlencoded", + ), + ) + .body(serde_urlencoded::to_string(body).map_err(|_| { + Error::InvalidRequest("failed to serialize body".to_string()) + })?)) + } } diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 9b219e0e..0feb5b51 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -138,6 +138,7 @@ pub enum BodyContentType { Json, FormUrlencoded, Text(String), + FormData, } impl FromStr for BodyContentType { @@ -152,6 +153,7 @@ impl FromStr for BodyContentType { "text/plain" | "text/x-markdown" => { Ok(Self::Text(String::from(&s[..offset]))) } + "multipart/form-data" => Ok(Self::FormData), _ => Err(Error::UnexpectedFormat(format!( "unexpected content type: {}", s @@ -169,6 +171,7 @@ impl fmt::Display for BodyContentType { Self::Json => "application/json", Self::FormUrlencoded => "application/x-www-form-urlencoded", Self::Text(typ) => &typ, + Self::FormData => "multipart/form-data", }) } } @@ -996,6 +999,23 @@ impl Generator { // returns an error in the case of a serialization failure. .form_urlencoded(&body)? }), + ( + OperationParameterKind::Body( + BodyContentType::FormData + ), + OperationParameterType::Type(typ), + ) => { + let params = &method.params.iter().filter_map(|param| match ¶m.kind{ + OperationParameterKind::Body(typ1) => { Some((typ, typ1))}, + _ => None, + } ).collect::>(); + // todo: somehow get hold of form schema and pass that + // todo: in this, recognize binary format of string and + // interpret them as files respectively reqwest::multipart::Part::stream + dbg!(params); + Some(quote! { + .form_multipart(&body)? + })}, (OperationParameterKind::Body(_), _) => { unreachable!("invalid body kind/type combination") } @@ -2205,7 +2225,9 @@ impl Generator { }?; OperationParameterType::RawBody } - BodyContentType::Json | BodyContentType::FormUrlencoded => { + BodyContentType::Json + | BodyContentType::FormUrlencoded + | BodyContentType::FormData => { // TODO it would be legal to have the encoding field set for // application/x-www-form-urlencoded content, but I'm not sure // how to interpret the values. From 2ae53f7088f867c180e274c4a099119df2588574 Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 22 Oct 2023 07:30:01 +0200 Subject: [PATCH 04/38] impl files as Into WIP * own OperationParamaterType * delare replaced PartMeta --- progenitor-client/src/progenitor_client.rs | 22 ----- progenitor-impl/src/lib.rs | 46 ++++++++- progenitor-impl/src/method.rs | 104 +++++++++++++++------ 3 files changed, 123 insertions(+), 49 deletions(-) diff --git a/progenitor-client/src/progenitor_client.rs b/progenitor-client/src/progenitor_client.rs index fae24445..a412f7b3 100644 --- a/progenitor-client/src/progenitor_client.rs +++ b/progenitor-client/src/progenitor_client.rs @@ -397,10 +397,6 @@ pub trait RequestBuilderExt { self, body: &T, ) -> Result>; - fn form_multipart( - self, - body: &T, - ) -> Result>; } impl RequestBuilderExt for RequestBuilder { @@ -419,22 +415,4 @@ impl RequestBuilderExt for RequestBuilder { Error::InvalidRequest("failed to serialize body".to_string()) })?)) } - - fn form_multipart( - self, - body: &T, - ) -> Result> { - let mut local_var_form = reqwest::multipart::Form::new(); - // todo: fill form and self.multipart it - Ok(self - .header( - reqwest::header::CONTENT_TYPE, - reqwest::header::HeaderValue::from_static( - "application/x-www-form-urlencoded", - ), - ) - .body(serde_urlencoded::to_string(body).map_err(|_| { - Error::InvalidRequest("failed to serialize body".to_string()) - })?)) - } } diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index fb853963..7583b270 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -46,7 +46,7 @@ pub struct Generator { uses_websockets: bool, } -#[derive(Default, Clone)] +#[derive(Clone)] pub struct GenerationSettings { interface: InterfaceStyle, tag: TagStyle, @@ -60,6 +60,50 @@ pub struct GenerationSettings { convert: Vec<(schemars::schema::SchemaObject, String, Vec)>, } +impl Default for GenerationSettings { + fn default() -> Self { + // set Type from String to meta data for form body parts + use schemars::schema::{InstanceType, SchemaObject}; + let bin_string = SchemaObject { + instance_type: Some(schemars::schema::SingleOrVec::Single( + Box::new(InstanceType::String), + )), + format: Some("binary".to_string()), + ..SchemaObject::default() + }; + // todo: best place to create default conversion, or typify? + // todo: declare PartMeta struct and TypeImpls in progenitor_client + /* + use schemars::{schema_for, JsonSchema}; + use serde::Serialize; + #[derive(JsonSchema, Serialize)] + struct PartMeta { + #[serde(default)] + file_name: Option, + #[serde(default)] + mime_str: Option, + } + let bin_schema = schema_for!(PartMeta).schema.into(); + let bin_typ = self + .type_space + .add_type_with_name(&bin_schema, Some("PartMeta".to_string()))?; + */ + let convert = + (bin_string, "PartMeta".to_string(), vec![TypeImpl::Default]); + Self { + interface: InterfaceStyle::default(), + tag: TagStyle::default(), + inner_type: None, + pre_hook: None, + post_hook: None, + extra_derives: Vec::default(), + patch: HashMap::default(), + replace: HashMap::default(), + convert: vec![convert], + } + } +} + #[derive(Clone, Deserialize, PartialEq, Eq)] pub enum InterfaceStyle { Positional, diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 0feb5b51..0aa64679 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -441,9 +441,7 @@ impl Generator { self.uses_websockets = true; } - if let Some(body_param) = self.get_body_param(operation, components)? { - params.push(body_param); - } + params.extend(self.get_body_params(operation, components)?); let tmp = crate::template::parse(path)?; let names = tmp.names(); @@ -1003,18 +1001,11 @@ impl Generator { OperationParameterKind::Body( BodyContentType::FormData ), - OperationParameterType::Type(typ), + OperationParameterType::Type(_), ) => { - let params = &method.params.iter().filter_map(|param| match ¶m.kind{ - OperationParameterKind::Body(typ1) => { Some((typ, typ1))}, - _ => None, - } ).collect::>(); - // todo: somehow get hold of form schema and pass that - // todo: in this, recognize binary format of string and - // interpret them as files respectively reqwest::multipart::Part::stream - dbg!(params); + // todo: generate form Some(quote! { - .form_multipart(&body)? + .multipart(form) })}, (OperationParameterKind::Body(_), _) => { unreachable!("invalid body kind/type combination") @@ -1023,7 +1014,7 @@ impl Generator { } }); // ... and there can be at most one body. - assert!(body_func.clone().count() <= 1); + //assert!(body_func.clone().count() <= 1); let (success_response_items, response_type) = self.extract_responses( method, @@ -2122,19 +2113,19 @@ impl Generator { impl_body } - fn get_body_param( + fn get_body_params( &mut self, operation: &openapiv3::Operation, components: &Option, - ) -> Result> { + ) -> Result> { let body = match &operation.request_body { Some(body) => body.item(components)?, - None => return Ok(None), + None => return Ok(vec![]), }; let (content_str, media_type) = match (body.content.first(), body.content.len()) { - (None, _) => return Ok(None), + (None, _) => return Ok(vec![]), (Some(first), 1) => first, (_, n) => todo!( "more media types than expected for {}: {}", @@ -2225,9 +2216,7 @@ impl Generator { }?; OperationParameterType::RawBody } - BodyContentType::Json - | BodyContentType::FormUrlencoded - | BodyContentType::FormData => { + BodyContentType::Json | BodyContentType::FormUrlencoded => { // TODO it would be legal to have the encoding field set for // application/x-www-form-urlencoded content, but I'm not sure // how to interpret the values. @@ -2246,15 +2235,80 @@ impl Generator { .add_type_with_name(&schema.to_schema(), Some(name))?; OperationParameterType::Type(typ) } + BodyContentType::FormData => { + let name = sanitize( + &format!( + "{}-body", + operation.operation_id.as_ref().unwrap(), + ), + Case::Pascal, + ); + let schema = schema.to_schema(); + let typ = + self.type_space.add_type_with_name(&schema, Some(name))?; + OperationParameterType::Type(typ) + } }; - Ok(Some(OperationParameter { + let mut body_params = vec![]; + if let BodyContentType::FormData = content_type { + match &schema.item(components)?.schema_kind { + openapiv3::SchemaKind::Type( + openapiv3::Type::Object(obj_type), + ) => obj_type + .properties + .iter() + .filter_map(|(key, item)| match item { + ReferenceOr::Item(i) => { + match (&i.schema_data, &i.schema_kind) { + (openapiv3::SchemaData { + nullable: false, + discriminator: None, + default: None, + description, + .. + } , + openapiv3::SchemaKind::Type(openapiv3::Type::String( + openapiv3::StringType { + format: + openapiv3::VariantOrUnknownOrEmpty::Item( + openapiv3::StringFormat::Binary, + ), + pattern: None, + enumeration, + min_length: None, + max_length: None, + }, + )), + ) if enumeration.is_empty() => { + Some(OperationParameter { + name: key.to_string(), + api_name: key.to_string(), + description: description.clone(), + // todo: own Type and FormData Kind + typ: OperationParameterType::RawBody, + kind: OperationParameterKind::Body(BodyContentType::OctetStream), + }) + } + , + _ => None, + } + } + _ => None, + }). + for_each(|p| body_params.push(p)), + _ => {}, + } + }; + let body_param = OperationParameter { name: "body".to_string(), api_name: "body".to_string(), description: body.description.clone(), typ, kind: OperationParameterKind::Body(content_type), - })) + }; + body_params.push(body_param); + Ok(body_params) } } @@ -2416,9 +2470,7 @@ fn sort_params(raw_params: &mut [OperationParameter], names: &[String]) { ( OperationParameterKind::Body(_), OperationParameterKind::Body(_), - ) => { - panic!("should only be one body") - } + ) => Ordering::Less, // Header params are in lexicographic order. ( From 5bc61b0c1cb38093adb1932b6189b8faddcc8932 Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 22 Oct 2023 10:29:25 +0200 Subject: [PATCH 05/38] switch binary string repr to some unit for now --- progenitor-impl/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index 7583b270..136248a7 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -88,8 +88,9 @@ impl Default for GenerationSettings { .type_space .add_type_with_name(&bin_schema, Some("PartMeta".to_string()))?; */ + // () errors and doesn't convert to unit let convert = - (bin_string, "PartMeta".to_string(), vec![TypeImpl::Default]); + (bin_string, "Vec<()>".to_string(), vec![TypeImpl::Default]); Self { interface: InterfaceStyle::default(), tag: TagStyle::default(), From 31daaedf377389aba2dfcf6d04b71c23a6e9676b Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 22 Oct 2023 11:00:11 +0200 Subject: [PATCH 06/38] introduce (optional) FormPart WIP Todo: * some work on cli * some work on httpmock * generate form in method body --- progenitor-impl/src/cli.rs | 8 +++ progenitor-impl/src/httpmock.rs | 17 +++++ progenitor-impl/src/lib.rs | 2 + progenitor-impl/src/method.rs | 110 ++++++++++++++++++++++++++++---- 4 files changed, 124 insertions(+), 13 deletions(-) diff --git a/progenitor-impl/src/cli.rs b/progenitor-impl/src/cli.rs index e03067b2..91ab8f07 100644 --- a/progenitor-impl/src/cli.rs +++ b/progenitor-impl/src/cli.rs @@ -408,6 +408,14 @@ impl Generator { OperationParameterType::Type(body_type_id) => { Some(body_type_id) } + OperationParameterType::FormPart => { + let content_type = ¶m.kind; + eprintln!( + "todo: add --{}-file parameter for {content_type:?})", + param.name + ); + None + } }); if let Some(body_type_id) = maybe_body_type_id { diff --git a/progenitor-impl/src/httpmock.rs b/progenitor-impl/src/httpmock.rs index 1689590e..4bae5c04 100644 --- a/progenitor-impl/src/httpmock.rs +++ b/progenitor-impl/src/httpmock.rs @@ -179,6 +179,19 @@ impl Generator { }, _ => unreachable!(), }, + OperationParameterType::FormPart => match kind { + OperationParameterKind::Body( + BodyContentType::FormData(required), + ) => { + // todo: how to mock? + if *required { + quote! { Part } + } else { + quote! { Option } + } + } + _ => unreachable!(), + }, }; let name_ident = format_ident!("{}", name); @@ -257,6 +270,10 @@ impl Generator { _ => unreachable!(), } } + OperationParameterType::FormPart => { + // todo: what does it do? + quote! { self.0 } + } } } }; diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index 136248a7..d4fbf9b5 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -362,6 +362,8 @@ impl Generator { use progenitor_client::{encode_path, RequestBuilderExt}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; + #[allow(unused_imports)] + use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 0aa64679..9f77f6ac 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -106,6 +106,7 @@ pub struct OperationParameter { pub enum OperationParameterType { Type(TypeId), RawBody, + FormPart, } #[derive(Debug, PartialEq, Eq)] @@ -123,6 +124,9 @@ impl OperationParameterKind { OperationParameterKind::Path => true, OperationParameterKind::Query(required) => *required, OperationParameterKind::Header(required) => *required, + OperationParameterKind::Body(BodyContentType::FormData( + required, + )) => *required, // TODO may be optional OperationParameterKind::Body(_) => true, } @@ -138,7 +142,7 @@ pub enum BodyContentType { Json, FormUrlencoded, Text(String), - FormData, + FormData(bool), } impl FromStr for BodyContentType { @@ -153,7 +157,7 @@ impl FromStr for BodyContentType { "text/plain" | "text/x-markdown" => { Ok(Self::Text(String::from(&s[..offset]))) } - "multipart/form-data" => Ok(Self::FormData), + "multipart/form-data" => Ok(Self::FormData(true)), _ => Err(Error::UnexpectedFormat(format!( "unexpected content type: {}", s @@ -171,7 +175,7 @@ impl fmt::Display for BodyContentType { Self::Json => "application/json", Self::FormUrlencoded => "application/x-www-form-urlencoded", Self::Text(typ) => &typ, - Self::FormData => "multipart/form-data", + Self::FormData(_) => "multipart/form-data", }) } } @@ -634,6 +638,12 @@ impl Generator { } } (OperationParameterType::RawBody, true) => unreachable!(), + (OperationParameterType::FormPart, false) => { + quote! { Part } + } + (OperationParameterType::FormPart, true) => { + quote! { Option } + } }; quote! { #name: #typ @@ -999,7 +1009,7 @@ impl Generator { }), ( OperationParameterKind::Body( - BodyContentType::FormData + BodyContentType::FormData(true) ), OperationParameterType::Type(_), ) => { @@ -1007,6 +1017,12 @@ impl Generator { Some(quote! { .multipart(form) })}, + ( + OperationParameterKind::Body( + BodyContentType::FormData(_) + ), + OperationParameterType::FormPart, + ) => None, // FormData parts are handled separately (OperationParameterKind::Body(_), _) => { unreachable!("invalid body kind/type combination") } @@ -1014,7 +1030,7 @@ impl Generator { } }); // ... and there can be at most one body. - //assert!(body_func.clone().count() <= 1); + assert!(body_func.clone().count() <= 1); let (success_response_items, response_type) = self.extract_responses( method, @@ -1528,6 +1544,22 @@ impl Generator { cloneable = false; Ok(quote! { Result }) } + OperationParameterType::FormPart => { + cloneable = false; + if let OperationParameterKind::Body( + BodyContentType::FormData(required), + ) = param.kind + { + // todo: probably incorrect, to what does this translate? + if required { + Ok(quote! { reqwest::multipart::Part }) + } else { + Ok(quote! { Option }) + } + } else { + unreachable!() + } + } }) .collect::>>()?; @@ -1560,6 +1592,24 @@ impl Generator { let err_msg = format!("{} was not initialized", param.name); Ok(quote! { Err(#err_msg.to_string()) }) } + OperationParameterType::FormPart => { + if let OperationParameterKind::Body( + BodyContentType::FormData(required), + ) = param.kind + { + // todo: probably incorrect, to what does this translate? + // Result or not? + if required { + let err_msg = + format!("{} was not initialized", param.name); + Ok(quote! { Err(#err_msg.to_string()) }) + } else { + Ok(quote! { Ok(None) }) + } + } else { + unreachable!() + } + } }) .collect::>>()?; @@ -1584,6 +1634,7 @@ impl Generator { } } OperationParameterType::RawBody => Ok(quote! {}), + OperationParameterType::FormPart => Ok(quote! {}), }) .collect::>>()?; @@ -1651,6 +1702,7 @@ impl Generator { // a `body_map()` method that operates on the // builder itself. (Some(builder_name), false) => { + // todo: rm for ...Kind::FormData assert_eq!(param.name, "body"); let typ = ty.ident(); let err_msg = format!( @@ -1719,6 +1771,30 @@ impl Generator { }, _ => unreachable!(), } + OperationParameterType::FormPart => match ¶m.kind { + OperationParameterKind::Body(BodyContentType::FormData(required)) => { + if *required { + Ok(quote! { + pub fn #param_name(mut self, value: P) -> Self + where P: reqwest::multipart::Part + { + self.#param_name = value; + self + } + }) + } else { + Ok(quote! { + pub fn #param_name(mut self, value: Option

) -> Self + where P: reqwest::multipart::Part + { + self.#param_name = value; + self + } + }) + } + }, + _ => unreachable!(), + }, } }) .collect::>>()?; @@ -2235,7 +2311,7 @@ impl Generator { .add_type_with_name(&schema.to_schema(), Some(name))?; OperationParameterType::Type(typ) } - BodyContentType::FormData => { + BodyContentType::FormData(_) => { let name = sanitize( &format!( "{}-body", @@ -2251,7 +2327,8 @@ impl Generator { }; let mut body_params = vec![]; - if let BodyContentType::FormData = content_type { + // additional Parts from Type(TypeId) fields + if let BodyContentType::FormData(_) = content_type { match &schema.item(components)?.schema_kind { openapiv3::SchemaKind::Type( openapiv3::Type::Object(obj_type), @@ -2281,13 +2358,14 @@ impl Generator { }, )), ) if enumeration.is_empty() => { + let required = obj_type.required.contains(key); + dbg!((key, description.clone().unwrap(), required, obj_type.required.clone())); Some(OperationParameter { name: key.to_string(), api_name: key.to_string(), description: description.clone(), - // todo: own Type and FormData Kind - typ: OperationParameterType::RawBody, - kind: OperationParameterKind::Body(BodyContentType::OctetStream), + typ: OperationParameterType::FormPart, + kind: OperationParameterKind::Body(BodyContentType::FormData(required)), }) } , @@ -2454,7 +2532,7 @@ fn sort_params(raw_params: &mut [OperationParameter], names: &[String]) { OperationParameterKind::Header(_), ) => Ordering::Less, - // Body params are last and should be singular. + // Body params are almost last and should be singular. ( OperationParameterKind::Body(_), OperationParameterKind::Path, @@ -2467,11 +2545,17 @@ fn sort_params(raw_params: &mut [OperationParameter], names: &[String]) { OperationParameterKind::Body(_), OperationParameterKind::Header(_), ) => Ordering::Greater, + // FormPart should come after their serializable Body ( OperationParameterKind::Body(_), - OperationParameterKind::Body(_), + OperationParameterKind::Body(BodyContentType::FormData(_)), ) => Ordering::Less, - + ( + OperationParameterKind::Body(_), + OperationParameterKind::Body(_), + ) => { + panic!("only one body with non FormData expected") + } // Header params are in lexicographic order. ( OperationParameterKind::Header(_), From 7ce4a0f0ed912bfb65154d24a432c042180d25c0 Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 22 Oct 2023 12:01:12 +0200 Subject: [PATCH 07/38] cut and dry --- progenitor-impl/src/method.rs | 132 +++++++++++++++++----------------- 1 file changed, 67 insertions(+), 65 deletions(-) diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 9f77f6ac..4bc4d51b 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -2292,7 +2292,9 @@ impl Generator { }?; OperationParameterType::RawBody } - BodyContentType::Json | BodyContentType::FormUrlencoded => { + BodyContentType::Json + | BodyContentType::FormUrlencoded + | BodyContentType::FormData(_) => { // TODO it would be legal to have the encoding field set for // application/x-www-form-urlencoded content, but I'm not sure // how to interpret the values. @@ -2311,73 +2313,18 @@ impl Generator { .add_type_with_name(&schema.to_schema(), Some(name))?; OperationParameterType::Type(typ) } - BodyContentType::FormData(_) => { - let name = sanitize( - &format!( - "{}-body", - operation.operation_id.as_ref().unwrap(), - ), - Case::Pascal, - ); - let schema = schema.to_schema(); - let typ = - self.type_space.add_type_with_name(&schema, Some(name))?; - OperationParameterType::Type(typ) - } }; - let mut body_params = vec![]; - // additional Parts from Type(TypeId) fields - if let BodyContentType::FormData(_) = content_type { - match &schema.item(components)?.schema_kind { - openapiv3::SchemaKind::Type( - openapiv3::Type::Object(obj_type), - ) => obj_type - .properties - .iter() - .filter_map(|(key, item)| match item { - ReferenceOr::Item(i) => { - match (&i.schema_data, &i.schema_kind) { - (openapiv3::SchemaData { - nullable: false, - discriminator: None, - default: None, - description, - .. - } , - openapiv3::SchemaKind::Type(openapiv3::Type::String( - openapiv3::StringType { - format: - openapiv3::VariantOrUnknownOrEmpty::Item( - openapiv3::StringFormat::Binary, - ), - pattern: None, - enumeration, - min_length: None, - max_length: None, - }, - )), - ) if enumeration.is_empty() => { - let required = obj_type.required.contains(key); - dbg!((key, description.clone().unwrap(), required, obj_type.required.clone())); - Some(OperationParameter { - name: key.to_string(), - api_name: key.to_string(), - description: description.clone(), - typ: OperationParameterType::FormPart, - kind: OperationParameterKind::Body(BodyContentType::FormData(required)), - }) - } - , - _ => None, - } - } - _ => None, - }). - for_each(|p| body_params.push(p)), - _ => {}, - } + let item_schema = schema.item(components)?; + let mut body_params = match (&content_type, &item_schema.schema_kind) { + // add parameters from binary strings of FormData body + ( + BodyContentType::FormData(_), + openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj_type)), + ) => get_form_data_params(obj_type).collect(), + _ => Vec::with_capacity(1), }; + let body_param = OperationParameter { name: "body".to_string(), api_name: "body".to_string(), @@ -2386,10 +2333,65 @@ impl Generator { kind: OperationParameterKind::Body(content_type), }; body_params.push(body_param); + Ok(body_params) } } +fn get_form_data_params( + obj_type: &openapiv3::ObjectType, +) -> impl Iterator + '_ { + obj_type + .properties + .iter() + .filter_map(|(key, item)| match item { + ReferenceOr::Item(i) => Some((key, i)), + _ => None, + }) + .filter_map(|(key, i)| { + match (&i.schema_data, &i.schema_kind) { + ( + openapiv3::SchemaData { + // todo: assert nullable if not required? + //nullable: false, + default: None, + discriminator: None, + description, + .. + }, + openapiv3::SchemaKind::Type(openapiv3::Type::String( + openapiv3::StringType { + format: + openapiv3::VariantOrUnknownOrEmpty::Item( + openapiv3::StringFormat::Binary, + ), + pattern: None, + enumeration, + min_length: None, + max_length: None, + }, + )), + ) if enumeration.is_empty() => { + let required = obj_type.required.contains(key); + dbg!((key, description.clone().unwrap(), required)); + Some((key, required, description)) + } + _ => None, + } + }) + .map(|(key, req, description)| { + let kind = + OperationParameterKind::Body(BodyContentType::FormData(req)); + OperationParameter { + name: key.to_string(), + api_name: key.to_string(), + description: description.clone(), + typ: OperationParameterType::FormPart, + kind, + } + }) +} + fn make_doc_comment(method: &OperationMethod) -> String { let mut buf = String::new(); From 42984709430fbca59aff515c4b81a79e6984a894 Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 22 Oct 2023 14:04:05 +0200 Subject: [PATCH 08/38] generate form in method body WIP except: create form from body --- progenitor-impl/src/method.rs | 71 ++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 4bc4d51b..4da997ef 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -844,6 +844,7 @@ impl Generator { // Generate a unique Ident for internal variables let url_ident = unique_ident_from("url", ¶m_names); let query_ident = unique_ident_from("query", ¶m_names); + let form_ident = unique_ident_from("form", ¶m_names); let request_ident = unique_ident_from("request", ¶m_names); let response_ident = unique_ident_from("response", ¶m_names); let result_ident = unique_ident_from("result", ¶m_names); @@ -963,6 +964,9 @@ impl Generator { let #url_ident = #url_path; }; + // Generate code to generate a form + let form_parts = get_form_builder(&method.params, &form_ident); + // Generate code to handle the body param. let body_func = method.params.iter().filter_map(|param| { match (¶m.kind, ¶m.typ) { @@ -1013,9 +1017,8 @@ impl Generator { ), OperationParameterType::Type(_), ) => { - // todo: generate form Some(quote! { - .multipart(form) + .multipart(#form_ident) })}, ( OperationParameterKind::Body( @@ -1184,6 +1187,8 @@ impl Generator { #headers_build + #(#form_parts)* + let #request_ident = #client.client . #method_func (#url_ident) #accept_header @@ -2392,6 +2397,68 @@ fn get_form_data_params( }) } +fn get_form_builder( + params: &[OperationParameter], + form_ident: &proc_macro2::Ident, +) -> Vec { + let mut form_parts = vec![]; + let mut form_parts_optional = vec![]; + let mut build_form = false; + + params + .iter() + .filter_map(|param| match (¶m.kind, ¶m.typ) { + ( + OperationParameterKind::Body(BodyContentType::FormData( + required, + )), + OperationParameterType::FormPart, + ) => { + let param_ident = format_ident!("{}", param.api_name.as_str()); + let name = param.api_name.clone(); + Some((*required, name, param_ident)) + } + ( + OperationParameterKind::Body(BodyContentType::FormData(true)), + OperationParameterType::Type(_), + ) => { + build_form = true; + None + } + _ => None, + }) + .for_each(|(required, name, param_ident)| { + if required { + form_parts.push(quote! { .part(#name, #param_ident) }); + } else { + form_parts_optional.push(quote! { + if let Some(#param_ident) = #param_ident { + #form_ident = #form_ident.part(#name, #param_ident); + }; + }); + }; + }); + + // only build if any param or at least a body without additional + // parts + if form_parts.len() > 0 || form_parts_optional.len() > 0 || build_form { + let mutt = if form_parts_optional.len() > 0 { + quote! {mut} + } else { + quote! {} + }; + form_parts.insert( + 0, + // todo: create form from body + quote! {let #mutt #form_ident = reqwest::multipart::Form::new()}, + ); + form_parts.push(quote! {;}); + form_parts.extend(form_parts_optional); + }; + + form_parts +} + fn make_doc_comment(method: &OperationMethod) -> String { let mut buf = String::new(); From 0f71c90a01c32dc07f75f00aa75c478665c69edc Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 22 Oct 2023 15:04:14 +0200 Subject: [PATCH 09/38] Revert "un-require unstable cargo fmt for now" This reverts commit 9d8a7898a875a09d31d0e01b3b32f2a1ad3add3a. --- cargo-progenitor/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cargo-progenitor/src/main.rs b/cargo-progenitor/src/main.rs index c5a7298a..ce965f80 100644 --- a/cargo-progenitor/src/main.rs +++ b/cargo-progenitor/src/main.rs @@ -94,8 +94,8 @@ impl From for TagStyle { fn reformat_code(input: String) -> String { let config = rustfmt_wrapper::config::Config { - //normalize_doc_attributes: Some(true), - //wrap_comments: Some(true), + normalize_doc_attributes: Some(true), + wrap_comments: Some(true), ..Default::default() }; space_out_items(rustfmt_wrapper::rustfmt_config(config, input).unwrap()) From e89338c265f35cf87c6bce4a954614e0eedba4bb Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 22 Oct 2023 15:20:04 +0200 Subject: [PATCH 10/38] EXPECTORATE=overwrite cargo test adds: use reqwest::multipart::Part; --- progenitor-impl/tests/output/buildomat-builder-tagged.out | 2 ++ progenitor-impl/tests/output/buildomat-builder.out | 2 ++ progenitor-impl/tests/output/buildomat-positional.out | 2 ++ progenitor-impl/tests/output/keeper-builder-tagged.out | 2 ++ progenitor-impl/tests/output/keeper-builder.out | 2 ++ progenitor-impl/tests/output/keeper-positional.out | 2 ++ progenitor-impl/tests/output/nexus-builder-tagged.out | 2 ++ progenitor-impl/tests/output/nexus-builder.out | 2 ++ progenitor-impl/tests/output/nexus-positional.out | 2 ++ progenitor-impl/tests/output/param-collision-builder-tagged.out | 2 ++ progenitor-impl/tests/output/param-collision-builder.out | 2 ++ progenitor-impl/tests/output/param-collision-positional.out | 2 ++ progenitor-impl/tests/output/param-overrides-builder-tagged.out | 2 ++ progenitor-impl/tests/output/param-overrides-builder.out | 2 ++ progenitor-impl/tests/output/param-overrides-positional.out | 2 ++ progenitor-impl/tests/output/propolis-server-builder-tagged.out | 2 ++ progenitor-impl/tests/output/propolis-server-builder.out | 2 ++ progenitor-impl/tests/output/propolis-server-positional.out | 2 ++ progenitor-impl/tests/output/test_default_params_builder.out | 2 ++ progenitor-impl/tests/output/test_default_params_positional.out | 2 ++ progenitor-impl/tests/output/test_freeform_response.out | 2 ++ progenitor-impl/tests/output/test_renamed_parameters.out | 2 ++ 22 files changed, 44 insertions(+) diff --git a/progenitor-impl/tests/output/buildomat-builder-tagged.out b/progenitor-impl/tests/output/buildomat-builder-tagged.out index 832b351d..5874b70a 100644 --- a/progenitor-impl/tests/output/buildomat-builder-tagged.out +++ b/progenitor-impl/tests/output/buildomat-builder-tagged.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/buildomat-builder.out b/progenitor-impl/tests/output/buildomat-builder.out index 077f8489..1f3e3fb0 100644 --- a/progenitor-impl/tests/output/buildomat-builder.out +++ b/progenitor-impl/tests/output/buildomat-builder.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/buildomat-positional.out b/progenitor-impl/tests/output/buildomat-positional.out index 1713415c..c5d59df7 100644 --- a/progenitor-impl/tests/output/buildomat-positional.out +++ b/progenitor-impl/tests/output/buildomat-positional.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/keeper-builder-tagged.out b/progenitor-impl/tests/output/keeper-builder-tagged.out index 42065bb3..11f7412a 100644 --- a/progenitor-impl/tests/output/keeper-builder-tagged.out +++ b/progenitor-impl/tests/output/keeper-builder-tagged.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/keeper-builder.out b/progenitor-impl/tests/output/keeper-builder.out index bd019a77..f404648e 100644 --- a/progenitor-impl/tests/output/keeper-builder.out +++ b/progenitor-impl/tests/output/keeper-builder.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/keeper-positional.out b/progenitor-impl/tests/output/keeper-positional.out index 280b3ed2..291da4f4 100644 --- a/progenitor-impl/tests/output/keeper-positional.out +++ b/progenitor-impl/tests/output/keeper-positional.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/nexus-builder-tagged.out b/progenitor-impl/tests/output/nexus-builder-tagged.out index 679c98ac..e1448a74 100644 --- a/progenitor-impl/tests/output/nexus-builder-tagged.out +++ b/progenitor-impl/tests/output/nexus-builder-tagged.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/nexus-builder.out b/progenitor-impl/tests/output/nexus-builder.out index 9139fac9..bf312015 100644 --- a/progenitor-impl/tests/output/nexus-builder.out +++ b/progenitor-impl/tests/output/nexus-builder.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/nexus-positional.out b/progenitor-impl/tests/output/nexus-positional.out index 60652844..580d4892 100644 --- a/progenitor-impl/tests/output/nexus-positional.out +++ b/progenitor-impl/tests/output/nexus-positional.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/param-collision-builder-tagged.out b/progenitor-impl/tests/output/param-collision-builder-tagged.out index 4de691e0..b1542561 100644 --- a/progenitor-impl/tests/output/param-collision-builder-tagged.out +++ b/progenitor-impl/tests/output/param-collision-builder-tagged.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/param-collision-builder.out b/progenitor-impl/tests/output/param-collision-builder.out index 5efd0dc0..b5cc8c83 100644 --- a/progenitor-impl/tests/output/param-collision-builder.out +++ b/progenitor-impl/tests/output/param-collision-builder.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/param-collision-positional.out b/progenitor-impl/tests/output/param-collision-positional.out index 8b40af66..25c615f4 100644 --- a/progenitor-impl/tests/output/param-collision-positional.out +++ b/progenitor-impl/tests/output/param-collision-positional.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/param-overrides-builder-tagged.out b/progenitor-impl/tests/output/param-overrides-builder-tagged.out index 71d12a3a..7b34531b 100644 --- a/progenitor-impl/tests/output/param-overrides-builder-tagged.out +++ b/progenitor-impl/tests/output/param-overrides-builder-tagged.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/param-overrides-builder.out b/progenitor-impl/tests/output/param-overrides-builder.out index d0c638f8..b623b6b6 100644 --- a/progenitor-impl/tests/output/param-overrides-builder.out +++ b/progenitor-impl/tests/output/param-overrides-builder.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/param-overrides-positional.out b/progenitor-impl/tests/output/param-overrides-positional.out index 05610f4b..ce2a0b60 100644 --- a/progenitor-impl/tests/output/param-overrides-positional.out +++ b/progenitor-impl/tests/output/param-overrides-positional.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/propolis-server-builder-tagged.out b/progenitor-impl/tests/output/propolis-server-builder-tagged.out index 7a8cd299..79d70fed 100644 --- a/progenitor-impl/tests/output/propolis-server-builder-tagged.out +++ b/progenitor-impl/tests/output/propolis-server-builder-tagged.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/propolis-server-builder.out b/progenitor-impl/tests/output/propolis-server-builder.out index 3e93141b..9608a3b9 100644 --- a/progenitor-impl/tests/output/propolis-server-builder.out +++ b/progenitor-impl/tests/output/propolis-server-builder.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/propolis-server-positional.out b/progenitor-impl/tests/output/propolis-server-positional.out index 0a69540f..b32577c8 100644 --- a/progenitor-impl/tests/output/propolis-server-positional.out +++ b/progenitor-impl/tests/output/propolis-server-positional.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/test_default_params_builder.out b/progenitor-impl/tests/output/test_default_params_builder.out index 59aa94e2..1b11b1bc 100644 --- a/progenitor-impl/tests/output/test_default_params_builder.out +++ b/progenitor-impl/tests/output/test_default_params_builder.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/test_default_params_positional.out b/progenitor-impl/tests/output/test_default_params_positional.out index 5ca013cb..4cce6c83 100644 --- a/progenitor-impl/tests/output/test_default_params_positional.out +++ b/progenitor-impl/tests/output/test_default_params_positional.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/test_freeform_response.out b/progenitor-impl/tests/output/test_freeform_response.out index ba64d543..4665b3de 100644 --- a/progenitor-impl/tests/output/test_freeform_response.out +++ b/progenitor-impl/tests/output/test_freeform_response.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/test_renamed_parameters.out b/progenitor-impl/tests/output/test_renamed_parameters.out index 9701c655..547cd363 100644 --- a/progenitor-impl/tests/output/test_renamed_parameters.out +++ b/progenitor-impl/tests/output/test_renamed_parameters.out @@ -3,6 +3,8 @@ use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] From d614dff8551e909c80a131e38c57f62a5a9216c6 Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 22 Oct 2023 16:00:58 +0200 Subject: [PATCH 11/38] fix Part body builder impls --- progenitor-impl/src/lib.rs | 1 + progenitor-impl/src/method.rs | 17 ++++++----------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index d4fbf9b5..c22abde6 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -502,6 +502,7 @@ impl Generator { HeaderValue, RequestBuilderExt, ResponseValue, + Part, }; #(#builder_struct)* diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 4da997ef..d6b4bfbf 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -1555,11 +1555,10 @@ impl Generator { BodyContentType::FormData(required), ) = param.kind { - // todo: probably incorrect, to what does this translate? if required { - Ok(quote! { reqwest::multipart::Part }) + Ok(quote! { Result }) } else { - Ok(quote! { Option }) + Ok(quote! { Result, String> }) } } else { unreachable!() @@ -1602,8 +1601,6 @@ impl Generator { BodyContentType::FormData(required), ) = param.kind { - // todo: probably incorrect, to what does this translate? - // Result or not? if required { let err_msg = format!("{} was not initialized", param.name); @@ -1780,19 +1777,17 @@ impl Generator { OperationParameterKind::Body(BodyContentType::FormData(required)) => { if *required { Ok(quote! { - pub fn #param_name(mut self, value: P) -> Self - where P: reqwest::multipart::Part + pub fn #param_name(mut self, value: Part) -> Self { - self.#param_name = value; + self.#param_name = Ok(value); self } }) } else { Ok(quote! { - pub fn #param_name(mut self, value: Option

) -> Self - where P: reqwest::multipart::Part + pub fn #param_name(mut self, value: Option) -> Self { - self.#param_name = value; + self.#param_name = Ok(value); self } }) From 45f099ad0696f62ef9b727e63422824580d97942 Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 22 Oct 2023 16:02:36 +0200 Subject: [PATCH 12/38] EXPECTORATE=overwrite cargo test adds super::Part import --- progenitor-impl/tests/output/buildomat-builder.out | 3 ++- progenitor-impl/tests/output/keeper-builder.out | 3 ++- progenitor-impl/tests/output/nexus-builder.out | 3 ++- progenitor-impl/tests/output/param-collision-builder.out | 3 ++- progenitor-impl/tests/output/param-overrides-builder.out | 3 ++- progenitor-impl/tests/output/propolis-server-builder.out | 3 ++- progenitor-impl/tests/output/test_default_params_builder.out | 3 ++- 7 files changed, 14 insertions(+), 7 deletions(-) diff --git a/progenitor-impl/tests/output/buildomat-builder.out b/progenitor-impl/tests/output/buildomat-builder.out index 1f3e3fb0..a4e3402c 100644 --- a/progenitor-impl/tests/output/buildomat-builder.out +++ b/progenitor-impl/tests/output/buildomat-builder.out @@ -1870,7 +1870,8 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, + encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, + ResponseValue, }; ///Builder for [`Client::control_hold`] /// diff --git a/progenitor-impl/tests/output/keeper-builder.out b/progenitor-impl/tests/output/keeper-builder.out index f404648e..d33750f0 100644 --- a/progenitor-impl/tests/output/keeper-builder.out +++ b/progenitor-impl/tests/output/keeper-builder.out @@ -1060,7 +1060,8 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, + encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, + ResponseValue, }; ///Builder for [`Client::enrol`] /// diff --git a/progenitor-impl/tests/output/nexus-builder.out b/progenitor-impl/tests/output/nexus-builder.out index bf312015..45676e1f 100644 --- a/progenitor-impl/tests/output/nexus-builder.out +++ b/progenitor-impl/tests/output/nexus-builder.out @@ -20822,7 +20822,8 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, + encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, + ResponseValue, }; ///Builder for [`Client::disk_view_by_id`] /// diff --git a/progenitor-impl/tests/output/param-collision-builder.out b/progenitor-impl/tests/output/param-collision-builder.out index b5cc8c83..20fce7d8 100644 --- a/progenitor-impl/tests/output/param-collision-builder.out +++ b/progenitor-impl/tests/output/param-collision-builder.out @@ -105,7 +105,8 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, + encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, + ResponseValue, }; ///Builder for [`Client::key_get`] /// diff --git a/progenitor-impl/tests/output/param-overrides-builder.out b/progenitor-impl/tests/output/param-overrides-builder.out index b623b6b6..f2000928 100644 --- a/progenitor-impl/tests/output/param-overrides-builder.out +++ b/progenitor-impl/tests/output/param-overrides-builder.out @@ -99,7 +99,8 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, + encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, + ResponseValue, }; ///Builder for [`Client::key_get`] /// diff --git a/progenitor-impl/tests/output/propolis-server-builder.out b/progenitor-impl/tests/output/propolis-server-builder.out index 9608a3b9..5eda5d4b 100644 --- a/progenitor-impl/tests/output/propolis-server-builder.out +++ b/progenitor-impl/tests/output/propolis-server-builder.out @@ -2114,7 +2114,8 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, + encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, + ResponseValue, }; ///Builder for [`Client::instance_get`] /// diff --git a/progenitor-impl/tests/output/test_default_params_builder.out b/progenitor-impl/tests/output/test_default_params_builder.out index 1b11b1bc..b3896518 100644 --- a/progenitor-impl/tests/output/test_default_params_builder.out +++ b/progenitor-impl/tests/output/test_default_params_builder.out @@ -304,7 +304,8 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, + encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, + ResponseValue, }; ///Builder for [`Client::default_params`] /// From 348a30f9a736e66f67ad3c684d968b77cb0cae8c Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 22 Oct 2023 20:12:57 +0200 Subject: [PATCH 13/38] cleanup --- progenitor-impl/src/lib.rs | 1 + progenitor-impl/src/method.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index c22abde6..3bc63dda 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -80,6 +80,7 @@ impl Default for GenerationSettings { struct PartMeta { #[serde(default)] file_name: Option, + // probably wait until Mime in reqwest #[serde(default)] mime_str: Option, } diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index d6b4bfbf..ed48920c 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -2373,7 +2373,6 @@ fn get_form_data_params( )), ) if enumeration.is_empty() => { let required = obj_type.required.contains(key); - dbg!((key, description.clone().unwrap(), required)); Some((key, required, description)) } _ => None, From 4c2d2b97a0daaf3a358033e7d9e14a2808776b5a Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 22 Oct 2023 20:20:07 +0200 Subject: [PATCH 14/38] set params from form WIP approach has two flaws: * form params optional cannot be recovered from typify or ergonomically from schema * fails for bodies with nested compound types, e.g. Map, Vec --- progenitor-impl/src/method.rs | 182 ++++++++++++++++++++++------------ 1 file changed, 117 insertions(+), 65 deletions(-) diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index ed48920c..a8349100 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -964,8 +964,8 @@ impl Generator { let #url_ident = #url_path; }; - // Generate code to generate a form - let form_parts = get_form_builder(&method.params, &form_ident); + // Generate code to generate a form. + let form_build = self.get_form_builder(&method.params, &form_ident)?; // Generate code to handle the body param. let body_func = method.params.iter().filter_map(|param| { @@ -1187,7 +1187,7 @@ impl Generator { #headers_build - #(#form_parts)* + #(#form_build)* let #request_ident = #client.client . #method_func (#url_ident) @@ -2336,6 +2336,120 @@ impl Generator { Ok(body_params) } + + fn get_form_builder( + &self, + params: &[OperationParameter], + form_ident: &proc_macro2::Ident, + ) -> Result> { + let mut form_parts = vec![]; + let mut form_parts_optional = vec![]; + let mut build_form_type_id = None; + let mut form_data_names = std::collections::HashSet::with_capacity(1); + + params + .iter() + .filter_map(|param| match (¶m.kind, ¶m.typ) { + ( + OperationParameterKind::Body(BodyContentType::FormData( + required, + )), + OperationParameterType::FormPart, + ) => Some((*required, ¶m.api_name)), + ( + OperationParameterKind::Body(BodyContentType::FormData( + true, + )), + OperationParameterType::Type(type_id), + ) => { + build_form_type_id = Some(type_id); + None + } + _ => None, + }) + .for_each(|(required, api_name)| { + let param_ident = format_ident!("{}", api_name); + form_data_names.insert(param_ident.clone()); + if required { + form_parts.push(quote! { .part(#api_name, #param_ident) }); + } else { + form_parts_optional.push(quote! { + if let Some(#param_ident) = #param_ident { + #form_ident = #form_ident.part(#api_name, #param_ident); + }; + }); + }; + }); + + // set params from form + if let Some(type_id) = build_form_type_id { + let typ = self.get_type_space().get_type(type_id)?; + let td = typ.details(); + let typify::TypeDetails::Struct(tstru) = td else { + unreachable!() + }; + tstru + .properties() + .filter_map(|(prop_name, prop_id)| { + self.get_type_space() + .get_type(&prop_id) + .ok() + .map(|prop_typ| (prop_name, prop_typ)) + }) + .map(|(prop_name, prop_typ)| { + // todo: this is not sufficient and correct to determine optional. + // Determining optional would need access to StructPropertyState::Optional + // or has_default() for that matter. This is used for + // serde skip_serializing_if annotations as well. + let required = !matches!( + prop_typ.details(), + typify::TypeDetails::Option(_) + | typify::TypeDetails::Unit + ); + (required, prop_name) + }) + .for_each(|(required, prop_name)| { + // todo: .to_string() serialization is what most servers will expect + // and work with, but fails for nested types which some servers + // suggest in their schema. + let body_ident = format_ident!("body"); + let param_ident = format_ident!("{}", prop_name); + if !form_data_names.contains(¶m_ident) { + if required { + form_parts.push(quote! { .text(#prop_name, #body_ident.#param_ident.to_string()) }); + } else { + form_parts_optional.push(quote! { + if let Some(v) = &#body_ident.#param_ident { + #form_ident = #form_ident.text(#prop_name, v.to_string()); + }; + }); + }; + } else { + dbg!(("DUP", prop_name, required)); + }; + }); + } + + // only build if any param or at least a body without additional parts + if form_parts.len() > 0 + || form_parts_optional.len() > 0 + || build_form_type_id.is_some() + { + let mutt = if form_parts_optional.len() > 0 { + quote! {mut} + } else { + quote! {} + }; + form_parts.insert( + 0, + quote! {let #mutt #form_ident = reqwest::multipart::Form::new()}, + ); + form_parts.push(quote! {;}); + form_parts.extend(form_parts_optional); + }; + + Ok(form_parts) + } } fn get_form_data_params( @@ -2391,68 +2505,6 @@ fn get_form_data_params( }) } -fn get_form_builder( - params: &[OperationParameter], - form_ident: &proc_macro2::Ident, -) -> Vec { - let mut form_parts = vec![]; - let mut form_parts_optional = vec![]; - let mut build_form = false; - - params - .iter() - .filter_map(|param| match (¶m.kind, ¶m.typ) { - ( - OperationParameterKind::Body(BodyContentType::FormData( - required, - )), - OperationParameterType::FormPart, - ) => { - let param_ident = format_ident!("{}", param.api_name.as_str()); - let name = param.api_name.clone(); - Some((*required, name, param_ident)) - } - ( - OperationParameterKind::Body(BodyContentType::FormData(true)), - OperationParameterType::Type(_), - ) => { - build_form = true; - None - } - _ => None, - }) - .for_each(|(required, name, param_ident)| { - if required { - form_parts.push(quote! { .part(#name, #param_ident) }); - } else { - form_parts_optional.push(quote! { - if let Some(#param_ident) = #param_ident { - #form_ident = #form_ident.part(#name, #param_ident); - }; - }); - }; - }); - - // only build if any param or at least a body without additional - // parts - if form_parts.len() > 0 || form_parts_optional.len() > 0 || build_form { - let mutt = if form_parts_optional.len() > 0 { - quote! {mut} - } else { - quote! {} - }; - form_parts.insert( - 0, - // todo: create form from body - quote! {let #mutt #form_ident = reqwest::multipart::Form::new()}, - ); - form_parts.push(quote! {;}); - form_parts.extend(form_parts_optional); - }; - - form_parts -} - fn make_doc_comment(method: &OperationMethod) -> String { let mut buf = String::new(); From 2a806f5135d25e1d6feec0dfbe8526d4452ebdeb Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 22 Oct 2023 22:19:00 +0200 Subject: [PATCH 15/38] serialize form data body as serde strings the main benefit is correct treatment of optionals and a baseline support for nested compound parameters --- progenitor-impl/src/method.rs | 73 +++++++++++++++++------------------ 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index a8349100..fdd15339 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -2368,8 +2368,8 @@ impl Generator { _ => None, }) .for_each(|(required, api_name)| { + form_data_names.insert(api_name.clone()); let param_ident = format_ident!("{}", api_name); - form_data_names.insert(param_ident.clone()); if required { form_parts.push(quote! { .part(#api_name, #param_ident) }); } else { @@ -2388,46 +2388,35 @@ impl Generator { let typify::TypeDetails::Struct(tstru) = td else { unreachable!() }; - tstru + let body_parts = tstru .properties() .filter_map(|(prop_name, prop_id)| { - self.get_type_space() - .get_type(&prop_id) - .ok() - .map(|prop_typ| (prop_name, prop_typ)) - }) - .map(|(prop_name, prop_typ)| { - // todo: this is not sufficient and correct to determine optional. - // Determining optional would need access to StructPropertyState::Optional - // or has_default() for that matter. This is used for - // serde skip_serializing_if annotations as well. - let required = !matches!( - prop_typ.details(), - typify::TypeDetails::Option(_) - | typify::TypeDetails::Unit - ); - (required, prop_name) + if form_data_names.contains(prop_name) { + None + } + else + { + self.get_type_space() + .get_type(&prop_id) + .ok() + .map(|_| prop_name) + } }) - .for_each(|(required, prop_name)| { - // todo: .to_string() serialization is what most servers will expect - // and work with, but fails for nested types which some servers - // suggest in their schema. - let body_ident = format_ident!("body"); - let param_ident = format_ident!("{}", prop_name); - if !form_data_names.contains(¶m_ident) { - if required { - form_parts.push(quote! { .text(#prop_name, #body_ident.#param_ident.to_string()) }); - } else { - form_parts_optional.push(quote! { - if let Some(v) = &#body_ident.#param_ident { - #form_ident = #form_ident.text(#prop_name, v.to_string()); - }; - }); + .map(|prop_name| { + // Stringish serialization is what most servers expect, + // for nested types we also assume they want those json + // formatted. A customizable solution would be possible + // with a custom Serializer. + quote! { + if let Some(v) = + body.get(#prop_name).map(serde_json::to_string) + { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + #form_ident = #form_ident.text(#prop_name, v); }; - } else { - dbg!(("DUP", prop_name, required)); - }; + } }); + form_parts_optional.extend(body_parts); } // only build if any param or at least a body without additional parts @@ -2442,7 +2431,17 @@ impl Generator { }; form_parts.insert( 0, - quote! {let #mutt #form_ident = reqwest::multipart::Form::new()}, + quote! { + // prepare body + let body = serde_json::to_value(body) + // todo: impl From serde::Error? + .map_err(|e| Error::InvalidRequest(e.to_string()))? + .as_object() + .cloned() + .ok_or_else(|| Error::InvalidRequest("Serializing body failed.".to_string()))?; + // prepare form + let #mutt #form_ident = reqwest::multipart::Form::new() + }, ); form_parts.push(quote! {;}); form_parts.extend(form_parts_optional); From 692b02a4c4577b75557b30943775d2cfbb95238b Mon Sep 17 00:00:00 2001 From: ahirner Date: Thu, 26 Oct 2023 07:37:26 +0200 Subject: [PATCH 16/38] add openapi-api sample schema curl -L -o sample_openapi/openai-openapi.yaml https://raw.githubusercontent.com/openai/openai-openapi/84e9aa615dcb81c66d2d63b8d5dad025259223b2/openapi.yaml --- sample_openapi/openai-openapi.yaml | 4617 ++++++++++++++++++++++++++++ 1 file changed, 4617 insertions(+) create mode 100644 sample_openapi/openai-openapi.yaml diff --git a/sample_openapi/openai-openapi.yaml b/sample_openapi/openai-openapi.yaml new file mode 100644 index 00000000..a1266253 --- /dev/null +++ b/sample_openapi/openai-openapi.yaml @@ -0,0 +1,4617 @@ +openapi: 3.0.0 +info: + title: OpenAI API + description: The OpenAI REST API. Please see https://platform.openai.com/docs/api-reference for more details. + version: "2.0.0" + termsOfService: https://openai.com/policies/terms-of-use + contact: + name: OpenAI Support + url: https://help.openai.com/ + license: + name: MIT + url: https://github.com/openai/openai-openapi/blob/master/LICENSE +servers: + - url: https://api.openai.com/v1 +tags: + - name: Audio + description: Learn how to turn audio into text. + - name: Chat + description: Given a list of messages comprising a conversation, the model will return a response. + - name: Completions + description: Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. + - name: Embeddings + description: Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. + - name: Fine-tuning + description: Manage fine-tuning jobs to tailor a model to your specific training data. + - name: Files + description: Files are used to upload documents that can be used with features like fine-tuning. + - name: Images + description: Given a prompt and/or an input image, the model will generate a new image. + - name: Models + description: List and describe the various models available in the API. + - name: Moderations + description: Given a input text, outputs if the model classifies it as violating OpenAI's content policy. + - name: Fine-tunes + description: Manage legacy fine-tuning jobs to tailor a model to your specific training data. + - name: Edits + description: Given a prompt and an instruction, the model will return an edited version of the prompt. + +paths: + # Note: When adding an endpoint, make sure you also add it in the `groups` section, in the end of this file, + # under the appropriate group + /chat/completions: + post: + operationId: createChatCompletion + tags: + - Chat + summary: Creates a model response for the given chat conversation. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateChatCompletionRequest" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/CreateChatCompletionResponse" + + x-oaiMeta: + name: Create chat completion + group: chat + returns: | + Returns a [chat completion](/docs/api-reference/chat/object) object, or a streamed sequence of [chat completion chunk](/docs/api-reference/chat/streaming) objects if the request is streamed. + path: create + examples: + - title: No Streaming + request: + curl: | + curl https://api.openai.com/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d '{ + "model": "VAR_model_id", + "messages": [ + { + "role": "system", + "content": "You are a helpful assistant." + }, + { + "role": "user", + "content": "Hello!" + } + ] + }' + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + + completion = openai.ChatCompletion.create( + model="VAR_model_id", + messages=[ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hello!"} + ] + ) + + print(completion.choices[0].message) + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const completion = await openai.chat.completions.create({ + messages: [{ role: "system", content: "You are a helpful assistant." }], + model: "VAR_model_id", + }); + + console.log(completion.choices[0]); + } + + main(); + response: &chat_completion_example | + { + "id": "chatcmpl-123", + "object": "chat.completion", + "created": 1677652288, + "model": "gpt-3.5-turbo-0613", + "choices": [{ + "index": 0, + "message": { + "role": "assistant", + "content": "\n\nHello there, how may I assist you today?", + }, + "finish_reason": "stop" + }], + "usage": { + "prompt_tokens": 9, + "completion_tokens": 12, + "total_tokens": 21 + } + } + - title: Streaming + request: + curl: | + curl https://api.openai.com/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d '{ + "model": "VAR_model_id", + "messages": [ + { + "role": "system", + "content": "You are a helpful assistant." + }, + { + "role": "user", + "content": "Hello!" + } + ], + "stream": true + }' + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + + completion = openai.ChatCompletion.create( + model="VAR_model_id", + messages=[ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hello!"} + ], + stream=True + ) + + for chunk in completion: + print(chunk.choices[0].delta) + + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const completion = await openai.chat.completions.create({ + model: "VAR_model_id", + messages: [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hello!"} + ], + stream: true, + }); + + for await (const chunk of completion) { + console.log(chunk.choices[0].delta.content); + } + } + + main(); + response: &chat_completion_chunk_example | + {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]} + + {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]} + + {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"!"},"finish_reason":null}]} + + .... + + {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":" today"},"finish_reason":null}]} + + {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"?"},"finish_reason":null}]} + + {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} + - title: Function calling + request: + curl: | + curl https://api.openai.com/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d '{ + "model": "gpt-3.5-turbo", + "messages": [ + { + "role": "user", + "content": "What is the weather like in Boston?" + } + ], + "functions": [ + { + "name": "get_current_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "type": "string", + "enum": ["celsius", "fahrenheit"] + } + }, + "required": ["location"] + } + } + ], + "function_call": "auto" + }' + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + + functions = [ + { + "name": "get_current_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA", + }, + "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, + }, + "required": ["location"], + }, + } + ] + messages = [{"role": "user", "content": "What's the weather like in Boston today?"}] + completion = openai.ChatCompletion.create( + model="VAR_model_id", + messages=messages, + functions=functions, + function_call="auto", # auto is default, but we'll be explicit + ) + + print(completion) + + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const messages = [{"role": "user", "content": "What's the weather like in Boston today?"}]; + const functions = [ + { + "name": "get_current_weather", + "description": "Get the current weather in a given location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA", + }, + "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, + }, + "required": ["location"], + }, + } + ]; + + const response = await openai.chat.completions.create({ + model: "gpt-3.5-turbo", + messages: messages, + functions: functions, + function_call: "auto", // auto is default, but we'll be explicit + }); + + console.log(response); + } + + main(); + response: &chat_completion_function_example | + { + "choices": [ + { + "finish_reason": "function_call", + "index": 0, + "message": { + "content": null, + "function_call": { + "arguments": "{\n \"location\": \"Boston, MA\"\n}", + "name": "get_current_weather" + }, + "role": "assistant" + } + } + ], + "created": 1694028367, + "model": "gpt-3.5-turbo-0613", + "object": "chat.completion", + "usage": { + "completion_tokens": 18, + "prompt_tokens": 82, + "total_tokens": 100 + } + } + /completions: + post: + operationId: createCompletion + tags: + - Completions + summary: Creates a completion for the provided prompt and parameters. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateCompletionRequest" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/CreateCompletionResponse" + x-oaiMeta: + name: Create completion + returns: | + Returns a [completion](/docs/api-reference/completions/object) object, or a sequence of completion objects if the request is streamed. + legacy: true + examples: + - title: No streaming + request: + curl: | + curl https://api.openai.com/v1/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d '{ + "model": "VAR_model_id", + "prompt": "Say this is a test", + "max_tokens": 7, + "temperature": 0 + }' + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.Completion.create( + model="VAR_model_id", + prompt="Say this is a test", + max_tokens=7, + temperature=0 + ) + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const completion = await openai.completions.create({ + model: "VAR_model_id", + prompt: "Say this is a test.", + max_tokens: 7, + temperature: 0, + }); + + console.log(completion); + } + main(); + response: | + { + "id": "cmpl-uqkvlQyYK7bGYrRHQ0eXlWi7", + "object": "text_completion", + "created": 1589478378, + "model": "VAR_model_id", + "choices": [ + { + "text": "\n\nThis is indeed a test", + "index": 0, + "logprobs": null, + "finish_reason": "length" + } + ], + "usage": { + "prompt_tokens": 5, + "completion_tokens": 7, + "total_tokens": 12 + } + } + - title: Streaming + request: + curl: | + curl https://api.openai.com/v1/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d '{ + "model": "VAR_model_id", + "prompt": "Say this is a test", + "max_tokens": 7, + "temperature": 0, + "stream": true + }' + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + for chunk in openai.Completion.create( + model="VAR_model_id", + prompt="Say this is a test", + max_tokens=7, + temperature=0, + stream=True + ): + print(chunk['choices'][0]['text']) + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const stream = await openai.completions.create({ + model: "VAR_model_id", + prompt: "Say this is a test.", + stream: true, + }); + + for await (const chunk of stream) { + console.log(chunk.choices[0].text) + } + } + main(); + response: | + { + "id": "cmpl-7iA7iJjj8V2zOkCGvWF2hAkDWBQZe", + "object": "text_completion", + "created": 1690759702, + "choices": [ + { + "text": "This", + "index": 0, + "logprobs": null, + "finish_reason": null + } + ], + "model": "gpt-3.5-turbo-instruct" + } + /edits: + post: + operationId: createEdit + deprecated: true + tags: + - Edits + summary: Creates a new edit for the provided input, instruction, and parameters. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateEditRequest" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/CreateEditResponse" + x-oaiMeta: + name: Create edit + returns: | + Returns an [edit](/docs/api-reference/edits/object) object. + group: edits + examples: + request: + curl: | + curl https://api.openai.com/v1/edits \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d '{ + "model": "VAR_model_id", + "input": "What day of the wek is it?", + "instruction": "Fix the spelling mistakes" + }' + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.Edit.create( + model="VAR_model_id", + input="What day of the wek is it?", + instruction="Fix the spelling mistakes" + ) + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const edit = await openai.edits.create({ + model: "VAR_model_id", + input: "What day of the wek is it?", + instruction: "Fix the spelling mistakes.", + }); + + console.log(edit); + } + + main(); + response: &edit_example | + { + "object": "edit", + "created": 1589478378, + "choices": [ + { + "text": "What day of the week is it?", + "index": 0, + } + ], + "usage": { + "prompt_tokens": 25, + "completion_tokens": 32, + "total_tokens": 57 + } + } + + /images/generations: + post: + operationId: createImage + tags: + - Images + summary: Creates an image given a prompt. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateImageRequest" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ImagesResponse" + x-oaiMeta: + name: Create image + returns: Returns a list of [image](/docs/api-reference/images/object) objects. + examples: + request: + curl: | + curl https://api.openai.com/v1/images/generations \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d '{ + "prompt": "A cute baby sea otter", + "n": 2, + "size": "1024x1024" + }' + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.Image.create( + prompt="A cute baby sea otter", + n=2, + size="1024x1024" + ) + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const image = await openai.images.generate({ prompt: "A cute baby sea otter" }); + + console.log(image.data); + } + main(); + response: | + { + "created": 1589478378, + "data": [ + { + "url": "https://..." + }, + { + "url": "https://..." + } + ] + } + + /images/edits: + post: + operationId: createImageEdit + tags: + - Images + summary: Creates an edited or extended image given an original image and a prompt. + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/CreateImageEditRequest" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ImagesResponse" + x-oaiMeta: + name: Create image edit + returns: Returns a list of [image](/docs/api-reference/images/object) objects. + examples: + request: + curl: | + curl https://api.openai.com/v1/images/edits \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -F image="@otter.png" \ + -F mask="@mask.png" \ + -F prompt="A cute baby sea otter wearing a beret" \ + -F n=2 \ + -F size="1024x1024" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.Image.create_edit( + image=open("otter.png", "rb"), + mask=open("mask.png", "rb"), + prompt="A cute baby sea otter wearing a beret", + n=2, + size="1024x1024" + ) + node.js: |- + import fs from "fs"; + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const image = await openai.images.edit({ + image: fs.createReadStream("otter.png"), + mask: fs.createReadStream("mask.png"), + prompt: "A cute baby sea otter wearing a beret", + }); + + console.log(image.data); + } + main(); + response: | + { + "created": 1589478378, + "data": [ + { + "url": "https://..." + }, + { + "url": "https://..." + } + ] + } + + /images/variations: + post: + operationId: createImageVariation + tags: + - Images + summary: Creates a variation of a given image. + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/CreateImageVariationRequest" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ImagesResponse" + x-oaiMeta: + name: Create image variation + returns: Returns a list of [image](/docs/api-reference/images/object) objects. + examples: + request: + curl: | + curl https://api.openai.com/v1/images/variations \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -F image="@otter.png" \ + -F n=2 \ + -F size="1024x1024" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.Image.create_variation( + image=open("otter.png", "rb"), + n=2, + size="1024x1024" + ) + node.js: |- + import fs from "fs"; + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const image = await openai.images.createVariation({ + image: fs.createReadStream("otter.png"), + }); + + console.log(image.data); + } + main(); + response: | + { + "created": 1589478378, + "data": [ + { + "url": "https://..." + }, + { + "url": "https://..." + } + ] + } + + /embeddings: + post: + operationId: createEmbedding + tags: + - Embeddings + summary: Creates an embedding vector representing the input text. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateEmbeddingRequest" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/CreateEmbeddingResponse" + x-oaiMeta: + name: Create embeddings + returns: A list of [embedding](/docs/api-reference/embeddings/object) objects. + examples: + request: + curl: | + curl https://api.openai.com/v1/embeddings \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "input": "The food was delicious and the waiter...", + "model": "text-embedding-ada-002", + "encoding_format": "float" + }' + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.Embedding.create( + model="text-embedding-ada-002", + input="The food was delicious and the waiter...", + encoding_format="float" + ) + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const embedding = await openai.embeddings.create({ + model: "text-embedding-ada-002", + input: "The quick brown fox jumped over the lazy dog", + encoding_format: "float", + }); + + console.log(embedding); + } + + main(); + response: | + { + "object": "list", + "data": [ + { + "object": "embedding", + "embedding": [ + 0.0023064255, + -0.009327292, + .... (1536 floats total for ada-002) + -0.0028842222, + ], + "index": 0 + } + ], + "model": "text-embedding-ada-002", + "usage": { + "prompt_tokens": 8, + "total_tokens": 8 + } + } + + /audio/transcriptions: + post: + operationId: createTranscription + tags: + - Audio + summary: Transcribes audio into the input language. + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/CreateTranscriptionRequest" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/CreateTranscriptionResponse" + x-oaiMeta: + name: Create transcription + returns: The transcriped text. + examples: + request: + curl: | + curl https://api.openai.com/v1/audio/transcriptions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: multipart/form-data" \ + -F file="@/path/to/file/audio.mp3" \ + -F model="whisper-1" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + audio_file = open("audio.mp3", "rb") + transcript = openai.Audio.transcribe("whisper-1", audio_file) + node: |- + import fs from "fs"; + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const transcription = await openai.audio.transcriptions.create({ + file: fs.createReadStream("audio.mp3"), + model: "whisper-1", + }); + + console.log(transcription.text); + } + main(); + response: | + { + "text": "Imagine the wildest idea that you've ever had, and you're curious about how it might scale to something that's a 100, a 1,000 times bigger. This is a place where you can get to do that." + } + + /audio/translations: + post: + operationId: createTranslation + tags: + - Audio + summary: Translates audio into English. + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/CreateTranslationRequest" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/CreateTranslationResponse" + x-oaiMeta: + name: Create translation + returns: The translated text. + examples: + request: + curl: | + curl https://api.openai.com/v1/audio/translations \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: multipart/form-data" \ + -F file="@/path/to/file/german.m4a" \ + -F model="whisper-1" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + audio_file = open("german.m4a", "rb") + transcript = openai.Audio.translate("whisper-1", audio_file) + node: | + const { Configuration, OpenAIApi } = require("openai"); + const configuration = new Configuration({ + apiKey: process.env.OPENAI_API_KEY, + }); + const openai = new OpenAIApi(configuration); + const resp = await openai.createTranslation( + fs.createReadStream("audio.mp3"), + "whisper-1" + ); + response: | + { + "text": "Hello, my name is Wolfgang and I come from Germany. Where are you heading today?" + } + + /files: + get: + operationId: listFiles + tags: + - Files + summary: Returns a list of files that belong to the user's organization. + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ListFilesResponse" + x-oaiMeta: + name: List files + returns: A list of [file](/docs/api-reference/files/object) objects. + examples: + request: + curl: | + curl https://api.openai.com/v1/files \ + -H "Authorization: Bearer $OPENAI_API_KEY" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.File.list() + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const list = await openai.files.list(); + + for await (const file of list) { + console.log(file); + } + } + + main(); + response: | + { + "data": [ + { + "id": "file-abc123", + "object": "file", + "bytes": 175, + "created_at": 1613677385, + "filename": "train.jsonl", + "purpose": "search" + }, + { + "id": "file-abc123", + "object": "file", + "bytes": 140, + "created_at": 1613779121, + "filename": "puppy.jsonl", + "purpose": "search" + } + ], + "object": "list" + } + post: + operationId: createFile + tags: + - Files + summary: | + Upload a file that can be used across various endpoints/features. Currently, the size of all the files uploaded by one organization can be up to 1 GB. Please [contact us](https://help.openai.com/) if you need to increase the storage limit. + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: "#/components/schemas/CreateFileRequest" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/OpenAIFile" + x-oaiMeta: + name: Upload file + returns: The uploaded [file](/docs/api-reference/files/object) object. + examples: + request: + curl: | + curl https://api.openai.com/v1/files \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -F purpose="fine-tune" \ + -F file="@mydata.jsonl" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.File.create( + file=open("mydata.jsonl", "rb"), + purpose='fine-tune' + ) + node.js: |- + import fs from "fs"; + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const file = await openai.files.create({ + file: fs.createReadStream("mydata.jsonl"), + purpose: "fine-tune", + }); + + console.log(file); + } + + main(); + response: | + { + "id": "file-abc123", + "object": "file", + "bytes": 140, + "created_at": 1613779121, + "filename": "mydata.jsonl", + "purpose": "fine-tune", + "status": "uploaded" | "processed" | "pending" | "error" + } + + /files/{file_id}: + delete: + operationId: deleteFile + tags: + - Files + summary: Delete a file. + parameters: + - in: path + name: file_id + required: true + schema: + type: string + description: The ID of the file to use for this request. + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/DeleteFileResponse" + x-oaiMeta: + name: Delete file + returns: Deletion status. + examples: + request: + curl: | + curl https://api.openai.com/v1/files/file-abc123 \ + -X DELETE \ + -H "Authorization: Bearer $OPENAI_API_KEY" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.File.delete("file-abc123") + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const file = await openai.files.del("file-abc123"); + + console.log(file); + } + + main(); + response: | + { + "id": "file-abc123", + "object": "file", + "deleted": true + } + get: + operationId: retrieveFile + tags: + - Files + summary: Returns information about a specific file. + parameters: + - in: path + name: file_id + required: true + schema: + type: string + description: The ID of the file to use for this request. + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/OpenAIFile" + x-oaiMeta: + name: Retrieve file + returns: The [file](/docs/api-reference/files/object) object matching the specified ID. + examples: + request: + curl: | + curl https://api.openai.com/v1/files/file-abc123 \ + -H "Authorization: Bearer $OPENAI_API_KEY" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.File.retrieve("file-abc123") + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const file = await openai.files.retrieve("file-abc123"); + + console.log(file); + } + + main(); + response: | + { + "id": "file-abc123", + "object": "file", + "bytes": 140, + "created_at": 1613779657, + "filename": "mydata.jsonl", + "purpose": "fine-tune" + } + + /files/{file_id}/content: + get: + operationId: downloadFile + tags: + - Files + summary: Returns the contents of the specified file. + parameters: + - in: path + name: file_id + required: true + schema: + type: string + description: The ID of the file to use for this request. + responses: + "200": + description: OK + content: + application/json: + schema: + type: string + x-oaiMeta: + name: Retrieve file content + returns: The file content. + examples: + request: + curl: | + curl https://api.openai.com/v1/files/file-abc123/content \ + -H "Authorization: Bearer $OPENAI_API_KEY" > file.jsonl + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + content = openai.File.download("file-abc123") + node.js: | + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const file = await openai.files.retrieveContent("file-abc123"); + + console.log(file); + } + + main(); + + /fine_tuning/jobs: + post: + operationId: createFineTuningJob + tags: + - Fine-tuning + summary: | + Creates a job that fine-tunes a specified model from a given dataset. + + Response includes details of the enqueued job including job status and the name of the fine-tuned models once complete. + + [Learn more about fine-tuning](/docs/guides/fine-tuning) + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateFineTuningJobRequest" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/FineTuningJob" + x-oaiMeta: + name: Create fine-tuning job + returns: A [fine-tuning.job](/docs/api-reference/fine-tuning/object) object. + examples: + - title: No hyperparameters + request: + curl: | + curl https://api.openai.com/v1/fine_tuning/jobs \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d '{ + "training_file": "file-abc123", + "model": "gpt-3.5-turbo" + }' + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.FineTuningJob.create(training_file="file-abc123", model="gpt-3.5-turbo") + node.js: | + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const fineTune = await openai.fineTuning.jobs.create({ + training_file: "file-abc123" + }); + + console.log(fineTune); + } + + main(); + response: | + { + "object": "fine_tuning.job", + "id": "ftjob-abc123", + "model": "gpt-3.5-turbo-0613", + "created_at": 1614807352, + "fine_tuned_model": null, + "organization_id": "org-123", + "result_files": [], + "status": "queued", + "validation_file": null, + "training_file": "file-abc123", + } + - title: Hyperparameters + request: + curl: | + curl https://api.openai.com/v1/fine_tuning/jobs \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d '{ + "training_file": "file-abc123", + "model": "gpt-3.5-turbo", + "hyperparameters": { + "n_epochs": 2 + } + }' + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.FineTuningJob.create(training_file="file-abc123", model="gpt-3.5-turbo", hyperparameters={"n_epochs":2}) + node.js: | + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const fineTune = await openai.fineTuning.jobs.create({ + training_file: "file-abc123", + model: "gpt-3.5-turbo", + hyperparameters: { n_epochs: 2 } + }); + + console.log(fineTune); + } + + main(); + response: | + { + "object": "fine_tuning.job", + "id": "ftjob-abc123", + "model": "gpt-3.5-turbo-0613", + "created_at": 1614807352, + "fine_tuned_model": null, + "organization_id": "org-123", + "result_files": [], + "status": "queued", + "validation_file": null, + "training_file": "file-abc123", + "hyperparameters":{"n_epochs":2}, + } + - title: Validation file + request: + curl: | + curl https://api.openai.com/v1/fine_tuning/jobs \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d '{ + "training_file": "file-abc123", + "validation_file": "file-abc123", + "model": "gpt-3.5-turbo" + }' + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.FineTuningJob.create(training_file="file-abc123", validation_file="file-abc123", model="gpt-3.5-turbo") + node.js: | + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const fineTune = await openai.fineTuning.jobs.create({ + training_file: "file-abc123", + validation_file: "file-abc123" + }); + + console.log(fineTune); + } + + main(); + response: | + { + "object": "fine_tuning.job", + "id": "ftjob-abc123", + "model": "gpt-3.5-turbo-0613", + "created_at": 1614807352, + "fine_tuned_model": null, + "organization_id": "org-123", + "result_files": [], + "status": "queued", + "validation_file": "file-abc123", + "training_file": "file-abc123", + } + get: + operationId: listPaginatedFineTuningJobs + tags: + - Fine-tuning + summary: | + List your organization's fine-tuning jobs + parameters: + - name: after + in: query + description: Identifier for the last job from the previous pagination request. + required: false + schema: + type: string + - name: limit + in: query + description: Number of fine-tuning jobs to retrieve. + required: false + schema: + type: integer + default: 20 + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ListPaginatedFineTuningJobsResponse" + x-oaiMeta: + name: List fine-tuning jobs + returns: A list of paginated [fine-tuning job](/docs/api-reference/fine-tuning/object) objects. + examples: + request: + curl: | + curl https://api.openai.com/v1/fine_tuning/jobs?limit=2 \ + -H "Authorization: Bearer $OPENAI_API_KEY" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.FineTuningJob.list() + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const list = await openai.fineTuning.jobs.list(); + + for await (const fineTune of list) { + console.log(fineTune); + } + } + + main(); + response: | + { + "object": "list", + "data": [ + { + "object": "fine_tuning.job.event", + "id": "ft-event-TjX0lMfOniCZX64t9PUQT5hn", + "created_at": 1689813489, + "level": "warn", + "message": "Fine tuning process stopping due to job cancellation", + "data": null, + "type": "message" + }, + { ... }, + { ... } + ], "has_more": true + } + /fine_tuning/jobs/{fine_tuning_job_id}: + get: + operationId: retrieveFineTuningJob + tags: + - Fine-tuning + summary: | + Get info about a fine-tuning job. + + [Learn more about fine-tuning](/docs/guides/fine-tuning) + parameters: + - in: path + name: fine_tuning_job_id + required: true + schema: + type: string + example: ft-AF1WoRqd3aJAHsqc9NY7iL8F + description: | + The ID of the fine-tuning job. + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/FineTuningJob" + x-oaiMeta: + name: Retrieve fine-tuning job + returns: The [fine-tuning](/docs/api-reference/fine-tunes/object) object with the given ID. + examples: + request: + curl: | + curl https://api.openai.com/v1/fine_tuning/jobs/ft-AF1WoRqd3aJAHsqc9NY7iL8F \ + -H "Authorization: Bearer $OPENAI_API_KEY" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.FineTuningJob.retrieve("ftjob-abc123") + node.js: | + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const fineTune = await openai.fineTuning.jobs.retrieve("ftjob-abc123"); + + console.log(fineTune); + } + + main(); + response: &fine_tuning_example | + { + "object": "fine_tuning.job", + "id": "ftjob-abc123", + "model": "davinci-002", + "created_at": 1692661014, + "finished_at": 1692661190, + "fine_tuned_model": "ft:davinci-002:my-org:custom_suffix:7q8mpxmy", + "organization_id": "org-123", + "result_files": [ + "file-abc123" + ], + "status": "succeeded", + "validation_file": null, + "training_file": "file-abc123", + "hyperparameters": { + "n_epochs": 4, + }, + "trained_tokens": 5768 + } + /fine_tuning/jobs/{fine_tuning_job_id}/events: + get: + operationId: listFineTuningEvents + tags: + - Fine-tuning + summary: | + Get status updates for a fine-tuning job. + parameters: + - in: path + name: fine_tuning_job_id + required: true + schema: + type: string + example: ft-AF1WoRqd3aJAHsqc9NY7iL8F + description: | + The ID of the fine-tuning job to get events for. + - name: after + in: query + description: Identifier for the last event from the previous pagination request. + required: false + schema: + type: string + - name: limit + in: query + description: Number of events to retrieve. + required: false + schema: + type: integer + default: 20 + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ListFineTuningJobEventsResponse" + x-oaiMeta: + name: List fine-tuning events + returns: A list of fine-tuning event objects. + examples: + request: + curl: | + curl https://api.openai.com/v1/fine_tuning/jobs/ftjob-abc123/events \ + -H "Authorization: Bearer $OPENAI_API_KEY" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.FineTuningJob.list_events(id="ftjob-abc123", limit=2) + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const list = await openai.fineTuning.list_events(id="ftjob-abc123", limit=2); + + for await (const fineTune of list) { + console.log(fineTune); + } + } + + main(); + response: | + { + "object": "list", + "data": [ + { + "object": "fine_tuning.job.event", + "id": "ft-event-ddTJfwuMVpfLXseO0Am0Gqjm", + "created_at": 1692407401, + "level": "info", + "message": "Fine tuning job successfully completed", + "data": null, + "type": "message" + }, + { + "object": "fine_tuning.job.event", + "id": "ft-event-tyiGuB72evQncpH87xe505Sv", + "created_at": 1692407400, + "level": "info", + "message": "New fine-tuned model created: ft:gpt-3.5-turbo:openai::7p4lURel", + "data": null, + "type": "message" + } + ], + "has_more": true + } + + /fine_tuning/jobs/{fine_tuning_job_id}/cancel: + post: + operationId: cancelFineTuningJob + tags: + - Fine-tuning + summary: | + Immediately cancel a fine-tune job. + parameters: + - in: path + name: fine_tuning_job_id + required: true + schema: + type: string + example: ft-AF1WoRqd3aJAHsqc9NY7iL8F + description: | + The ID of the fine-tuning job to cancel. + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/FineTuningJob" + x-oaiMeta: + name: Cancel fine-tuning + returns: The cancelled [fine-tuning](/docs/api-reference/fine-tuning/object) object. + examples: + request: + curl: | + curl -X POST https://api.openai.com/v1/fine_tuning/jobs/ftjob-abc123/cancel \ + -H "Authorization: Bearer $OPENAI_API_KEY" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.FineTuningJob.cancel("ftjob-abc123") + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const fineTune = await openai.fineTuning.jobs.cancel("ftjob-abc123"); + + console.log(fineTune); + } + main(); + response: | + { + "object": "fine_tuning.job", + "id": "ftjob-abc123", + "model": "gpt-3.5-turbo-0613", + "created_at": 1689376978, + "fine_tuned_model": null, + "organization_id": "org-123", + "result_files": [], + "hyperparameters": { + "n_epochs": "auto" + }, + "status": "cancelled", + "validation_file": "file-abc123", + "training_file": "file-abc123" + } + + /fine-tunes: + post: + operationId: createFineTune + deprecated: true + tags: + - Fine-tunes + summary: | + Creates a job that fine-tunes a specified model from a given dataset. + + Response includes details of the enqueued job including job status and the name of the fine-tuned models once complete. + + [Learn more about fine-tuning](/docs/guides/legacy-fine-tuning) + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateFineTuneRequest" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/FineTune" + x-oaiMeta: + name: Create fine-tune + returns: A [fine-tune](/docs/api-reference/fine-tunes/object) object. + examples: + request: + curl: | + curl https://api.openai.com/v1/fine-tunes \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d '{ + "training_file": "file-abc123" + }' + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.FineTune.create(training_file="file-abc123") + node.js: | + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const fineTune = await openai.fineTunes.create({ + training_file: "file-abc123" + }); + + console.log(fineTune); + } + + main(); + response: | + { + "id": "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + "object": "fine-tune", + "model": "curie", + "created_at": 1614807352, + "events": [ + { + "object": "fine-tune-event", + "created_at": 1614807352, + "level": "info", + "message": "Job enqueued. Waiting for jobs ahead to complete. Queue number: 0." + } + ], + "fine_tuned_model": null, + "hyperparams": { + "batch_size": 4, + "learning_rate_multiplier": 0.1, + "n_epochs": 4, + "prompt_loss_weight": 0.1, + }, + "organization_id": "org-123", + "result_files": [], + "status": "pending", + "validation_files": [], + "training_files": [ + { + "id": "file-abc123", + "object": "file", + "bytes": 1547276, + "created_at": 1610062281, + "filename": "my-data-train.jsonl", + "purpose": "fine-tune-train" + } + ], + "updated_at": 1614807352, + } + get: + operationId: listFineTunes + deprecated: true + tags: + - Fine-tunes + summary: | + List your organization's fine-tuning jobs + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ListFineTunesResponse" + x-oaiMeta: + name: List fine-tunes + returns: A list of [fine-tune](/docs/api-reference/fine-tunes/object) objects. + examples: + request: + curl: | + curl https://api.openai.com/v1/fine-tunes \ + -H "Authorization: Bearer $OPENAI_API_KEY" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.FineTune.list() + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const list = await openai.fineTunes.list(); + + for await (const fineTune of list) { + console.log(fineTune); + } + } + + main(); + response: | + { + "object": "list", + "data": [ + { + "id": "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + "object": "fine-tune", + "model": "curie", + "created_at": 1614807352, + "fine_tuned_model": null, + "hyperparams": { ... }, + "organization_id": "org-123", + "result_files": [], + "status": "pending", + "validation_files": [], + "training_files": [ { ... } ], + "updated_at": 1614807352, + }, + { ... }, + { ... } + ] + } + + /fine-tunes/{fine_tune_id}: + get: + operationId: retrieveFineTune + deprecated: true + tags: + - Fine-tunes + summary: | + Gets info about the fine-tune job. + + [Learn more about fine-tuning](/docs/guides/legacy-fine-tuning) + parameters: + - in: path + name: fine_tune_id + required: true + schema: + type: string + example: ft-AF1WoRqd3aJAHsqc9NY7iL8F + description: | + The ID of the fine-tune job + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/FineTune" + x-oaiMeta: + name: Retrieve fine-tune + returns: The [fine-tune](/docs/api-reference/fine-tunes/object) object with the given ID. + examples: + request: + curl: | + curl https://api.openai.com/v1/fine-tunes/ft-AF1WoRqd3aJAHsqc9NY7iL8F \ + -H "Authorization: Bearer $OPENAI_API_KEY" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.FineTune.retrieve(id="ft-AF1WoRqd3aJAHsqc9NY7iL8F") + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const fineTune = await openai.fineTunes.retrieve("ft-AF1WoRqd3aJAHsqc9NY7iL8F"); + + console.log(fineTune); + } + + main(); + response: &fine_tune_example | + { + "id": "ft-AF1WoRqd3aJAHsqc9NY7iL8F", + "object": "fine-tune", + "model": "curie", + "created_at": 1614807352, + "events": [ + { + "object": "fine-tune-event", + "created_at": 1614807352, + "level": "info", + "message": "Job enqueued. Waiting for jobs ahead to complete. Queue number: 0." + }, + { + "object": "fine-tune-event", + "created_at": 1614807356, + "level": "info", + "message": "Job started." + }, + { + "object": "fine-tune-event", + "created_at": 1614807861, + "level": "info", + "message": "Uploaded snapshot: curie:ft-acmeco-2021-03-03-21-44-20." + }, + { + "object": "fine-tune-event", + "created_at": 1614807864, + "level": "info", + "message": "Uploaded result files: file-abc123." + }, + { + "object": "fine-tune-event", + "created_at": 1614807864, + "level": "info", + "message": "Job succeeded." + } + ], + "fine_tuned_model": "curie:ft-acmeco-2021-03-03-21-44-20", + "hyperparams": { + "batch_size": 4, + "learning_rate_multiplier": 0.1, + "n_epochs": 4, + "prompt_loss_weight": 0.1, + }, + "organization_id": "org-123", + "result_files": [ + { + "id": "file-abc123", + "object": "file", + "bytes": 81509, + "created_at": 1614807863, + "filename": "compiled_results.csv", + "purpose": "fine-tune-results" + } + ], + "status": "succeeded", + "validation_files": [], + "training_files": [ + { + "id": "file-abc123", + "object": "file", + "bytes": 1547276, + "created_at": 1610062281, + "filename": "my-data-train.jsonl", + "purpose": "fine-tune-train" + } + ], + "updated_at": 1614807865, + } + + /fine-tunes/{fine_tune_id}/cancel: + post: + operationId: cancelFineTune + deprecated: true + tags: + - Fine-tunes + summary: | + Immediately cancel a fine-tune job. + parameters: + - in: path + name: fine_tune_id + required: true + schema: + type: string + example: ft-AF1WoRqd3aJAHsqc9NY7iL8F + description: | + The ID of the fine-tune job to cancel + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/FineTune" + x-oaiMeta: + name: Cancel fine-tune + returns: The cancelled [fine-tune](/docs/api-reference/fine-tunes/object) object. + examples: + request: + curl: | + curl https://api.openai.com/v1/fine-tunes/ft-AF1WoRqd3aJAHsqc9NY7iL8F/cancel \ + -H "Authorization: Bearer $OPENAI_API_KEY" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.FineTune.cancel(id="ft-AF1WoRqd3aJAHsqc9NY7iL8F") + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const fineTune = await openai.fineTunes.cancel("ft-AF1WoRqd3aJAHsqc9NY7iL8F"); + + console.log(fineTune); + } + main(); + response: | + { + "id": "ft-xhrpBbvVUzYGo8oUO1FY4nI7", + "object": "fine-tune", + "model": "curie", + "created_at": 1614807770, + "events": [ { ... } ], + "fine_tuned_model": null, + "hyperparams": { ... }, + "organization_id": "org-123", + "result_files": [], + "status": "cancelled", + "validation_files": [], + "training_files": [ + { + "id": "file-abc123", + "object": "file", + "bytes": 1547276, + "created_at": 1610062281, + "filename": "my-data-train.jsonl", + "purpose": "fine-tune-train" + } + ], + "updated_at": 1614807789, + } + + /fine-tunes/{fine_tune_id}/events: + get: + operationId: listFineTuneEvents + deprecated: true + tags: + - Fine-tunes + summary: | + Get fine-grained status updates for a fine-tune job. + parameters: + - in: path + name: fine_tune_id + required: true + schema: + type: string + example: ft-AF1WoRqd3aJAHsqc9NY7iL8F + description: | + The ID of the fine-tune job to get events for. + - in: query + name: stream + required: false + schema: + type: boolean + default: false + description: | + Whether to stream events for the fine-tune job. If set to true, + events will be sent as data-only + [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) + as they become available. The stream will terminate with a + `data: [DONE]` message when the job is finished (succeeded, cancelled, + or failed). + + If set to false, only events generated so far will be returned. + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ListFineTuneEventsResponse" + x-oaiMeta: + name: List fine-tune events + returns: A list of fine-tune event objects. + examples: + request: + curl: | + curl https://api.openai.com/v1/fine-tunes/ft-AF1WoRqd3aJAHsqc9NY7iL8F/events \ + -H "Authorization: Bearer $OPENAI_API_KEY" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.FineTune.list_events(id="ft-AF1WoRqd3aJAHsqc9NY7iL8F") + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const fineTune = await openai.fineTunes.listEvents("ft-AF1WoRqd3aJAHsqc9NY7iL8F"); + + console.log(fineTune); + } + main(); + response: | + { + "object": "list", + "data": [ + { + "object": "fine-tune-event", + "created_at": 1614807352, + "level": "info", + "message": "Job enqueued. Waiting for jobs ahead to complete. Queue number: 0." + }, + { + "object": "fine-tune-event", + "created_at": 1614807356, + "level": "info", + "message": "Job started." + }, + { + "object": "fine-tune-event", + "created_at": 1614807861, + "level": "info", + "message": "Uploaded snapshot: curie:ft-acmeco-2021-03-03-21-44-20." + }, + { + "object": "fine-tune-event", + "created_at": 1614807864, + "level": "info", + "message": "Uploaded result files: file-abc123" + }, + { + "object": "fine-tune-event", + "created_at": 1614807864, + "level": "info", + "message": "Job succeeded." + } + ] + } + + /models: + get: + operationId: listModels + tags: + - Models + summary: Lists the currently available models, and provides basic information about each one such as the owner and availability. + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/ListModelsResponse" + x-oaiMeta: + name: List models + returns: A list of [model](/docs/api-reference/models/object) objects. + examples: + request: + curl: | + curl https://api.openai.com/v1/models \ + -H "Authorization: Bearer $OPENAI_API_KEY" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.Model.list() + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const list = await openai.models.list(); + + for await (const model of list) { + console.log(model); + } + } + main(); + response: | + { + "object": "list", + "data": [ + { + "id": "model-id-0", + "object": "model", + "created": 1686935002, + "owned_by": "organization-owner" + }, + { + "id": "model-id-1", + "object": "model", + "created": 1686935002, + "owned_by": "organization-owner", + }, + { + "id": "model-id-2", + "object": "model", + "created": 1686935002, + "owned_by": "openai" + }, + ], + "object": "list" + } + + /models/{model}: + get: + operationId: retrieveModel + tags: + - Models + summary: Retrieves a model instance, providing basic information about the model such as the owner and permissioning. + parameters: + - in: path + name: model + required: true + schema: + type: string + # ideally this will be an actual ID, so this will always work from browser + example: gpt-3.5-turbo + description: The ID of the model to use for this request + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Model" + x-oaiMeta: + name: Retrieve model + returns: The [model](/docs/api-reference/models/object) object matching the specified ID. + examples: + request: + curl: | + curl https://api.openai.com/v1/models/VAR_model_id \ + -H "Authorization: Bearer $OPENAI_API_KEY" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.Model.retrieve("VAR_model_id") + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const model = await openai.models.retrieve("gpt-3.5-turbo"); + + console.log(model); + } + + main(); + response: &retrieve_model_response | + { + "id": "VAR_model_id", + "object": "model", + "created": 1686935002, + "owned_by": "openai" + } + delete: + operationId: deleteModel + tags: + - Models + summary: Delete a fine-tuned model. You must have the Owner role in your organization to delete a model. + parameters: + - in: path + name: model + required: true + schema: + type: string + example: ft:gpt-3.5-turbo:acemeco:suffix:abc123 + description: The model to delete + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/DeleteModelResponse" + x-oaiMeta: + name: Delete fine-tune model + returns: Deletion status. + examples: + request: + curl: | + curl https://api.openai.com/v1/models/ft:gpt-3.5-turbo:acemeco:suffix:abc123 \ + -X DELETE \ + -H "Authorization: Bearer $OPENAI_API_KEY" + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.Model.delete("ft:gpt-3.5-turbo:acemeco:suffix:abc123") + node.js: |- + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const model = await openai.models.del("ft:gpt-3.5-turbo:acemeco:suffix:abc123"); + + console.log(model); + } + main(); + response: | + { + "id": "ft:gpt-3.5-turbo:acemeco:suffix:abc123", + "object": "model", + "deleted": true + } + + /moderations: + post: + operationId: createModeration + tags: + - Moderations + summary: Classifies if text violates OpenAI's Content Policy + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateModerationRequest" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/CreateModerationResponse" + x-oaiMeta: + name: Create moderation + returns: A [moderation](/docs/api-reference/moderations/object) object. + examples: + request: + curl: | + curl https://api.openai.com/v1/moderations \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -d '{ + "input": "I want to kill them." + }' + python: | + import os + import openai + openai.api_key = os.getenv("OPENAI_API_KEY") + openai.Moderation.create( + input="I want to kill them.", + ) + node.js: | + import OpenAI from "openai"; + + const openai = new OpenAI(); + + async function main() { + const moderation = await openai.moderations.create({ input: "I want to kill them." }); + + console.log(moderation); + } + main(); + response: &moderation_example | + { + "id": "modr-XXXXX", + "model": "text-moderation-005", + "results": [ + { + "flagged": true, + "categories": { + "sexual": false, + "hate": false, + "harassment": false, + "self-harm": false, + "sexual/minors": false, + "hate/threatening": false, + "violence/graphic": false, + "self-harm/intent": false, + "self-harm/instructions": false, + "harassment/threatening": true, + "violence": true, + }, + "category_scores": { + "sexual": 1.2282071e-06, + "hate": 0.010696256, + "harassment": 0.29842457, + "self-harm": 1.5236925e-08, + "sexual/minors": 5.7246268e-08, + "hate/threatening": 0.0060676364, + "violence/graphic": 4.435014e-06, + "self-harm/intent": 8.098441e-10, + "self-harm/instructions": 2.8498655e-11, + "harassment/threatening": 0.63055265, + "violence": 0.99011886, + } + } + ] + } + +components: + + securitySchemes: + ApiKeyAuth: + type: http + scheme: 'bearer' + + schemas: + Error: + type: object + properties: + code: + type: string + nullable: true + message: + type: string + nullable: false + param: + type: string + nullable: true + type: + type: string + nullable: false + required: + - type + - message + - param + - code + + ErrorResponse: + type: object + properties: + error: + $ref: "#/components/schemas/Error" + required: + - error + + ListModelsResponse: + type: object + properties: + object: + type: string + data: + type: array + items: + $ref: "#/components/schemas/Model" + required: + - object + - data + + DeleteModelResponse: + type: object + properties: + id: + type: string + deleted: + type: boolean + object: + type: string + required: + - id + - object + - deleted + + CreateCompletionRequest: + type: object + properties: + model: + description: &model_description | + ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](/docs/models/overview) for descriptions of them. + anyOf: + - type: string + - type: string + enum: + [ + "babbage-002", + "davinci-002", + "gpt-3.5-turbo-instruct", + "text-davinci-003", + "text-davinci-002", + "text-davinci-001", + "code-davinci-002", + "text-curie-001", + "text-babbage-001", + "text-ada-001", + ] + x-oaiTypeLabel: string + prompt: + description: &completions_prompt_description | + The prompt(s) to generate completions for, encoded as a string, array of strings, array of tokens, or array of token arrays. + + Note that <|endoftext|> is the document separator that the model sees during training, so if a prompt is not specified the model will generate as if from the beginning of a new document. + default: "<|endoftext|>" + nullable: true + oneOf: + - type: string + default: "" + example: "This is a test." + - type: array + items: + type: string + default: "" + example: "This is a test." + - type: array + minItems: 1 + items: + type: integer + example: "[1212, 318, 257, 1332, 13]" + - type: array + minItems: 1 + items: + type: array + minItems: 1 + items: + type: integer + example: "[[1212, 318, 257, 1332, 13]]" + best_of: + type: integer + default: 1 + minimum: 0 + maximum: 20 + nullable: true + description: &completions_best_of_description | + Generates `best_of` completions server-side and returns the "best" (the one with the highest log probability per token). Results cannot be streamed. + + When used with `n`, `best_of` controls the number of candidate completions and `n` specifies how many to return – `best_of` must be greater than `n`. + + **Note:** Because this parameter generates many completions, it can quickly consume your token quota. Use carefully and ensure that you have reasonable settings for `max_tokens` and `stop`. + echo: + type: boolean + default: false + nullable: true + description: &completions_echo_description > + Echo back the prompt in addition to the completion + frequency_penalty: + type: number + default: 0 + minimum: -2 + maximum: 2 + nullable: true + description: &completions_frequency_penalty_description | + Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. + + [See more information about frequency and presence penalties.](/docs/guides/gpt/parameter-details) + logit_bias: &completions_logit_bias + type: object + x-oaiTypeLabel: map + default: null + nullable: true + additionalProperties: + type: integer + description: &completions_logit_bias_description | + Modify the likelihood of specified tokens appearing in the completion. + + Accepts a json object that maps tokens (specified by their token ID in the GPT tokenizer) to an associated bias value from -100 to 100. You can use this [tokenizer tool](/tokenizer?view=bpe) (which works for both GPT-2 and GPT-3) to convert text to token IDs. Mathematically, the bias is added to the logits generated by the model prior to sampling. The exact effect will vary per model, but values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token. + + As an example, you can pass `{"50256": -100}` to prevent the <|endoftext|> token from being generated. + logprobs: &completions_logprobs_configuration + type: integer + minimum: 0 + maximum: 5 + default: null + nullable: true + description: &completions_logprobs_description | + Include the log probabilities on the `logprobs` most likely tokens, as well the chosen tokens. For example, if `logprobs` is 5, the API will return a list of the 5 most likely tokens. The API will always return the `logprob` of the sampled token, so there may be up to `logprobs+1` elements in the response. + + The maximum value for `logprobs` is 5. + max_tokens: + type: integer + minimum: 0 + default: 16 + example: 16 + nullable: true + description: &completions_max_tokens_description | + The maximum number of [tokens](/tokenizer) to generate in the completion. + + The token count of your prompt plus `max_tokens` cannot exceed the model's context length. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) for counting tokens. + n: + type: integer + minimum: 1 + maximum: 128 + default: 1 + example: 1 + nullable: true + description: &completions_completions_description | + How many completions to generate for each prompt. + + **Note:** Because this parameter generates many completions, it can quickly consume your token quota. Use carefully and ensure that you have reasonable settings for `max_tokens` and `stop`. + presence_penalty: + type: number + default: 0 + minimum: -2 + maximum: 2 + nullable: true + description: &completions_presence_penalty_description | + Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics. + + [See more information about frequency and presence penalties.](/docs/guides/gpt/parameter-details) + stop: + description: &completions_stop_description > + Up to 4 sequences where the API will stop generating further tokens. The returned text will not contain the stop sequence. + default: null + nullable: true + oneOf: + - type: string + default: <|endoftext|> + example: "\n" + nullable: true + - type: array + minItems: 1 + maxItems: 4 + items: + type: string + example: '["\n"]' + stream: + description: > + Whether to stream back partial progress. If set, tokens will be sent as data-only [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) + as they become available, with the stream terminated by a `data: [DONE]` message. [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions). + type: boolean + nullable: true + default: false + suffix: + description: The suffix that comes after a completion of inserted text. + default: null + nullable: true + type: string + example: "test." + temperature: + type: number + minimum: 0 + maximum: 2 + default: 1 + example: 1 + nullable: true + description: &completions_temperature_description | + What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. + + We generally recommend altering this or `top_p` but not both. + top_p: + type: number + minimum: 0 + maximum: 1 + default: 1 + example: 1 + nullable: true + description: &completions_top_p_description | + An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. + + We generally recommend altering this or `temperature` but not both. + user: &end_user_param_configuration + type: string + example: user-1234 + description: | + A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. [Learn more](/docs/guides/safety-best-practices/end-user-ids). + required: + - model + - prompt + + CreateCompletionResponse: + type: object + description: | + Represents a completion response from the API. Note: both the streamed and non-streamed response objects share the same shape (unlike the chat endpoint). + properties: + id: + type: string + description: A unique identifier for the completion. + choices: + type: array + description: The list of completion choices the model generated for the input prompt. + items: + type: object + required: + - finish_reason + - index + - logprobs + - text + properties: + finish_reason: + type: string + description: &completion_finish_reason_description | + The reason the model stopped generating tokens. This will be `stop` if the model hit a natural stop point or a provided stop sequence, + `length` if the maximum number of tokens specified in the request was reached, + or `content_filter` if content was omitted due to a flag from our content filters. + enum: ["stop", "length", "content_filter"] + index: + type: integer + logprobs: + type: object + nullable: true + properties: + text_offset: + type: array + items: + type: integer + token_logprobs: + type: array + items: + type: number + tokens: + type: array + items: + type: string + top_logprobs: + type: array + items: + type: object + additionalProperties: + type: integer + text: + type: string + created: + type: integer + description: The Unix timestamp (in seconds) of when the completion was created. + model: + type: string + description: The model used for completion. + object: + type: string + description: The object type, which is always "text_completion" + usage: + $ref: "#/components/schemas/CompletionUsage" + required: + - id + - object + - created + - model + - choices + x-oaiMeta: + name: The completion object + legacy: true + example: | + { + "id": "cmpl-uqkvlQyYK7bGYrRHQ0eXlWi7", + "object": "text_completion", + "created": 1589478378, + "model": "gpt-3.5-turbo", + "choices": [ + { + "text": "\n\nThis is indeed a test", + "index": 0, + "logprobs": null, + "finish_reason": "length" + } + ], + "usage": { + "prompt_tokens": 5, + "completion_tokens": 7, + "total_tokens": 12 + } + } + + + ChatCompletionRequestMessage: + type: object + properties: + content: + type: string + nullable: true + description: The contents of the message. `content` is required for all messages, and may be null for assistant messages with function calls. + function_call: + type: object + description: The name and arguments of a function that should be called, as generated by the model. + properties: + arguments: + type: string + description: The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function. + name: + type: string + description: The name of the function to call. + required: + - arguments + - name + name: + type: string + description: The name of the author of this message. `name` is required if role is `function`, and it should be the name of the function whose response is in the `content`. May contain a-z, A-Z, 0-9, and underscores, with a maximum length of 64 characters. + role: + type: string + enum: ["system", "user", "assistant", "function"] + description: The role of the messages author. One of `system`, `user`, `assistant`, or `function`. + required: + - content + - role + + ChatCompletionFunctionParameters: + type: object + description: "The parameters the functions accepts, described as a JSON Schema object. See the [guide](/docs/guides/gpt/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format.\n\nTo describe a function that accepts no parameters, provide the value `{\"type\": \"object\", \"properties\": {}}`." + additionalProperties: true + + ChatCompletionFunctions: + type: object + properties: + description: + type: string + description: A description of what the function does, used by the model to choose when and how to call the function. + name: + type: string + description: The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64. + parameters: + $ref: "#/components/schemas/ChatCompletionFunctionParameters" + required: + - name + - parameters + + ChatCompletionFunctionCallOption: + type: object + properties: + name: + type: string + description: The name of the function to call. + required: + - name + + ChatCompletionResponseMessage: + type: object + description: A chat completion message generated by the model. + properties: + content: + type: string + description: The contents of the message. + nullable: true + function_call: + type: object + description: The name and arguments of a function that should be called, as generated by the model. + properties: + arguments: + type: string + description: The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function. + name: + type: string + description: The name of the function to call. + required: + - name + - arguments + role: + type: string + enum: ["system", "user", "assistant", "function"] + description: The role of the author of this message. + required: + - role + - content + + ChatCompletionStreamResponseDelta: + type: object + description: A chat completion delta generated by streamed model responses. + properties: + content: + type: string + description: The contents of the chunk message. + nullable: true + function_call: + type: object + description: The name and arguments of a function that should be called, as generated by the model. + properties: + arguments: + type: string + description: The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function. + name: + type: string + description: The name of the function to call. + role: + type: string + enum: ["system", "user", "assistant", "function"] + description: The role of the author of this message. + + CreateChatCompletionRequest: + type: object + properties: + messages: + description: A list of messages comprising the conversation so far. [Example Python code](https://cookbook.openai.com/examples/how_to_format_inputs_to_chatgpt_models). + type: array + minItems: 1 + items: + $ref: "#/components/schemas/ChatCompletionRequestMessage" + model: + description: ID of the model to use. See the [model endpoint compatibility](/docs/models/model-endpoint-compatibility) table for details on which models work with the Chat API. + example: "gpt-3.5-turbo" + anyOf: + - type: string + - type: string + enum: + [ + "gpt-4", + "gpt-4-0314", + "gpt-4-0613", + "gpt-4-32k", + "gpt-4-32k-0314", + "gpt-4-32k-0613", + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k", + "gpt-3.5-turbo-0301", + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-16k-0613", + ] + x-oaiTypeLabel: string + frequency_penalty: + type: number + default: 0 + minimum: -2 + maximum: 2 + nullable: true + description: *completions_frequency_penalty_description + function_call: + description: > + Controls how the model calls functions. "none" means the model will not call a function and instead generates a message. "auto" means the model can pick between generating a message or calling a function. Specifying a particular function via `{"name": "my_function"}` forces the model to call that function. "none" is the default when no functions are present. "auto" is the default if functions are present. + oneOf: + - type: string + enum: [none, auto] + - $ref: "#/components/schemas/ChatCompletionFunctionCallOption" + functions: + description: A list of functions the model may generate JSON inputs for. + type: array + minItems: 1 + maxItems: 128 + items: + $ref: "#/components/schemas/ChatCompletionFunctions" + logit_bias: + type: object + x-oaiTypeLabel: map + default: null + nullable: true + additionalProperties: + type: integer + description: | + Modify the likelihood of specified tokens appearing in the completion. + + Accepts a json object that maps tokens (specified by their token ID in the tokenizer) to an associated bias value from -100 to 100. Mathematically, the bias is added to the logits generated by the model prior to sampling. The exact effect will vary per model, but values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token. + max_tokens: + description: | + The maximum number of [tokens](/tokenizer) to generate in the chat completion. + + The total length of input tokens and generated tokens is limited by the model's context length. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) for counting tokens. + default: inf + type: integer + nullable: true + n: + type: integer + minimum: 1 + maximum: 128 + default: 1 + example: 1 + nullable: true + description: How many chat completion choices to generate for each input message. + presence_penalty: + type: number + default: 0 + minimum: -2 + maximum: 2 + nullable: true + description: *completions_presence_penalty_description + stop: + description: | + Up to 4 sequences where the API will stop generating further tokens. + default: null + oneOf: + - type: string + nullable: true + - type: array + minItems: 1 + maxItems: 4 + items: + type: string + stream: + description: > + If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) + as they become available, with the stream terminated by a `data: [DONE]` message. [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions). + type: boolean + nullable: true + default: false + temperature: + type: number + minimum: 0 + maximum: 2 + default: 1 + example: 1 + nullable: true + description: *completions_temperature_description + top_p: + type: number + minimum: 0 + maximum: 1 + default: 1 + example: 1 + nullable: true + description: *completions_top_p_description + user: *end_user_param_configuration + required: + - model + - messages + + CreateChatCompletionResponse: + type: object + description: Represents a chat completion response returned by model, based on the provided input. + properties: + id: + type: string + description: A unique identifier for the chat completion. + choices: + type: array + description: A list of chat completion choices. Can be more than one if `n` is greater than 1. + items: + type: object + required: + - finish_reason + - index + - message + properties: + finish_reason: + type: string + description: &chat_completion_finish_reason_description | + The reason the model stopped generating tokens. This will be `stop` if the model hit a natural stop point or a provided stop sequence, + `length` if the maximum number of tokens specified in the request was reached, + `content_filter` if content was omitted due to a flag from our content filters, + or `function_call` if the model called a function. + enum: ["stop", "length", "function_call", "content_filter"] + index: + type: integer + description: The index of the choice in the list of choices. + message: + $ref: "#/components/schemas/ChatCompletionResponseMessage" + created: + type: integer + description: The Unix timestamp (in seconds) of when the chat completion was created. + model: + type: string + description: The model used for the chat completion. + object: + type: string + description: The object type, which is always `chat.completion`. + usage: + $ref: "#/components/schemas/CompletionUsage" + required: + - choices + - created + - id + - model + - object + x-oaiMeta: + name: The chat completion object + group: chat + example: *chat_completion_example + + CreateChatCompletionFunctionResponse: + type: object + description: Represents a chat completion response returned by model, based on the provided input. + properties: + id: + type: string + description: A unique identifier for the chat completion. + choices: + type: array + description: A list of chat completion choices. Can be more than one if `n` is greater than 1. + items: + type: object + required: + - finish_reason + - index + - message + properties: + finish_reason: + type: string + description: &chat_completion_function_finish_reason_description | + The reason the model stopped generating tokens. This will be `stop` if the model hit a natural stop point or a provided stop sequence, `length` if the maximum number of tokens specified in the request was reached, `content_filter` if content was omitted due to a flag from our content filters, or `function_call` if the model called a function. + enum: ["stop", "length", "function_call", "content_filter"] + index: + type: integer + description: The index of the choice in the list of choices. + message: + $ref: "#/components/schemas/ChatCompletionResponseMessage" + created: + type: integer + description: The Unix timestamp (in seconds) of when the chat completion was created. + model: + type: string + description: The model used for the chat completion. + object: + type: string + description: The object type, which is always `chat.completion`. + usage: + $ref: "#/components/schemas/CompletionUsage" + required: + - choices + - created + - id + - model + - object + x-oaiMeta: + name: The chat completion object + group: chat + example: *chat_completion_function_example + + ListPaginatedFineTuningJobsResponse: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/FineTuningJob" + has_more: + type: boolean + object: + type: string + required: + - object + - data + - has_more + + CreateChatCompletionStreamResponse: + type: object + description: Represents a streamed chunk of a chat completion response returned by model, based on the provided input. + properties: + id: + type: string + description: A unique identifier for the chat completion. Each chunk has the same ID. + choices: + type: array + description: A list of chat completion choices. Can be more than one if `n` is greater than 1. + items: + type: object + required: + - delta + - finish_reason + - index + properties: + delta: + $ref: "#/components/schemas/ChatCompletionStreamResponseDelta" + finish_reason: + type: string + description: *chat_completion_finish_reason_description + enum: ["stop", "length", "function_call", "content_filter"] + nullable: true + index: + type: integer + description: The index of the choice in the list of choices. + created: + type: integer + description: The Unix timestamp (in seconds) of when the chat completion was created. Each chunk has the same timestamp. + model: + type: string + description: The model to generate the completion. + object: + type: string + description: The object type, which is always `chat.completion.chunk`. + required: + - choices + - created + - id + - model + - object + x-oaiMeta: + name: The chat completion chunk object + group: chat + example: *chat_completion_chunk_example + + CreateEditRequest: + type: object + properties: + instruction: + description: The instruction that tells the model how to edit the prompt. + type: string + example: "Fix the spelling mistakes." + model: + description: ID of the model to use. You can use the `text-davinci-edit-001` or `code-davinci-edit-001` model with this endpoint. + example: "text-davinci-edit-001" + anyOf: + - type: string + - type: string + enum: ["text-davinci-edit-001", "code-davinci-edit-001"] + x-oaiTypeLabel: string + input: + description: The input text to use as a starting point for the edit. + type: string + default: "" + nullable: true + example: "What day of the wek is it?" + n: + type: integer + minimum: 1 + maximum: 20 + default: 1 + example: 1 + nullable: true + description: How many edits to generate for the input and instruction. + temperature: + type: number + minimum: 0 + maximum: 2 + default: 1 + example: 1 + nullable: true + description: *completions_temperature_description + top_p: + type: number + minimum: 0 + maximum: 1 + default: 1 + example: 1 + nullable: true + description: *completions_top_p_description + required: + - model + - instruction + + CreateEditResponse: + type: object + title: Edit + deprecated: true + properties: + choices: + type: array + description: A list of edit choices. Can be more than one if `n` is greater than 1. + items: + type: object + required: + - text + - index + - finish_reason + properties: + finish_reason: + type: string + description: *completion_finish_reason_description + enum: ["stop", "length"] + index: + type: integer + description: The index of the choice in the list of choices. + text: + type: string + description: The edited result. + object: + type: string + description: The object type, which is always `edit`. + created: + type: integer + description: The Unix timestamp (in seconds) of when the edit was created. + usage: + $ref: "#/components/schemas/CompletionUsage" + required: + - object + - created + - choices + - usage + x-oaiMeta: + name: The edit object + example: *edit_example + + CreateImageRequest: + type: object + properties: + prompt: + description: A text description of the desired image(s). The maximum length is 1000 characters. + type: string + example: "A cute baby sea otter" + n: &images_n + type: integer + minimum: 1 + maximum: 10 + default: 1 + example: 1 + nullable: true + description: The number of images to generate. Must be between 1 and 10. + response_format: &images_response_format + type: string + enum: ["url", "b64_json"] + default: "url" + example: "url" + nullable: true + description: The format in which the generated images are returned. Must be one of `url` or `b64_json`. + size: &images_size + type: string + enum: ["256x256", "512x512", "1024x1024"] + default: "1024x1024" + example: "1024x1024" + nullable: true + description: The size of the generated images. Must be one of `256x256`, `512x512`, or `1024x1024`. + user: *end_user_param_configuration + required: + - prompt + + ImagesResponse: + properties: + created: + type: integer + data: + type: array + items: + $ref: "#/components/schemas/Image" + required: + - created + - data + + Image: + type: object + description: Represents the url or the content of an image generated by the OpenAI API. + properties: + b64_json: + type: string + description: The base64-encoded JSON of the generated image, if `response_format` is `b64_json`. + url: + type: string + description: The URL of the generated image, if `response_format` is `url` (default). + x-oaiMeta: + name: The image object + example: | + { + "url": "..." + } + + CreateImageEditRequest: + type: object + properties: + image: + description: The image to edit. Must be a valid PNG file, less than 4MB, and square. If mask is not provided, image must have transparency, which will be used as the mask. + type: string + format: binary + prompt: + description: A text description of the desired image(s). The maximum length is 1000 characters. + type: string + example: "A cute baby sea otter wearing a beret" + mask: + description: An additional image whose fully transparent areas (e.g. where alpha is zero) indicate where `image` should be edited. Must be a valid PNG file, less than 4MB, and have the same dimensions as `image`. + type: string + format: binary + n: *images_n + size: *images_size + response_format: *images_response_format + user: *end_user_param_configuration + required: + - prompt + - image + + CreateImageVariationRequest: + type: object + properties: + image: + description: The image to use as the basis for the variation(s). Must be a valid PNG file, less than 4MB, and square. + type: string + format: binary + n: *images_n + response_format: *images_response_format + size: *images_size + user: *end_user_param_configuration + required: + - image + + CreateModerationRequest: + type: object + properties: + input: + description: The input text to classify + oneOf: + - type: string + default: "" + example: "I want to kill them." + - type: array + items: + type: string + default: "" + example: "I want to kill them." + model: + description: | + Two content moderations models are available: `text-moderation-stable` and `text-moderation-latest`. + + The default is `text-moderation-latest` which will be automatically upgraded over time. This ensures you are always using our most accurate model. If you use `text-moderation-stable`, we will provide advanced notice before updating the model. Accuracy of `text-moderation-stable` may be slightly lower than for `text-moderation-latest`. + nullable: false + default: "text-moderation-latest" + example: "text-moderation-stable" + anyOf: + - type: string + - type: string + enum: ["text-moderation-latest", "text-moderation-stable"] + x-oaiTypeLabel: string + required: + - input + + CreateModerationResponse: + type: object + description: Represents policy compliance report by OpenAI's content moderation model against a given input. + properties: + id: + type: string + description: The unique identifier for the moderation request. + model: + type: string + description: The model used to generate the moderation results. + results: + type: array + description: A list of moderation objects. + items: + type: object + properties: + flagged: + type: boolean + description: Whether the content violates [OpenAI's usage policies](/policies/usage-policies). + categories: + type: object + description: A list of the categories, and whether they are flagged or not. + properties: + hate: + type: boolean + description: Content that expresses, incites, or promotes hate based on race, gender, ethnicity, religion, nationality, sexual orientation, disability status, or caste. Hateful content aimed at non-protected groups (e.g., chess players) is harrassment. + hate/threatening: + type: boolean + description: Hateful content that also includes violence or serious harm towards the targeted group based on race, gender, ethnicity, religion, nationality, sexual orientation, disability status, or caste. + harassment: + type: boolean + description: Content that expresses, incites, or promotes harassing language towards any target. + harassment/threatening: + type: boolean + description: Harassment content that also includes violence or serious harm towards any target. + self-harm: + type: boolean + description: Content that promotes, encourages, or depicts acts of self-harm, such as suicide, cutting, and eating disorders. + self-harm/intent: + type: boolean + description: Content where the speaker expresses that they are engaging or intend to engage in acts of self-harm, such as suicide, cutting, and eating disorders. + self-harm/instructions: + type: boolean + description: Content that encourages performing acts of self-harm, such as suicide, cutting, and eating disorders, or that gives instructions or advice on how to commit such acts. + sexual: + type: boolean + description: Content meant to arouse sexual excitement, such as the description of sexual activity, or that promotes sexual services (excluding sex education and wellness). + sexual/minors: + type: boolean + description: Sexual content that includes an individual who is under 18 years old. + violence: + type: boolean + description: Content that depicts death, violence, or physical injury. + violence/graphic: + type: boolean + description: Content that depicts death, violence, or physical injury in graphic detail. + required: + - hate + - hate/threatening + - harassment + - harassment/threatening + - self-harm + - self-harm/intent + - self-harm/instructions + - sexual + - sexual/minors + - violence + - violence/graphic + category_scores: + type: object + description: A list of the categories along with their scores as predicted by model. + properties: + hate: + type: number + description: The score for the category 'hate'. + hate/threatening: + type: number + description: The score for the category 'hate/threatening'. + harassment: + type: number + description: The score for the category 'harassment'. + harassment/threatening: + type: number + description: The score for the category 'harassment/threatening'. + self-harm: + type: number + description: The score for the category 'self-harm'. + self-harm/intent: + type: number + description: The score for the category 'self-harm/intent'. + self-harm/instructions: + type: number + description: The score for the category 'self-harm/instructions'. + sexual: + type: number + description: The score for the category 'sexual'. + sexual/minors: + type: number + description: The score for the category 'sexual/minors'. + violence: + type: number + description: The score for the category 'violence'. + violence/graphic: + type: number + description: The score for the category 'violence/graphic'. + required: + - hate + - hate/threatening + - harassment + - harassment/threatening + - self-harm + - self-harm/intent + - self-harm/instructions + - sexual + - sexual/minors + - violence + - violence/graphic + required: + - flagged + - categories + - category_scores + required: + - id + - model + - results + x-oaiMeta: + name: The moderation object + example: *moderation_example + + ListFilesResponse: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/OpenAIFile" + object: + type: string + required: + - object + - data + + CreateFileRequest: + type: object + additionalProperties: false + properties: + file: + description: | + The file object (not file name) to be uploaded. + + If the `purpose` is set to "fine-tune", the file will be used for fine-tuning. + type: string + format: binary + purpose: + description: | + The intended purpose of the uploaded file. + + Use "fine-tune" for [fine-tuning](/docs/api-reference/fine-tuning). This allows us to validate the format of the uploaded file is correct for fine-tuning. + type: string + required: + - file + - purpose + + DeleteFileResponse: + type: object + properties: + id: + type: string + object: + type: string + deleted: + type: boolean + required: + - id + - object + - deleted + + CreateFineTuningJobRequest: + type: object + properties: + model: + description: | + The name of the model to fine-tune. You can select one of the + [supported models](/docs/guides/fine-tuning/what-models-can-be-fine-tuned). + example: "gpt-3.5-turbo" + anyOf: + - type: string + - type: string + enum: ["babbage-002", "davinci-002", "gpt-3.5-turbo"] + x-oaiTypeLabel: string + training_file: + description: | + The ID of an uploaded file that contains training data. + + See [upload file](/docs/api-reference/files/upload) for how to upload a file. + + Your dataset must be formatted as a JSONL file. Additionally, you must upload your file with the purpose `fine-tune`. + + See the [fine-tuning guide](/docs/guides/fine-tuning) for more details. + type: string + example: "file-abc123" + hyperparameters: + type: object + description: The hyperparameters used for the fine-tuning job. + properties: + n_epochs: + description: | + The number of epochs to train the model for. An epoch refers to one + full cycle through the training dataset. + oneOf: + - type: string + enum: [auto] + - type: integer + minimum: 1 + maximum: 50 + default: auto + suffix: + description: | + A string of up to 18 characters that will be added to your fine-tuned model name. + + For example, a `suffix` of "custom-model-name" would produce a model name like `ft:gpt-3.5-turbo:openai:custom-model-name:7p4lURel`. + type: string + minLength: 1 + maxLength: 40 + default: null + nullable: true + validation_file: + description: | + The ID of an uploaded file that contains validation data. + + If you provide this file, the data is used to generate validation + metrics periodically during fine-tuning. These metrics can be viewed in + the fine-tuning results file. + The same data should not be present in both train and validation files. + + Your dataset must be formatted as a JSONL file. You must upload your file with the purpose `fine-tune`. + + See the [fine-tuning guide](/docs/guides/fine-tuning) for more details. + type: string + nullable: true + example: "file-abc123" + required: + - model + - training_file + + ListFineTuningJobEventsResponse: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/FineTuningJobEvent" + object: + type: string + required: + - object + - data + + CreateFineTuneRequest: + type: object + properties: + training_file: + description: | + The ID of an uploaded file that contains training data. + + See [upload file](/docs/api-reference/files/upload) for how to upload a file. + + Your dataset must be formatted as a JSONL file, where each training + example is a JSON object with the keys "prompt" and "completion". + Additionally, you must upload your file with the purpose `fine-tune`. + + See the [fine-tuning guide](/docs/guides/legacy-fine-tuning/creating-training-data) for more details. + type: string + example: "file-abc123" + batch_size: + description: | + The batch size to use for training. The batch size is the number of + training examples used to train a single forward and backward pass. + + By default, the batch size will be dynamically configured to be + ~0.2% of the number of examples in the training set, capped at 256 - + in general, we've found that larger batch sizes tend to work better + for larger datasets. + default: null + type: integer + nullable: true + classification_betas: + description: | + If this is provided, we calculate F-beta scores at the specified + beta values. The F-beta score is a generalization of F-1 score. + This is only used for binary classification. + + With a beta of 1 (i.e. the F-1 score), precision and recall are + given the same weight. A larger beta score puts more weight on + recall and less on precision. A smaller beta score puts more weight + on precision and less on recall. + type: array + items: + type: number + example: [0.6, 1, 1.5, 2] + default: null + nullable: true + classification_n_classes: + description: | + The number of classes in a classification task. + + This parameter is required for multiclass classification. + type: integer + default: null + nullable: true + classification_positive_class: + description: | + The positive class in binary classification. + + This parameter is needed to generate precision, recall, and F1 + metrics when doing binary classification. + type: string + default: null + nullable: true + compute_classification_metrics: + description: | + If set, we calculate classification-specific metrics such as accuracy + and F-1 score using the validation set at the end of every epoch. + These metrics can be viewed in the [results file](/docs/guides/legacy-fine-tuning/analyzing-your-fine-tuned-model). + + In order to compute classification metrics, you must provide a + `validation_file`. Additionally, you must + specify `classification_n_classes` for multiclass classification or + `classification_positive_class` for binary classification. + type: boolean + default: false + nullable: true + hyperparameters: + type: object + description: The hyperparameters used for the fine-tuning job. + properties: + n_epochs: + description: | + The number of epochs to train the model for. An epoch refers to one + full cycle through the training dataset. + oneOf: + - type: string + enum: [auto] + - type: integer + minimum: 1 + maximum: 50 + default: auto + learning_rate_multiplier: + description: | + The learning rate multiplier to use for training. + The fine-tuning learning rate is the original learning rate used for + pretraining multiplied by this value. + + By default, the learning rate multiplier is the 0.05, 0.1, or 0.2 + depending on final `batch_size` (larger learning rates tend to + perform better with larger batch sizes). We recommend experimenting + with values in the range 0.02 to 0.2 to see what produces the best + results. + default: null + type: number + nullable: true + model: + description: | + The name of the base model to fine-tune. You can select one of "ada", + "babbage", "curie", "davinci", or a fine-tuned model created after 2022-04-21 and before 2023-08-22. + To learn more about these models, see the + [Models](/docs/models) documentation. + default: "curie" + example: "curie" + nullable: true + anyOf: + - type: string + - type: string + enum: ["ada", "babbage", "curie", "davinci"] + x-oaiTypeLabel: string + prompt_loss_weight: + description: | + The weight to use for loss on the prompt tokens. This controls how + much the model tries to learn to generate the prompt (as compared + to the completion which always has a weight of 1.0), and can add + a stabilizing effect to training when completions are short. + + If prompts are extremely long (relative to completions), it may make + sense to reduce this weight so as to avoid over-prioritizing + learning the prompt. + default: 0.01 + type: number + nullable: true + suffix: + description: | + A string of up to 40 characters that will be added to your fine-tuned model name. + + For example, a `suffix` of "custom-model-name" would produce a model name like `ada:ft-your-org:custom-model-name-2022-02-15-04-21-04`. + type: string + minLength: 1 + maxLength: 40 + default: null + nullable: true + validation_file: + description: | + The ID of an uploaded file that contains validation data. + + If you provide this file, the data is used to generate validation + metrics periodically during fine-tuning. These metrics can be viewed in + the [fine-tuning results file](/docs/guides/legacy-fine-tuning/analyzing-your-fine-tuned-model). + Your train and validation data should be mutually exclusive. + + Your dataset must be formatted as a JSONL file, where each validation + example is a JSON object with the keys "prompt" and "completion". + Additionally, you must upload your file with the purpose `fine-tune`. + + See the [fine-tuning guide](/docs/guides/legacy-fine-tuning/creating-training-data) for more details. + type: string + nullable: true + example: "file-abc123" + required: + - training_file + + ListFineTunesResponse: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/FineTune" + object: + type: string + required: + - object + - data + + ListFineTuneEventsResponse: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/FineTuneEvent" + object: + type: string + required: + - object + - data + + CreateEmbeddingRequest: + type: object + additionalProperties: false + properties: + input: + description: | + Input text to embed, encoded as a string or array of tokens. To embed multiple inputs in a single request, pass an array of strings or array of token arrays. The input must not exceed the max input tokens for the model (8192 tokens for `text-embedding-ada-002`) and cannot be an empty string. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) for counting tokens. + example: "The quick brown fox jumped over the lazy dog" + oneOf: + - type: string + default: "" + example: "This is a test." + - type: array + minItems: 1 + items: + type: string + default: "" + example: "This is a test." + - type: array + minItems: 1 + items: + type: integer + example: "[1212, 318, 257, 1332, 13]" + - type: array + minItems: 1 + items: + type: array + minItems: 1 + items: + type: integer + example: "[[1212, 318, 257, 1332, 13]]" + model: + description: *model_description + example: "text-embedding-ada-002" + anyOf: + - type: string + - type: string + enum: ["text-embedding-ada-002"] + x-oaiTypeLabel: string + encoding_format: + description: "The format to return the embeddings in. Can be either `float` or [`base64`](https://pypi.org/project/pybase64/)." + example: "float" + default: "float" + type: string + enum: ["float", "base64"] + user: *end_user_param_configuration + required: + - model + - input + + CreateEmbeddingResponse: + type: object + properties: + data: + type: array + description: The list of embeddings generated by the model. + items: + $ref: "#/components/schemas/Embedding" + model: + type: string + description: The name of the model used to generate the embedding. + object: + type: string + description: The object type, which is always "embedding". + usage: + type: object + description: The usage information for the request. + properties: + prompt_tokens: + type: integer + description: The number of tokens used by the prompt. + total_tokens: + type: integer + description: The total number of tokens used by the request. + required: + - prompt_tokens + - total_tokens + required: + - object + - model + - data + - usage + + CreateTranscriptionRequest: + type: object + additionalProperties: false + properties: + file: + description: | + The audio file object (not file name) to transcribe, in one of these formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm. + type: string + x-oaiTypeLabel: file + format: binary + model: + description: | + ID of the model to use. Only `whisper-1` is currently available. + example: whisper-1 + anyOf: + - type: string + - type: string + enum: ["whisper-1"] + x-oaiTypeLabel: string + language: + description: | + The language of the input audio. Supplying the input language in [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) format will improve accuracy and latency. + type: string + prompt: + description: | + An optional text to guide the model's style or continue a previous audio segment. The [prompt](/docs/guides/speech-to-text/prompting) should match the audio language. + type: string + response_format: + description: | + The format of the transcript output, in one of these options: json, text, srt, verbose_json, or vtt. + type: string + enum: + - json + - text + - srt + - verbose_json + - vtt + default: json + temperature: + description: | + The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to automatically increase the temperature until certain thresholds are hit. + type: number + default: 0 + required: + - file + - model + + # Note: This does not currently support the non-default response format types. + CreateTranscriptionResponse: + type: object + properties: + text: + type: string + required: + - text + + CreateTranslationRequest: + type: object + additionalProperties: false + properties: + file: + description: | + The audio file object (not file name) translate, in one of these formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm. + type: string + x-oaiTypeLabel: file + format: binary + model: + description: | + ID of the model to use. Only `whisper-1` is currently available. + example: whisper-1 + anyOf: + - type: string + - type: string + enum: ["whisper-1"] + x-oaiTypeLabel: string + prompt: + description: | + An optional text to guide the model's style or continue a previous audio segment. The [prompt](/docs/guides/speech-to-text/prompting) should be in English. + type: string + response_format: + description: | + The format of the transcript output, in one of these options: json, text, srt, verbose_json, or vtt. + type: string + default: json + temperature: + description: | + The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to automatically increase the temperature until certain thresholds are hit. + type: number + default: 0 + required: + - file + - model + + # Note: This does not currently support the non-default response format types. + CreateTranslationResponse: + type: object + properties: + text: + type: string + required: + - text + + Model: + title: Model + description: Describes an OpenAI model offering that can be used with the API. + properties: + id: + type: string + description: The model identifier, which can be referenced in the API endpoints. + created: + type: integer + description: The Unix timestamp (in seconds) when the model was created. + object: + type: string + description: The object type, which is always "model". + owned_by: + type: string + description: The organization that owns the model. + required: + - id + - object + - created + - owned_by + x-oaiMeta: + name: The model object + example: *retrieve_model_response + + OpenAIFile: + title: OpenAIFile + description: | + The `File` object represents a document that has been uploaded to OpenAI. + properties: + id: + type: string + description: The file identifier, which can be referenced in the API endpoints. + bytes: + type: integer + description: The size of the file in bytes. + created_at: + type: integer + description: The Unix timestamp (in seconds) for when the file was created. + filename: + type: string + description: The name of the file. + object: + type: string + description: The object type, which is always "file". + purpose: + type: string + description: The intended purpose of the file. Currently, only "fine-tune" is supported. + status: + type: string + description: The current status of the file, which can be either `uploaded`, `processed`, `pending`, `error`, `deleting` or `deleted`. + status_details: + type: string + nullable: true + description: | + Additional details about the status of the file. If the file is in the `error` state, this will include a message describing the error. + required: + - id + - object + - bytes + - created_at + - filename + - purpose + - format + x-oaiMeta: + name: The file object + example: | + { + "id": "file-abc123", + "object": "file", + "bytes": 120000, + "created_at": 1677610602, + "filename": "my_file.jsonl", + "purpose": "fine-tune", + "status": "uploaded", + "status_details": null + } + Embedding: + type: object + description: | + Represents an embedding vector returned by embedding endpoint. + properties: + index: + type: integer + description: The index of the embedding in the list of embeddings. + embedding: + type: array + description: | + The embedding vector, which is a list of floats. The length of vector depends on the model as listed in the [embedding guide](/docs/guides/embeddings). + items: + type: number + object: + type: string + description: The object type, which is always "embedding". + required: + - index + - object + - embedding + x-oaiMeta: + name: The embedding object + example: | + { + "object": "embedding", + "embedding": [ + 0.0023064255, + -0.009327292, + .... (1536 floats total for ada-002) + -0.0028842222, + ], + "index": 0 + } + + FineTuningJob: + type: object + title: FineTuningJob + description: | + The `fine_tuning.job` object represents a fine-tuning job that has been created through the API. + properties: + id: + type: string + description: The object identifier, which can be referenced in the API endpoints. + created_at: + type: integer + description: The Unix timestamp (in seconds) for when the fine-tuning job was created. + error: + type: object + nullable: true + description: For fine-tuning jobs that have `failed`, this will contain more information on the cause of the failure. + properties: + code: + type: string + description: A machine-readable error code. + message: + type: string + description: A human-readable error message. + param: + type: string + description: The parameter that was invalid, usually `training_file` or `validation_file`. This field will be null if the failure was not parameter-specific. + nullable: true + required: + - code + - message + - param + fine_tuned_model: + type: string + nullable: true + description: The name of the fine-tuned model that is being created. The value will be null if the fine-tuning job is still running. + finished_at: + type: integer + nullable: true + description: The Unix timestamp (in seconds) for when the fine-tuning job was finished. The value will be null if the fine-tuning job is still running. + hyperparameters: + type: object + description: The hyperparameters used for the fine-tuning job. See the [fine-tuning guide](/docs/guides/fine-tuning) for more details. + properties: + n_epochs: + oneOf: + - type: string + enum: [auto] + - type: integer + minimum: 1 + maximum: 50 + default: auto + description: + The number of epochs to train the model for. An epoch refers to one full cycle through the training dataset. + + "auto" decides the optimal number of epochs based on the size of the dataset. If setting the number manually, we support any number between 1 and 50 epochs. + required: + - n_epochs + model: + type: string + description: The base model that is being fine-tuned. + object: + type: string + description: The object type, which is always "fine_tuning.job". + organization_id: + type: string + description: The organization that owns the fine-tuning job. + result_files: + type: array + description: The compiled results file ID(s) for the fine-tuning job. You can retrieve the results with the [Files API](/docs/api-reference/files/retrieve-contents). + items: + type: string + example: file-abc123 + status: + type: string + description: The current status of the fine-tuning job, which can be either `validating_files`, `queued`, `running`, `succeeded`, `failed`, or `cancelled`. + trained_tokens: + type: integer + nullable: true + description: The total number of billable tokens processed by this fine-tuning job. The value will be null if the fine-tuning job is still running. + training_file: + type: string + description: The file ID used for training. You can retrieve the training data with the [Files API](/docs/api-reference/files/retrieve-contents). + validation_file: + type: string + nullable: true + description: The file ID used for validation. You can retrieve the validation results with the [Files API](/docs/api-reference/files/retrieve-contents). + required: + - created_at + - error + - finished_at + - fine_tuned_model + - hyperparameters + - id + - model + - object + - organization_id + - result_files + - status + - trained_tokens + - training_file + - validation_file + x-oaiMeta: + name: The fine-tuning job object + example: *fine_tuning_example + + FineTuningJobEvent: + type: object + description: Fine-tuning job event object + properties: + id: + type: string + created_at: + type: integer + level: + type: string + enum: ["info", "warn", "error"] + message: + type: string + object: + type: string + required: + - id + - object + - created_at + - level + - message + x-oaiMeta: + name: The fine-tuning job event object + example: | + { + "object": "event", + "id": "ftevent-abc123" + "created_at": 1677610602, + "level": "info", + "message": "Created fine-tuning job" + } + + FineTune: + type: object + deprecated: true + description: | + The `FineTune` object represents a legacy fine-tune job that has been created through the API. + properties: + id: + type: string + description: The object identifier, which can be referenced in the API endpoints. + created_at: + type: integer + description: The Unix timestamp (in seconds) for when the fine-tuning job was created. + events: + type: array + description: The list of events that have been observed in the lifecycle of the FineTune job. + items: + $ref: "#/components/schemas/FineTuneEvent" + fine_tuned_model: + type: string + nullable: true + description: The name of the fine-tuned model that is being created. + hyperparams: + type: object + description: The hyperparameters used for the fine-tuning job. See the [fine-tuning guide](/docs/guides/legacy-fine-tuning/hyperparameters) for more details. + properties: + batch_size: + type: integer + description: | + The batch size to use for training. The batch size is the number of + training examples used to train a single forward and backward pass. + classification_n_classes: + type: integer + description: | + The number of classes to use for computing classification metrics. + classification_positive_class: + type: string + description: | + The positive class to use for computing classification metrics. + compute_classification_metrics: + type: boolean + description: | + The classification metrics to compute using the validation dataset at the end of every epoch. + learning_rate_multiplier: + type: number + description: | + The learning rate multiplier to use for training. + n_epochs: + type: integer + description: | + The number of epochs to train the model for. An epoch refers to one + full cycle through the training dataset. + prompt_loss_weight: + type: number + description: | + The weight to use for loss on the prompt tokens. + required: + - batch_size + - learning_rate_multiplier + - n_epochs + - prompt_loss_weight + model: + type: string + description: The base model that is being fine-tuned. + object: + type: string + description: The object type, which is always "fine-tune". + organization_id: + type: string + description: The organization that owns the fine-tuning job. + result_files: + type: array + description: The compiled results files for the fine-tuning job. + items: + $ref: "#/components/schemas/OpenAIFile" + status: + type: string + description: The current status of the fine-tuning job, which can be either `created`, `running`, `succeeded`, `failed`, or `cancelled`. + training_files: + type: array + description: The list of files used for training. + items: + $ref: "#/components/schemas/OpenAIFile" + updated_at: + type: integer + description: The Unix timestamp (in seconds) for when the fine-tuning job was last updated. + validation_files: + type: array + description: The list of files used for validation. + items: + $ref: "#/components/schemas/OpenAIFile" + required: + - created_at + - fine_tuned_model + - hyperparams + - id + - model + - object + - organization_id + - result_files + - status + - training_files + - updated_at + - validation_files + x-oaiMeta: + name: The fine-tune object + example: *fine_tune_example + + FineTuneEvent: + type: object + deprecated: true + description: Fine-tune event object + properties: + created_at: + type: integer + level: + type: string + message: + type: string + object: + type: string + required: + - object + - created_at + - level + - message + x-oaiMeta: + name: The fine-tune event object + example: | + { + "object": "event", + "created_at": 1677610602, + "level": "info", + "message": "Created fine-tune job" + } + + CompletionUsage: + type: object + description: Usage statistics for the completion request. + properties: + completion_tokens: + type: integer + description: Number of tokens in the generated completion. + prompt_tokens: + type: integer + description: Number of tokens in the prompt. + total_tokens: + type: integer + description: Total number of tokens used in the request (prompt + completion). + required: + - prompt_tokens + - completion_tokens + - total_tokens + +security: + - ApiKeyAuth: [] + +x-oaiMeta: + groups: + # > General Notes + # The `groups` section is used to generate the API reference pages and navigation, in the same + # order listed below. Additionally, each `group` can have a list of `sections`, each of which + # will become a navigation subroute and subsection under the group. Each section has: + # - `type`: Currently, either an `endpoint` or `object`, depending on how the section needs to + # be rendered + # - `key`: The reference key that can be used to lookup the section definition + # - `path`: The path (url) of the section, which is used to generate the navigation link. + # + # > The `object` sections maps to a schema component and the following fields are read for rendering + # - `x-oaiMeta.name`: The name of the object, which will become the section title + # - `x-oaiMeta.example`: The example object, which will be used to generate the example sample (always JSON) + # - `description`: The description of the object, which will be used to generate the section description + # + # > The `endpoint` section maps to an operation path and the following fields are read for rendering: + # - `x-oaiMeta.name`: The name of the endpoint, which will become the section title + # - `x-oaiMeta.examples`: The endpoint examples, which can be an object (meaning a single variation, most + # endpoints, or an array of objects, meaning multiple variations, e.g. the + # chat completion and completion endpoints, with streamed and non-streamed examples. + # - `x-oaiMeta.returns`: text describing what the endpoint returns. + # - `summary`: The summary of the endpoint, which will be used to generate the section description + - id: audio + title: Audio + description: | + Learn how to turn audio into text. + + Related guide: [Speech to text](/docs/guides/speech-to-text) + sections: + - type: endpoint + key: createTranscription + path: createTranscription + - type: endpoint + key: createTranslation + path: createTranslation + - id: chat + title: Chat + description: | + Given a list of messages comprising a conversation, the model will return a response. + + Related guide: [Chat completions](/docs/guides/gpt) + sections: + - type: object + key: CreateChatCompletionResponse + path: object + - type: object + key: CreateChatCompletionStreamResponse + path: streaming + - type: endpoint + key: createChatCompletion + path: create + - id: completions + title: Completions + legacy: true + description: | + Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. We recommend most users use our Chat completions API. [Learn more](/docs/deprecations/2023-07-06-gpt-and-embeddings) + + Related guide: [Legacy Completions](/docs/guides/gpt/completions-api) + sections: + - type: object + key: CreateCompletionResponse + path: object + - type: endpoint + key: createCompletion + path: create + - id: embeddings + title: Embeddings + description: | + Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. + + Related guide: [Embeddings](/docs/guides/embeddings) + sections: + - type: object + key: Embedding + path: object + - type: endpoint + key: createEmbedding + path: create + - id: fine-tuning + title: Fine-tuning + description: | + Manage fine-tuning jobs to tailor a model to your specific training data. + + Related guide: [Fine-tune models](/docs/guides/fine-tuning) + sections: + - type: object + key: FineTuningJob + path: object + - type: endpoint + key: createFineTuningJob + path: create + - type: endpoint + key: listPaginatedFineTuningJobs + path: list + - type: endpoint + key: retrieveFineTuningJob + path: retrieve + - type: endpoint + key: cancelFineTuningJob + path: cancel + - type: object + key: FineTuningJobEvent + path: event-object + - type: endpoint + key: listFineTuningEvents + path: list-events + - id: files + title: Files + description: | + Files are used to upload documents that can be used with features like [fine-tuning](/docs/api-reference/fine-tuning). + sections: + - type: object + key: OpenAIFile + path: object + - type: endpoint + key: listFiles + path: list + - type: endpoint + key: createFile + path: create + - type: endpoint + key: deleteFile + path: delete + - type: endpoint + key: retrieveFile + path: retrieve + - type: endpoint + key: downloadFile + path: retrieve-contents + - id: images + title: Images + description: | + Given a prompt and/or an input image, the model will generate a new image. + + Related guide: [Image generation](/docs/guides/images) + sections: + - type: object + key: Image + path: object + - type: endpoint + key: createImage + path: create + - type: endpoint + key: createImageEdit + path: createEdit + - type: endpoint + key: createImageVariation + path: createVariation + - id: models + title: Models + description: | + List and describe the various models available in the API. You can refer to the [Models](/docs/models) documentation to understand what models are available and the differences between them. + sections: + - type: object + key: Model + path: object + - type: endpoint + key: listModels + path: list + - type: endpoint + key: retrieveModel + path: retrieve + - type: endpoint + key: deleteModel + path: delete + - id: moderations + title: Moderations + description: | + Given a input text, outputs if the model classifies it as violating OpenAI's content policy. + + Related guide: [Moderations](/docs/guides/moderation) + sections: + - type: object + key: CreateModerationResponse + path: object + - type: endpoint + key: createModeration + path: create + - id: fine-tunes + title: Fine-tunes + deprecated: true + description: | + Manage legacy fine-tuning jobs to tailor a model to your specific training data. + + We recommend transitioning to the updating [fine-tuning API](/docs/guides/fine-tuning) + sections: + - type: object + key: FineTune + path: object + - type: endpoint + key: createFineTune + path: create + - type: endpoint + key: listFineTunes + path: list + - type: endpoint + key: retrieveFineTune + path: retrieve + - type: endpoint + key: cancelFineTune + path: cancel + - type: object + key: FineTuneEvent + path: event-object + - type: endpoint + key: listFineTuneEvents + path: list-events + - id: edits + title: Edits + deprecated: true + description: | + Given a prompt and an instruction, the model will return an edited version of the prompt. + sections: + - type: object + key: CreateEditResponse + path: object + - type: endpoint + key: createEdit + path: create From 89b16ea52197a465c6a6f531b0a5a60d5085c7a0 Mon Sep 17 00:00:00 2001 From: ahirner Date: Thu, 26 Oct 2023 07:45:11 +0200 Subject: [PATCH 17/38] prune and describe openai-openapi.yaml * removed endpoints not tagged with Images * removed x-oaiMeta key * inlined end_user_param_configuration still gen fail: TypeError(InvalidValue) --- sample_openapi/README.md | 4 +- sample_openapi/openai-openapi.yaml | 4468 +--------------------------- 2 files changed, 111 insertions(+), 4361 deletions(-) diff --git a/sample_openapi/README.md b/sample_openapi/README.md index 54dddbe6..bb219d4c 100644 --- a/sample_openapi/README.md +++ b/sample_openapi/README.md @@ -9,5 +9,7 @@ have been pulled in from other projects: - `keeper.json`: https://github.com/jclulow/keeper - `nexus.json`: https://github.com/oxidecomputer/omicron - `propolis-server.json`: https://github.com/oxidecomputer/propolis +- `openai-openapi.yaml`: https://github.com/openai/openai-openapi -Note that keeper and buildomat have diverged in order to validate support for various features. The others should not be changed other than updating them from their source repository. \ No newline at end of file +Note that keeper, buildomat and openai-openapi.yaml have diverged in order to validate support for various features. +The others should not be changed other than updating them from their source repository. diff --git a/sample_openapi/openai-openapi.yaml b/sample_openapi/openai-openapi.yaml index a1266253..5630fea1 100644 --- a/sample_openapi/openai-openapi.yaml +++ b/sample_openapi/openai-openapi.yaml @@ -13,546 +13,10 @@ info: servers: - url: https://api.openai.com/v1 tags: - - name: Audio - description: Learn how to turn audio into text. - - name: Chat - description: Given a list of messages comprising a conversation, the model will return a response. - - name: Completions - description: Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. - - name: Embeddings - description: Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. - - name: Fine-tuning - description: Manage fine-tuning jobs to tailor a model to your specific training data. - - name: Files - description: Files are used to upload documents that can be used with features like fine-tuning. - name: Images description: Given a prompt and/or an input image, the model will generate a new image. - - name: Models - description: List and describe the various models available in the API. - - name: Moderations - description: Given a input text, outputs if the model classifies it as violating OpenAI's content policy. - - name: Fine-tunes - description: Manage legacy fine-tuning jobs to tailor a model to your specific training data. - - name: Edits - description: Given a prompt and an instruction, the model will return an edited version of the prompt. - + paths: - # Note: When adding an endpoint, make sure you also add it in the `groups` section, in the end of this file, - # under the appropriate group - /chat/completions: - post: - operationId: createChatCompletion - tags: - - Chat - summary: Creates a model response for the given chat conversation. - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/CreateChatCompletionRequest" - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/CreateChatCompletionResponse" - - x-oaiMeta: - name: Create chat completion - group: chat - returns: | - Returns a [chat completion](/docs/api-reference/chat/object) object, or a streamed sequence of [chat completion chunk](/docs/api-reference/chat/streaming) objects if the request is streamed. - path: create - examples: - - title: No Streaming - request: - curl: | - curl https://api.openai.com/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d '{ - "model": "VAR_model_id", - "messages": [ - { - "role": "system", - "content": "You are a helpful assistant." - }, - { - "role": "user", - "content": "Hello!" - } - ] - }' - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - - completion = openai.ChatCompletion.create( - model="VAR_model_id", - messages=[ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "Hello!"} - ] - ) - - print(completion.choices[0].message) - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const completion = await openai.chat.completions.create({ - messages: [{ role: "system", content: "You are a helpful assistant." }], - model: "VAR_model_id", - }); - - console.log(completion.choices[0]); - } - - main(); - response: &chat_completion_example | - { - "id": "chatcmpl-123", - "object": "chat.completion", - "created": 1677652288, - "model": "gpt-3.5-turbo-0613", - "choices": [{ - "index": 0, - "message": { - "role": "assistant", - "content": "\n\nHello there, how may I assist you today?", - }, - "finish_reason": "stop" - }], - "usage": { - "prompt_tokens": 9, - "completion_tokens": 12, - "total_tokens": 21 - } - } - - title: Streaming - request: - curl: | - curl https://api.openai.com/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d '{ - "model": "VAR_model_id", - "messages": [ - { - "role": "system", - "content": "You are a helpful assistant." - }, - { - "role": "user", - "content": "Hello!" - } - ], - "stream": true - }' - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - - completion = openai.ChatCompletion.create( - model="VAR_model_id", - messages=[ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "Hello!"} - ], - stream=True - ) - - for chunk in completion: - print(chunk.choices[0].delta) - - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const completion = await openai.chat.completions.create({ - model: "VAR_model_id", - messages: [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "Hello!"} - ], - stream: true, - }); - - for await (const chunk of completion) { - console.log(chunk.choices[0].delta.content); - } - } - - main(); - response: &chat_completion_chunk_example | - {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]} - - {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]} - - {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"!"},"finish_reason":null}]} - - .... - - {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":" today"},"finish_reason":null}]} - - {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"content":"?"},"finish_reason":null}]} - - {"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} - - title: Function calling - request: - curl: | - curl https://api.openai.com/v1/chat/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d '{ - "model": "gpt-3.5-turbo", - "messages": [ - { - "role": "user", - "content": "What is the weather like in Boston?" - } - ], - "functions": [ - { - "name": "get_current_weather", - "description": "Get the current weather in a given location", - "parameters": { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state, e.g. San Francisco, CA" - }, - "unit": { - "type": "string", - "enum": ["celsius", "fahrenheit"] - } - }, - "required": ["location"] - } - } - ], - "function_call": "auto" - }' - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - - functions = [ - { - "name": "get_current_weather", - "description": "Get the current weather in a given location", - "parameters": { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state, e.g. San Francisco, CA", - }, - "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, - }, - "required": ["location"], - }, - } - ] - messages = [{"role": "user", "content": "What's the weather like in Boston today?"}] - completion = openai.ChatCompletion.create( - model="VAR_model_id", - messages=messages, - functions=functions, - function_call="auto", # auto is default, but we'll be explicit - ) - - print(completion) - - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const messages = [{"role": "user", "content": "What's the weather like in Boston today?"}]; - const functions = [ - { - "name": "get_current_weather", - "description": "Get the current weather in a given location", - "parameters": { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state, e.g. San Francisco, CA", - }, - "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, - }, - "required": ["location"], - }, - } - ]; - - const response = await openai.chat.completions.create({ - model: "gpt-3.5-turbo", - messages: messages, - functions: functions, - function_call: "auto", // auto is default, but we'll be explicit - }); - - console.log(response); - } - - main(); - response: &chat_completion_function_example | - { - "choices": [ - { - "finish_reason": "function_call", - "index": 0, - "message": { - "content": null, - "function_call": { - "arguments": "{\n \"location\": \"Boston, MA\"\n}", - "name": "get_current_weather" - }, - "role": "assistant" - } - } - ], - "created": 1694028367, - "model": "gpt-3.5-turbo-0613", - "object": "chat.completion", - "usage": { - "completion_tokens": 18, - "prompt_tokens": 82, - "total_tokens": 100 - } - } - /completions: - post: - operationId: createCompletion - tags: - - Completions - summary: Creates a completion for the provided prompt and parameters. - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/CreateCompletionRequest" - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/CreateCompletionResponse" - x-oaiMeta: - name: Create completion - returns: | - Returns a [completion](/docs/api-reference/completions/object) object, or a sequence of completion objects if the request is streamed. - legacy: true - examples: - - title: No streaming - request: - curl: | - curl https://api.openai.com/v1/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d '{ - "model": "VAR_model_id", - "prompt": "Say this is a test", - "max_tokens": 7, - "temperature": 0 - }' - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.Completion.create( - model="VAR_model_id", - prompt="Say this is a test", - max_tokens=7, - temperature=0 - ) - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const completion = await openai.completions.create({ - model: "VAR_model_id", - prompt: "Say this is a test.", - max_tokens: 7, - temperature: 0, - }); - - console.log(completion); - } - main(); - response: | - { - "id": "cmpl-uqkvlQyYK7bGYrRHQ0eXlWi7", - "object": "text_completion", - "created": 1589478378, - "model": "VAR_model_id", - "choices": [ - { - "text": "\n\nThis is indeed a test", - "index": 0, - "logprobs": null, - "finish_reason": "length" - } - ], - "usage": { - "prompt_tokens": 5, - "completion_tokens": 7, - "total_tokens": 12 - } - } - - title: Streaming - request: - curl: | - curl https://api.openai.com/v1/completions \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d '{ - "model": "VAR_model_id", - "prompt": "Say this is a test", - "max_tokens": 7, - "temperature": 0, - "stream": true - }' - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - for chunk in openai.Completion.create( - model="VAR_model_id", - prompt="Say this is a test", - max_tokens=7, - temperature=0, - stream=True - ): - print(chunk['choices'][0]['text']) - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const stream = await openai.completions.create({ - model: "VAR_model_id", - prompt: "Say this is a test.", - stream: true, - }); - - for await (const chunk of stream) { - console.log(chunk.choices[0].text) - } - } - main(); - response: | - { - "id": "cmpl-7iA7iJjj8V2zOkCGvWF2hAkDWBQZe", - "object": "text_completion", - "created": 1690759702, - "choices": [ - { - "text": "This", - "index": 0, - "logprobs": null, - "finish_reason": null - } - ], - "model": "gpt-3.5-turbo-instruct" - } - /edits: - post: - operationId: createEdit - deprecated: true - tags: - - Edits - summary: Creates a new edit for the provided input, instruction, and parameters. - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/CreateEditRequest" - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/CreateEditResponse" - x-oaiMeta: - name: Create edit - returns: | - Returns an [edit](/docs/api-reference/edits/object) object. - group: edits - examples: - request: - curl: | - curl https://api.openai.com/v1/edits \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d '{ - "model": "VAR_model_id", - "input": "What day of the wek is it?", - "instruction": "Fix the spelling mistakes" - }' - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.Edit.create( - model="VAR_model_id", - input="What day of the wek is it?", - instruction="Fix the spelling mistakes" - ) - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const edit = await openai.edits.create({ - model: "VAR_model_id", - input: "What day of the wek is it?", - instruction: "Fix the spelling mistakes.", - }); - - console.log(edit); - } - - main(); - response: &edit_example | - { - "object": "edit", - "created": 1589478378, - "choices": [ - { - "text": "What day of the week is it?", - "index": 0, - } - ], - "usage": { - "prompt_tokens": 25, - "completion_tokens": 32, - "total_tokens": 57 - } - } - /images/generations: post: operationId: createImage @@ -757,3861 +221,145 @@ paths: ] } - /embeddings: - post: - operationId: createEmbedding - tags: - - Embeddings - summary: Creates an embedding vector representing the input text. - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/CreateEmbeddingRequest" - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/CreateEmbeddingResponse" - x-oaiMeta: - name: Create embeddings - returns: A list of [embedding](/docs/api-reference/embeddings/object) objects. - examples: - request: - curl: | - curl https://api.openai.com/v1/embeddings \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "input": "The food was delicious and the waiter...", - "model": "text-embedding-ada-002", - "encoding_format": "float" - }' - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.Embedding.create( - model="text-embedding-ada-002", - input="The food was delicious and the waiter...", - encoding_format="float" - ) - node.js: |- - import OpenAI from "openai"; +components: - const openai = new OpenAI(); + securitySchemes: + ApiKeyAuth: + type: http + scheme: 'bearer' - async function main() { - const embedding = await openai.embeddings.create({ - model: "text-embedding-ada-002", - input: "The quick brown fox jumped over the lazy dog", - encoding_format: "float", - }); + schemas: + Error: + type: object + properties: + code: + type: string + nullable: true + message: + type: string + nullable: false + param: + type: string + nullable: true + type: + type: string + nullable: false + required: + - type + - message + - param + - code - console.log(embedding); - } + ErrorResponse: + type: object + properties: + error: + $ref: "#/components/schemas/Error" + required: + - error - main(); - response: | - { - "object": "list", - "data": [ - { - "object": "embedding", - "embedding": [ - 0.0023064255, - -0.009327292, - .... (1536 floats total for ada-002) - -0.0028842222, - ], - "index": 0 - } - ], - "model": "text-embedding-ada-002", - "usage": { - "prompt_tokens": 8, - "total_tokens": 8 - } - } + CreateImageRequest: + type: object + properties: + prompt: + description: A text description of the desired image(s). The maximum length is 1000 characters. + type: string + example: "A cute baby sea otter" + n: &images_n + type: integer + minimum: 1 + maximum: 10 + default: 1 + example: 1 + nullable: true + description: The number of images to generate. Must be between 1 and 10. + response_format: &images_response_format + type: string + enum: ["url", "b64_json"] + default: "url" + example: "url" + nullable: true + description: The format in which the generated images are returned. Must be one of `url` or `b64_json`. + size: &images_size + type: string + enum: ["256x256", "512x512", "1024x1024"] + default: "1024x1024" + example: "1024x1024" + nullable: true + description: The size of the generated images. Must be one of `256x256`, `512x512`, or `1024x1024`. + user: &end_user_param_configuration + type: string + example: user-1234 + description: | + A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. [Learn more](/docs/guides/safety-best-practices/end-user-ids). + required: + - prompt - /audio/transcriptions: - post: - operationId: createTranscription - tags: - - Audio - summary: Transcribes audio into the input language. - requestBody: - required: true - content: - multipart/form-data: - schema: - $ref: "#/components/schemas/CreateTranscriptionRequest" - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/CreateTranscriptionResponse" - x-oaiMeta: - name: Create transcription - returns: The transcriped text. - examples: - request: - curl: | - curl https://api.openai.com/v1/audio/transcriptions \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: multipart/form-data" \ - -F file="@/path/to/file/audio.mp3" \ - -F model="whisper-1" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - audio_file = open("audio.mp3", "rb") - transcript = openai.Audio.transcribe("whisper-1", audio_file) - node: |- - import fs from "fs"; - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const transcription = await openai.audio.transcriptions.create({ - file: fs.createReadStream("audio.mp3"), - model: "whisper-1", - }); - - console.log(transcription.text); - } - main(); - response: | - { - "text": "Imagine the wildest idea that you've ever had, and you're curious about how it might scale to something that's a 100, a 1,000 times bigger. This is a place where you can get to do that." - } - - /audio/translations: - post: - operationId: createTranslation - tags: - - Audio - summary: Translates audio into English. - requestBody: - required: true - content: - multipart/form-data: - schema: - $ref: "#/components/schemas/CreateTranslationRequest" - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/CreateTranslationResponse" - x-oaiMeta: - name: Create translation - returns: The translated text. - examples: - request: - curl: | - curl https://api.openai.com/v1/audio/translations \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: multipart/form-data" \ - -F file="@/path/to/file/german.m4a" \ - -F model="whisper-1" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - audio_file = open("german.m4a", "rb") - transcript = openai.Audio.translate("whisper-1", audio_file) - node: | - const { Configuration, OpenAIApi } = require("openai"); - const configuration = new Configuration({ - apiKey: process.env.OPENAI_API_KEY, - }); - const openai = new OpenAIApi(configuration); - const resp = await openai.createTranslation( - fs.createReadStream("audio.mp3"), - "whisper-1" - ); - response: | - { - "text": "Hello, my name is Wolfgang and I come from Germany. Where are you heading today?" - } - - /files: - get: - operationId: listFiles - tags: - - Files - summary: Returns a list of files that belong to the user's organization. - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/ListFilesResponse" - x-oaiMeta: - name: List files - returns: A list of [file](/docs/api-reference/files/object) objects. - examples: - request: - curl: | - curl https://api.openai.com/v1/files \ - -H "Authorization: Bearer $OPENAI_API_KEY" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.File.list() - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const list = await openai.files.list(); - - for await (const file of list) { - console.log(file); - } - } - - main(); - response: | - { - "data": [ - { - "id": "file-abc123", - "object": "file", - "bytes": 175, - "created_at": 1613677385, - "filename": "train.jsonl", - "purpose": "search" - }, - { - "id": "file-abc123", - "object": "file", - "bytes": 140, - "created_at": 1613779121, - "filename": "puppy.jsonl", - "purpose": "search" - } - ], - "object": "list" - } - post: - operationId: createFile - tags: - - Files - summary: | - Upload a file that can be used across various endpoints/features. Currently, the size of all the files uploaded by one organization can be up to 1 GB. Please [contact us](https://help.openai.com/) if you need to increase the storage limit. - requestBody: - required: true - content: - multipart/form-data: - schema: - $ref: "#/components/schemas/CreateFileRequest" - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/OpenAIFile" - x-oaiMeta: - name: Upload file - returns: The uploaded [file](/docs/api-reference/files/object) object. - examples: - request: - curl: | - curl https://api.openai.com/v1/files \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -F purpose="fine-tune" \ - -F file="@mydata.jsonl" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.File.create( - file=open("mydata.jsonl", "rb"), - purpose='fine-tune' - ) - node.js: |- - import fs from "fs"; - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const file = await openai.files.create({ - file: fs.createReadStream("mydata.jsonl"), - purpose: "fine-tune", - }); - - console.log(file); - } - - main(); - response: | - { - "id": "file-abc123", - "object": "file", - "bytes": 140, - "created_at": 1613779121, - "filename": "mydata.jsonl", - "purpose": "fine-tune", - "status": "uploaded" | "processed" | "pending" | "error" - } - - /files/{file_id}: - delete: - operationId: deleteFile - tags: - - Files - summary: Delete a file. - parameters: - - in: path - name: file_id - required: true - schema: - type: string - description: The ID of the file to use for this request. - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/DeleteFileResponse" - x-oaiMeta: - name: Delete file - returns: Deletion status. - examples: - request: - curl: | - curl https://api.openai.com/v1/files/file-abc123 \ - -X DELETE \ - -H "Authorization: Bearer $OPENAI_API_KEY" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.File.delete("file-abc123") - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const file = await openai.files.del("file-abc123"); - - console.log(file); - } - - main(); - response: | - { - "id": "file-abc123", - "object": "file", - "deleted": true - } - get: - operationId: retrieveFile - tags: - - Files - summary: Returns information about a specific file. - parameters: - - in: path - name: file_id - required: true - schema: - type: string - description: The ID of the file to use for this request. - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/OpenAIFile" - x-oaiMeta: - name: Retrieve file - returns: The [file](/docs/api-reference/files/object) object matching the specified ID. - examples: - request: - curl: | - curl https://api.openai.com/v1/files/file-abc123 \ - -H "Authorization: Bearer $OPENAI_API_KEY" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.File.retrieve("file-abc123") - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const file = await openai.files.retrieve("file-abc123"); - - console.log(file); - } - - main(); - response: | - { - "id": "file-abc123", - "object": "file", - "bytes": 140, - "created_at": 1613779657, - "filename": "mydata.jsonl", - "purpose": "fine-tune" - } - - /files/{file_id}/content: - get: - operationId: downloadFile - tags: - - Files - summary: Returns the contents of the specified file. - parameters: - - in: path - name: file_id - required: true - schema: - type: string - description: The ID of the file to use for this request. - responses: - "200": - description: OK - content: - application/json: - schema: - type: string - x-oaiMeta: - name: Retrieve file content - returns: The file content. - examples: - request: - curl: | - curl https://api.openai.com/v1/files/file-abc123/content \ - -H "Authorization: Bearer $OPENAI_API_KEY" > file.jsonl - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - content = openai.File.download("file-abc123") - node.js: | - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const file = await openai.files.retrieveContent("file-abc123"); - - console.log(file); - } - - main(); - - /fine_tuning/jobs: - post: - operationId: createFineTuningJob - tags: - - Fine-tuning - summary: | - Creates a job that fine-tunes a specified model from a given dataset. - - Response includes details of the enqueued job including job status and the name of the fine-tuned models once complete. - - [Learn more about fine-tuning](/docs/guides/fine-tuning) - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/CreateFineTuningJobRequest" - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/FineTuningJob" - x-oaiMeta: - name: Create fine-tuning job - returns: A [fine-tuning.job](/docs/api-reference/fine-tuning/object) object. - examples: - - title: No hyperparameters - request: - curl: | - curl https://api.openai.com/v1/fine_tuning/jobs \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d '{ - "training_file": "file-abc123", - "model": "gpt-3.5-turbo" - }' - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.FineTuningJob.create(training_file="file-abc123", model="gpt-3.5-turbo") - node.js: | - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const fineTune = await openai.fineTuning.jobs.create({ - training_file: "file-abc123" - }); - - console.log(fineTune); - } - - main(); - response: | - { - "object": "fine_tuning.job", - "id": "ftjob-abc123", - "model": "gpt-3.5-turbo-0613", - "created_at": 1614807352, - "fine_tuned_model": null, - "organization_id": "org-123", - "result_files": [], - "status": "queued", - "validation_file": null, - "training_file": "file-abc123", - } - - title: Hyperparameters - request: - curl: | - curl https://api.openai.com/v1/fine_tuning/jobs \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d '{ - "training_file": "file-abc123", - "model": "gpt-3.5-turbo", - "hyperparameters": { - "n_epochs": 2 - } - }' - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.FineTuningJob.create(training_file="file-abc123", model="gpt-3.5-turbo", hyperparameters={"n_epochs":2}) - node.js: | - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const fineTune = await openai.fineTuning.jobs.create({ - training_file: "file-abc123", - model: "gpt-3.5-turbo", - hyperparameters: { n_epochs: 2 } - }); - - console.log(fineTune); - } - - main(); - response: | - { - "object": "fine_tuning.job", - "id": "ftjob-abc123", - "model": "gpt-3.5-turbo-0613", - "created_at": 1614807352, - "fine_tuned_model": null, - "organization_id": "org-123", - "result_files": [], - "status": "queued", - "validation_file": null, - "training_file": "file-abc123", - "hyperparameters":{"n_epochs":2}, - } - - title: Validation file - request: - curl: | - curl https://api.openai.com/v1/fine_tuning/jobs \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d '{ - "training_file": "file-abc123", - "validation_file": "file-abc123", - "model": "gpt-3.5-turbo" - }' - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.FineTuningJob.create(training_file="file-abc123", validation_file="file-abc123", model="gpt-3.5-turbo") - node.js: | - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const fineTune = await openai.fineTuning.jobs.create({ - training_file: "file-abc123", - validation_file: "file-abc123" - }); - - console.log(fineTune); - } - - main(); - response: | - { - "object": "fine_tuning.job", - "id": "ftjob-abc123", - "model": "gpt-3.5-turbo-0613", - "created_at": 1614807352, - "fine_tuned_model": null, - "organization_id": "org-123", - "result_files": [], - "status": "queued", - "validation_file": "file-abc123", - "training_file": "file-abc123", - } - get: - operationId: listPaginatedFineTuningJobs - tags: - - Fine-tuning - summary: | - List your organization's fine-tuning jobs - parameters: - - name: after - in: query - description: Identifier for the last job from the previous pagination request. - required: false - schema: - type: string - - name: limit - in: query - description: Number of fine-tuning jobs to retrieve. - required: false - schema: - type: integer - default: 20 - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/ListPaginatedFineTuningJobsResponse" - x-oaiMeta: - name: List fine-tuning jobs - returns: A list of paginated [fine-tuning job](/docs/api-reference/fine-tuning/object) objects. - examples: - request: - curl: | - curl https://api.openai.com/v1/fine_tuning/jobs?limit=2 \ - -H "Authorization: Bearer $OPENAI_API_KEY" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.FineTuningJob.list() - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const list = await openai.fineTuning.jobs.list(); - - for await (const fineTune of list) { - console.log(fineTune); - } - } - - main(); - response: | - { - "object": "list", - "data": [ - { - "object": "fine_tuning.job.event", - "id": "ft-event-TjX0lMfOniCZX64t9PUQT5hn", - "created_at": 1689813489, - "level": "warn", - "message": "Fine tuning process stopping due to job cancellation", - "data": null, - "type": "message" - }, - { ... }, - { ... } - ], "has_more": true - } - /fine_tuning/jobs/{fine_tuning_job_id}: - get: - operationId: retrieveFineTuningJob - tags: - - Fine-tuning - summary: | - Get info about a fine-tuning job. - - [Learn more about fine-tuning](/docs/guides/fine-tuning) - parameters: - - in: path - name: fine_tuning_job_id - required: true - schema: - type: string - example: ft-AF1WoRqd3aJAHsqc9NY7iL8F - description: | - The ID of the fine-tuning job. - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/FineTuningJob" - x-oaiMeta: - name: Retrieve fine-tuning job - returns: The [fine-tuning](/docs/api-reference/fine-tunes/object) object with the given ID. - examples: - request: - curl: | - curl https://api.openai.com/v1/fine_tuning/jobs/ft-AF1WoRqd3aJAHsqc9NY7iL8F \ - -H "Authorization: Bearer $OPENAI_API_KEY" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.FineTuningJob.retrieve("ftjob-abc123") - node.js: | - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const fineTune = await openai.fineTuning.jobs.retrieve("ftjob-abc123"); - - console.log(fineTune); - } - - main(); - response: &fine_tuning_example | - { - "object": "fine_tuning.job", - "id": "ftjob-abc123", - "model": "davinci-002", - "created_at": 1692661014, - "finished_at": 1692661190, - "fine_tuned_model": "ft:davinci-002:my-org:custom_suffix:7q8mpxmy", - "organization_id": "org-123", - "result_files": [ - "file-abc123" - ], - "status": "succeeded", - "validation_file": null, - "training_file": "file-abc123", - "hyperparameters": { - "n_epochs": 4, - }, - "trained_tokens": 5768 - } - /fine_tuning/jobs/{fine_tuning_job_id}/events: - get: - operationId: listFineTuningEvents - tags: - - Fine-tuning - summary: | - Get status updates for a fine-tuning job. - parameters: - - in: path - name: fine_tuning_job_id - required: true - schema: - type: string - example: ft-AF1WoRqd3aJAHsqc9NY7iL8F - description: | - The ID of the fine-tuning job to get events for. - - name: after - in: query - description: Identifier for the last event from the previous pagination request. - required: false - schema: - type: string - - name: limit - in: query - description: Number of events to retrieve. - required: false - schema: - type: integer - default: 20 - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/ListFineTuningJobEventsResponse" - x-oaiMeta: - name: List fine-tuning events - returns: A list of fine-tuning event objects. - examples: - request: - curl: | - curl https://api.openai.com/v1/fine_tuning/jobs/ftjob-abc123/events \ - -H "Authorization: Bearer $OPENAI_API_KEY" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.FineTuningJob.list_events(id="ftjob-abc123", limit=2) - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const list = await openai.fineTuning.list_events(id="ftjob-abc123", limit=2); - - for await (const fineTune of list) { - console.log(fineTune); - } - } - - main(); - response: | - { - "object": "list", - "data": [ - { - "object": "fine_tuning.job.event", - "id": "ft-event-ddTJfwuMVpfLXseO0Am0Gqjm", - "created_at": 1692407401, - "level": "info", - "message": "Fine tuning job successfully completed", - "data": null, - "type": "message" - }, - { - "object": "fine_tuning.job.event", - "id": "ft-event-tyiGuB72evQncpH87xe505Sv", - "created_at": 1692407400, - "level": "info", - "message": "New fine-tuned model created: ft:gpt-3.5-turbo:openai::7p4lURel", - "data": null, - "type": "message" - } - ], - "has_more": true - } - - /fine_tuning/jobs/{fine_tuning_job_id}/cancel: - post: - operationId: cancelFineTuningJob - tags: - - Fine-tuning - summary: | - Immediately cancel a fine-tune job. - parameters: - - in: path - name: fine_tuning_job_id - required: true - schema: - type: string - example: ft-AF1WoRqd3aJAHsqc9NY7iL8F - description: | - The ID of the fine-tuning job to cancel. - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/FineTuningJob" - x-oaiMeta: - name: Cancel fine-tuning - returns: The cancelled [fine-tuning](/docs/api-reference/fine-tuning/object) object. - examples: - request: - curl: | - curl -X POST https://api.openai.com/v1/fine_tuning/jobs/ftjob-abc123/cancel \ - -H "Authorization: Bearer $OPENAI_API_KEY" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.FineTuningJob.cancel("ftjob-abc123") - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const fineTune = await openai.fineTuning.jobs.cancel("ftjob-abc123"); - - console.log(fineTune); - } - main(); - response: | - { - "object": "fine_tuning.job", - "id": "ftjob-abc123", - "model": "gpt-3.5-turbo-0613", - "created_at": 1689376978, - "fine_tuned_model": null, - "organization_id": "org-123", - "result_files": [], - "hyperparameters": { - "n_epochs": "auto" - }, - "status": "cancelled", - "validation_file": "file-abc123", - "training_file": "file-abc123" - } - - /fine-tunes: - post: - operationId: createFineTune - deprecated: true - tags: - - Fine-tunes - summary: | - Creates a job that fine-tunes a specified model from a given dataset. - - Response includes details of the enqueued job including job status and the name of the fine-tuned models once complete. - - [Learn more about fine-tuning](/docs/guides/legacy-fine-tuning) - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/CreateFineTuneRequest" - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/FineTune" - x-oaiMeta: - name: Create fine-tune - returns: A [fine-tune](/docs/api-reference/fine-tunes/object) object. - examples: - request: - curl: | - curl https://api.openai.com/v1/fine-tunes \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d '{ - "training_file": "file-abc123" - }' - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.FineTune.create(training_file="file-abc123") - node.js: | - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const fineTune = await openai.fineTunes.create({ - training_file: "file-abc123" - }); - - console.log(fineTune); - } - - main(); - response: | - { - "id": "ft-AF1WoRqd3aJAHsqc9NY7iL8F", - "object": "fine-tune", - "model": "curie", - "created_at": 1614807352, - "events": [ - { - "object": "fine-tune-event", - "created_at": 1614807352, - "level": "info", - "message": "Job enqueued. Waiting for jobs ahead to complete. Queue number: 0." - } - ], - "fine_tuned_model": null, - "hyperparams": { - "batch_size": 4, - "learning_rate_multiplier": 0.1, - "n_epochs": 4, - "prompt_loss_weight": 0.1, - }, - "organization_id": "org-123", - "result_files": [], - "status": "pending", - "validation_files": [], - "training_files": [ - { - "id": "file-abc123", - "object": "file", - "bytes": 1547276, - "created_at": 1610062281, - "filename": "my-data-train.jsonl", - "purpose": "fine-tune-train" - } - ], - "updated_at": 1614807352, - } - get: - operationId: listFineTunes - deprecated: true - tags: - - Fine-tunes - summary: | - List your organization's fine-tuning jobs - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/ListFineTunesResponse" - x-oaiMeta: - name: List fine-tunes - returns: A list of [fine-tune](/docs/api-reference/fine-tunes/object) objects. - examples: - request: - curl: | - curl https://api.openai.com/v1/fine-tunes \ - -H "Authorization: Bearer $OPENAI_API_KEY" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.FineTune.list() - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const list = await openai.fineTunes.list(); - - for await (const fineTune of list) { - console.log(fineTune); - } - } - - main(); - response: | - { - "object": "list", - "data": [ - { - "id": "ft-AF1WoRqd3aJAHsqc9NY7iL8F", - "object": "fine-tune", - "model": "curie", - "created_at": 1614807352, - "fine_tuned_model": null, - "hyperparams": { ... }, - "organization_id": "org-123", - "result_files": [], - "status": "pending", - "validation_files": [], - "training_files": [ { ... } ], - "updated_at": 1614807352, - }, - { ... }, - { ... } - ] - } - - /fine-tunes/{fine_tune_id}: - get: - operationId: retrieveFineTune - deprecated: true - tags: - - Fine-tunes - summary: | - Gets info about the fine-tune job. - - [Learn more about fine-tuning](/docs/guides/legacy-fine-tuning) - parameters: - - in: path - name: fine_tune_id - required: true - schema: - type: string - example: ft-AF1WoRqd3aJAHsqc9NY7iL8F - description: | - The ID of the fine-tune job - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/FineTune" - x-oaiMeta: - name: Retrieve fine-tune - returns: The [fine-tune](/docs/api-reference/fine-tunes/object) object with the given ID. - examples: - request: - curl: | - curl https://api.openai.com/v1/fine-tunes/ft-AF1WoRqd3aJAHsqc9NY7iL8F \ - -H "Authorization: Bearer $OPENAI_API_KEY" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.FineTune.retrieve(id="ft-AF1WoRqd3aJAHsqc9NY7iL8F") - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const fineTune = await openai.fineTunes.retrieve("ft-AF1WoRqd3aJAHsqc9NY7iL8F"); - - console.log(fineTune); - } - - main(); - response: &fine_tune_example | - { - "id": "ft-AF1WoRqd3aJAHsqc9NY7iL8F", - "object": "fine-tune", - "model": "curie", - "created_at": 1614807352, - "events": [ - { - "object": "fine-tune-event", - "created_at": 1614807352, - "level": "info", - "message": "Job enqueued. Waiting for jobs ahead to complete. Queue number: 0." - }, - { - "object": "fine-tune-event", - "created_at": 1614807356, - "level": "info", - "message": "Job started." - }, - { - "object": "fine-tune-event", - "created_at": 1614807861, - "level": "info", - "message": "Uploaded snapshot: curie:ft-acmeco-2021-03-03-21-44-20." - }, - { - "object": "fine-tune-event", - "created_at": 1614807864, - "level": "info", - "message": "Uploaded result files: file-abc123." - }, - { - "object": "fine-tune-event", - "created_at": 1614807864, - "level": "info", - "message": "Job succeeded." - } - ], - "fine_tuned_model": "curie:ft-acmeco-2021-03-03-21-44-20", - "hyperparams": { - "batch_size": 4, - "learning_rate_multiplier": 0.1, - "n_epochs": 4, - "prompt_loss_weight": 0.1, - }, - "organization_id": "org-123", - "result_files": [ - { - "id": "file-abc123", - "object": "file", - "bytes": 81509, - "created_at": 1614807863, - "filename": "compiled_results.csv", - "purpose": "fine-tune-results" - } - ], - "status": "succeeded", - "validation_files": [], - "training_files": [ - { - "id": "file-abc123", - "object": "file", - "bytes": 1547276, - "created_at": 1610062281, - "filename": "my-data-train.jsonl", - "purpose": "fine-tune-train" - } - ], - "updated_at": 1614807865, - } - - /fine-tunes/{fine_tune_id}/cancel: - post: - operationId: cancelFineTune - deprecated: true - tags: - - Fine-tunes - summary: | - Immediately cancel a fine-tune job. - parameters: - - in: path - name: fine_tune_id - required: true - schema: - type: string - example: ft-AF1WoRqd3aJAHsqc9NY7iL8F - description: | - The ID of the fine-tune job to cancel - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/FineTune" - x-oaiMeta: - name: Cancel fine-tune - returns: The cancelled [fine-tune](/docs/api-reference/fine-tunes/object) object. - examples: - request: - curl: | - curl https://api.openai.com/v1/fine-tunes/ft-AF1WoRqd3aJAHsqc9NY7iL8F/cancel \ - -H "Authorization: Bearer $OPENAI_API_KEY" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.FineTune.cancel(id="ft-AF1WoRqd3aJAHsqc9NY7iL8F") - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const fineTune = await openai.fineTunes.cancel("ft-AF1WoRqd3aJAHsqc9NY7iL8F"); - - console.log(fineTune); - } - main(); - response: | - { - "id": "ft-xhrpBbvVUzYGo8oUO1FY4nI7", - "object": "fine-tune", - "model": "curie", - "created_at": 1614807770, - "events": [ { ... } ], - "fine_tuned_model": null, - "hyperparams": { ... }, - "organization_id": "org-123", - "result_files": [], - "status": "cancelled", - "validation_files": [], - "training_files": [ - { - "id": "file-abc123", - "object": "file", - "bytes": 1547276, - "created_at": 1610062281, - "filename": "my-data-train.jsonl", - "purpose": "fine-tune-train" - } - ], - "updated_at": 1614807789, - } - - /fine-tunes/{fine_tune_id}/events: - get: - operationId: listFineTuneEvents - deprecated: true - tags: - - Fine-tunes - summary: | - Get fine-grained status updates for a fine-tune job. - parameters: - - in: path - name: fine_tune_id - required: true - schema: - type: string - example: ft-AF1WoRqd3aJAHsqc9NY7iL8F - description: | - The ID of the fine-tune job to get events for. - - in: query - name: stream - required: false - schema: - type: boolean - default: false - description: | - Whether to stream events for the fine-tune job. If set to true, - events will be sent as data-only - [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) - as they become available. The stream will terminate with a - `data: [DONE]` message when the job is finished (succeeded, cancelled, - or failed). - - If set to false, only events generated so far will be returned. - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/ListFineTuneEventsResponse" - x-oaiMeta: - name: List fine-tune events - returns: A list of fine-tune event objects. - examples: - request: - curl: | - curl https://api.openai.com/v1/fine-tunes/ft-AF1WoRqd3aJAHsqc9NY7iL8F/events \ - -H "Authorization: Bearer $OPENAI_API_KEY" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.FineTune.list_events(id="ft-AF1WoRqd3aJAHsqc9NY7iL8F") - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const fineTune = await openai.fineTunes.listEvents("ft-AF1WoRqd3aJAHsqc9NY7iL8F"); - - console.log(fineTune); - } - main(); - response: | - { - "object": "list", - "data": [ - { - "object": "fine-tune-event", - "created_at": 1614807352, - "level": "info", - "message": "Job enqueued. Waiting for jobs ahead to complete. Queue number: 0." - }, - { - "object": "fine-tune-event", - "created_at": 1614807356, - "level": "info", - "message": "Job started." - }, - { - "object": "fine-tune-event", - "created_at": 1614807861, - "level": "info", - "message": "Uploaded snapshot: curie:ft-acmeco-2021-03-03-21-44-20." - }, - { - "object": "fine-tune-event", - "created_at": 1614807864, - "level": "info", - "message": "Uploaded result files: file-abc123" - }, - { - "object": "fine-tune-event", - "created_at": 1614807864, - "level": "info", - "message": "Job succeeded." - } - ] - } - - /models: - get: - operationId: listModels - tags: - - Models - summary: Lists the currently available models, and provides basic information about each one such as the owner and availability. - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/ListModelsResponse" - x-oaiMeta: - name: List models - returns: A list of [model](/docs/api-reference/models/object) objects. - examples: - request: - curl: | - curl https://api.openai.com/v1/models \ - -H "Authorization: Bearer $OPENAI_API_KEY" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.Model.list() - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const list = await openai.models.list(); - - for await (const model of list) { - console.log(model); - } - } - main(); - response: | - { - "object": "list", - "data": [ - { - "id": "model-id-0", - "object": "model", - "created": 1686935002, - "owned_by": "organization-owner" - }, - { - "id": "model-id-1", - "object": "model", - "created": 1686935002, - "owned_by": "organization-owner", - }, - { - "id": "model-id-2", - "object": "model", - "created": 1686935002, - "owned_by": "openai" - }, - ], - "object": "list" - } - - /models/{model}: - get: - operationId: retrieveModel - tags: - - Models - summary: Retrieves a model instance, providing basic information about the model such as the owner and permissioning. - parameters: - - in: path - name: model - required: true - schema: - type: string - # ideally this will be an actual ID, so this will always work from browser - example: gpt-3.5-turbo - description: The ID of the model to use for this request - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Model" - x-oaiMeta: - name: Retrieve model - returns: The [model](/docs/api-reference/models/object) object matching the specified ID. - examples: - request: - curl: | - curl https://api.openai.com/v1/models/VAR_model_id \ - -H "Authorization: Bearer $OPENAI_API_KEY" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.Model.retrieve("VAR_model_id") - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const model = await openai.models.retrieve("gpt-3.5-turbo"); - - console.log(model); - } - - main(); - response: &retrieve_model_response | - { - "id": "VAR_model_id", - "object": "model", - "created": 1686935002, - "owned_by": "openai" - } - delete: - operationId: deleteModel - tags: - - Models - summary: Delete a fine-tuned model. You must have the Owner role in your organization to delete a model. - parameters: - - in: path - name: model - required: true - schema: - type: string - example: ft:gpt-3.5-turbo:acemeco:suffix:abc123 - description: The model to delete - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/DeleteModelResponse" - x-oaiMeta: - name: Delete fine-tune model - returns: Deletion status. - examples: - request: - curl: | - curl https://api.openai.com/v1/models/ft:gpt-3.5-turbo:acemeco:suffix:abc123 \ - -X DELETE \ - -H "Authorization: Bearer $OPENAI_API_KEY" - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.Model.delete("ft:gpt-3.5-turbo:acemeco:suffix:abc123") - node.js: |- - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const model = await openai.models.del("ft:gpt-3.5-turbo:acemeco:suffix:abc123"); - - console.log(model); - } - main(); - response: | - { - "id": "ft:gpt-3.5-turbo:acemeco:suffix:abc123", - "object": "model", - "deleted": true - } - - /moderations: - post: - operationId: createModeration - tags: - - Moderations - summary: Classifies if text violates OpenAI's Content Policy - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/CreateModerationRequest" - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/CreateModerationResponse" - x-oaiMeta: - name: Create moderation - returns: A [moderation](/docs/api-reference/moderations/object) object. - examples: - request: - curl: | - curl https://api.openai.com/v1/moderations \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -d '{ - "input": "I want to kill them." - }' - python: | - import os - import openai - openai.api_key = os.getenv("OPENAI_API_KEY") - openai.Moderation.create( - input="I want to kill them.", - ) - node.js: | - import OpenAI from "openai"; - - const openai = new OpenAI(); - - async function main() { - const moderation = await openai.moderations.create({ input: "I want to kill them." }); - - console.log(moderation); - } - main(); - response: &moderation_example | - { - "id": "modr-XXXXX", - "model": "text-moderation-005", - "results": [ - { - "flagged": true, - "categories": { - "sexual": false, - "hate": false, - "harassment": false, - "self-harm": false, - "sexual/minors": false, - "hate/threatening": false, - "violence/graphic": false, - "self-harm/intent": false, - "self-harm/instructions": false, - "harassment/threatening": true, - "violence": true, - }, - "category_scores": { - "sexual": 1.2282071e-06, - "hate": 0.010696256, - "harassment": 0.29842457, - "self-harm": 1.5236925e-08, - "sexual/minors": 5.7246268e-08, - "hate/threatening": 0.0060676364, - "violence/graphic": 4.435014e-06, - "self-harm/intent": 8.098441e-10, - "self-harm/instructions": 2.8498655e-11, - "harassment/threatening": 0.63055265, - "violence": 0.99011886, - } - } - ] - } - -components: - - securitySchemes: - ApiKeyAuth: - type: http - scheme: 'bearer' - - schemas: - Error: - type: object - properties: - code: - type: string - nullable: true - message: - type: string - nullable: false - param: - type: string - nullable: true - type: - type: string - nullable: false - required: - - type - - message - - param - - code - - ErrorResponse: - type: object - properties: - error: - $ref: "#/components/schemas/Error" - required: - - error - - ListModelsResponse: - type: object - properties: - object: - type: string - data: - type: array - items: - $ref: "#/components/schemas/Model" - required: - - object - - data - - DeleteModelResponse: - type: object - properties: - id: - type: string - deleted: - type: boolean - object: - type: string - required: - - id - - object - - deleted - - CreateCompletionRequest: - type: object - properties: - model: - description: &model_description | - ID of the model to use. You can use the [List models](/docs/api-reference/models/list) API to see all of your available models, or see our [Model overview](/docs/models/overview) for descriptions of them. - anyOf: - - type: string - - type: string - enum: - [ - "babbage-002", - "davinci-002", - "gpt-3.5-turbo-instruct", - "text-davinci-003", - "text-davinci-002", - "text-davinci-001", - "code-davinci-002", - "text-curie-001", - "text-babbage-001", - "text-ada-001", - ] - x-oaiTypeLabel: string - prompt: - description: &completions_prompt_description | - The prompt(s) to generate completions for, encoded as a string, array of strings, array of tokens, or array of token arrays. - - Note that <|endoftext|> is the document separator that the model sees during training, so if a prompt is not specified the model will generate as if from the beginning of a new document. - default: "<|endoftext|>" - nullable: true - oneOf: - - type: string - default: "" - example: "This is a test." - - type: array - items: - type: string - default: "" - example: "This is a test." - - type: array - minItems: 1 - items: - type: integer - example: "[1212, 318, 257, 1332, 13]" - - type: array - minItems: 1 - items: - type: array - minItems: 1 - items: - type: integer - example: "[[1212, 318, 257, 1332, 13]]" - best_of: - type: integer - default: 1 - minimum: 0 - maximum: 20 - nullable: true - description: &completions_best_of_description | - Generates `best_of` completions server-side and returns the "best" (the one with the highest log probability per token). Results cannot be streamed. - - When used with `n`, `best_of` controls the number of candidate completions and `n` specifies how many to return – `best_of` must be greater than `n`. - - **Note:** Because this parameter generates many completions, it can quickly consume your token quota. Use carefully and ensure that you have reasonable settings for `max_tokens` and `stop`. - echo: - type: boolean - default: false - nullable: true - description: &completions_echo_description > - Echo back the prompt in addition to the completion - frequency_penalty: - type: number - default: 0 - minimum: -2 - maximum: 2 - nullable: true - description: &completions_frequency_penalty_description | - Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim. - - [See more information about frequency and presence penalties.](/docs/guides/gpt/parameter-details) - logit_bias: &completions_logit_bias - type: object - x-oaiTypeLabel: map - default: null - nullable: true - additionalProperties: - type: integer - description: &completions_logit_bias_description | - Modify the likelihood of specified tokens appearing in the completion. - - Accepts a json object that maps tokens (specified by their token ID in the GPT tokenizer) to an associated bias value from -100 to 100. You can use this [tokenizer tool](/tokenizer?view=bpe) (which works for both GPT-2 and GPT-3) to convert text to token IDs. Mathematically, the bias is added to the logits generated by the model prior to sampling. The exact effect will vary per model, but values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token. - - As an example, you can pass `{"50256": -100}` to prevent the <|endoftext|> token from being generated. - logprobs: &completions_logprobs_configuration - type: integer - minimum: 0 - maximum: 5 - default: null - nullable: true - description: &completions_logprobs_description | - Include the log probabilities on the `logprobs` most likely tokens, as well the chosen tokens. For example, if `logprobs` is 5, the API will return a list of the 5 most likely tokens. The API will always return the `logprob` of the sampled token, so there may be up to `logprobs+1` elements in the response. - - The maximum value for `logprobs` is 5. - max_tokens: - type: integer - minimum: 0 - default: 16 - example: 16 - nullable: true - description: &completions_max_tokens_description | - The maximum number of [tokens](/tokenizer) to generate in the completion. - - The token count of your prompt plus `max_tokens` cannot exceed the model's context length. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) for counting tokens. - n: - type: integer - minimum: 1 - maximum: 128 - default: 1 - example: 1 - nullable: true - description: &completions_completions_description | - How many completions to generate for each prompt. - - **Note:** Because this parameter generates many completions, it can quickly consume your token quota. Use carefully and ensure that you have reasonable settings for `max_tokens` and `stop`. - presence_penalty: - type: number - default: 0 - minimum: -2 - maximum: 2 - nullable: true - description: &completions_presence_penalty_description | - Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics. - - [See more information about frequency and presence penalties.](/docs/guides/gpt/parameter-details) - stop: - description: &completions_stop_description > - Up to 4 sequences where the API will stop generating further tokens. The returned text will not contain the stop sequence. - default: null - nullable: true - oneOf: - - type: string - default: <|endoftext|> - example: "\n" - nullable: true - - type: array - minItems: 1 - maxItems: 4 - items: - type: string - example: '["\n"]' - stream: - description: > - Whether to stream back partial progress. If set, tokens will be sent as data-only [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) - as they become available, with the stream terminated by a `data: [DONE]` message. [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions). - type: boolean - nullable: true - default: false - suffix: - description: The suffix that comes after a completion of inserted text. - default: null - nullable: true - type: string - example: "test." - temperature: - type: number - minimum: 0 - maximum: 2 - default: 1 - example: 1 - nullable: true - description: &completions_temperature_description | - What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. - - We generally recommend altering this or `top_p` but not both. - top_p: - type: number - minimum: 0 - maximum: 1 - default: 1 - example: 1 - nullable: true - description: &completions_top_p_description | - An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. - - We generally recommend altering this or `temperature` but not both. - user: &end_user_param_configuration - type: string - example: user-1234 - description: | - A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. [Learn more](/docs/guides/safety-best-practices/end-user-ids). - required: - - model - - prompt - - CreateCompletionResponse: - type: object - description: | - Represents a completion response from the API. Note: both the streamed and non-streamed response objects share the same shape (unlike the chat endpoint). - properties: - id: - type: string - description: A unique identifier for the completion. - choices: - type: array - description: The list of completion choices the model generated for the input prompt. - items: - type: object - required: - - finish_reason - - index - - logprobs - - text - properties: - finish_reason: - type: string - description: &completion_finish_reason_description | - The reason the model stopped generating tokens. This will be `stop` if the model hit a natural stop point or a provided stop sequence, - `length` if the maximum number of tokens specified in the request was reached, - or `content_filter` if content was omitted due to a flag from our content filters. - enum: ["stop", "length", "content_filter"] - index: - type: integer - logprobs: - type: object - nullable: true - properties: - text_offset: - type: array - items: - type: integer - token_logprobs: - type: array - items: - type: number - tokens: - type: array - items: - type: string - top_logprobs: - type: array - items: - type: object - additionalProperties: - type: integer - text: - type: string - created: - type: integer - description: The Unix timestamp (in seconds) of when the completion was created. - model: - type: string - description: The model used for completion. - object: - type: string - description: The object type, which is always "text_completion" - usage: - $ref: "#/components/schemas/CompletionUsage" - required: - - id - - object - - created - - model - - choices - x-oaiMeta: - name: The completion object - legacy: true - example: | - { - "id": "cmpl-uqkvlQyYK7bGYrRHQ0eXlWi7", - "object": "text_completion", - "created": 1589478378, - "model": "gpt-3.5-turbo", - "choices": [ - { - "text": "\n\nThis is indeed a test", - "index": 0, - "logprobs": null, - "finish_reason": "length" - } - ], - "usage": { - "prompt_tokens": 5, - "completion_tokens": 7, - "total_tokens": 12 - } - } - - - ChatCompletionRequestMessage: - type: object - properties: - content: - type: string - nullable: true - description: The contents of the message. `content` is required for all messages, and may be null for assistant messages with function calls. - function_call: - type: object - description: The name and arguments of a function that should be called, as generated by the model. - properties: - arguments: - type: string - description: The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function. - name: - type: string - description: The name of the function to call. - required: - - arguments - - name - name: - type: string - description: The name of the author of this message. `name` is required if role is `function`, and it should be the name of the function whose response is in the `content`. May contain a-z, A-Z, 0-9, and underscores, with a maximum length of 64 characters. - role: - type: string - enum: ["system", "user", "assistant", "function"] - description: The role of the messages author. One of `system`, `user`, `assistant`, or `function`. - required: - - content - - role - - ChatCompletionFunctionParameters: - type: object - description: "The parameters the functions accepts, described as a JSON Schema object. See the [guide](/docs/guides/gpt/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format.\n\nTo describe a function that accepts no parameters, provide the value `{\"type\": \"object\", \"properties\": {}}`." - additionalProperties: true - - ChatCompletionFunctions: - type: object - properties: - description: - type: string - description: A description of what the function does, used by the model to choose when and how to call the function. - name: - type: string - description: The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64. - parameters: - $ref: "#/components/schemas/ChatCompletionFunctionParameters" - required: - - name - - parameters - - ChatCompletionFunctionCallOption: - type: object - properties: - name: - type: string - description: The name of the function to call. - required: - - name - - ChatCompletionResponseMessage: - type: object - description: A chat completion message generated by the model. - properties: - content: - type: string - description: The contents of the message. - nullable: true - function_call: - type: object - description: The name and arguments of a function that should be called, as generated by the model. - properties: - arguments: - type: string - description: The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function. - name: - type: string - description: The name of the function to call. - required: - - name - - arguments - role: - type: string - enum: ["system", "user", "assistant", "function"] - description: The role of the author of this message. - required: - - role - - content - - ChatCompletionStreamResponseDelta: - type: object - description: A chat completion delta generated by streamed model responses. - properties: - content: - type: string - description: The contents of the chunk message. - nullable: true - function_call: - type: object - description: The name and arguments of a function that should be called, as generated by the model. - properties: - arguments: - type: string - description: The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function. - name: - type: string - description: The name of the function to call. - role: - type: string - enum: ["system", "user", "assistant", "function"] - description: The role of the author of this message. - - CreateChatCompletionRequest: - type: object - properties: - messages: - description: A list of messages comprising the conversation so far. [Example Python code](https://cookbook.openai.com/examples/how_to_format_inputs_to_chatgpt_models). - type: array - minItems: 1 - items: - $ref: "#/components/schemas/ChatCompletionRequestMessage" - model: - description: ID of the model to use. See the [model endpoint compatibility](/docs/models/model-endpoint-compatibility) table for details on which models work with the Chat API. - example: "gpt-3.5-turbo" - anyOf: - - type: string - - type: string - enum: - [ - "gpt-4", - "gpt-4-0314", - "gpt-4-0613", - "gpt-4-32k", - "gpt-4-32k-0314", - "gpt-4-32k-0613", - "gpt-3.5-turbo", - "gpt-3.5-turbo-16k", - "gpt-3.5-turbo-0301", - "gpt-3.5-turbo-0613", - "gpt-3.5-turbo-16k-0613", - ] - x-oaiTypeLabel: string - frequency_penalty: - type: number - default: 0 - minimum: -2 - maximum: 2 - nullable: true - description: *completions_frequency_penalty_description - function_call: - description: > - Controls how the model calls functions. "none" means the model will not call a function and instead generates a message. "auto" means the model can pick between generating a message or calling a function. Specifying a particular function via `{"name": "my_function"}` forces the model to call that function. "none" is the default when no functions are present. "auto" is the default if functions are present. - oneOf: - - type: string - enum: [none, auto] - - $ref: "#/components/schemas/ChatCompletionFunctionCallOption" - functions: - description: A list of functions the model may generate JSON inputs for. - type: array - minItems: 1 - maxItems: 128 - items: - $ref: "#/components/schemas/ChatCompletionFunctions" - logit_bias: - type: object - x-oaiTypeLabel: map - default: null - nullable: true - additionalProperties: - type: integer - description: | - Modify the likelihood of specified tokens appearing in the completion. - - Accepts a json object that maps tokens (specified by their token ID in the tokenizer) to an associated bias value from -100 to 100. Mathematically, the bias is added to the logits generated by the model prior to sampling. The exact effect will vary per model, but values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token. - max_tokens: - description: | - The maximum number of [tokens](/tokenizer) to generate in the chat completion. - - The total length of input tokens and generated tokens is limited by the model's context length. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) for counting tokens. - default: inf - type: integer - nullable: true - n: - type: integer - minimum: 1 - maximum: 128 - default: 1 - example: 1 - nullable: true - description: How many chat completion choices to generate for each input message. - presence_penalty: - type: number - default: 0 - minimum: -2 - maximum: 2 - nullable: true - description: *completions_presence_penalty_description - stop: - description: | - Up to 4 sequences where the API will stop generating further tokens. - default: null - oneOf: - - type: string - nullable: true - - type: array - minItems: 1 - maxItems: 4 - items: - type: string - stream: - description: > - If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) - as they become available, with the stream terminated by a `data: [DONE]` message. [Example Python code](https://cookbook.openai.com/examples/how_to_stream_completions). - type: boolean - nullable: true - default: false - temperature: - type: number - minimum: 0 - maximum: 2 - default: 1 - example: 1 - nullable: true - description: *completions_temperature_description - top_p: - type: number - minimum: 0 - maximum: 1 - default: 1 - example: 1 - nullable: true - description: *completions_top_p_description - user: *end_user_param_configuration - required: - - model - - messages - - CreateChatCompletionResponse: - type: object - description: Represents a chat completion response returned by model, based on the provided input. - properties: - id: - type: string - description: A unique identifier for the chat completion. - choices: - type: array - description: A list of chat completion choices. Can be more than one if `n` is greater than 1. - items: - type: object - required: - - finish_reason - - index - - message - properties: - finish_reason: - type: string - description: &chat_completion_finish_reason_description | - The reason the model stopped generating tokens. This will be `stop` if the model hit a natural stop point or a provided stop sequence, - `length` if the maximum number of tokens specified in the request was reached, - `content_filter` if content was omitted due to a flag from our content filters, - or `function_call` if the model called a function. - enum: ["stop", "length", "function_call", "content_filter"] - index: - type: integer - description: The index of the choice in the list of choices. - message: - $ref: "#/components/schemas/ChatCompletionResponseMessage" - created: - type: integer - description: The Unix timestamp (in seconds) of when the chat completion was created. - model: - type: string - description: The model used for the chat completion. - object: - type: string - description: The object type, which is always `chat.completion`. - usage: - $ref: "#/components/schemas/CompletionUsage" - required: - - choices - - created - - id - - model - - object - x-oaiMeta: - name: The chat completion object - group: chat - example: *chat_completion_example - - CreateChatCompletionFunctionResponse: - type: object - description: Represents a chat completion response returned by model, based on the provided input. - properties: - id: - type: string - description: A unique identifier for the chat completion. - choices: - type: array - description: A list of chat completion choices. Can be more than one if `n` is greater than 1. - items: - type: object - required: - - finish_reason - - index - - message - properties: - finish_reason: - type: string - description: &chat_completion_function_finish_reason_description | - The reason the model stopped generating tokens. This will be `stop` if the model hit a natural stop point or a provided stop sequence, `length` if the maximum number of tokens specified in the request was reached, `content_filter` if content was omitted due to a flag from our content filters, or `function_call` if the model called a function. - enum: ["stop", "length", "function_call", "content_filter"] - index: - type: integer - description: The index of the choice in the list of choices. - message: - $ref: "#/components/schemas/ChatCompletionResponseMessage" - created: - type: integer - description: The Unix timestamp (in seconds) of when the chat completion was created. - model: - type: string - description: The model used for the chat completion. - object: - type: string - description: The object type, which is always `chat.completion`. - usage: - $ref: "#/components/schemas/CompletionUsage" - required: - - choices - - created - - id - - model - - object - x-oaiMeta: - name: The chat completion object - group: chat - example: *chat_completion_function_example - - ListPaginatedFineTuningJobsResponse: - type: object - properties: - data: - type: array - items: - $ref: "#/components/schemas/FineTuningJob" - has_more: - type: boolean - object: - type: string - required: - - object - - data - - has_more - - CreateChatCompletionStreamResponse: - type: object - description: Represents a streamed chunk of a chat completion response returned by model, based on the provided input. - properties: - id: - type: string - description: A unique identifier for the chat completion. Each chunk has the same ID. - choices: - type: array - description: A list of chat completion choices. Can be more than one if `n` is greater than 1. - items: - type: object - required: - - delta - - finish_reason - - index - properties: - delta: - $ref: "#/components/schemas/ChatCompletionStreamResponseDelta" - finish_reason: - type: string - description: *chat_completion_finish_reason_description - enum: ["stop", "length", "function_call", "content_filter"] - nullable: true - index: - type: integer - description: The index of the choice in the list of choices. - created: - type: integer - description: The Unix timestamp (in seconds) of when the chat completion was created. Each chunk has the same timestamp. - model: - type: string - description: The model to generate the completion. - object: - type: string - description: The object type, which is always `chat.completion.chunk`. - required: - - choices - - created - - id - - model - - object - x-oaiMeta: - name: The chat completion chunk object - group: chat - example: *chat_completion_chunk_example - - CreateEditRequest: - type: object - properties: - instruction: - description: The instruction that tells the model how to edit the prompt. - type: string - example: "Fix the spelling mistakes." - model: - description: ID of the model to use. You can use the `text-davinci-edit-001` or `code-davinci-edit-001` model with this endpoint. - example: "text-davinci-edit-001" - anyOf: - - type: string - - type: string - enum: ["text-davinci-edit-001", "code-davinci-edit-001"] - x-oaiTypeLabel: string - input: - description: The input text to use as a starting point for the edit. - type: string - default: "" - nullable: true - example: "What day of the wek is it?" - n: - type: integer - minimum: 1 - maximum: 20 - default: 1 - example: 1 - nullable: true - description: How many edits to generate for the input and instruction. - temperature: - type: number - minimum: 0 - maximum: 2 - default: 1 - example: 1 - nullable: true - description: *completions_temperature_description - top_p: - type: number - minimum: 0 - maximum: 1 - default: 1 - example: 1 - nullable: true - description: *completions_top_p_description - required: - - model - - instruction - - CreateEditResponse: - type: object - title: Edit - deprecated: true - properties: - choices: - type: array - description: A list of edit choices. Can be more than one if `n` is greater than 1. - items: - type: object - required: - - text - - index - - finish_reason - properties: - finish_reason: - type: string - description: *completion_finish_reason_description - enum: ["stop", "length"] - index: - type: integer - description: The index of the choice in the list of choices. - text: - type: string - description: The edited result. - object: - type: string - description: The object type, which is always `edit`. - created: - type: integer - description: The Unix timestamp (in seconds) of when the edit was created. - usage: - $ref: "#/components/schemas/CompletionUsage" - required: - - object - - created - - choices - - usage - x-oaiMeta: - name: The edit object - example: *edit_example - - CreateImageRequest: - type: object - properties: - prompt: - description: A text description of the desired image(s). The maximum length is 1000 characters. - type: string - example: "A cute baby sea otter" - n: &images_n - type: integer - minimum: 1 - maximum: 10 - default: 1 - example: 1 - nullable: true - description: The number of images to generate. Must be between 1 and 10. - response_format: &images_response_format - type: string - enum: ["url", "b64_json"] - default: "url" - example: "url" - nullable: true - description: The format in which the generated images are returned. Must be one of `url` or `b64_json`. - size: &images_size - type: string - enum: ["256x256", "512x512", "1024x1024"] - default: "1024x1024" - example: "1024x1024" - nullable: true - description: The size of the generated images. Must be one of `256x256`, `512x512`, or `1024x1024`. - user: *end_user_param_configuration - required: - - prompt - - ImagesResponse: - properties: - created: - type: integer - data: - type: array - items: - $ref: "#/components/schemas/Image" - required: - - created - - data - - Image: - type: object - description: Represents the url or the content of an image generated by the OpenAI API. - properties: - b64_json: - type: string - description: The base64-encoded JSON of the generated image, if `response_format` is `b64_json`. - url: - type: string - description: The URL of the generated image, if `response_format` is `url` (default). - x-oaiMeta: - name: The image object - example: | - { - "url": "..." - } - - CreateImageEditRequest: - type: object - properties: - image: - description: The image to edit. Must be a valid PNG file, less than 4MB, and square. If mask is not provided, image must have transparency, which will be used as the mask. - type: string - format: binary - prompt: - description: A text description of the desired image(s). The maximum length is 1000 characters. - type: string - example: "A cute baby sea otter wearing a beret" - mask: - description: An additional image whose fully transparent areas (e.g. where alpha is zero) indicate where `image` should be edited. Must be a valid PNG file, less than 4MB, and have the same dimensions as `image`. - type: string - format: binary - n: *images_n - size: *images_size - response_format: *images_response_format - user: *end_user_param_configuration - required: - - prompt - - image - - CreateImageVariationRequest: - type: object - properties: - image: - description: The image to use as the basis for the variation(s). Must be a valid PNG file, less than 4MB, and square. - type: string - format: binary - n: *images_n - response_format: *images_response_format - size: *images_size - user: *end_user_param_configuration - required: - - image - - CreateModerationRequest: - type: object - properties: - input: - description: The input text to classify - oneOf: - - type: string - default: "" - example: "I want to kill them." - - type: array - items: - type: string - default: "" - example: "I want to kill them." - model: - description: | - Two content moderations models are available: `text-moderation-stable` and `text-moderation-latest`. - - The default is `text-moderation-latest` which will be automatically upgraded over time. This ensures you are always using our most accurate model. If you use `text-moderation-stable`, we will provide advanced notice before updating the model. Accuracy of `text-moderation-stable` may be slightly lower than for `text-moderation-latest`. - nullable: false - default: "text-moderation-latest" - example: "text-moderation-stable" - anyOf: - - type: string - - type: string - enum: ["text-moderation-latest", "text-moderation-stable"] - x-oaiTypeLabel: string - required: - - input - - CreateModerationResponse: - type: object - description: Represents policy compliance report by OpenAI's content moderation model against a given input. - properties: - id: - type: string - description: The unique identifier for the moderation request. - model: - type: string - description: The model used to generate the moderation results. - results: - type: array - description: A list of moderation objects. - items: - type: object - properties: - flagged: - type: boolean - description: Whether the content violates [OpenAI's usage policies](/policies/usage-policies). - categories: - type: object - description: A list of the categories, and whether they are flagged or not. - properties: - hate: - type: boolean - description: Content that expresses, incites, or promotes hate based on race, gender, ethnicity, religion, nationality, sexual orientation, disability status, or caste. Hateful content aimed at non-protected groups (e.g., chess players) is harrassment. - hate/threatening: - type: boolean - description: Hateful content that also includes violence or serious harm towards the targeted group based on race, gender, ethnicity, religion, nationality, sexual orientation, disability status, or caste. - harassment: - type: boolean - description: Content that expresses, incites, or promotes harassing language towards any target. - harassment/threatening: - type: boolean - description: Harassment content that also includes violence or serious harm towards any target. - self-harm: - type: boolean - description: Content that promotes, encourages, or depicts acts of self-harm, such as suicide, cutting, and eating disorders. - self-harm/intent: - type: boolean - description: Content where the speaker expresses that they are engaging or intend to engage in acts of self-harm, such as suicide, cutting, and eating disorders. - self-harm/instructions: - type: boolean - description: Content that encourages performing acts of self-harm, such as suicide, cutting, and eating disorders, or that gives instructions or advice on how to commit such acts. - sexual: - type: boolean - description: Content meant to arouse sexual excitement, such as the description of sexual activity, or that promotes sexual services (excluding sex education and wellness). - sexual/minors: - type: boolean - description: Sexual content that includes an individual who is under 18 years old. - violence: - type: boolean - description: Content that depicts death, violence, or physical injury. - violence/graphic: - type: boolean - description: Content that depicts death, violence, or physical injury in graphic detail. - required: - - hate - - hate/threatening - - harassment - - harassment/threatening - - self-harm - - self-harm/intent - - self-harm/instructions - - sexual - - sexual/minors - - violence - - violence/graphic - category_scores: - type: object - description: A list of the categories along with their scores as predicted by model. - properties: - hate: - type: number - description: The score for the category 'hate'. - hate/threatening: - type: number - description: The score for the category 'hate/threatening'. - harassment: - type: number - description: The score for the category 'harassment'. - harassment/threatening: - type: number - description: The score for the category 'harassment/threatening'. - self-harm: - type: number - description: The score for the category 'self-harm'. - self-harm/intent: - type: number - description: The score for the category 'self-harm/intent'. - self-harm/instructions: - type: number - description: The score for the category 'self-harm/instructions'. - sexual: - type: number - description: The score for the category 'sexual'. - sexual/minors: - type: number - description: The score for the category 'sexual/minors'. - violence: - type: number - description: The score for the category 'violence'. - violence/graphic: - type: number - description: The score for the category 'violence/graphic'. - required: - - hate - - hate/threatening - - harassment - - harassment/threatening - - self-harm - - self-harm/intent - - self-harm/instructions - - sexual - - sexual/minors - - violence - - violence/graphic - required: - - flagged - - categories - - category_scores - required: - - id - - model - - results - x-oaiMeta: - name: The moderation object - example: *moderation_example - - ListFilesResponse: - type: object - properties: - data: - type: array - items: - $ref: "#/components/schemas/OpenAIFile" - object: - type: string - required: - - object - - data - - CreateFileRequest: - type: object - additionalProperties: false - properties: - file: - description: | - The file object (not file name) to be uploaded. - - If the `purpose` is set to "fine-tune", the file will be used for fine-tuning. - type: string - format: binary - purpose: - description: | - The intended purpose of the uploaded file. - - Use "fine-tune" for [fine-tuning](/docs/api-reference/fine-tuning). This allows us to validate the format of the uploaded file is correct for fine-tuning. - type: string - required: - - file - - purpose - - DeleteFileResponse: - type: object - properties: - id: - type: string - object: - type: string - deleted: - type: boolean - required: - - id - - object - - deleted - - CreateFineTuningJobRequest: - type: object - properties: - model: - description: | - The name of the model to fine-tune. You can select one of the - [supported models](/docs/guides/fine-tuning/what-models-can-be-fine-tuned). - example: "gpt-3.5-turbo" - anyOf: - - type: string - - type: string - enum: ["babbage-002", "davinci-002", "gpt-3.5-turbo"] - x-oaiTypeLabel: string - training_file: - description: | - The ID of an uploaded file that contains training data. - - See [upload file](/docs/api-reference/files/upload) for how to upload a file. - - Your dataset must be formatted as a JSONL file. Additionally, you must upload your file with the purpose `fine-tune`. - - See the [fine-tuning guide](/docs/guides/fine-tuning) for more details. - type: string - example: "file-abc123" - hyperparameters: - type: object - description: The hyperparameters used for the fine-tuning job. - properties: - n_epochs: - description: | - The number of epochs to train the model for. An epoch refers to one - full cycle through the training dataset. - oneOf: - - type: string - enum: [auto] - - type: integer - minimum: 1 - maximum: 50 - default: auto - suffix: - description: | - A string of up to 18 characters that will be added to your fine-tuned model name. - - For example, a `suffix` of "custom-model-name" would produce a model name like `ft:gpt-3.5-turbo:openai:custom-model-name:7p4lURel`. - type: string - minLength: 1 - maxLength: 40 - default: null - nullable: true - validation_file: - description: | - The ID of an uploaded file that contains validation data. - - If you provide this file, the data is used to generate validation - metrics periodically during fine-tuning. These metrics can be viewed in - the fine-tuning results file. - The same data should not be present in both train and validation files. - - Your dataset must be formatted as a JSONL file. You must upload your file with the purpose `fine-tune`. - - See the [fine-tuning guide](/docs/guides/fine-tuning) for more details. - type: string - nullable: true - example: "file-abc123" - required: - - model - - training_file - - ListFineTuningJobEventsResponse: - type: object - properties: - data: - type: array - items: - $ref: "#/components/schemas/FineTuningJobEvent" - object: - type: string - required: - - object - - data - - CreateFineTuneRequest: - type: object - properties: - training_file: - description: | - The ID of an uploaded file that contains training data. - - See [upload file](/docs/api-reference/files/upload) for how to upload a file. - - Your dataset must be formatted as a JSONL file, where each training - example is a JSON object with the keys "prompt" and "completion". - Additionally, you must upload your file with the purpose `fine-tune`. - - See the [fine-tuning guide](/docs/guides/legacy-fine-tuning/creating-training-data) for more details. - type: string - example: "file-abc123" - batch_size: - description: | - The batch size to use for training. The batch size is the number of - training examples used to train a single forward and backward pass. - - By default, the batch size will be dynamically configured to be - ~0.2% of the number of examples in the training set, capped at 256 - - in general, we've found that larger batch sizes tend to work better - for larger datasets. - default: null - type: integer - nullable: true - classification_betas: - description: | - If this is provided, we calculate F-beta scores at the specified - beta values. The F-beta score is a generalization of F-1 score. - This is only used for binary classification. - - With a beta of 1 (i.e. the F-1 score), precision and recall are - given the same weight. A larger beta score puts more weight on - recall and less on precision. A smaller beta score puts more weight - on precision and less on recall. - type: array - items: - type: number - example: [0.6, 1, 1.5, 2] - default: null - nullable: true - classification_n_classes: - description: | - The number of classes in a classification task. - - This parameter is required for multiclass classification. - type: integer - default: null - nullable: true - classification_positive_class: - description: | - The positive class in binary classification. - - This parameter is needed to generate precision, recall, and F1 - metrics when doing binary classification. - type: string - default: null - nullable: true - compute_classification_metrics: - description: | - If set, we calculate classification-specific metrics such as accuracy - and F-1 score using the validation set at the end of every epoch. - These metrics can be viewed in the [results file](/docs/guides/legacy-fine-tuning/analyzing-your-fine-tuned-model). - - In order to compute classification metrics, you must provide a - `validation_file`. Additionally, you must - specify `classification_n_classes` for multiclass classification or - `classification_positive_class` for binary classification. - type: boolean - default: false - nullable: true - hyperparameters: - type: object - description: The hyperparameters used for the fine-tuning job. - properties: - n_epochs: - description: | - The number of epochs to train the model for. An epoch refers to one - full cycle through the training dataset. - oneOf: - - type: string - enum: [auto] - - type: integer - minimum: 1 - maximum: 50 - default: auto - learning_rate_multiplier: - description: | - The learning rate multiplier to use for training. - The fine-tuning learning rate is the original learning rate used for - pretraining multiplied by this value. - - By default, the learning rate multiplier is the 0.05, 0.1, or 0.2 - depending on final `batch_size` (larger learning rates tend to - perform better with larger batch sizes). We recommend experimenting - with values in the range 0.02 to 0.2 to see what produces the best - results. - default: null - type: number - nullable: true - model: - description: | - The name of the base model to fine-tune. You can select one of "ada", - "babbage", "curie", "davinci", or a fine-tuned model created after 2022-04-21 and before 2023-08-22. - To learn more about these models, see the - [Models](/docs/models) documentation. - default: "curie" - example: "curie" - nullable: true - anyOf: - - type: string - - type: string - enum: ["ada", "babbage", "curie", "davinci"] - x-oaiTypeLabel: string - prompt_loss_weight: - description: | - The weight to use for loss on the prompt tokens. This controls how - much the model tries to learn to generate the prompt (as compared - to the completion which always has a weight of 1.0), and can add - a stabilizing effect to training when completions are short. - - If prompts are extremely long (relative to completions), it may make - sense to reduce this weight so as to avoid over-prioritizing - learning the prompt. - default: 0.01 - type: number - nullable: true - suffix: - description: | - A string of up to 40 characters that will be added to your fine-tuned model name. - - For example, a `suffix` of "custom-model-name" would produce a model name like `ada:ft-your-org:custom-model-name-2022-02-15-04-21-04`. - type: string - minLength: 1 - maxLength: 40 - default: null - nullable: true - validation_file: - description: | - The ID of an uploaded file that contains validation data. - - If you provide this file, the data is used to generate validation - metrics periodically during fine-tuning. These metrics can be viewed in - the [fine-tuning results file](/docs/guides/legacy-fine-tuning/analyzing-your-fine-tuned-model). - Your train and validation data should be mutually exclusive. - - Your dataset must be formatted as a JSONL file, where each validation - example is a JSON object with the keys "prompt" and "completion". - Additionally, you must upload your file with the purpose `fine-tune`. - - See the [fine-tuning guide](/docs/guides/legacy-fine-tuning/creating-training-data) for more details. - type: string - nullable: true - example: "file-abc123" - required: - - training_file - - ListFineTunesResponse: - type: object - properties: - data: - type: array - items: - $ref: "#/components/schemas/FineTune" - object: - type: string - required: - - object - - data - - ListFineTuneEventsResponse: - type: object - properties: - data: - type: array - items: - $ref: "#/components/schemas/FineTuneEvent" - object: - type: string - required: - - object - - data - - CreateEmbeddingRequest: - type: object - additionalProperties: false - properties: - input: - description: | - Input text to embed, encoded as a string or array of tokens. To embed multiple inputs in a single request, pass an array of strings or array of token arrays. The input must not exceed the max input tokens for the model (8192 tokens for `text-embedding-ada-002`) and cannot be an empty string. [Example Python code](https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken) for counting tokens. - example: "The quick brown fox jumped over the lazy dog" - oneOf: - - type: string - default: "" - example: "This is a test." - - type: array - minItems: 1 - items: - type: string - default: "" - example: "This is a test." - - type: array - minItems: 1 - items: - type: integer - example: "[1212, 318, 257, 1332, 13]" - - type: array - minItems: 1 - items: - type: array - minItems: 1 - items: - type: integer - example: "[[1212, 318, 257, 1332, 13]]" - model: - description: *model_description - example: "text-embedding-ada-002" - anyOf: - - type: string - - type: string - enum: ["text-embedding-ada-002"] - x-oaiTypeLabel: string - encoding_format: - description: "The format to return the embeddings in. Can be either `float` or [`base64`](https://pypi.org/project/pybase64/)." - example: "float" - default: "float" - type: string - enum: ["float", "base64"] - user: *end_user_param_configuration - required: - - model - - input - - CreateEmbeddingResponse: - type: object - properties: - data: - type: array - description: The list of embeddings generated by the model. - items: - $ref: "#/components/schemas/Embedding" - model: - type: string - description: The name of the model used to generate the embedding. - object: - type: string - description: The object type, which is always "embedding". - usage: - type: object - description: The usage information for the request. - properties: - prompt_tokens: - type: integer - description: The number of tokens used by the prompt. - total_tokens: - type: integer - description: The total number of tokens used by the request. - required: - - prompt_tokens - - total_tokens - required: - - object - - model - - data - - usage - - CreateTranscriptionRequest: - type: object - additionalProperties: false - properties: - file: - description: | - The audio file object (not file name) to transcribe, in one of these formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm. - type: string - x-oaiTypeLabel: file - format: binary - model: - description: | - ID of the model to use. Only `whisper-1` is currently available. - example: whisper-1 - anyOf: - - type: string - - type: string - enum: ["whisper-1"] - x-oaiTypeLabel: string - language: - description: | - The language of the input audio. Supplying the input language in [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) format will improve accuracy and latency. - type: string - prompt: - description: | - An optional text to guide the model's style or continue a previous audio segment. The [prompt](/docs/guides/speech-to-text/prompting) should match the audio language. - type: string - response_format: - description: | - The format of the transcript output, in one of these options: json, text, srt, verbose_json, or vtt. - type: string - enum: - - json - - text - - srt - - verbose_json - - vtt - default: json - temperature: - description: | - The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to automatically increase the temperature until certain thresholds are hit. - type: number - default: 0 - required: - - file - - model - - # Note: This does not currently support the non-default response format types. - CreateTranscriptionResponse: - type: object - properties: - text: - type: string - required: - - text - - CreateTranslationRequest: - type: object - additionalProperties: false - properties: - file: - description: | - The audio file object (not file name) translate, in one of these formats: flac, mp3, mp4, mpeg, mpga, m4a, ogg, wav, or webm. - type: string - x-oaiTypeLabel: file - format: binary - model: - description: | - ID of the model to use. Only `whisper-1` is currently available. - example: whisper-1 - anyOf: - - type: string - - type: string - enum: ["whisper-1"] - x-oaiTypeLabel: string - prompt: - description: | - An optional text to guide the model's style or continue a previous audio segment. The [prompt](/docs/guides/speech-to-text/prompting) should be in English. - type: string - response_format: - description: | - The format of the transcript output, in one of these options: json, text, srt, verbose_json, or vtt. - type: string - default: json - temperature: - description: | - The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use [log probability](https://en.wikipedia.org/wiki/Log_probability) to automatically increase the temperature until certain thresholds are hit. - type: number - default: 0 - required: - - file - - model - - # Note: This does not currently support the non-default response format types. - CreateTranslationResponse: - type: object - properties: - text: - type: string - required: - - text - - Model: - title: Model - description: Describes an OpenAI model offering that can be used with the API. + ImagesResponse: properties: - id: - type: string - description: The model identifier, which can be referenced in the API endpoints. created: type: integer - description: The Unix timestamp (in seconds) when the model was created. - object: - type: string - description: The object type, which is always "model". - owned_by: - type: string - description: The organization that owns the model. - required: - - id - - object - - created - - owned_by - x-oaiMeta: - name: The model object - example: *retrieve_model_response - - OpenAIFile: - title: OpenAIFile - description: | - The `File` object represents a document that has been uploaded to OpenAI. - properties: - id: - type: string - description: The file identifier, which can be referenced in the API endpoints. - bytes: - type: integer - description: The size of the file in bytes. - created_at: - type: integer - description: The Unix timestamp (in seconds) for when the file was created. - filename: - type: string - description: The name of the file. - object: - type: string - description: The object type, which is always "file". - purpose: - type: string - description: The intended purpose of the file. Currently, only "fine-tune" is supported. - status: - type: string - description: The current status of the file, which can be either `uploaded`, `processed`, `pending`, `error`, `deleting` or `deleted`. - status_details: - type: string - nullable: true - description: | - Additional details about the status of the file. If the file is in the `error` state, this will include a message describing the error. - required: - - id - - object - - bytes - - created_at - - filename - - purpose - - format - x-oaiMeta: - name: The file object - example: | - { - "id": "file-abc123", - "object": "file", - "bytes": 120000, - "created_at": 1677610602, - "filename": "my_file.jsonl", - "purpose": "fine-tune", - "status": "uploaded", - "status_details": null - } - Embedding: - type: object - description: | - Represents an embedding vector returned by embedding endpoint. - properties: - index: - type: integer - description: The index of the embedding in the list of embeddings. - embedding: + data: type: array - description: | - The embedding vector, which is a list of floats. The length of vector depends on the model as listed in the [embedding guide](/docs/guides/embeddings). items: - type: number - object: - type: string - description: The object type, which is always "embedding". + $ref: "#/components/schemas/Image" required: - - index - - object - - embedding - x-oaiMeta: - name: The embedding object - example: | - { - "object": "embedding", - "embedding": [ - 0.0023064255, - -0.009327292, - .... (1536 floats total for ada-002) - -0.0028842222, - ], - "index": 0 - } - - FineTuningJob: - type: object - title: FineTuningJob - description: | - The `fine_tuning.job` object represents a fine-tuning job that has been created through the API. - properties: - id: - type: string - description: The object identifier, which can be referenced in the API endpoints. - created_at: - type: integer - description: The Unix timestamp (in seconds) for when the fine-tuning job was created. - error: - type: object - nullable: true - description: For fine-tuning jobs that have `failed`, this will contain more information on the cause of the failure. - properties: - code: - type: string - description: A machine-readable error code. - message: - type: string - description: A human-readable error message. - param: - type: string - description: The parameter that was invalid, usually `training_file` or `validation_file`. This field will be null if the failure was not parameter-specific. - nullable: true - required: - - code - - message - - param - fine_tuned_model: - type: string - nullable: true - description: The name of the fine-tuned model that is being created. The value will be null if the fine-tuning job is still running. - finished_at: - type: integer - nullable: true - description: The Unix timestamp (in seconds) for when the fine-tuning job was finished. The value will be null if the fine-tuning job is still running. - hyperparameters: - type: object - description: The hyperparameters used for the fine-tuning job. See the [fine-tuning guide](/docs/guides/fine-tuning) for more details. - properties: - n_epochs: - oneOf: - - type: string - enum: [auto] - - type: integer - minimum: 1 - maximum: 50 - default: auto - description: - The number of epochs to train the model for. An epoch refers to one full cycle through the training dataset. + - created + - data - "auto" decides the optimal number of epochs based on the size of the dataset. If setting the number manually, we support any number between 1 and 50 epochs. - required: - - n_epochs - model: - type: string - description: The base model that is being fine-tuned. - object: - type: string - description: The object type, which is always "fine_tuning.job". - organization_id: - type: string - description: The organization that owns the fine-tuning job. - result_files: - type: array - description: The compiled results file ID(s) for the fine-tuning job. You can retrieve the results with the [Files API](/docs/api-reference/files/retrieve-contents). - items: - type: string - example: file-abc123 - status: - type: string - description: The current status of the fine-tuning job, which can be either `validating_files`, `queued`, `running`, `succeeded`, `failed`, or `cancelled`. - trained_tokens: - type: integer - nullable: true - description: The total number of billable tokens processed by this fine-tuning job. The value will be null if the fine-tuning job is still running. - training_file: - type: string - description: The file ID used for training. You can retrieve the training data with the [Files API](/docs/api-reference/files/retrieve-contents). - validation_file: - type: string - nullable: true - description: The file ID used for validation. You can retrieve the validation results with the [Files API](/docs/api-reference/files/retrieve-contents). - required: - - created_at - - error - - finished_at - - fine_tuned_model - - hyperparameters - - id - - model - - object - - organization_id - - result_files - - status - - trained_tokens - - training_file - - validation_file - x-oaiMeta: - name: The fine-tuning job object - example: *fine_tuning_example - - FineTuningJobEvent: + Image: type: object - description: Fine-tuning job event object + description: Represents the url or the content of an image generated by the OpenAI API. properties: - id: - type: string - created_at: - type: integer - level: - type: string - enum: ["info", "warn", "error"] - message: + b64_json: type: string - object: + description: The base64-encoded JSON of the generated image, if `response_format` is `b64_json`. + url: type: string - required: - - id - - object - - created_at - - level - - message + description: The URL of the generated image, if `response_format` is `url` (default). x-oaiMeta: - name: The fine-tuning job event object + name: The image object example: | { - "object": "event", - "id": "ftevent-abc123" - "created_at": 1677610602, - "level": "info", - "message": "Created fine-tuning job" + "url": "..." } - FineTune: + CreateImageEditRequest: type: object - deprecated: true - description: | - The `FineTune` object represents a legacy fine-tune job that has been created through the API. properties: - id: - type: string - description: The object identifier, which can be referenced in the API endpoints. - created_at: - type: integer - description: The Unix timestamp (in seconds) for when the fine-tuning job was created. - events: - type: array - description: The list of events that have been observed in the lifecycle of the FineTune job. - items: - $ref: "#/components/schemas/FineTuneEvent" - fine_tuned_model: - type: string - nullable: true - description: The name of the fine-tuned model that is being created. - hyperparams: - type: object - description: The hyperparameters used for the fine-tuning job. See the [fine-tuning guide](/docs/guides/legacy-fine-tuning/hyperparameters) for more details. - properties: - batch_size: - type: integer - description: | - The batch size to use for training. The batch size is the number of - training examples used to train a single forward and backward pass. - classification_n_classes: - type: integer - description: | - The number of classes to use for computing classification metrics. - classification_positive_class: - type: string - description: | - The positive class to use for computing classification metrics. - compute_classification_metrics: - type: boolean - description: | - The classification metrics to compute using the validation dataset at the end of every epoch. - learning_rate_multiplier: - type: number - description: | - The learning rate multiplier to use for training. - n_epochs: - type: integer - description: | - The number of epochs to train the model for. An epoch refers to one - full cycle through the training dataset. - prompt_loss_weight: - type: number - description: | - The weight to use for loss on the prompt tokens. - required: - - batch_size - - learning_rate_multiplier - - n_epochs - - prompt_loss_weight - model: - type: string - description: The base model that is being fine-tuned. - object: + image: + description: The image to edit. Must be a valid PNG file, less than 4MB, and square. If mask is not provided, image must have transparency, which will be used as the mask. type: string - description: The object type, which is always "fine-tune". - organization_id: + format: binary + prompt: + description: A text description of the desired image(s). The maximum length is 1000 characters. type: string - description: The organization that owns the fine-tuning job. - result_files: - type: array - description: The compiled results files for the fine-tuning job. - items: - $ref: "#/components/schemas/OpenAIFile" - status: + example: "A cute baby sea otter wearing a beret" + mask: + description: An additional image whose fully transparent areas (e.g. where alpha is zero) indicate where `image` should be edited. Must be a valid PNG file, less than 4MB, and have the same dimensions as `image`. type: string - description: The current status of the fine-tuning job, which can be either `created`, `running`, `succeeded`, `failed`, or `cancelled`. - training_files: - type: array - description: The list of files used for training. - items: - $ref: "#/components/schemas/OpenAIFile" - updated_at: - type: integer - description: The Unix timestamp (in seconds) for when the fine-tuning job was last updated. - validation_files: - type: array - description: The list of files used for validation. - items: - $ref: "#/components/schemas/OpenAIFile" + format: binary + n: *images_n + size: *images_size + response_format: *images_response_format + user: *end_user_param_configuration required: - - created_at - - fine_tuned_model - - hyperparams - - id - - model - - object - - organization_id - - result_files - - status - - training_files - - updated_at - - validation_files - x-oaiMeta: - name: The fine-tune object - example: *fine_tune_example + - prompt + - image - FineTuneEvent: + CreateImageVariationRequest: type: object - deprecated: true - description: Fine-tune event object properties: - created_at: - type: integer - level: - type: string - message: - type: string - object: + image: + description: The image to use as the basis for the variation(s). Must be a valid PNG file, less than 4MB, and square. type: string + format: binary + n: *images_n + response_format: *images_response_format + size: *images_size + user: *end_user_param_configuration required: - - object - - created_at - - level - - message - x-oaiMeta: - name: The fine-tune event object - example: | - { - "object": "event", - "created_at": 1677610602, - "level": "info", - "message": "Created fine-tune job" - } - - CompletionUsage: - type: object - description: Usage statistics for the completion request. - properties: - completion_tokens: - type: integer - description: Number of tokens in the generated completion. - prompt_tokens: - type: integer - description: Number of tokens in the prompt. - total_tokens: - type: integer - description: Total number of tokens used in the request (prompt + completion). - required: - - prompt_tokens - - completion_tokens - - total_tokens + - image security: - ApiKeyAuth: [] - -x-oaiMeta: - groups: - # > General Notes - # The `groups` section is used to generate the API reference pages and navigation, in the same - # order listed below. Additionally, each `group` can have a list of `sections`, each of which - # will become a navigation subroute and subsection under the group. Each section has: - # - `type`: Currently, either an `endpoint` or `object`, depending on how the section needs to - # be rendered - # - `key`: The reference key that can be used to lookup the section definition - # - `path`: The path (url) of the section, which is used to generate the navigation link. - # - # > The `object` sections maps to a schema component and the following fields are read for rendering - # - `x-oaiMeta.name`: The name of the object, which will become the section title - # - `x-oaiMeta.example`: The example object, which will be used to generate the example sample (always JSON) - # - `description`: The description of the object, which will be used to generate the section description - # - # > The `endpoint` section maps to an operation path and the following fields are read for rendering: - # - `x-oaiMeta.name`: The name of the endpoint, which will become the section title - # - `x-oaiMeta.examples`: The endpoint examples, which can be an object (meaning a single variation, most - # endpoints, or an array of objects, meaning multiple variations, e.g. the - # chat completion and completion endpoints, with streamed and non-streamed examples. - # - `x-oaiMeta.returns`: text describing what the endpoint returns. - # - `summary`: The summary of the endpoint, which will be used to generate the section description - - id: audio - title: Audio - description: | - Learn how to turn audio into text. - - Related guide: [Speech to text](/docs/guides/speech-to-text) - sections: - - type: endpoint - key: createTranscription - path: createTranscription - - type: endpoint - key: createTranslation - path: createTranslation - - id: chat - title: Chat - description: | - Given a list of messages comprising a conversation, the model will return a response. - - Related guide: [Chat completions](/docs/guides/gpt) - sections: - - type: object - key: CreateChatCompletionResponse - path: object - - type: object - key: CreateChatCompletionStreamResponse - path: streaming - - type: endpoint - key: createChatCompletion - path: create - - id: completions - title: Completions - legacy: true - description: | - Given a prompt, the model will return one or more predicted completions, and can also return the probabilities of alternative tokens at each position. We recommend most users use our Chat completions API. [Learn more](/docs/deprecations/2023-07-06-gpt-and-embeddings) - - Related guide: [Legacy Completions](/docs/guides/gpt/completions-api) - sections: - - type: object - key: CreateCompletionResponse - path: object - - type: endpoint - key: createCompletion - path: create - - id: embeddings - title: Embeddings - description: | - Get a vector representation of a given input that can be easily consumed by machine learning models and algorithms. - - Related guide: [Embeddings](/docs/guides/embeddings) - sections: - - type: object - key: Embedding - path: object - - type: endpoint - key: createEmbedding - path: create - - id: fine-tuning - title: Fine-tuning - description: | - Manage fine-tuning jobs to tailor a model to your specific training data. - - Related guide: [Fine-tune models](/docs/guides/fine-tuning) - sections: - - type: object - key: FineTuningJob - path: object - - type: endpoint - key: createFineTuningJob - path: create - - type: endpoint - key: listPaginatedFineTuningJobs - path: list - - type: endpoint - key: retrieveFineTuningJob - path: retrieve - - type: endpoint - key: cancelFineTuningJob - path: cancel - - type: object - key: FineTuningJobEvent - path: event-object - - type: endpoint - key: listFineTuningEvents - path: list-events - - id: files - title: Files - description: | - Files are used to upload documents that can be used with features like [fine-tuning](/docs/api-reference/fine-tuning). - sections: - - type: object - key: OpenAIFile - path: object - - type: endpoint - key: listFiles - path: list - - type: endpoint - key: createFile - path: create - - type: endpoint - key: deleteFile - path: delete - - type: endpoint - key: retrieveFile - path: retrieve - - type: endpoint - key: downloadFile - path: retrieve-contents - - id: images - title: Images - description: | - Given a prompt and/or an input image, the model will generate a new image. - - Related guide: [Image generation](/docs/guides/images) - sections: - - type: object - key: Image - path: object - - type: endpoint - key: createImage - path: create - - type: endpoint - key: createImageEdit - path: createEdit - - type: endpoint - key: createImageVariation - path: createVariation - - id: models - title: Models - description: | - List and describe the various models available in the API. You can refer to the [Models](/docs/models) documentation to understand what models are available and the differences between them. - sections: - - type: object - key: Model - path: object - - type: endpoint - key: listModels - path: list - - type: endpoint - key: retrieveModel - path: retrieve - - type: endpoint - key: deleteModel - path: delete - - id: moderations - title: Moderations - description: | - Given a input text, outputs if the model classifies it as violating OpenAI's content policy. - - Related guide: [Moderations](/docs/guides/moderation) - sections: - - type: object - key: CreateModerationResponse - path: object - - type: endpoint - key: createModeration - path: create - - id: fine-tunes - title: Fine-tunes - deprecated: true - description: | - Manage legacy fine-tuning jobs to tailor a model to your specific training data. - - We recommend transitioning to the updating [fine-tuning API](/docs/guides/fine-tuning) - sections: - - type: object - key: FineTune - path: object - - type: endpoint - key: createFineTune - path: create - - type: endpoint - key: listFineTunes - path: list - - type: endpoint - key: retrieveFineTune - path: retrieve - - type: endpoint - key: cancelFineTune - path: cancel - - type: object - key: FineTuneEvent - path: event-object - - type: endpoint - key: listFineTuneEvents - path: list-events - - id: edits - title: Edits - deprecated: true - description: | - Given a prompt and an instruction, the model will return an edited version of the prompt. - sections: - - type: object - key: CreateEditResponse - path: object - - type: endpoint - key: createEdit - path: create From 6489729d5b077219f56d0df168c4771b95360b6f Mon Sep 17 00:00:00 2001 From: ahirner Date: Thu, 26 Oct 2023 09:16:53 +0200 Subject: [PATCH 18/38] uses_serde_json also depends on multipart/form-data bodies --- cargo-progenitor/src/main.rs | 2 +- progenitor-impl/src/lib.rs | 7 +++++++ progenitor-impl/src/method.rs | 7 +++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cargo-progenitor/src/main.rs b/cargo-progenitor/src/main.rs index ce965f80..75e61db2 100644 --- a/cargo-progenitor/src/main.rs +++ b/cargo-progenitor/src/main.rs @@ -290,7 +290,7 @@ pub fn dependencies(builder: Generator, include_client: bool) -> Vec { dependency_versions.get("rand").unwrap() )); } - if type_space.uses_serde_json() { + if type_space.uses_serde_json() || builder.uses_serde_json() { deps.push(format!( "serde_json = \"{}\"", dependency_versions.get("serde_json").unwrap() diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index 3bc63dda..179bc92d 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -44,6 +44,7 @@ pub struct Generator { settings: GenerationSettings, uses_futures: bool, uses_websockets: bool, + uses_serde_json: bool, } #[derive(Clone)] @@ -213,6 +214,7 @@ impl Default for Generator { settings: Default::default(), uses_futures: Default::default(), uses_websockets: Default::default(), + uses_serde_json: Default::default(), } } } @@ -253,6 +255,7 @@ impl Generator { settings: settings.clone(), uses_futures: false, uses_websockets: false, + uses_serde_json: false, } } @@ -570,6 +573,10 @@ impl Generator { pub fn uses_websockets(&self) -> bool { self.uses_websockets } + + pub fn uses_serde_json(&self) -> bool { + self.uses_serde_json + } } pub fn space_out_items(content: String) -> Result { diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index fdd15339..2667d070 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -446,6 +446,13 @@ impl Generator { } params.extend(self.get_body_params(operation, components)?); + if params + .iter() + .any(|param| matches!(param.typ, OperationParameterType::FormPart)) + { + // body fields use serde_json for serialization + self.uses_serde_json = true; + } let tmp = crate::template::parse(path)?; let names = tmp.names(); From 90f353d4db33d129e65139ffde0e41646786dbf5 Mon Sep 17 00:00:00 2001 From: ahirner Date: Thu, 26 Oct 2023 09:27:07 +0200 Subject: [PATCH 19/38] no need to filter body_parts for existing types --- progenitor-impl/src/method.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 2667d070..e17af8f6 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -2397,16 +2397,13 @@ impl Generator { }; let body_parts = tstru .properties() - .filter_map(|(prop_name, prop_id)| { + .filter_map(|(prop_name, _)| { if form_data_names.contains(prop_name) { None } else { - self.get_type_space() - .get_type(&prop_id) - .ok() - .map(|_| prop_name) + Some(prop_name) } }) .map(|prop_name| { From 6474706767c010372ce35fcb8442c2c7469f1b86 Mon Sep 17 00:00:00 2001 From: ahirner Date: Thu, 26 Oct 2023 09:29:00 +0200 Subject: [PATCH 20/38] explicit type requirement err --- progenitor-impl/src/method.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index e17af8f6..808e3010 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -2393,7 +2393,7 @@ impl Generator { let typ = self.get_type_space().get_type(type_id)?; let td = typ.details(); let typify::TypeDetails::Struct(tstru) = td else { - unreachable!() + unimplemented!("form-data bodies must be a Struct") }; let body_parts = tstru .properties() From c3ffd792f89e48778877c633f51b29b2a19fd258 Mon Sep 17 00:00:00 2001 From: ahirner Date: Thu, 26 Oct 2023 09:38:20 +0200 Subject: [PATCH 21/38] verify_apis("openai-openapi.yaml") and EXPECTORATE=overwrite cargo test --- .../output/openai-openapi-builder-tagged.out | 1594 ++++++++++++++++ .../tests/output/openai-openapi-builder.out | 1600 +++++++++++++++++ .../tests/output/openai-openapi-cli.out | 444 +++++ .../tests/output/openai-openapi-httpmock.out | 188 ++ .../output/openai-openapi-positional.out | 778 ++++++++ progenitor-impl/tests/test_output.rs | 5 + 6 files changed, 4609 insertions(+) create mode 100644 progenitor-impl/tests/output/openai-openapi-builder-tagged.out create mode 100644 progenitor-impl/tests/output/openai-openapi-builder.out create mode 100644 progenitor-impl/tests/output/openai-openapi-cli.out create mode 100644 progenitor-impl/tests/output/openai-openapi-httpmock.out create mode 100644 progenitor-impl/tests/output/openai-openapi-positional.out diff --git a/progenitor-impl/tests/output/openai-openapi-builder-tagged.out b/progenitor-impl/tests/output/openai-openapi-builder-tagged.out new file mode 100644 index 00000000..5a80b9f5 --- /dev/null +++ b/progenitor-impl/tests/output/openai-openapi-builder-tagged.out @@ -0,0 +1,1594 @@ +#[allow(unused_imports)] +use progenitor_client::{encode_path, RequestBuilderExt}; +pub use progenitor_client::{ByteStream, Error, ResponseValue}; +#[allow(unused_imports)] +use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; +pub mod types { + use serde::{Deserialize, Serialize}; + #[allow(unused_imports)] + use std::convert::TryFrom; + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateImageEditRequest { + ///The image to edit. Must be a valid PNG file, less than 4MB, and + /// square. If mask is not provided, image must have transparency, which + /// will be used as the mask. + pub image: Vec<()>, + ///An additional image whose fully transparent areas (e.g. where alpha + /// is zero) indicate where `image` should be edited. Must be a valid + /// PNG file, less than 4MB, and have the same dimensions as `image`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mask: Option>, + ///The number of images to generate. Must be between 1 and 10. + #[serde(default = "defaults::create_image_edit_request_n")] + pub n: Option, + ///A text description of the desired image(s). The maximum length is + /// 1000 characters. + pub prompt: String, + ///The format in which the generated images are returned. Must be one + /// of `url` or `b64_json`. + #[serde(default = "defaults::create_image_edit_request_response_format")] + pub response_format: Option, + ///The size of the generated images. Must be one of `256x256`, + /// `512x512`, or `1024x1024`. + #[serde(default = "defaults::create_image_edit_request_size")] + pub size: Option, + ///A unique identifier representing your end-user, which can help + /// OpenAI to monitor and detect abuse. [Learn + /// more](/docs/guides/safety-best-practices/end-user-ids). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, + } + + impl From<&CreateImageEditRequest> for CreateImageEditRequest { + fn from(value: &CreateImageEditRequest) -> Self { + value.clone() + } + } + + impl CreateImageEditRequest { + pub fn builder() -> builder::CreateImageEditRequest { + builder::CreateImageEditRequest::default() + } + } + + ///The format in which the generated images are returned. Must be one of + /// `url` or `b64_json`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageEditRequestResponseFormat { + #[serde(rename = "url")] + Url, + #[serde(rename = "b64_json")] + B64Json, + } + + impl From<&CreateImageEditRequestResponseFormat> for CreateImageEditRequestResponseFormat { + fn from(value: &CreateImageEditRequestResponseFormat) -> Self { + value.clone() + } + } + + impl ToString for CreateImageEditRequestResponseFormat { + fn to_string(&self) -> String { + match *self { + Self::Url => "url".to_string(), + Self::B64Json => "b64_json".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageEditRequestResponseFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "url" => Ok(Self::Url), + "b64_json" => Ok(Self::B64Json), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageEditRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageEditRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageEditRequestResponseFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + ///The size of the generated images. Must be one of `256x256`, `512x512`, + /// or `1024x1024`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageEditRequestSize { + #[serde(rename = "256x256")] + _256x256, + #[serde(rename = "512x512")] + _512x512, + #[serde(rename = "1024x1024")] + _1024x1024, + } + + impl From<&CreateImageEditRequestSize> for CreateImageEditRequestSize { + fn from(value: &CreateImageEditRequestSize) -> Self { + value.clone() + } + } + + impl ToString for CreateImageEditRequestSize { + fn to_string(&self) -> String { + match *self { + Self::_256x256 => "256x256".to_string(), + Self::_512x512 => "512x512".to_string(), + Self::_1024x1024 => "1024x1024".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageEditRequestSize { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "256x256" => Ok(Self::_256x256), + "512x512" => Ok(Self::_512x512), + "1024x1024" => Ok(Self::_1024x1024), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageEditRequestSize { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageEditRequestSize { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageEditRequestSize { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateImageRequest { + ///The number of images to generate. Must be between 1 and 10. + #[serde(default = "defaults::create_image_request_n")] + pub n: Option, + ///A text description of the desired image(s). The maximum length is + /// 1000 characters. + pub prompt: String, + ///The format in which the generated images are returned. Must be one + /// of `url` or `b64_json`. + #[serde(default = "defaults::create_image_request_response_format")] + pub response_format: Option, + ///The size of the generated images. Must be one of `256x256`, + /// `512x512`, or `1024x1024`. + #[serde(default = "defaults::create_image_request_size")] + pub size: Option, + ///A unique identifier representing your end-user, which can help + /// OpenAI to monitor and detect abuse. [Learn + /// more](/docs/guides/safety-best-practices/end-user-ids). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, + } + + impl From<&CreateImageRequest> for CreateImageRequest { + fn from(value: &CreateImageRequest) -> Self { + value.clone() + } + } + + impl CreateImageRequest { + pub fn builder() -> builder::CreateImageRequest { + builder::CreateImageRequest::default() + } + } + + ///The format in which the generated images are returned. Must be one of + /// `url` or `b64_json`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageRequestResponseFormat { + #[serde(rename = "url")] + Url, + #[serde(rename = "b64_json")] + B64Json, + } + + impl From<&CreateImageRequestResponseFormat> for CreateImageRequestResponseFormat { + fn from(value: &CreateImageRequestResponseFormat) -> Self { + value.clone() + } + } + + impl ToString for CreateImageRequestResponseFormat { + fn to_string(&self) -> String { + match *self { + Self::Url => "url".to_string(), + Self::B64Json => "b64_json".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageRequestResponseFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "url" => Ok(Self::Url), + "b64_json" => Ok(Self::B64Json), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageRequestResponseFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + ///The size of the generated images. Must be one of `256x256`, `512x512`, + /// or `1024x1024`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageRequestSize { + #[serde(rename = "256x256")] + _256x256, + #[serde(rename = "512x512")] + _512x512, + #[serde(rename = "1024x1024")] + _1024x1024, + } + + impl From<&CreateImageRequestSize> for CreateImageRequestSize { + fn from(value: &CreateImageRequestSize) -> Self { + value.clone() + } + } + + impl ToString for CreateImageRequestSize { + fn to_string(&self) -> String { + match *self { + Self::_256x256 => "256x256".to_string(), + Self::_512x512 => "512x512".to_string(), + Self::_1024x1024 => "1024x1024".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageRequestSize { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "256x256" => Ok(Self::_256x256), + "512x512" => Ok(Self::_512x512), + "1024x1024" => Ok(Self::_1024x1024), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageRequestSize { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageRequestSize { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageRequestSize { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateImageVariationRequest { + ///The image to use as the basis for the variation(s). Must be a valid + /// PNG file, less than 4MB, and square. + pub image: Vec<()>, + ///The number of images to generate. Must be between 1 and 10. + #[serde(default = "defaults::create_image_variation_request_n")] + pub n: Option, + ///The format in which the generated images are returned. Must be one + /// of `url` or `b64_json`. + #[serde(default = "defaults::create_image_variation_request_response_format")] + pub response_format: Option, + ///The size of the generated images. Must be one of `256x256`, + /// `512x512`, or `1024x1024`. + #[serde(default = "defaults::create_image_variation_request_size")] + pub size: Option, + ///A unique identifier representing your end-user, which can help + /// OpenAI to monitor and detect abuse. [Learn + /// more](/docs/guides/safety-best-practices/end-user-ids). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, + } + + impl From<&CreateImageVariationRequest> for CreateImageVariationRequest { + fn from(value: &CreateImageVariationRequest) -> Self { + value.clone() + } + } + + impl CreateImageVariationRequest { + pub fn builder() -> builder::CreateImageVariationRequest { + builder::CreateImageVariationRequest::default() + } + } + + ///The format in which the generated images are returned. Must be one of + /// `url` or `b64_json`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageVariationRequestResponseFormat { + #[serde(rename = "url")] + Url, + #[serde(rename = "b64_json")] + B64Json, + } + + impl From<&CreateImageVariationRequestResponseFormat> + for CreateImageVariationRequestResponseFormat + { + fn from(value: &CreateImageVariationRequestResponseFormat) -> Self { + value.clone() + } + } + + impl ToString for CreateImageVariationRequestResponseFormat { + fn to_string(&self) -> String { + match *self { + Self::Url => "url".to_string(), + Self::B64Json => "b64_json".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageVariationRequestResponseFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "url" => Ok(Self::Url), + "b64_json" => Ok(Self::B64Json), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageVariationRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageVariationRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageVariationRequestResponseFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + ///The size of the generated images. Must be one of `256x256`, `512x512`, + /// or `1024x1024`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageVariationRequestSize { + #[serde(rename = "256x256")] + _256x256, + #[serde(rename = "512x512")] + _512x512, + #[serde(rename = "1024x1024")] + _1024x1024, + } + + impl From<&CreateImageVariationRequestSize> for CreateImageVariationRequestSize { + fn from(value: &CreateImageVariationRequestSize) -> Self { + value.clone() + } + } + + impl ToString for CreateImageVariationRequestSize { + fn to_string(&self) -> String { + match *self { + Self::_256x256 => "256x256".to_string(), + Self::_512x512 => "512x512".to_string(), + Self::_1024x1024 => "1024x1024".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageVariationRequestSize { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "256x256" => Ok(Self::_256x256), + "512x512" => Ok(Self::_512x512), + "1024x1024" => Ok(Self::_1024x1024), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageVariationRequestSize { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageVariationRequestSize { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageVariationRequestSize { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Error { + pub code: Option, + pub message: String, + pub param: Option, + #[serde(rename = "type")] + pub type_: String, + } + + impl From<&Error> for Error { + fn from(value: &Error) -> Self { + value.clone() + } + } + + impl Error { + pub fn builder() -> builder::Error { + builder::Error::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ErrorResponse { + pub error: Error, + } + + impl From<&ErrorResponse> for ErrorResponse { + fn from(value: &ErrorResponse) -> Self { + value.clone() + } + } + + impl ErrorResponse { + pub fn builder() -> builder::ErrorResponse { + builder::ErrorResponse::default() + } + } + + ///Represents the url or the content of an image generated by the OpenAI + /// API. + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Image { + ///The base64-encoded JSON of the generated image, if `response_format` + /// is `b64_json`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub b64_json: Option, + ///The URL of the generated image, if `response_format` is `url` + /// (default). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url: Option, + } + + impl From<&Image> for Image { + fn from(value: &Image) -> Self { + value.clone() + } + } + + impl Image { + pub fn builder() -> builder::Image { + builder::Image::default() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ImagesResponse { + pub created: i64, + pub data: Vec, + } + + impl From<&ImagesResponse> for ImagesResponse { + fn from(value: &ImagesResponse) -> Self { + value.clone() + } + } + + impl ImagesResponse { + pub fn builder() -> builder::ImagesResponse { + builder::ImagesResponse::default() + } + } + + pub mod builder { + #[derive(Clone, Debug)] + pub struct CreateImageEditRequest { + image: Result, String>, + mask: Result>, String>, + n: Result, String>, + prompt: Result, + response_format: Result, String>, + size: Result, String>, + user: Result, String>, + } + + impl Default for CreateImageEditRequest { + fn default() -> Self { + Self { + image: Err("no value supplied for image".to_string()), + mask: Ok(Default::default()), + n: Ok(super::defaults::create_image_edit_request_n()), + prompt: Err("no value supplied for prompt".to_string()), + response_format: Ok( + super::defaults::create_image_edit_request_response_format(), + ), + size: Ok(super::defaults::create_image_edit_request_size()), + user: Ok(Default::default()), + } + } + } + + impl CreateImageEditRequest { + pub fn image(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.image = value + .try_into() + .map_err(|e| format!("error converting supplied value for image: {}", e)); + self + } + pub fn mask(mut self, value: T) -> Self + where + T: std::convert::TryInto>>, + T::Error: std::fmt::Display, + { + self.mask = value + .try_into() + .map_err(|e| format!("error converting supplied value for mask: {}", e)); + self + } + pub fn n(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.n = value + .try_into() + .map_err(|e| format!("error converting supplied value for n: {}", e)); + self + } + pub fn prompt(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.prompt = value + .try_into() + .map_err(|e| format!("error converting supplied value for prompt: {}", e)); + self + } + pub fn response_format(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.response_format = value.try_into().map_err(|e| { + format!("error converting supplied value for response_format: {}", e) + }); + self + } + pub fn size(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.size = value + .try_into() + .map_err(|e| format!("error converting supplied value for size: {}", e)); + self + } + pub fn user(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.user = value + .try_into() + .map_err(|e| format!("error converting supplied value for user: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::CreateImageEditRequest { + type Error = String; + fn try_from(value: CreateImageEditRequest) -> Result { + Ok(Self { + image: value.image?, + mask: value.mask?, + n: value.n?, + prompt: value.prompt?, + response_format: value.response_format?, + size: value.size?, + user: value.user?, + }) + } + } + + impl From for CreateImageEditRequest { + fn from(value: super::CreateImageEditRequest) -> Self { + Self { + image: Ok(value.image), + mask: Ok(value.mask), + n: Ok(value.n), + prompt: Ok(value.prompt), + response_format: Ok(value.response_format), + size: Ok(value.size), + user: Ok(value.user), + } + } + } + + #[derive(Clone, Debug)] + pub struct CreateImageRequest { + n: Result, String>, + prompt: Result, + response_format: Result, String>, + size: Result, String>, + user: Result, String>, + } + + impl Default for CreateImageRequest { + fn default() -> Self { + Self { + n: Ok(super::defaults::create_image_request_n()), + prompt: Err("no value supplied for prompt".to_string()), + response_format: Ok(super::defaults::create_image_request_response_format()), + size: Ok(super::defaults::create_image_request_size()), + user: Ok(Default::default()), + } + } + } + + impl CreateImageRequest { + pub fn n(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.n = value + .try_into() + .map_err(|e| format!("error converting supplied value for n: {}", e)); + self + } + pub fn prompt(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.prompt = value + .try_into() + .map_err(|e| format!("error converting supplied value for prompt: {}", e)); + self + } + pub fn response_format(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.response_format = value.try_into().map_err(|e| { + format!("error converting supplied value for response_format: {}", e) + }); + self + } + pub fn size(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.size = value + .try_into() + .map_err(|e| format!("error converting supplied value for size: {}", e)); + self + } + pub fn user(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.user = value + .try_into() + .map_err(|e| format!("error converting supplied value for user: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::CreateImageRequest { + type Error = String; + fn try_from(value: CreateImageRequest) -> Result { + Ok(Self { + n: value.n?, + prompt: value.prompt?, + response_format: value.response_format?, + size: value.size?, + user: value.user?, + }) + } + } + + impl From for CreateImageRequest { + fn from(value: super::CreateImageRequest) -> Self { + Self { + n: Ok(value.n), + prompt: Ok(value.prompt), + response_format: Ok(value.response_format), + size: Ok(value.size), + user: Ok(value.user), + } + } + } + + #[derive(Clone, Debug)] + pub struct CreateImageVariationRequest { + image: Result, String>, + n: Result, String>, + response_format: + Result, String>, + size: Result, String>, + user: Result, String>, + } + + impl Default for CreateImageVariationRequest { + fn default() -> Self { + Self { + image: Err("no value supplied for image".to_string()), + n: Ok(super::defaults::create_image_variation_request_n()), + response_format: Ok( + super::defaults::create_image_variation_request_response_format(), + ), + size: Ok(super::defaults::create_image_variation_request_size()), + user: Ok(Default::default()), + } + } + } + + impl CreateImageVariationRequest { + pub fn image(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.image = value + .try_into() + .map_err(|e| format!("error converting supplied value for image: {}", e)); + self + } + pub fn n(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.n = value + .try_into() + .map_err(|e| format!("error converting supplied value for n: {}", e)); + self + } + pub fn response_format(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.response_format = value.try_into().map_err(|e| { + format!("error converting supplied value for response_format: {}", e) + }); + self + } + pub fn size(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.size = value + .try_into() + .map_err(|e| format!("error converting supplied value for size: {}", e)); + self + } + pub fn user(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.user = value + .try_into() + .map_err(|e| format!("error converting supplied value for user: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::CreateImageVariationRequest { + type Error = String; + fn try_from(value: CreateImageVariationRequest) -> Result { + Ok(Self { + image: value.image?, + n: value.n?, + response_format: value.response_format?, + size: value.size?, + user: value.user?, + }) + } + } + + impl From for CreateImageVariationRequest { + fn from(value: super::CreateImageVariationRequest) -> Self { + Self { + image: Ok(value.image), + n: Ok(value.n), + response_format: Ok(value.response_format), + size: Ok(value.size), + user: Ok(value.user), + } + } + } + + #[derive(Clone, Debug)] + pub struct Error { + code: Result, String>, + message: Result, + param: Result, String>, + type_: Result, + } + + impl Default for Error { + fn default() -> Self { + Self { + code: Err("no value supplied for code".to_string()), + message: Err("no value supplied for message".to_string()), + param: Err("no value supplied for param".to_string()), + type_: Err("no value supplied for type_".to_string()), + } + } + } + + impl Error { + pub fn code(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.code = value + .try_into() + .map_err(|e| format!("error converting supplied value for code: {}", e)); + self + } + pub fn message(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.message = value + .try_into() + .map_err(|e| format!("error converting supplied value for message: {}", e)); + self + } + pub fn param(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.param = value + .try_into() + .map_err(|e| format!("error converting supplied value for param: {}", e)); + self + } + pub fn type_(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.type_ = value + .try_into() + .map_err(|e| format!("error converting supplied value for type_: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::Error { + type Error = String; + fn try_from(value: Error) -> Result { + Ok(Self { + code: value.code?, + message: value.message?, + param: value.param?, + type_: value.type_?, + }) + } + } + + impl From for Error { + fn from(value: super::Error) -> Self { + Self { + code: Ok(value.code), + message: Ok(value.message), + param: Ok(value.param), + type_: Ok(value.type_), + } + } + } + + #[derive(Clone, Debug)] + pub struct ErrorResponse { + error: Result, + } + + impl Default for ErrorResponse { + fn default() -> Self { + Self { + error: Err("no value supplied for error".to_string()), + } + } + } + + impl ErrorResponse { + pub fn error(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.error = value + .try_into() + .map_err(|e| format!("error converting supplied value for error: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::ErrorResponse { + type Error = String; + fn try_from(value: ErrorResponse) -> Result { + Ok(Self { + error: value.error?, + }) + } + } + + impl From for ErrorResponse { + fn from(value: super::ErrorResponse) -> Self { + Self { + error: Ok(value.error), + } + } + } + + #[derive(Clone, Debug)] + pub struct Image { + b64_json: Result, String>, + url: Result, String>, + } + + impl Default for Image { + fn default() -> Self { + Self { + b64_json: Ok(Default::default()), + url: Ok(Default::default()), + } + } + } + + impl Image { + pub fn b64_json(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.b64_json = value + .try_into() + .map_err(|e| format!("error converting supplied value for b64_json: {}", e)); + self + } + pub fn url(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.url = value + .try_into() + .map_err(|e| format!("error converting supplied value for url: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::Image { + type Error = String; + fn try_from(value: Image) -> Result { + Ok(Self { + b64_json: value.b64_json?, + url: value.url?, + }) + } + } + + impl From for Image { + fn from(value: super::Image) -> Self { + Self { + b64_json: Ok(value.b64_json), + url: Ok(value.url), + } + } + } + + #[derive(Clone, Debug)] + pub struct ImagesResponse { + created: Result, + data: Result, String>, + } + + impl Default for ImagesResponse { + fn default() -> Self { + Self { + created: Err("no value supplied for created".to_string()), + data: Err("no value supplied for data".to_string()), + } + } + } + + impl ImagesResponse { + pub fn created(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.created = value + .try_into() + .map_err(|e| format!("error converting supplied value for created: {}", e)); + self + } + pub fn data(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.data = value + .try_into() + .map_err(|e| format!("error converting supplied value for data: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::ImagesResponse { + type Error = String; + fn try_from(value: ImagesResponse) -> Result { + Ok(Self { + created: value.created?, + data: value.data?, + }) + } + } + + impl From for ImagesResponse { + fn from(value: super::ImagesResponse) -> Self { + Self { + created: Ok(value.created), + data: Ok(value.data), + } + } + } + } + + pub mod defaults { + pub(super) fn create_image_edit_request_n() -> Option { + Some(1_i64) + } + + pub(super) fn create_image_edit_request_response_format( + ) -> Option { + Some(super::CreateImageEditRequestResponseFormat::Url) + } + + pub(super) fn create_image_edit_request_size() -> Option + { + Some(super::CreateImageEditRequestSize::_1024x1024) + } + + pub(super) fn create_image_request_n() -> Option { + Some(1_i64) + } + + pub(super) fn create_image_request_response_format( + ) -> Option { + Some(super::CreateImageRequestResponseFormat::Url) + } + + pub(super) fn create_image_request_size() -> Option { + Some(super::CreateImageRequestSize::_1024x1024) + } + + pub(super) fn create_image_variation_request_n() -> Option { + Some(1_i64) + } + + pub(super) fn create_image_variation_request_response_format( + ) -> Option { + Some(super::CreateImageVariationRequestResponseFormat::Url) + } + + pub(super) fn create_image_variation_request_size( + ) -> Option { + Some(super::CreateImageVariationRequestSize::_1024x1024) + } + } +} + +#[derive(Clone, Debug)] +///Client for OpenAI API +/// +///The OpenAI REST API. Please see https://platform.openai.com/docs/api-reference for more details. +/// +///https://openai.com/policies/terms-of-use +/// +///Version: 2.0.0 +pub struct Client { + pub(crate) baseurl: String, + pub(crate) client: reqwest::Client, +} + +impl Client { + /// Create a new client. + /// + /// `baseurl` is the base URL provided to the internal + /// `reqwest::Client`, and should include a scheme and hostname, + /// as well as port and a path stem if applicable. + pub fn new(baseurl: &str) -> Self { + #[cfg(not(target_arch = "wasm32"))] + let client = { + let dur = std::time::Duration::from_secs(15); + reqwest::ClientBuilder::new() + .connect_timeout(dur) + .timeout(dur) + }; + #[cfg(target_arch = "wasm32")] + let client = reqwest::ClientBuilder::new(); + Self::new_with_client(baseurl, client.build().unwrap()) + } + + /// Construct a new client with an existing `reqwest::Client`, + /// allowing more control over its configuration. + /// + /// `baseurl` is the base URL provided to the internal + /// `reqwest::Client`, and should include a scheme and hostname, + /// as well as port and a path stem if applicable. + pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { + Self { + baseurl: baseurl.to_string(), + client, + } + } + + /// Get the base URL to which requests are made. + pub fn baseurl(&self) -> &String { + &self.baseurl + } + + /// Get the internal `reqwest::Client` used to make requests. + pub fn client(&self) -> &reqwest::Client { + &self.client + } + + /// Get the version of this API. + /// + /// This string is pulled directly from the source OpenAPI + /// document and may be in any format the API selects. + pub fn api_version(&self) -> &'static str { + "2.0.0" + } +} + +pub trait ClientImagesExt { + ///Creates an image given a prompt + /// + ///Sends a `POST` request to `/images/generations` + /// + ///```ignore + /// let response = client.create_image() + /// .body(body) + /// .send() + /// .await; + /// ``` + fn create_image(&self) -> builder::CreateImage; + ///Creates an edited or extended image given an original image and a prompt + /// + ///Sends a `POST` request to `/images/edits` + /// + ///Arguments: + /// - `body` + /// - `mask`: An additional image whose fully transparent areas (e.g. where + /// alpha is zero) indicate where `image` should be edited. Must be a + /// valid PNG file, less than 4MB, and have the same dimensions as + /// `image`. + /// - `image`: The image to edit. Must be a valid PNG file, less than 4MB, + /// and square. If mask is not provided, image must have transparency, + /// which will be used as the mask. + ///```ignore + /// let response = client.create_image_edit() + /// .body(body) + /// .mask(mask) + /// .image(image) + /// .send() + /// .await; + /// ``` + fn create_image_edit(&self) -> builder::CreateImageEdit; + ///Creates a variation of a given image + /// + ///Sends a `POST` request to `/images/variations` + /// + ///Arguments: + /// - `body` + /// - `image`: The image to use as the basis for the variation(s). Must be a + /// valid PNG file, less than 4MB, and square. + ///```ignore + /// let response = client.create_image_variation() + /// .body(body) + /// .image(image) + /// .send() + /// .await; + /// ``` + fn create_image_variation(&self) -> builder::CreateImageVariation; +} + +impl ClientImagesExt for Client { + fn create_image(&self) -> builder::CreateImage { + builder::CreateImage::new(self) + } + + fn create_image_edit(&self) -> builder::CreateImageEdit { + builder::CreateImageEdit::new(self) + } + + fn create_image_variation(&self) -> builder::CreateImageVariation { + builder::CreateImageVariation::new(self) + } +} + +pub mod builder { + use super::types; + #[allow(unused_imports)] + use super::{ + encode_path, ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, + }; + ///Builder for [`ClientImagesExt::create_image`] + /// + ///[`ClientImagesExt::create_image`]: super::ClientImagesExt::create_image + #[derive(Debug, Clone)] + pub struct CreateImage<'a> { + client: &'a super::Client, + body: Result, + } + + impl<'a> CreateImage<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + body: Ok(types::builder::CreateImageRequest::default()), + } + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.body = value + .try_into() + .map(From::from) + .map_err(|_| "conversion to `CreateImageRequest` for body failed".to_string()); + self + } + + pub fn body_map(mut self, f: F) -> Self + where + F: std::ops::FnOnce( + types::builder::CreateImageRequest, + ) -> types::builder::CreateImageRequest, + { + self.body = self.body.map(f); + self + } + + ///Sends a `POST` request to `/images/generations` + pub async fn send(self) -> Result, Error<()>> { + let Self { client, body } = self; + let body = body + .and_then(std::convert::TryInto::::try_into) + .map_err(Error::InvalidRequest)?; + let url = format!("{}/images/generations", client.baseurl,); + let request = client + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + + ///Builder for [`ClientImagesExt::create_image_edit`] + /// + ///[`ClientImagesExt::create_image_edit`]: super::ClientImagesExt::create_image_edit + #[derive(Debug)] + pub struct CreateImageEdit<'a> { + client: &'a super::Client, + body: Result, + mask: Result, String>, + image: Result, + } + + impl<'a> CreateImageEdit<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + body: Ok(types::builder::CreateImageEditRequest::default()), + mask: Ok(None), + image: Err("image was not initialized".to_string()), + } + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.body = value + .try_into() + .map(From::from) + .map_err(|_| "conversion to `CreateImageEditRequest` for body failed".to_string()); + self + } + + pub fn body_map(mut self, f: F) -> Self + where + F: std::ops::FnOnce( + types::builder::CreateImageEditRequest, + ) -> types::builder::CreateImageEditRequest, + { + self.body = self.body.map(f); + self + } + + pub fn mask(mut self, value: Option) -> Self { + self.mask = Ok(value); + self + } + + pub fn image(mut self, value: Part) -> Self { + self.image = Ok(value); + self + } + + ///Sends a `POST` request to `/images/edits` + pub async fn send(self) -> Result, Error<()>> { + let Self { + client, + body, + mask, + image, + } = self; + let body = body + .and_then(std::convert::TryInto::::try_into) + .map_err(Error::InvalidRequest)?; + let mask = mask.map_err(Error::InvalidRequest)?; + let image = image.map_err(Error::InvalidRequest)?; + let url = format!("{}/images/edits", client.baseurl,); + let body = serde_json::to_value(body) + .map_err(|e| Error::InvalidRequest(e.to_string()))? + .as_object() + .cloned() + .ok_or_else(|| Error::InvalidRequest("Serializing body failed.".to_string()))?; + let mut form = reqwest::multipart::Form::new().part("image", image); + if let Some(mask) = mask { + form = form.part("mask", mask); + }; + if let Some(v) = body.get("n").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("n", v); + }; + if let Some(v) = body.get("prompt").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("prompt", v); + }; + if let Some(v) = body.get("response_format").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("response_format", v); + }; + if let Some(v) = body.get("size").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("size", v); + }; + if let Some(v) = body.get("user").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("user", v); + }; + let request = client + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .multipart(form) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + + ///Builder for [`ClientImagesExt::create_image_variation`] + /// + ///[`ClientImagesExt::create_image_variation`]: super::ClientImagesExt::create_image_variation + #[derive(Debug)] + pub struct CreateImageVariation<'a> { + client: &'a super::Client, + body: Result, + image: Result, + } + + impl<'a> CreateImageVariation<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + body: Ok(types::builder::CreateImageVariationRequest::default()), + image: Err("image was not initialized".to_string()), + } + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.body = value.try_into().map(From::from).map_err(|_| { + "conversion to `CreateImageVariationRequest` for body failed".to_string() + }); + self + } + + pub fn body_map(mut self, f: F) -> Self + where + F: std::ops::FnOnce( + types::builder::CreateImageVariationRequest, + ) -> types::builder::CreateImageVariationRequest, + { + self.body = self.body.map(f); + self + } + + pub fn image(mut self, value: Part) -> Self { + self.image = Ok(value); + self + } + + ///Sends a `POST` request to `/images/variations` + pub async fn send(self) -> Result, Error<()>> { + let Self { + client, + body, + image, + } = self; + let body = body + .and_then(std::convert::TryInto::::try_into) + .map_err(Error::InvalidRequest)?; + let image = image.map_err(Error::InvalidRequest)?; + let url = format!("{}/images/variations", client.baseurl,); + let body = serde_json::to_value(body) + .map_err(|e| Error::InvalidRequest(e.to_string()))? + .as_object() + .cloned() + .ok_or_else(|| Error::InvalidRequest("Serializing body failed.".to_string()))?; + let mut form = reqwest::multipart::Form::new().part("image", image); + if let Some(v) = body.get("n").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("n", v); + }; + if let Some(v) = body.get("response_format").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("response_format", v); + }; + if let Some(v) = body.get("size").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("size", v); + }; + if let Some(v) = body.get("user").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("user", v); + }; + let request = client + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .multipart(form) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + } +} + +pub mod prelude { + pub use super::Client; + pub use super::ClientImagesExt; +} diff --git a/progenitor-impl/tests/output/openai-openapi-builder.out b/progenitor-impl/tests/output/openai-openapi-builder.out new file mode 100644 index 00000000..fe28944b --- /dev/null +++ b/progenitor-impl/tests/output/openai-openapi-builder.out @@ -0,0 +1,1600 @@ +#[allow(unused_imports)] +use progenitor_client::{encode_path, RequestBuilderExt}; +pub use progenitor_client::{ByteStream, Error, ResponseValue}; +#[allow(unused_imports)] +use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; +pub mod types { + use serde::{Deserialize, Serialize}; + #[allow(unused_imports)] + use std::convert::TryFrom; + #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] + pub struct CreateImageEditRequest { + ///The image to edit. Must be a valid PNG file, less than 4MB, and + /// square. If mask is not provided, image must have transparency, which + /// will be used as the mask. + pub image: Vec<()>, + ///An additional image whose fully transparent areas (e.g. where alpha + /// is zero) indicate where `image` should be edited. Must be a valid + /// PNG file, less than 4MB, and have the same dimensions as `image`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mask: Option>, + ///The number of images to generate. Must be between 1 and 10. + #[serde(default = "defaults::create_image_edit_request_n")] + pub n: Option, + ///A text description of the desired image(s). The maximum length is + /// 1000 characters. + pub prompt: String, + ///The format in which the generated images are returned. Must be one + /// of `url` or `b64_json`. + #[serde(default = "defaults::create_image_edit_request_response_format")] + pub response_format: Option, + ///The size of the generated images. Must be one of `256x256`, + /// `512x512`, or `1024x1024`. + #[serde(default = "defaults::create_image_edit_request_size")] + pub size: Option, + ///A unique identifier representing your end-user, which can help + /// OpenAI to monitor and detect abuse. [Learn + /// more](/docs/guides/safety-best-practices/end-user-ids). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, + } + + impl From<&CreateImageEditRequest> for CreateImageEditRequest { + fn from(value: &CreateImageEditRequest) -> Self { + value.clone() + } + } + + impl CreateImageEditRequest { + pub fn builder() -> builder::CreateImageEditRequest { + builder::CreateImageEditRequest::default() + } + } + + ///The format in which the generated images are returned. Must be one of + /// `url` or `b64_json`. + #[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize, + )] + pub enum CreateImageEditRequestResponseFormat { + #[serde(rename = "url")] + Url, + #[serde(rename = "b64_json")] + B64Json, + } + + impl From<&CreateImageEditRequestResponseFormat> for CreateImageEditRequestResponseFormat { + fn from(value: &CreateImageEditRequestResponseFormat) -> Self { + value.clone() + } + } + + impl ToString for CreateImageEditRequestResponseFormat { + fn to_string(&self) -> String { + match *self { + Self::Url => "url".to_string(), + Self::B64Json => "b64_json".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageEditRequestResponseFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "url" => Ok(Self::Url), + "b64_json" => Ok(Self::B64Json), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageEditRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageEditRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageEditRequestResponseFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + ///The size of the generated images. Must be one of `256x256`, `512x512`, + /// or `1024x1024`. + #[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize, + )] + pub enum CreateImageEditRequestSize { + #[serde(rename = "256x256")] + _256x256, + #[serde(rename = "512x512")] + _512x512, + #[serde(rename = "1024x1024")] + _1024x1024, + } + + impl From<&CreateImageEditRequestSize> for CreateImageEditRequestSize { + fn from(value: &CreateImageEditRequestSize) -> Self { + value.clone() + } + } + + impl ToString for CreateImageEditRequestSize { + fn to_string(&self) -> String { + match *self { + Self::_256x256 => "256x256".to_string(), + Self::_512x512 => "512x512".to_string(), + Self::_1024x1024 => "1024x1024".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageEditRequestSize { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "256x256" => Ok(Self::_256x256), + "512x512" => Ok(Self::_512x512), + "1024x1024" => Ok(Self::_1024x1024), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageEditRequestSize { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageEditRequestSize { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageEditRequestSize { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] + pub struct CreateImageRequest { + ///The number of images to generate. Must be between 1 and 10. + #[serde(default = "defaults::create_image_request_n")] + pub n: Option, + ///A text description of the desired image(s). The maximum length is + /// 1000 characters. + pub prompt: String, + ///The format in which the generated images are returned. Must be one + /// of `url` or `b64_json`. + #[serde(default = "defaults::create_image_request_response_format")] + pub response_format: Option, + ///The size of the generated images. Must be one of `256x256`, + /// `512x512`, or `1024x1024`. + #[serde(default = "defaults::create_image_request_size")] + pub size: Option, + ///A unique identifier representing your end-user, which can help + /// OpenAI to monitor and detect abuse. [Learn + /// more](/docs/guides/safety-best-practices/end-user-ids). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, + } + + impl From<&CreateImageRequest> for CreateImageRequest { + fn from(value: &CreateImageRequest) -> Self { + value.clone() + } + } + + impl CreateImageRequest { + pub fn builder() -> builder::CreateImageRequest { + builder::CreateImageRequest::default() + } + } + + ///The format in which the generated images are returned. Must be one of + /// `url` or `b64_json`. + #[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize, + )] + pub enum CreateImageRequestResponseFormat { + #[serde(rename = "url")] + Url, + #[serde(rename = "b64_json")] + B64Json, + } + + impl From<&CreateImageRequestResponseFormat> for CreateImageRequestResponseFormat { + fn from(value: &CreateImageRequestResponseFormat) -> Self { + value.clone() + } + } + + impl ToString for CreateImageRequestResponseFormat { + fn to_string(&self) -> String { + match *self { + Self::Url => "url".to_string(), + Self::B64Json => "b64_json".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageRequestResponseFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "url" => Ok(Self::Url), + "b64_json" => Ok(Self::B64Json), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageRequestResponseFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + ///The size of the generated images. Must be one of `256x256`, `512x512`, + /// or `1024x1024`. + #[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize, + )] + pub enum CreateImageRequestSize { + #[serde(rename = "256x256")] + _256x256, + #[serde(rename = "512x512")] + _512x512, + #[serde(rename = "1024x1024")] + _1024x1024, + } + + impl From<&CreateImageRequestSize> for CreateImageRequestSize { + fn from(value: &CreateImageRequestSize) -> Self { + value.clone() + } + } + + impl ToString for CreateImageRequestSize { + fn to_string(&self) -> String { + match *self { + Self::_256x256 => "256x256".to_string(), + Self::_512x512 => "512x512".to_string(), + Self::_1024x1024 => "1024x1024".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageRequestSize { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "256x256" => Ok(Self::_256x256), + "512x512" => Ok(Self::_512x512), + "1024x1024" => Ok(Self::_1024x1024), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageRequestSize { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageRequestSize { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageRequestSize { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] + pub struct CreateImageVariationRequest { + ///The image to use as the basis for the variation(s). Must be a valid + /// PNG file, less than 4MB, and square. + pub image: Vec<()>, + ///The number of images to generate. Must be between 1 and 10. + #[serde(default = "defaults::create_image_variation_request_n")] + pub n: Option, + ///The format in which the generated images are returned. Must be one + /// of `url` or `b64_json`. + #[serde(default = "defaults::create_image_variation_request_response_format")] + pub response_format: Option, + ///The size of the generated images. Must be one of `256x256`, + /// `512x512`, or `1024x1024`. + #[serde(default = "defaults::create_image_variation_request_size")] + pub size: Option, + ///A unique identifier representing your end-user, which can help + /// OpenAI to monitor and detect abuse. [Learn + /// more](/docs/guides/safety-best-practices/end-user-ids). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, + } + + impl From<&CreateImageVariationRequest> for CreateImageVariationRequest { + fn from(value: &CreateImageVariationRequest) -> Self { + value.clone() + } + } + + impl CreateImageVariationRequest { + pub fn builder() -> builder::CreateImageVariationRequest { + builder::CreateImageVariationRequest::default() + } + } + + ///The format in which the generated images are returned. Must be one of + /// `url` or `b64_json`. + #[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize, + )] + pub enum CreateImageVariationRequestResponseFormat { + #[serde(rename = "url")] + Url, + #[serde(rename = "b64_json")] + B64Json, + } + + impl From<&CreateImageVariationRequestResponseFormat> + for CreateImageVariationRequestResponseFormat + { + fn from(value: &CreateImageVariationRequestResponseFormat) -> Self { + value.clone() + } + } + + impl ToString for CreateImageVariationRequestResponseFormat { + fn to_string(&self) -> String { + match *self { + Self::Url => "url".to_string(), + Self::B64Json => "b64_json".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageVariationRequestResponseFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "url" => Ok(Self::Url), + "b64_json" => Ok(Self::B64Json), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageVariationRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageVariationRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageVariationRequestResponseFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + ///The size of the generated images. Must be one of `256x256`, `512x512`, + /// or `1024x1024`. + #[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize, + )] + pub enum CreateImageVariationRequestSize { + #[serde(rename = "256x256")] + _256x256, + #[serde(rename = "512x512")] + _512x512, + #[serde(rename = "1024x1024")] + _1024x1024, + } + + impl From<&CreateImageVariationRequestSize> for CreateImageVariationRequestSize { + fn from(value: &CreateImageVariationRequestSize) -> Self { + value.clone() + } + } + + impl ToString for CreateImageVariationRequestSize { + fn to_string(&self) -> String { + match *self { + Self::_256x256 => "256x256".to_string(), + Self::_512x512 => "512x512".to_string(), + Self::_1024x1024 => "1024x1024".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageVariationRequestSize { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "256x256" => Ok(Self::_256x256), + "512x512" => Ok(Self::_512x512), + "1024x1024" => Ok(Self::_1024x1024), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageVariationRequestSize { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageVariationRequestSize { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageVariationRequestSize { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] + pub struct Error { + pub code: Option, + pub message: String, + pub param: Option, + #[serde(rename = "type")] + pub type_: String, + } + + impl From<&Error> for Error { + fn from(value: &Error) -> Self { + value.clone() + } + } + + impl Error { + pub fn builder() -> builder::Error { + builder::Error::default() + } + } + + #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] + pub struct ErrorResponse { + pub error: Error, + } + + impl From<&ErrorResponse> for ErrorResponse { + fn from(value: &ErrorResponse) -> Self { + value.clone() + } + } + + impl ErrorResponse { + pub fn builder() -> builder::ErrorResponse { + builder::ErrorResponse::default() + } + } + + ///Represents the url or the content of an image generated by the OpenAI + /// API. + #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] + pub struct Image { + ///The base64-encoded JSON of the generated image, if `response_format` + /// is `b64_json`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub b64_json: Option, + ///The URL of the generated image, if `response_format` is `url` + /// (default). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url: Option, + } + + impl From<&Image> for Image { + fn from(value: &Image) -> Self { + value.clone() + } + } + + impl Image { + pub fn builder() -> builder::Image { + builder::Image::default() + } + } + + #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] + pub struct ImagesResponse { + pub created: i64, + pub data: Vec, + } + + impl From<&ImagesResponse> for ImagesResponse { + fn from(value: &ImagesResponse) -> Self { + value.clone() + } + } + + impl ImagesResponse { + pub fn builder() -> builder::ImagesResponse { + builder::ImagesResponse::default() + } + } + + pub mod builder { + #[derive(Clone, Debug)] + pub struct CreateImageEditRequest { + image: Result, String>, + mask: Result>, String>, + n: Result, String>, + prompt: Result, + response_format: Result, String>, + size: Result, String>, + user: Result, String>, + } + + impl Default for CreateImageEditRequest { + fn default() -> Self { + Self { + image: Err("no value supplied for image".to_string()), + mask: Ok(Default::default()), + n: Ok(super::defaults::create_image_edit_request_n()), + prompt: Err("no value supplied for prompt".to_string()), + response_format: Ok( + super::defaults::create_image_edit_request_response_format(), + ), + size: Ok(super::defaults::create_image_edit_request_size()), + user: Ok(Default::default()), + } + } + } + + impl CreateImageEditRequest { + pub fn image(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.image = value + .try_into() + .map_err(|e| format!("error converting supplied value for image: {}", e)); + self + } + pub fn mask(mut self, value: T) -> Self + where + T: std::convert::TryInto>>, + T::Error: std::fmt::Display, + { + self.mask = value + .try_into() + .map_err(|e| format!("error converting supplied value for mask: {}", e)); + self + } + pub fn n(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.n = value + .try_into() + .map_err(|e| format!("error converting supplied value for n: {}", e)); + self + } + pub fn prompt(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.prompt = value + .try_into() + .map_err(|e| format!("error converting supplied value for prompt: {}", e)); + self + } + pub fn response_format(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.response_format = value.try_into().map_err(|e| { + format!("error converting supplied value for response_format: {}", e) + }); + self + } + pub fn size(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.size = value + .try_into() + .map_err(|e| format!("error converting supplied value for size: {}", e)); + self + } + pub fn user(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.user = value + .try_into() + .map_err(|e| format!("error converting supplied value for user: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::CreateImageEditRequest { + type Error = String; + fn try_from(value: CreateImageEditRequest) -> Result { + Ok(Self { + image: value.image?, + mask: value.mask?, + n: value.n?, + prompt: value.prompt?, + response_format: value.response_format?, + size: value.size?, + user: value.user?, + }) + } + } + + impl From for CreateImageEditRequest { + fn from(value: super::CreateImageEditRequest) -> Self { + Self { + image: Ok(value.image), + mask: Ok(value.mask), + n: Ok(value.n), + prompt: Ok(value.prompt), + response_format: Ok(value.response_format), + size: Ok(value.size), + user: Ok(value.user), + } + } + } + + #[derive(Clone, Debug)] + pub struct CreateImageRequest { + n: Result, String>, + prompt: Result, + response_format: Result, String>, + size: Result, String>, + user: Result, String>, + } + + impl Default for CreateImageRequest { + fn default() -> Self { + Self { + n: Ok(super::defaults::create_image_request_n()), + prompt: Err("no value supplied for prompt".to_string()), + response_format: Ok(super::defaults::create_image_request_response_format()), + size: Ok(super::defaults::create_image_request_size()), + user: Ok(Default::default()), + } + } + } + + impl CreateImageRequest { + pub fn n(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.n = value + .try_into() + .map_err(|e| format!("error converting supplied value for n: {}", e)); + self + } + pub fn prompt(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.prompt = value + .try_into() + .map_err(|e| format!("error converting supplied value for prompt: {}", e)); + self + } + pub fn response_format(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.response_format = value.try_into().map_err(|e| { + format!("error converting supplied value for response_format: {}", e) + }); + self + } + pub fn size(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.size = value + .try_into() + .map_err(|e| format!("error converting supplied value for size: {}", e)); + self + } + pub fn user(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.user = value + .try_into() + .map_err(|e| format!("error converting supplied value for user: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::CreateImageRequest { + type Error = String; + fn try_from(value: CreateImageRequest) -> Result { + Ok(Self { + n: value.n?, + prompt: value.prompt?, + response_format: value.response_format?, + size: value.size?, + user: value.user?, + }) + } + } + + impl From for CreateImageRequest { + fn from(value: super::CreateImageRequest) -> Self { + Self { + n: Ok(value.n), + prompt: Ok(value.prompt), + response_format: Ok(value.response_format), + size: Ok(value.size), + user: Ok(value.user), + } + } + } + + #[derive(Clone, Debug)] + pub struct CreateImageVariationRequest { + image: Result, String>, + n: Result, String>, + response_format: + Result, String>, + size: Result, String>, + user: Result, String>, + } + + impl Default for CreateImageVariationRequest { + fn default() -> Self { + Self { + image: Err("no value supplied for image".to_string()), + n: Ok(super::defaults::create_image_variation_request_n()), + response_format: Ok( + super::defaults::create_image_variation_request_response_format(), + ), + size: Ok(super::defaults::create_image_variation_request_size()), + user: Ok(Default::default()), + } + } + } + + impl CreateImageVariationRequest { + pub fn image(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.image = value + .try_into() + .map_err(|e| format!("error converting supplied value for image: {}", e)); + self + } + pub fn n(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.n = value + .try_into() + .map_err(|e| format!("error converting supplied value for n: {}", e)); + self + } + pub fn response_format(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.response_format = value.try_into().map_err(|e| { + format!("error converting supplied value for response_format: {}", e) + }); + self + } + pub fn size(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.size = value + .try_into() + .map_err(|e| format!("error converting supplied value for size: {}", e)); + self + } + pub fn user(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.user = value + .try_into() + .map_err(|e| format!("error converting supplied value for user: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::CreateImageVariationRequest { + type Error = String; + fn try_from(value: CreateImageVariationRequest) -> Result { + Ok(Self { + image: value.image?, + n: value.n?, + response_format: value.response_format?, + size: value.size?, + user: value.user?, + }) + } + } + + impl From for CreateImageVariationRequest { + fn from(value: super::CreateImageVariationRequest) -> Self { + Self { + image: Ok(value.image), + n: Ok(value.n), + response_format: Ok(value.response_format), + size: Ok(value.size), + user: Ok(value.user), + } + } + } + + #[derive(Clone, Debug)] + pub struct Error { + code: Result, String>, + message: Result, + param: Result, String>, + type_: Result, + } + + impl Default for Error { + fn default() -> Self { + Self { + code: Err("no value supplied for code".to_string()), + message: Err("no value supplied for message".to_string()), + param: Err("no value supplied for param".to_string()), + type_: Err("no value supplied for type_".to_string()), + } + } + } + + impl Error { + pub fn code(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.code = value + .try_into() + .map_err(|e| format!("error converting supplied value for code: {}", e)); + self + } + pub fn message(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.message = value + .try_into() + .map_err(|e| format!("error converting supplied value for message: {}", e)); + self + } + pub fn param(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.param = value + .try_into() + .map_err(|e| format!("error converting supplied value for param: {}", e)); + self + } + pub fn type_(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.type_ = value + .try_into() + .map_err(|e| format!("error converting supplied value for type_: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::Error { + type Error = String; + fn try_from(value: Error) -> Result { + Ok(Self { + code: value.code?, + message: value.message?, + param: value.param?, + type_: value.type_?, + }) + } + } + + impl From for Error { + fn from(value: super::Error) -> Self { + Self { + code: Ok(value.code), + message: Ok(value.message), + param: Ok(value.param), + type_: Ok(value.type_), + } + } + } + + #[derive(Clone, Debug)] + pub struct ErrorResponse { + error: Result, + } + + impl Default for ErrorResponse { + fn default() -> Self { + Self { + error: Err("no value supplied for error".to_string()), + } + } + } + + impl ErrorResponse { + pub fn error(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.error = value + .try_into() + .map_err(|e| format!("error converting supplied value for error: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::ErrorResponse { + type Error = String; + fn try_from(value: ErrorResponse) -> Result { + Ok(Self { + error: value.error?, + }) + } + } + + impl From for ErrorResponse { + fn from(value: super::ErrorResponse) -> Self { + Self { + error: Ok(value.error), + } + } + } + + #[derive(Clone, Debug)] + pub struct Image { + b64_json: Result, String>, + url: Result, String>, + } + + impl Default for Image { + fn default() -> Self { + Self { + b64_json: Ok(Default::default()), + url: Ok(Default::default()), + } + } + } + + impl Image { + pub fn b64_json(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.b64_json = value + .try_into() + .map_err(|e| format!("error converting supplied value for b64_json: {}", e)); + self + } + pub fn url(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.url = value + .try_into() + .map_err(|e| format!("error converting supplied value for url: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::Image { + type Error = String; + fn try_from(value: Image) -> Result { + Ok(Self { + b64_json: value.b64_json?, + url: value.url?, + }) + } + } + + impl From for Image { + fn from(value: super::Image) -> Self { + Self { + b64_json: Ok(value.b64_json), + url: Ok(value.url), + } + } + } + + #[derive(Clone, Debug)] + pub struct ImagesResponse { + created: Result, + data: Result, String>, + } + + impl Default for ImagesResponse { + fn default() -> Self { + Self { + created: Err("no value supplied for created".to_string()), + data: Err("no value supplied for data".to_string()), + } + } + } + + impl ImagesResponse { + pub fn created(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.created = value + .try_into() + .map_err(|e| format!("error converting supplied value for created: {}", e)); + self + } + pub fn data(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.data = value + .try_into() + .map_err(|e| format!("error converting supplied value for data: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::ImagesResponse { + type Error = String; + fn try_from(value: ImagesResponse) -> Result { + Ok(Self { + created: value.created?, + data: value.data?, + }) + } + } + + impl From for ImagesResponse { + fn from(value: super::ImagesResponse) -> Self { + Self { + created: Ok(value.created), + data: Ok(value.data), + } + } + } + } + + pub mod defaults { + pub(super) fn create_image_edit_request_n() -> Option { + Some(1_i64) + } + + pub(super) fn create_image_edit_request_response_format( + ) -> Option { + Some(super::CreateImageEditRequestResponseFormat::Url) + } + + pub(super) fn create_image_edit_request_size() -> Option + { + Some(super::CreateImageEditRequestSize::_1024x1024) + } + + pub(super) fn create_image_request_n() -> Option { + Some(1_i64) + } + + pub(super) fn create_image_request_response_format( + ) -> Option { + Some(super::CreateImageRequestResponseFormat::Url) + } + + pub(super) fn create_image_request_size() -> Option { + Some(super::CreateImageRequestSize::_1024x1024) + } + + pub(super) fn create_image_variation_request_n() -> Option { + Some(1_i64) + } + + pub(super) fn create_image_variation_request_response_format( + ) -> Option { + Some(super::CreateImageVariationRequestResponseFormat::Url) + } + + pub(super) fn create_image_variation_request_size( + ) -> Option { + Some(super::CreateImageVariationRequestSize::_1024x1024) + } + } +} + +#[derive(Clone, Debug)] +///Client for OpenAI API +/// +///The OpenAI REST API. Please see https://platform.openai.com/docs/api-reference for more details. +/// +///https://openai.com/policies/terms-of-use +/// +///Version: 2.0.0 +pub struct Client { + pub(crate) baseurl: String, + pub(crate) client: reqwest::Client, +} + +impl Client { + /// Create a new client. + /// + /// `baseurl` is the base URL provided to the internal + /// `reqwest::Client`, and should include a scheme and hostname, + /// as well as port and a path stem if applicable. + pub fn new(baseurl: &str) -> Self { + #[cfg(not(target_arch = "wasm32"))] + let client = { + let dur = std::time::Duration::from_secs(15); + reqwest::ClientBuilder::new() + .connect_timeout(dur) + .timeout(dur) + }; + #[cfg(target_arch = "wasm32")] + let client = reqwest::ClientBuilder::new(); + Self::new_with_client(baseurl, client.build().unwrap()) + } + + /// Construct a new client with an existing `reqwest::Client`, + /// allowing more control over its configuration. + /// + /// `baseurl` is the base URL provided to the internal + /// `reqwest::Client`, and should include a scheme and hostname, + /// as well as port and a path stem if applicable. + pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { + Self { + baseurl: baseurl.to_string(), + client, + } + } + + /// Get the base URL to which requests are made. + pub fn baseurl(&self) -> &String { + &self.baseurl + } + + /// Get the internal `reqwest::Client` used to make requests. + pub fn client(&self) -> &reqwest::Client { + &self.client + } + + /// Get the version of this API. + /// + /// This string is pulled directly from the source OpenAPI + /// document and may be in any format the API selects. + pub fn api_version(&self) -> &'static str { + "2.0.0" + } +} + +impl Client { + ///Creates an image given a prompt + /// + ///Sends a `POST` request to `/images/generations` + /// + ///```ignore + /// let response = client.create_image() + /// .body(body) + /// .send() + /// .await; + /// ``` + pub fn create_image(&self) -> builder::CreateImage { + builder::CreateImage::new(self) + } + + ///Creates an edited or extended image given an original image and a prompt + /// + ///Sends a `POST` request to `/images/edits` + /// + ///Arguments: + /// - `body` + /// - `mask`: An additional image whose fully transparent areas (e.g. where + /// alpha is zero) indicate where `image` should be edited. Must be a + /// valid PNG file, less than 4MB, and have the same dimensions as + /// `image`. + /// - `image`: The image to edit. Must be a valid PNG file, less than 4MB, + /// and square. If mask is not provided, image must have transparency, + /// which will be used as the mask. + ///```ignore + /// let response = client.create_image_edit() + /// .body(body) + /// .mask(mask) + /// .image(image) + /// .send() + /// .await; + /// ``` + pub fn create_image_edit(&self) -> builder::CreateImageEdit { + builder::CreateImageEdit::new(self) + } + + ///Creates a variation of a given image + /// + ///Sends a `POST` request to `/images/variations` + /// + ///Arguments: + /// - `body` + /// - `image`: The image to use as the basis for the variation(s). Must be a + /// valid PNG file, less than 4MB, and square. + ///```ignore + /// let response = client.create_image_variation() + /// .body(body) + /// .image(image) + /// .send() + /// .await; + /// ``` + pub fn create_image_variation(&self) -> builder::CreateImageVariation { + builder::CreateImageVariation::new(self) + } +} + +pub mod builder { + use super::types; + #[allow(unused_imports)] + use super::{ + encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, + ResponseValue, + }; + ///Builder for [`Client::create_image`] + /// + ///[`Client::create_image`]: super::Client::create_image + #[derive(Debug, Clone)] + pub struct CreateImage<'a> { + client: &'a super::Client, + body: Result, + } + + impl<'a> CreateImage<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + body: Ok(types::builder::CreateImageRequest::default()), + } + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.body = value + .try_into() + .map(From::from) + .map_err(|_| "conversion to `CreateImageRequest` for body failed".to_string()); + self + } + + pub fn body_map(mut self, f: F) -> Self + where + F: std::ops::FnOnce( + types::builder::CreateImageRequest, + ) -> types::builder::CreateImageRequest, + { + self.body = self.body.map(f); + self + } + + ///Sends a `POST` request to `/images/generations` + pub async fn send(self) -> Result, Error<()>> { + let Self { client, body } = self; + let body = body + .and_then(std::convert::TryInto::::try_into) + .map_err(Error::InvalidRequest)?; + let url = format!("{}/images/generations", client.baseurl,); + let request = client + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + + ///Builder for [`Client::create_image_edit`] + /// + ///[`Client::create_image_edit`]: super::Client::create_image_edit + #[derive(Debug)] + pub struct CreateImageEdit<'a> { + client: &'a super::Client, + body: Result, + mask: Result, String>, + image: Result, + } + + impl<'a> CreateImageEdit<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + body: Ok(types::builder::CreateImageEditRequest::default()), + mask: Ok(None), + image: Err("image was not initialized".to_string()), + } + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.body = value + .try_into() + .map(From::from) + .map_err(|_| "conversion to `CreateImageEditRequest` for body failed".to_string()); + self + } + + pub fn body_map(mut self, f: F) -> Self + where + F: std::ops::FnOnce( + types::builder::CreateImageEditRequest, + ) -> types::builder::CreateImageEditRequest, + { + self.body = self.body.map(f); + self + } + + pub fn mask(mut self, value: Option) -> Self { + self.mask = Ok(value); + self + } + + pub fn image(mut self, value: Part) -> Self { + self.image = Ok(value); + self + } + + ///Sends a `POST` request to `/images/edits` + pub async fn send(self) -> Result, Error<()>> { + let Self { + client, + body, + mask, + image, + } = self; + let body = body + .and_then(std::convert::TryInto::::try_into) + .map_err(Error::InvalidRequest)?; + let mask = mask.map_err(Error::InvalidRequest)?; + let image = image.map_err(Error::InvalidRequest)?; + let url = format!("{}/images/edits", client.baseurl,); + let body = serde_json::to_value(body) + .map_err(|e| Error::InvalidRequest(e.to_string()))? + .as_object() + .cloned() + .ok_or_else(|| Error::InvalidRequest("Serializing body failed.".to_string()))?; + let mut form = reqwest::multipart::Form::new().part("image", image); + if let Some(mask) = mask { + form = form.part("mask", mask); + }; + if let Some(v) = body.get("n").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("n", v); + }; + if let Some(v) = body.get("prompt").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("prompt", v); + }; + if let Some(v) = body.get("response_format").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("response_format", v); + }; + if let Some(v) = body.get("size").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("size", v); + }; + if let Some(v) = body.get("user").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("user", v); + }; + let request = client + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .multipart(form) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + + ///Builder for [`Client::create_image_variation`] + /// + ///[`Client::create_image_variation`]: super::Client::create_image_variation + #[derive(Debug)] + pub struct CreateImageVariation<'a> { + client: &'a super::Client, + body: Result, + image: Result, + } + + impl<'a> CreateImageVariation<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + body: Ok(types::builder::CreateImageVariationRequest::default()), + image: Err("image was not initialized".to_string()), + } + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.body = value.try_into().map(From::from).map_err(|_| { + "conversion to `CreateImageVariationRequest` for body failed".to_string() + }); + self + } + + pub fn body_map(mut self, f: F) -> Self + where + F: std::ops::FnOnce( + types::builder::CreateImageVariationRequest, + ) -> types::builder::CreateImageVariationRequest, + { + self.body = self.body.map(f); + self + } + + pub fn image(mut self, value: Part) -> Self { + self.image = Ok(value); + self + } + + ///Sends a `POST` request to `/images/variations` + pub async fn send(self) -> Result, Error<()>> { + let Self { + client, + body, + image, + } = self; + let body = body + .and_then(std::convert::TryInto::::try_into) + .map_err(Error::InvalidRequest)?; + let image = image.map_err(Error::InvalidRequest)?; + let url = format!("{}/images/variations", client.baseurl,); + let body = serde_json::to_value(body) + .map_err(|e| Error::InvalidRequest(e.to_string()))? + .as_object() + .cloned() + .ok_or_else(|| Error::InvalidRequest("Serializing body failed.".to_string()))?; + let mut form = reqwest::multipart::Form::new().part("image", image); + if let Some(v) = body.get("n").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("n", v); + }; + if let Some(v) = body.get("response_format").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("response_format", v); + }; + if let Some(v) = body.get("size").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("size", v); + }; + if let Some(v) = body.get("user").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("user", v); + }; + let request = client + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .multipart(form) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + } +} + +pub mod prelude { + pub use self::super::Client; +} diff --git a/progenitor-impl/tests/output/openai-openapi-cli.out b/progenitor-impl/tests/output/openai-openapi-cli.out new file mode 100644 index 00000000..593c73f1 --- /dev/null +++ b/progenitor-impl/tests/output/openai-openapi-cli.out @@ -0,0 +1,444 @@ +pub struct Cli { + client: sdk::Client, + over: T, +} + +impl Cli { + pub fn new(client: sdk::Client) -> Self { + Self { client, over: () } + } + + pub fn get_command(cmd: CliCommand) -> clap::Command { + match cmd { + CliCommand::CreateImage => Self::cli_create_image(), + CliCommand::CreateImageEdit => Self::cli_create_image_edit(), + CliCommand::CreateImageVariation => Self::cli_create_image_variation(), + } + } + + pub fn cli_create_image() -> clap::Command { + clap::Command::new("") + .arg( + clap::Arg::new("n") + .long("n") + .value_parser(clap::value_parser!(i64)) + .required(false) + .help("The number of images to generate. Must be between 1 and 10."), + ) + .arg( + clap::Arg::new("prompt") + .long("prompt") + .value_parser(clap::value_parser!(String)) + .required_unless_present("json-body") + .help( + "A text description of the desired image(s). The maximum length is 1000 \ + characters.", + ), + ) + .arg( + clap::Arg::new("response-format") + .long("response-format") + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::CreateImageRequestResponseFormat::Url.to_string(), + types::CreateImageRequestResponseFormat::B64Json.to_string(), + ]), + |s| types::CreateImageRequestResponseFormat::try_from(s).unwrap(), + )) + .required(false) + .help( + "The format in which the generated images are returned. Must be one of \ + `url` or `b64_json`.", + ), + ) + .arg( + clap::Arg::new("size") + .long("size") + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::CreateImageRequestSize::_256x256.to_string(), + types::CreateImageRequestSize::_512x512.to_string(), + types::CreateImageRequestSize::_1024x1024.to_string(), + ]), + |s| types::CreateImageRequestSize::try_from(s).unwrap(), + )) + .required(false) + .help( + "The size of the generated images. Must be one of `256x256`, `512x512`, \ + or `1024x1024`.", + ), + ) + .arg( + clap::Arg::new("user") + .long("user") + .value_parser(clap::value_parser!(String)) + .required(false) + .help( + "A unique identifier representing your end-user, which can help OpenAI to \ + monitor and detect abuse. [Learn \ + more](/docs/guides/safety-best-practices/end-user-ids).\n", + ), + ) + .arg( + clap::Arg::new("json-body") + .long("json-body") + .value_name("JSON-FILE") + .required(false) + .value_parser(clap::value_parser!(std::path::PathBuf)) + .help("Path to a file that contains the full json body."), + ) + .arg( + clap::Arg::new("json-body-template") + .long("json-body-template") + .action(clap::ArgAction::SetTrue) + .help("XXX"), + ) + .about("Creates an image given a prompt.") + } + + pub fn cli_create_image_edit() -> clap::Command { + clap::Command::new("") + .arg( + clap::Arg::new("n") + .long("n") + .value_parser(clap::value_parser!(i64)) + .required(false) + .help("The number of images to generate. Must be between 1 and 10."), + ) + .arg( + clap::Arg::new("prompt") + .long("prompt") + .value_parser(clap::value_parser!(String)) + .required_unless_present("json-body") + .help( + "A text description of the desired image(s). The maximum length is 1000 \ + characters.", + ), + ) + .arg( + clap::Arg::new("response-format") + .long("response-format") + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::CreateImageEditRequestResponseFormat::Url.to_string(), + types::CreateImageEditRequestResponseFormat::B64Json.to_string(), + ]), + |s| types::CreateImageEditRequestResponseFormat::try_from(s).unwrap(), + )) + .required(false) + .help( + "The format in which the generated images are returned. Must be one of \ + `url` or `b64_json`.", + ), + ) + .arg( + clap::Arg::new("size") + .long("size") + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::CreateImageEditRequestSize::_256x256.to_string(), + types::CreateImageEditRequestSize::_512x512.to_string(), + types::CreateImageEditRequestSize::_1024x1024.to_string(), + ]), + |s| types::CreateImageEditRequestSize::try_from(s).unwrap(), + )) + .required(false) + .help( + "The size of the generated images. Must be one of `256x256`, `512x512`, \ + or `1024x1024`.", + ), + ) + .arg( + clap::Arg::new("user") + .long("user") + .value_parser(clap::value_parser!(String)) + .required(false) + .help( + "A unique identifier representing your end-user, which can help OpenAI to \ + monitor and detect abuse. [Learn \ + more](/docs/guides/safety-best-practices/end-user-ids).\n", + ), + ) + .arg( + clap::Arg::new("json-body") + .long("json-body") + .value_name("JSON-FILE") + .required(true) + .value_parser(clap::value_parser!(std::path::PathBuf)) + .help("Path to a file that contains the full json body."), + ) + .arg( + clap::Arg::new("json-body-template") + .long("json-body-template") + .action(clap::ArgAction::SetTrue) + .help("XXX"), + ) + .about("Creates an edited or extended image given an original image and a prompt.") + } + + pub fn cli_create_image_variation() -> clap::Command { + clap::Command::new("") + .arg( + clap::Arg::new("n") + .long("n") + .value_parser(clap::value_parser!(i64)) + .required(false) + .help("The number of images to generate. Must be between 1 and 10."), + ) + .arg( + clap::Arg::new("response-format") + .long("response-format") + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::CreateImageVariationRequestResponseFormat::Url.to_string(), + types::CreateImageVariationRequestResponseFormat::B64Json.to_string(), + ]), + |s| types::CreateImageVariationRequestResponseFormat::try_from(s).unwrap(), + )) + .required(false) + .help( + "The format in which the generated images are returned. Must be one of \ + `url` or `b64_json`.", + ), + ) + .arg( + clap::Arg::new("size") + .long("size") + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::CreateImageVariationRequestSize::_256x256.to_string(), + types::CreateImageVariationRequestSize::_512x512.to_string(), + types::CreateImageVariationRequestSize::_1024x1024.to_string(), + ]), + |s| types::CreateImageVariationRequestSize::try_from(s).unwrap(), + )) + .required(false) + .help( + "The size of the generated images. Must be one of `256x256`, `512x512`, \ + or `1024x1024`.", + ), + ) + .arg( + clap::Arg::new("user") + .long("user") + .value_parser(clap::value_parser!(String)) + .required(false) + .help( + "A unique identifier representing your end-user, which can help OpenAI to \ + monitor and detect abuse. [Learn \ + more](/docs/guides/safety-best-practices/end-user-ids).\n", + ), + ) + .arg( + clap::Arg::new("json-body") + .long("json-body") + .value_name("JSON-FILE") + .required(true) + .value_parser(clap::value_parser!(std::path::PathBuf)) + .help("Path to a file that contains the full json body."), + ) + .arg( + clap::Arg::new("json-body-template") + .long("json-body-template") + .action(clap::ArgAction::SetTrue) + .help("XXX"), + ) + .about("Creates a variation of a given image.") + } +} + +impl Cli { + pub fn new_with_override(client: sdk::Client, over: T) -> Self { + Self { client, over } + } + + pub async fn execute(&self, cmd: CliCommand, matches: &clap::ArgMatches) { + match cmd { + CliCommand::CreateImage => { + self.execute_create_image(matches).await; + } + CliCommand::CreateImageEdit => { + self.execute_create_image_edit(matches).await; + } + CliCommand::CreateImageVariation => { + self.execute_create_image_variation(matches).await; + } + } + } + + pub async fn execute_create_image(&self, matches: &clap::ArgMatches) { + let mut request = self.client.create_image(); + if let Some(value) = matches.get_one::("n") { + request = request.body_map(|body| body.n(value.clone())) + } + + if let Some(value) = matches.get_one::("prompt") { + request = request.body_map(|body| body.prompt(value.clone())) + } + + if let Some(value) = + matches.get_one::("response-format") + { + request = request.body_map(|body| body.response_format(value.clone())) + } + + if let Some(value) = matches.get_one::("size") { + request = request.body_map(|body| body.size(value.clone())) + } + + if let Some(value) = matches.get_one::("user") { + request = request.body_map(|body| body.user(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.over + .execute_create_image(matches, &mut request) + .unwrap(); + let result = request.send().await; + match result { + Ok(r) => { + println!("success\n{:#?}", r) + } + Err(r) => { + println!("success\n{:#?}", r) + } + } + } + + pub async fn execute_create_image_edit(&self, matches: &clap::ArgMatches) { + let mut request = self.client.create_image_edit(); + if let Some(value) = matches.get_one::("n") { + request = request.body_map(|body| body.n(value.clone())) + } + + if let Some(value) = matches.get_one::("prompt") { + request = request.body_map(|body| body.prompt(value.clone())) + } + + if let Some(value) = + matches.get_one::("response-format") + { + request = request.body_map(|body| body.response_format(value.clone())) + } + + if let Some(value) = matches.get_one::("size") { + request = request.body_map(|body| body.size(value.clone())) + } + + if let Some(value) = matches.get_one::("user") { + request = request.body_map(|body| body.user(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = + serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.over + .execute_create_image_edit(matches, &mut request) + .unwrap(); + let result = request.send().await; + match result { + Ok(r) => { + println!("success\n{:#?}", r) + } + Err(r) => { + println!("success\n{:#?}", r) + } + } + } + + pub async fn execute_create_image_variation(&self, matches: &clap::ArgMatches) { + let mut request = self.client.create_image_variation(); + if let Some(value) = matches.get_one::("n") { + request = request.body_map(|body| body.n(value.clone())) + } + + if let Some(value) = + matches.get_one::("response-format") + { + request = request.body_map(|body| body.response_format(value.clone())) + } + + if let Some(value) = matches.get_one::("size") { + request = request.body_map(|body| body.size(value.clone())) + } + + if let Some(value) = matches.get_one::("user") { + request = request.body_map(|body| body.user(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = + serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.over + .execute_create_image_variation(matches, &mut request) + .unwrap(); + let result = request.send().await; + match result { + Ok(r) => { + println!("success\n{:#?}", r) + } + Err(r) => { + println!("success\n{:#?}", r) + } + } + } +} + +pub trait CliOverride { + fn execute_create_image( + &self, + matches: &clap::ArgMatches, + request: &mut builder::CreateImage, + ) -> Result<(), String> { + Ok(()) + } + + fn execute_create_image_edit( + &self, + matches: &clap::ArgMatches, + request: &mut builder::CreateImageEdit, + ) -> Result<(), String> { + Ok(()) + } + + fn execute_create_image_variation( + &self, + matches: &clap::ArgMatches, + request: &mut builder::CreateImageVariation, + ) -> Result<(), String> { + Ok(()) + } +} + +impl CliOverride for () {} + +#[derive(Copy, Clone, Debug)] +pub enum CliCommand { + CreateImage, + CreateImageEdit, + CreateImageVariation, +} + +impl CliCommand { + pub fn iter() -> impl Iterator { + vec![ + CliCommand::CreateImage, + CliCommand::CreateImageEdit, + CliCommand::CreateImageVariation, + ] + .into_iter() + } +} diff --git a/progenitor-impl/tests/output/openai-openapi-httpmock.out b/progenitor-impl/tests/output/openai-openapi-httpmock.out new file mode 100644 index 00000000..c5984b11 --- /dev/null +++ b/progenitor-impl/tests/output/openai-openapi-httpmock.out @@ -0,0 +1,188 @@ +pub mod operations { + #![doc = r" [`When`](httpmock::When) and [`Then`](httpmock::Then)"] + #![doc = r" wrappers for each operation. Each can be converted to"] + #![doc = r" its inner type with a call to `into_inner()`. This can"] + #![doc = r" be used to explicitly deviate from permitted values."] + use sdk::*; + pub struct CreateImageWhen(httpmock::When); + impl CreateImageWhen { + pub fn new(inner: httpmock::When) -> Self { + Self( + inner + .method(httpmock::Method::POST) + .path_matches(regex::Regex::new("^/images/generations$").unwrap()), + ) + } + + pub fn into_inner(self) -> httpmock::When { + self.0 + } + + pub fn body(self, value: &types::CreateImageRequest) -> Self { + Self(self.0.json_body_obj(value)) + } + } + + pub struct CreateImageThen(httpmock::Then); + impl CreateImageThen { + pub fn new(inner: httpmock::Then) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> httpmock::Then { + self.0 + } + + pub fn ok(self, value: &types::ImagesResponse) -> Self { + Self( + self.0 + .status(200u16) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + } + + pub struct CreateImageEditWhen(httpmock::When); + impl CreateImageEditWhen { + pub fn new(inner: httpmock::When) -> Self { + Self( + inner + .method(httpmock::Method::POST) + .path_matches(regex::Regex::new("^/images/edits$").unwrap()), + ) + } + + pub fn into_inner(self) -> httpmock::When { + self.0 + } + + pub fn body(self, value: &types::CreateImageEditRequest) -> Self { + Self(self.0.json_body_obj(value)) + } + + pub fn mask(self, value: Option) -> Self { + self.0 + } + + pub fn image(self, value: Part) -> Self { + self.0 + } + } + + pub struct CreateImageEditThen(httpmock::Then); + impl CreateImageEditThen { + pub fn new(inner: httpmock::Then) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> httpmock::Then { + self.0 + } + + pub fn ok(self, value: &types::ImagesResponse) -> Self { + Self( + self.0 + .status(200u16) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + } + + pub struct CreateImageVariationWhen(httpmock::When); + impl CreateImageVariationWhen { + pub fn new(inner: httpmock::When) -> Self { + Self( + inner + .method(httpmock::Method::POST) + .path_matches(regex::Regex::new("^/images/variations$").unwrap()), + ) + } + + pub fn into_inner(self) -> httpmock::When { + self.0 + } + + pub fn body(self, value: &types::CreateImageVariationRequest) -> Self { + Self(self.0.json_body_obj(value)) + } + + pub fn image(self, value: Part) -> Self { + self.0 + } + } + + pub struct CreateImageVariationThen(httpmock::Then); + impl CreateImageVariationThen { + pub fn new(inner: httpmock::Then) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> httpmock::Then { + self.0 + } + + pub fn ok(self, value: &types::ImagesResponse) -> Self { + Self( + self.0 + .status(200u16) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + } +} + +#[doc = r" An extension trait for [`MockServer`](httpmock::MockServer) that"] +#[doc = r" adds a method for each operation. These are the equivalent of"] +#[doc = r" type-checked [`mock()`](httpmock::MockServer::mock) calls."] +pub trait MockServerExt { + fn create_image(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::CreateImageWhen, operations::CreateImageThen); + fn create_image_edit(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::CreateImageEditWhen, operations::CreateImageEditThen); + fn create_image_variation(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::CreateImageVariationWhen, operations::CreateImageVariationThen); +} + +impl MockServerExt for httpmock::MockServer { + fn create_image(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::CreateImageWhen, operations::CreateImageThen), + { + self.mock(|when, then| { + config_fn( + operations::CreateImageWhen::new(when), + operations::CreateImageThen::new(then), + ) + }) + } + + fn create_image_edit(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::CreateImageEditWhen, operations::CreateImageEditThen), + { + self.mock(|when, then| { + config_fn( + operations::CreateImageEditWhen::new(when), + operations::CreateImageEditThen::new(then), + ) + }) + } + + fn create_image_variation(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::CreateImageVariationWhen, operations::CreateImageVariationThen), + { + self.mock(|when, then| { + config_fn( + operations::CreateImageVariationWhen::new(when), + operations::CreateImageVariationThen::new(then), + ) + }) + } +} diff --git a/progenitor-impl/tests/output/openai-openapi-positional.out b/progenitor-impl/tests/output/openai-openapi-positional.out new file mode 100644 index 00000000..e335e5d9 --- /dev/null +++ b/progenitor-impl/tests/output/openai-openapi-positional.out @@ -0,0 +1,778 @@ +#[allow(unused_imports)] +use progenitor_client::{encode_path, RequestBuilderExt}; +pub use progenitor_client::{ByteStream, Error, ResponseValue}; +#[allow(unused_imports)] +use reqwest::header::{HeaderMap, HeaderValue}; +#[allow(unused_imports)] +use reqwest::multipart::Part; +pub mod types { + use serde::{Deserialize, Serialize}; + #[allow(unused_imports)] + use std::convert::TryFrom; + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateImageEditRequest { + ///The image to edit. Must be a valid PNG file, less than 4MB, and + /// square. If mask is not provided, image must have transparency, which + /// will be used as the mask. + pub image: String, + ///An additional image whose fully transparent areas (e.g. where alpha + /// is zero) indicate where `image` should be edited. Must be a valid + /// PNG file, less than 4MB, and have the same dimensions as `image`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mask: Option, + ///The number of images to generate. Must be between 1 and 10. + #[serde(default = "defaults::create_image_edit_request_n")] + pub n: Option, + ///A text description of the desired image(s). The maximum length is + /// 1000 characters. + pub prompt: String, + ///The format in which the generated images are returned. Must be one + /// of `url` or `b64_json`. + #[serde(default = "defaults::create_image_edit_request_response_format")] + pub response_format: Option, + ///The size of the generated images. Must be one of `256x256`, + /// `512x512`, or `1024x1024`. + #[serde(default = "defaults::create_image_edit_request_size")] + pub size: Option, + ///A unique identifier representing your end-user, which can help + /// OpenAI to monitor and detect abuse. [Learn + /// more](/docs/guides/safety-best-practices/end-user-ids). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, + } + + impl From<&CreateImageEditRequest> for CreateImageEditRequest { + fn from(value: &CreateImageEditRequest) -> Self { + value.clone() + } + } + + ///The format in which the generated images are returned. Must be one of + /// `url` or `b64_json`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageEditRequestResponseFormat { + #[serde(rename = "url")] + Url, + #[serde(rename = "b64_json")] + B64Json, + } + + impl From<&CreateImageEditRequestResponseFormat> for CreateImageEditRequestResponseFormat { + fn from(value: &CreateImageEditRequestResponseFormat) -> Self { + value.clone() + } + } + + impl ToString for CreateImageEditRequestResponseFormat { + fn to_string(&self) -> String { + match *self { + Self::Url => "url".to_string(), + Self::B64Json => "b64_json".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageEditRequestResponseFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "url" => Ok(Self::Url), + "b64_json" => Ok(Self::B64Json), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageEditRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageEditRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageEditRequestResponseFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + ///The size of the generated images. Must be one of `256x256`, `512x512`, + /// or `1024x1024`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageEditRequestSize { + #[serde(rename = "256x256")] + _256x256, + #[serde(rename = "512x512")] + _512x512, + #[serde(rename = "1024x1024")] + _1024x1024, + } + + impl From<&CreateImageEditRequestSize> for CreateImageEditRequestSize { + fn from(value: &CreateImageEditRequestSize) -> Self { + value.clone() + } + } + + impl ToString for CreateImageEditRequestSize { + fn to_string(&self) -> String { + match *self { + Self::_256x256 => "256x256".to_string(), + Self::_512x512 => "512x512".to_string(), + Self::_1024x1024 => "1024x1024".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageEditRequestSize { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "256x256" => Ok(Self::_256x256), + "512x512" => Ok(Self::_512x512), + "1024x1024" => Ok(Self::_1024x1024), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageEditRequestSize { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageEditRequestSize { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageEditRequestSize { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateImageRequest { + ///The number of images to generate. Must be between 1 and 10. + #[serde(default = "defaults::create_image_request_n")] + pub n: Option, + ///A text description of the desired image(s). The maximum length is + /// 1000 characters. + pub prompt: String, + ///The format in which the generated images are returned. Must be one + /// of `url` or `b64_json`. + #[serde(default = "defaults::create_image_request_response_format")] + pub response_format: Option, + ///The size of the generated images. Must be one of `256x256`, + /// `512x512`, or `1024x1024`. + #[serde(default = "defaults::create_image_request_size")] + pub size: Option, + ///A unique identifier representing your end-user, which can help + /// OpenAI to monitor and detect abuse. [Learn + /// more](/docs/guides/safety-best-practices/end-user-ids). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, + } + + impl From<&CreateImageRequest> for CreateImageRequest { + fn from(value: &CreateImageRequest) -> Self { + value.clone() + } + } + + ///The format in which the generated images are returned. Must be one of + /// `url` or `b64_json`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageRequestResponseFormat { + #[serde(rename = "url")] + Url, + #[serde(rename = "b64_json")] + B64Json, + } + + impl From<&CreateImageRequestResponseFormat> for CreateImageRequestResponseFormat { + fn from(value: &CreateImageRequestResponseFormat) -> Self { + value.clone() + } + } + + impl ToString for CreateImageRequestResponseFormat { + fn to_string(&self) -> String { + match *self { + Self::Url => "url".to_string(), + Self::B64Json => "b64_json".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageRequestResponseFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "url" => Ok(Self::Url), + "b64_json" => Ok(Self::B64Json), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageRequestResponseFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + ///The size of the generated images. Must be one of `256x256`, `512x512`, + /// or `1024x1024`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageRequestSize { + #[serde(rename = "256x256")] + _256x256, + #[serde(rename = "512x512")] + _512x512, + #[serde(rename = "1024x1024")] + _1024x1024, + } + + impl From<&CreateImageRequestSize> for CreateImageRequestSize { + fn from(value: &CreateImageRequestSize) -> Self { + value.clone() + } + } + + impl ToString for CreateImageRequestSize { + fn to_string(&self) -> String { + match *self { + Self::_256x256 => "256x256".to_string(), + Self::_512x512 => "512x512".to_string(), + Self::_1024x1024 => "1024x1024".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageRequestSize { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "256x256" => Ok(Self::_256x256), + "512x512" => Ok(Self::_512x512), + "1024x1024" => Ok(Self::_1024x1024), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageRequestSize { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageRequestSize { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageRequestSize { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateImageVariationRequest { + ///The image to use as the basis for the variation(s). Must be a valid + /// PNG file, less than 4MB, and square. + pub image: String, + ///The number of images to generate. Must be between 1 and 10. + #[serde(default = "defaults::create_image_variation_request_n")] + pub n: Option, + ///The format in which the generated images are returned. Must be one + /// of `url` or `b64_json`. + #[serde(default = "defaults::create_image_variation_request_response_format")] + pub response_format: Option, + ///The size of the generated images. Must be one of `256x256`, + /// `512x512`, or `1024x1024`. + #[serde(default = "defaults::create_image_variation_request_size")] + pub size: Option, + ///A unique identifier representing your end-user, which can help + /// OpenAI to monitor and detect abuse. [Learn + /// more](/docs/guides/safety-best-practices/end-user-ids). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, + } + + impl From<&CreateImageVariationRequest> for CreateImageVariationRequest { + fn from(value: &CreateImageVariationRequest) -> Self { + value.clone() + } + } + + ///The format in which the generated images are returned. Must be one of + /// `url` or `b64_json`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageVariationRequestResponseFormat { + #[serde(rename = "url")] + Url, + #[serde(rename = "b64_json")] + B64Json, + } + + impl From<&CreateImageVariationRequestResponseFormat> + for CreateImageVariationRequestResponseFormat + { + fn from(value: &CreateImageVariationRequestResponseFormat) -> Self { + value.clone() + } + } + + impl ToString for CreateImageVariationRequestResponseFormat { + fn to_string(&self) -> String { + match *self { + Self::Url => "url".to_string(), + Self::B64Json => "b64_json".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageVariationRequestResponseFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "url" => Ok(Self::Url), + "b64_json" => Ok(Self::B64Json), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageVariationRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageVariationRequestResponseFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageVariationRequestResponseFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + ///The size of the generated images. Must be one of `256x256`, `512x512`, + /// or `1024x1024`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageVariationRequestSize { + #[serde(rename = "256x256")] + _256x256, + #[serde(rename = "512x512")] + _512x512, + #[serde(rename = "1024x1024")] + _1024x1024, + } + + impl From<&CreateImageVariationRequestSize> for CreateImageVariationRequestSize { + fn from(value: &CreateImageVariationRequestSize) -> Self { + value.clone() + } + } + + impl ToString for CreateImageVariationRequestSize { + fn to_string(&self) -> String { + match *self { + Self::_256x256 => "256x256".to_string(), + Self::_512x512 => "512x512".to_string(), + Self::_1024x1024 => "1024x1024".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageVariationRequestSize { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "256x256" => Ok(Self::_256x256), + "512x512" => Ok(Self::_512x512), + "1024x1024" => Ok(Self::_1024x1024), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageVariationRequestSize { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageVariationRequestSize { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageVariationRequestSize { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Error { + pub code: Option, + pub message: String, + pub param: Option, + #[serde(rename = "type")] + pub type_: String, + } + + impl From<&Error> for Error { + fn from(value: &Error) -> Self { + value.clone() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ErrorResponse { + pub error: Error, + } + + impl From<&ErrorResponse> for ErrorResponse { + fn from(value: &ErrorResponse) -> Self { + value.clone() + } + } + + ///Represents the url or the content of an image generated by the OpenAI + /// API. + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Image { + ///The base64-encoded JSON of the generated image, if `response_format` + /// is `b64_json`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub b64_json: Option, + ///The URL of the generated image, if `response_format` is `url` + /// (default). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url: Option, + } + + impl From<&Image> for Image { + fn from(value: &Image) -> Self { + value.clone() + } + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ImagesResponse { + pub created: i64, + pub data: Vec, + } + + impl From<&ImagesResponse> for ImagesResponse { + fn from(value: &ImagesResponse) -> Self { + value.clone() + } + } + + pub mod defaults { + pub(super) fn create_image_edit_request_n() -> Option { + Some(1_i64) + } + + pub(super) fn create_image_edit_request_response_format( + ) -> Option { + Some(super::CreateImageEditRequestResponseFormat::Url) + } + + pub(super) fn create_image_edit_request_size() -> Option + { + Some(super::CreateImageEditRequestSize::_1024x1024) + } + + pub(super) fn create_image_request_n() -> Option { + Some(1_i64) + } + + pub(super) fn create_image_request_response_format( + ) -> Option { + Some(super::CreateImageRequestResponseFormat::Url) + } + + pub(super) fn create_image_request_size() -> Option { + Some(super::CreateImageRequestSize::_1024x1024) + } + + pub(super) fn create_image_variation_request_n() -> Option { + Some(1_i64) + } + + pub(super) fn create_image_variation_request_response_format( + ) -> Option { + Some(super::CreateImageVariationRequestResponseFormat::Url) + } + + pub(super) fn create_image_variation_request_size( + ) -> Option { + Some(super::CreateImageVariationRequestSize::_1024x1024) + } + } +} + +#[derive(Clone, Debug)] +///Client for OpenAI API +/// +///The OpenAI REST API. Please see https://platform.openai.com/docs/api-reference for more details. +/// +///https://openai.com/policies/terms-of-use +/// +///Version: 2.0.0 +pub struct Client { + pub(crate) baseurl: String, + pub(crate) client: reqwest::Client, +} + +impl Client { + /// Create a new client. + /// + /// `baseurl` is the base URL provided to the internal + /// `reqwest::Client`, and should include a scheme and hostname, + /// as well as port and a path stem if applicable. + pub fn new(baseurl: &str) -> Self { + #[cfg(not(target_arch = "wasm32"))] + let client = { + let dur = std::time::Duration::from_secs(15); + reqwest::ClientBuilder::new() + .connect_timeout(dur) + .timeout(dur) + }; + #[cfg(target_arch = "wasm32")] + let client = reqwest::ClientBuilder::new(); + Self::new_with_client(baseurl, client.build().unwrap()) + } + + /// Construct a new client with an existing `reqwest::Client`, + /// allowing more control over its configuration. + /// + /// `baseurl` is the base URL provided to the internal + /// `reqwest::Client`, and should include a scheme and hostname, + /// as well as port and a path stem if applicable. + pub fn new_with_client(baseurl: &str, client: reqwest::Client) -> Self { + Self { + baseurl: baseurl.to_string(), + client, + } + } + + /// Get the base URL to which requests are made. + pub fn baseurl(&self) -> &String { + &self.baseurl + } + + /// Get the internal `reqwest::Client` used to make requests. + pub fn client(&self) -> &reqwest::Client { + &self.client + } + + /// Get the version of this API. + /// + /// This string is pulled directly from the source OpenAPI + /// document and may be in any format the API selects. + pub fn api_version(&self) -> &'static str { + "2.0.0" + } +} + +impl Client { + ///Creates an image given a prompt + /// + ///Sends a `POST` request to `/images/generations` + pub async fn create_image<'a>( + &'a self, + body: &'a types::CreateImageRequest, + ) -> Result, Error<()>> { + let url = format!("{}/images/generations", self.baseurl,); + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + + ///Creates an edited or extended image given an original image and a prompt + /// + ///Sends a `POST` request to `/images/edits` + /// + ///Arguments: + /// - `body` + /// - `mask`: An additional image whose fully transparent areas (e.g. where + /// alpha is zero) indicate where `image` should be edited. Must be a + /// valid PNG file, less than 4MB, and have the same dimensions as + /// `image`. + /// - `image`: The image to edit. Must be a valid PNG file, less than 4MB, + /// and square. If mask is not provided, image must have transparency, + /// which will be used as the mask. + pub async fn create_image_edit<'a>( + &'a self, + body: &'a types::CreateImageEditRequest, + mask: Option, + image: Part, + ) -> Result, Error<()>> { + let url = format!("{}/images/edits", self.baseurl,); + let body = serde_json::to_value(body) + .map_err(|e| Error::InvalidRequest(e.to_string()))? + .as_object() + .cloned() + .ok_or_else(|| Error::InvalidRequest("Serializing body failed.".to_string()))?; + let mut form = reqwest::multipart::Form::new().part("image", image); + if let Some(mask) = mask { + form = form.part("mask", mask); + }; + if let Some(v) = body.get("n").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("n", v); + }; + if let Some(v) = body.get("prompt").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("prompt", v); + }; + if let Some(v) = body.get("response_format").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("response_format", v); + }; + if let Some(v) = body.get("size").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("size", v); + }; + if let Some(v) = body.get("user").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("user", v); + }; + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .multipart(form) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } + + ///Creates a variation of a given image + /// + ///Sends a `POST` request to `/images/variations` + /// + ///Arguments: + /// - `body` + /// - `image`: The image to use as the basis for the variation(s). Must be a + /// valid PNG file, less than 4MB, and square. + pub async fn create_image_variation<'a>( + &'a self, + body: &'a types::CreateImageVariationRequest, + image: Part, + ) -> Result, Error<()>> { + let url = format!("{}/images/variations", self.baseurl,); + let body = serde_json::to_value(body) + .map_err(|e| Error::InvalidRequest(e.to_string()))? + .as_object() + .cloned() + .ok_or_else(|| Error::InvalidRequest("Serializing body failed.".to_string()))?; + let mut form = reqwest::multipart::Form::new().part("image", image); + if let Some(v) = body.get("n").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("n", v); + }; + if let Some(v) = body.get("response_format").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("response_format", v); + }; + if let Some(v) = body.get("size").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("size", v); + }; + if let Some(v) = body.get("user").map(serde_json::to_string) { + let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + form = form.text("user", v); + }; + let request = self + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .multipart(form) + .build()?; + let result = self.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + _ => Err(Error::UnexpectedResponse(response)), + } + } +} + +pub mod prelude { + pub use super::Client; +} diff --git a/progenitor-impl/tests/test_output.rs b/progenitor-impl/tests/test_output.rs index d50325af..15c3a69b 100644 --- a/progenitor-impl/tests/test_output.rs +++ b/progenitor-impl/tests/test_output.rs @@ -170,3 +170,8 @@ fn test_param_collision() { fn test_github() { verify_apis("api.github.com.json"); } + +#[test] +fn test_openai() { + verify_apis("openai-openapi.yaml") +} From aea0b0ac3f0a9c51254a72f891797aef80a2e6c7 Mon Sep 17 00:00:00 2001 From: ahirner Date: Fri, 27 Oct 2023 02:34:15 +0200 Subject: [PATCH 22/38] don't require (cli) form part WIP reference types are emitted in modified and and modified versions though --- progenitor-impl/src/cli.rs | 91 ++- progenitor-impl/src/lib.rs | 33 +- progenitor-impl/src/method.rs | 70 ++- .../output/openai-openapi-builder-tagged.out | 544 ++++++++++++++++- .../tests/output/openai-openapi-builder.out | 552 +++++++++++++++++- .../tests/output/openai-openapi-cli.out | 101 +++- .../tests/output/openai-openapi-httpmock.out | 4 +- .../output/openai-openapi-positional.out | 322 +++++++++- 8 files changed, 1605 insertions(+), 112 deletions(-) diff --git a/progenitor-impl/src/cli.rs b/progenitor-impl/src/cli.rs index 91ab8f07..96910706 100644 --- a/progenitor-impl/src/cli.rs +++ b/progenitor-impl/src/cli.rs @@ -10,7 +10,8 @@ use typify::{Type, TypeEnumVariant, TypeSpaceImpl, TypeStructPropInfo}; use crate::{ method::{ - OperationParameterKind, OperationParameterType, OperationResponseStatus, + BodyContentType, OperationParameterKind, OperationParameterType, + OperationResponseStatus, }, to_schema::ToSchema, util::{sanitize, Case}, @@ -393,28 +394,15 @@ impl Generator { args.add_arg(arg_name, CliArg { parser, consumer }) } - let maybe_body_type_id = method - .params - .iter() - .find(|param| { - matches!(¶m.kind, OperationParameterKind::Body(_)) - }) - .and_then(|param| match ¶m.typ { - // TODO not sure how to deal with raw bodies, but we definitely - // need **some** input so we shouldn't just ignore it... as we - // are currently... - OperationParameterType::RawBody => None, - - OperationParameterType::Type(body_type_id) => { - Some(body_type_id) - } - OperationParameterType::FormPart => { - let content_type = ¶m.kind; - eprintln!( - "todo: add --{}-file parameter for {content_type:?})", - param.name - ); - None + // args that destructure a structured body + let maybe_body_type_id = + method.params.iter().find_map(|param| { + match (¶m.kind, ¶m.typ) { + ( + OperationParameterKind::Body(_), + OperationParameterType::Type(body_type_id), + ) => Some(body_type_id), + _ => None, } }); @@ -438,6 +426,63 @@ impl Generator { } } + // args to send binary files + let body_file_args = method + .params + .iter() + .filter_map(|param| match (¶m.kind, ¶m.typ) { + // TODO: impl, which should be pretty + // straight forward with a custom consumer + ( _, OperationParameterType::RawBody) => None, + // TODO: impl, which should be pretty + // straight forward with a custom consumer + ( OperationParameterKind::Body(BodyContentType::OctetStream), + _, + ) => None, + ( + OperationParameterKind::Body(BodyContentType::FormData( + required, + )), + OperationParameterType::FormPart, + ) => Some((param, *required)), + _ => None, + }) + .map(|(param, required)| { + let help = if let Some(description) = ¶m.description { + quote! { .help(#description) } + } else { + quote! { .help("Path to file.") } + }; + let arg_name = param.api_name.to_kebab_case(); + let request_field = format_ident!("{}", param.name); + let parser = quote! { + clap::Arg::new(#arg_name) + .required(#required) + .value_parser(clap::value_parser!(std::path::PathBuf)) + #help + }; + let part = if required { + quote! { part } + } else { + quote ! { Some(part) } + }; + let consumer = quote! { + if let Some(file_name) = matches.get_one::(#arg_name) { + use std::io::Read; + let mut file = std::fs::File::open(&file_name).unwrap(); + let mut value = Vec::new(); + file.read_to_end(&mut value).unwrap(); + // todo: here we can construct a (tokio) stream for + // large files, set mime types etc. + let part = reqwest::multipart::Part::bytes(value); + request = request.#request_field(#part); + } + + }; + (param.api_name.clone(), CliArg { parser, consumer }) + }); + args.args.extend(body_file_args); + let parser_args = args.args.values().map(|CliArg { parser, .. }| parser); diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index 179bc92d..7fda48b5 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -72,27 +72,18 @@ impl Default for GenerationSettings { format: Some("binary".to_string()), ..SchemaObject::default() }; - // todo: best place to create default conversion, or typify? - // todo: declare PartMeta struct and TypeImpls in progenitor_client - /* - use schemars::{schema_for, JsonSchema}; - use serde::Serialize; - #[derive(JsonSchema, Serialize)] - struct PartMeta { - #[serde(default)] - file_name: Option, - // probably wait until Mime in reqwest - #[serde(default)] - mime_str: Option, - } - let bin_schema = schema_for!(PartMeta).schema.into(); - let bin_typ = self - .type_space - .add_type_with_name(&bin_schema, Some("PartMeta".to_string()))?; - */ - // () errors and doesn't convert to unit - let convert = - (bin_string, "Vec<()>".to_string(), vec![TypeImpl::Default]); + // This treats binary strings on the level of generated types. + // For now, we'd want to remove this field because + // the only supported binary string is handled as FormPart instead. + // Not sure if or how patch could help, at least making it not + // required. + // TODO: remove after the right ref objects can be modified. + let convert = ( + bin_string, + // () errors and doesn't convert to unit + "Vec<()>".to_string(), + vec![TypeImpl::Default, TypeImpl::Default, TypeImpl::Display], + ); Self { interface: InterfaceStyle::default(), tag: TagStyle::default(), diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 808e3010..c312981e 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -2225,6 +2225,16 @@ impl Generator { let content_type = BodyContentType::from_str(content_str)?; + let item_schema = schema.item(components)?; + let mut body_params = match (&content_type, &item_schema.schema_kind) { + // add parameters from binary strings of FormData body + ( + BodyContentType::FormData(_), + openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj_type)), + ) => get_form_data_params(obj_type).collect(), + _ => Vec::with_capacity(1), + }; + let typ = match content_type { BodyContentType::OctetStream => { // For an octet stream, we expect a simple, specific schema: @@ -2299,9 +2309,7 @@ impl Generator { }?; OperationParameterType::RawBody } - BodyContentType::Json - | BodyContentType::FormUrlencoded - | BodyContentType::FormData(_) => { + BodyContentType::Json | BodyContentType::FormUrlencoded => { // TODO it would be legal to have the encoding field set for // application/x-www-form-urlencoded content, but I'm not sure // how to interpret the values. @@ -2320,16 +2328,52 @@ impl Generator { .add_type_with_name(&schema.to_schema(), Some(name))?; OperationParameterType::Type(typ) } - }; - - let item_schema = schema.item(components)?; - let mut body_params = match (&content_type, &item_schema.schema_kind) { - // add parameters from binary strings of FormData body - ( - BodyContentType::FormData(_), - openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj_type)), - ) => get_form_data_params(obj_type).collect(), - _ => Vec::with_capacity(1), + BodyContentType::FormData(_) => { + // remove FormPart properties + // TODO: move into util + let schema = match schema { + ReferenceOr::Reference { reference } => { + let idx = reference.rfind('/').unwrap(); + let key = &reference[idx + 1..]; + let parameters = ::get_components( + components.as_ref().unwrap(), + ); + parameters + .get(key) + .cloned() + .unwrap_or_else(|| panic!("key {} is missing", key)) + .into_item() + .unwrap() + } + ReferenceOr::Item(s) => s.to_owned(), + }; + let mut schema = schema.to_schema(); + if let schemars::schema::Schema::Object( + schemars::schema::SchemaObject { + object: Some(ref mut object), + .. + }, + ) = schema + { + for param in body_params.iter() { + dbg!(("remove", ¶m.api_name, ¶m.description)); + object.properties.remove_entry(¶m.api_name); + } + }; + // TODO: because add_ref_types is called before, the unmodified + // body also exists as additional type ending in Request. + // There should be a better way to modify a reference type. + let name = sanitize( + &format!( + "{}-body", + operation.operation_id.as_ref().unwrap(), + ), + Case::Pascal, + ); + let typ = + self.type_space.add_type_with_name(&schema, Some(name))?; + OperationParameterType::Type(typ) + } }; let body_param = OperationParameter { diff --git a/progenitor-impl/tests/output/openai-openapi-builder-tagged.out b/progenitor-impl/tests/output/openai-openapi-builder-tagged.out index 5a80b9f5..1e20354f 100644 --- a/progenitor-impl/tests/output/openai-openapi-builder-tagged.out +++ b/progenitor-impl/tests/output/openai-openapi-builder-tagged.out @@ -9,6 +9,159 @@ pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] use std::convert::TryFrom; + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateImageEditBody { + ///The number of images to generate. Must be between 1 and 10. + #[serde(default = "defaults::create_image_edit_body_n")] + pub n: Option, + ///A text description of the desired image(s). The maximum length is + /// 1000 characters. + pub prompt: String, + ///The format in which the generated images are returned. Must be one + /// of `url` or `b64_json`. + #[serde(default = "defaults::create_image_edit_body_response_format")] + pub response_format: Option, + ///The size of the generated images. Must be one of `256x256`, + /// `512x512`, or `1024x1024`. + #[serde(default = "defaults::create_image_edit_body_size")] + pub size: Option, + ///A unique identifier representing your end-user, which can help + /// OpenAI to monitor and detect abuse. [Learn + /// more](/docs/guides/safety-best-practices/end-user-ids). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, + } + + impl From<&CreateImageEditBody> for CreateImageEditBody { + fn from(value: &CreateImageEditBody) -> Self { + value.clone() + } + } + + impl CreateImageEditBody { + pub fn builder() -> builder::CreateImageEditBody { + builder::CreateImageEditBody::default() + } + } + + ///The format in which the generated images are returned. Must be one of + /// `url` or `b64_json`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageEditBodyResponseFormat { + #[serde(rename = "url")] + Url, + #[serde(rename = "b64_json")] + B64Json, + } + + impl From<&CreateImageEditBodyResponseFormat> for CreateImageEditBodyResponseFormat { + fn from(value: &CreateImageEditBodyResponseFormat) -> Self { + value.clone() + } + } + + impl ToString for CreateImageEditBodyResponseFormat { + fn to_string(&self) -> String { + match *self { + Self::Url => "url".to_string(), + Self::B64Json => "b64_json".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageEditBodyResponseFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "url" => Ok(Self::Url), + "b64_json" => Ok(Self::B64Json), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageEditBodyResponseFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageEditBodyResponseFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageEditBodyResponseFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + ///The size of the generated images. Must be one of `256x256`, `512x512`, + /// or `1024x1024`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageEditBodySize { + #[serde(rename = "256x256")] + _256x256, + #[serde(rename = "512x512")] + _512x512, + #[serde(rename = "1024x1024")] + _1024x1024, + } + + impl From<&CreateImageEditBodySize> for CreateImageEditBodySize { + fn from(value: &CreateImageEditBodySize) -> Self { + value.clone() + } + } + + impl ToString for CreateImageEditBodySize { + fn to_string(&self) -> String { + match *self { + Self::_256x256 => "256x256".to_string(), + Self::_512x512 => "512x512".to_string(), + Self::_1024x1024 => "1024x1024".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageEditBodySize { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "256x256" => Ok(Self::_256x256), + "512x512" => Ok(Self::_512x512), + "1024x1024" => Ok(Self::_1024x1024), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageEditBodySize { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageEditBodySize { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageEditBodySize { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct CreateImageEditRequest { ///The image to edit. Must be a valid PNG file, less than 4MB, and @@ -324,6 +477,156 @@ pub mod types { } } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateImageVariationBody { + ///The number of images to generate. Must be between 1 and 10. + #[serde(default = "defaults::create_image_variation_body_n")] + pub n: Option, + ///The format in which the generated images are returned. Must be one + /// of `url` or `b64_json`. + #[serde(default = "defaults::create_image_variation_body_response_format")] + pub response_format: Option, + ///The size of the generated images. Must be one of `256x256`, + /// `512x512`, or `1024x1024`. + #[serde(default = "defaults::create_image_variation_body_size")] + pub size: Option, + ///A unique identifier representing your end-user, which can help + /// OpenAI to monitor and detect abuse. [Learn + /// more](/docs/guides/safety-best-practices/end-user-ids). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, + } + + impl From<&CreateImageVariationBody> for CreateImageVariationBody { + fn from(value: &CreateImageVariationBody) -> Self { + value.clone() + } + } + + impl CreateImageVariationBody { + pub fn builder() -> builder::CreateImageVariationBody { + builder::CreateImageVariationBody::default() + } + } + + ///The format in which the generated images are returned. Must be one of + /// `url` or `b64_json`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageVariationBodyResponseFormat { + #[serde(rename = "url")] + Url, + #[serde(rename = "b64_json")] + B64Json, + } + + impl From<&CreateImageVariationBodyResponseFormat> for CreateImageVariationBodyResponseFormat { + fn from(value: &CreateImageVariationBodyResponseFormat) -> Self { + value.clone() + } + } + + impl ToString for CreateImageVariationBodyResponseFormat { + fn to_string(&self) -> String { + match *self { + Self::Url => "url".to_string(), + Self::B64Json => "b64_json".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageVariationBodyResponseFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "url" => Ok(Self::Url), + "b64_json" => Ok(Self::B64Json), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageVariationBodyResponseFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageVariationBodyResponseFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageVariationBodyResponseFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + ///The size of the generated images. Must be one of `256x256`, `512x512`, + /// or `1024x1024`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageVariationBodySize { + #[serde(rename = "256x256")] + _256x256, + #[serde(rename = "512x512")] + _512x512, + #[serde(rename = "1024x1024")] + _1024x1024, + } + + impl From<&CreateImageVariationBodySize> for CreateImageVariationBodySize { + fn from(value: &CreateImageVariationBodySize) -> Self { + value.clone() + } + } + + impl ToString for CreateImageVariationBodySize { + fn to_string(&self) -> String { + match *self { + Self::_256x256 => "256x256".to_string(), + Self::_512x512 => "512x512".to_string(), + Self::_1024x1024 => "1024x1024".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageVariationBodySize { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "256x256" => Ok(Self::_256x256), + "512x512" => Ok(Self::_512x512), + "1024x1024" => Ok(Self::_1024x1024), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageVariationBodySize { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageVariationBodySize { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageVariationBodySize { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct CreateImageVariationRequest { ///The image to use as the basis for the variation(s). Must be a valid @@ -562,6 +865,105 @@ pub mod types { } pub mod builder { + #[derive(Clone, Debug)] + pub struct CreateImageEditBody { + n: Result, String>, + prompt: Result, + response_format: Result, String>, + size: Result, String>, + user: Result, String>, + } + + impl Default for CreateImageEditBody { + fn default() -> Self { + Self { + n: Ok(super::defaults::create_image_edit_body_n()), + prompt: Err("no value supplied for prompt".to_string()), + response_format: Ok(super::defaults::create_image_edit_body_response_format()), + size: Ok(super::defaults::create_image_edit_body_size()), + user: Ok(Default::default()), + } + } + } + + impl CreateImageEditBody { + pub fn n(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.n = value + .try_into() + .map_err(|e| format!("error converting supplied value for n: {}", e)); + self + } + pub fn prompt(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.prompt = value + .try_into() + .map_err(|e| format!("error converting supplied value for prompt: {}", e)); + self + } + pub fn response_format(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.response_format = value.try_into().map_err(|e| { + format!("error converting supplied value for response_format: {}", e) + }); + self + } + pub fn size(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.size = value + .try_into() + .map_err(|e| format!("error converting supplied value for size: {}", e)); + self + } + pub fn user(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.user = value + .try_into() + .map_err(|e| format!("error converting supplied value for user: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::CreateImageEditBody { + type Error = String; + fn try_from(value: CreateImageEditBody) -> Result { + Ok(Self { + n: value.n?, + prompt: value.prompt?, + response_format: value.response_format?, + size: value.size?, + user: value.user?, + }) + } + } + + impl From for CreateImageEditBody { + fn from(value: super::CreateImageEditBody) -> Self { + Self { + n: Ok(value.n), + prompt: Ok(value.prompt), + response_format: Ok(value.response_format), + size: Ok(value.size), + user: Ok(value.user), + } + } + } + #[derive(Clone, Debug)] pub struct CreateImageEditRequest { image: Result, String>, @@ -790,6 +1192,93 @@ pub mod types { } } + #[derive(Clone, Debug)] + pub struct CreateImageVariationBody { + n: Result, String>, + response_format: Result, String>, + size: Result, String>, + user: Result, String>, + } + + impl Default for CreateImageVariationBody { + fn default() -> Self { + Self { + n: Ok(super::defaults::create_image_variation_body_n()), + response_format: Ok( + super::defaults::create_image_variation_body_response_format(), + ), + size: Ok(super::defaults::create_image_variation_body_size()), + user: Ok(Default::default()), + } + } + } + + impl CreateImageVariationBody { + pub fn n(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.n = value + .try_into() + .map_err(|e| format!("error converting supplied value for n: {}", e)); + self + } + pub fn response_format(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.response_format = value.try_into().map_err(|e| { + format!("error converting supplied value for response_format: {}", e) + }); + self + } + pub fn size(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.size = value + .try_into() + .map_err(|e| format!("error converting supplied value for size: {}", e)); + self + } + pub fn user(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.user = value + .try_into() + .map_err(|e| format!("error converting supplied value for user: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::CreateImageVariationBody { + type Error = String; + fn try_from(value: CreateImageVariationBody) -> Result { + Ok(Self { + n: value.n?, + response_format: value.response_format?, + size: value.size?, + user: value.user?, + }) + } + } + + impl From for CreateImageVariationBody { + fn from(value: super::CreateImageVariationBody) -> Self { + Self { + n: Ok(value.n), + response_format: Ok(value.response_format), + size: Ok(value.size), + user: Ok(value.user), + } + } + } + #[derive(Clone, Debug)] pub struct CreateImageVariationRequest { image: Result, String>, @@ -1136,6 +1625,19 @@ pub mod types { } pub mod defaults { + pub(super) fn create_image_edit_body_n() -> Option { + Some(1_i64) + } + + pub(super) fn create_image_edit_body_response_format( + ) -> Option { + Some(super::CreateImageEditBodyResponseFormat::Url) + } + + pub(super) fn create_image_edit_body_size() -> Option { + Some(super::CreateImageEditBodySize::_1024x1024) + } + pub(super) fn create_image_edit_request_n() -> Option { Some(1_i64) } @@ -1163,6 +1665,20 @@ pub mod types { Some(super::CreateImageRequestSize::_1024x1024) } + pub(super) fn create_image_variation_body_n() -> Option { + Some(1_i64) + } + + pub(super) fn create_image_variation_body_response_format( + ) -> Option { + Some(super::CreateImageVariationBodyResponseFormat::Url) + } + + pub(super) fn create_image_variation_body_size( + ) -> Option { + Some(super::CreateImageVariationBodySize::_1024x1024) + } + pub(super) fn create_image_variation_request_n() -> Option { Some(1_i64) } @@ -1384,7 +1900,7 @@ pub mod builder { #[derive(Debug)] pub struct CreateImageEdit<'a> { client: &'a super::Client, - body: Result, + body: Result, mask: Result, String>, image: Result, } @@ -1393,7 +1909,7 @@ pub mod builder { pub fn new(client: &'a super::Client) -> Self { Self { client: client, - body: Ok(types::builder::CreateImageEditRequest::default()), + body: Ok(types::builder::CreateImageEditBody::default()), mask: Ok(None), image: Err("image was not initialized".to_string()), } @@ -1401,20 +1917,20 @@ pub mod builder { pub fn body(mut self, value: V) -> Self where - V: std::convert::TryInto, + V: std::convert::TryInto, { self.body = value .try_into() .map(From::from) - .map_err(|_| "conversion to `CreateImageEditRequest` for body failed".to_string()); + .map_err(|_| "conversion to `CreateImageEditBody` for body failed".to_string()); self } pub fn body_map(mut self, f: F) -> Self where F: std::ops::FnOnce( - types::builder::CreateImageEditRequest, - ) -> types::builder::CreateImageEditRequest, + types::builder::CreateImageEditBody, + ) -> types::builder::CreateImageEditBody, { self.body = self.body.map(f); self @@ -1439,7 +1955,7 @@ pub mod builder { image, } = self; let body = body - .and_then(std::convert::TryInto::::try_into) + .and_then(std::convert::TryInto::::try_into) .map_err(Error::InvalidRequest)?; let mask = mask.map_err(Error::InvalidRequest)?; let image = image.map_err(Error::InvalidRequest)?; @@ -1497,7 +2013,7 @@ pub mod builder { #[derive(Debug)] pub struct CreateImageVariation<'a> { client: &'a super::Client, - body: Result, + body: Result, image: Result, } @@ -1505,17 +2021,17 @@ pub mod builder { pub fn new(client: &'a super::Client) -> Self { Self { client: client, - body: Ok(types::builder::CreateImageVariationRequest::default()), + body: Ok(types::builder::CreateImageVariationBody::default()), image: Err("image was not initialized".to_string()), } } pub fn body(mut self, value: V) -> Self where - V: std::convert::TryInto, + V: std::convert::TryInto, { self.body = value.try_into().map(From::from).map_err(|_| { - "conversion to `CreateImageVariationRequest` for body failed".to_string() + "conversion to `CreateImageVariationBody` for body failed".to_string() }); self } @@ -1523,8 +2039,8 @@ pub mod builder { pub fn body_map(mut self, f: F) -> Self where F: std::ops::FnOnce( - types::builder::CreateImageVariationRequest, - ) -> types::builder::CreateImageVariationRequest, + types::builder::CreateImageVariationBody, + ) -> types::builder::CreateImageVariationBody, { self.body = self.body.map(f); self @@ -1543,7 +2059,7 @@ pub mod builder { image, } = self; let body = body - .and_then(std::convert::TryInto::::try_into) + .and_then(std::convert::TryInto::::try_into) .map_err(Error::InvalidRequest)?; let image = image.map_err(Error::InvalidRequest)?; let url = format!("{}/images/variations", client.baseurl,); diff --git a/progenitor-impl/tests/output/openai-openapi-builder.out b/progenitor-impl/tests/output/openai-openapi-builder.out index fe28944b..4af7fad7 100644 --- a/progenitor-impl/tests/output/openai-openapi-builder.out +++ b/progenitor-impl/tests/output/openai-openapi-builder.out @@ -9,6 +9,163 @@ pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] use std::convert::TryFrom; + #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] + pub struct CreateImageEditBody { + ///The number of images to generate. Must be between 1 and 10. + #[serde(default = "defaults::create_image_edit_body_n")] + pub n: Option, + ///A text description of the desired image(s). The maximum length is + /// 1000 characters. + pub prompt: String, + ///The format in which the generated images are returned. Must be one + /// of `url` or `b64_json`. + #[serde(default = "defaults::create_image_edit_body_response_format")] + pub response_format: Option, + ///The size of the generated images. Must be one of `256x256`, + /// `512x512`, or `1024x1024`. + #[serde(default = "defaults::create_image_edit_body_size")] + pub size: Option, + ///A unique identifier representing your end-user, which can help + /// OpenAI to monitor and detect abuse. [Learn + /// more](/docs/guides/safety-best-practices/end-user-ids). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, + } + + impl From<&CreateImageEditBody> for CreateImageEditBody { + fn from(value: &CreateImageEditBody) -> Self { + value.clone() + } + } + + impl CreateImageEditBody { + pub fn builder() -> builder::CreateImageEditBody { + builder::CreateImageEditBody::default() + } + } + + ///The format in which the generated images are returned. Must be one of + /// `url` or `b64_json`. + #[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize, + )] + pub enum CreateImageEditBodyResponseFormat { + #[serde(rename = "url")] + Url, + #[serde(rename = "b64_json")] + B64Json, + } + + impl From<&CreateImageEditBodyResponseFormat> for CreateImageEditBodyResponseFormat { + fn from(value: &CreateImageEditBodyResponseFormat) -> Self { + value.clone() + } + } + + impl ToString for CreateImageEditBodyResponseFormat { + fn to_string(&self) -> String { + match *self { + Self::Url => "url".to_string(), + Self::B64Json => "b64_json".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageEditBodyResponseFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "url" => Ok(Self::Url), + "b64_json" => Ok(Self::B64Json), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageEditBodyResponseFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageEditBodyResponseFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageEditBodyResponseFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + ///The size of the generated images. Must be one of `256x256`, `512x512`, + /// or `1024x1024`. + #[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize, + )] + pub enum CreateImageEditBodySize { + #[serde(rename = "256x256")] + _256x256, + #[serde(rename = "512x512")] + _512x512, + #[serde(rename = "1024x1024")] + _1024x1024, + } + + impl From<&CreateImageEditBodySize> for CreateImageEditBodySize { + fn from(value: &CreateImageEditBodySize) -> Self { + value.clone() + } + } + + impl ToString for CreateImageEditBodySize { + fn to_string(&self) -> String { + match *self { + Self::_256x256 => "256x256".to_string(), + Self::_512x512 => "512x512".to_string(), + Self::_1024x1024 => "1024x1024".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageEditBodySize { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "256x256" => Ok(Self::_256x256), + "512x512" => Ok(Self::_512x512), + "1024x1024" => Ok(Self::_1024x1024), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageEditBodySize { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageEditBodySize { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageEditBodySize { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] pub struct CreateImageEditRequest { ///The image to edit. Must be a valid PNG file, less than 4MB, and @@ -332,6 +489,160 @@ pub mod types { } } + #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] + pub struct CreateImageVariationBody { + ///The number of images to generate. Must be between 1 and 10. + #[serde(default = "defaults::create_image_variation_body_n")] + pub n: Option, + ///The format in which the generated images are returned. Must be one + /// of `url` or `b64_json`. + #[serde(default = "defaults::create_image_variation_body_response_format")] + pub response_format: Option, + ///The size of the generated images. Must be one of `256x256`, + /// `512x512`, or `1024x1024`. + #[serde(default = "defaults::create_image_variation_body_size")] + pub size: Option, + ///A unique identifier representing your end-user, which can help + /// OpenAI to monitor and detect abuse. [Learn + /// more](/docs/guides/safety-best-practices/end-user-ids). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, + } + + impl From<&CreateImageVariationBody> for CreateImageVariationBody { + fn from(value: &CreateImageVariationBody) -> Self { + value.clone() + } + } + + impl CreateImageVariationBody { + pub fn builder() -> builder::CreateImageVariationBody { + builder::CreateImageVariationBody::default() + } + } + + ///The format in which the generated images are returned. Must be one of + /// `url` or `b64_json`. + #[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize, + )] + pub enum CreateImageVariationBodyResponseFormat { + #[serde(rename = "url")] + Url, + #[serde(rename = "b64_json")] + B64Json, + } + + impl From<&CreateImageVariationBodyResponseFormat> for CreateImageVariationBodyResponseFormat { + fn from(value: &CreateImageVariationBodyResponseFormat) -> Self { + value.clone() + } + } + + impl ToString for CreateImageVariationBodyResponseFormat { + fn to_string(&self) -> String { + match *self { + Self::Url => "url".to_string(), + Self::B64Json => "b64_json".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageVariationBodyResponseFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "url" => Ok(Self::Url), + "b64_json" => Ok(Self::B64Json), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageVariationBodyResponseFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageVariationBodyResponseFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageVariationBodyResponseFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + ///The size of the generated images. Must be one of `256x256`, `512x512`, + /// or `1024x1024`. + #[derive( + Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize, + )] + pub enum CreateImageVariationBodySize { + #[serde(rename = "256x256")] + _256x256, + #[serde(rename = "512x512")] + _512x512, + #[serde(rename = "1024x1024")] + _1024x1024, + } + + impl From<&CreateImageVariationBodySize> for CreateImageVariationBodySize { + fn from(value: &CreateImageVariationBodySize) -> Self { + value.clone() + } + } + + impl ToString for CreateImageVariationBodySize { + fn to_string(&self) -> String { + match *self { + Self::_256x256 => "256x256".to_string(), + Self::_512x512 => "512x512".to_string(), + Self::_1024x1024 => "1024x1024".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageVariationBodySize { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "256x256" => Ok(Self::_256x256), + "512x512" => Ok(Self::_512x512), + "1024x1024" => Ok(Self::_1024x1024), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageVariationBodySize { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageVariationBodySize { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageVariationBodySize { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] pub struct CreateImageVariationRequest { ///The image to use as the basis for the variation(s). Must be a valid @@ -574,6 +885,105 @@ pub mod types { } pub mod builder { + #[derive(Clone, Debug)] + pub struct CreateImageEditBody { + n: Result, String>, + prompt: Result, + response_format: Result, String>, + size: Result, String>, + user: Result, String>, + } + + impl Default for CreateImageEditBody { + fn default() -> Self { + Self { + n: Ok(super::defaults::create_image_edit_body_n()), + prompt: Err("no value supplied for prompt".to_string()), + response_format: Ok(super::defaults::create_image_edit_body_response_format()), + size: Ok(super::defaults::create_image_edit_body_size()), + user: Ok(Default::default()), + } + } + } + + impl CreateImageEditBody { + pub fn n(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.n = value + .try_into() + .map_err(|e| format!("error converting supplied value for n: {}", e)); + self + } + pub fn prompt(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.prompt = value + .try_into() + .map_err(|e| format!("error converting supplied value for prompt: {}", e)); + self + } + pub fn response_format(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.response_format = value.try_into().map_err(|e| { + format!("error converting supplied value for response_format: {}", e) + }); + self + } + pub fn size(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.size = value + .try_into() + .map_err(|e| format!("error converting supplied value for size: {}", e)); + self + } + pub fn user(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.user = value + .try_into() + .map_err(|e| format!("error converting supplied value for user: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::CreateImageEditBody { + type Error = String; + fn try_from(value: CreateImageEditBody) -> Result { + Ok(Self { + n: value.n?, + prompt: value.prompt?, + response_format: value.response_format?, + size: value.size?, + user: value.user?, + }) + } + } + + impl From for CreateImageEditBody { + fn from(value: super::CreateImageEditBody) -> Self { + Self { + n: Ok(value.n), + prompt: Ok(value.prompt), + response_format: Ok(value.response_format), + size: Ok(value.size), + user: Ok(value.user), + } + } + } + #[derive(Clone, Debug)] pub struct CreateImageEditRequest { image: Result, String>, @@ -802,6 +1212,93 @@ pub mod types { } } + #[derive(Clone, Debug)] + pub struct CreateImageVariationBody { + n: Result, String>, + response_format: Result, String>, + size: Result, String>, + user: Result, String>, + } + + impl Default for CreateImageVariationBody { + fn default() -> Self { + Self { + n: Ok(super::defaults::create_image_variation_body_n()), + response_format: Ok( + super::defaults::create_image_variation_body_response_format(), + ), + size: Ok(super::defaults::create_image_variation_body_size()), + user: Ok(Default::default()), + } + } + } + + impl CreateImageVariationBody { + pub fn n(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.n = value + .try_into() + .map_err(|e| format!("error converting supplied value for n: {}", e)); + self + } + pub fn response_format(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.response_format = value.try_into().map_err(|e| { + format!("error converting supplied value for response_format: {}", e) + }); + self + } + pub fn size(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.size = value + .try_into() + .map_err(|e| format!("error converting supplied value for size: {}", e)); + self + } + pub fn user(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.user = value + .try_into() + .map_err(|e| format!("error converting supplied value for user: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::CreateImageVariationBody { + type Error = String; + fn try_from(value: CreateImageVariationBody) -> Result { + Ok(Self { + n: value.n?, + response_format: value.response_format?, + size: value.size?, + user: value.user?, + }) + } + } + + impl From for CreateImageVariationBody { + fn from(value: super::CreateImageVariationBody) -> Self { + Self { + n: Ok(value.n), + response_format: Ok(value.response_format), + size: Ok(value.size), + user: Ok(value.user), + } + } + } + #[derive(Clone, Debug)] pub struct CreateImageVariationRequest { image: Result, String>, @@ -1148,6 +1645,19 @@ pub mod types { } pub mod defaults { + pub(super) fn create_image_edit_body_n() -> Option { + Some(1_i64) + } + + pub(super) fn create_image_edit_body_response_format( + ) -> Option { + Some(super::CreateImageEditBodyResponseFormat::Url) + } + + pub(super) fn create_image_edit_body_size() -> Option { + Some(super::CreateImageEditBodySize::_1024x1024) + } + pub(super) fn create_image_edit_request_n() -> Option { Some(1_i64) } @@ -1175,6 +1685,20 @@ pub mod types { Some(super::CreateImageRequestSize::_1024x1024) } + pub(super) fn create_image_variation_body_n() -> Option { + Some(1_i64) + } + + pub(super) fn create_image_variation_body_response_format( + ) -> Option { + Some(super::CreateImageVariationBodyResponseFormat::Url) + } + + pub(super) fn create_image_variation_body_size( + ) -> Option { + Some(super::CreateImageVariationBodySize::_1024x1024) + } + pub(super) fn create_image_variation_request_n() -> Option { Some(1_i64) } @@ -1391,7 +1915,7 @@ pub mod builder { #[derive(Debug)] pub struct CreateImageEdit<'a> { client: &'a super::Client, - body: Result, + body: Result, mask: Result, String>, image: Result, } @@ -1400,7 +1924,7 @@ pub mod builder { pub fn new(client: &'a super::Client) -> Self { Self { client: client, - body: Ok(types::builder::CreateImageEditRequest::default()), + body: Ok(types::builder::CreateImageEditBody::default()), mask: Ok(None), image: Err("image was not initialized".to_string()), } @@ -1408,20 +1932,20 @@ pub mod builder { pub fn body(mut self, value: V) -> Self where - V: std::convert::TryInto, + V: std::convert::TryInto, { self.body = value .try_into() .map(From::from) - .map_err(|_| "conversion to `CreateImageEditRequest` for body failed".to_string()); + .map_err(|_| "conversion to `CreateImageEditBody` for body failed".to_string()); self } pub fn body_map(mut self, f: F) -> Self where F: std::ops::FnOnce( - types::builder::CreateImageEditRequest, - ) -> types::builder::CreateImageEditRequest, + types::builder::CreateImageEditBody, + ) -> types::builder::CreateImageEditBody, { self.body = self.body.map(f); self @@ -1446,7 +1970,7 @@ pub mod builder { image, } = self; let body = body - .and_then(std::convert::TryInto::::try_into) + .and_then(std::convert::TryInto::::try_into) .map_err(Error::InvalidRequest)?; let mask = mask.map_err(Error::InvalidRequest)?; let image = image.map_err(Error::InvalidRequest)?; @@ -1504,7 +2028,7 @@ pub mod builder { #[derive(Debug)] pub struct CreateImageVariation<'a> { client: &'a super::Client, - body: Result, + body: Result, image: Result, } @@ -1512,17 +2036,17 @@ pub mod builder { pub fn new(client: &'a super::Client) -> Self { Self { client: client, - body: Ok(types::builder::CreateImageVariationRequest::default()), + body: Ok(types::builder::CreateImageVariationBody::default()), image: Err("image was not initialized".to_string()), } } pub fn body(mut self, value: V) -> Self where - V: std::convert::TryInto, + V: std::convert::TryInto, { self.body = value.try_into().map(From::from).map_err(|_| { - "conversion to `CreateImageVariationRequest` for body failed".to_string() + "conversion to `CreateImageVariationBody` for body failed".to_string() }); self } @@ -1530,8 +2054,8 @@ pub mod builder { pub fn body_map(mut self, f: F) -> Self where F: std::ops::FnOnce( - types::builder::CreateImageVariationRequest, - ) -> types::builder::CreateImageVariationRequest, + types::builder::CreateImageVariationBody, + ) -> types::builder::CreateImageVariationBody, { self.body = self.body.map(f); self @@ -1550,7 +2074,7 @@ pub mod builder { image, } = self; let body = body - .and_then(std::convert::TryInto::::try_into) + .and_then(std::convert::TryInto::::try_into) .map_err(Error::InvalidRequest)?; let image = image.map_err(Error::InvalidRequest)?; let url = format!("{}/images/variations", client.baseurl,); diff --git a/progenitor-impl/tests/output/openai-openapi-cli.out b/progenitor-impl/tests/output/openai-openapi-cli.out index 593c73f1..261b52b1 100644 --- a/progenitor-impl/tests/output/openai-openapi-cli.out +++ b/progenitor-impl/tests/output/openai-openapi-cli.out @@ -98,6 +98,26 @@ impl Cli { pub fn cli_create_image_edit() -> clap::Command { clap::Command::new("") + .arg( + clap::Arg::new("image") + .required(true) + .value_parser(clap::value_parser!(std::path::PathBuf)) + .help( + "The image to edit. Must be a valid PNG file, less than 4MB, and square. \ + If mask is not provided, image must have transparency, which will be \ + used as the mask.", + ), + ) + .arg( + clap::Arg::new("mask") + .required(false) + .value_parser(clap::value_parser!(std::path::PathBuf)) + .help( + "An additional image whose fully transparent areas (e.g. where alpha is \ + zero) indicate where `image` should be edited. Must be a valid PNG file, \ + less than 4MB, and have the same dimensions as `image`.", + ), + ) .arg( clap::Arg::new("n") .long("n") @@ -120,10 +140,10 @@ impl Cli { .long("response-format") .value_parser(clap::builder::TypedValueParser::map( clap::builder::PossibleValuesParser::new([ - types::CreateImageEditRequestResponseFormat::Url.to_string(), - types::CreateImageEditRequestResponseFormat::B64Json.to_string(), + types::CreateImageEditBodyResponseFormat::Url.to_string(), + types::CreateImageEditBodyResponseFormat::B64Json.to_string(), ]), - |s| types::CreateImageEditRequestResponseFormat::try_from(s).unwrap(), + |s| types::CreateImageEditBodyResponseFormat::try_from(s).unwrap(), )) .required(false) .help( @@ -136,11 +156,11 @@ impl Cli { .long("size") .value_parser(clap::builder::TypedValueParser::map( clap::builder::PossibleValuesParser::new([ - types::CreateImageEditRequestSize::_256x256.to_string(), - types::CreateImageEditRequestSize::_512x512.to_string(), - types::CreateImageEditRequestSize::_1024x1024.to_string(), + types::CreateImageEditBodySize::_256x256.to_string(), + types::CreateImageEditBodySize::_512x512.to_string(), + types::CreateImageEditBodySize::_1024x1024.to_string(), ]), - |s| types::CreateImageEditRequestSize::try_from(s).unwrap(), + |s| types::CreateImageEditBodySize::try_from(s).unwrap(), )) .required(false) .help( @@ -163,7 +183,7 @@ impl Cli { clap::Arg::new("json-body") .long("json-body") .value_name("JSON-FILE") - .required(true) + .required(false) .value_parser(clap::value_parser!(std::path::PathBuf)) .help("Path to a file that contains the full json body."), ) @@ -178,6 +198,15 @@ impl Cli { pub fn cli_create_image_variation() -> clap::Command { clap::Command::new("") + .arg( + clap::Arg::new("image") + .required(true) + .value_parser(clap::value_parser!(std::path::PathBuf)) + .help( + "The image to use as the basis for the variation(s). Must be a valid PNG \ + file, less than 4MB, and square.", + ), + ) .arg( clap::Arg::new("n") .long("n") @@ -190,10 +219,10 @@ impl Cli { .long("response-format") .value_parser(clap::builder::TypedValueParser::map( clap::builder::PossibleValuesParser::new([ - types::CreateImageVariationRequestResponseFormat::Url.to_string(), - types::CreateImageVariationRequestResponseFormat::B64Json.to_string(), + types::CreateImageVariationBodyResponseFormat::Url.to_string(), + types::CreateImageVariationBodyResponseFormat::B64Json.to_string(), ]), - |s| types::CreateImageVariationRequestResponseFormat::try_from(s).unwrap(), + |s| types::CreateImageVariationBodyResponseFormat::try_from(s).unwrap(), )) .required(false) .help( @@ -206,11 +235,11 @@ impl Cli { .long("size") .value_parser(clap::builder::TypedValueParser::map( clap::builder::PossibleValuesParser::new([ - types::CreateImageVariationRequestSize::_256x256.to_string(), - types::CreateImageVariationRequestSize::_512x512.to_string(), - types::CreateImageVariationRequestSize::_1024x1024.to_string(), + types::CreateImageVariationBodySize::_256x256.to_string(), + types::CreateImageVariationBodySize::_512x512.to_string(), + types::CreateImageVariationBodySize::_1024x1024.to_string(), ]), - |s| types::CreateImageVariationRequestSize::try_from(s).unwrap(), + |s| types::CreateImageVariationBodySize::try_from(s).unwrap(), )) .required(false) .help( @@ -233,7 +262,7 @@ impl Cli { clap::Arg::new("json-body") .long("json-body") .value_name("JSON-FILE") - .required(true) + .required(false) .value_parser(clap::value_parser!(std::path::PathBuf)) .help("Path to a file that contains the full json body."), ) @@ -312,6 +341,24 @@ impl Cli { pub async fn execute_create_image_edit(&self, matches: &clap::ArgMatches) { let mut request = self.client.create_image_edit(); + if let Some(file_name) = matches.get_one::("image") { + use std::io::Read; + let mut file = std::fs::File::open(&file_name).unwrap(); + let mut value = Vec::new(); + file.read_to_end(&mut value).unwrap(); + let part = reqwest::multipart::Part::bytes(value); + request = request.image(part); + } + + if let Some(file_name) = matches.get_one::("mask") { + use std::io::Read; + let mut file = std::fs::File::open(&file_name).unwrap(); + let mut value = Vec::new(); + file.read_to_end(&mut value).unwrap(); + let part = reqwest::multipart::Part::bytes(value); + request = request.mask(Some(part)); + } + if let Some(value) = matches.get_one::("n") { request = request.body_map(|body| body.n(value.clone())) } @@ -321,12 +368,12 @@ impl Cli { } if let Some(value) = - matches.get_one::("response-format") + matches.get_one::("response-format") { request = request.body_map(|body| body.response_format(value.clone())) } - if let Some(value) = matches.get_one::("size") { + if let Some(value) = matches.get_one::("size") { request = request.body_map(|body| body.size(value.clone())) } @@ -336,8 +383,7 @@ impl Cli { if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = - serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } @@ -357,17 +403,26 @@ impl Cli { pub async fn execute_create_image_variation(&self, matches: &clap::ArgMatches) { let mut request = self.client.create_image_variation(); + if let Some(file_name) = matches.get_one::("image") { + use std::io::Read; + let mut file = std::fs::File::open(&file_name).unwrap(); + let mut value = Vec::new(); + file.read_to_end(&mut value).unwrap(); + let part = reqwest::multipart::Part::bytes(value); + request = request.image(part); + } + if let Some(value) = matches.get_one::("n") { request = request.body_map(|body| body.n(value.clone())) } if let Some(value) = - matches.get_one::("response-format") + matches.get_one::("response-format") { request = request.body_map(|body| body.response_format(value.clone())) } - if let Some(value) = matches.get_one::("size") { + if let Some(value) = matches.get_one::("size") { request = request.body_map(|body| body.size(value.clone())) } @@ -378,7 +433,7 @@ impl Cli { if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); let body_value = - serde_json::from_str::(&body_txt).unwrap(); + serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } diff --git a/progenitor-impl/tests/output/openai-openapi-httpmock.out b/progenitor-impl/tests/output/openai-openapi-httpmock.out index c5984b11..95a8dedc 100644 --- a/progenitor-impl/tests/output/openai-openapi-httpmock.out +++ b/progenitor-impl/tests/output/openai-openapi-httpmock.out @@ -57,7 +57,7 @@ pub mod operations { self.0 } - pub fn body(self, value: &types::CreateImageEditRequest) -> Self { + pub fn body(self, value: &types::CreateImageEditBody) -> Self { Self(self.0.json_body_obj(value)) } @@ -104,7 +104,7 @@ pub mod operations { self.0 } - pub fn body(self, value: &types::CreateImageVariationRequest) -> Self { + pub fn body(self, value: &types::CreateImageVariationBody) -> Self { Self(self.0.json_body_obj(value)) } diff --git a/progenitor-impl/tests/output/openai-openapi-positional.out b/progenitor-impl/tests/output/openai-openapi-positional.out index e335e5d9..bfd9b177 100644 --- a/progenitor-impl/tests/output/openai-openapi-positional.out +++ b/progenitor-impl/tests/output/openai-openapi-positional.out @@ -9,6 +9,153 @@ pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] use std::convert::TryFrom; + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateImageEditBody { + ///The number of images to generate. Must be between 1 and 10. + #[serde(default = "defaults::create_image_edit_body_n")] + pub n: Option, + ///A text description of the desired image(s). The maximum length is + /// 1000 characters. + pub prompt: String, + ///The format in which the generated images are returned. Must be one + /// of `url` or `b64_json`. + #[serde(default = "defaults::create_image_edit_body_response_format")] + pub response_format: Option, + ///The size of the generated images. Must be one of `256x256`, + /// `512x512`, or `1024x1024`. + #[serde(default = "defaults::create_image_edit_body_size")] + pub size: Option, + ///A unique identifier representing your end-user, which can help + /// OpenAI to monitor and detect abuse. [Learn + /// more](/docs/guides/safety-best-practices/end-user-ids). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, + } + + impl From<&CreateImageEditBody> for CreateImageEditBody { + fn from(value: &CreateImageEditBody) -> Self { + value.clone() + } + } + + ///The format in which the generated images are returned. Must be one of + /// `url` or `b64_json`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageEditBodyResponseFormat { + #[serde(rename = "url")] + Url, + #[serde(rename = "b64_json")] + B64Json, + } + + impl From<&CreateImageEditBodyResponseFormat> for CreateImageEditBodyResponseFormat { + fn from(value: &CreateImageEditBodyResponseFormat) -> Self { + value.clone() + } + } + + impl ToString for CreateImageEditBodyResponseFormat { + fn to_string(&self) -> String { + match *self { + Self::Url => "url".to_string(), + Self::B64Json => "b64_json".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageEditBodyResponseFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "url" => Ok(Self::Url), + "b64_json" => Ok(Self::B64Json), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageEditBodyResponseFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageEditBodyResponseFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageEditBodyResponseFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + ///The size of the generated images. Must be one of `256x256`, `512x512`, + /// or `1024x1024`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageEditBodySize { + #[serde(rename = "256x256")] + _256x256, + #[serde(rename = "512x512")] + _512x512, + #[serde(rename = "1024x1024")] + _1024x1024, + } + + impl From<&CreateImageEditBodySize> for CreateImageEditBodySize { + fn from(value: &CreateImageEditBodySize) -> Self { + value.clone() + } + } + + impl ToString for CreateImageEditBodySize { + fn to_string(&self) -> String { + match *self { + Self::_256x256 => "256x256".to_string(), + Self::_512x512 => "512x512".to_string(), + Self::_1024x1024 => "1024x1024".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageEditBodySize { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "256x256" => Ok(Self::_256x256), + "512x512" => Ok(Self::_512x512), + "1024x1024" => Ok(Self::_1024x1024), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageEditBodySize { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageEditBodySize { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageEditBodySize { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct CreateImageEditRequest { ///The image to edit. Must be a valid PNG file, less than 4MB, and @@ -312,6 +459,150 @@ pub mod types { } } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct CreateImageVariationBody { + ///The number of images to generate. Must be between 1 and 10. + #[serde(default = "defaults::create_image_variation_body_n")] + pub n: Option, + ///The format in which the generated images are returned. Must be one + /// of `url` or `b64_json`. + #[serde(default = "defaults::create_image_variation_body_response_format")] + pub response_format: Option, + ///The size of the generated images. Must be one of `256x256`, + /// `512x512`, or `1024x1024`. + #[serde(default = "defaults::create_image_variation_body_size")] + pub size: Option, + ///A unique identifier representing your end-user, which can help + /// OpenAI to monitor and detect abuse. [Learn + /// more](/docs/guides/safety-best-practices/end-user-ids). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user: Option, + } + + impl From<&CreateImageVariationBody> for CreateImageVariationBody { + fn from(value: &CreateImageVariationBody) -> Self { + value.clone() + } + } + + ///The format in which the generated images are returned. Must be one of + /// `url` or `b64_json`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageVariationBodyResponseFormat { + #[serde(rename = "url")] + Url, + #[serde(rename = "b64_json")] + B64Json, + } + + impl From<&CreateImageVariationBodyResponseFormat> for CreateImageVariationBodyResponseFormat { + fn from(value: &CreateImageVariationBodyResponseFormat) -> Self { + value.clone() + } + } + + impl ToString for CreateImageVariationBodyResponseFormat { + fn to_string(&self) -> String { + match *self { + Self::Url => "url".to_string(), + Self::B64Json => "b64_json".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageVariationBodyResponseFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "url" => Ok(Self::Url), + "b64_json" => Ok(Self::B64Json), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageVariationBodyResponseFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageVariationBodyResponseFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageVariationBodyResponseFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + + ///The size of the generated images. Must be one of `256x256`, `512x512`, + /// or `1024x1024`. + #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub enum CreateImageVariationBodySize { + #[serde(rename = "256x256")] + _256x256, + #[serde(rename = "512x512")] + _512x512, + #[serde(rename = "1024x1024")] + _1024x1024, + } + + impl From<&CreateImageVariationBodySize> for CreateImageVariationBodySize { + fn from(value: &CreateImageVariationBodySize) -> Self { + value.clone() + } + } + + impl ToString for CreateImageVariationBodySize { + fn to_string(&self) -> String { + match *self { + Self::_256x256 => "256x256".to_string(), + Self::_512x512 => "512x512".to_string(), + Self::_1024x1024 => "1024x1024".to_string(), + } + } + } + + impl std::str::FromStr for CreateImageVariationBodySize { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "256x256" => Ok(Self::_256x256), + "512x512" => Ok(Self::_512x512), + "1024x1024" => Ok(Self::_1024x1024), + _ => Err("invalid value"), + } + } + } + + impl std::convert::TryFrom<&str> for CreateImageVariationBodySize { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for CreateImageVariationBodySize { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for CreateImageVariationBodySize { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct CreateImageVariationRequest { ///The image to use as the basis for the variation(s). Must be a valid @@ -520,6 +811,19 @@ pub mod types { } pub mod defaults { + pub(super) fn create_image_edit_body_n() -> Option { + Some(1_i64) + } + + pub(super) fn create_image_edit_body_response_format( + ) -> Option { + Some(super::CreateImageEditBodyResponseFormat::Url) + } + + pub(super) fn create_image_edit_body_size() -> Option { + Some(super::CreateImageEditBodySize::_1024x1024) + } + pub(super) fn create_image_edit_request_n() -> Option { Some(1_i64) } @@ -547,6 +851,20 @@ pub mod types { Some(super::CreateImageRequestSize::_1024x1024) } + pub(super) fn create_image_variation_body_n() -> Option { + Some(1_i64) + } + + pub(super) fn create_image_variation_body_response_format( + ) -> Option { + Some(super::CreateImageVariationBodyResponseFormat::Url) + } + + pub(super) fn create_image_variation_body_size( + ) -> Option { + Some(super::CreateImageVariationBodySize::_1024x1024) + } + pub(super) fn create_image_variation_request_n() -> Option { Some(1_i64) } @@ -668,7 +986,7 @@ impl Client { /// which will be used as the mask. pub async fn create_image_edit<'a>( &'a self, - body: &'a types::CreateImageEditRequest, + body: &'a types::CreateImageEditBody, mask: Option, image: Part, ) -> Result, Error<()>> { @@ -729,7 +1047,7 @@ impl Client { /// valid PNG file, less than 4MB, and square. pub async fn create_image_variation<'a>( &'a self, - body: &'a types::CreateImageVariationRequest, + body: &'a types::CreateImageVariationBody, image: Part, ) -> Result, Error<()>> { let url = format!("{}/images/variations", self.baseurl,); From 191105fd141e0e0dbb1de234bd8d1327679588d5 Mon Sep 17 00:00:00 2001 From: ahirner Date: Sat, 28 Oct 2023 12:01:32 +0200 Subject: [PATCH 23/38] fix quoting form data string values --- progenitor-client/src/progenitor_client.rs | 14 ++++++ progenitor-impl/src/method.rs | 23 +++++----- .../output/openai-openapi-builder-tagged.out | 36 +++++++-------- .../tests/output/openai-openapi-builder.out | 36 +++++++-------- .../output/openai-openapi-positional.out | 36 +++++++-------- progenitor/tests/test_client.rs | 45 +++++++++++++++++++ 6 files changed, 123 insertions(+), 67 deletions(-) diff --git a/progenitor-client/src/progenitor_client.rs b/progenitor-client/src/progenitor_client.rs index a412f7b3..736a7abb 100644 --- a/progenitor-client/src/progenitor_client.rs +++ b/progenitor-client/src/progenitor_client.rs @@ -416,3 +416,17 @@ impl RequestBuilderExt for RequestBuilder { })?)) } } + +#[doc(hidden)] +pub fn to_form_string( + value: &serde_json::Value, +) -> Result> { + match value.as_str() { + // Take unquoted String Value + Some(v) => Ok(v.to_string()), + // otherwise convert to quoted json + None => serde_json::to_string(value).map_err(|_| { + Error::InvalidRequest("failed to serialize Value".to_string()) + }), + } +} diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index c312981e..04eff2ef 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -2356,7 +2356,6 @@ impl Generator { ) = schema { for param in body_params.iter() { - dbg!(("remove", ¶m.api_name, ¶m.description)); object.properties.remove_entry(¶m.api_name); } }; @@ -2441,25 +2440,24 @@ impl Generator { }; let body_parts = tstru .properties() + // skip Parts made from binary strings .filter_map(|(prop_name, _)| { if form_data_names.contains(prop_name) { None - } - else - { + } else { Some(prop_name) } }) .map(|prop_name| { - // Stringish serialization is what most servers expect, - // for nested types we also assume they want those json - // formatted. A customizable solution would be possible - // with a custom Serializer. + // Serialialize text fields. + // Unquoted strings are what servers expect for + // single values, including datetimes. + // We assume they want a json string for nested types. + // The field is not set if it skipped in Serialize. quote! { - if let Some(v) = - body.get(#prop_name).map(serde_json::to_string) + if let Some(v) = body.get(#prop_name) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + let v = progenitor_client::to_form_string(v)?; #form_ident = #form_ident.text(#prop_name, v); }; } @@ -2480,9 +2478,8 @@ impl Generator { form_parts.insert( 0, quote! { - // prepare body + // prepare body as json Map let body = serde_json::to_value(body) - // todo: impl From serde::Error? .map_err(|e| Error::InvalidRequest(e.to_string()))? .as_object() .cloned() diff --git a/progenitor-impl/tests/output/openai-openapi-builder-tagged.out b/progenitor-impl/tests/output/openai-openapi-builder-tagged.out index 1e20354f..1e78738e 100644 --- a/progenitor-impl/tests/output/openai-openapi-builder-tagged.out +++ b/progenitor-impl/tests/output/openai-openapi-builder-tagged.out @@ -1969,24 +1969,24 @@ pub mod builder { if let Some(mask) = mask { form = form.part("mask", mask); }; - if let Some(v) = body.get("n").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("n") { + let v = progenitor_client::to_form_string(v)?; form = form.text("n", v); }; - if let Some(v) = body.get("prompt").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("prompt") { + let v = progenitor_client::to_form_string(v)?; form = form.text("prompt", v); }; - if let Some(v) = body.get("response_format").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("response_format") { + let v = progenitor_client::to_form_string(v)?; form = form.text("response_format", v); }; - if let Some(v) = body.get("size").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("size") { + let v = progenitor_client::to_form_string(v)?; form = form.text("size", v); }; - if let Some(v) = body.get("user").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("user") { + let v = progenitor_client::to_form_string(v)?; form = form.text("user", v); }; let request = client @@ -2069,20 +2069,20 @@ pub mod builder { .cloned() .ok_or_else(|| Error::InvalidRequest("Serializing body failed.".to_string()))?; let mut form = reqwest::multipart::Form::new().part("image", image); - if let Some(v) = body.get("n").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("n") { + let v = progenitor_client::to_form_string(v)?; form = form.text("n", v); }; - if let Some(v) = body.get("response_format").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("response_format") { + let v = progenitor_client::to_form_string(v)?; form = form.text("response_format", v); }; - if let Some(v) = body.get("size").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("size") { + let v = progenitor_client::to_form_string(v)?; form = form.text("size", v); }; - if let Some(v) = body.get("user").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("user") { + let v = progenitor_client::to_form_string(v)?; form = form.text("user", v); }; let request = client diff --git a/progenitor-impl/tests/output/openai-openapi-builder.out b/progenitor-impl/tests/output/openai-openapi-builder.out index 4af7fad7..2c0c57c5 100644 --- a/progenitor-impl/tests/output/openai-openapi-builder.out +++ b/progenitor-impl/tests/output/openai-openapi-builder.out @@ -1984,24 +1984,24 @@ pub mod builder { if let Some(mask) = mask { form = form.part("mask", mask); }; - if let Some(v) = body.get("n").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("n") { + let v = progenitor_client::to_form_string(v)?; form = form.text("n", v); }; - if let Some(v) = body.get("prompt").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("prompt") { + let v = progenitor_client::to_form_string(v)?; form = form.text("prompt", v); }; - if let Some(v) = body.get("response_format").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("response_format") { + let v = progenitor_client::to_form_string(v)?; form = form.text("response_format", v); }; - if let Some(v) = body.get("size").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("size") { + let v = progenitor_client::to_form_string(v)?; form = form.text("size", v); }; - if let Some(v) = body.get("user").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("user") { + let v = progenitor_client::to_form_string(v)?; form = form.text("user", v); }; let request = client @@ -2084,20 +2084,20 @@ pub mod builder { .cloned() .ok_or_else(|| Error::InvalidRequest("Serializing body failed.".to_string()))?; let mut form = reqwest::multipart::Form::new().part("image", image); - if let Some(v) = body.get("n").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("n") { + let v = progenitor_client::to_form_string(v)?; form = form.text("n", v); }; - if let Some(v) = body.get("response_format").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("response_format") { + let v = progenitor_client::to_form_string(v)?; form = form.text("response_format", v); }; - if let Some(v) = body.get("size").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("size") { + let v = progenitor_client::to_form_string(v)?; form = form.text("size", v); }; - if let Some(v) = body.get("user").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("user") { + let v = progenitor_client::to_form_string(v)?; form = form.text("user", v); }; let request = client diff --git a/progenitor-impl/tests/output/openai-openapi-positional.out b/progenitor-impl/tests/output/openai-openapi-positional.out index bfd9b177..77aa4af0 100644 --- a/progenitor-impl/tests/output/openai-openapi-positional.out +++ b/progenitor-impl/tests/output/openai-openapi-positional.out @@ -1000,24 +1000,24 @@ impl Client { if let Some(mask) = mask { form = form.part("mask", mask); }; - if let Some(v) = body.get("n").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("n") { + let v = progenitor_client::to_form_string(v)?; form = form.text("n", v); }; - if let Some(v) = body.get("prompt").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("prompt") { + let v = progenitor_client::to_form_string(v)?; form = form.text("prompt", v); }; - if let Some(v) = body.get("response_format").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("response_format") { + let v = progenitor_client::to_form_string(v)?; form = form.text("response_format", v); }; - if let Some(v) = body.get("size").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("size") { + let v = progenitor_client::to_form_string(v)?; form = form.text("size", v); }; - if let Some(v) = body.get("user").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("user") { + let v = progenitor_client::to_form_string(v)?; form = form.text("user", v); }; let request = self @@ -1057,20 +1057,20 @@ impl Client { .cloned() .ok_or_else(|| Error::InvalidRequest("Serializing body failed.".to_string()))?; let mut form = reqwest::multipart::Form::new().part("image", image); - if let Some(v) = body.get("n").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("n") { + let v = progenitor_client::to_form_string(v)?; form = form.text("n", v); }; - if let Some(v) = body.get("response_format").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("response_format") { + let v = progenitor_client::to_form_string(v)?; form = form.text("response_format", v); }; - if let Some(v) = body.get("size").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("size") { + let v = progenitor_client::to_form_string(v)?; form = form.text("size", v); }; - if let Some(v) = body.get("user").map(serde_json::to_string) { - let v = v.map_err(|e| Error::InvalidRequest(e.to_string()))?; + if let Some(v) = body.get("user") { + let v = progenitor_client::to_form_string(v)?; form = form.text("user", v); }; let request = self diff --git a/progenitor/tests/test_client.rs b/progenitor/tests/test_client.rs index 85391320..eafd4f0e 100644 --- a/progenitor/tests/test_client.rs +++ b/progenitor/tests/test_client.rs @@ -26,3 +26,48 @@ fn test_error() { (Err(e) as Result<(), progenitor_client::Error>).unwrap(); } + +// Validate how form data text fields are passed +#[test] +fn test_to_form_data_field() -> Result<(), progenitor_client::Error> { + use progenitor_client::to_form_string as to; + use serde::Serialize; + + #[derive(Serialize)] + struct Nested { + x: String, + y: usize, + } + #[derive(Serialize)] + struct Body { + a: String, + b: isize, + c: Nested, + d: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + e: Option, + } + + let body = Body { + a: "foo".to_string(), + b: -42, + c: Nested { + x: "bar".to_string(), + y: 42, + }, + d: None, + e: None, + }; + + let map = serde_json::to_value(body) + .unwrap() + .as_object() + .unwrap() + .clone(); + assert_eq!(to(map.get("a").unwrap())?, "foo"); + assert_eq!(to(map.get("b").unwrap())?, "-42"); + assert_eq!(to(map.get("c").unwrap())?, r#"{"x":"bar","y":42}"#); + assert_eq!(to(map.get("d").unwrap())?, "null"); + assert_eq!(map.get("e"), None); + Ok(()) +} From ba30bb19cee7f8788ebed06acc4bd3624561dafd Mon Sep 17 00:00:00 2001 From: ahirner Date: Sat, 28 Oct 2023 12:19:22 +0200 Subject: [PATCH 24/38] fix to_form_string builder import --- progenitor-impl/src/lib.rs | 3 ++- progenitor-impl/src/method.rs | 2 +- .../tests/output/buildomat-builder-tagged.out | 2 +- .../tests/output/buildomat-builder.out | 6 ++--- .../tests/output/buildomat-positional.out | 2 +- .../tests/output/keeper-builder-tagged.out | 2 +- .../tests/output/keeper-builder.out | 6 ++--- .../tests/output/keeper-positional.out | 2 +- .../tests/output/nexus-builder-tagged.out | 2 +- .../tests/output/nexus-builder.out | 6 ++--- .../tests/output/nexus-positional.out | 2 +- .../output/openai-openapi-builder-tagged.out | 20 ++++++++-------- .../tests/output/openai-openapi-builder.out | 24 +++++++++---------- .../output/openai-openapi-positional.out | 20 ++++++++-------- .../output/param-collision-builder-tagged.out | 2 +- .../tests/output/param-collision-builder.out | 6 ++--- .../output/param-collision-positional.out | 2 +- .../output/param-overrides-builder-tagged.out | 2 +- .../tests/output/param-overrides-builder.out | 6 ++--- .../output/param-overrides-positional.out | 2 +- .../output/propolis-server-builder-tagged.out | 2 +- .../tests/output/propolis-server-builder.out | 6 ++--- .../output/propolis-server-positional.out | 2 +- .../output/test_default_params_builder.out | 6 ++--- .../output/test_default_params_positional.out | 2 +- .../tests/output/test_freeform_response.out | 2 +- .../tests/output/test_renamed_parameters.out | 2 +- 27 files changed, 71 insertions(+), 70 deletions(-) diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index 7fda48b5..2c656215 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -354,7 +354,7 @@ impl Generator { // public interface of Client. pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] - use progenitor_client::{encode_path, RequestBuilderExt}; + use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; #[allow(unused_imports)] @@ -491,6 +491,7 @@ impl Generator { #[allow(unused_imports)] use super::{ encode_path, + to_form_string, ByteStream, Error, HeaderMap, diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 04eff2ef..1a1cc537 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -2457,7 +2457,7 @@ impl Generator { quote! { if let Some(v) = body.get(#prop_name) { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; #form_ident = #form_ident.text(#prop_name, v); }; } diff --git a/progenitor-impl/tests/output/buildomat-builder-tagged.out b/progenitor-impl/tests/output/buildomat-builder-tagged.out index 5874b70a..837218b7 100644 --- a/progenitor-impl/tests/output/buildomat-builder-tagged.out +++ b/progenitor-impl/tests/output/buildomat-builder-tagged.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; diff --git a/progenitor-impl/tests/output/buildomat-builder.out b/progenitor-impl/tests/output/buildomat-builder.out index a4e3402c..d8ea9bf5 100644 --- a/progenitor-impl/tests/output/buildomat-builder.out +++ b/progenitor-impl/tests/output/buildomat-builder.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; @@ -1870,8 +1870,8 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, - ResponseValue, + encode_path, to_form_string, ByteStream, Error, HeaderMap, HeaderValue, Part, + RequestBuilderExt, ResponseValue, }; ///Builder for [`Client::control_hold`] /// diff --git a/progenitor-impl/tests/output/buildomat-positional.out b/progenitor-impl/tests/output/buildomat-positional.out index c5d59df7..8c0d477a 100644 --- a/progenitor-impl/tests/output/buildomat-positional.out +++ b/progenitor-impl/tests/output/buildomat-positional.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; diff --git a/progenitor-impl/tests/output/keeper-builder-tagged.out b/progenitor-impl/tests/output/keeper-builder-tagged.out index 11f7412a..e252f297 100644 --- a/progenitor-impl/tests/output/keeper-builder-tagged.out +++ b/progenitor-impl/tests/output/keeper-builder-tagged.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; diff --git a/progenitor-impl/tests/output/keeper-builder.out b/progenitor-impl/tests/output/keeper-builder.out index d33750f0..b8abdc7d 100644 --- a/progenitor-impl/tests/output/keeper-builder.out +++ b/progenitor-impl/tests/output/keeper-builder.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; @@ -1060,8 +1060,8 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, - ResponseValue, + encode_path, to_form_string, ByteStream, Error, HeaderMap, HeaderValue, Part, + RequestBuilderExt, ResponseValue, }; ///Builder for [`Client::enrol`] /// diff --git a/progenitor-impl/tests/output/keeper-positional.out b/progenitor-impl/tests/output/keeper-positional.out index 291da4f4..37fc33e5 100644 --- a/progenitor-impl/tests/output/keeper-positional.out +++ b/progenitor-impl/tests/output/keeper-positional.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; diff --git a/progenitor-impl/tests/output/nexus-builder-tagged.out b/progenitor-impl/tests/output/nexus-builder-tagged.out index e1448a74..7bf9eee3 100644 --- a/progenitor-impl/tests/output/nexus-builder-tagged.out +++ b/progenitor-impl/tests/output/nexus-builder-tagged.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; diff --git a/progenitor-impl/tests/output/nexus-builder.out b/progenitor-impl/tests/output/nexus-builder.out index 45676e1f..cbecab45 100644 --- a/progenitor-impl/tests/output/nexus-builder.out +++ b/progenitor-impl/tests/output/nexus-builder.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; @@ -20822,8 +20822,8 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, - ResponseValue, + encode_path, to_form_string, ByteStream, Error, HeaderMap, HeaderValue, Part, + RequestBuilderExt, ResponseValue, }; ///Builder for [`Client::disk_view_by_id`] /// diff --git a/progenitor-impl/tests/output/nexus-positional.out b/progenitor-impl/tests/output/nexus-positional.out index 580d4892..92c5c87c 100644 --- a/progenitor-impl/tests/output/nexus-positional.out +++ b/progenitor-impl/tests/output/nexus-positional.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; diff --git a/progenitor-impl/tests/output/openai-openapi-builder-tagged.out b/progenitor-impl/tests/output/openai-openapi-builder-tagged.out index 1e78738e..9ac8d26c 100644 --- a/progenitor-impl/tests/output/openai-openapi-builder-tagged.out +++ b/progenitor-impl/tests/output/openai-openapi-builder-tagged.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; @@ -1970,23 +1970,23 @@ pub mod builder { form = form.part("mask", mask); }; if let Some(v) = body.get("n") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("n", v); }; if let Some(v) = body.get("prompt") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("prompt", v); }; if let Some(v) = body.get("response_format") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("response_format", v); }; if let Some(v) = body.get("size") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("size", v); }; if let Some(v) = body.get("user") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("user", v); }; let request = client @@ -2070,19 +2070,19 @@ pub mod builder { .ok_or_else(|| Error::InvalidRequest("Serializing body failed.".to_string()))?; let mut form = reqwest::multipart::Form::new().part("image", image); if let Some(v) = body.get("n") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("n", v); }; if let Some(v) = body.get("response_format") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("response_format", v); }; if let Some(v) = body.get("size") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("size", v); }; if let Some(v) = body.get("user") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("user", v); }; let request = client diff --git a/progenitor-impl/tests/output/openai-openapi-builder.out b/progenitor-impl/tests/output/openai-openapi-builder.out index 2c0c57c5..4652fd53 100644 --- a/progenitor-impl/tests/output/openai-openapi-builder.out +++ b/progenitor-impl/tests/output/openai-openapi-builder.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; @@ -1843,8 +1843,8 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, - ResponseValue, + encode_path, to_form_string, ByteStream, Error, HeaderMap, HeaderValue, Part, + RequestBuilderExt, ResponseValue, }; ///Builder for [`Client::create_image`] /// @@ -1985,23 +1985,23 @@ pub mod builder { form = form.part("mask", mask); }; if let Some(v) = body.get("n") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("n", v); }; if let Some(v) = body.get("prompt") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("prompt", v); }; if let Some(v) = body.get("response_format") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("response_format", v); }; if let Some(v) = body.get("size") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("size", v); }; if let Some(v) = body.get("user") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("user", v); }; let request = client @@ -2085,19 +2085,19 @@ pub mod builder { .ok_or_else(|| Error::InvalidRequest("Serializing body failed.".to_string()))?; let mut form = reqwest::multipart::Form::new().part("image", image); if let Some(v) = body.get("n") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("n", v); }; if let Some(v) = body.get("response_format") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("response_format", v); }; if let Some(v) = body.get("size") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("size", v); }; if let Some(v) = body.get("user") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("user", v); }; let request = client diff --git a/progenitor-impl/tests/output/openai-openapi-positional.out b/progenitor-impl/tests/output/openai-openapi-positional.out index 77aa4af0..9d52d7b9 100644 --- a/progenitor-impl/tests/output/openai-openapi-positional.out +++ b/progenitor-impl/tests/output/openai-openapi-positional.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; @@ -1001,23 +1001,23 @@ impl Client { form = form.part("mask", mask); }; if let Some(v) = body.get("n") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("n", v); }; if let Some(v) = body.get("prompt") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("prompt", v); }; if let Some(v) = body.get("response_format") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("response_format", v); }; if let Some(v) = body.get("size") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("size", v); }; if let Some(v) = body.get("user") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("user", v); }; let request = self @@ -1058,19 +1058,19 @@ impl Client { .ok_or_else(|| Error::InvalidRequest("Serializing body failed.".to_string()))?; let mut form = reqwest::multipart::Form::new().part("image", image); if let Some(v) = body.get("n") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("n", v); }; if let Some(v) = body.get("response_format") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("response_format", v); }; if let Some(v) = body.get("size") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("size", v); }; if let Some(v) = body.get("user") { - let v = progenitor_client::to_form_string(v)?; + let v = to_form_string(v)?; form = form.text("user", v); }; let request = self diff --git a/progenitor-impl/tests/output/param-collision-builder-tagged.out b/progenitor-impl/tests/output/param-collision-builder-tagged.out index b1542561..cdf7bcef 100644 --- a/progenitor-impl/tests/output/param-collision-builder-tagged.out +++ b/progenitor-impl/tests/output/param-collision-builder-tagged.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; diff --git a/progenitor-impl/tests/output/param-collision-builder.out b/progenitor-impl/tests/output/param-collision-builder.out index 20fce7d8..5c1d9288 100644 --- a/progenitor-impl/tests/output/param-collision-builder.out +++ b/progenitor-impl/tests/output/param-collision-builder.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; @@ -105,8 +105,8 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, - ResponseValue, + encode_path, to_form_string, ByteStream, Error, HeaderMap, HeaderValue, Part, + RequestBuilderExt, ResponseValue, }; ///Builder for [`Client::key_get`] /// diff --git a/progenitor-impl/tests/output/param-collision-positional.out b/progenitor-impl/tests/output/param-collision-positional.out index 25c615f4..abdff8a4 100644 --- a/progenitor-impl/tests/output/param-collision-positional.out +++ b/progenitor-impl/tests/output/param-collision-positional.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; diff --git a/progenitor-impl/tests/output/param-overrides-builder-tagged.out b/progenitor-impl/tests/output/param-overrides-builder-tagged.out index 7b34531b..3593fa8e 100644 --- a/progenitor-impl/tests/output/param-overrides-builder-tagged.out +++ b/progenitor-impl/tests/output/param-overrides-builder-tagged.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; diff --git a/progenitor-impl/tests/output/param-overrides-builder.out b/progenitor-impl/tests/output/param-overrides-builder.out index f2000928..7e417273 100644 --- a/progenitor-impl/tests/output/param-overrides-builder.out +++ b/progenitor-impl/tests/output/param-overrides-builder.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; @@ -99,8 +99,8 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, - ResponseValue, + encode_path, to_form_string, ByteStream, Error, HeaderMap, HeaderValue, Part, + RequestBuilderExt, ResponseValue, }; ///Builder for [`Client::key_get`] /// diff --git a/progenitor-impl/tests/output/param-overrides-positional.out b/progenitor-impl/tests/output/param-overrides-positional.out index ce2a0b60..a7b9d2fd 100644 --- a/progenitor-impl/tests/output/param-overrides-positional.out +++ b/progenitor-impl/tests/output/param-overrides-positional.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; diff --git a/progenitor-impl/tests/output/propolis-server-builder-tagged.out b/progenitor-impl/tests/output/propolis-server-builder-tagged.out index 79d70fed..035a9fcc 100644 --- a/progenitor-impl/tests/output/propolis-server-builder-tagged.out +++ b/progenitor-impl/tests/output/propolis-server-builder-tagged.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; diff --git a/progenitor-impl/tests/output/propolis-server-builder.out b/progenitor-impl/tests/output/propolis-server-builder.out index 5eda5d4b..c5ceb80f 100644 --- a/progenitor-impl/tests/output/propolis-server-builder.out +++ b/progenitor-impl/tests/output/propolis-server-builder.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; @@ -2114,8 +2114,8 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, - ResponseValue, + encode_path, to_form_string, ByteStream, Error, HeaderMap, HeaderValue, Part, + RequestBuilderExt, ResponseValue, }; ///Builder for [`Client::instance_get`] /// diff --git a/progenitor-impl/tests/output/propolis-server-positional.out b/progenitor-impl/tests/output/propolis-server-positional.out index b32577c8..bdc7d206 100644 --- a/progenitor-impl/tests/output/propolis-server-positional.out +++ b/progenitor-impl/tests/output/propolis-server-positional.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; diff --git a/progenitor-impl/tests/output/test_default_params_builder.out b/progenitor-impl/tests/output/test_default_params_builder.out index b3896518..6b4dff79 100644 --- a/progenitor-impl/tests/output/test_default_params_builder.out +++ b/progenitor-impl/tests/output/test_default_params_builder.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; @@ -304,8 +304,8 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, ByteStream, Error, HeaderMap, HeaderValue, Part, RequestBuilderExt, - ResponseValue, + encode_path, to_form_string, ByteStream, Error, HeaderMap, HeaderValue, Part, + RequestBuilderExt, ResponseValue, }; ///Builder for [`Client::default_params`] /// diff --git a/progenitor-impl/tests/output/test_default_params_positional.out b/progenitor-impl/tests/output/test_default_params_positional.out index 4cce6c83..ea55cf83 100644 --- a/progenitor-impl/tests/output/test_default_params_positional.out +++ b/progenitor-impl/tests/output/test_default_params_positional.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; diff --git a/progenitor-impl/tests/output/test_freeform_response.out b/progenitor-impl/tests/output/test_freeform_response.out index 4665b3de..216ff1b0 100644 --- a/progenitor-impl/tests/output/test_freeform_response.out +++ b/progenitor-impl/tests/output/test_freeform_response.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; diff --git a/progenitor-impl/tests/output/test_renamed_parameters.out b/progenitor-impl/tests/output/test_renamed_parameters.out index 547cd363..cff4488d 100644 --- a/progenitor-impl/tests/output/test_renamed_parameters.out +++ b/progenitor-impl/tests/output/test_renamed_parameters.out @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, RequestBuilderExt}; +use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; From 77ff7d29146922731b26116b959cfd1589fbb073 Mon Sep 17 00:00:00 2001 From: ahirner Date: Sat, 28 Oct 2023 13:43:51 +0200 Subject: [PATCH 25/38] factor out uses_request_file_part --- progenitor-impl/src/cli.rs | 61 +++++++++++++++---- .../tests/output/openai-openapi-cli.out | 37 +++++------ 2 files changed, 67 insertions(+), 31 deletions(-) diff --git a/progenitor-impl/src/cli.rs b/progenitor-impl/src/cli.rs index 96910706..7022af6c 100644 --- a/progenitor-impl/src/cli.rs +++ b/progenitor-impl/src/cli.rs @@ -22,6 +22,7 @@ struct CliOperation { cli_fn: TokenStream, execute_fn: TokenStream, execute_trait: TokenStream, + uses_request_file_part: bool, } impl Generator { @@ -101,9 +102,28 @@ impl Generator { }) .collect::>(); + let request_file = methods.iter().any(|method|method.uses_request_file_part).then(|| { + quote! { + fn request_file_part(path: &std::path::PathBuf) -> reqwest::multipart::Part { + use std::io::Read; + let mut file = std::fs::File::open(&path).unwrap(); + let mut value = Vec::new(); + file.read_to_end(&mut value).unwrap(); + // TODO: probably try setting mime type with mime_guess + let part = reqwest::multipart::Part::bytes(value); + if let Some(file_name) = path.file_name() { + part.file_name(file_name.to_string_lossy().into_owned()) + } else { + part + } + } + } + }); + let crate_ident = format_ident!("{}", crate_name); let code = quote! { + #request_file pub struct Cli { client: #crate_ident::Client, over: T, @@ -183,6 +203,7 @@ impl Generator { let CliArg { parser: parser_args, consumer: consumer_args, + uses_request_file_part, } = self.cli_method_args(method); let about = method.summary.as_ref().map(|summary| { @@ -316,6 +337,7 @@ impl Generator { cli_fn, execute_fn, execute_trait, + uses_request_file_part, } } @@ -391,7 +413,7 @@ impl Generator { } }; - args.add_arg(arg_name, CliArg { parser, consumer }) + args.add_arg(arg_name, CliArg::new(parser, consumer)) } // args that destructure a structured body @@ -467,19 +489,12 @@ impl Generator { quote ! { Some(part) } }; let consumer = quote! { - if let Some(file_name) = matches.get_one::(#arg_name) { - use std::io::Read; - let mut file = std::fs::File::open(&file_name).unwrap(); - let mut value = Vec::new(); - file.read_to_end(&mut value).unwrap(); - // todo: here we can construct a (tokio) stream for - // large files, set mime types etc. - let part = reqwest::multipart::Part::bytes(value); + if let Some(path) = matches.get_one::(#arg_name) { + let part = request_file_part(path); request = request.#request_field(#part); } - }; - (param.api_name.clone(), CliArg { parser, consumer }) + (param.api_name.clone(), CliArg { parser, consumer, uses_request_file_part: true }) }); args.args.extend(body_file_args); @@ -550,7 +565,14 @@ impl Generator { #body_json_consumer }; - CliArg { parser, consumer } + CliArg { + parser, + consumer, + uses_request_file_part: args + .args + .values() + .any(|arg| arg.uses_request_file_part), + } } fn cli_method_body_arg( @@ -623,7 +645,7 @@ impl Generator { }) } }; - args.add_arg(prop_name, CliArg { parser, consumer }) + args.add_arg(prop_name, CliArg::new(parser, consumer)) } else if required { args.body_required() } @@ -727,6 +749,19 @@ struct CliArg { /// Code to consume the argument consumer: TokenStream, + + /// Whether request_file_part is used to consume the argument + uses_request_file_part: bool, +} + +impl CliArg { + fn new(parser: TokenStream, consumer: TokenStream) -> Self { + Self { + parser, + consumer, + uses_request_file_part: false, + } + } } #[derive(Debug, Default, PartialEq, Eq)] diff --git a/progenitor-impl/tests/output/openai-openapi-cli.out b/progenitor-impl/tests/output/openai-openapi-cli.out index 261b52b1..4252d3de 100644 --- a/progenitor-impl/tests/output/openai-openapi-cli.out +++ b/progenitor-impl/tests/output/openai-openapi-cli.out @@ -1,3 +1,16 @@ +fn request_file_part(path: &std::path::PathBuf) -> reqwest::multipart::Part { + use std::io::Read; + let mut file = std::fs::File::open(&path).unwrap(); + let mut value = Vec::new(); + file.read_to_end(&mut value).unwrap(); + let part = reqwest::multipart::Part::bytes(value); + if let Some(file_name) = path.file_name() { + part.file_name(file_name.to_string_lossy().into_owned()) + } else { + part + } +} + pub struct Cli { client: sdk::Client, over: T, @@ -341,21 +354,13 @@ impl Cli { pub async fn execute_create_image_edit(&self, matches: &clap::ArgMatches) { let mut request = self.client.create_image_edit(); - if let Some(file_name) = matches.get_one::("image") { - use std::io::Read; - let mut file = std::fs::File::open(&file_name).unwrap(); - let mut value = Vec::new(); - file.read_to_end(&mut value).unwrap(); - let part = reqwest::multipart::Part::bytes(value); + if let Some(path) = matches.get_one::("image") { + let part = request_file_part(path); request = request.image(part); } - if let Some(file_name) = matches.get_one::("mask") { - use std::io::Read; - let mut file = std::fs::File::open(&file_name).unwrap(); - let mut value = Vec::new(); - file.read_to_end(&mut value).unwrap(); - let part = reqwest::multipart::Part::bytes(value); + if let Some(path) = matches.get_one::("mask") { + let part = request_file_part(path); request = request.mask(Some(part)); } @@ -403,12 +408,8 @@ impl Cli { pub async fn execute_create_image_variation(&self, matches: &clap::ArgMatches) { let mut request = self.client.create_image_variation(); - if let Some(file_name) = matches.get_one::("image") { - use std::io::Read; - let mut file = std::fs::File::open(&file_name).unwrap(); - let mut value = Vec::new(); - file.read_to_end(&mut value).unwrap(); - let part = reqwest::multipart::Part::bytes(value); + if let Some(path) = matches.get_one::("image") { + let part = request_file_part(path); request = request.image(part); } From 716193f8221b99b4fb8d936760c3883f6ec00590 Mon Sep 17 00:00:00 2001 From: ahirner Date: Sat, 28 Oct 2023 16:46:05 +0200 Subject: [PATCH 26/38] let cli consumers override file request part --- progenitor-impl/src/cli.rs | 26 +++++++------- .../tests/output/openai-openapi-cli.out | 35 ++++++++++--------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/progenitor-impl/src/cli.rs b/progenitor-impl/src/cli.rs index 7022af6c..563cea7a 100644 --- a/progenitor-impl/src/cli.rs +++ b/progenitor-impl/src/cli.rs @@ -104,18 +104,20 @@ impl Generator { let request_file = methods.iter().any(|method|method.uses_request_file_part).then(|| { quote! { - fn request_file_part(path: &std::path::PathBuf) -> reqwest::multipart::Part { + fn request_file_part( + &self, + path: &std::path::PathBuf, + ) -> Result { use std::io::Read; - let mut file = std::fs::File::open(&path).unwrap(); + let mut file = std::fs::File::open(&path).map_err(|e| e.to_string())?; let mut value = Vec::new(); - file.read_to_end(&mut value).unwrap(); - // TODO: probably try setting mime type with mime_guess + file.read_to_end(&mut value).map_err(|e| e.to_string())?; let part = reqwest::multipart::Part::bytes(value); - if let Some(file_name) = path.file_name() { + Ok(if let Some(file_name) = path.file_name() { part.file_name(file_name.to_string_lossy().into_owned()) } else { part - } + }) } } }); @@ -123,7 +125,6 @@ impl Generator { let crate_ident = format_ident!("{}", crate_name); let code = quote! { - #request_file pub struct Cli { client: #crate_ident::Client, over: T, @@ -172,6 +173,7 @@ impl Generator { pub trait CliOverride { #(#trait_ops)* + #request_file } impl CliOverride for () {} @@ -478,10 +480,10 @@ impl Generator { let arg_name = param.api_name.to_kebab_case(); let request_field = format_ident!("{}", param.name); let parser = quote! { - clap::Arg::new(#arg_name) - .required(#required) - .value_parser(clap::value_parser!(std::path::PathBuf)) - #help + clap::Arg::new(#arg_name) + .required(#required) + .value_parser(clap::value_parser!(std::path::PathBuf)) + #help }; let part = if required { quote! { part } @@ -490,7 +492,7 @@ impl Generator { }; let consumer = quote! { if let Some(path) = matches.get_one::(#arg_name) { - let part = request_file_part(path); + let part = self.over.request_file_part(path).unwrap(); request = request.#request_field(#part); } }; diff --git a/progenitor-impl/tests/output/openai-openapi-cli.out b/progenitor-impl/tests/output/openai-openapi-cli.out index 4252d3de..d4192265 100644 --- a/progenitor-impl/tests/output/openai-openapi-cli.out +++ b/progenitor-impl/tests/output/openai-openapi-cli.out @@ -1,16 +1,3 @@ -fn request_file_part(path: &std::path::PathBuf) -> reqwest::multipart::Part { - use std::io::Read; - let mut file = std::fs::File::open(&path).unwrap(); - let mut value = Vec::new(); - file.read_to_end(&mut value).unwrap(); - let part = reqwest::multipart::Part::bytes(value); - if let Some(file_name) = path.file_name() { - part.file_name(file_name.to_string_lossy().into_owned()) - } else { - part - } -} - pub struct Cli { client: sdk::Client, over: T, @@ -355,12 +342,12 @@ impl Cli { pub async fn execute_create_image_edit(&self, matches: &clap::ArgMatches) { let mut request = self.client.create_image_edit(); if let Some(path) = matches.get_one::("image") { - let part = request_file_part(path); + let part = self.over.request_file_part(path).unwrap(); request = request.image(part); } if let Some(path) = matches.get_one::("mask") { - let part = request_file_part(path); + let part = self.over.request_file_part(path).unwrap(); request = request.mask(Some(part)); } @@ -409,7 +396,7 @@ impl Cli { pub async fn execute_create_image_variation(&self, matches: &clap::ArgMatches) { let mut request = self.client.create_image_variation(); if let Some(path) = matches.get_one::("image") { - let part = request_file_part(path); + let part = self.over.request_file_part(path).unwrap(); request = request.image(part); } @@ -477,6 +464,22 @@ pub trait CliOverride { ) -> Result<(), String> { Ok(()) } + + fn request_file_part( + &self, + path: &std::path::PathBuf, + ) -> Result { + use std::io::Read; + let mut file = std::fs::File::open(&path).map_err(|e| e.to_string())?; + let mut value = Vec::new(); + file.read_to_end(&mut value).map_err(|e| e.to_string())?; + let part = reqwest::multipart::Part::bytes(value); + Ok(if let Some(file_name) = path.file_name() { + part.file_name(file_name.to_string_lossy().into_owned()) + } else { + part + }) + } } impl CliOverride for () {} From 35ed72e7603361d5a06043cafa784db9e77e1816 Mon Sep 17 00:00:00 2001 From: ahirner Date: Sat, 28 Oct 2023 22:14:35 +0200 Subject: [PATCH 27/38] httpmock form data to a reasonable extent --- progenitor-impl/src/httpmock.rs | 35 ++++++++++++++++--- .../tests/output/openai-openapi-httpmock.out | 16 +++++---- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/progenitor-impl/src/httpmock.rs b/progenitor-impl/src/httpmock.rs index 4bae5c04..32320124 100644 --- a/progenitor-impl/src/httpmock.rs +++ b/progenitor-impl/src/httpmock.rs @@ -183,11 +183,13 @@ impl Generator { OperationParameterKind::Body( BodyContentType::FormData(required), ) => { - // todo: how to mock? + // No data can be accessed from a reqwest::multiform::Part + // Hence, only form-data headers will be checked + // automatically. if *required { - quote! { Part } + quote! { () } } else { - quote! { Option } + quote! { Option<()> } } } _ => unreachable!(), @@ -270,9 +272,32 @@ impl Generator { _ => unreachable!(), } } + // no binary data checks supported: + // https://github.com/alexliesenfeld/httpmock/issues/39#issuecomment-983140233 + // you should check ascii text manually OperationParameterType::FormPart => { - // todo: what does it do? - quote! { self.0 } + if let BodyContentType::FormData(required) = + body_content_type + { + let f = + format!("form-data; name=\"{}\"", name); + if *required { + quote! { + Self(self.0.body_contains(#f)) + } + } else { + quote! { + if let Some(()) = value { + Self(self.0.body_contains(#f)) + } + else { + self + } + } + } + } else { + unreachable!() + } } } } diff --git a/progenitor-impl/tests/output/openai-openapi-httpmock.out b/progenitor-impl/tests/output/openai-openapi-httpmock.out index 95a8dedc..d610333e 100644 --- a/progenitor-impl/tests/output/openai-openapi-httpmock.out +++ b/progenitor-impl/tests/output/openai-openapi-httpmock.out @@ -61,12 +61,16 @@ pub mod operations { Self(self.0.json_body_obj(value)) } - pub fn mask(self, value: Option) -> Self { - self.0 + pub fn mask(self, value: Option<()>) -> Self { + if let Some(()) = value { + Self(self.0.body_contains("form-data; name=\"mask\"")) + } else { + self + } } - pub fn image(self, value: Part) -> Self { - self.0 + pub fn image(self, value: ()) -> Self { + Self(self.0.body_contains("form-data; name=\"image\"")) } } @@ -108,8 +112,8 @@ pub mod operations { Self(self.0.json_body_obj(value)) } - pub fn image(self, value: Part) -> Self { - self.0 + pub fn image(self, value: ()) -> Self { + Self(self.0.body_contains("form-data; name=\"image\"")) } } From 0ab99117461265c981157f6f053b330c32a9019d Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 29 Oct 2023 00:53:18 +0200 Subject: [PATCH 28/38] blanket form data usage flag to remove unneeded .out diffs --- cargo-progenitor/src/main.rs | 2 +- progenitor-impl/src/lib.rs | 32 +++++++++++++------ progenitor-impl/src/method.rs | 21 ++++++------ .../tests/output/buildomat-builder-tagged.out | 4 +-- .../tests/output/buildomat-builder.out | 7 ++-- .../tests/output/buildomat-positional.out | 4 +-- .../tests/output/keeper-builder-tagged.out | 4 +-- .../tests/output/keeper-builder.out | 7 ++-- .../tests/output/keeper-positional.out | 4 +-- .../tests/output/nexus-builder-tagged.out | 4 +-- .../tests/output/nexus-builder.out | 7 ++-- .../tests/output/nexus-positional.out | 4 +-- .../output/openai-openapi-builder-tagged.out | 3 +- .../tests/output/openai-openapi-builder.out | 3 +- .../output/openai-openapi-positional.out | 3 +- .../output/param-collision-builder-tagged.out | 4 +-- .../tests/output/param-collision-builder.out | 7 ++-- .../output/param-collision-positional.out | 4 +-- .../output/param-overrides-builder-tagged.out | 4 +-- .../tests/output/param-overrides-builder.out | 7 ++-- .../output/param-overrides-positional.out | 4 +-- .../output/propolis-server-builder-tagged.out | 4 +-- .../tests/output/propolis-server-builder.out | 7 ++-- .../output/propolis-server-positional.out | 4 +-- .../output/test_default_params_builder.out | 7 ++-- .../output/test_default_params_positional.out | 4 +-- .../tests/output/test_freeform_response.out | 4 +-- .../tests/output/test_renamed_parameters.out | 4 +-- 28 files changed, 66 insertions(+), 107 deletions(-) diff --git a/cargo-progenitor/src/main.rs b/cargo-progenitor/src/main.rs index 75e61db2..fc8542b5 100644 --- a/cargo-progenitor/src/main.rs +++ b/cargo-progenitor/src/main.rs @@ -290,7 +290,7 @@ pub fn dependencies(builder: Generator, include_client: bool) -> Vec { dependency_versions.get("rand").unwrap() )); } - if type_space.uses_serde_json() || builder.uses_serde_json() { + if type_space.uses_serde_json() || builder.uses_form_parts() { deps.push(format!( "serde_json = \"{}\"", dependency_versions.get("serde_json").unwrap() diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index 2c656215..9665bfb3 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -44,7 +44,7 @@ pub struct Generator { settings: GenerationSettings, uses_futures: bool, uses_websockets: bool, - uses_serde_json: bool, + uses_form_parts: bool, } #[derive(Clone)] @@ -205,7 +205,7 @@ impl Default for Generator { settings: Default::default(), uses_futures: Default::default(), uses_websockets: Default::default(), - uses_serde_json: Default::default(), + uses_form_parts: Default::default(), } } } @@ -246,7 +246,7 @@ impl Generator { settings: settings.clone(), uses_futures: false, uses_websockets: false, - uses_serde_json: false, + uses_form_parts: false, } } @@ -348,17 +348,24 @@ impl Generator { }; let version_str = &spec.info.version; + let (to_form_string, pub_part) = if self.uses_form_parts { + ( + Some(quote! { ,to_form_string }), + Some(quote! {pub use reqwest::multipart::Part; }), + ) + } else { + (None, None) + }; let file = quote! { // Re-export ResponseValue and Error since those are used by the // public interface of Client. pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] - use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; + use progenitor_client::{encode_path, RequestBuilderExt #to_form_string }; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; - #[allow(unused_imports)] - use reqwest::multipart::Part; + #pub_part pub mod types { use serde::{Deserialize, Serialize}; @@ -481,6 +488,12 @@ impl Generator { .map(|method| self.builder_impl(method)) .collect::>(); + let form_parts = self.uses_form_parts.then(|| { + quote! { + to_form_string, + Part, + } + }); let out = quote! { impl Client { #(#builder_methods)* @@ -491,14 +504,13 @@ impl Generator { #[allow(unused_imports)] use super::{ encode_path, - to_form_string, + #form_parts ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, - Part, }; #(#builder_struct)* @@ -566,8 +578,8 @@ impl Generator { self.uses_websockets } - pub fn uses_serde_json(&self) -> bool { - self.uses_serde_json + pub fn uses_form_parts(&self) -> bool { + self.uses_form_parts } } diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 1a1cc537..79497176 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -446,12 +446,15 @@ impl Generator { } params.extend(self.get_body_params(operation, components)?); - if params - .iter() - .any(|param| matches!(param.typ, OperationParameterType::FormPart)) - { - // body fields use serde_json for serialization - self.uses_serde_json = true; + // check if some bodies use serde_json and more for serialization + if params.iter().any(|param| { + matches!(param.typ, OperationParameterType::FormPart) + || matches!( + param.kind, + OperationParameterKind::Body(BodyContentType::FormData(_)) + ) + }) { + self.uses_form_parts = true; } let tmp = crate::template::parse(path)?; @@ -2470,11 +2473,9 @@ impl Generator { || form_parts_optional.len() > 0 || build_form_type_id.is_some() { - let mutt = if form_parts_optional.len() > 0 { + let mutt = (form_parts_optional.len() > 0).then(|| { quote! {mut} - } else { - quote! {} - }; + }); form_parts.insert( 0, quote! { diff --git a/progenitor-impl/tests/output/buildomat-builder-tagged.out b/progenitor-impl/tests/output/buildomat-builder-tagged.out index 837218b7..832b351d 100644 --- a/progenitor-impl/tests/output/buildomat-builder-tagged.out +++ b/progenitor-impl/tests/output/buildomat-builder-tagged.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/buildomat-builder.out b/progenitor-impl/tests/output/buildomat-builder.out index d8ea9bf5..077f8489 100644 --- a/progenitor-impl/tests/output/buildomat-builder.out +++ b/progenitor-impl/tests/output/buildomat-builder.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] @@ -1870,8 +1868,7 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, to_form_string, ByteStream, Error, HeaderMap, HeaderValue, Part, - RequestBuilderExt, ResponseValue, + encode_path, ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, }; ///Builder for [`Client::control_hold`] /// diff --git a/progenitor-impl/tests/output/buildomat-positional.out b/progenitor-impl/tests/output/buildomat-positional.out index 8c0d477a..1713415c 100644 --- a/progenitor-impl/tests/output/buildomat-positional.out +++ b/progenitor-impl/tests/output/buildomat-positional.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/keeper-builder-tagged.out b/progenitor-impl/tests/output/keeper-builder-tagged.out index e252f297..42065bb3 100644 --- a/progenitor-impl/tests/output/keeper-builder-tagged.out +++ b/progenitor-impl/tests/output/keeper-builder-tagged.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/keeper-builder.out b/progenitor-impl/tests/output/keeper-builder.out index b8abdc7d..bd019a77 100644 --- a/progenitor-impl/tests/output/keeper-builder.out +++ b/progenitor-impl/tests/output/keeper-builder.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] @@ -1060,8 +1058,7 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, to_form_string, ByteStream, Error, HeaderMap, HeaderValue, Part, - RequestBuilderExt, ResponseValue, + encode_path, ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, }; ///Builder for [`Client::enrol`] /// diff --git a/progenitor-impl/tests/output/keeper-positional.out b/progenitor-impl/tests/output/keeper-positional.out index 37fc33e5..280b3ed2 100644 --- a/progenitor-impl/tests/output/keeper-positional.out +++ b/progenitor-impl/tests/output/keeper-positional.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/nexus-builder-tagged.out b/progenitor-impl/tests/output/nexus-builder-tagged.out index 7bf9eee3..679c98ac 100644 --- a/progenitor-impl/tests/output/nexus-builder-tagged.out +++ b/progenitor-impl/tests/output/nexus-builder-tagged.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/nexus-builder.out b/progenitor-impl/tests/output/nexus-builder.out index cbecab45..9139fac9 100644 --- a/progenitor-impl/tests/output/nexus-builder.out +++ b/progenitor-impl/tests/output/nexus-builder.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] @@ -20822,8 +20820,7 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, to_form_string, ByteStream, Error, HeaderMap, HeaderValue, Part, - RequestBuilderExt, ResponseValue, + encode_path, ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, }; ///Builder for [`Client::disk_view_by_id`] /// diff --git a/progenitor-impl/tests/output/nexus-positional.out b/progenitor-impl/tests/output/nexus-positional.out index 92c5c87c..60652844 100644 --- a/progenitor-impl/tests/output/nexus-positional.out +++ b/progenitor-impl/tests/output/nexus-positional.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/openai-openapi-builder-tagged.out b/progenitor-impl/tests/output/openai-openapi-builder-tagged.out index 9ac8d26c..e6c98c03 100644 --- a/progenitor-impl/tests/output/openai-openapi-builder-tagged.out +++ b/progenitor-impl/tests/output/openai-openapi-builder-tagged.out @@ -3,8 +3,7 @@ use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; +pub use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/openai-openapi-builder.out b/progenitor-impl/tests/output/openai-openapi-builder.out index 4652fd53..3251a5c7 100644 --- a/progenitor-impl/tests/output/openai-openapi-builder.out +++ b/progenitor-impl/tests/output/openai-openapi-builder.out @@ -3,8 +3,7 @@ use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; +pub use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/openai-openapi-positional.out b/progenitor-impl/tests/output/openai-openapi-positional.out index 9d52d7b9..c2e7901c 100644 --- a/progenitor-impl/tests/output/openai-openapi-positional.out +++ b/progenitor-impl/tests/output/openai-openapi-positional.out @@ -3,8 +3,7 @@ use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; +pub use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/param-collision-builder-tagged.out b/progenitor-impl/tests/output/param-collision-builder-tagged.out index cdf7bcef..4de691e0 100644 --- a/progenitor-impl/tests/output/param-collision-builder-tagged.out +++ b/progenitor-impl/tests/output/param-collision-builder-tagged.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/param-collision-builder.out b/progenitor-impl/tests/output/param-collision-builder.out index 5c1d9288..5efd0dc0 100644 --- a/progenitor-impl/tests/output/param-collision-builder.out +++ b/progenitor-impl/tests/output/param-collision-builder.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] @@ -105,8 +103,7 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, to_form_string, ByteStream, Error, HeaderMap, HeaderValue, Part, - RequestBuilderExt, ResponseValue, + encode_path, ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, }; ///Builder for [`Client::key_get`] /// diff --git a/progenitor-impl/tests/output/param-collision-positional.out b/progenitor-impl/tests/output/param-collision-positional.out index abdff8a4..8b40af66 100644 --- a/progenitor-impl/tests/output/param-collision-positional.out +++ b/progenitor-impl/tests/output/param-collision-positional.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/param-overrides-builder-tagged.out b/progenitor-impl/tests/output/param-overrides-builder-tagged.out index 3593fa8e..71d12a3a 100644 --- a/progenitor-impl/tests/output/param-overrides-builder-tagged.out +++ b/progenitor-impl/tests/output/param-overrides-builder-tagged.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/param-overrides-builder.out b/progenitor-impl/tests/output/param-overrides-builder.out index 7e417273..d0c638f8 100644 --- a/progenitor-impl/tests/output/param-overrides-builder.out +++ b/progenitor-impl/tests/output/param-overrides-builder.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] @@ -99,8 +97,7 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, to_form_string, ByteStream, Error, HeaderMap, HeaderValue, Part, - RequestBuilderExt, ResponseValue, + encode_path, ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, }; ///Builder for [`Client::key_get`] /// diff --git a/progenitor-impl/tests/output/param-overrides-positional.out b/progenitor-impl/tests/output/param-overrides-positional.out index a7b9d2fd..05610f4b 100644 --- a/progenitor-impl/tests/output/param-overrides-positional.out +++ b/progenitor-impl/tests/output/param-overrides-positional.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/propolis-server-builder-tagged.out b/progenitor-impl/tests/output/propolis-server-builder-tagged.out index 035a9fcc..7a8cd299 100644 --- a/progenitor-impl/tests/output/propolis-server-builder-tagged.out +++ b/progenitor-impl/tests/output/propolis-server-builder-tagged.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/propolis-server-builder.out b/progenitor-impl/tests/output/propolis-server-builder.out index c5ceb80f..3e93141b 100644 --- a/progenitor-impl/tests/output/propolis-server-builder.out +++ b/progenitor-impl/tests/output/propolis-server-builder.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] @@ -2114,8 +2112,7 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, to_form_string, ByteStream, Error, HeaderMap, HeaderValue, Part, - RequestBuilderExt, ResponseValue, + encode_path, ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, }; ///Builder for [`Client::instance_get`] /// diff --git a/progenitor-impl/tests/output/propolis-server-positional.out b/progenitor-impl/tests/output/propolis-server-positional.out index bdc7d206..0a69540f 100644 --- a/progenitor-impl/tests/output/propolis-server-positional.out +++ b/progenitor-impl/tests/output/propolis-server-positional.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/test_default_params_builder.out b/progenitor-impl/tests/output/test_default_params_builder.out index 6b4dff79..59aa94e2 100644 --- a/progenitor-impl/tests/output/test_default_params_builder.out +++ b/progenitor-impl/tests/output/test_default_params_builder.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] @@ -304,8 +302,7 @@ pub mod builder { use super::types; #[allow(unused_imports)] use super::{ - encode_path, to_form_string, ByteStream, Error, HeaderMap, HeaderValue, Part, - RequestBuilderExt, ResponseValue, + encode_path, ByteStream, Error, HeaderMap, HeaderValue, RequestBuilderExt, ResponseValue, }; ///Builder for [`Client::default_params`] /// diff --git a/progenitor-impl/tests/output/test_default_params_positional.out b/progenitor-impl/tests/output/test_default_params_positional.out index ea55cf83..5ca013cb 100644 --- a/progenitor-impl/tests/output/test_default_params_positional.out +++ b/progenitor-impl/tests/output/test_default_params_positional.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/test_freeform_response.out b/progenitor-impl/tests/output/test_freeform_response.out index 216ff1b0..ba64d543 100644 --- a/progenitor-impl/tests/output/test_freeform_response.out +++ b/progenitor-impl/tests/output/test_freeform_response.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] diff --git a/progenitor-impl/tests/output/test_renamed_parameters.out b/progenitor-impl/tests/output/test_renamed_parameters.out index cff4488d..9701c655 100644 --- a/progenitor-impl/tests/output/test_renamed_parameters.out +++ b/progenitor-impl/tests/output/test_renamed_parameters.out @@ -1,10 +1,8 @@ #[allow(unused_imports)] -use progenitor_client::{encode_path, to_form_string, RequestBuilderExt}; +use progenitor_client::{encode_path, RequestBuilderExt}; pub use progenitor_client::{ByteStream, Error, ResponseValue}; #[allow(unused_imports)] use reqwest::header::{HeaderMap, HeaderValue}; -#[allow(unused_imports)] -use reqwest::multipart::Part; pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] From 91bea0bc3c5532ee34fd1a693e3e32fd34e0f35d Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 29 Oct 2023 12:56:02 +0100 Subject: [PATCH 29/38] cargo clippy on new code --- progenitor-impl/src/method.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 79497176..807b64c1 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -2469,11 +2469,11 @@ impl Generator { } // only build if any param or at least a body without additional parts - if form_parts.len() > 0 - || form_parts_optional.len() > 0 + if !form_parts.is_empty() + || !form_parts_optional.is_empty() || build_form_type_id.is_some() { - let mutt = (form_parts_optional.len() > 0).then(|| { + let mutt = (!form_parts_optional.is_empty()).then(|| { quote! {mut} }); form_parts.insert( From fa31f6ecf67d74f029f31109630bd8c653cdf2d2 Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 29 Oct 2023 16:10:46 +0100 Subject: [PATCH 30/38] clarify use_form_parts comment --- progenitor-impl/src/method.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 807b64c1..333ce6c4 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -446,7 +446,8 @@ impl Generator { } params.extend(self.get_body_params(operation, components)?); - // check if some bodies use serde_json and more for serialization + // Set use_form_parts if any parameter requires all or a subset of + // features from serde_json and associated imports if params.iter().any(|param| { matches!(param.typ, OperationParameterType::FormPart) || matches!( From 2c9cbdb256e913207e44451dfbb88b78332035fe Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 29 Oct 2023 16:14:28 +0100 Subject: [PATCH 31/38] move unreachable variant of method param last --- progenitor-impl/src/method.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 333ce6c4..d26bccae 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -648,13 +648,13 @@ impl Generator { _ => unreachable!(), } } - (OperationParameterType::RawBody, true) => unreachable!(), (OperationParameterType::FormPart, false) => { quote! { Part } } (OperationParameterType::FormPart, true) => { quote! { Option } } + (OperationParameterType::RawBody, true) => unreachable!(), }; quote! { #name: #typ From 829199c7c876eb920494a45c0915a808460c8437 Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 29 Oct 2023 22:14:51 +0100 Subject: [PATCH 32/38] document new OperationParameter variants --- progenitor-impl/src/method.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index d26bccae..f347f771 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -106,6 +106,7 @@ pub struct OperationParameter { pub enum OperationParameterType { Type(TypeId), RawBody, + /// Denotes binary parts of a multipart/form-data body. FormPart, } @@ -142,6 +143,11 @@ pub enum BodyContentType { Json, FormUrlencoded, Text(String), + /// Binary or text parts of a multipart/form-data body. + /// The bool signifies if it is required. + // TODO any other body content ought to become optional, + // after which flag would be redundant + // (see comment in OperationParameterKind) FormData(bool), } From 52064256760b91c7bc398a461978bc62bd6840a1 Mon Sep 17 00:00:00 2001 From: ahirner Date: Sun, 29 Oct 2023 23:00:45 +0100 Subject: [PATCH 33/38] docstring for (still hidden) to_form_string --- progenitor-client/src/progenitor_client.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/progenitor-client/src/progenitor_client.rs b/progenitor-client/src/progenitor_client.rs index 736a7abb..aa88b504 100644 --- a/progenitor-client/src/progenitor_client.rs +++ b/progenitor-client/src/progenitor_client.rs @@ -417,6 +417,21 @@ impl RequestBuilderExt for RequestBuilder { } } +/// Returns a string from any json value which is expected +/// to work as `multipart/form-data` text. In particular, a +/// [`serde_json::Value::String`] remains unquoted. +/// +/// # Example +/// ``` +/// # use progenitor_client::to_form_string; +/// use serde_json::json; +/// assert_eq!(to_form_string::<()>(&json!("a")).unwrap(), "a"); +/// assert_eq!(to_form_string::<()>(&json!(101)).unwrap(), "101"); +/// assert_eq!( +/// to_form_string::<()>(&json!({"a": 202})).unwrap(), +/// "{\"a\":202}" +/// ); +/// ``` #[doc(hidden)] pub fn to_form_string( value: &serde_json::Value, From b4e916d2684fc55c5eece3d63034064c49a5e2d5 Mon Sep 17 00:00:00 2001 From: ahirner Date: Mon, 30 Oct 2023 17:56:40 +0100 Subject: [PATCH 34/38] TypeId based FormData bodies for text fields are still required , thus no additional todo here --- progenitor-impl/src/method.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index f347f771..ee66e229 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -1721,7 +1721,6 @@ impl Generator { // a `body_map()` method that operates on the // builder itself. (Some(builder_name), false) => { - // todo: rm for ...Kind::FormData assert_eq!(param.name, "body"); let typ = ty.ident(); let err_msg = format!( From 106205f868589b2711ea98d9e3fbde61ceee944c Mon Sep 17 00:00:00 2001 From: ahirner Date: Mon, 30 Oct 2023 18:27:52 +0100 Subject: [PATCH 35/38] nullable binary strings in form-data hardly make sense --- progenitor-impl/src/method.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index ee66e229..82d7ec0b 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -2517,8 +2517,10 @@ fn get_form_data_params( match (&i.schema_data, &i.schema_kind) { ( openapiv3::SchemaData { - // todo: assert nullable if not required? - //nullable: false, + // nullable is a different concept than required + // and is not a typical or documented case for + // binary strings in form-data properties + nullable: false, default: None, discriminator: None, description, From 2a8e24ad51dd071b7eb5a89e3b358fc34ab4a508 Mon Sep 17 00:00:00 2001 From: ahirner Date: Mon, 30 Oct 2023 19:02:25 +0100 Subject: [PATCH 36/38] no need for own dereferencing of schema objects ReferenceOrExt already does everything we need, amazing --- progenitor-impl/src/method.rs | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index 82d7ec0b..ea70a6c8 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -2251,7 +2251,7 @@ impl Generator { // "type": "string", // "format": "binary" // } - match schema.item(components)? { + match item_schema { openapiv3::Schema { schema_data: openapiv3::SchemaData { @@ -2288,7 +2288,7 @@ impl Generator { // "schema": { // "type": "string", // } - match schema.item(components)? { + match item_schema { openapiv3::Schema { schema_data: openapiv3::SchemaData { @@ -2339,24 +2339,7 @@ impl Generator { } BodyContentType::FormData(_) => { // remove FormPart properties - // TODO: move into util - let schema = match schema { - ReferenceOr::Reference { reference } => { - let idx = reference.rfind('/').unwrap(); - let key = &reference[idx + 1..]; - let parameters = ::get_components( - components.as_ref().unwrap(), - ); - parameters - .get(key) - .cloned() - .unwrap_or_else(|| panic!("key {} is missing", key)) - .into_item() - .unwrap() - } - ReferenceOr::Item(s) => s.to_owned(), - }; - let mut schema = schema.to_schema(); + let mut schema = item_schema.to_schema(); if let schemars::schema::Schema::Object( schemars::schema::SchemaObject { object: Some(ref mut object), From 9e204ed2279467258237b5c43043a4fd01e42713 Mon Sep 17 00:00:00 2001 From: ahirner Date: Mon, 30 Oct 2023 19:08:08 +0100 Subject: [PATCH 37/38] There is no need to treat binary strings at the type id level anymore .. because a cloned and pruned body type is created separately. We probalby want to get rid of redundant body structs iff. they are referenced in multipart/form-data contents only --- progenitor-impl/src/lib.rs | 39 +------------------ .../output/openai-openapi-builder-tagged.out | 18 ++++----- .../tests/output/openai-openapi-builder.out | 18 ++++----- 3 files changed, 19 insertions(+), 56 deletions(-) diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index 9665bfb3..8d35eed1 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -47,7 +47,7 @@ pub struct Generator { uses_form_parts: bool, } -#[derive(Clone)] +#[derive(Clone, Default)] pub struct GenerationSettings { interface: InterfaceStyle, tag: TagStyle, @@ -61,43 +61,6 @@ pub struct GenerationSettings { convert: Vec<(schemars::schema::SchemaObject, String, Vec)>, } -impl Default for GenerationSettings { - fn default() -> Self { - // set Type from String to meta data for form body parts - use schemars::schema::{InstanceType, SchemaObject}; - let bin_string = SchemaObject { - instance_type: Some(schemars::schema::SingleOrVec::Single( - Box::new(InstanceType::String), - )), - format: Some("binary".to_string()), - ..SchemaObject::default() - }; - // This treats binary strings on the level of generated types. - // For now, we'd want to remove this field because - // the only supported binary string is handled as FormPart instead. - // Not sure if or how patch could help, at least making it not - // required. - // TODO: remove after the right ref objects can be modified. - let convert = ( - bin_string, - // () errors and doesn't convert to unit - "Vec<()>".to_string(), - vec![TypeImpl::Default, TypeImpl::Default, TypeImpl::Display], - ); - Self { - interface: InterfaceStyle::default(), - tag: TagStyle::default(), - inner_type: None, - pre_hook: None, - post_hook: None, - extra_derives: Vec::default(), - patch: HashMap::default(), - replace: HashMap::default(), - convert: vec![convert], - } - } -} - #[derive(Clone, Deserialize, PartialEq, Eq)] pub enum InterfaceStyle { Positional, diff --git a/progenitor-impl/tests/output/openai-openapi-builder-tagged.out b/progenitor-impl/tests/output/openai-openapi-builder-tagged.out index e6c98c03..7fca8784 100644 --- a/progenitor-impl/tests/output/openai-openapi-builder-tagged.out +++ b/progenitor-impl/tests/output/openai-openapi-builder-tagged.out @@ -166,12 +166,12 @@ pub mod types { ///The image to edit. Must be a valid PNG file, less than 4MB, and /// square. If mask is not provided, image must have transparency, which /// will be used as the mask. - pub image: Vec<()>, + pub image: String, ///An additional image whose fully transparent areas (e.g. where alpha /// is zero) indicate where `image` should be edited. Must be a valid /// PNG file, less than 4MB, and have the same dimensions as `image`. #[serde(default, skip_serializing_if = "Option::is_none")] - pub mask: Option>, + pub mask: Option, ///The number of images to generate. Must be between 1 and 10. #[serde(default = "defaults::create_image_edit_request_n")] pub n: Option, @@ -630,7 +630,7 @@ pub mod types { pub struct CreateImageVariationRequest { ///The image to use as the basis for the variation(s). Must be a valid /// PNG file, less than 4MB, and square. - pub image: Vec<()>, + pub image: String, ///The number of images to generate. Must be between 1 and 10. #[serde(default = "defaults::create_image_variation_request_n")] pub n: Option, @@ -965,8 +965,8 @@ pub mod types { #[derive(Clone, Debug)] pub struct CreateImageEditRequest { - image: Result, String>, - mask: Result>, String>, + image: Result, + mask: Result, String>, n: Result, String>, prompt: Result, response_format: Result, String>, @@ -993,7 +993,7 @@ pub mod types { impl CreateImageEditRequest { pub fn image(mut self, value: T) -> Self where - T: std::convert::TryInto>, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.image = value @@ -1003,7 +1003,7 @@ pub mod types { } pub fn mask(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.mask = value @@ -1280,7 +1280,7 @@ pub mod types { #[derive(Clone, Debug)] pub struct CreateImageVariationRequest { - image: Result, String>, + image: Result, n: Result, String>, response_format: Result, String>, @@ -1305,7 +1305,7 @@ pub mod types { impl CreateImageVariationRequest { pub fn image(mut self, value: T) -> Self where - T: std::convert::TryInto>, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.image = value diff --git a/progenitor-impl/tests/output/openai-openapi-builder.out b/progenitor-impl/tests/output/openai-openapi-builder.out index 3251a5c7..b0e49513 100644 --- a/progenitor-impl/tests/output/openai-openapi-builder.out +++ b/progenitor-impl/tests/output/openai-openapi-builder.out @@ -170,12 +170,12 @@ pub mod types { ///The image to edit. Must be a valid PNG file, less than 4MB, and /// square. If mask is not provided, image must have transparency, which /// will be used as the mask. - pub image: Vec<()>, + pub image: String, ///An additional image whose fully transparent areas (e.g. where alpha /// is zero) indicate where `image` should be edited. Must be a valid /// PNG file, less than 4MB, and have the same dimensions as `image`. #[serde(default, skip_serializing_if = "Option::is_none")] - pub mask: Option>, + pub mask: Option, ///The number of images to generate. Must be between 1 and 10. #[serde(default = "defaults::create_image_edit_request_n")] pub n: Option, @@ -646,7 +646,7 @@ pub mod types { pub struct CreateImageVariationRequest { ///The image to use as the basis for the variation(s). Must be a valid /// PNG file, less than 4MB, and square. - pub image: Vec<()>, + pub image: String, ///The number of images to generate. Must be between 1 and 10. #[serde(default = "defaults::create_image_variation_request_n")] pub n: Option, @@ -985,8 +985,8 @@ pub mod types { #[derive(Clone, Debug)] pub struct CreateImageEditRequest { - image: Result, String>, - mask: Result>, String>, + image: Result, + mask: Result, String>, n: Result, String>, prompt: Result, response_format: Result, String>, @@ -1013,7 +1013,7 @@ pub mod types { impl CreateImageEditRequest { pub fn image(mut self, value: T) -> Self where - T: std::convert::TryInto>, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.image = value @@ -1023,7 +1023,7 @@ pub mod types { } pub fn mask(mut self, value: T) -> Self where - T: std::convert::TryInto>>, + T: std::convert::TryInto>, T::Error: std::fmt::Display, { self.mask = value @@ -1300,7 +1300,7 @@ pub mod types { #[derive(Clone, Debug)] pub struct CreateImageVariationRequest { - image: Result, String>, + image: Result, n: Result, String>, response_format: Result, String>, @@ -1325,7 +1325,7 @@ pub mod types { impl CreateImageVariationRequest { pub fn image(mut self, value: T) -> Self where - T: std::convert::TryInto>, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.image = value From f8baafaacc7d215f5c7402c4d7bce5b33c6d265f Mon Sep 17 00:00:00 2001 From: ahirner Date: Mon, 30 Oct 2023 23:37:58 +0100 Subject: [PATCH 38/38] remove multipart/form-data structs from output if they are exclusively referenced in such requests --- progenitor-impl/src/cli.rs | 14 +- progenitor-impl/src/httpmock.rs | 14 +- progenitor-impl/src/lib.rs | 109 +++- progenitor-impl/src/method.rs | 3 - .../output/openai-openapi-builder-tagged.out | 576 ----------------- .../tests/output/openai-openapi-builder.out | 584 ------------------ .../output/openai-openapi-positional.out | 333 ---------- 7 files changed, 108 insertions(+), 1525 deletions(-) diff --git a/progenitor-impl/src/cli.rs b/progenitor-impl/src/cli.rs index 563cea7a..69fcf5da 100644 --- a/progenitor-impl/src/cli.rs +++ b/progenitor-impl/src/cli.rs @@ -13,9 +13,8 @@ use crate::{ BodyContentType, OperationParameterKind, OperationParameterType, OperationResponseStatus, }, - to_schema::ToSchema, util::{sanitize, Case}, - validate_openapi, Generator, Result, + Generator, Result, }; struct CliOperation { @@ -32,16 +31,7 @@ impl Generator { spec: &OpenAPI, crate_name: &str, ) -> Result { - 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)?; + self.add_ref_types(spec)?; let raw_methods = spec .paths diff --git a/progenitor-impl/src/httpmock.rs b/progenitor-impl/src/httpmock.rs index 32320124..3e5a96a9 100644 --- a/progenitor-impl/src/httpmock.rs +++ b/progenitor-impl/src/httpmock.rs @@ -12,9 +12,8 @@ use crate::{ OperationParameterKind, OperationParameterType, OperationResponse, OperationResponseStatus, }, - to_schema::ToSchema, util::{sanitize, Case}, - validate_openapi, Generator, Result, + Generator, Result, }; struct MockOp { @@ -31,16 +30,7 @@ impl Generator { spec: &OpenAPI, crate_name: &str, ) -> Result { - 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)?; + self.add_ref_types(spec)?; let raw_methods = spec .paths diff --git a/progenitor-impl/src/lib.rs b/progenitor-impl/src/lib.rs index 8d35eed1..3e365521 100644 --- a/progenitor-impl/src/lib.rs +++ b/progenitor-impl/src/lib.rs @@ -213,17 +213,43 @@ impl Generator { } } - pub fn generate_tokens(&mut self, spec: &OpenAPI) -> Result { + fn add_ref_types(&mut self, spec: &OpenAPI) -> Result<()> { validate_openapi(spec)?; + // collect all component names in request body media references + // that are only used for multipart/form-data operations. + let media_conents = 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)| operation) + }) + .flat_map(|operation| operation.request_body.iter()) + .filter_map(|b| b.as_item().map(|b| &b.content)) + .flat_map(|body_content| body_content.iter()); + let form_data_refs_only = get_exclusive_formdata_refs(media_conents); + // 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| { + let filter = form_data_refs_only.clone(); + components.schemas.iter().map(move |(name, ref_or_schema)| { + (!filter.contains(name.as_str())) + .then(|| (name.clone(), ref_or_schema.to_schema())) + }) }) - }); + .filter_map(std::convert::identity); self.type_space.add_ref_types(schemas)?; + Ok(()) + } + + pub fn generate_tokens(&mut self, spec: &OpenAPI) -> Result { + self.add_ref_types(spec)?; let raw_methods = spec .paths @@ -604,10 +630,51 @@ pub fn validate_openapi(spec: &OpenAPI) -> Result<()> { Ok(()) } +/// Return all component names where schema references are keyed +/// by multipart/form-data but nowhere else. The last part of a media +/// schema's json path reference is considered the component name. +fn get_exclusive_formdata_refs<'a>( + media_contents: impl IntoIterator< + Item = (impl AsRef, &'a openapiv3::MediaType), + >, +) -> HashSet { + let mut used_form_excl = HashSet::::new(); + let mut used_elsewhere = HashSet::::new(); + media_contents + .into_iter() + .for_each(|(content_type, media)| { + if let Some(schema_ref) = + media.schema.as_ref().and_then(|s| match s { + openapiv3::ReferenceOr::Reference { reference } => { + Some(reference) + } + openapiv3::ReferenceOr::Item(_) => None, + }) + { + let component_ref = schema_ref + .rsplit_once('/') + .map(|(_a, b)| b) + .unwrap_or(&schema_ref); + + // note that body content types are keyed by content_type + // and not media.encoding, thus don't use it to compare + if content_type.as_ref() != "multipart/form-data" { + used_form_excl.remove(component_ref); + used_elsewhere.insert(component_ref.to_owned()); + } else if !used_elsewhere.contains(schema_ref) { + used_form_excl.insert(component_ref.to_owned()); + }; + }; + }); + used_form_excl +} + #[cfg(test)] mod tests { + use openapiv3::ReferenceOr; use serde_json::json; + use super::*; use crate::Error; #[test] @@ -641,4 +708,36 @@ mod tests { "internal error nope", ); } + + #[test] + fn form_media_matches_exclusively() { + use openapiv3::MediaType; + let form_str = "multipart/form-data"; + let request1: MediaType = MediaType { + schema: Some(ReferenceOr::ref_("#/components/schemas/Request1")), + ..Default::default() + }; + let request2: MediaType = MediaType { + schema: Some(ReferenceOr::ref_("foo/Request2")), + ..Default::default() + }; + + let request3: MediaType = MediaType { + schema: Some(ReferenceOr::ref_("Request3")), + ..Default::default() + }; + + let media_contents = vec![ + (form_str, &request2), + ("other", &request1), + (form_str, &request1), + ("another", &request2), + (form_str, &request3), + ("somewhere", &request1), + ]; + assert_eq!( + get_exclusive_formdata_refs(media_contents), + HashSet::from_iter(vec!["Request3".to_string()].into_iter()) + ); + } } diff --git a/progenitor-impl/src/method.rs b/progenitor-impl/src/method.rs index ea70a6c8..a62a70c9 100644 --- a/progenitor-impl/src/method.rs +++ b/progenitor-impl/src/method.rs @@ -2351,9 +2351,6 @@ impl Generator { object.properties.remove_entry(¶m.api_name); } }; - // TODO: because add_ref_types is called before, the unmodified - // body also exists as additional type ending in Request. - // There should be a better way to modify a reference type. let name = sanitize( &format!( "{}-body", diff --git a/progenitor-impl/tests/output/openai-openapi-builder-tagged.out b/progenitor-impl/tests/output/openai-openapi-builder-tagged.out index 7fca8784..630b8e52 100644 --- a/progenitor-impl/tests/output/openai-openapi-builder-tagged.out +++ b/progenitor-impl/tests/output/openai-openapi-builder-tagged.out @@ -161,168 +161,6 @@ pub mod types { } } - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct CreateImageEditRequest { - ///The image to edit. Must be a valid PNG file, less than 4MB, and - /// square. If mask is not provided, image must have transparency, which - /// will be used as the mask. - pub image: String, - ///An additional image whose fully transparent areas (e.g. where alpha - /// is zero) indicate where `image` should be edited. Must be a valid - /// PNG file, less than 4MB, and have the same dimensions as `image`. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub mask: Option, - ///The number of images to generate. Must be between 1 and 10. - #[serde(default = "defaults::create_image_edit_request_n")] - pub n: Option, - ///A text description of the desired image(s). The maximum length is - /// 1000 characters. - pub prompt: String, - ///The format in which the generated images are returned. Must be one - /// of `url` or `b64_json`. - #[serde(default = "defaults::create_image_edit_request_response_format")] - pub response_format: Option, - ///The size of the generated images. Must be one of `256x256`, - /// `512x512`, or `1024x1024`. - #[serde(default = "defaults::create_image_edit_request_size")] - pub size: Option, - ///A unique identifier representing your end-user, which can help - /// OpenAI to monitor and detect abuse. [Learn - /// more](/docs/guides/safety-best-practices/end-user-ids). - #[serde(default, skip_serializing_if = "Option::is_none")] - pub user: Option, - } - - impl From<&CreateImageEditRequest> for CreateImageEditRequest { - fn from(value: &CreateImageEditRequest) -> Self { - value.clone() - } - } - - impl CreateImageEditRequest { - pub fn builder() -> builder::CreateImageEditRequest { - builder::CreateImageEditRequest::default() - } - } - - ///The format in which the generated images are returned. Must be one of - /// `url` or `b64_json`. - #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] - pub enum CreateImageEditRequestResponseFormat { - #[serde(rename = "url")] - Url, - #[serde(rename = "b64_json")] - B64Json, - } - - impl From<&CreateImageEditRequestResponseFormat> for CreateImageEditRequestResponseFormat { - fn from(value: &CreateImageEditRequestResponseFormat) -> Self { - value.clone() - } - } - - impl ToString for CreateImageEditRequestResponseFormat { - fn to_string(&self) -> String { - match *self { - Self::Url => "url".to_string(), - Self::B64Json => "b64_json".to_string(), - } - } - } - - impl std::str::FromStr for CreateImageEditRequestResponseFormat { - type Err = &'static str; - fn from_str(value: &str) -> Result { - match value { - "url" => Ok(Self::Url), - "b64_json" => Ok(Self::B64Json), - _ => Err("invalid value"), - } - } - } - - impl std::convert::TryFrom<&str> for CreateImageEditRequestResponseFormat { - type Error = &'static str; - fn try_from(value: &str) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom<&String> for CreateImageEditRequestResponseFormat { - type Error = &'static str; - fn try_from(value: &String) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom for CreateImageEditRequestResponseFormat { - type Error = &'static str; - fn try_from(value: String) -> Result { - value.parse() - } - } - - ///The size of the generated images. Must be one of `256x256`, `512x512`, - /// or `1024x1024`. - #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] - pub enum CreateImageEditRequestSize { - #[serde(rename = "256x256")] - _256x256, - #[serde(rename = "512x512")] - _512x512, - #[serde(rename = "1024x1024")] - _1024x1024, - } - - impl From<&CreateImageEditRequestSize> for CreateImageEditRequestSize { - fn from(value: &CreateImageEditRequestSize) -> Self { - value.clone() - } - } - - impl ToString for CreateImageEditRequestSize { - fn to_string(&self) -> String { - match *self { - Self::_256x256 => "256x256".to_string(), - Self::_512x512 => "512x512".to_string(), - Self::_1024x1024 => "1024x1024".to_string(), - } - } - } - - impl std::str::FromStr for CreateImageEditRequestSize { - type Err = &'static str; - fn from_str(value: &str) -> Result { - match value { - "256x256" => Ok(Self::_256x256), - "512x512" => Ok(Self::_512x512), - "1024x1024" => Ok(Self::_1024x1024), - _ => Err("invalid value"), - } - } - } - - impl std::convert::TryFrom<&str> for CreateImageEditRequestSize { - type Error = &'static str; - fn try_from(value: &str) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom<&String> for CreateImageEditRequestSize { - type Error = &'static str; - fn try_from(value: &String) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom for CreateImageEditRequestSize { - type Error = &'static str; - fn try_from(value: String) -> Result { - value.parse() - } - } - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct CreateImageRequest { ///The number of images to generate. Must be between 1 and 10. @@ -626,161 +464,6 @@ pub mod types { } } - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct CreateImageVariationRequest { - ///The image to use as the basis for the variation(s). Must be a valid - /// PNG file, less than 4MB, and square. - pub image: String, - ///The number of images to generate. Must be between 1 and 10. - #[serde(default = "defaults::create_image_variation_request_n")] - pub n: Option, - ///The format in which the generated images are returned. Must be one - /// of `url` or `b64_json`. - #[serde(default = "defaults::create_image_variation_request_response_format")] - pub response_format: Option, - ///The size of the generated images. Must be one of `256x256`, - /// `512x512`, or `1024x1024`. - #[serde(default = "defaults::create_image_variation_request_size")] - pub size: Option, - ///A unique identifier representing your end-user, which can help - /// OpenAI to monitor and detect abuse. [Learn - /// more](/docs/guides/safety-best-practices/end-user-ids). - #[serde(default, skip_serializing_if = "Option::is_none")] - pub user: Option, - } - - impl From<&CreateImageVariationRequest> for CreateImageVariationRequest { - fn from(value: &CreateImageVariationRequest) -> Self { - value.clone() - } - } - - impl CreateImageVariationRequest { - pub fn builder() -> builder::CreateImageVariationRequest { - builder::CreateImageVariationRequest::default() - } - } - - ///The format in which the generated images are returned. Must be one of - /// `url` or `b64_json`. - #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] - pub enum CreateImageVariationRequestResponseFormat { - #[serde(rename = "url")] - Url, - #[serde(rename = "b64_json")] - B64Json, - } - - impl From<&CreateImageVariationRequestResponseFormat> - for CreateImageVariationRequestResponseFormat - { - fn from(value: &CreateImageVariationRequestResponseFormat) -> Self { - value.clone() - } - } - - impl ToString for CreateImageVariationRequestResponseFormat { - fn to_string(&self) -> String { - match *self { - Self::Url => "url".to_string(), - Self::B64Json => "b64_json".to_string(), - } - } - } - - impl std::str::FromStr for CreateImageVariationRequestResponseFormat { - type Err = &'static str; - fn from_str(value: &str) -> Result { - match value { - "url" => Ok(Self::Url), - "b64_json" => Ok(Self::B64Json), - _ => Err("invalid value"), - } - } - } - - impl std::convert::TryFrom<&str> for CreateImageVariationRequestResponseFormat { - type Error = &'static str; - fn try_from(value: &str) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom<&String> for CreateImageVariationRequestResponseFormat { - type Error = &'static str; - fn try_from(value: &String) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom for CreateImageVariationRequestResponseFormat { - type Error = &'static str; - fn try_from(value: String) -> Result { - value.parse() - } - } - - ///The size of the generated images. Must be one of `256x256`, `512x512`, - /// or `1024x1024`. - #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] - pub enum CreateImageVariationRequestSize { - #[serde(rename = "256x256")] - _256x256, - #[serde(rename = "512x512")] - _512x512, - #[serde(rename = "1024x1024")] - _1024x1024, - } - - impl From<&CreateImageVariationRequestSize> for CreateImageVariationRequestSize { - fn from(value: &CreateImageVariationRequestSize) -> Self { - value.clone() - } - } - - impl ToString for CreateImageVariationRequestSize { - fn to_string(&self) -> String { - match *self { - Self::_256x256 => "256x256".to_string(), - Self::_512x512 => "512x512".to_string(), - Self::_1024x1024 => "1024x1024".to_string(), - } - } - } - - impl std::str::FromStr for CreateImageVariationRequestSize { - type Err = &'static str; - fn from_str(value: &str) -> Result { - match value { - "256x256" => Ok(Self::_256x256), - "512x512" => Ok(Self::_512x512), - "1024x1024" => Ok(Self::_1024x1024), - _ => Err("invalid value"), - } - } - } - - impl std::convert::TryFrom<&str> for CreateImageVariationRequestSize { - type Error = &'static str; - fn try_from(value: &str) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom<&String> for CreateImageVariationRequestSize { - type Error = &'static str; - fn try_from(value: &String) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom for CreateImageVariationRequestSize { - type Error = &'static str; - fn try_from(value: String) -> Result { - value.parse() - } - } - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Error { pub code: Option, @@ -963,135 +646,6 @@ pub mod types { } } - #[derive(Clone, Debug)] - pub struct CreateImageEditRequest { - image: Result, - mask: Result, String>, - n: Result, String>, - prompt: Result, - response_format: Result, String>, - size: Result, String>, - user: Result, String>, - } - - impl Default for CreateImageEditRequest { - fn default() -> Self { - Self { - image: Err("no value supplied for image".to_string()), - mask: Ok(Default::default()), - n: Ok(super::defaults::create_image_edit_request_n()), - prompt: Err("no value supplied for prompt".to_string()), - response_format: Ok( - super::defaults::create_image_edit_request_response_format(), - ), - size: Ok(super::defaults::create_image_edit_request_size()), - user: Ok(Default::default()), - } - } - } - - impl CreateImageEditRequest { - pub fn image(mut self, value: T) -> Self - where - T: std::convert::TryInto, - T::Error: std::fmt::Display, - { - self.image = value - .try_into() - .map_err(|e| format!("error converting supplied value for image: {}", e)); - self - } - pub fn mask(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.mask = value - .try_into() - .map_err(|e| format!("error converting supplied value for mask: {}", e)); - self - } - pub fn n(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.n = value - .try_into() - .map_err(|e| format!("error converting supplied value for n: {}", e)); - self - } - pub fn prompt(mut self, value: T) -> Self - where - T: std::convert::TryInto, - T::Error: std::fmt::Display, - { - self.prompt = value - .try_into() - .map_err(|e| format!("error converting supplied value for prompt: {}", e)); - self - } - pub fn response_format(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.response_format = value.try_into().map_err(|e| { - format!("error converting supplied value for response_format: {}", e) - }); - self - } - pub fn size(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.size = value - .try_into() - .map_err(|e| format!("error converting supplied value for size: {}", e)); - self - } - pub fn user(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.user = value - .try_into() - .map_err(|e| format!("error converting supplied value for user: {}", e)); - self - } - } - - impl std::convert::TryFrom for super::CreateImageEditRequest { - type Error = String; - fn try_from(value: CreateImageEditRequest) -> Result { - Ok(Self { - image: value.image?, - mask: value.mask?, - n: value.n?, - prompt: value.prompt?, - response_format: value.response_format?, - size: value.size?, - user: value.user?, - }) - } - } - - impl From for CreateImageEditRequest { - fn from(value: super::CreateImageEditRequest) -> Self { - Self { - image: Ok(value.image), - mask: Ok(value.mask), - n: Ok(value.n), - prompt: Ok(value.prompt), - response_format: Ok(value.response_format), - size: Ok(value.size), - user: Ok(value.user), - } - } - } - #[derive(Clone, Debug)] pub struct CreateImageRequest { n: Result, String>, @@ -1278,108 +832,6 @@ pub mod types { } } - #[derive(Clone, Debug)] - pub struct CreateImageVariationRequest { - image: Result, - n: Result, String>, - response_format: - Result, String>, - size: Result, String>, - user: Result, String>, - } - - impl Default for CreateImageVariationRequest { - fn default() -> Self { - Self { - image: Err("no value supplied for image".to_string()), - n: Ok(super::defaults::create_image_variation_request_n()), - response_format: Ok( - super::defaults::create_image_variation_request_response_format(), - ), - size: Ok(super::defaults::create_image_variation_request_size()), - user: Ok(Default::default()), - } - } - } - - impl CreateImageVariationRequest { - pub fn image(mut self, value: T) -> Self - where - T: std::convert::TryInto, - T::Error: std::fmt::Display, - { - self.image = value - .try_into() - .map_err(|e| format!("error converting supplied value for image: {}", e)); - self - } - pub fn n(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.n = value - .try_into() - .map_err(|e| format!("error converting supplied value for n: {}", e)); - self - } - pub fn response_format(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.response_format = value.try_into().map_err(|e| { - format!("error converting supplied value for response_format: {}", e) - }); - self - } - pub fn size(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.size = value - .try_into() - .map_err(|e| format!("error converting supplied value for size: {}", e)); - self - } - pub fn user(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.user = value - .try_into() - .map_err(|e| format!("error converting supplied value for user: {}", e)); - self - } - } - - impl std::convert::TryFrom for super::CreateImageVariationRequest { - type Error = String; - fn try_from(value: CreateImageVariationRequest) -> Result { - Ok(Self { - image: value.image?, - n: value.n?, - response_format: value.response_format?, - size: value.size?, - user: value.user?, - }) - } - } - - impl From for CreateImageVariationRequest { - fn from(value: super::CreateImageVariationRequest) -> Self { - Self { - image: Ok(value.image), - n: Ok(value.n), - response_format: Ok(value.response_format), - size: Ok(value.size), - user: Ok(value.user), - } - } - } - #[derive(Clone, Debug)] pub struct Error { code: Result, String>, @@ -1637,20 +1089,6 @@ pub mod types { Some(super::CreateImageEditBodySize::_1024x1024) } - pub(super) fn create_image_edit_request_n() -> Option { - Some(1_i64) - } - - pub(super) fn create_image_edit_request_response_format( - ) -> Option { - Some(super::CreateImageEditRequestResponseFormat::Url) - } - - pub(super) fn create_image_edit_request_size() -> Option - { - Some(super::CreateImageEditRequestSize::_1024x1024) - } - pub(super) fn create_image_request_n() -> Option { Some(1_i64) } @@ -1677,20 +1115,6 @@ pub mod types { ) -> Option { Some(super::CreateImageVariationBodySize::_1024x1024) } - - pub(super) fn create_image_variation_request_n() -> Option { - Some(1_i64) - } - - pub(super) fn create_image_variation_request_response_format( - ) -> Option { - Some(super::CreateImageVariationRequestResponseFormat::Url) - } - - pub(super) fn create_image_variation_request_size( - ) -> Option { - Some(super::CreateImageVariationRequestSize::_1024x1024) - } } } diff --git a/progenitor-impl/tests/output/openai-openapi-builder.out b/progenitor-impl/tests/output/openai-openapi-builder.out index b0e49513..a7cc1334 100644 --- a/progenitor-impl/tests/output/openai-openapi-builder.out +++ b/progenitor-impl/tests/output/openai-openapi-builder.out @@ -165,172 +165,6 @@ pub mod types { } } - #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] - pub struct CreateImageEditRequest { - ///The image to edit. Must be a valid PNG file, less than 4MB, and - /// square. If mask is not provided, image must have transparency, which - /// will be used as the mask. - pub image: String, - ///An additional image whose fully transparent areas (e.g. where alpha - /// is zero) indicate where `image` should be edited. Must be a valid - /// PNG file, less than 4MB, and have the same dimensions as `image`. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub mask: Option, - ///The number of images to generate. Must be between 1 and 10. - #[serde(default = "defaults::create_image_edit_request_n")] - pub n: Option, - ///A text description of the desired image(s). The maximum length is - /// 1000 characters. - pub prompt: String, - ///The format in which the generated images are returned. Must be one - /// of `url` or `b64_json`. - #[serde(default = "defaults::create_image_edit_request_response_format")] - pub response_format: Option, - ///The size of the generated images. Must be one of `256x256`, - /// `512x512`, or `1024x1024`. - #[serde(default = "defaults::create_image_edit_request_size")] - pub size: Option, - ///A unique identifier representing your end-user, which can help - /// OpenAI to monitor and detect abuse. [Learn - /// more](/docs/guides/safety-best-practices/end-user-ids). - #[serde(default, skip_serializing_if = "Option::is_none")] - pub user: Option, - } - - impl From<&CreateImageEditRequest> for CreateImageEditRequest { - fn from(value: &CreateImageEditRequest) -> Self { - value.clone() - } - } - - impl CreateImageEditRequest { - pub fn builder() -> builder::CreateImageEditRequest { - builder::CreateImageEditRequest::default() - } - } - - ///The format in which the generated images are returned. Must be one of - /// `url` or `b64_json`. - #[derive( - Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize, - )] - pub enum CreateImageEditRequestResponseFormat { - #[serde(rename = "url")] - Url, - #[serde(rename = "b64_json")] - B64Json, - } - - impl From<&CreateImageEditRequestResponseFormat> for CreateImageEditRequestResponseFormat { - fn from(value: &CreateImageEditRequestResponseFormat) -> Self { - value.clone() - } - } - - impl ToString for CreateImageEditRequestResponseFormat { - fn to_string(&self) -> String { - match *self { - Self::Url => "url".to_string(), - Self::B64Json => "b64_json".to_string(), - } - } - } - - impl std::str::FromStr for CreateImageEditRequestResponseFormat { - type Err = &'static str; - fn from_str(value: &str) -> Result { - match value { - "url" => Ok(Self::Url), - "b64_json" => Ok(Self::B64Json), - _ => Err("invalid value"), - } - } - } - - impl std::convert::TryFrom<&str> for CreateImageEditRequestResponseFormat { - type Error = &'static str; - fn try_from(value: &str) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom<&String> for CreateImageEditRequestResponseFormat { - type Error = &'static str; - fn try_from(value: &String) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom for CreateImageEditRequestResponseFormat { - type Error = &'static str; - fn try_from(value: String) -> Result { - value.parse() - } - } - - ///The size of the generated images. Must be one of `256x256`, `512x512`, - /// or `1024x1024`. - #[derive( - Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize, - )] - pub enum CreateImageEditRequestSize { - #[serde(rename = "256x256")] - _256x256, - #[serde(rename = "512x512")] - _512x512, - #[serde(rename = "1024x1024")] - _1024x1024, - } - - impl From<&CreateImageEditRequestSize> for CreateImageEditRequestSize { - fn from(value: &CreateImageEditRequestSize) -> Self { - value.clone() - } - } - - impl ToString for CreateImageEditRequestSize { - fn to_string(&self) -> String { - match *self { - Self::_256x256 => "256x256".to_string(), - Self::_512x512 => "512x512".to_string(), - Self::_1024x1024 => "1024x1024".to_string(), - } - } - } - - impl std::str::FromStr for CreateImageEditRequestSize { - type Err = &'static str; - fn from_str(value: &str) -> Result { - match value { - "256x256" => Ok(Self::_256x256), - "512x512" => Ok(Self::_512x512), - "1024x1024" => Ok(Self::_1024x1024), - _ => Err("invalid value"), - } - } - } - - impl std::convert::TryFrom<&str> for CreateImageEditRequestSize { - type Error = &'static str; - fn try_from(value: &str) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom<&String> for CreateImageEditRequestSize { - type Error = &'static str; - fn try_from(value: &String) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom for CreateImageEditRequestSize { - type Error = &'static str; - fn try_from(value: String) -> Result { - value.parse() - } - } - #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] pub struct CreateImageRequest { ///The number of images to generate. Must be between 1 and 10. @@ -642,165 +476,6 @@ pub mod types { } } - #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] - pub struct CreateImageVariationRequest { - ///The image to use as the basis for the variation(s). Must be a valid - /// PNG file, less than 4MB, and square. - pub image: String, - ///The number of images to generate. Must be between 1 and 10. - #[serde(default = "defaults::create_image_variation_request_n")] - pub n: Option, - ///The format in which the generated images are returned. Must be one - /// of `url` or `b64_json`. - #[serde(default = "defaults::create_image_variation_request_response_format")] - pub response_format: Option, - ///The size of the generated images. Must be one of `256x256`, - /// `512x512`, or `1024x1024`. - #[serde(default = "defaults::create_image_variation_request_size")] - pub size: Option, - ///A unique identifier representing your end-user, which can help - /// OpenAI to monitor and detect abuse. [Learn - /// more](/docs/guides/safety-best-practices/end-user-ids). - #[serde(default, skip_serializing_if = "Option::is_none")] - pub user: Option, - } - - impl From<&CreateImageVariationRequest> for CreateImageVariationRequest { - fn from(value: &CreateImageVariationRequest) -> Self { - value.clone() - } - } - - impl CreateImageVariationRequest { - pub fn builder() -> builder::CreateImageVariationRequest { - builder::CreateImageVariationRequest::default() - } - } - - ///The format in which the generated images are returned. Must be one of - /// `url` or `b64_json`. - #[derive( - Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize, - )] - pub enum CreateImageVariationRequestResponseFormat { - #[serde(rename = "url")] - Url, - #[serde(rename = "b64_json")] - B64Json, - } - - impl From<&CreateImageVariationRequestResponseFormat> - for CreateImageVariationRequestResponseFormat - { - fn from(value: &CreateImageVariationRequestResponseFormat) -> Self { - value.clone() - } - } - - impl ToString for CreateImageVariationRequestResponseFormat { - fn to_string(&self) -> String { - match *self { - Self::Url => "url".to_string(), - Self::B64Json => "b64_json".to_string(), - } - } - } - - impl std::str::FromStr for CreateImageVariationRequestResponseFormat { - type Err = &'static str; - fn from_str(value: &str) -> Result { - match value { - "url" => Ok(Self::Url), - "b64_json" => Ok(Self::B64Json), - _ => Err("invalid value"), - } - } - } - - impl std::convert::TryFrom<&str> for CreateImageVariationRequestResponseFormat { - type Error = &'static str; - fn try_from(value: &str) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom<&String> for CreateImageVariationRequestResponseFormat { - type Error = &'static str; - fn try_from(value: &String) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom for CreateImageVariationRequestResponseFormat { - type Error = &'static str; - fn try_from(value: String) -> Result { - value.parse() - } - } - - ///The size of the generated images. Must be one of `256x256`, `512x512`, - /// or `1024x1024`. - #[derive( - Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize, - )] - pub enum CreateImageVariationRequestSize { - #[serde(rename = "256x256")] - _256x256, - #[serde(rename = "512x512")] - _512x512, - #[serde(rename = "1024x1024")] - _1024x1024, - } - - impl From<&CreateImageVariationRequestSize> for CreateImageVariationRequestSize { - fn from(value: &CreateImageVariationRequestSize) -> Self { - value.clone() - } - } - - impl ToString for CreateImageVariationRequestSize { - fn to_string(&self) -> String { - match *self { - Self::_256x256 => "256x256".to_string(), - Self::_512x512 => "512x512".to_string(), - Self::_1024x1024 => "1024x1024".to_string(), - } - } - } - - impl std::str::FromStr for CreateImageVariationRequestSize { - type Err = &'static str; - fn from_str(value: &str) -> Result { - match value { - "256x256" => Ok(Self::_256x256), - "512x512" => Ok(Self::_512x512), - "1024x1024" => Ok(Self::_1024x1024), - _ => Err("invalid value"), - } - } - } - - impl std::convert::TryFrom<&str> for CreateImageVariationRequestSize { - type Error = &'static str; - fn try_from(value: &str) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom<&String> for CreateImageVariationRequestSize { - type Error = &'static str; - fn try_from(value: &String) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom for CreateImageVariationRequestSize { - type Error = &'static str; - fn try_from(value: String) -> Result { - value.parse() - } - } - #[derive(Clone, Debug, Deserialize, JsonSchema, Serialize)] pub struct Error { pub code: Option, @@ -983,135 +658,6 @@ pub mod types { } } - #[derive(Clone, Debug)] - pub struct CreateImageEditRequest { - image: Result, - mask: Result, String>, - n: Result, String>, - prompt: Result, - response_format: Result, String>, - size: Result, String>, - user: Result, String>, - } - - impl Default for CreateImageEditRequest { - fn default() -> Self { - Self { - image: Err("no value supplied for image".to_string()), - mask: Ok(Default::default()), - n: Ok(super::defaults::create_image_edit_request_n()), - prompt: Err("no value supplied for prompt".to_string()), - response_format: Ok( - super::defaults::create_image_edit_request_response_format(), - ), - size: Ok(super::defaults::create_image_edit_request_size()), - user: Ok(Default::default()), - } - } - } - - impl CreateImageEditRequest { - pub fn image(mut self, value: T) -> Self - where - T: std::convert::TryInto, - T::Error: std::fmt::Display, - { - self.image = value - .try_into() - .map_err(|e| format!("error converting supplied value for image: {}", e)); - self - } - pub fn mask(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.mask = value - .try_into() - .map_err(|e| format!("error converting supplied value for mask: {}", e)); - self - } - pub fn n(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.n = value - .try_into() - .map_err(|e| format!("error converting supplied value for n: {}", e)); - self - } - pub fn prompt(mut self, value: T) -> Self - where - T: std::convert::TryInto, - T::Error: std::fmt::Display, - { - self.prompt = value - .try_into() - .map_err(|e| format!("error converting supplied value for prompt: {}", e)); - self - } - pub fn response_format(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.response_format = value.try_into().map_err(|e| { - format!("error converting supplied value for response_format: {}", e) - }); - self - } - pub fn size(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.size = value - .try_into() - .map_err(|e| format!("error converting supplied value for size: {}", e)); - self - } - pub fn user(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.user = value - .try_into() - .map_err(|e| format!("error converting supplied value for user: {}", e)); - self - } - } - - impl std::convert::TryFrom for super::CreateImageEditRequest { - type Error = String; - fn try_from(value: CreateImageEditRequest) -> Result { - Ok(Self { - image: value.image?, - mask: value.mask?, - n: value.n?, - prompt: value.prompt?, - response_format: value.response_format?, - size: value.size?, - user: value.user?, - }) - } - } - - impl From for CreateImageEditRequest { - fn from(value: super::CreateImageEditRequest) -> Self { - Self { - image: Ok(value.image), - mask: Ok(value.mask), - n: Ok(value.n), - prompt: Ok(value.prompt), - response_format: Ok(value.response_format), - size: Ok(value.size), - user: Ok(value.user), - } - } - } - #[derive(Clone, Debug)] pub struct CreateImageRequest { n: Result, String>, @@ -1298,108 +844,6 @@ pub mod types { } } - #[derive(Clone, Debug)] - pub struct CreateImageVariationRequest { - image: Result, - n: Result, String>, - response_format: - Result, String>, - size: Result, String>, - user: Result, String>, - } - - impl Default for CreateImageVariationRequest { - fn default() -> Self { - Self { - image: Err("no value supplied for image".to_string()), - n: Ok(super::defaults::create_image_variation_request_n()), - response_format: Ok( - super::defaults::create_image_variation_request_response_format(), - ), - size: Ok(super::defaults::create_image_variation_request_size()), - user: Ok(Default::default()), - } - } - } - - impl CreateImageVariationRequest { - pub fn image(mut self, value: T) -> Self - where - T: std::convert::TryInto, - T::Error: std::fmt::Display, - { - self.image = value - .try_into() - .map_err(|e| format!("error converting supplied value for image: {}", e)); - self - } - pub fn n(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.n = value - .try_into() - .map_err(|e| format!("error converting supplied value for n: {}", e)); - self - } - pub fn response_format(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.response_format = value.try_into().map_err(|e| { - format!("error converting supplied value for response_format: {}", e) - }); - self - } - pub fn size(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.size = value - .try_into() - .map_err(|e| format!("error converting supplied value for size: {}", e)); - self - } - pub fn user(mut self, value: T) -> Self - where - T: std::convert::TryInto>, - T::Error: std::fmt::Display, - { - self.user = value - .try_into() - .map_err(|e| format!("error converting supplied value for user: {}", e)); - self - } - } - - impl std::convert::TryFrom for super::CreateImageVariationRequest { - type Error = String; - fn try_from(value: CreateImageVariationRequest) -> Result { - Ok(Self { - image: value.image?, - n: value.n?, - response_format: value.response_format?, - size: value.size?, - user: value.user?, - }) - } - } - - impl From for CreateImageVariationRequest { - fn from(value: super::CreateImageVariationRequest) -> Self { - Self { - image: Ok(value.image), - n: Ok(value.n), - response_format: Ok(value.response_format), - size: Ok(value.size), - user: Ok(value.user), - } - } - } - #[derive(Clone, Debug)] pub struct Error { code: Result, String>, @@ -1657,20 +1101,6 @@ pub mod types { Some(super::CreateImageEditBodySize::_1024x1024) } - pub(super) fn create_image_edit_request_n() -> Option { - Some(1_i64) - } - - pub(super) fn create_image_edit_request_response_format( - ) -> Option { - Some(super::CreateImageEditRequestResponseFormat::Url) - } - - pub(super) fn create_image_edit_request_size() -> Option - { - Some(super::CreateImageEditRequestSize::_1024x1024) - } - pub(super) fn create_image_request_n() -> Option { Some(1_i64) } @@ -1697,20 +1127,6 @@ pub mod types { ) -> Option { Some(super::CreateImageVariationBodySize::_1024x1024) } - - pub(super) fn create_image_variation_request_n() -> Option { - Some(1_i64) - } - - pub(super) fn create_image_variation_request_response_format( - ) -> Option { - Some(super::CreateImageVariationRequestResponseFormat::Url) - } - - pub(super) fn create_image_variation_request_size( - ) -> Option { - Some(super::CreateImageVariationRequestSize::_1024x1024) - } } } diff --git a/progenitor-impl/tests/output/openai-openapi-positional.out b/progenitor-impl/tests/output/openai-openapi-positional.out index c2e7901c..03c29fbf 100644 --- a/progenitor-impl/tests/output/openai-openapi-positional.out +++ b/progenitor-impl/tests/output/openai-openapi-positional.out @@ -155,162 +155,6 @@ pub mod types { } } - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct CreateImageEditRequest { - ///The image to edit. Must be a valid PNG file, less than 4MB, and - /// square. If mask is not provided, image must have transparency, which - /// will be used as the mask. - pub image: String, - ///An additional image whose fully transparent areas (e.g. where alpha - /// is zero) indicate where `image` should be edited. Must be a valid - /// PNG file, less than 4MB, and have the same dimensions as `image`. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub mask: Option, - ///The number of images to generate. Must be between 1 and 10. - #[serde(default = "defaults::create_image_edit_request_n")] - pub n: Option, - ///A text description of the desired image(s). The maximum length is - /// 1000 characters. - pub prompt: String, - ///The format in which the generated images are returned. Must be one - /// of `url` or `b64_json`. - #[serde(default = "defaults::create_image_edit_request_response_format")] - pub response_format: Option, - ///The size of the generated images. Must be one of `256x256`, - /// `512x512`, or `1024x1024`. - #[serde(default = "defaults::create_image_edit_request_size")] - pub size: Option, - ///A unique identifier representing your end-user, which can help - /// OpenAI to monitor and detect abuse. [Learn - /// more](/docs/guides/safety-best-practices/end-user-ids). - #[serde(default, skip_serializing_if = "Option::is_none")] - pub user: Option, - } - - impl From<&CreateImageEditRequest> for CreateImageEditRequest { - fn from(value: &CreateImageEditRequest) -> Self { - value.clone() - } - } - - ///The format in which the generated images are returned. Must be one of - /// `url` or `b64_json`. - #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] - pub enum CreateImageEditRequestResponseFormat { - #[serde(rename = "url")] - Url, - #[serde(rename = "b64_json")] - B64Json, - } - - impl From<&CreateImageEditRequestResponseFormat> for CreateImageEditRequestResponseFormat { - fn from(value: &CreateImageEditRequestResponseFormat) -> Self { - value.clone() - } - } - - impl ToString for CreateImageEditRequestResponseFormat { - fn to_string(&self) -> String { - match *self { - Self::Url => "url".to_string(), - Self::B64Json => "b64_json".to_string(), - } - } - } - - impl std::str::FromStr for CreateImageEditRequestResponseFormat { - type Err = &'static str; - fn from_str(value: &str) -> Result { - match value { - "url" => Ok(Self::Url), - "b64_json" => Ok(Self::B64Json), - _ => Err("invalid value"), - } - } - } - - impl std::convert::TryFrom<&str> for CreateImageEditRequestResponseFormat { - type Error = &'static str; - fn try_from(value: &str) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom<&String> for CreateImageEditRequestResponseFormat { - type Error = &'static str; - fn try_from(value: &String) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom for CreateImageEditRequestResponseFormat { - type Error = &'static str; - fn try_from(value: String) -> Result { - value.parse() - } - } - - ///The size of the generated images. Must be one of `256x256`, `512x512`, - /// or `1024x1024`. - #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] - pub enum CreateImageEditRequestSize { - #[serde(rename = "256x256")] - _256x256, - #[serde(rename = "512x512")] - _512x512, - #[serde(rename = "1024x1024")] - _1024x1024, - } - - impl From<&CreateImageEditRequestSize> for CreateImageEditRequestSize { - fn from(value: &CreateImageEditRequestSize) -> Self { - value.clone() - } - } - - impl ToString for CreateImageEditRequestSize { - fn to_string(&self) -> String { - match *self { - Self::_256x256 => "256x256".to_string(), - Self::_512x512 => "512x512".to_string(), - Self::_1024x1024 => "1024x1024".to_string(), - } - } - } - - impl std::str::FromStr for CreateImageEditRequestSize { - type Err = &'static str; - fn from_str(value: &str) -> Result { - match value { - "256x256" => Ok(Self::_256x256), - "512x512" => Ok(Self::_512x512), - "1024x1024" => Ok(Self::_1024x1024), - _ => Err("invalid value"), - } - } - } - - impl std::convert::TryFrom<&str> for CreateImageEditRequestSize { - type Error = &'static str; - fn try_from(value: &str) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom<&String> for CreateImageEditRequestSize { - type Error = &'static str; - fn try_from(value: &String) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom for CreateImageEditRequestSize { - type Error = &'static str; - fn try_from(value: String) -> Result { - value.parse() - } - } - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct CreateImageRequest { ///The number of images to generate. Must be between 1 and 10. @@ -602,155 +446,6 @@ pub mod types { } } - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct CreateImageVariationRequest { - ///The image to use as the basis for the variation(s). Must be a valid - /// PNG file, less than 4MB, and square. - pub image: String, - ///The number of images to generate. Must be between 1 and 10. - #[serde(default = "defaults::create_image_variation_request_n")] - pub n: Option, - ///The format in which the generated images are returned. Must be one - /// of `url` or `b64_json`. - #[serde(default = "defaults::create_image_variation_request_response_format")] - pub response_format: Option, - ///The size of the generated images. Must be one of `256x256`, - /// `512x512`, or `1024x1024`. - #[serde(default = "defaults::create_image_variation_request_size")] - pub size: Option, - ///A unique identifier representing your end-user, which can help - /// OpenAI to monitor and detect abuse. [Learn - /// more](/docs/guides/safety-best-practices/end-user-ids). - #[serde(default, skip_serializing_if = "Option::is_none")] - pub user: Option, - } - - impl From<&CreateImageVariationRequest> for CreateImageVariationRequest { - fn from(value: &CreateImageVariationRequest) -> Self { - value.clone() - } - } - - ///The format in which the generated images are returned. Must be one of - /// `url` or `b64_json`. - #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] - pub enum CreateImageVariationRequestResponseFormat { - #[serde(rename = "url")] - Url, - #[serde(rename = "b64_json")] - B64Json, - } - - impl From<&CreateImageVariationRequestResponseFormat> - for CreateImageVariationRequestResponseFormat - { - fn from(value: &CreateImageVariationRequestResponseFormat) -> Self { - value.clone() - } - } - - impl ToString for CreateImageVariationRequestResponseFormat { - fn to_string(&self) -> String { - match *self { - Self::Url => "url".to_string(), - Self::B64Json => "b64_json".to_string(), - } - } - } - - impl std::str::FromStr for CreateImageVariationRequestResponseFormat { - type Err = &'static str; - fn from_str(value: &str) -> Result { - match value { - "url" => Ok(Self::Url), - "b64_json" => Ok(Self::B64Json), - _ => Err("invalid value"), - } - } - } - - impl std::convert::TryFrom<&str> for CreateImageVariationRequestResponseFormat { - type Error = &'static str; - fn try_from(value: &str) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom<&String> for CreateImageVariationRequestResponseFormat { - type Error = &'static str; - fn try_from(value: &String) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom for CreateImageVariationRequestResponseFormat { - type Error = &'static str; - fn try_from(value: String) -> Result { - value.parse() - } - } - - ///The size of the generated images. Must be one of `256x256`, `512x512`, - /// or `1024x1024`. - #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] - pub enum CreateImageVariationRequestSize { - #[serde(rename = "256x256")] - _256x256, - #[serde(rename = "512x512")] - _512x512, - #[serde(rename = "1024x1024")] - _1024x1024, - } - - impl From<&CreateImageVariationRequestSize> for CreateImageVariationRequestSize { - fn from(value: &CreateImageVariationRequestSize) -> Self { - value.clone() - } - } - - impl ToString for CreateImageVariationRequestSize { - fn to_string(&self) -> String { - match *self { - Self::_256x256 => "256x256".to_string(), - Self::_512x512 => "512x512".to_string(), - Self::_1024x1024 => "1024x1024".to_string(), - } - } - } - - impl std::str::FromStr for CreateImageVariationRequestSize { - type Err = &'static str; - fn from_str(value: &str) -> Result { - match value { - "256x256" => Ok(Self::_256x256), - "512x512" => Ok(Self::_512x512), - "1024x1024" => Ok(Self::_1024x1024), - _ => Err("invalid value"), - } - } - } - - impl std::convert::TryFrom<&str> for CreateImageVariationRequestSize { - type Error = &'static str; - fn try_from(value: &str) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom<&String> for CreateImageVariationRequestSize { - type Error = &'static str; - fn try_from(value: &String) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom for CreateImageVariationRequestSize { - type Error = &'static str; - fn try_from(value: String) -> Result { - value.parse() - } - } - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Error { pub code: Option, @@ -823,20 +518,6 @@ pub mod types { Some(super::CreateImageEditBodySize::_1024x1024) } - pub(super) fn create_image_edit_request_n() -> Option { - Some(1_i64) - } - - pub(super) fn create_image_edit_request_response_format( - ) -> Option { - Some(super::CreateImageEditRequestResponseFormat::Url) - } - - pub(super) fn create_image_edit_request_size() -> Option - { - Some(super::CreateImageEditRequestSize::_1024x1024) - } - pub(super) fn create_image_request_n() -> Option { Some(1_i64) } @@ -863,20 +544,6 @@ pub mod types { ) -> Option { Some(super::CreateImageVariationBodySize::_1024x1024) } - - pub(super) fn create_image_variation_request_n() -> Option { - Some(1_i64) - } - - pub(super) fn create_image_variation_request_response_format( - ) -> Option { - Some(super::CreateImageVariationRequestResponseFormat::Url) - } - - pub(super) fn create_image_variation_request_size( - ) -> Option { - Some(super::CreateImageVariationRequestSize::_1024x1024) - } } }