Skip to main content

gitlab/api/projects/packages/generic/
upload.rs

1// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4// option. This file may not be copied, modified, or distributed
5// except according to those terms.
6
7use crate::api::common::{self, NameOrId};
8use crate::api::endpoint_prelude::*;
9use crate::api::ParamValue;
10use derive_builder::Builder;
11
12/// The package status.
13///
14/// It can be default (default) or hidden. Hidden packages do not appear in the UI or package API
15/// list endpoints.
16#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum UploadPackageStatus {
19    /// All packages that appear in the UI
20    #[default]
21    Default,
22    /// All packages that don't appear in the UI
23    Hidden,
24}
25
26impl UploadPackageStatus {
27    /// The status as a query parameter
28    fn as_str(self) -> &'static str {
29        match self {
30            Self::Default => "default",
31            Self::Hidden => "hidden",
32        }
33    }
34}
35
36impl ParamValue<'static> for UploadPackageStatus {
37    fn as_value(&self) -> Cow<'static, str> {
38        self.as_str().into()
39    }
40}
41
42/// The response payload.
43///
44/// By default, the response is empty.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46#[non_exhaustive]
47pub enum UploadPackageSelect {
48    /// Returns details of the package file record created by this request
49    PackageFile,
50}
51
52impl UploadPackageSelect {
53    fn as_str(self) -> &'static str {
54        match self {
55            Self::PackageFile => "package_file",
56        }
57    }
58}
59
60impl ParamValue<'static> for UploadPackageSelect {
61    fn as_value(&self) -> Cow<'static, str> {
62        self.as_str().into()
63    }
64}
65
66/// Upload a package file of a single package.
67#[derive(Debug, Builder, Clone)]
68#[builder(setter(strip_option))]
69pub struct UploadPackageFile<'a> {
70    /// The project to query for the packages.
71    #[builder(setter(into))]
72    project: NameOrId<'a>,
73
74    /// The package name.
75    ///
76    /// It can contain only lowercase letters (a-z), uppercase letter (A-Z), numbers (0-9), dots
77    /// (.), hyphens (-), or underscores (_).
78    #[builder(setter(into))]
79    package_name: Cow<'a, str>,
80
81    /// The package version.
82    ///
83    /// The following regex validates this: `\A(\.?[\w\+-]+\.?)+\z`.
84    #[builder(setter(into))]
85    package_version: Cow<'a, str>,
86
87    /// The filename.
88    ///
89    /// It can contain only lowercase letters (a-z), uppercase letter (A-Z), numbers (0-9), dots
90    /// (.), hyphens (-), or underscores (_).
91    #[builder(setter(into))]
92    file_name: Cow<'a, str>,
93
94    /// The package status
95    ///
96    /// It can be default (default) or hidden. Hidden packages do not appear in the UI or package
97    /// API list endpoints.
98    #[builder(default)]
99    status: Option<UploadPackageStatus>,
100
101    /// The response payload.
102    ///
103    /// By default, the response is empty. Valid values are: package_file. package_file returns
104    /// details of the package file record created by this request.
105    #[builder(default)]
106    select: Option<UploadPackageSelect>,
107
108    /// The file as an array of bytes.
109    #[builder(setter(into))]
110    contents: Cow<'a, [u8]>,
111}
112
113impl<'a> UploadPackageFile<'a> {
114    /// Create a builder for the endpoint.
115    pub fn builder() -> UploadPackageFileBuilder<'a> {
116        UploadPackageFileBuilder::default()
117    }
118}
119
120impl Endpoint for UploadPackageFile<'_> {
121    fn method(&self) -> Method {
122        Method::PUT
123    }
124
125    fn endpoint(&self) -> Cow<'static, str> {
126        format!(
127            "projects/{}/packages/generic/{}/{}/{}",
128            self.project,
129            common::path_escaped(self.package_name.as_ref()),
130            common::path_escaped(self.package_version.as_ref()),
131            common::directory_path_escaped(self.file_name.as_ref()),
132        )
133        .into()
134    }
135
136    fn parameters(&self) -> QueryParams<'_> {
137        let mut params = QueryParams::default();
138
139        params
140            .push_opt("status", self.status)
141            .push_opt("select", self.select);
142
143        params
144    }
145
146    fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
147        Ok(Some(("application/octet-stream", self.contents.to_vec())))
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use http::Method;
154
155    use crate::{
156        api::{
157            self,
158            projects::packages::generic::upload::{
159                UploadPackageFile, UploadPackageFileBuilderError,
160            },
161            Query,
162        },
163        test::client::{ExpectedUrl, SingleTestClient},
164    };
165
166    use super::{UploadPackageSelect, UploadPackageStatus};
167
168    #[test]
169    fn project_is_needed() {
170        let err = UploadPackageFile::builder()
171            .package_name("test_package")
172            .package_version("1.2.3")
173            .file_name("test_file.zip")
174            .contents(&b"contents"[..])
175            .build()
176            .unwrap_err();
177        crate::test::assert_missing_field!(err, UploadPackageFileBuilderError, "project");
178    }
179
180    #[test]
181    fn package_name_is_needed() {
182        let err = UploadPackageFile::builder()
183            .project(1)
184            .package_version("1.2.3")
185            .file_name("test_file.zip")
186            .contents(&b"contents"[..])
187            .build()
188            .unwrap_err();
189        crate::test::assert_missing_field!(err, UploadPackageFileBuilderError, "package_name");
190    }
191
192    #[test]
193    fn package_version_is_needed() {
194        let err = UploadPackageFile::builder()
195            .project(1)
196            .package_name("test_package")
197            .file_name("test_file.zip")
198            .contents(&b"contents"[..])
199            .build()
200            .unwrap_err();
201        crate::test::assert_missing_field!(err, UploadPackageFileBuilderError, "package_version");
202    }
203
204    #[test]
205    fn file_name_is_needed() {
206        let err = UploadPackageFile::builder()
207            .project(1)
208            .package_name("test_package")
209            .package_version("1.2.3")
210            .contents(&b"contents"[..])
211            .build()
212            .unwrap_err();
213        crate::test::assert_missing_field!(err, UploadPackageFileBuilderError, "file_name");
214    }
215
216    #[test]
217    fn contents_is_needed() {
218        let err = UploadPackageFile::builder()
219            .project(1)
220            .package_name("test_package")
221            .package_version("1.2.3")
222            .file_name("test_file.zip")
223            .build()
224            .unwrap_err();
225        crate::test::assert_missing_field!(err, UploadPackageFileBuilderError, "contents");
226    }
227
228    #[test]
229    fn required_parameter_are_sufficient() {
230        UploadPackageFile::builder()
231            .project(1)
232            .package_name("test_package")
233            .package_version("1.2.3")
234            .file_name("test_file.zip")
235            .contents(&b"contents"[..])
236            .build()
237            .unwrap();
238    }
239
240    #[test]
241    fn endpoint() {
242        let contents = &b"contents"[..];
243        let endpoint = ExpectedUrl::builder()
244            .method(Method::PUT)
245            .endpoint("projects/1337/packages/generic/test%20package/1.2.3%201/test%20file.zip")
246            .body(contents.to_vec())
247            .content_type("application/octet-stream")
248            .build()
249            .unwrap();
250        let client = SingleTestClient::new_raw(endpoint, "");
251
252        let endpoint = UploadPackageFile::builder()
253            .project(1337)
254            .package_name("test package")
255            .package_version("1.2.3 1")
256            .file_name("test file.zip")
257            .contents(contents)
258            .build()
259            .unwrap();
260        api::ignore(endpoint).query(&client).unwrap();
261    }
262
263    #[test]
264    fn endpoint_pathed() {
265        let contents = &b"contents"[..];
266        let endpoint = ExpectedUrl::builder()
267            .method(Method::PUT)
268            .endpoint("projects/1337/packages/generic/test%20package/1.2.3%201/test/file.zip")
269            .body(contents.to_vec())
270            .content_type("application/octet-stream")
271            .build()
272            .unwrap();
273        let client = SingleTestClient::new_raw(endpoint, "");
274
275        let endpoint = UploadPackageFile::builder()
276            .project(1337)
277            .package_name("test package")
278            .package_version("1.2.3 1")
279            .file_name("test/file.zip")
280            .contents(contents)
281            .build()
282            .unwrap();
283        api::ignore(endpoint).query(&client).unwrap();
284    }
285
286    #[test]
287    fn endpoint_status() {
288        let contents = &b"contents"[..];
289        let endpoint = ExpectedUrl::builder()
290            .method(Method::PUT)
291            .endpoint("projects/1337/packages/generic/test%20package/1.2.3%201/test%20file.zip")
292            .add_query_params(&[("status", "hidden")])
293            .body(contents.to_vec())
294            .content_type("application/octet-stream")
295            .build()
296            .unwrap();
297        let client = SingleTestClient::new_raw(endpoint, "");
298
299        let endpoint = UploadPackageFile::builder()
300            .project(1337)
301            .package_name("test package")
302            .package_version("1.2.3 1")
303            .file_name("test file.zip")
304            .contents(contents)
305            .status(UploadPackageStatus::Hidden)
306            .build()
307            .unwrap();
308        api::ignore(endpoint).query(&client).unwrap();
309    }
310
311    #[test]
312    fn endpoint_select() {
313        let contents = &b"contents"[..];
314        let endpoint = ExpectedUrl::builder()
315            .method(Method::PUT)
316            .endpoint("projects/1337/packages/generic/test%20package/1.2.3%201/test%20file.zip")
317            .add_query_params(&[("select", "package_file")])
318            .body(contents.to_vec())
319            .content_type("application/octet-stream")
320            .build()
321            .unwrap();
322        let client = SingleTestClient::new_raw(endpoint, "");
323
324        let endpoint = UploadPackageFile::builder()
325            .project(1337)
326            .package_name("test package")
327            .package_version("1.2.3 1")
328            .file_name("test file.zip")
329            .contents(contents)
330            .select(UploadPackageSelect::PackageFile)
331            .build()
332            .unwrap();
333        api::ignore(endpoint).query(&client).unwrap();
334    }
335}