use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum CustomFieldType {
#[default]
Text,
Number,
Enum,
MultiEnum,
Date,
People,
Percent,
Currency,
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct CustomFieldEnumOption {
pub gid: String,
pub name: String,
#[serde(default)]
pub color: Option<String>,
#[serde(default)]
pub enabled: Option<bool>,
#[serde(default)]
pub resource_type: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct CustomFieldDateValue {
#[serde(default)]
pub date: Option<String>,
#[serde(default)]
pub start_on: Option<String>,
#[serde(default)]
pub due_on: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub struct CustomField {
pub gid: String,
pub name: String,
#[serde(default)]
pub resource_type: Option<String>,
#[serde(rename = "type")]
pub field_type: CustomFieldType,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub enabled: Option<bool>,
#[serde(default)]
pub display_value: Option<String>,
#[serde(default)]
pub text_value: Option<String>,
#[serde(default)]
pub number_value: Option<f64>,
#[serde(default)]
pub percent_value: Option<f64>,
#[serde(default)]
pub currency_code: Option<String>,
#[serde(default)]
pub enum_value: Option<CustomFieldEnumOption>,
#[serde(default)]
pub multi_enum_values: Vec<CustomFieldEnumOption>,
#[serde(default)]
pub date_value: Option<CustomFieldDateValue>,
#[serde(default)]
pub people_value: Vec<String>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
#[derive(Debug, Clone)]
pub enum CustomFieldValue {
Text(String),
Number(f64),
Bool(bool),
EnumOption(String),
MultiEnum(Vec<String>),
Date(String),
DateRange {
start_on: Option<String>,
due_on: Option<String>,
},
Json(Value),
}
impl CustomFieldValue {
#[must_use]
pub fn into_value(self) -> Value {
match self {
Self::Text(value) => Value::String(value),
Self::Number(value) => {
serde_json::Number::from_f64(value).map_or(Value::Null, Value::Number)
}
Self::Bool(value) => Value::Bool(value),
Self::EnumOption(gid) => Value::String(gid),
Self::MultiEnum(gids) => Value::Array(gids.into_iter().map(Value::String).collect()),
Self::Date(date) => Value::String(date),
Self::DateRange { start_on, due_on } => {
let mut map = serde_json::Map::new();
if let Some(start_on) = start_on {
map.insert("start_on".into(), Value::String(start_on));
}
if let Some(due_on) = due_on {
map.insert("due_on".into(), Value::String(due_on));
}
Value::Object(map)
}
Self::Json(value) => value,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn converts_text_value() {
let value = CustomFieldValue::Text("hello".into()).into_value();
assert_eq!(value, Value::String("hello".into()));
}
#[test]
fn converts_multi_enum_value() {
let value = CustomFieldValue::MultiEnum(vec!["111".into(), "222".into()]).into_value();
assert_eq!(
value,
Value::Array(vec![
Value::String("111".into()),
Value::String("222".into())
])
);
}
#[test]
fn converts_date_range() {
let value = CustomFieldValue::DateRange {
start_on: Some("2024-01-01".into()),
due_on: None,
}
.into_value();
assert!(
value
.as_object()
.and_then(|map| map.get("start_on"))
.is_some()
);
}
}