use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use super::common::{CustomField, MetaData, NtRef};
use crate::{QBCreatable, QBDeletable, QBFullUpdatable, QBItem, QBToRef, QBTypeError};
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Default)]
#[serde(rename_all = "PascalCase", default)]
#[cfg_attr(
feature = "builder",
derive(Builder),
builder(default, build_fn(error = "QBTypeError"), setter(into, strip_option))
)]
pub struct Attachable {
pub id: Option<String>,
pub sync_token: Option<String>,
#[serde(skip_serializing)]
pub meta_data: Option<MetaData>,
#[cfg_attr(feature = "builder", builder(setter(custom)))]
pub file_name: Option<String>,
#[cfg_attr(feature = "builder", builder(setter(custom)))]
#[serde(skip)]
pub file_path: Option<PathBuf>,
pub note: Option<String>,
pub category: Option<AttachmentCategory>,
#[cfg_attr(feature = "builder", builder(setter(custom)))]
pub content_type: Option<String>,
pub place_name: Option<String>,
#[cfg_attr(feature = "builder", builder(setter(strip_option)))]
pub attachable_ref: Option<Vec<AttachableRef>>,
pub long: Option<String>,
pub tag: Option<String>,
pub lat: Option<String>,
pub file_access_uri: Option<String>,
pub size: Option<f64>,
pub thumbnail_file_access_uri: Option<String>,
pub temp_download_uri: Option<String>,
}
#[must_use]
pub fn content_type_from_ext(ext: &str) -> Option<&'static str> {
let out = match ext {
"ai" | "eps" => "application/postscript",
"csv" => "text/csv",
"doc" => "application/msword",
"docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"gif" => "image/gif",
"jpeg" => "image/jpeg",
"jpg" => "image/jpg",
"png" => "image/png",
"rtf" => "text/rtf",
"txt" => "text/plain",
"tif" => "image/tiff",
"ods" => "application/vnd.oasis.opendocument.spreadsheet",
"pdf" => "application/pdf",
"xls" => "application/vnd.ms-excel",
"xml" => "text/xml",
"xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
_ => return None,
};
Some(out)
}
#[cfg(feature = "builder")]
impl AttachableBuilder {
pub fn file(&mut self, path: &impl AsRef<Path>) -> Result<&mut Self, QBTypeError> {
let path = path.as_ref();
self.file_path = Some(Some(path.to_path_buf()));
self.file_name = Some(Some(
path.file_name()
.ok_or(QBTypeError::ValidationError(
"No file name on file path".into(),
))?
.to_str()
.ok_or(QBTypeError::ValidationError(
"Could not turn file name into string".into(),
))?
.to_owned(),
));
self.content_type = Some(
content_type_from_ext(
path.extension()
.ok_or(QBTypeError::ValidationError(
"No extension on file/dir".into(),
))?
.to_string_lossy()
.as_ref(),
)
.map(|f| f.to_owned()),
);
Ok(self)
}
}
pub trait QBAttachable {
fn can_upload(&self) -> Result<(), QBTypeError>;
fn file_path(&self) -> Option<&Path>;
}
impl QBAttachable for Attachable {
fn can_upload(&self) -> Result<(), QBTypeError> {
if self.note.is_none() && self.file_name.is_none() {
return Err(QBTypeError::MissingField("note"));
}
if self.file_name.is_none() {
return Err(QBTypeError::MissingField("file_name"));
}
Ok(())
}
fn file_path(&self) -> Option<&Path> {
self.file_path.as_deref()
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Default)]
pub enum AttachmentCategory {
ContactPhoto,
Document,
Image,
Receipt,
Signature,
Sound,
#[default]
Other,
}
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Default)]
#[serde(rename_all = "PascalCase", default)]
#[cfg_attr(feature = "builder", derive(Builder), builder(default))]
pub struct AttachableRef {
pub include_on_send: Option<bool>,
pub line_info: Option<String>,
pub no_ref_only: Option<bool>,
pub custom_field: Option<Vec<CustomField>>,
#[serde(rename = "type")]
pub ref_type: Option<String>,
pub inactive: Option<bool>,
pub entity_ref: Option<NtRef>,
}
impl From<NtRef> for AttachableRef {
fn from(value: NtRef) -> Self {
AttachableRef {
entity_ref: Some(value),
..Default::default()
}
}
}
pub trait QBToAttachableRef: QBToRef {
fn to_attach_ref(&self) -> Result<AttachableRef, QBTypeError> {
let value = self.to_ref()?;
Ok(value.into())
}
}
impl<T: QBToRef> QBToAttachableRef for T {}
impl QBCreatable for Attachable {
fn can_create(&self) -> bool {
(self.file_name.is_some() || self.note.is_some())
&& self.content_type.is_some()
&& self.file_path.is_some()
}
}
impl QBDeletable for Attachable {}
impl QBFullUpdatable for Attachable {
fn can_full_update(&self) -> bool {
self.has_read() && self.can_create()
}
}