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::ident::AtIdentifier;
20use jacquard_common::types::string::{AtUri, Cid, Datetime};
21use jacquard_common::types::uri::{RecordUri, UriError};
22use jacquard_common::xrpc::XrpcResp;
23use jacquard_derive::{IntoStatic, lexicon, open_union};
24use jacquard_lexicon::lexicon::LexiconDoc;
25use jacquard_lexicon::schema::LexiconSchema;
26
27#[allow(unused_imports)]
28use jacquard_lexicon::validation::{ConstraintError, ValidationPath};
29use serde::{Serialize, Deserialize};
30use crate::com_atproto::repo::strong_ref::StrongRef;
31use crate::pub_leaflet::pages::canvas::Canvas;
32use crate::pub_leaflet::pages::linear_document::LinearDocument;
33use crate::pub_leaflet::publication::Preferences;
34use crate::pub_leaflet::publication::Theme;
35#[lexicon]
38#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)]
39#[serde(rename_all = "camelCase", rename = "pub.leaflet.document", tag = "$type")]
40pub struct Document<'a> {
41 #[serde(borrow)]
42 pub author: AtIdentifier<'a>,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 #[serde(borrow)]
45 pub cover_image: Option<BlobRef<'a>>,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 #[serde(borrow)]
48 pub description: Option<CowStr<'a>>,
49 #[serde(borrow)]
50 pub pages: Vec<DocumentPagesItem<'a>>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 #[serde(borrow)]
53 pub post_ref: Option<StrongRef<'a>>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 #[serde(borrow)]
56 pub preferences: Option<Preferences<'a>>,
57 #[serde(skip_serializing_if = "Option::is_none")]
58 #[serde(borrow)]
59 pub publication: Option<AtUri<'a>>,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub published_at: Option<Datetime>,
62 #[serde(skip_serializing_if = "Option::is_none")]
63 #[serde(borrow)]
64 pub tags: Option<Vec<CowStr<'a>>>,
65 #[serde(skip_serializing_if = "Option::is_none")]
66 #[serde(borrow)]
67 pub theme: Option<Theme<'a>>,
68 #[serde(borrow)]
69 pub title: CowStr<'a>,
70}
71
72
73#[open_union]
74#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)]
75#[serde(tag = "$type", bound(deserialize = "'de: 'a"))]
76pub enum DocumentPagesItem<'a> {
77 #[serde(rename = "pub.leaflet.pages.linearDocument")]
78 LinearDocument(Box<LinearDocument<'a>>),
79 #[serde(rename = "pub.leaflet.pages.canvas")]
80 Canvas(Box<Canvas<'a>>),
81}
82
83#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)]
86#[serde(rename_all = "camelCase")]
87pub struct DocumentGetRecordOutput<'a> {
88 #[serde(skip_serializing_if = "Option::is_none")]
89 #[serde(borrow)]
90 pub cid: Option<Cid<'a>>,
91 #[serde(borrow)]
92 pub uri: AtUri<'a>,
93 #[serde(borrow)]
94 pub value: Document<'a>,
95}
96
97impl<'a> Document<'a> {
98 pub fn uri(
99 uri: impl Into<CowStr<'a>>,
100 ) -> Result<RecordUri<'a, DocumentRecord>, UriError> {
101 RecordUri::try_from_uri(AtUri::new_cow(uri.into())?)
102 }
103}
104
105#[derive(Debug, Serialize, Deserialize)]
108pub struct DocumentRecord;
109impl XrpcResp for DocumentRecord {
110 const NSID: &'static str = "pub.leaflet.document";
111 const ENCODING: &'static str = "application/json";
112 type Output<'de> = DocumentGetRecordOutput<'de>;
113 type Err<'de> = RecordError<'de>;
114}
115
116impl From<DocumentGetRecordOutput<'_>> for Document<'_> {
117 fn from(output: DocumentGetRecordOutput<'_>) -> Self {
118 use jacquard_common::IntoStatic;
119 output.value.into_static()
120 }
121}
122
123impl Collection for Document<'_> {
124 const NSID: &'static str = "pub.leaflet.document";
125 type Record = DocumentRecord;
126}
127
128impl Collection for DocumentRecord {
129 const NSID: &'static str = "pub.leaflet.document";
130 type Record = DocumentRecord;
131}
132
133impl<'a> LexiconSchema for Document<'a> {
134 fn nsid() -> &'static str {
135 "pub.leaflet.document"
136 }
137 fn def_name() -> &'static str {
138 "main"
139 }
140 fn lexicon_doc() -> LexiconDoc<'static> {
141 lexicon_doc_pub_leaflet_document()
142 }
143 fn validate(&self) -> Result<(), ConstraintError> {
144 if let Some(ref value) = self.cover_image {
145 {
146 let size = value.blob().size;
147 if size > 1000000usize {
148 return Err(ConstraintError::BlobTooLarge {
149 path: ValidationPath::from_field("cover_image"),
150 max: 1000000usize,
151 actual: size,
152 });
153 }
154 }
155 }
156 if let Some(ref value) = self.cover_image {
157 {
158 let mime = value.blob().mime_type.as_str();
159 let accepted: &[&str] = &["image/png", "image/jpeg", "image/webp"];
160 let matched = accepted
161 .iter()
162 .any(|pattern| {
163 if *pattern == "*/*" {
164 true
165 } else if pattern.ends_with("/*") {
166 let prefix = &pattern[..pattern.len() - 2];
167 mime.starts_with(prefix)
168 && mime.as_bytes().get(prefix.len()) == Some(&b'/')
169 } else {
170 mime == *pattern
171 }
172 });
173 if !matched {
174 return Err(ConstraintError::BlobMimeTypeNotAccepted {
175 path: ValidationPath::from_field("cover_image"),
176 accepted: vec![
177 "image/png".to_string(), "image/jpeg".to_string(),
178 "image/webp".to_string()
179 ],
180 actual: mime.to_string(),
181 });
182 }
183 }
184 }
185 if let Some(ref value) = self.description {
186 #[allow(unused_comparisons)]
187 if <str>::len(value.as_ref()) > 30000usize {
188 return Err(ConstraintError::MaxLength {
189 path: ValidationPath::from_field("description"),
190 max: 30000usize,
191 actual: <str>::len(value.as_ref()),
192 });
193 }
194 }
195 if let Some(ref value) = self.description {
196 {
197 let count = UnicodeSegmentation::graphemes(value.as_ref(), true).count();
198 if count > 3000usize {
199 return Err(ConstraintError::MaxGraphemes {
200 path: ValidationPath::from_field("description"),
201 max: 3000usize,
202 actual: count,
203 });
204 }
205 }
206 }
207 {
208 let value = &self.title;
209 #[allow(unused_comparisons)]
210 if <str>::len(value.as_ref()) > 5000usize {
211 return Err(ConstraintError::MaxLength {
212 path: ValidationPath::from_field("title"),
213 max: 5000usize,
214 actual: <str>::len(value.as_ref()),
215 });
216 }
217 }
218 {
219 let value = &self.title;
220 {
221 let count = UnicodeSegmentation::graphemes(value.as_ref(), true).count();
222 if count > 500usize {
223 return Err(ConstraintError::MaxGraphemes {
224 path: ValidationPath::from_field("title"),
225 max: 500usize,
226 actual: count,
227 });
228 }
229 }
230 }
231 Ok(())
232 }
233}
234
235pub mod document_state {
236
237 pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
238 #[allow(unused)]
239 use ::core::marker::PhantomData;
240 mod sealed {
241 pub trait Sealed {}
242 }
243 pub trait State: sealed::Sealed {
245 type Title;
246 type Author;
247 type Pages;
248 }
249 pub struct Empty(());
251 impl sealed::Sealed for Empty {}
252 impl State for Empty {
253 type Title = Unset;
254 type Author = Unset;
255 type Pages = Unset;
256 }
257 pub struct SetTitle<S: State = Empty>(PhantomData<fn() -> S>);
259 impl<S: State> sealed::Sealed for SetTitle<S> {}
260 impl<S: State> State for SetTitle<S> {
261 type Title = Set<members::title>;
262 type Author = S::Author;
263 type Pages = S::Pages;
264 }
265 pub struct SetAuthor<S: State = Empty>(PhantomData<fn() -> S>);
267 impl<S: State> sealed::Sealed for SetAuthor<S> {}
268 impl<S: State> State for SetAuthor<S> {
269 type Title = S::Title;
270 type Author = Set<members::author>;
271 type Pages = S::Pages;
272 }
273 pub struct SetPages<S: State = Empty>(PhantomData<fn() -> S>);
275 impl<S: State> sealed::Sealed for SetPages<S> {}
276 impl<S: State> State for SetPages<S> {
277 type Title = S::Title;
278 type Author = S::Author;
279 type Pages = Set<members::pages>;
280 }
281 #[allow(non_camel_case_types)]
283 pub mod members {
284 pub struct title(());
286 pub struct author(());
288 pub struct pages(());
290 }
291}
292
293pub struct DocumentBuilder<'a, S: document_state::State> {
295 _state: PhantomData<fn() -> S>,
296 _fields: (
297 Option<AtIdentifier<'a>>,
298 Option<BlobRef<'a>>,
299 Option<CowStr<'a>>,
300 Option<Vec<DocumentPagesItem<'a>>>,
301 Option<StrongRef<'a>>,
302 Option<Preferences<'a>>,
303 Option<AtUri<'a>>,
304 Option<Datetime>,
305 Option<Vec<CowStr<'a>>>,
306 Option<Theme<'a>>,
307 Option<CowStr<'a>>,
308 ),
309 _lifetime: PhantomData<&'a ()>,
310}
311
312impl<'a> Document<'a> {
313 pub fn new() -> DocumentBuilder<'a, document_state::Empty> {
315 DocumentBuilder::new()
316 }
317}
318
319impl<'a> DocumentBuilder<'a, document_state::Empty> {
320 pub fn new() -> Self {
322 DocumentBuilder {
323 _state: PhantomData,
324 _fields: (None, None, None, None, None, None, None, None, None, None, None),
325 _lifetime: PhantomData,
326 }
327 }
328}
329
330impl<'a, S> DocumentBuilder<'a, S>
331where
332 S: document_state::State,
333 S::Author: document_state::IsUnset,
334{
335 pub fn author(
337 mut self,
338 value: impl Into<AtIdentifier<'a>>,
339 ) -> DocumentBuilder<'a, document_state::SetAuthor<S>> {
340 self._fields.0 = Option::Some(value.into());
341 DocumentBuilder {
342 _state: PhantomData,
343 _fields: self._fields,
344 _lifetime: PhantomData,
345 }
346 }
347}
348
349impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
350 pub fn cover_image(mut self, value: impl Into<Option<BlobRef<'a>>>) -> Self {
352 self._fields.1 = value.into();
353 self
354 }
355 pub fn maybe_cover_image(mut self, value: Option<BlobRef<'a>>) -> Self {
357 self._fields.1 = value;
358 self
359 }
360}
361
362impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
363 pub fn description(mut self, value: impl Into<Option<CowStr<'a>>>) -> Self {
365 self._fields.2 = value.into();
366 self
367 }
368 pub fn maybe_description(mut self, value: Option<CowStr<'a>>) -> Self {
370 self._fields.2 = value;
371 self
372 }
373}
374
375impl<'a, S> DocumentBuilder<'a, S>
376where
377 S: document_state::State,
378 S::Pages: document_state::IsUnset,
379{
380 pub fn pages(
382 mut self,
383 value: impl Into<Vec<DocumentPagesItem<'a>>>,
384 ) -> DocumentBuilder<'a, document_state::SetPages<S>> {
385 self._fields.3 = Option::Some(value.into());
386 DocumentBuilder {
387 _state: PhantomData,
388 _fields: self._fields,
389 _lifetime: PhantomData,
390 }
391 }
392}
393
394impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
395 pub fn post_ref(mut self, value: impl Into<Option<StrongRef<'a>>>) -> Self {
397 self._fields.4 = value.into();
398 self
399 }
400 pub fn maybe_post_ref(mut self, value: Option<StrongRef<'a>>) -> Self {
402 self._fields.4 = value;
403 self
404 }
405}
406
407impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
408 pub fn preferences(mut self, value: impl Into<Option<Preferences<'a>>>) -> Self {
410 self._fields.5 = value.into();
411 self
412 }
413 pub fn maybe_preferences(mut self, value: Option<Preferences<'a>>) -> Self {
415 self._fields.5 = value;
416 self
417 }
418}
419
420impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
421 pub fn publication(mut self, value: impl Into<Option<AtUri<'a>>>) -> Self {
423 self._fields.6 = value.into();
424 self
425 }
426 pub fn maybe_publication(mut self, value: Option<AtUri<'a>>) -> Self {
428 self._fields.6 = value;
429 self
430 }
431}
432
433impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
434 pub fn published_at(mut self, value: impl Into<Option<Datetime>>) -> Self {
436 self._fields.7 = value.into();
437 self
438 }
439 pub fn maybe_published_at(mut self, value: Option<Datetime>) -> Self {
441 self._fields.7 = value;
442 self
443 }
444}
445
446impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
447 pub fn tags(mut self, value: impl Into<Option<Vec<CowStr<'a>>>>) -> Self {
449 self._fields.8 = value.into();
450 self
451 }
452 pub fn maybe_tags(mut self, value: Option<Vec<CowStr<'a>>>) -> Self {
454 self._fields.8 = value;
455 self
456 }
457}
458
459impl<'a, S: document_state::State> DocumentBuilder<'a, S> {
460 pub fn theme(mut self, value: impl Into<Option<Theme<'a>>>) -> Self {
462 self._fields.9 = value.into();
463 self
464 }
465 pub fn maybe_theme(mut self, value: Option<Theme<'a>>) -> Self {
467 self._fields.9 = value;
468 self
469 }
470}
471
472impl<'a, S> DocumentBuilder<'a, S>
473where
474 S: document_state::State,
475 S::Title: document_state::IsUnset,
476{
477 pub fn title(
479 mut self,
480 value: impl Into<CowStr<'a>>,
481 ) -> DocumentBuilder<'a, document_state::SetTitle<S>> {
482 self._fields.10 = Option::Some(value.into());
483 DocumentBuilder {
484 _state: PhantomData,
485 _fields: self._fields,
486 _lifetime: PhantomData,
487 }
488 }
489}
490
491impl<'a, S> DocumentBuilder<'a, S>
492where
493 S: document_state::State,
494 S::Title: document_state::IsSet,
495 S::Author: document_state::IsSet,
496 S::Pages: document_state::IsSet,
497{
498 pub fn build(self) -> Document<'a> {
500 Document {
501 author: self._fields.0.unwrap(),
502 cover_image: self._fields.1,
503 description: self._fields.2,
504 pages: self._fields.3.unwrap(),
505 post_ref: self._fields.4,
506 preferences: self._fields.5,
507 publication: self._fields.6,
508 published_at: self._fields.7,
509 tags: self._fields.8,
510 theme: self._fields.9,
511 title: self._fields.10.unwrap(),
512 extra_data: Default::default(),
513 }
514 }
515 pub fn build_with_data(
517 self,
518 extra_data: BTreeMap<
519 jacquard_common::deps::smol_str::SmolStr,
520 jacquard_common::types::value::Data<'a>,
521 >,
522 ) -> Document<'a> {
523 Document {
524 author: self._fields.0.unwrap(),
525 cover_image: self._fields.1,
526 description: self._fields.2,
527 pages: self._fields.3.unwrap(),
528 post_ref: self._fields.4,
529 preferences: self._fields.5,
530 publication: self._fields.6,
531 published_at: self._fields.7,
532 tags: self._fields.8,
533 theme: self._fields.9,
534 title: self._fields.10.unwrap(),
535 extra_data: Some(extra_data),
536 }
537 }
538}
539
540fn lexicon_doc_pub_leaflet_document() -> LexiconDoc<'static> {
541 #[allow(unused_imports)]
542 use jacquard_common::{CowStr, deps::smol_str::SmolStr, types::blob::MimeType};
543 use jacquard_lexicon::lexicon::*;
544 use alloc::collections::BTreeMap;
545 LexiconDoc {
546 lexicon: Lexicon::Lexicon1,
547 id: CowStr::new_static("pub.leaflet.document"),
548 defs: {
549 let mut map = BTreeMap::new();
550 map.insert(
551 SmolStr::new_static("main"),
552 LexUserType::Record(LexRecord {
553 description: Some(
554 CowStr::new_static("Record containing a document"),
555 ),
556 key: Some(CowStr::new_static("tid")),
557 record: LexRecordRecord::Object(LexObject {
558 required: Some(
559 vec![
560 SmolStr::new_static("pages"), SmolStr::new_static("author"),
561 SmolStr::new_static("title")
562 ],
563 ),
564 properties: {
565 #[allow(unused_mut)]
566 let mut map = BTreeMap::new();
567 map.insert(
568 SmolStr::new_static("author"),
569 LexObjectProperty::String(LexString {
570 format: Some(LexStringFormat::AtIdentifier),
571 ..Default::default()
572 }),
573 );
574 map.insert(
575 SmolStr::new_static("coverImage"),
576 LexObjectProperty::Blob(LexBlob { ..Default::default() }),
577 );
578 map.insert(
579 SmolStr::new_static("description"),
580 LexObjectProperty::String(LexString {
581 max_length: Some(30000usize),
582 max_graphemes: Some(3000usize),
583 ..Default::default()
584 }),
585 );
586 map.insert(
587 SmolStr::new_static("pages"),
588 LexObjectProperty::Array(LexArray {
589 items: LexArrayItem::Union(LexRefUnion {
590 refs: vec![
591 CowStr::new_static("pub.leaflet.pages.linearDocument"),
592 CowStr::new_static("pub.leaflet.pages.canvas")
593 ],
594 ..Default::default()
595 }),
596 ..Default::default()
597 }),
598 );
599 map.insert(
600 SmolStr::new_static("postRef"),
601 LexObjectProperty::Ref(LexRef {
602 r#ref: CowStr::new_static("com.atproto.repo.strongRef"),
603 ..Default::default()
604 }),
605 );
606 map.insert(
607 SmolStr::new_static("preferences"),
608 LexObjectProperty::Ref(LexRef {
609 r#ref: CowStr::new_static(
610 "pub.leaflet.publication#preferences",
611 ),
612 ..Default::default()
613 }),
614 );
615 map.insert(
616 SmolStr::new_static("publication"),
617 LexObjectProperty::String(LexString {
618 format: Some(LexStringFormat::AtUri),
619 ..Default::default()
620 }),
621 );
622 map.insert(
623 SmolStr::new_static("publishedAt"),
624 LexObjectProperty::String(LexString {
625 format: Some(LexStringFormat::Datetime),
626 ..Default::default()
627 }),
628 );
629 map.insert(
630 SmolStr::new_static("tags"),
631 LexObjectProperty::Array(LexArray {
632 items: LexArrayItem::String(LexString {
633 max_length: Some(50usize),
634 ..Default::default()
635 }),
636 ..Default::default()
637 }),
638 );
639 map.insert(
640 SmolStr::new_static("theme"),
641 LexObjectProperty::Ref(LexRef {
642 r#ref: CowStr::new_static("pub.leaflet.publication#theme"),
643 ..Default::default()
644 }),
645 );
646 map.insert(
647 SmolStr::new_static("title"),
648 LexObjectProperty::String(LexString {
649 max_length: Some(5000usize),
650 max_graphemes: Some(500usize),
651 ..Default::default()
652 }),
653 );
654 map
655 },
656 ..Default::default()
657 }),
658 ..Default::default()
659 }),
660 );
661 map
662 },
663 ..Default::default()
664 }
665}