use crate::{
common::{Data, IanaString, IanaType},
icalendar::{
ICalendarDisplayType, ICalendarEntry, ICalendarFeatureType, ICalendarParameter,
ICalendarParameterName, ICalendarParameterValue, ICalendarParticipationRole,
ICalendarParticipationStatus, ICalendarProperty, ICalendarRelated, ICalendarUserTypes,
ICalendarValue, ICalendarValueType, Uri,
},
jscalendar::{
JSCalendarId, JSCalendarLinkDisplay, JSCalendarParticipantKind, JSCalendarParticipantRole,
JSCalendarParticipationStatus, JSCalendarProperty, JSCalendarRelativeTo, JSCalendarValue,
JSCalendarVirtualLocationFeature, import::ICalendarParams,
},
};
use ahash::AHashMap;
use jmap_tools::{Key, Map, Value};
pub(super) trait ExtractParams {
fn extract_params(
&mut self,
entry: &mut ICalendarEntry,
extract: &[ICalendarParameterName],
) -> Option<String>;
}
impl<I: JSCalendarId, B: JSCalendarId> ExtractParams
for AHashMap<
Key<'static, JSCalendarProperty<I>>,
Value<'static, JSCalendarProperty<I>, JSCalendarValue<I, B>>,
>
{
fn extract_params(
&mut self,
entry: &mut ICalendarEntry,
extract: &[ICalendarParameterName],
) -> Option<String> {
let mut jsid = None;
for param in std::mem::take(&mut entry.params) {
if !extract.contains(¶m.name) {
entry.params.push(param);
continue;
}
match param.name {
ICalendarParameterName::Cn => {
if let Some(text) = param.value.into_text() {
self.insert(Key::Property(JSCalendarProperty::Name), Value::Str(text));
}
}
ICalendarParameterName::Cutype => {
self.insert(
Key::Property(JSCalendarProperty::Kind),
match param.value {
ICalendarParameterValue::Text(value) => Value::Str(value.into()),
ICalendarParameterValue::Cutype(value) => match value {
ICalendarUserTypes::Individual => {
Value::Element(JSCalendarValue::ParticipantKind(
JSCalendarParticipantKind::Individual,
))
}
ICalendarUserTypes::Group => {
Value::Element(JSCalendarValue::ParticipantKind(
JSCalendarParticipantKind::Group,
))
}
ICalendarUserTypes::Resource => {
Value::Element(JSCalendarValue::ParticipantKind(
JSCalendarParticipantKind::Resource,
))
}
ICalendarUserTypes::Room => {
Value::Element(JSCalendarValue::ParticipantKind(
JSCalendarParticipantKind::Location,
))
}
ICalendarUserTypes::Unknown => {
Value::Str(value.as_str().to_lowercase().into())
}
},
_ => continue,
},
);
}
ICalendarParameterName::DelegatedFrom => {
if let Some(text) = param.value.into_text() {
self.insert(
Key::Property(JSCalendarProperty::DelegatedFrom),
Value::Str(text),
);
}
}
ICalendarParameterName::DelegatedTo => {
if let Some(text) = param.value.into_text() {
self.insert(
Key::Property(JSCalendarProperty::DelegatedTo),
Value::Str(text),
);
}
}
ICalendarParameterName::Email => {
if let Some(text) = param.value.into_text() {
self.insert(Key::Property(JSCalendarProperty::Email), Value::Str(text));
}
}
ICalendarParameterName::Rsvp => {
if let Some(boolean) = param.value.as_bool() {
self.insert(
Key::Property(JSCalendarProperty::ExpectReply),
Value::Bool(boolean),
);
}
}
ICalendarParameterName::Member => {
if let Some(text) = param.value.into_text() {
self.entry(Key::Property(JSCalendarProperty::MemberOf))
.or_insert_with(Value::new_object)
.as_object_mut()
.unwrap()
.insert(Key::from(text), Value::Bool(true));
}
}
ICalendarParameterName::Partstat => {
self.insert(
Key::Property(JSCalendarProperty::ParticipationStatus),
match param.value {
ICalendarParameterValue::Partstat(value) => {
Value::Element(JSCalendarValue::ParticipationStatus(match value {
ICalendarParticipationStatus::NeedsAction => {
JSCalendarParticipationStatus::NeedsAction
}
ICalendarParticipationStatus::Declined => {
JSCalendarParticipationStatus::Declined
}
ICalendarParticipationStatus::Tentative => {
JSCalendarParticipationStatus::Tentative
}
ICalendarParticipationStatus::Delegated => {
JSCalendarParticipationStatus::Delegated
}
ICalendarParticipationStatus::Completed
| ICalendarParticipationStatus::InProcess
| ICalendarParticipationStatus::Failed
| ICalendarParticipationStatus::Accepted => {
JSCalendarParticipationStatus::Accepted
}
}))
}
ICalendarParameterValue::Text(value) => Value::Str(value.into()),
_ => continue,
},
);
}
ICalendarParameterName::Role => {
let role = match param.value {
ICalendarParameterValue::Role(value) => {
Key::Property(JSCalendarProperty::ParticipantRole(match value {
ICalendarParticipationRole::Chair => {
JSCalendarParticipantRole::Chair
}
ICalendarParticipationRole::ReqParticipant => {
JSCalendarParticipantRole::Required
}
ICalendarParticipationRole::OptParticipant => {
JSCalendarParticipantRole::Optional
}
ICalendarParticipationRole::NonParticipant => {
JSCalendarParticipantRole::Informational
}
ICalendarParticipationRole::Owner => {
JSCalendarParticipantRole::Owner
}
}))
}
ICalendarParameterValue::Text(value) => Key::Owned(value),
_ => continue,
};
self.entry(Key::Property(JSCalendarProperty::Roles))
.or_insert_with(Value::new_object)
.as_object_mut()
.unwrap()
.insert(role, Value::Bool(true));
}
ICalendarParameterName::SentBy => {
if let Some(text) = param.value.into_text() {
self.insert(Key::Property(JSCalendarProperty::SentBy), Value::Str(text));
}
}
ICalendarParameterName::Fmttype => {
if let Some(text) = param.value.into_text() {
self.insert(
Key::Property(
if matches!(entry.name, ICalendarProperty::StyledDescription) {
JSCalendarProperty::DescriptionContentType
} else {
JSCalendarProperty::ContentType
},
),
Value::Str(text),
);
}
}
ICalendarParameterName::Label => {
if let Some(text) = param.value.into_text() {
self.insert(
Key::Property(if matches!(entry.name, ICalendarProperty::Conference) {
JSCalendarProperty::Name
} else {
JSCalendarProperty::Title
}),
Value::Str(text),
);
}
}
ICalendarParameterName::Size => {
if let Some(number) = param.value.as_integer() {
self.insert(
Key::Property(JSCalendarProperty::Size),
Value::Number(number.into()),
);
} else {
entry.params.push(ICalendarParameter::size(param.value));
}
}
ICalendarParameterName::Linkrel => match param.value {
ICalendarParameterValue::Linkrel(linkrel) => {
self.insert(
Key::Property(JSCalendarProperty::Rel),
Value::Element(JSCalendarValue::LinkRelation(linkrel)),
);
}
ICalendarParameterValue::Text(value) => {
self.insert(
Key::Property(JSCalendarProperty::Rel),
Value::Str(value.into()),
);
}
value => {
entry.params.push(ICalendarParameter::linkrel(value));
}
},
ICalendarParameterName::Related => {
if let ICalendarParameterValue::Related(related) = param.value {
self.insert(
Key::Property(JSCalendarProperty::RelativeTo),
Value::Element(JSCalendarValue::RelativeTo(match related {
ICalendarRelated::Start => JSCalendarRelativeTo::Start,
ICalendarRelated::End => JSCalendarRelativeTo::End,
})),
);
} else {
entry.params.push(ICalendarParameter::related(param.value));
}
}
ICalendarParameterName::Feature => {
let feature = match param.value {
ICalendarParameterValue::Feature(value) => {
Key::Property(JSCalendarProperty::VirtualLocationFeature(match value {
ICalendarFeatureType::Audio => {
JSCalendarVirtualLocationFeature::Audio
}
ICalendarFeatureType::Chat => {
JSCalendarVirtualLocationFeature::Chat
}
ICalendarFeatureType::Feed => {
JSCalendarVirtualLocationFeature::Feed
}
ICalendarFeatureType::Moderator => {
JSCalendarVirtualLocationFeature::Moderator
}
ICalendarFeatureType::Phone => {
JSCalendarVirtualLocationFeature::Phone
}
ICalendarFeatureType::Screen => {
JSCalendarVirtualLocationFeature::Screen
}
ICalendarFeatureType::Video => {
JSCalendarVirtualLocationFeature::Video
}
}))
}
ICalendarParameterValue::Text(value) => Key::Owned(value),
_ => continue,
};
self.entry(Key::Property(JSCalendarProperty::Features))
.or_insert_with(Value::new_object)
.as_object_mut()
.unwrap()
.insert(feature, Value::Bool(true));
}
ICalendarParameterName::Language => {
if let Some(text) = param.value.into_text() {
self.insert(Key::Property(JSCalendarProperty::Locale), Value::Str(text));
}
}
ICalendarParameterName::Display => {
let display = match param.value {
ICalendarParameterValue::Display(value) => {
Key::Property(JSCalendarProperty::LinkDisplay(match value {
ICalendarDisplayType::Badge => JSCalendarLinkDisplay::Badge,
ICalendarDisplayType::Graphic => JSCalendarLinkDisplay::Graphic,
ICalendarDisplayType::Fullsize => JSCalendarLinkDisplay::Fullsize,
ICalendarDisplayType::Thumbnail => JSCalendarLinkDisplay::Thumbnail,
}))
}
ICalendarParameterValue::Text(value) => Key::Owned(value),
value => {
entry.params.push(ICalendarParameter::display(value));
continue;
}
};
self.entry(Key::Property(JSCalendarProperty::Display))
.or_insert_with(Value::new_object)
.as_object_mut()
.unwrap()
.insert(display, Value::Bool(true));
}
ICalendarParameterName::Jsid => {
jsid = param.value.into_text().map(|v| v.into_owned());
}
ICalendarParameterName::Range
| ICalendarParameterName::Reltype
| ICalendarParameterName::Dir
| ICalendarParameterName::Fbtype
| ICalendarParameterName::ScheduleAgent
| ICalendarParameterName::ScheduleForceSend
| ICalendarParameterName::ScheduleStatus
| ICalendarParameterName::Tzid
| ICalendarParameterName::Value
| ICalendarParameterName::Filename
| ICalendarParameterName::ManagedId
| ICalendarParameterName::Order
| ICalendarParameterName::Schema
| ICalendarParameterName::Derived
| ICalendarParameterName::Gap
| ICalendarParameterName::Jsptr
| ICalendarParameterName::Other(_)
| ICalendarParameterName::Altrep => {}
}
}
jsid
}
}
impl<I: JSCalendarId, B: JSCalendarId> ICalendarParams<I, B> {
pub(super) fn into_jscalendar_value(
self,
) -> Option<Map<'static, JSCalendarProperty<I>, JSCalendarValue<I, B>>> {
if !self.0.is_empty() {
let mut obj = Map::from(Vec::with_capacity(self.0.len()));
for (param, value) in self.0 {
let value = if value.len() > 1 {
Value::Array(value)
} else {
value.into_iter().next().unwrap()
};
obj.insert_unchecked(Key::Owned(param.into_string().to_ascii_lowercase()), value);
}
Some(obj)
} else {
None
}
}
}
impl ICalendarValue {
pub(super) fn into_jscalendar_value<I: JSCalendarId, B: JSCalendarId>(
self,
value_type: Option<&IanaType<ICalendarValueType, String>>,
) -> Value<'static, JSCalendarProperty<I>, JSCalendarValue<I, B>> {
match self {
ICalendarValue::Text(v) => Value::Str(v.into()),
ICalendarValue::Integer(v) => Value::Number(v.into()),
ICalendarValue::Float(v) => Value::Number(v.into()),
ICalendarValue::Boolean(v) => Value::Bool(v),
ICalendarValue::PartialDateTime(v) => {
let mut out = String::new();
let _ = v.format_as_ical(
&mut out,
value_type.and_then(|v| v.iana()).unwrap_or(
match (v.has_date(), v.has_time(), v.has_zone()) {
(true, true, _) => &ICalendarValueType::DateTime,
(true, _, _) => &ICalendarValueType::Date,
(_, true, _) => &ICalendarValueType::Time,
(_, _, true) => &ICalendarValueType::UtcOffset,
_ => &ICalendarValueType::Text,
},
),
);
Value::Str(out.into())
}
ICalendarValue::Binary(v) => Value::Str(
Uri::Data(Data {
content_type: None,
data: v,
})
.into_unwrapped_string()
.into(),
),
ICalendarValue::Uri(v) => Value::Str(v.into_unwrapped_string().into()),
ICalendarValue::Duration(v) => Value::Str(v.to_string().into()),
ICalendarValue::RecurrenceRule(v) => Value::Str(v.to_string().into()),
ICalendarValue::Period(v) => Value::Str(v.to_string().into()),
ICalendarValue::CalendarScale(v) => Value::Str(v.as_str().into()),
ICalendarValue::Method(v) => Value::Str(v.as_str().into()),
ICalendarValue::Classification(v) => Value::Str(v.as_str().into()),
ICalendarValue::Status(v) => Value::Str(v.as_str().into()),
ICalendarValue::Transparency(v) => Value::Str(v.as_str().into()),
ICalendarValue::Action(v) => Value::Str(v.as_str().into()),
ICalendarValue::BusyType(v) => Value::Str(v.as_str().into()),
ICalendarValue::ParticipantType(v) => Value::Str(v.as_str().into()),
ICalendarValue::ResourceType(v) => Value::Str(v.as_str().into()),
ICalendarValue::Proximity(v) => Value::Str(v.as_str().into()),
}
}
}