lib.rs (48658B)
1 #![forbid(unsafe_code)] 2 3 use radroots_events::article::RadrootsArticle; 4 use radroots_events::calendar::{ 5 RadrootsCalendar, RadrootsCalendarDateEvent, RadrootsCalendarEventRsvp, 6 RadrootsCalendarTimeEvent, 7 }; 8 use radroots_events::comment::RadrootsComment; 9 use radroots_events::coop::RadrootsCoop; 10 use radroots_events::document::RadrootsDocument; 11 use radroots_events::farm::RadrootsFarm; 12 use radroots_events::farm_crdt::RadrootsFarmCrdtChange; 13 use radroots_events::farm_file::RadrootsFarmFileMetadata; 14 use radroots_events::farm_workspace::RadrootsFarmWorkspaceManifest; 15 use radroots_events::file_metadata::RadrootsFileMetadata; 16 use radroots_events::follow::RadrootsFollow; 17 use radroots_events::gift_wrap::RadrootsGiftWrap; 18 use radroots_events::group::{ 19 RadrootsGroupAdmins, RadrootsGroupCreateGroup, RadrootsGroupCreateInvite, 20 RadrootsGroupDeleteEvent, RadrootsGroupDeleteGroup, RadrootsGroupEditMetadata, 21 RadrootsGroupJoinRequest, RadrootsGroupLeaveRequest, RadrootsGroupMembers, 22 RadrootsGroupMetadata, RadrootsGroupPutUser, RadrootsGroupRemoveUser, RadrootsGroupRoles, 23 }; 24 use radroots_events::http_auth::RadrootsHttpAuth; 25 use radroots_events::job_feedback::RadrootsJobFeedback; 26 use radroots_events::job_request::RadrootsJobRequest; 27 use radroots_events::job_result::RadrootsJobResult; 28 use radroots_events::list::RadrootsList; 29 use radroots_events::list_set::RadrootsListSet; 30 use radroots_events::listing::RadrootsListing; 31 use radroots_events::message::RadrootsMessage; 32 use radroots_events::message_file::RadrootsMessageFile; 33 use radroots_events::plot::RadrootsPlot; 34 use radroots_events::post::RadrootsPost; 35 use radroots_events::reaction::RadrootsReaction; 36 use radroots_events::relay_auth::RadrootsRelayAuth; 37 use radroots_events::report::RadrootsReport; 38 use radroots_events::repost::{RadrootsGenericRepost, RadrootsRepost}; 39 use radroots_events::seal::RadrootsSeal; 40 use radroots_events_codec::article::encode::article_build_tags; 41 use radroots_events_codec::calendar::encode::{ 42 calendar_collection_build_tags, calendar_date_event_build_tags, calendar_time_event_build_tags, 43 rsvp_build_tags, 44 }; 45 use radroots_events_codec::comment::encode::comment_build_tags; 46 use radroots_events_codec::coop::encode::coop_build_tags; 47 use radroots_events_codec::document::encode::document_build_tags; 48 use radroots_events_codec::farm::encode::farm_build_tags; 49 use radroots_events_codec::farm_crdt::encode::farm_crdt_change_build_tags_with_author; 50 use radroots_events_codec::farm_file::encode::farm_file_metadata_build_tags; 51 use radroots_events_codec::farm_workspace::encode::farm_workspace_build_tags; 52 use radroots_events_codec::file_metadata::encode::file_metadata_build_tags; 53 use radroots_events_codec::follow::encode::follow_build_tags; 54 use radroots_events_codec::gift_wrap::encode::gift_wrap_build_tags; 55 use radroots_events_codec::group::encode::{ 56 group_admins_build_tags, group_create_group_build_tags, group_create_invite_build_tags, 57 group_delete_event_build_tags, group_delete_group_build_tags, group_edit_metadata_build_tags, 58 group_join_request_build_tags, group_leave_request_build_tags, group_members_build_tags, 59 group_metadata_build_tags, group_put_user_build_tags, group_remove_user_build_tags, 60 group_roles_build_tags, 61 }; 62 use radroots_events_codec::http_auth::encode::http_auth_build_tags; 63 use radroots_events_codec::job::feedback::encode::job_feedback_build_tags; 64 use radroots_events_codec::job::request::encode::job_request_build_tags; 65 use radroots_events_codec::job::result::encode::job_result_build_tags; 66 use radroots_events_codec::list::encode::list_build_tags; 67 use radroots_events_codec::list_set::encode::list_set_build_tags; 68 use radroots_events_codec::listing::tags::{ 69 listing_tags as listing_tags_impl, listing_tags_full as listing_tags_full_impl, 70 }; 71 use radroots_events_codec::message::encode::message_build_tags; 72 use radroots_events_codec::message_file::encode::message_file_build_tags; 73 use radroots_events_codec::plot::encode::plot_build_tags; 74 use radroots_events_codec::post::encode::post_build_tags; 75 use radroots_events_codec::reaction::encode::reaction_build_tags; 76 use radroots_events_codec::relay_auth::encode::relay_auth_build_tags; 77 use radroots_events_codec::report::encode::report_build_tags; 78 use radroots_events_codec::repost::encode::{generic_repost_build_tags, repost_build_tags}; 79 use radroots_events_codec::seal::encode::seal_build_tags; 80 use serde::de::DeserializeOwned; 81 #[cfg(target_arch = "wasm32")] 82 use wasm_bindgen::JsValue; 83 #[cfg(target_arch = "wasm32")] 84 use wasm_bindgen::prelude::*; 85 86 #[cfg(target_arch = "wasm32")] 87 type RadrootsJsValue = JsValue; 88 89 #[cfg(not(target_arch = "wasm32"))] 90 type RadrootsJsValue = String; 91 92 fn err_js<E: ToString>(err: E) -> RadrootsJsValue { 93 #[cfg(target_arch = "wasm32")] 94 { 95 JsValue::from_str(&err.to_string()) 96 } 97 #[cfg(not(target_arch = "wasm32"))] 98 { 99 err.to_string() 100 } 101 } 102 103 fn normalized_payload(input: &str) -> &str { 104 if input.is_empty() { "{}" } else { input } 105 } 106 107 fn parse_json<T: DeserializeOwned>(input: &str) -> Result<T, RadrootsJsValue> { 108 serde_json::from_str(normalized_payload(input)).map_err(err_js) 109 } 110 111 fn tags_to_json(tags: Vec<Vec<String>>) -> Result<String, RadrootsJsValue> { 112 serde_json::to_string(&tags).map_err(err_js) 113 } 114 115 fn build_tags_json<T, E, F>(input: &str, build: F) -> Result<String, RadrootsJsValue> 116 where 117 T: DeserializeOwned, 118 E: ToString, 119 F: FnOnce(&T) -> Result<Vec<Vec<String>>, E>, 120 { 121 let value = parse_json::<T>(input)?; 122 let tags = build(&value).map_err(err_js)?; 123 tags_to_json(tags) 124 } 125 126 fn build_tags_json_infallible<T, F>(input: &str, build: F) -> Result<String, RadrootsJsValue> 127 where 128 T: DeserializeOwned, 129 F: FnOnce(&T) -> Vec<Vec<String>>, 130 { 131 let value = parse_json::<T>(input)?; 132 let tags = build(&value); 133 tags_to_json(tags) 134 } 135 136 #[derive(serde::Deserialize)] 137 struct FarmCrdtTagsInput { 138 change: RadrootsFarmCrdtChange, 139 author_pubkey: String, 140 } 141 142 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = listing_tags))] 143 pub fn listing_tags(listing_json: &str) -> Result<String, RadrootsJsValue> { 144 build_tags_json::<RadrootsListing, _, _>(listing_json, listing_tags_impl) 145 } 146 147 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = listing_tags_full))] 148 pub fn listing_tags_full(listing_json: &str) -> Result<String, RadrootsJsValue> { 149 build_tags_json::<RadrootsListing, _, _>(listing_json, listing_tags_full_impl) 150 } 151 152 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = post_tags))] 153 pub fn post_tags(post_json: &str) -> Result<String, RadrootsJsValue> { 154 build_tags_json::<RadrootsPost, _, _>(post_json, post_build_tags) 155 } 156 157 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = comment_tags))] 158 pub fn comment_tags(comment_json: &str) -> Result<String, RadrootsJsValue> { 159 build_tags_json::<RadrootsComment, _, _>(comment_json, comment_build_tags) 160 } 161 162 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = article_tags))] 163 pub fn article_tags(article_json: &str) -> Result<String, RadrootsJsValue> { 164 build_tags_json::<RadrootsArticle, _, _>(article_json, article_build_tags) 165 } 166 167 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = file_metadata_tags))] 168 pub fn file_metadata_tags(metadata_json: &str) -> Result<String, RadrootsJsValue> { 169 build_tags_json::<RadrootsFileMetadata, _, _>(metadata_json, file_metadata_build_tags) 170 } 171 172 #[cfg_attr( 173 target_arch = "wasm32", 174 wasm_bindgen(js_name = calendar_date_event_tags) 175 )] 176 pub fn calendar_date_event_tags(event_json: &str) -> Result<String, RadrootsJsValue> { 177 build_tags_json::<RadrootsCalendarDateEvent, _, _>(event_json, calendar_date_event_build_tags) 178 } 179 180 #[cfg_attr( 181 target_arch = "wasm32", 182 wasm_bindgen(js_name = calendar_time_event_tags) 183 )] 184 pub fn calendar_time_event_tags(event_json: &str) -> Result<String, RadrootsJsValue> { 185 build_tags_json::<RadrootsCalendarTimeEvent, _, _>(event_json, calendar_time_event_build_tags) 186 } 187 188 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = calendar_tags))] 189 pub fn calendar_tags(calendar_json: &str) -> Result<String, RadrootsJsValue> { 190 build_tags_json::<RadrootsCalendar, _, _>(calendar_json, calendar_collection_build_tags) 191 } 192 193 #[cfg_attr( 194 target_arch = "wasm32", 195 wasm_bindgen(js_name = calendar_event_rsvp_tags) 196 )] 197 pub fn calendar_event_rsvp_tags(rsvp_json: &str) -> Result<String, RadrootsJsValue> { 198 build_tags_json::<RadrootsCalendarEventRsvp, _, _>(rsvp_json, rsvp_build_tags) 199 } 200 201 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = repost_tags))] 202 pub fn repost_tags(repost_json: &str) -> Result<String, RadrootsJsValue> { 203 build_tags_json::<RadrootsRepost, _, _>(repost_json, repost_build_tags) 204 } 205 206 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = generic_repost_tags))] 207 pub fn generic_repost_tags(repost_json: &str) -> Result<String, RadrootsJsValue> { 208 build_tags_json::<RadrootsGenericRepost, _, _>(repost_json, generic_repost_build_tags) 209 } 210 211 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = report_tags))] 212 pub fn report_tags(report_json: &str) -> Result<String, RadrootsJsValue> { 213 build_tags_json::<RadrootsReport, _, _>(report_json, report_build_tags) 214 } 215 216 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = follow_tags))] 217 pub fn follow_tags(follow_json: &str) -> Result<String, RadrootsJsValue> { 218 build_tags_json::<RadrootsFollow, _, _>(follow_json, follow_build_tags) 219 } 220 221 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = document_tags))] 222 pub fn document_tags(document_json: &str) -> Result<String, RadrootsJsValue> { 223 build_tags_json::<RadrootsDocument, _, _>(document_json, document_build_tags) 224 } 225 226 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = coop_tags))] 227 pub fn coop_tags(coop_json: &str) -> Result<String, RadrootsJsValue> { 228 build_tags_json::<RadrootsCoop, _, _>(coop_json, coop_build_tags) 229 } 230 231 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = farm_tags))] 232 pub fn farm_tags(farm_json: &str) -> Result<String, RadrootsJsValue> { 233 build_tags_json::<RadrootsFarm, _, _>(farm_json, farm_build_tags) 234 } 235 236 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = list_tags))] 237 pub fn list_tags(list_json: &str) -> Result<String, RadrootsJsValue> { 238 build_tags_json::<RadrootsList, _, _>(list_json, list_build_tags) 239 } 240 241 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = list_set_tags))] 242 pub fn list_set_tags(list_json: &str) -> Result<String, RadrootsJsValue> { 243 build_tags_json::<RadrootsListSet, _, _>(list_json, list_set_build_tags) 244 } 245 246 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = plot_tags))] 247 pub fn plot_tags(plot_json: &str) -> Result<String, RadrootsJsValue> { 248 build_tags_json::<RadrootsPlot, _, _>(plot_json, plot_build_tags) 249 } 250 251 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = job_request_tags))] 252 pub fn job_request_tags(job_json: &str) -> Result<String, RadrootsJsValue> { 253 build_tags_json_infallible::<RadrootsJobRequest, _>(job_json, job_request_build_tags) 254 } 255 256 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = job_result_tags))] 257 pub fn job_result_tags(job_json: &str) -> Result<String, RadrootsJsValue> { 258 build_tags_json_infallible::<RadrootsJobResult, _>(job_json, job_result_build_tags) 259 } 260 261 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = job_feedback_tags))] 262 pub fn job_feedback_tags(job_json: &str) -> Result<String, RadrootsJsValue> { 263 build_tags_json_infallible::<RadrootsJobFeedback, _>(job_json, job_feedback_build_tags) 264 } 265 266 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = reaction_tags))] 267 pub fn reaction_tags(reaction_json: &str) -> Result<String, RadrootsJsValue> { 268 build_tags_json::<RadrootsReaction, _, _>(reaction_json, reaction_build_tags) 269 } 270 271 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = message_tags))] 272 pub fn message_tags(message_json: &str) -> Result<String, RadrootsJsValue> { 273 build_tags_json::<RadrootsMessage, _, _>(message_json, message_build_tags) 274 } 275 276 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = message_file_tags))] 277 pub fn message_file_tags(message_json: &str) -> Result<String, RadrootsJsValue> { 278 build_tags_json::<RadrootsMessageFile, _, _>(message_json, message_file_build_tags) 279 } 280 281 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = seal_tags))] 282 pub fn seal_tags(seal_json: &str) -> Result<String, RadrootsJsValue> { 283 build_tags_json::<RadrootsSeal, _, _>(seal_json, seal_build_tags) 284 } 285 286 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = gift_wrap_tags))] 287 pub fn gift_wrap_tags(gift_wrap_json: &str) -> Result<String, RadrootsJsValue> { 288 build_tags_json::<RadrootsGiftWrap, _, _>(gift_wrap_json, gift_wrap_build_tags) 289 } 290 291 #[cfg_attr( 292 target_arch = "wasm32", 293 wasm_bindgen(js_name = farm_workspace_manifest_tags) 294 )] 295 pub fn farm_workspace_manifest_tags(workspace_json: &str) -> Result<String, RadrootsJsValue> { 296 build_tags_json::<RadrootsFarmWorkspaceManifest, _, _>( 297 workspace_json, 298 farm_workspace_build_tags, 299 ) 300 } 301 302 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = farm_crdt_change_tags))] 303 pub fn farm_crdt_change_tags(input_json: &str) -> Result<String, RadrootsJsValue> { 304 let input = parse_json::<FarmCrdtTagsInput>(input_json)?; 305 let tags = farm_crdt_change_build_tags_with_author(&input.change, Some(&input.author_pubkey)) 306 .map_err(err_js)?; 307 tags_to_json(tags) 308 } 309 310 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = farm_file_metadata_tags))] 311 pub fn farm_file_metadata_tags(file_json: &str) -> Result<String, RadrootsJsValue> { 312 build_tags_json::<RadrootsFarmFileMetadata, _, _>(file_json, farm_file_metadata_build_tags) 313 } 314 315 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = relay_auth_tags))] 316 pub fn relay_auth_tags(auth_json: &str) -> Result<String, RadrootsJsValue> { 317 build_tags_json::<RadrootsRelayAuth, _, _>(auth_json, relay_auth_build_tags) 318 } 319 320 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = http_auth_tags))] 321 pub fn http_auth_tags(auth_json: &str) -> Result<String, RadrootsJsValue> { 322 build_tags_json::<RadrootsHttpAuth, _, _>(auth_json, http_auth_build_tags) 323 } 324 325 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = group_put_user_tags))] 326 pub fn group_put_user_tags(group_json: &str) -> Result<String, RadrootsJsValue> { 327 build_tags_json::<RadrootsGroupPutUser, _, _>(group_json, group_put_user_build_tags) 328 } 329 330 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = group_remove_user_tags))] 331 pub fn group_remove_user_tags(group_json: &str) -> Result<String, RadrootsJsValue> { 332 build_tags_json::<RadrootsGroupRemoveUser, _, _>(group_json, group_remove_user_build_tags) 333 } 334 335 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = group_create_group_tags))] 336 pub fn group_create_group_tags(group_json: &str) -> Result<String, RadrootsJsValue> { 337 build_tags_json::<RadrootsGroupCreateGroup, _, _>(group_json, group_create_group_build_tags) 338 } 339 340 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = group_edit_metadata_tags))] 341 pub fn group_edit_metadata_tags(group_json: &str) -> Result<String, RadrootsJsValue> { 342 build_tags_json::<RadrootsGroupEditMetadata, _, _>(group_json, group_edit_metadata_build_tags) 343 } 344 345 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = group_delete_group_tags))] 346 pub fn group_delete_group_tags(group_json: &str) -> Result<String, RadrootsJsValue> { 347 build_tags_json::<RadrootsGroupDeleteGroup, _, _>(group_json, group_delete_group_build_tags) 348 } 349 350 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = group_delete_event_tags))] 351 pub fn group_delete_event_tags(group_json: &str) -> Result<String, RadrootsJsValue> { 352 build_tags_json::<RadrootsGroupDeleteEvent, _, _>(group_json, group_delete_event_build_tags) 353 } 354 355 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = group_create_invite_tags))] 356 pub fn group_create_invite_tags(group_json: &str) -> Result<String, RadrootsJsValue> { 357 build_tags_json::<RadrootsGroupCreateInvite, _, _>(group_json, group_create_invite_build_tags) 358 } 359 360 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = group_join_request_tags))] 361 pub fn group_join_request_tags(group_json: &str) -> Result<String, RadrootsJsValue> { 362 build_tags_json::<RadrootsGroupJoinRequest, _, _>(group_json, group_join_request_build_tags) 363 } 364 365 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = group_leave_request_tags))] 366 pub fn group_leave_request_tags(group_json: &str) -> Result<String, RadrootsJsValue> { 367 build_tags_json::<RadrootsGroupLeaveRequest, _, _>(group_json, group_leave_request_build_tags) 368 } 369 370 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = group_metadata_tags))] 371 pub fn group_metadata_tags(group_json: &str) -> Result<String, RadrootsJsValue> { 372 build_tags_json::<RadrootsGroupMetadata, _, _>(group_json, group_metadata_build_tags) 373 } 374 375 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = group_admins_tags))] 376 pub fn group_admins_tags(group_json: &str) -> Result<String, RadrootsJsValue> { 377 build_tags_json::<RadrootsGroupAdmins, _, _>(group_json, group_admins_build_tags) 378 } 379 380 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = group_members_tags))] 381 pub fn group_members_tags(group_json: &str) -> Result<String, RadrootsJsValue> { 382 build_tags_json::<RadrootsGroupMembers, _, _>(group_json, group_members_build_tags) 383 } 384 385 #[cfg_attr(target_arch = "wasm32", wasm_bindgen(js_name = group_roles_tags))] 386 pub fn group_roles_tags(group_json: &str) -> Result<String, RadrootsJsValue> { 387 build_tags_json::<RadrootsGroupRoles, _, _>(group_json, group_roles_build_tags) 388 } 389 390 #[cfg(test)] 391 mod tests { 392 use super::*; 393 use radroots_core::{ 394 RadrootsCoreCurrency, RadrootsCoreDecimal, RadrootsCoreMoney, RadrootsCoreQuantity, 395 RadrootsCoreQuantityPrice, RadrootsCoreUnit, 396 }; 397 use radroots_events::farm::RadrootsFarmRef; 398 use radroots_events::farm_crdt::{ 399 RADROOTS_FARM_CRDT_CHANGE_SCHEMA, RadrootsCrdtBackend, RadrootsFarmCrdtDocumentKind, 400 RadrootsFarmSemanticKind, 401 }; 402 use radroots_events::farm_file::{ 403 RadrootsFarmFileDimensions, RadrootsFarmFileMetadata, RadrootsFarmFileSource, 404 }; 405 use radroots_events::farm_workspace::{ 406 RADROOTS_FARM_WORKSPACE_PROTOCOL_VERSION, RADROOTS_FARM_WORKSPACE_SCHEMA, 407 RadrootsFarmWorkspaceManifest, RadrootsFarmWorkspaceMediaServer, RadrootsFarmWorkspaceRef, 408 RadrootsFarmWorkspaceRelay, RadrootsFarmWorkspaceRelayMode, 409 }; 410 use radroots_events::group::{ 411 RadrootsGroupAdmins, RadrootsGroupCreateGroup, RadrootsGroupCreateInvite, 412 RadrootsGroupDeleteEvent, RadrootsGroupDeleteGroup, RadrootsGroupEditMetadata, 413 RadrootsGroupEditableMetadata, RadrootsGroupJoinRequest, RadrootsGroupLeaveRequest, 414 RadrootsGroupMembers, RadrootsGroupMetadata, RadrootsGroupPutUser, RadrootsGroupRemoveUser, 415 RadrootsGroupRole, RadrootsGroupRoles, RadrootsGroupUserRef, 416 }; 417 use radroots_events::http_auth::RadrootsHttpAuth; 418 use radroots_events::job::JobInputType; 419 use radroots_events::job_request::{RadrootsJobInput, RadrootsJobParam}; 420 use radroots_events::kinds::KIND_FARM_FILE_METADATA; 421 use radroots_events::listing::{RadrootsListingBin, RadrootsListingProduct}; 422 use radroots_events::relay_auth::RadrootsRelayAuth; 423 use radroots_events::social::{ 424 RadrootsCalendarDateValue, RadrootsCalendarEventFreeBusy, RadrootsCalendarEventRsvpStatus, 425 RadrootsCalendarParticipant, RadrootsReportFileTarget, RadrootsReportType, 426 RadrootsSocialFarmAnchor, RadrootsSocialLocation, RadrootsSocialMediaDimensions, 427 RadrootsSocialMediaMetadata, RadrootsSocialTarget, 428 }; 429 430 fn sample_listing() -> RadrootsListing { 431 let quantity = 432 RadrootsCoreQuantity::new(RadrootsCoreDecimal::from(1u32), RadrootsCoreUnit::Each); 433 let price = RadrootsCoreQuantityPrice::new( 434 RadrootsCoreMoney::new(RadrootsCoreDecimal::from(10u32), RadrootsCoreCurrency::USD), 435 quantity.clone(), 436 ); 437 438 RadrootsListing { 439 d_tag: "AAAAAAAAAAAAAAAAAAAAAg".parse().expect("listing d tag"), 440 published_at: None, 441 farm: RadrootsFarmRef { 442 pubkey: "farm_pubkey".to_string(), 443 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 444 }, 445 product: RadrootsListingProduct { 446 key: "sku".to_string(), 447 title: "widget".to_string(), 448 category: "tools".to_string(), 449 summary: None, 450 process: None, 451 lot: None, 452 location: None, 453 profile: None, 454 year: None, 455 }, 456 primary_bin_id: "bin-1".parse().expect("primary bin id"), 457 bins: vec![RadrootsListingBin { 458 bin_id: "bin-1".parse().expect("bin id"), 459 quantity, 460 price_per_canonical_unit: price, 461 display_amount: None, 462 display_unit: None, 463 display_label: None, 464 display_price: None, 465 display_price_unit: None, 466 }], 467 resource_area: None, 468 plot: None, 469 discounts: None, 470 inventory_available: None, 471 availability: None, 472 delivery_method: None, 473 location: None, 474 images: None, 475 } 476 } 477 478 fn synthetic_pubkey(seed: char) -> String { 479 seed.to_string().repeat(64) 480 } 481 482 fn synthetic_event_id(seed: char) -> String { 483 seed.to_string().repeat(64) 484 } 485 486 fn social_farm_anchor() -> RadrootsSocialFarmAnchor { 487 RadrootsSocialFarmAnchor { 488 farm: RadrootsFarmRef { 489 pubkey: synthetic_pubkey('a'), 490 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 491 }, 492 relays: Some(vec!["wss://relay.example.test".to_string()]), 493 } 494 } 495 496 fn event_target(kind: u32, seed: char) -> RadrootsSocialTarget { 497 RadrootsSocialTarget::Event { 498 id: synthetic_event_id(seed), 499 author: Some(synthetic_pubkey('b')), 500 event_kind: Some(kind), 501 relays: Some(vec!["wss://relay.example.test".to_string()]), 502 } 503 } 504 505 fn address_target(kind: u32, d_tag: &str) -> RadrootsSocialTarget { 506 let author = synthetic_pubkey('c'); 507 RadrootsSocialTarget::Address { 508 address: format!("{kind}:{author}:{d_tag}"), 509 author: Some(author), 510 event_kind: Some(kind), 511 relays: Some(vec!["wss://relay2.example.test".to_string()]), 512 } 513 } 514 515 fn social_location() -> RadrootsSocialLocation { 516 RadrootsSocialLocation { 517 name: Some("field edge".to_string()), 518 geohash: Some("c23nb62w20st".to_string()), 519 } 520 } 521 522 fn sample_post() -> RadrootsPost { 523 RadrootsPost { 524 content: "field update".to_string(), 525 farm: Some(social_farm_anchor()), 526 address_refs: Some(vec![address_target(30023, "AAAAAAAAAAAAAAAAAAAAAQ")]), 527 location: Some(social_location()), 528 topics: Some(vec!["soil".to_string(), "market".to_string()]), 529 quote_refs: Some(vec![event_target(30023, 'd')]), 530 media: Some(vec![RadrootsSocialMediaMetadata { 531 url: Some("https://media.example.test/field.jpg".to_string()), 532 mime_type: Some("image/jpeg".to_string()), 533 sha256: Some( 534 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".to_string(), 535 ), 536 original_sha256: None, 537 size: Some(4096), 538 dimensions: Some(RadrootsSocialMediaDimensions { 539 width: 1200, 540 height: 800, 541 }), 542 blurhash: None, 543 thumbnails: None, 544 image: None, 545 summary: Some("field photo".to_string()), 546 alt: Some("rows after harvest".to_string()), 547 fallback: None, 548 magnet: Some("magnet:?xt=urn:btih:abc".to_string()), 549 content_hashes: Some(vec!["sha256:field".to_string()]), 550 services: Some(vec!["https://media.example.test".to_string()]), 551 imeta: None, 552 }]), 553 } 554 } 555 556 fn sample_article() -> RadrootsArticle { 557 RadrootsArticle { 558 d_tag: "AAAAAAAAAAAAAAAAAAAAAg".to_string(), 559 title: "soil notes".to_string(), 560 content: "# soil notes".to_string(), 561 summary: Some("cover crop observations".to_string()), 562 image: Some("https://media.example.test/article.jpg".to_string()), 563 published_at: Some(1_780_000_000), 564 farm: Some(social_farm_anchor()), 565 location: Some(social_location()), 566 topics: Some(vec!["soil".to_string(), "cover-crops".to_string()]), 567 } 568 } 569 570 fn sample_public_file_metadata() -> RadrootsFileMetadata { 571 RadrootsFileMetadata { 572 url: "https://media.example.test/public.jpg".to_string(), 573 mime_type: "image/jpeg".to_string(), 574 sha256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".to_string(), 575 original_sha256: Some( 576 "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789".to_string(), 577 ), 578 size: Some(4096), 579 dimensions: Some(RadrootsSocialMediaDimensions { 580 width: 1200, 581 height: 800, 582 }), 583 blurhash: None, 584 thumbnails: None, 585 summary: Some("public field photo".to_string()), 586 alt: Some("rows after harvest".to_string()), 587 fallback: Some("https://media.example.test/fallback.jpg".to_string()), 588 magnet: Some("magnet:?xt=urn:btih:abc".to_string()), 589 content_hashes: Some(vec!["sha256:field".to_string()]), 590 services: Some(vec!["https://media.example.test".to_string()]), 591 content: Some("caption".to_string()), 592 } 593 } 594 595 fn sample_calendar_date_event() -> RadrootsCalendarDateEvent { 596 RadrootsCalendarDateEvent { 597 d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(), 598 title: "market day".to_string(), 599 start: "2026-06-20".to_string(), 600 description: Some("Farm stand pickup window.".to_string()), 601 end: Some("2026-06-21".to_string()), 602 days: Some(vec![RadrootsCalendarDateValue { 603 value: "2026-06-20".to_string(), 604 }]), 605 location: Some(social_location()), 606 summary: Some("weekly pickup".to_string()), 607 image: None, 608 participants: Some(vec![RadrootsCalendarParticipant { 609 pubkey: synthetic_pubkey('e'), 610 relay: Some("wss://relay.example.test".to_string()), 611 role: Some("host".to_string()), 612 }]), 613 } 614 } 615 616 fn sample_calendar_time_event() -> RadrootsCalendarTimeEvent { 617 RadrootsCalendarTimeEvent { 618 d_tag: "AAAAAAAAAAAAAAAAAAAA-A".to_string(), 619 title: "wash pack shift".to_string(), 620 start: 1_781_895_600, 621 dates: vec![RadrootsCalendarDateValue { 622 value: "2026-06-20".to_string(), 623 }], 624 description: Some("Prepare CSA bins before pickup.".to_string()), 625 end: Some(1_781_899_200), 626 start_tzid: Some("America/Vancouver".to_string()), 627 end_tzid: Some("America/Vancouver".to_string()), 628 location: Some(social_location()), 629 summary: Some("field crew".to_string()), 630 image: None, 631 participants: None, 632 } 633 } 634 635 fn sample_calendar() -> RadrootsCalendar { 636 RadrootsCalendar { 637 d_tag: "AAAAAAAAAAAAAAAAAAAA_A".to_string(), 638 title: "farm calendar".to_string(), 639 events: vec![address_target(31923, "AAAAAAAAAAAAAAAAAAAA-A")], 640 description: Some("Shared schedule for farm operations.".to_string()), 641 summary: Some("field schedule".to_string()), 642 image: None, 643 } 644 } 645 646 fn sample_calendar_rsvp() -> RadrootsCalendarEventRsvp { 647 RadrootsCalendarEventRsvp { 648 d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(), 649 event: address_target(31923, "AAAAAAAAAAAAAAAAAAAA-A"), 650 event_id: Some(synthetic_event_id('f')), 651 status: RadrootsCalendarEventRsvpStatus::Tentative, 652 free_busy: Some(RadrootsCalendarEventFreeBusy::Busy), 653 note: Some("depends on harvest".to_string()), 654 participants: None, 655 } 656 } 657 658 fn sample_comment() -> RadrootsComment { 659 RadrootsComment { 660 root: event_target(30023, 'a'), 661 parent: address_target(30023, "AAAAAAAAAAAAAAAAAAAAAg"), 662 content: "great notes".to_string(), 663 } 664 } 665 666 fn sample_reaction() -> RadrootsReaction { 667 RadrootsReaction { 668 target: address_target(30023, "AAAAAAAAAAAAAAAAAAAAAg"), 669 content: String::new(), 670 } 671 } 672 673 fn sample_repost() -> RadrootsRepost { 674 RadrootsRepost { 675 target: event_target(1, 'b'), 676 content: Some("field update".to_string()), 677 } 678 } 679 680 fn sample_generic_repost() -> RadrootsGenericRepost { 681 RadrootsGenericRepost { 682 target: address_target(30023, "AAAAAAAAAAAAAAAAAAAAAg"), 683 target_kind: 30023, 684 content: Some("article share".to_string()), 685 } 686 } 687 688 fn sample_report() -> RadrootsReport { 689 RadrootsReport { 690 reported_pubkey: synthetic_pubkey('b'), 691 report_type: RadrootsReportType::Spam, 692 event: Some(event_target(1, 'c')), 693 file: Some(RadrootsReportFileTarget { 694 sha256: Some( 695 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".to_string(), 696 ), 697 url: Some("https://media.example.test/bad.jpg".to_string()), 698 magnet: None, 699 }), 700 content: Some("spam report".to_string()), 701 } 702 } 703 704 fn sample_job_request() -> RadrootsJobRequest { 705 RadrootsJobRequest { 706 kind: 5100, 707 inputs: vec![RadrootsJobInput { 708 data: "alpha".to_string(), 709 input_type: JobInputType::Text, 710 relay: None, 711 marker: None, 712 }], 713 output: None, 714 params: vec![RadrootsJobParam { 715 key: "mode".to_string(), 716 value: "fast".to_string(), 717 }], 718 bid_sat: Some(42), 719 relays: vec!["wss://relay.example.com".to_string()], 720 providers: vec!["provider-a".to_string()], 721 topics: vec!["topic-a".to_string()], 722 encrypted: false, 723 } 724 } 725 726 fn sample_workspace_manifest() -> RadrootsFarmWorkspaceManifest { 727 RadrootsFarmWorkspaceManifest { 728 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 729 schema: RADROOTS_FARM_WORKSPACE_SCHEMA.to_string(), 730 farm_group_id: "field-group".to_string(), 731 name: "Small Regen Farm".to_string(), 732 owner_pubkey: "workspace_owner_pubkey".to_string(), 733 farm: Some(RadrootsFarmRef { 734 pubkey: "farm_pubkey".to_string(), 735 d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(), 736 }), 737 relays: vec![RadrootsFarmWorkspaceRelay { 738 url: "wss://relay.example.invalid/farm/field-group".to_string(), 739 mode: RadrootsFarmWorkspaceRelayMode::ReadWrite, 740 }], 741 media_servers: vec![RadrootsFarmWorkspaceMediaServer { 742 url: "https://media.example.invalid/farm/field-group".to_string(), 743 service: "RadrootsPrivateMedia".to_string(), 744 }], 745 supported_kinds: vec![78, 30078, KIND_FARM_FILE_METADATA], 746 protocol_version: RADROOTS_FARM_WORKSPACE_PROTOCOL_VERSION.to_string(), 747 created_at_ms: 1_780_000_000_000, 748 updated_at_ms: None, 749 } 750 } 751 752 fn sample_crdt_change() -> RadrootsFarmCrdtChange { 753 RadrootsFarmCrdtChange { 754 schema: RADROOTS_FARM_CRDT_CHANGE_SCHEMA.to_string(), 755 workspace: RadrootsFarmWorkspaceRef { 756 pubkey: "workspace_pubkey".to_string(), 757 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 758 }, 759 farm_group_id: "field-group".to_string(), 760 document_id: "AAAAAAAAAAAAAAAAAAAAAg".to_string(), 761 document_kind: RadrootsFarmCrdtDocumentKind::FarmTask, 762 crdt_backend: RadrootsCrdtBackend::Automerge, 763 crdt_backend_version: Some("0.x".to_string()), 764 actor_id: "actor_abc".to_string(), 765 change_hash: "crdt_hash_abc".to_string(), 766 dependencies: Vec::new(), 767 encoded_change: "abc-DEF_012".to_string(), 768 semantic_kind: RadrootsFarmSemanticKind::FarmTaskCreate, 769 business_time_ms: 1_780_000_000_000, 770 author_member_id: Some("member_abc".to_string()), 771 app_version: Some("0.1.0".to_string()), 772 } 773 } 774 775 fn sample_file_metadata() -> RadrootsFarmFileMetadata { 776 RadrootsFarmFileMetadata { 777 d_tag: "AAAAAAAAAAAAAAAAAAAAAQ".to_string(), 778 workspace: RadrootsFarmWorkspaceRef { 779 pubkey: "workspace_pubkey".to_string(), 780 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 781 }, 782 farm_group_id: "field-group".to_string(), 783 owner_document_id: "AAAAAAAAAAAAAAAAAAAAAg".to_string(), 784 owner_document_kind: RadrootsFarmCrdtDocumentKind::FarmTask, 785 caption: Some("Tomatoes harvested from Patch Y.".to_string()), 786 url: "https://media.example.invalid/blob/sha256".to_string(), 787 mime_type: "image/jpeg".to_string(), 788 sha256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".to_string(), 789 original_sha256: None, 790 size_bytes: Some(123_456), 791 dimensions: Some(RadrootsFarmFileDimensions { w: 1600, h: 1200 }), 792 blurhash: None, 793 thumb: Some(RadrootsFarmFileSource { 794 url: "https://media.example.invalid/thumb/sha256".to_string(), 795 mime_type: Some("image/jpeg".to_string()), 796 dimensions: Some(RadrootsFarmFileDimensions { w: 320, h: 240 }), 797 }), 798 image: None, 799 alt: Some("Harvested tomatoes in a crate".to_string()), 800 fallbacks: Vec::new(), 801 } 802 } 803 804 fn sample_group_metadata() -> RadrootsGroupEditableMetadata { 805 RadrootsGroupEditableMetadata { 806 name: Some("Small Regen Farm".to_string()), 807 about: Some("Field app group".to_string()), 808 picture: Some("https://media.example.invalid/group.png".to_string()), 809 is_private: false, 810 is_restricted: true, 811 is_closed: false, 812 is_hidden: false, 813 supported_kinds: Some(vec![78, 30078, KIND_FARM_FILE_METADATA]), 814 } 815 } 816 817 fn sample_group_user(role: &str) -> RadrootsGroupUserRef { 818 RadrootsGroupUserRef { 819 pubkey: format!("{role}_pubkey"), 820 roles: vec![role.to_string()], 821 } 822 } 823 824 fn sample_group_role() -> RadrootsGroupRole { 825 RadrootsGroupRole { 826 name: "member".to_string(), 827 description: Some("can read and write group events".to_string()), 828 permissions: vec!["read".to_string(), "write".to_string()], 829 } 830 } 831 832 fn assert_tags_json(value: Result<String, RadrootsJsValue>) { 833 let tags = tags_json(value); 834 assert!(!tags.is_empty()); 835 } 836 837 fn tags_json(value: Result<String, RadrootsJsValue>) -> Vec<Vec<String>> { 838 let json = value.expect("tags json"); 839 serde_json::from_str(&json).expect("tags") 840 } 841 842 fn has_tag(tags: &[Vec<String>], key: &str, value: &str) -> bool { 843 tags.iter().any(|tag| { 844 tag.first().map(|entry| entry.as_str()) == Some(key) 845 && tag.get(1).map(|entry| entry.as_str()) == Some(value) 846 }) 847 } 848 849 #[test] 850 fn bindings_reject_invalid_json() { 851 let bindings: [fn(&str) -> Result<String, RadrootsJsValue>; 46] = [ 852 listing_tags, 853 listing_tags_full, 854 post_tags, 855 comment_tags, 856 article_tags, 857 file_metadata_tags, 858 calendar_date_event_tags, 859 calendar_time_event_tags, 860 calendar_tags, 861 calendar_event_rsvp_tags, 862 repost_tags, 863 generic_repost_tags, 864 report_tags, 865 follow_tags, 866 document_tags, 867 coop_tags, 868 farm_tags, 869 list_tags, 870 list_set_tags, 871 plot_tags, 872 job_request_tags, 873 job_result_tags, 874 job_feedback_tags, 875 reaction_tags, 876 message_tags, 877 message_file_tags, 878 seal_tags, 879 gift_wrap_tags, 880 farm_workspace_manifest_tags, 881 farm_crdt_change_tags, 882 farm_file_metadata_tags, 883 relay_auth_tags, 884 http_auth_tags, 885 group_put_user_tags, 886 group_remove_user_tags, 887 group_create_group_tags, 888 group_edit_metadata_tags, 889 group_delete_group_tags, 890 group_delete_event_tags, 891 group_create_invite_tags, 892 group_join_request_tags, 893 group_leave_request_tags, 894 group_metadata_tags, 895 group_admins_tags, 896 group_members_tags, 897 group_roles_tags, 898 ]; 899 900 for binding in bindings { 901 assert!(binding("{").is_err()); 902 } 903 assert!(listing_tags("").is_err()); 904 } 905 906 #[test] 907 fn bindings_encode_to_json_when_input_is_valid() { 908 let listing_json = serde_json::to_string(&sample_listing()).expect("listing json"); 909 let listing_tags_json = listing_tags(&listing_json).expect("listing tags"); 910 let listing_tags: Vec<Vec<String>> = 911 serde_json::from_str(&listing_tags_json).expect("listing tags json"); 912 assert!(!listing_tags.is_empty()); 913 914 let request_json = serde_json::to_string(&sample_job_request()).expect("request json"); 915 let request_tags_json = job_request_tags(&request_json).expect("request tags"); 916 let request_tags: Vec<Vec<String>> = 917 serde_json::from_str(&request_tags_json).expect("request tags json"); 918 assert!(!request_tags.is_empty()); 919 } 920 921 #[test] 922 fn social_bindings_encode_to_json_when_input_is_valid() { 923 assert_tags_json(post_tags( 924 &serde_json::to_string(&sample_post()).expect("post json"), 925 )); 926 assert_tags_json(comment_tags( 927 &serde_json::to_string(&sample_comment()).expect("comment json"), 928 )); 929 assert_tags_json(article_tags( 930 &serde_json::to_string(&sample_article()).expect("article json"), 931 )); 932 assert_tags_json(file_metadata_tags( 933 &serde_json::to_string(&sample_public_file_metadata()).expect("file json"), 934 )); 935 assert_tags_json(calendar_date_event_tags( 936 &serde_json::to_string(&sample_calendar_date_event()).expect("date json"), 937 )); 938 let time_tags = tags_json(calendar_time_event_tags( 939 &serde_json::to_string(&sample_calendar_time_event()).expect("time json"), 940 )); 941 assert!(has_tag(&time_tags, "D", "2026-06-20")); 942 assert_tags_json(calendar_tags( 943 &serde_json::to_string(&sample_calendar()).expect("calendar json"), 944 )); 945 assert_tags_json(calendar_event_rsvp_tags( 946 &serde_json::to_string(&sample_calendar_rsvp()).expect("rsvp json"), 947 )); 948 assert_tags_json(reaction_tags( 949 &serde_json::to_string(&sample_reaction()).expect("reaction json"), 950 )); 951 assert_tags_json(repost_tags( 952 &serde_json::to_string(&sample_repost()).expect("repost json"), 953 )); 954 assert_tags_json(generic_repost_tags( 955 &serde_json::to_string(&sample_generic_repost()).expect("generic repost json"), 956 )); 957 assert_tags_json(report_tags( 958 &serde_json::to_string(&sample_report()).expect("report json"), 959 )); 960 } 961 962 #[test] 963 fn social_bindings_surface_builder_errors() { 964 let mut article = sample_article(); 965 article.d_tag.clear(); 966 assert!(article_tags(&serde_json::to_string(&article).expect("article json")).is_err()); 967 968 let mut comment = sample_comment(); 969 comment.root = event_target(1, 'a'); 970 assert!(comment_tags(&serde_json::to_string(&comment).expect("comment json")).is_err()); 971 972 let mut reaction = sample_reaction(); 973 reaction.target = RadrootsSocialTarget::External { 974 id: "https://example.test/object".to_string(), 975 external_kind: "web".to_string(), 976 hint: None, 977 }; 978 assert!(reaction_tags(&serde_json::to_string(&reaction).expect("reaction json")).is_err()); 979 980 let mut rsvp = sample_calendar_rsvp(); 981 rsvp.event = event_target(31923, 'f'); 982 assert!( 983 calendar_event_rsvp_tags(&serde_json::to_string(&rsvp).expect("rsvp json")).is_err() 984 ); 985 986 let mut report = sample_report(); 987 report.reported_pubkey.clear(); 988 assert!(report_tags(&serde_json::to_string(&report).expect("report json")).is_err()); 989 } 990 991 #[test] 992 fn field_bindings_encode_to_json_when_input_is_valid() { 993 let workspace_json = 994 serde_json::to_string(&sample_workspace_manifest()).expect("workspace json"); 995 assert_tags_json(farm_workspace_manifest_tags(&workspace_json)); 996 997 let crdt_json = serde_json::json!({ 998 "change": sample_crdt_change(), 999 "author_pubkey": "author_pubkey" 1000 }) 1001 .to_string(); 1002 assert_tags_json(farm_crdt_change_tags(&crdt_json)); 1003 1004 let file_json = serde_json::to_string(&sample_file_metadata()).expect("file json"); 1005 assert_tags_json(farm_file_metadata_tags(&file_json)); 1006 1007 let relay_auth_json = serde_json::to_string(&RadrootsRelayAuth { 1008 relay: "wss://relay.example.invalid/farm/field-group".to_string(), 1009 challenge: "relay-provided-challenge".to_string(), 1010 }) 1011 .expect("relay auth json"); 1012 assert_tags_json(relay_auth_tags(&relay_auth_json)); 1013 1014 let http_auth_json = serde_json::to_string(&RadrootsHttpAuth { 1015 url: "https://media.example.invalid/upload".to_string(), 1016 method: "POST".to_string(), 1017 payload_sha256: Some( 1018 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".to_string(), 1019 ), 1020 }) 1021 .expect("http auth json"); 1022 assert_tags_json(http_auth_tags(&http_auth_json)); 1023 } 1024 1025 #[test] 1026 fn field_bindings_surface_builder_errors() { 1027 let crdt_json = serde_json::json!({ 1028 "change": sample_crdt_change(), 1029 "author_pubkey": " " 1030 }) 1031 .to_string(); 1032 1033 assert!(farm_crdt_change_tags(&crdt_json).is_err()); 1034 } 1035 1036 #[test] 1037 fn group_bindings_encode_to_json_when_input_is_valid() { 1038 let metadata = sample_group_metadata(); 1039 assert_tags_json(group_put_user_tags( 1040 &serde_json::to_string(&RadrootsGroupPutUser { 1041 group_id: "field-group".to_string(), 1042 message: Some("add member".to_string()), 1043 pubkey: "member_pubkey".to_string(), 1044 roles: vec!["member".to_string()], 1045 }) 1046 .expect("put user json"), 1047 )); 1048 assert_tags_json(group_remove_user_tags( 1049 &serde_json::to_string(&RadrootsGroupRemoveUser { 1050 group_id: "field-group".to_string(), 1051 message: Some("remove member".to_string()), 1052 pubkey: "member_pubkey".to_string(), 1053 }) 1054 .expect("remove user json"), 1055 )); 1056 assert_tags_json(group_create_group_tags( 1057 &serde_json::to_string(&RadrootsGroupCreateGroup { 1058 group_id: "field-group".to_string(), 1059 message: Some("create group".to_string()), 1060 metadata: metadata.clone(), 1061 }) 1062 .expect("create group json"), 1063 )); 1064 assert_tags_json(group_edit_metadata_tags( 1065 &serde_json::to_string(&RadrootsGroupEditMetadata { 1066 group_id: "field-group".to_string(), 1067 message: Some("edit metadata".to_string()), 1068 metadata: metadata.clone(), 1069 }) 1070 .expect("edit metadata json"), 1071 )); 1072 assert_tags_json(group_delete_group_tags( 1073 &serde_json::to_string(&RadrootsGroupDeleteGroup { 1074 group_id: "field-group".to_string(), 1075 message: Some("delete group".to_string()), 1076 }) 1077 .expect("delete group json"), 1078 )); 1079 assert_tags_json(group_delete_event_tags( 1080 &serde_json::to_string(&RadrootsGroupDeleteEvent { 1081 group_id: "field-group".to_string(), 1082 message: Some("delete event".to_string()), 1083 event_id: "event_id".to_string(), 1084 }) 1085 .expect("delete event json"), 1086 )); 1087 let invite_tags = tags_json(group_create_invite_tags( 1088 &serde_json::to_string(&RadrootsGroupCreateInvite { 1089 group_id: "field-group".to_string(), 1090 message: Some("join the field group".to_string()), 1091 code: "invite-code".to_string(), 1092 }) 1093 .expect("invite json"), 1094 )); 1095 assert!(invite_tags.contains(&vec!["code".to_string(), "invite-code".to_string()])); 1096 assert_tags_json(group_join_request_tags( 1097 &serde_json::to_string(&RadrootsGroupJoinRequest { 1098 group_id: "field-group".to_string(), 1099 message: Some("requesting access".to_string()), 1100 code: Some("invite-code".to_string()), 1101 }) 1102 .expect("join json"), 1103 )); 1104 assert_tags_json(group_leave_request_tags( 1105 &serde_json::to_string(&RadrootsGroupLeaveRequest { 1106 group_id: "field-group".to_string(), 1107 message: Some("leaving".to_string()), 1108 }) 1109 .expect("leave json"), 1110 )); 1111 let metadata_tags = tags_json(group_metadata_tags( 1112 &serde_json::to_string(&RadrootsGroupMetadata { 1113 d_tag: "field-group".to_string(), 1114 metadata, 1115 }) 1116 .expect("metadata json"), 1117 )); 1118 assert!(metadata_tags.contains(&vec!["restricted".to_string()])); 1119 assert!(metadata_tags.contains(&vec![ 1120 "supported_kinds".to_string(), 1121 "78".to_string(), 1122 "30078".to_string(), 1123 KIND_FARM_FILE_METADATA.to_string() 1124 ])); 1125 assert_tags_json(group_admins_tags( 1126 &serde_json::to_string(&RadrootsGroupAdmins { 1127 d_tag: "field-group".to_string(), 1128 description: Some("group admins".to_string()), 1129 admins: vec![sample_group_user("admin")], 1130 }) 1131 .expect("admins json"), 1132 )); 1133 assert_tags_json(group_members_tags( 1134 &serde_json::to_string(&RadrootsGroupMembers { 1135 d_tag: "field-group".to_string(), 1136 description: Some("group members".to_string()), 1137 members: vec![sample_group_user("member")], 1138 }) 1139 .expect("members json"), 1140 )); 1141 assert_tags_json(group_roles_tags( 1142 &serde_json::to_string(&RadrootsGroupRoles { 1143 d_tag: "field-group".to_string(), 1144 description: Some("group roles".to_string()), 1145 roles: vec![sample_group_role()], 1146 }) 1147 .expect("roles json"), 1148 )); 1149 } 1150 1151 #[test] 1152 fn listing_bindings_surface_builder_errors() { 1153 let mut listing_json = serde_json::to_value(sample_listing()).expect("listing value"); 1154 listing_json["bins"] = serde_json::Value::Array(Vec::new()); 1155 let listing_json = serde_json::to_string(&listing_json).expect("listing json"); 1156 1157 assert!(listing_tags(&listing_json).is_err()); 1158 assert!(listing_tags_full(&listing_json).is_err()); 1159 } 1160 }