Skip to content

Commit

Permalink
Merge pull request #23 from blazzy/image-export
Browse files Browse the repository at this point in the history
Image exports as tarball streaming bytes
  • Loading branch information
blazzy authored Aug 21, 2024
2 parents 0bc2189 + 3896b29 commit 978be7b
Show file tree
Hide file tree
Showing 42 changed files with 8,933 additions and 8,309 deletions.
10 changes: 7 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ tokio = { version = "1.38.0", features = ["process"] }
tower-service = "0.3.2"
serde_path_to_error = "0.1.16"
url = "2.5.2"
bytes = "1.7.1"
futures = "0.3.30"

[dev-dependencies]
assert_matches = "1.5.0"
tokio = { version = "1.38.0", features = ["process", "macros"] }
tar = "0.4.41"
tokio = { version = "1.38.0", features = ["process", "macros", "fs"] }
tokio-test = "0.4.4"

[features]
Expand All @@ -53,6 +56,7 @@ ssh = ["dep:russh", "dep:russh-keys"]
[package.metadata.docs.rs]
all-features = true

[package.metadata.release]
pre-release-commit-message="v{{version}}"
[workspace.metadata.release]
allow-branch = ["master"]
pre-release-commit-message = "v{{version}}"
consolidate-commits = false
111 changes: 69 additions & 42 deletions openapi-client-gen/src/generate/rust_hyper_legacy/templates/apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ pub fn operations(spec: &Spec, tag: &Tag) -> Result<Vec<TokenStream>, Error> {
.map(|m| model_type(m, &spec.models))
.unwrap_or_else(|| Ok(quote! { () }))?;

let return_type = if Some(true) == success.map(|m| m.data.is_stream()) {
quote! { Pin<Box<dyn futures::stream::Stream<Item = Result<bytes::Bytes, Error>> + 'a>> }
} else {
quote! { Pin<Box<dyn Future<Output=Result<#response, Error>> + Send + 'a>> }
};

let summary = operation
.summary
.as_ref()
Expand Down Expand Up @@ -74,21 +80,11 @@ pub fn operations(spec: &Spec, tag: &Tag) -> Result<Vec<TokenStream>, Error> {
let body = serde_json::to_string(&#var_name)?;
req_builder = req_builder.header(hyper::header::CONTENT_TYPE, "application/json");
req_builder = req_builder.header(hyper::header::CONTENT_LENGTH, body.len());
let request = req_builder.body(body)?;
Ok(req_builder.body(body)?)
};
(body_param, create_body)
} else {
(TokenStream::new(), quote! { let request = req_builder.body(String::new())?;})
};

let execute_request = if let Some(success) = success {
if success.resolve_model(&spec.models)?.data.is_no_value() {
quote! { request::execute_request_unit(self.get_config(), request).await }
} else {
quote! {request::execute_request_json(self.get_config(), request).await }
}
} else {
quote! { request::execute_request_unit(self.get_config(), request).await }
(TokenStream::new(), quote! { Ok(req_builder.body(String::new())?) })
};

let set_path_params = if !operation.path_params.is_empty() {
Expand All @@ -111,14 +107,32 @@ pub fn operations(spec: &Spec, tag: &Tag) -> Result<Vec<TokenStream>, Error> {
let var_name = var_name(&param.name);

if param.required {
let to_string = parameter_to_str(&quote! { params.#var_name }, param);
quote! { query_pairs.append_pair(#name, #to_string); }
if let crate::parameter::Type::Array(_) = param.r#type {
quote! {
for value in params.#var_name {
query_pairs.append_pair(#name, &value.to_string());
}
}
} else {
let to_string = parameter_to_str(&quote! { params.#var_name }, param);
quote! { query_pairs.append_pair(#name, #to_string); }
}
} else {
let or_default = or_default(&param.x_client_default);
let to_string = parameter_to_str(&quote! { #var_name }, param);
quote! {
if let Some(#var_name) = params.#var_name #or_default {
query_pairs.append_pair(#name, #to_string);
if let crate::parameter::Type::Array(_) = param.r#type {
quote! {
if let Some(#var_name) = params.#var_name #or_default {
for value in #var_name {
query_pairs.append_pair(#name, &value.to_string());
}
}
}
} else {
quote! {
if let Some(#var_name) = params.#var_name #or_default {
query_pairs.append_pair(#name, #to_string);
}
}
}
}
Expand Down Expand Up @@ -167,9 +181,42 @@ pub fn operations(spec: &Spec, tag: &Tag) -> Result<Vec<TokenStream>, Error> {

let path = &operation.path;
let method = &operation.method.to_string();
let fn_lifetime = if operation.params_struct_has_str() {
Some(quote! { <'a> })
} else { None };

let build_request = quote! {
(|| {
let mut request_url = url::Url::parse(self.get_config().get_base_path())?;

let mut request_path = request_url.path().to_owned();
if request_path.ends_with('/') {
request_path.pop();
}
request_path.push_str(#path);
#set_path_params
request_url.set_path(&request_path);

let mut req_builder = self.get_config().req_builder(#method)?;

#process_params_struct

let hyper_uri: hyper::Uri = request_url.as_str().parse()?;
req_builder = req_builder.uri(hyper_uri);

#create_body
})()
};

let execute_request = if Some(true) == success.map(|m| m.data.is_stream()) {
quote! { request::execute_request_stream(self.get_config(), #build_request) }
} else if let Some(success) = success {
if success.resolve_model(&spec.models)?.data.is_no_value() {
quote! { Box::pin(request::execute_request_unit(self.get_config(), #build_request)) }
} else {
quote! { Box::pin(request::execute_request_json(self.get_config(), #build_request)) }
}
} else {
quote! { Box::pin(request::execute_request_unit(self.get_config(), #build_request)) }
};


Ok(quote! {
#[doc = #title]
Expand All @@ -179,28 +226,8 @@ pub fn operations(spec: &Spec, tag: &Tag) -> Result<Vec<TokenStream>, Error> {
&'a self,
#(#path_params)*
#params_struct #body_param
) -> Pin<Box<dyn Future<Output=Result<#response, Error>> + Send + 'a>> {
Box::pin(async move {
let mut request_url = url::Url::parse(self.get_config().get_base_path())?;

let mut request_path = request_url.path().to_owned();
if request_path.ends_with('/') {
request_path.pop();
}
request_path.push_str(#path);
#set_path_params
request_url.set_path(&request_path);

let mut req_builder = self.get_config().req_builder(#method)?;

#process_params_struct

let hyper_uri: hyper::Uri = request_url.as_str().parse()?;
req_builder = req_builder.uri(hyper_uri);

#create_body
#execute_request
})
) -> #return_type {
#execute_request
}
})
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,24 @@
use futures::stream;
use futures::stream::StreamExt;
use futures::Stream;
use http_body_util::BodyExt;
use serde_path_to_error::deserialize;
use std::pin::Pin;

use super::config::ClientConfig;
use super::error::Error;

pub async fn execute_request_bytes(
config: &dyn ClientConfig,
request: http::request::Request<String>,
) -> Result<hyper::body::Bytes, Error> {
let response = config.request(request).await?;
let status = response.status();
let bytes = response.into_body().collect().await?.to_bytes();
if status.is_success() {
Ok(bytes)
} else {
Err(Error::Api {
code: status,
body: bytes.into(),
})
}
}

pub async fn execute_request_unit(
config: &dyn ClientConfig,
request: http::request::Request<String>,
request: Result<http::request::Request<String>, Error>,
) -> Result<(), Error> {
execute_request_bytes(config, request).await?;
Ok(())
}

pub async fn execute_request_json<U>(
config: &dyn ClientConfig,
request: http::request::Request<String>,
request: Result<http::request::Request<String>, Error>,
) -> Result<U, Error>
where
for<'a> U: serde::Deserialize<'a>,
Expand All @@ -41,3 +28,51 @@ where

Ok(deserialize(deserializer)?)
}

pub async fn execute_request_bytes(
config: &dyn ClientConfig,
request: Result<http::request::Request<String>, Error>,
) -> Result<hyper::body::Bytes, Error> {
let response = config.request(request?).await?;
let status = response.status();
let bytes = response.into_body().collect().await?.to_bytes();
if status.is_success() {
Ok(bytes)
} else {
Err(Error::Api {
code: status,
body: bytes.into(),
})
}
}

pub fn execute_request_stream<'a>(
config: &'a dyn ClientConfig,
request: Result<http::request::Request<String>, Error>,
) -> Pin<Box<dyn Stream<Item = Result<bytes::Bytes, Error>> + 'a>> {
let result = async move {
let response = config.request(request?).await?;
let status = response.status();
let body = response.into_body();

if status.is_success() {
Ok(Box::pin(body.into_data_stream().map(
|result| match result {
Ok(bytes) => Ok(bytes),
Err(err) => Err(err.into()),
},
)))
} else {
let bytes = body.collect().await?.to_bytes();
Err(Error::Api {
code: status,
body: bytes.into(),
})
}
};
Box::pin(stream::once(result).flat_map(|result| match result {
Ok(stream) => stream,
Err(err) => Box::pin(stream::once(async { Err(err) }))
as Pin<Box<dyn Stream<Item = Result<_, Error>>>>,
}))
}
3 changes: 3 additions & 0 deletions openapi-client-gen/src/lang/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ pub fn model_type(model: &Model, models: &BTreeMap<String, Model>) -> Result<Tok
Err(Error::MissingModelRef(ref_str.into()))?
}
}
ModelData::Tarball => {
quote! { bytes::Bytes }
}
})
}

Expand Down
4 changes: 4 additions & 0 deletions openapi-client-gen/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@ pub enum ModelData {
HashMap(Box<Model>, bool),
ArbitraryValue,
NoValue,
Tarball,
Ref(String),
}

impl ModelData {
pub fn is_no_value(&self) -> bool {
matches!(&self, ModelData::NoValue)
}
pub fn is_stream(&self) -> bool {
matches!(&self, ModelData::Tarball)
}
}

#[derive(Clone)]
Expand Down
36 changes: 30 additions & 6 deletions openapi-client-gen/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use yaml_rust2::Yaml;
use yaml_rust2::YamlLoader;

use crate::error::Error;
use crate::model::Model;
use crate::model::{Model, ModelData};
use crate::operation::{Method, Operation};
use crate::parameter::BodyParameter;
use crate::parse;
Expand Down Expand Up @@ -136,6 +136,23 @@ impl Spec {
}
}

let mut produces = &Vec::<Yaml>::new();
produces = spec["produces"].as_vec().unwrap_or(produces);
if produces.is_empty() {
log::warn!("No response format for operation {}", operation_id);
}
if produces.len() > 1 {
log::warn!(
"Multiple response formats for operation {}. Choosing the first one",
operation_id
);
}

let produces = produces
.first()
.map(|y| y.as_str().unwrap())
.unwrap_or("text/plain");

if let Some(responses) = spec["responses"].as_hash() {
for (code, response) in responses {
let code: String = parse::string(code, "response code")?;
Expand All @@ -144,15 +161,22 @@ impl Spec {
} else {
response
};
operation.responses.insert(
code.clone(),
Model::new(
format!("{}_{}", operation_id, code),
let name = format!("{}_{}", operation_id, code);
let model = match produces {
"application/x-tar" => Model {
name,
title: None,
description: None,
data: ModelData::Tarball,
},
"application/json" | _ => Model::new(
name,
yaml,
&format!("#paths/{}/responses/{}", operation_id, code),
&mut self.models,
)?,
);
};
operation.responses.insert(code.clone(), model);
}
}

Expand Down
Loading

0 comments on commit 978be7b

Please sign in to comment.