1#[allow(unused_imports)]
9use alloc::collections::BTreeMap;
10
11#[allow(unused_imports)]
12use core::marker::PhantomData;
13use jacquard_common::CowStr;
14
15#[allow(unused_imports)]
16use jacquard_common::deps::codegen::unicode_segmentation::UnicodeSegmentation;
17use jacquard_common::types::blob::BlobRef;
18use jacquard_common::types::collection::{Collection, RecordError};
19use jacquard_common::types::string::{AtUri, Cid, Datetime, UriValue};
20use jacquard_common::types::uri::{RecordUri, UriError};
21use jacquard_common::xrpc::XrpcResp;
22use jacquard_derive::{IntoStatic, lexicon};
23use jacquard_lexicon::lexicon::LexiconDoc;
24use jacquard_lexicon::schema::LexiconSchema;
25
26#[allow(unused_imports)]
27use jacquard_lexicon::validation::{ConstraintError, ValidationPath};
28use serde::{Serialize, Deserialize};
29use crate::com_atproto::repo::strong_ref::StrongRef;
30use crate::pub_leaflet::content::Content;
31use crate::pub_leaflet::publication::Preferences;
32use crate::pub_leaflet::publication::Theme;
33
34#[lexicon]
35#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)]
36#[serde(rename_all = "camelCase", rename = "site.standard.document", tag = "$type")]
37pub struct Document<'a> {
38 #[serde(skip_serializing_if = "Option::is_none")]
39 #[serde(borrow)]
40 pub bsky_post_ref: Option<StrongRef<'a>>,
41 #[serde(skip_serializing_if = "Option::is_none")]
42 #[serde(borrow)]
43 pub content: Option<Content<'a>>,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 #[serde(borrow)]
46 pub cover_image: Option<BlobRef<'a>>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 #[serde(borrow)]
49 pub description: Option<CowStr<'a>>,
50 #[serde(skip_serializing_if = "Option::is_none")]
52 #[serde(borrow)]
53 pub path: Option<CowStr<'a>>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 #[serde(borrow)]
56 pub preferences: Option<Preferences<'a>>,
57 pub published_at: Datetime,
58 #[serde(borrow)]
60 pub site: UriValue<'a>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 #[serde(borrow)]
63 pub tags: Option<Vec<CowStr<'a>>>,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 #[serde(borrow)]
66 pub text_content: Option<CowStr<'a>>,
67 #[serde(skip_serializing_if = "Option::is_none")]
69 #[serde(borrow)]
70 pub theme: Option<Theme<'a>>,
71 #[serde(borrow)]
72 pub title: CowStr<'a>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub updated_at: Option<Datetime>,
75}
76
77#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)]
80#[serde(rename_all = "camelCase")]
81pub struct DocumentGetRecordOutput<'a> {
82 #[serde(skip_serializing_if = "Option::is_none")]
83 #[serde(borrow)]
84 pub cid: Option<Cid<'a>>,
85 #[serde(borrow)]
86 pub uri: AtUri<'a>,
87 #[serde(borrow)]
88 pub value: Document<'a>,
89}
90
91impl<'a> Document<'a> {
92 pub fn uri(
93 uri: impl Into<CowStr<'a>>,
94 ) -> Result<RecordUri<'a, DocumentRecord>, UriError> {
95 RecordUri::try_from_uri(AtUri::new_cow(uri.into())?)
96 }
97}
98
99#[derive(Debug, Serialize, Deserialize)]
102pub struct DocumentRecord;
103impl XrpcResp for DocumentRecord {
104 const NSID: &'static str = "site.standard.document";
105 const ENCODING: &'static str = "application/json";
106 type Output<'de> = DocumentGetRecordOutput<'de>;
107 type Err<'de> = RecordError<'de>;
108}
109
110impl From<DocumentGetRecordOutput<'_>> for Document<'_> {
111 fn from(output: DocumentGetRecordOutput<'_>) -> Self {
112 use jacquard_common::IntoStatic;
113 output.value.into_static()
114 }
115}
116
117impl Collection for Document<'_> {
118 const NSID: &'static str = "site.standard.document";
119 type Record = DocumentRecord;
120}
121
122impl Collection for DocumentRecord {
123 const NSID: &'static str = "site.standard.document";
124 type Record = DocumentRecord;
125}
126
127impl<'a> LexiconSchema for Document<'a> {
128 fn nsid() -> &'static str {
129 "site.standard.document"
130 }
131 fn def_name() -> &'static str {
132 "main"
133 }
134 fn lexicon_doc() -> LexiconDoc<'static> {
135 lexicon_doc_site_standard_document()
136 }
137 fn validate(&self) -> Result<(), ConstraintError> {
138 if let Some(ref value) = self.cover_image {
139 {
140 let size = value.blob().size;
141 if size > 1000000usize {
142 return Err(ConstraintError::BlobTooLarge {
143 path: ValidationPath::from_field("cover_image"),
144 max: 1000000usize,
145 actual: size,
146 });
147 }
148 }
149 }
150 if let Some(ref value) = self.cover_image {
151 {
152 let mime = value.blob().mime_type.as_str();
153 let accepted: &[&str] = &["image/*"];
154 let matched = accepted
155 .iter()
156 .any(|pattern| {
157 if *pattern == "*/*" {
158 true
159 } else if pattern.ends_with("/*") {
160 let prefix = &pattern[..pattern.len() - 2];
161 mime.starts_with(prefix)
162 && mime.as_bytes().get(prefix.len()) == Some(&b'/')
163 } else {
164 mime == *pattern
165 }
166 });
167 if !matched {
168 return Err(ConstraintError::BlobMimeTypeNotAccepted {
169 path: ValidationPath::from_field("cover_image"),
170 accepted: vec!["image/*".to_string()],
171 actual: mime.to_string(),
172 });
173 }
174 }
175 }
176 if let Some(ref value) = self.description {
177 #[allow(unused_comparisons)]
178 if <str>::len(value.as_ref()) > 30000usize {
179 return Err(ConstraintError::MaxLength {
180 path: ValidationPath::from_field("description"),
181 max: 30000usize,
182 actual: <str>::len(value.as_ref()),
183 });
184 }
185 }
186 if let Some(ref value) = self.description {
187 {
188 let count = UnicodeSegmentation::graphemes(value.as_ref(), true).count();
189 if count > 3000usize {
190 return Err(ConstraintError::MaxGraphemes {
191 path: ValidationPath::from_field("description"),
192 max: 3000usize,
193 actual: count,
194 });
195 }
196 }
197 }
198 {
199 let value = &self.title;
200 #[allow(unused_comparisons)]
201 if <str>::len(value.as_ref()) > 5000usize {
202 return Err(ConstraintError::MaxLength {
203 path: ValidationPath::from_field("title"),
204 max: 5000usize,
205 actual: <str>::len(value.as_ref()),
206 });
207 }
208 }
209 {
210 let value = &self.title;
211 {
212 let count = UnicodeSegmentation::graphemes(value.as_ref(), true).count();
213 if count > 500usize {
214 return Err(ConstraintError::MaxGraphemes {
215 path: ValidationPath::from_field("title"),
216 max: 500usize,
217 actual: count,
218 });
219 }
220 }
221 }
222 Ok(())
223 }
224}
225
226pub mod document_state {
227
228 pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
229 #[allow(unused)]
230 use ::core::marker::PhantomData;
231 mod sealed {
232 pub trait Sealed {}
233 }
234 pub trait State: sealed::Sealed {
236 type Title;
237 type PublishedAt;
238 type Site;
239 }
240 pub struct Empty(());
242 impl sealed::Sealed for Empty {}
243 impl State for Empty {
244 type Title = Unset;
245 type PublishedAt = Unset;
246 type Site = Unset;
247 }
248 pub struct SetTitle<S: State = Empty>(PhantomData<fn() -> S>);
250 impl<S: State> sealed::Sealed for SetTitle<S> {}
251 impl<S: State> State for SetTitle<S> {
252 type Title = Set<members::title>;
253 type PublishedAt = S::PublishedAt;
254 type Site = S::Site;
255 }
256 pub struct SetPublishedAt<S: State = Empty>(PhantomData<fn() -> S>);
258 impl<S: State> sealed::Sealed for SetPublishedAt<S> {}
259 impl<S: State> State for SetPublishedAt<S> {
260 type Title = S::Title;
261 type PublishedAt = Set<members::published_at>;
262 type Site = S::Site;
263 }
264 pub struct SetSite<S: State = Empty>(PhantomData<fn() -> S>);
266 impl<S: State> sealed::Sealed for SetSite<S> {}
267 impl<S: State> State for SetSite<S> {
268 type Title = S::Title;
269 type PublishedAt = S::PublishedAt;
270 type Site = Set<members::site>;
271 }
272 #[allow(non_camel_case_types)]
274 pub mod members {
275 pub struct title(());
277 pub struct published_at(());
279 pub struct site(());
281 }
282}
283
284pub struct DocumentBuilder<'a, S: document_state::State> {
286 _state: PhantomData<fn() -> S>,
287 _fields: (
288 Option<StrongRef<'a>>,
289 Option<Content<'a>>,
290 Option<BlobRef<'a>>,
291 Option<CowStr<'a>>,
292 Option<CowStr<'a>>,
293 Option<Preferences<'a>>,
294 Option<Datetime>,
295 Option<UriValue<'a>>,
296 Option<Vec<CowStr<'a>>>,
297 Option<CowStr<'a>>,
298 Option<Theme<'a>>,
299 Option<CowStr<'a>>,
300 Option<Datetime>,
301 ),
302 _lifetime: PhantomData<&'a ()>,
303}
304
305impl<'a> Document<'a> {
306 pub fn new() -> DocumentBuilder<'a, document_state::Empty> {
308 DocumentBuilder::new()
309 }
310}
311
312impl<'a> DocumentBuilder<'a, document_state::Empty> {
313 pub fn new() -> Self {
315 DocumentBuilder {
316 _state: PhantomData,
317 _fields: (
318 None,
319 None,
320 None,
321 None,
322 None,
323 None,
324 None,
325 None,
326 None,
327 None,
328 None,
329 None,
330 None,
331 ),
332 _lifetime: PhantomData,
333 }
334 }
335}
336
337impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
338 pub fn bsky_post_ref(mut self, value: impl Into<Option<StrongRef<'a>>>) -> Self {
340 self._fields.0 = value.into();
341 self
342 }
343 pub fn maybe_bsky_post_ref(mut self, value: Option<StrongRef<'a>>) -> Self {
345 self._fields.0 = value;
346 self
347 }
348}
349
350impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
351 pub fn content(mut self, value: impl Into<Option<Content<'a>>>) -> Self {
353 self._fields.1 = value.into();
354 self
355 }
356 pub fn maybe_content(mut self, value: Option<Content<'a>>) -> Self {
358 self._fields.1 = value;
359 self
360 }
361}
362
363impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
364 pub fn cover_image(mut self, value: impl Into<Option<BlobRef<'a>>>) -> Self {
366 self._fields.2 = value.into();
367 self
368 }
369 pub fn maybe_cover_image(mut self, value: Option<BlobRef<'a>>) -> Self {
371 self._fields.2 = value;
372 self
373 }
374}
375
376impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
377 pub fn description(mut self, value: impl Into<Option<CowStr<'a>>>) -> Self {
379 self._fields.3 = value.into();
380 self
381 }
382 pub fn maybe_description(mut self, value: Option<CowStr<'a>>) -> Self {
384 self._fields.3 = value;
385 self
386 }
387}
388
389impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
390 pub fn path(mut self, value: impl Into<Option<CowStr<'a>>>) -> Self {
392 self._fields.4 = value.into();
393 self
394 }
395 pub fn maybe_path(mut self, value: Option<CowStr<'a>>) -> Self {
397 self._fields.4 = value;
398 self
399 }
400}
401
402impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
403 pub fn preferences(mut self, value: impl Into<Option<Preferences<'a>>>) -> Self {
405 self._fields.5 = value.into();
406 self
407 }
408 pub fn maybe_preferences(mut self, value: Option<Preferences<'a>>) -> Self {
410 self._fields.5 = value;
411 self
412 }
413}
414
415impl<'a, S> DocumentBuilder<'a, S>
416where
417 S: document_state::State,
418 S::PublishedAt: document_state::IsUnset,
419{
420 pub fn published_at(
422 mut self,
423 value: impl Into<Datetime>,
424 ) -> DocumentBuilder<'a, document_state::SetPublishedAt<S>> {
425 self._fields.6 = Option::Some(value.into());
426 DocumentBuilder {
427 _state: PhantomData,
428 _fields: self._fields,
429 _lifetime: PhantomData,
430 }
431 }
432}
433
434impl<'a, S> DocumentBuilder<'a, S>
435where
436 S: document_state::State,
437 S::Site: document_state::IsUnset,
438{
439 pub fn site(
441 mut self,
442 value: impl Into<UriValue<'a>>,
443 ) -> DocumentBuilder<'a, document_state::SetSite<S>> {
444 self._fields.7 = Option::Some(value.into());
445 DocumentBuilder {
446 _state: PhantomData,
447 _fields: self._fields,
448 _lifetime: PhantomData,
449 }
450 }
451}
452
453impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
454 pub fn tags(mut self, value: impl Into<Option<Vec<CowStr<'a>>>>) -> Self {
456 self._fields.8 = value.into();
457 self
458 }
459 pub fn maybe_tags(mut self, value: Option<Vec<CowStr<'a>>>) -> Self {
461 self._fields.8 = value;
462 self
463 }
464}
465
466impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
467 pub fn text_content(mut self, value: impl Into<Option<CowStr<'a>>>) -> Self {
469 self._fields.9 = value.into();
470 self
471 }
472 pub fn maybe_text_content(mut self, value: Option<CowStr<'a>>) -> Self {
474 self._fields.9 = value;
475 self
476 }
477}
478
479impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
480 pub fn theme(mut self, value: impl Into<Option<Theme<'a>>>) -> Self {
482 self._fields.10 = value.into();
483 self
484 }
485 pub fn maybe_theme(mut self, value: Option<Theme<'a>>) -> Self {
487 self._fields.10 = value;
488 self
489 }
490}
491
492impl<'a, S> DocumentBuilder<'a, S>
493where
494 S: document_state::State,
495 S::Title: document_state::IsUnset,
496{
497 pub fn title(
499 mut self,
500 value: impl Into<CowStr<'a>>,
501 ) -> DocumentBuilder<'a, document_state::SetTitle<S>> {
502 self._fields.11 = Option::Some(value.into());
503 DocumentBuilder {
504 _state: PhantomData,
505 _fields: self._fields,
506 _lifetime: PhantomData,
507 }
508 }
509}
510
511impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
512 pub fn updated_at(mut self, value: impl Into<Option<Datetime>>) -> Self {
514 self._fields.12 = value.into();
515 self
516 }
517 pub fn maybe_updated_at(mut self, value: Option<Datetime>) -> Self {
519 self._fields.12 = value;
520 self
521 }
522}
523
524impl<'a, S> DocumentBuilder<'a, S>
525where
526 S: document_state::State,
527 S::Title: document_state::IsSet,
528 S::PublishedAt: document_state::IsSet,
529 S::Site: document_state::IsSet,
530{
531 pub fn build(self) -> Document<'a> {
533 Document {
534 bsky_post_ref: self._fields.0,
535 content: self._fields.1,
536 cover_image: self._fields.2,
537 description: self._fields.3,
538 path: self._fields.4,
539 preferences: self._fields.5,
540 published_at: self._fields.6.unwrap(),
541 site: self._fields.7.unwrap(),
542 tags: self._fields.8,
543 text_content: self._fields.9,
544 theme: self._fields.10,
545 title: self._fields.11.unwrap(),
546 updated_at: self._fields.12,
547 extra_data: Default::default(),
548 }
549 }
550 pub fn build_with_data(
552 self,
553 extra_data: BTreeMap<
554 jacquard_common::deps::smol_str::SmolStr,
555 jacquard_common::types::value::Data<'a>,
556 >,
557 ) -> Document<'a> {
558 Document {
559 bsky_post_ref: self._fields.0,
560 content: self._fields.1,
561 cover_image: self._fields.2,
562 description: self._fields.3,
563 path: self._fields.4,
564 preferences: self._fields.5,
565 published_at: self._fields.6.unwrap(),
566 site: self._fields.7.unwrap(),
567 tags: self._fields.8,
568 text_content: self._fields.9,
569 theme: self._fields.10,
570 title: self._fields.11.unwrap(),
571 updated_at: self._fields.12,
572 extra_data: Some(extra_data),
573 }
574 }
575}
576
577fn lexicon_doc_site_standard_document() -> LexiconDoc<'static> {
578 #[allow(unused_imports)]
579 use jacquard_common::{CowStr, deps::smol_str::SmolStr, types::blob::MimeType};
580 use jacquard_lexicon::lexicon::*;
581 use alloc::collections::BTreeMap;
582 LexiconDoc {
583 lexicon: Lexicon::Lexicon1,
584 id: CowStr::new_static("site.standard.document"),
585 defs: {
586 let mut map = BTreeMap::new();
587 map.insert(
588 SmolStr::new_static("main"),
589 LexUserType::Record(LexRecord {
590 key: Some(CowStr::new_static("tid")),
591 record: LexRecordRecord::Object(LexObject {
592 required: Some(
593 vec![
594 SmolStr::new_static("site"), SmolStr::new_static("title"),
595 SmolStr::new_static("publishedAt")
596 ],
597 ),
598 properties: {
599 #[allow(unused_mut)]
600 let mut map = BTreeMap::new();
601 map.insert(
602 SmolStr::new_static("bskyPostRef"),
603 LexObjectProperty::Ref(LexRef {
604 r#ref: CowStr::new_static("com.atproto.repo.strongRef"),
605 ..Default::default()
606 }),
607 );
608 map.insert(
609 SmolStr::new_static("content"),
610 LexObjectProperty::Union(LexRefUnion {
611 refs: vec![CowStr::new_static("pub.leaflet.content")],
612 closed: Some(false),
613 ..Default::default()
614 }),
615 );
616 map.insert(
617 SmolStr::new_static("coverImage"),
618 LexObjectProperty::Blob(LexBlob { ..Default::default() }),
619 );
620 map.insert(
621 SmolStr::new_static("description"),
622 LexObjectProperty::String(LexString {
623 max_length: Some(30000usize),
624 max_graphemes: Some(3000usize),
625 ..Default::default()
626 }),
627 );
628 map.insert(
629 SmolStr::new_static("path"),
630 LexObjectProperty::String(LexString {
631 description: Some(
632 CowStr::new_static(
633 "combine with the publication url or the document site to construct a full url to the document",
634 ),
635 ),
636 ..Default::default()
637 }),
638 );
639 map.insert(
640 SmolStr::new_static("preferences"),
641 LexObjectProperty::Union(LexRefUnion {
642 refs: vec![
643 CowStr::new_static("pub.leaflet.publication#preferences")
644 ],
645 closed: Some(false),
646 ..Default::default()
647 }),
648 );
649 map.insert(
650 SmolStr::new_static("publishedAt"),
651 LexObjectProperty::String(LexString {
652 format: Some(LexStringFormat::Datetime),
653 ..Default::default()
654 }),
655 );
656 map.insert(
657 SmolStr::new_static("site"),
658 LexObjectProperty::String(LexString {
659 description: Some(
660 CowStr::new_static(
661 "URI to the site or publication this document belongs to. Supports both AT-URIs (at://did/collection/rkey) for publication references and HTTPS URLs (https://example.com) for standalone documents or external sites.",
662 ),
663 ),
664 format: Some(LexStringFormat::Uri),
665 ..Default::default()
666 }),
667 );
668 map.insert(
669 SmolStr::new_static("tags"),
670 LexObjectProperty::Array(LexArray {
671 items: LexArrayItem::String(LexString {
672 max_length: Some(100usize),
673 max_graphemes: Some(50usize),
674 ..Default::default()
675 }),
676 ..Default::default()
677 }),
678 );
679 map.insert(
680 SmolStr::new_static("textContent"),
681 LexObjectProperty::String(LexString {
682 ..Default::default()
683 }),
684 );
685 map.insert(
686 SmolStr::new_static("theme"),
687 LexObjectProperty::Ref(LexRef {
688 r#ref: CowStr::new_static("pub.leaflet.publication#theme"),
689 ..Default::default()
690 }),
691 );
692 map.insert(
693 SmolStr::new_static("title"),
694 LexObjectProperty::String(LexString {
695 max_length: Some(5000usize),
696 max_graphemes: Some(500usize),
697 ..Default::default()
698 }),
699 );
700 map.insert(
701 SmolStr::new_static("updatedAt"),
702 LexObjectProperty::String(LexString {
703 format: Some(LexStringFormat::Datetime),
704 ..Default::default()
705 }),
706 );
707 map
708 },
709 ..Default::default()
710 }),
711 ..Default::default()
712 }),
713 );
714 map
715 },
716 ..Default::default()
717 }
718}