encode.rs (10888B)
1 #[cfg(not(feature = "std"))] 2 use alloc::{format, string::ToString, vec, vec::Vec}; 3 4 use radroots_events::{ 5 calendar::{ 6 RadrootsCalendar, RadrootsCalendarDateEvent, RadrootsCalendarEventRsvp, 7 RadrootsCalendarTimeEvent, 8 }, 9 kinds::{ 10 KIND_CALENDAR, KIND_CALENDAR_DATE_EVENT, KIND_CALENDAR_EVENT_RSVP, KIND_CALENDAR_TIME_EVENT, 11 }, 12 social::{ 13 RadrootsCalendarEventFreeBusy, RadrootsCalendarEventRsvpStatus, RadrootsSocialTarget, 14 }, 15 tags::{ 16 TAG_A, TAG_D, TAG_D_DAY, TAG_E, TAG_END, TAG_END_TZID, TAG_FREE_BUSY, TAG_IMAGE, TAG_START, 17 TAG_START_TZID, TAG_STATUS, TAG_SUMMARY, TAG_TITLE, 18 }, 19 }; 20 21 use crate::d_tag::validate_d_tag; 22 use crate::error::EventEncodeError; 23 use crate::field_helpers::{ 24 parse_address_tag, push_optional_tag, push_tag, push_tag_values, validate_lowercase_hex_64, 25 validate_non_empty_field, 26 }; 27 use crate::social_helpers::{ 28 push_location_tags, push_participants, validate_date, validate_date_end_after_start, 29 validate_end_after_start, 30 }; 31 use crate::wire::WireEventParts; 32 33 pub fn calendar_date_event_build_tags( 34 event: &RadrootsCalendarDateEvent, 35 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 36 validate_date_event(event)?; 37 let mut tags = Vec::new(); 38 push_tag(&mut tags, TAG_D, event.d_tag.as_str()); 39 push_tag(&mut tags, TAG_TITLE, event.title.as_str()); 40 push_tag(&mut tags, TAG_START, event.start.as_str()); 41 push_optional_tag(&mut tags, TAG_END, event.end.as_deref()); 42 if let Some(days) = event.days.as_ref() { 43 for day in days { 44 validate_date(&day.value, "days")?; 45 push_tag(&mut tags, TAG_D_DAY, day.value.as_str()); 46 } 47 } 48 if let Some(location) = event.location.as_ref() { 49 push_location_tags(&mut tags, location); 50 } 51 push_optional_tag(&mut tags, TAG_SUMMARY, event.summary.as_deref()); 52 push_optional_tag(&mut tags, TAG_IMAGE, event.image.as_deref()); 53 push_participants(&mut tags, event.participants.as_ref()); 54 Ok(tags) 55 } 56 57 pub fn calendar_time_event_build_tags( 58 event: &RadrootsCalendarTimeEvent, 59 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 60 validate_time_event(event)?; 61 let mut tags = Vec::new(); 62 push_tag(&mut tags, TAG_D, event.d_tag.as_str()); 63 push_tag(&mut tags, TAG_TITLE, event.title.as_str()); 64 push_tag(&mut tags, TAG_START, event.start.to_string()); 65 for date in &event.dates { 66 validate_date(&date.value, "dates")?; 67 push_tag(&mut tags, TAG_D_DAY, date.value.as_str()); 68 } 69 if let Some(end) = event.end { 70 push_tag(&mut tags, TAG_END, end.to_string()); 71 } 72 push_optional_tag(&mut tags, TAG_START_TZID, event.start_tzid.as_deref()); 73 push_optional_tag(&mut tags, TAG_END_TZID, event.end_tzid.as_deref()); 74 if let Some(location) = event.location.as_ref() { 75 push_location_tags(&mut tags, location); 76 } 77 push_optional_tag(&mut tags, TAG_SUMMARY, event.summary.as_deref()); 78 push_optional_tag(&mut tags, TAG_IMAGE, event.image.as_deref()); 79 push_participants(&mut tags, event.participants.as_ref()); 80 Ok(tags) 81 } 82 83 pub fn calendar_collection_build_tags( 84 calendar: &RadrootsCalendar, 85 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 86 validate_calendar_collection(calendar)?; 87 let mut tags = Vec::new(); 88 push_tag(&mut tags, TAG_D, calendar.d_tag.as_str()); 89 push_tag(&mut tags, TAG_TITLE, calendar.title.as_str()); 90 push_optional_tag(&mut tags, TAG_SUMMARY, calendar.summary.as_deref()); 91 push_optional_tag(&mut tags, TAG_IMAGE, calendar.image.as_deref()); 92 for event in &calendar.events { 93 push_calendar_event_address(&mut tags, event, "events")?; 94 } 95 Ok(tags) 96 } 97 98 pub fn rsvp_build_tags( 99 rsvp: &RadrootsCalendarEventRsvp, 100 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 101 validate_rsvp(rsvp)?; 102 let mut tags = Vec::new(); 103 push_tag(&mut tags, TAG_D, rsvp.d_tag.as_str()); 104 push_calendar_event_address(&mut tags, &rsvp.event, "event")?; 105 if let Some(event_id) = rsvp.event_id.as_deref() { 106 let mut tag = vec![TAG_E.to_string(), event_id.to_string()]; 107 if let Some(relays) = calendar_event_relays(&rsvp.event) { 108 tag.extend( 109 relays 110 .iter() 111 .filter(|relay| !relay.trim().is_empty()) 112 .cloned(), 113 ); 114 } 115 tags.push(tag); 116 } 117 push_tag(&mut tags, TAG_STATUS, rsvp_status_as_str(&rsvp.status)); 118 if let Some(free_busy) = rsvp.free_busy.as_ref() { 119 push_tag(&mut tags, TAG_FREE_BUSY, free_busy_as_str(free_busy)); 120 } 121 push_participants(&mut tags, rsvp.participants.as_ref()); 122 Ok(tags) 123 } 124 125 pub fn date_to_wire_parts( 126 event: &RadrootsCalendarDateEvent, 127 ) -> Result<WireEventParts, EventEncodeError> { 128 date_to_wire_parts_with_kind(event, KIND_CALENDAR_DATE_EVENT) 129 } 130 131 pub fn time_to_wire_parts( 132 event: &RadrootsCalendarTimeEvent, 133 ) -> Result<WireEventParts, EventEncodeError> { 134 time_to_wire_parts_with_kind(event, KIND_CALENDAR_TIME_EVENT) 135 } 136 137 pub fn calendar_to_wire_parts( 138 calendar: &RadrootsCalendar, 139 ) -> Result<WireEventParts, EventEncodeError> { 140 calendar_to_wire_parts_with_kind(calendar, KIND_CALENDAR) 141 } 142 143 pub fn rsvp_to_wire_parts( 144 rsvp: &RadrootsCalendarEventRsvp, 145 ) -> Result<WireEventParts, EventEncodeError> { 146 rsvp_to_wire_parts_with_kind(rsvp, KIND_CALENDAR_EVENT_RSVP) 147 } 148 149 pub fn date_to_wire_parts_with_kind( 150 event: &RadrootsCalendarDateEvent, 151 kind: u32, 152 ) -> Result<WireEventParts, EventEncodeError> { 153 if kind != KIND_CALENDAR_DATE_EVENT { 154 return Err(EventEncodeError::InvalidKind(kind)); 155 } 156 Ok(WireEventParts { 157 kind, 158 content: event.description.clone().unwrap_or_default(), 159 tags: calendar_date_event_build_tags(event)?, 160 }) 161 } 162 163 pub fn time_to_wire_parts_with_kind( 164 event: &RadrootsCalendarTimeEvent, 165 kind: u32, 166 ) -> Result<WireEventParts, EventEncodeError> { 167 if kind != KIND_CALENDAR_TIME_EVENT { 168 return Err(EventEncodeError::InvalidKind(kind)); 169 } 170 Ok(WireEventParts { 171 kind, 172 content: event.description.clone().unwrap_or_default(), 173 tags: calendar_time_event_build_tags(event)?, 174 }) 175 } 176 177 pub fn calendar_to_wire_parts_with_kind( 178 calendar: &RadrootsCalendar, 179 kind: u32, 180 ) -> Result<WireEventParts, EventEncodeError> { 181 if kind != KIND_CALENDAR { 182 return Err(EventEncodeError::InvalidKind(kind)); 183 } 184 Ok(WireEventParts { 185 kind, 186 content: calendar.description.clone().unwrap_or_default(), 187 tags: calendar_collection_build_tags(calendar)?, 188 }) 189 } 190 191 pub fn rsvp_to_wire_parts_with_kind( 192 rsvp: &RadrootsCalendarEventRsvp, 193 kind: u32, 194 ) -> Result<WireEventParts, EventEncodeError> { 195 if kind != KIND_CALENDAR_EVENT_RSVP { 196 return Err(EventEncodeError::InvalidKind(kind)); 197 } 198 Ok(WireEventParts { 199 kind, 200 content: rsvp.note.clone().unwrap_or_default(), 201 tags: rsvp_build_tags(rsvp)?, 202 }) 203 } 204 205 fn validate_date_event(event: &RadrootsCalendarDateEvent) -> Result<(), EventEncodeError> { 206 validate_d_tag(&event.d_tag, "d_tag")?; 207 validate_non_empty_field(&event.title, "title")?; 208 validate_date(&event.start, "start")?; 209 if let Some(end) = event.end.as_deref() { 210 validate_date(end, "end")?; 211 } 212 validate_date_end_after_start(&event.start, event.end.as_deref(), "end")?; 213 Ok(()) 214 } 215 216 fn validate_time_event(event: &RadrootsCalendarTimeEvent) -> Result<(), EventEncodeError> { 217 validate_d_tag(&event.d_tag, "d_tag")?; 218 validate_non_empty_field(&event.title, "title")?; 219 validate_end_after_start(event.start, event.end, "end")?; 220 if event.dates.is_empty() { 221 return Err(EventEncodeError::EmptyRequiredField("dates")); 222 } 223 for date in &event.dates { 224 validate_date(&date.value, "dates")?; 225 } 226 Ok(()) 227 } 228 229 fn validate_calendar_collection(calendar: &RadrootsCalendar) -> Result<(), EventEncodeError> { 230 validate_d_tag(&calendar.d_tag, "d_tag")?; 231 validate_non_empty_field(&calendar.title, "title")?; 232 if calendar.events.is_empty() { 233 return Err(EventEncodeError::EmptyRequiredField("events")); 234 } 235 Ok(()) 236 } 237 238 fn validate_rsvp(rsvp: &RadrootsCalendarEventRsvp) -> Result<(), EventEncodeError> { 239 validate_d_tag(&rsvp.d_tag, "d_tag")?; 240 validate_calendar_event_address(&rsvp.event, "event")?; 241 if let Some(event_id) = rsvp.event_id.as_deref() { 242 validate_lowercase_hex_64(event_id, "event_id")?; 243 } 244 Ok(()) 245 } 246 247 fn push_calendar_event_address( 248 tags: &mut Vec<Vec<String>>, 249 target: &RadrootsSocialTarget, 250 field: &'static str, 251 ) -> Result<(), EventEncodeError> { 252 let RadrootsSocialTarget::Address { 253 address, 254 event_kind, 255 relays, 256 .. 257 } = target 258 else { 259 return Err(EventEncodeError::InvalidField(field)); 260 }; 261 let address = 262 parse_address_tag(address, field).map_err(|_| EventEncodeError::InvalidField(field))?; 263 if !is_calendar_event_kind(address.kind) { 264 return Err(EventEncodeError::InvalidField(field)); 265 } 266 if let Some(event_kind) = event_kind 267 && *event_kind != address.kind 268 { 269 return Err(EventEncodeError::InvalidField(field)); 270 } 271 let value = format!("{}:{}:{}", address.kind, address.pubkey, address.d_tag); 272 if let Some(relays) = relays.as_ref() { 273 let mut values = Vec::with_capacity(1 + relays.len()); 274 values.push(value); 275 values.extend( 276 relays 277 .iter() 278 .filter(|relay| !relay.trim().is_empty()) 279 .cloned(), 280 ); 281 push_tag_values(tags, TAG_A, values); 282 } else { 283 push_tag(tags, TAG_A, value); 284 } 285 Ok(()) 286 } 287 288 fn validate_calendar_event_address( 289 target: &RadrootsSocialTarget, 290 field: &'static str, 291 ) -> Result<(), EventEncodeError> { 292 let mut tags = Vec::new(); 293 push_calendar_event_address(&mut tags, target, field) 294 } 295 296 fn calendar_event_relays(target: &RadrootsSocialTarget) -> Option<&Vec<String>> { 297 match target { 298 RadrootsSocialTarget::Address { 299 relays: Some(relays), 300 .. 301 } => Some(relays), 302 _ => None, 303 } 304 } 305 306 fn is_calendar_event_kind(kind: u32) -> bool { 307 matches!(kind, KIND_CALENDAR_DATE_EVENT | KIND_CALENDAR_TIME_EVENT) 308 } 309 310 fn rsvp_status_as_str(status: &RadrootsCalendarEventRsvpStatus) -> &'static str { 311 match status { 312 RadrootsCalendarEventRsvpStatus::Accepted => "accepted", 313 RadrootsCalendarEventRsvpStatus::Declined => "declined", 314 RadrootsCalendarEventRsvpStatus::Tentative => "tentative", 315 } 316 } 317 318 fn free_busy_as_str(free_busy: &RadrootsCalendarEventFreeBusy) -> &'static str { 319 match free_busy { 320 RadrootsCalendarEventFreeBusy::Free => "free", 321 RadrootsCalendarEventFreeBusy::Busy => "busy", 322 } 323 }