google_cloud_storage/http/objects/
upload.rs1use std::borrow::Cow;
2
3use reqwest::header::{CONTENT_LENGTH, CONTENT_TYPE};
4use reqwest::multipart::{Form, Part};
5use reqwest_middleware::{ClientWithMiddleware as Client, RequestBuilder};
6
7use crate::http::object_access_controls::{PredefinedObjectAcl, Projection};
8use crate::http::objects::{Encryption, Object};
9use crate::http::{Error, Escape};
10
11#[derive(Clone, Debug)]
12pub struct Media {
13 pub name: Cow<'static, str>,
14 pub content_type: Cow<'static, str>,
15 pub content_length: Option<u64>,
16}
17
18impl Media {
19 pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
20 Self {
21 name: name.into(),
22 content_type: "application/octet-stream".into(),
23 content_length: None,
24 }
25 }
26}
27
28#[derive(Clone, Debug)]
29pub enum UploadType {
30 Simple(Media),
31 Multipart(Box<Object>),
32}
33
34#[derive(Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize, Debug, Default)]
35#[serde(rename_all = "camelCase")]
36pub struct UploadObjectRequest {
37 #[serde(skip_serializing)]
40 pub bucket: String,
41 pub generation: Option<i64>,
42 pub if_generation_match: Option<i64>,
46 pub if_generation_not_match: Option<i64>,
51 pub if_metageneration_match: Option<i64>,
54 pub if_metageneration_not_match: Option<i64>,
57 pub kms_key_name: Option<String>,
61 pub predefined_acl: Option<PredefinedObjectAcl>,
72 pub projection: Option<Projection>,
78 #[serde(skip_serializing)]
79 pub encryption: Option<Encryption>,
80}
81
82pub(crate) fn build<T: Into<reqwest::Body>>(
83 base_url: &str,
84 client: &Client,
85 req: &UploadObjectRequest,
86 media: &Media,
87 body: T,
88) -> RequestBuilder {
89 let url = format!("{}/b/{}/o?uploadType=media", base_url, req.bucket.escape(),);
90 let mut builder = client
91 .post(url)
92 .query(&req)
93 .query(&[("name", &media.name)])
94 .body(body)
95 .header(CONTENT_TYPE, media.content_type.to_string());
96
97 if let Some(len) = media.content_length {
98 builder = builder.header(CONTENT_LENGTH, len.to_string())
99 }
100 if let Some(e) = &req.encryption {
101 e.with_headers(builder)
102 } else {
103 builder
104 }
105}
106
107pub(crate) fn build_multipart<T: Into<reqwest::Body>>(
108 base_url: &str,
109 client: &Client,
110 req: &UploadObjectRequest,
111 metadata: &Object,
112 body: T,
113) -> Result<RequestBuilder, Error> {
114 let url = format!("{}/b/{}/o?uploadType=multipart", base_url, req.bucket.escape(),);
115 let form = Form::new();
116 let metadata_part = Part::text(serde_json::to_string(metadata).expect("object serialize failed"))
117 .mime_str("application/json; charset=UTF-8")?;
118 let data_part = Part::stream(body);
119 let form = form.part("metadata", metadata_part).part("data", data_part);
120
121 let builder = client.post(url).query(&req).multipart(form);
123
124 Ok(if let Some(e) = &req.encryption {
125 e.with_headers(builder)
126 } else {
127 builder
128 })
129}
130
131pub(crate) fn build_resumable_session_simple(
132 base_url: &str,
133 client: &Client,
134 req: &UploadObjectRequest,
135 media: &Media,
136) -> RequestBuilder {
137 let url = format!("{}/b/{}/o?uploadType=resumable", base_url, req.bucket.escape(),);
138 let mut builder = client
139 .post(url)
140 .query(&req)
141 .query(&[("name", &media.name)])
142 .header(CONTENT_LENGTH, 0)
143 .header("X-Upload-Content-Type", media.content_type.to_string());
144 if let Some(len) = media.content_length {
145 builder = builder.header("X-Upload-Content-Length", len)
146 }
147 if let Some(e) = &req.encryption {
148 e.with_headers(builder)
149 } else {
150 builder
151 }
152}
153
154pub(crate) fn build_resumable_session_metadata(
155 base_url: &str,
156 client: &Client,
157 req: &UploadObjectRequest,
158 metadata: &Object,
159) -> RequestBuilder {
160 let url = format!("{}/b/{}/o?uploadType=resumable", base_url, req.bucket.escape(),);
161 let builder = client.post(url).query(&req).json(&metadata);
162 if let Some(e) = &req.encryption {
163 e.with_headers(builder)
164 } else {
165 builder
166 }
167}