use std::borrow::Cow;
use derive_builder::Builder;
use reqwest::Method;
use serde::Serialize;
use crate::api::users::UserEssentials;
use crate::api::{Endpoint, NoPagination, QueryParams, ReturnsJsonResponse};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
pub struct File {
pub id: u64,
pub filename: String,
pub filesize: u64,
pub content_type: String,
pub description: String,
pub token: String,
pub author: UserEssentials,
#[serde(
serialize_with = "crate::api::serialize_rfc3339",
deserialize_with = "crate::api::deserialize_rfc3339"
)]
pub created_on: time::OffsetDateTime,
pub digest: String,
pub downloads: u64,
}
#[derive(Debug, Clone, Builder)]
#[builder(setter(strip_option))]
pub struct ListProjectFiles<'a> {
#[builder(setter(into))]
project_id_or_name: Cow<'a, str>,
}
impl ReturnsJsonResponse for ListProjectFiles<'_> {}
impl NoPagination for ListProjectFiles<'_> {}
impl<'a> ListProjectFiles<'a> {
#[must_use]
pub fn builder() -> ListProjectFilesBuilder<'a> {
ListProjectFilesBuilder::default()
}
}
impl Endpoint for ListProjectFiles<'_> {
fn method(&self) -> Method {
Method::GET
}
fn endpoint(&self) -> Cow<'static, str> {
format!("projects/{}/files.json", self.project_id_or_name).into()
}
fn parameters(&self) -> QueryParams<'_> {
QueryParams::default()
}
}
#[derive(Debug, Clone, Builder, Serialize)]
#[builder(setter(strip_option))]
pub struct CreateFile<'a> {
#[builder(setter(into))]
#[serde(skip_serializing)]
project_id_or_name: Cow<'a, str>,
#[builder(setter(into))]
token: Cow<'a, str>,
#[builder(default)]
version_id: Option<u64>,
#[builder(setter(into), default)]
filename: Option<Cow<'a, str>>,
#[builder(setter(into), default)]
description: Option<Cow<'a, str>>,
}
impl<'a> CreateFile<'a> {
#[must_use]
pub fn builder() -> CreateFileBuilder<'a> {
CreateFileBuilder::default()
}
}
impl Endpoint for CreateFile<'_> {
fn method(&self) -> Method {
Method::POST
}
fn endpoint(&self) -> Cow<'static, str> {
format!("projects/{}/files.json", self.project_id_or_name).into()
}
fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, crate::Error> {
Ok(Some((
"application/json",
serde_json::to_vec(&FileWrapper::<CreateFile> {
file: (*self).to_owned(),
})?,
)))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
pub struct FileWrapper<T> {
pub file: T,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
pub struct FilesWrapper<T> {
pub files: Vec<T>,
}
#[cfg(test)]
mod test {
use super::*;
use crate::api::uploads::UploadFile;
use pretty_assertions::assert_eq;
use std::error::Error;
use tempfile;
use tracing_test::traced_test;
#[function_name::named]
#[traced_test]
#[test]
fn test_list_project_files_no_pagination() -> Result<(), Box<dyn Error>> {
let name = format!("unittest_{}", function_name!());
crate::api::test_helpers::with_project(&name, |redmine, _id, name| {
let endpoint = ListProjectFiles::builder()
.project_id_or_name(name)
.build()?;
redmine.json_response_body::<_, FilesWrapper<File>>(&endpoint)?;
Ok(())
})?;
Ok(())
}
#[function_name::named]
#[traced_test]
#[test]
fn test_create_file() -> Result<(), Box<dyn Error>> {
let name = format!("unittest_{}", function_name!());
crate::api::test_helpers::with_project(&name, |redmine, _id, name| {
let mut temp_file = tempfile::NamedTempFile::new()?;
use std::io::Write;
write!(temp_file, "test file content")?;
let upload_endpoint = UploadFile::builder()
.file(temp_file.path().to_path_buf())
.content_type("text/plain")
.build()?;
let upload: crate::api::uploads::UploadWrapper<crate::api::uploads::FileUploadToken> =
redmine.json_response_body(&upload_endpoint)?;
let endpoint = CreateFile::builder()
.project_id_or_name(name)
.token(upload.upload.token)
.build()?;
redmine.ignore_response_body(&endpoint)?;
Ok(())
})?;
Ok(())
}
#[function_name::named]
#[traced_test]
#[test]
fn test_completeness_file_type() -> Result<(), Box<dyn Error>> {
let name = format!("unittest_{}", function_name!());
crate::api::test_helpers::with_project(&name, |redmine, _id, name| {
let endpoint = ListProjectFiles::builder()
.project_id_or_name(name)
.build()?;
let raw_values =
redmine.json_response_body::<_, FilesWrapper<serde_json::Value>>(&endpoint)?;
for value in raw_values.files {
let o: File = serde_json::from_value(value.clone())?;
let reserialized = serde_json::to_value(o)?;
assert_eq!(value, reserialized);
}
Ok(())
})?;
Ok(())
}
}