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::collection::{Collection, RecordError};
18use jacquard_common::types::string::{AtUri, Cid, Datetime, Language};
19use jacquard_common::types::uri::{RecordUri, UriError};
20use jacquard_common::xrpc::XrpcResp;
21use jacquard_derive::{IntoStatic, lexicon, open_union};
22use jacquard_lexicon::lexicon::LexiconDoc;
23use jacquard_lexicon::schema::LexiconSchema;
24
25#[allow(unused_imports)]
26use jacquard_lexicon::validation::{ConstraintError, ValidationPath};
27use serde::{Serialize, Deserialize};
28use crate::app_bsky::embed::external::ExternalRecord;
29use crate::app_bsky::embed::images::Images;
30use crate::app_bsky::embed::record::Record;
31use crate::app_bsky::embed::record_with_media::RecordWithMedia;
32use crate::app_bsky::embed::video::Video;
33use crate::app_bsky::richtext::facet::Facet;
34use crate::com_atproto::label::SelfLabels;
35use crate::com_atproto::repo::strong_ref::StrongRef;
36use crate::net_anisota::feed::post;
37#[lexicon]
40#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)]
41#[serde(rename_all = "camelCase", rename = "net.anisota.feed.post", tag = "$type")]
42pub struct Post<'a> {
43 pub created_at: Datetime,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 #[serde(borrow)]
47 pub embed: Option<PostEmbed<'a>>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 #[serde(borrow)]
51 pub facets: Option<Vec<Facet<'a>>>,
52 #[serde(skip_serializing_if = "Option::is_none")]
54 #[serde(borrow)]
55 pub labels: Option<SelfLabels<'a>>,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub langs: Option<Vec<Language>>,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 #[serde(borrow)]
61 pub reply: Option<post::ReplyRef<'a>>,
62 #[serde(skip_serializing_if = "Option::is_none")]
64 #[serde(borrow)]
65 pub tags: Option<Vec<CowStr<'a>>>,
66 #[serde(borrow)]
68 pub text: CowStr<'a>,
69}
70
71
72#[open_union]
73#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)]
74#[serde(tag = "$type", bound(deserialize = "'de: 'a"))]
75pub enum PostEmbed<'a> {
76 #[serde(rename = "app.bsky.embed.images")]
77 Images(Box<Images<'a>>),
78 #[serde(rename = "app.bsky.embed.video")]
79 Video(Box<Video<'a>>),
80 #[serde(rename = "app.bsky.embed.external")]
81 External(Box<ExternalRecord<'a>>),
82 #[serde(rename = "app.bsky.embed.record")]
83 Record(Box<Record<'a>>),
84 #[serde(rename = "app.bsky.embed.recordWithMedia")]
85 RecordWithMedia(Box<RecordWithMedia<'a>>),
86}
87
88#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)]
91#[serde(rename_all = "camelCase")]
92pub struct PostGetRecordOutput<'a> {
93 #[serde(skip_serializing_if = "Option::is_none")]
94 #[serde(borrow)]
95 pub cid: Option<Cid<'a>>,
96 #[serde(borrow)]
97 pub uri: AtUri<'a>,
98 #[serde(borrow)]
99 pub value: Post<'a>,
100}
101
102
103#[lexicon]
104#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)]
105#[serde(rename_all = "camelCase")]
106pub struct ReplyRef<'a> {
107 #[serde(borrow)]
108 pub parent: StrongRef<'a>,
109 #[serde(borrow)]
110 pub root: StrongRef<'a>,
111}
112
113impl<'a> Post<'a> {
114 pub fn uri(
115 uri: impl Into<CowStr<'a>>,
116 ) -> Result<RecordUri<'a, PostRecord>, UriError> {
117 RecordUri::try_from_uri(AtUri::new_cow(uri.into())?)
118 }
119}
120
121#[derive(Debug, Serialize, Deserialize)]
124pub struct PostRecord;
125impl XrpcResp for PostRecord {
126 const NSID: &'static str = "net.anisota.feed.post";
127 const ENCODING: &'static str = "application/json";
128 type Output<'de> = PostGetRecordOutput<'de>;
129 type Err<'de> = RecordError<'de>;
130}
131
132impl From<PostGetRecordOutput<'_>> for Post<'_> {
133 fn from(output: PostGetRecordOutput<'_>) -> Self {
134 use jacquard_common::IntoStatic;
135 output.value.into_static()
136 }
137}
138
139impl Collection for Post<'_> {
140 const NSID: &'static str = "net.anisota.feed.post";
141 type Record = PostRecord;
142}
143
144impl Collection for PostRecord {
145 const NSID: &'static str = "net.anisota.feed.post";
146 type Record = PostRecord;
147}
148
149impl<'a> LexiconSchema for Post<'a> {
150 fn nsid() -> &'static str {
151 "net.anisota.feed.post"
152 }
153 fn def_name() -> &'static str {
154 "main"
155 }
156 fn lexicon_doc() -> LexiconDoc<'static> {
157 lexicon_doc_net_anisota_feed_post()
158 }
159 fn validate(&self) -> Result<(), ConstraintError> {
160 if let Some(ref value) = self.langs {
161 #[allow(unused_comparisons)]
162 if value.len() > 3usize {
163 return Err(ConstraintError::MaxLength {
164 path: ValidationPath::from_field("langs"),
165 max: 3usize,
166 actual: value.len(),
167 });
168 }
169 }
170 if let Some(ref value) = self.tags {
171 #[allow(unused_comparisons)]
172 if value.len() > 8usize {
173 return Err(ConstraintError::MaxLength {
174 path: ValidationPath::from_field("tags"),
175 max: 8usize,
176 actual: value.len(),
177 });
178 }
179 }
180 {
181 let value = &self.text;
182 #[allow(unused_comparisons)]
183 if <str>::len(value.as_ref()) > 3000usize {
184 return Err(ConstraintError::MaxLength {
185 path: ValidationPath::from_field("text"),
186 max: 3000usize,
187 actual: <str>::len(value.as_ref()),
188 });
189 }
190 }
191 {
192 let value = &self.text;
193 {
194 let count = UnicodeSegmentation::graphemes(value.as_ref(), true).count();
195 if count > 300usize {
196 return Err(ConstraintError::MaxGraphemes {
197 path: ValidationPath::from_field("text"),
198 max: 300usize,
199 actual: count,
200 });
201 }
202 }
203 }
204 Ok(())
205 }
206}
207
208impl<'a> LexiconSchema for ReplyRef<'a> {
209 fn nsid() -> &'static str {
210 "net.anisota.feed.post"
211 }
212 fn def_name() -> &'static str {
213 "replyRef"
214 }
215 fn lexicon_doc() -> LexiconDoc<'static> {
216 lexicon_doc_net_anisota_feed_post()
217 }
218 fn validate(&self) -> Result<(), ConstraintError> {
219 Ok(())
220 }
221}
222
223pub mod post_state {
224
225 pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
226 #[allow(unused)]
227 use ::core::marker::PhantomData;
228 mod sealed {
229 pub trait Sealed {}
230 }
231 pub trait State: sealed::Sealed {
233 type CreatedAt;
234 type Text;
235 }
236 pub struct Empty(());
238 impl sealed::Sealed for Empty {}
239 impl State for Empty {
240 type CreatedAt = Unset;
241 type Text = Unset;
242 }
243 pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>);
245 impl<S: State> sealed::Sealed for SetCreatedAt<S> {}
246 impl<S: State> State for SetCreatedAt<S> {
247 type CreatedAt = Set<members::created_at>;
248 type Text = S::Text;
249 }
250 pub struct SetText<S: State = Empty>(PhantomData<fn() -> S>);
252 impl<S: State> sealed::Sealed for SetText<S> {}
253 impl<S: State> State for SetText<S> {
254 type CreatedAt = S::CreatedAt;
255 type Text = Set<members::text>;
256 }
257 #[allow(non_camel_case_types)]
259 pub mod members {
260 pub struct created_at(());
262 pub struct text(());
264 }
265}
266
267pub struct PostBuilder<'a, S: post_state::State> {
269 _state: PhantomData<fn() -> S>,
270 _fields: (
271 Option<Datetime>,
272 Option<PostEmbed<'a>>,
273 Option<Vec<Facet<'a>>>,
274 Option<SelfLabels<'a>>,
275 Option<Vec<Language>>,
276 Option<post::ReplyRef<'a>>,
277 Option<Vec<CowStr<'a>>>,
278 Option<CowStr<'a>>,
279 ),
280 _lifetime: PhantomData<&'a ()>,
281}
282
283impl<'a> Post<'a> {
284 pub fn new() -> PostBuilder<'a, post_state::Empty> {
286 PostBuilder::new()
287 }
288}
289
290impl<'a> PostBuilder<'a, post_state::Empty> {
291 pub fn new() -> Self {
293 PostBuilder {
294 _state: PhantomData,
295 _fields: (None, None, None, None, None, None, None, None),
296 _lifetime: PhantomData,
297 }
298 }
299}
300
301impl<'a, S> PostBuilder<'a, S>
302where
303 S: post_state::State,
304 S::CreatedAt: post_state::IsUnset,
305{
306 pub fn created_at(
308 mut self,
309 value: impl Into<Datetime>,
310 ) -> PostBuilder<'a, post_state::SetCreatedAt<S>> {
311 self._fields.0 = Option::Some(value.into());
312 PostBuilder {
313 _state: PhantomData,
314 _fields: self._fields,
315 _lifetime: PhantomData,
316 }
317 }
318}
319
320impl<'a, S: post_state::State> PostBuilder<'a, S> {
321 pub fn embed(mut self, value: impl Into<Option<PostEmbed<'a>>>) -> Self {
323 self._fields.1 = value.into();
324 self
325 }
326 pub fn maybe_embed(mut self, value: Option<PostEmbed<'a>>) -> Self {
328 self._fields.1 = value;
329 self
330 }
331}
332
333impl<'a, S: post_state::State> PostBuilder<'a, S> {
334 pub fn facets(mut self, value: impl Into<Option<Vec<Facet<'a>>>>) -> Self {
336 self._fields.2 = value.into();
337 self
338 }
339 pub fn maybe_facets(mut self, value: Option<Vec<Facet<'a>>>) -> Self {
341 self._fields.2 = value;
342 self
343 }
344}
345
346impl<'a, S: post_state::State> PostBuilder<'a, S> {
347 pub fn labels(mut self, value: impl Into<Option<SelfLabels<'a>>>) -> Self {
349 self._fields.3 = value.into();
350 self
351 }
352 pub fn maybe_labels(mut self, value: Option<SelfLabels<'a>>) -> Self {
354 self._fields.3 = value;
355 self
356 }
357}
358
359impl<'a, S: post_state::State> PostBuilder<'a, S> {
360 pub fn langs(mut self, value: impl Into<Option<Vec<Language>>>) -> Self {
362 self._fields.4 = value.into();
363 self
364 }
365 pub fn maybe_langs(mut self, value: Option<Vec<Language>>) -> Self {
367 self._fields.4 = value;
368 self
369 }
370}
371
372impl<'a, S: post_state::State> PostBuilder<'a, S> {
373 pub fn reply(mut self, value: impl Into<Option<post::ReplyRef<'a>>>) -> Self {
375 self._fields.5 = value.into();
376 self
377 }
378 pub fn maybe_reply(mut self, value: Option<post::ReplyRef<'a>>) -> Self {
380 self._fields.5 = value;
381 self
382 }
383}
384
385impl<'a, S: post_state::State> PostBuilder<'a, S> {
386 pub fn tags(mut self, value: impl Into<Option<Vec<CowStr<'a>>>>) -> Self {
388 self._fields.6 = value.into();
389 self
390 }
391 pub fn maybe_tags(mut self, value: Option<Vec<CowStr<'a>>>) -> Self {
393 self._fields.6 = value;
394 self
395 }
396}
397
398impl<'a, S> PostBuilder<'a, S>
399where
400 S: post_state::State,
401 S::Text: post_state::IsUnset,
402{
403 pub fn text(
405 mut self,
406 value: impl Into<CowStr<'a>>,
407 ) -> PostBuilder<'a, post_state::SetText<S>> {
408 self._fields.7 = Option::Some(value.into());
409 PostBuilder {
410 _state: PhantomData,
411 _fields: self._fields,
412 _lifetime: PhantomData,
413 }
414 }
415}
416
417impl<'a, S> PostBuilder<'a, S>
418where
419 S: post_state::State,
420 S::CreatedAt: post_state::IsSet,
421 S::Text: post_state::IsSet,
422{
423 pub fn build(self) -> Post<'a> {
425 Post {
426 created_at: self._fields.0.unwrap(),
427 embed: self._fields.1,
428 facets: self._fields.2,
429 labels: self._fields.3,
430 langs: self._fields.4,
431 reply: self._fields.5,
432 tags: self._fields.6,
433 text: self._fields.7.unwrap(),
434 extra_data: Default::default(),
435 }
436 }
437 pub fn build_with_data(
439 self,
440 extra_data: BTreeMap<
441 jacquard_common::deps::smol_str::SmolStr,
442 jacquard_common::types::value::Data<'a>,
443 >,
444 ) -> Post<'a> {
445 Post {
446 created_at: self._fields.0.unwrap(),
447 embed: self._fields.1,
448 facets: self._fields.2,
449 labels: self._fields.3,
450 langs: self._fields.4,
451 reply: self._fields.5,
452 tags: self._fields.6,
453 text: self._fields.7.unwrap(),
454 extra_data: Some(extra_data),
455 }
456 }
457}
458
459fn lexicon_doc_net_anisota_feed_post() -> LexiconDoc<'static> {
460 #[allow(unused_imports)]
461 use jacquard_common::{CowStr, deps::smol_str::SmolStr, types::blob::MimeType};
462 use jacquard_lexicon::lexicon::*;
463 use alloc::collections::BTreeMap;
464 LexiconDoc {
465 lexicon: Lexicon::Lexicon1,
466 id: CowStr::new_static("net.anisota.feed.post"),
467 defs: {
468 let mut map = BTreeMap::new();
469 map.insert(
470 SmolStr::new_static("main"),
471 LexUserType::Record(LexRecord {
472 description: Some(
473 CowStr::new_static(
474 "A post that can be created on the Anisota network",
475 ),
476 ),
477 key: Some(CowStr::new_static("tid")),
478 record: LexRecordRecord::Object(LexObject {
479 required: Some(
480 vec![
481 SmolStr::new_static("text"),
482 SmolStr::new_static("createdAt")
483 ],
484 ),
485 properties: {
486 #[allow(unused_mut)]
487 let mut map = BTreeMap::new();
488 map.insert(
489 SmolStr::new_static("createdAt"),
490 LexObjectProperty::String(LexString {
491 description: Some(
492 CowStr::new_static(
493 "Client-declared timestamp when this post was created.",
494 ),
495 ),
496 format: Some(LexStringFormat::Datetime),
497 ..Default::default()
498 }),
499 );
500 map.insert(
501 SmolStr::new_static("embed"),
502 LexObjectProperty::Union(LexRefUnion {
503 refs: vec![
504 CowStr::new_static("app.bsky.embed.images"),
505 CowStr::new_static("app.bsky.embed.video"),
506 CowStr::new_static("app.bsky.embed.external"),
507 CowStr::new_static("app.bsky.embed.record"),
508 CowStr::new_static("app.bsky.embed.recordWithMedia")
509 ],
510 ..Default::default()
511 }),
512 );
513 map.insert(
514 SmolStr::new_static("facets"),
515 LexObjectProperty::Array(LexArray {
516 description: Some(
517 CowStr::new_static(
518 "Annotations of text (mentions, URLs, hashtags, etc)",
519 ),
520 ),
521 items: LexArrayItem::Ref(LexRef {
522 r#ref: CowStr::new_static("app.bsky.richtext.facet"),
523 ..Default::default()
524 }),
525 ..Default::default()
526 }),
527 );
528 map.insert(
529 SmolStr::new_static("labels"),
530 LexObjectProperty::Union(LexRefUnion {
531 description: Some(
532 CowStr::new_static(
533 "Self-label values for this post. Effectively content warnings.",
534 ),
535 ),
536 refs: vec![
537 CowStr::new_static("com.atproto.label.defs#selfLabels")
538 ],
539 ..Default::default()
540 }),
541 );
542 map.insert(
543 SmolStr::new_static("langs"),
544 LexObjectProperty::Array(LexArray {
545 description: Some(
546 CowStr::new_static(
547 "Indicates human language of post primary text content.",
548 ),
549 ),
550 items: LexArrayItem::String(LexString {
551 format: Some(LexStringFormat::Language),
552 ..Default::default()
553 }),
554 max_length: Some(3usize),
555 ..Default::default()
556 }),
557 );
558 map.insert(
559 SmolStr::new_static("reply"),
560 LexObjectProperty::Ref(LexRef {
561 r#ref: CowStr::new_static("#replyRef"),
562 ..Default::default()
563 }),
564 );
565 map.insert(
566 SmolStr::new_static("tags"),
567 LexObjectProperty::Array(LexArray {
568 description: Some(
569 CowStr::new_static(
570 "Additional hashtags, in addition to any included in post text and facets.",
571 ),
572 ),
573 items: LexArrayItem::String(LexString {
574 max_length: Some(640usize),
575 max_graphemes: Some(64usize),
576 ..Default::default()
577 }),
578 max_length: Some(8usize),
579 ..Default::default()
580 }),
581 );
582 map.insert(
583 SmolStr::new_static("text"),
584 LexObjectProperty::String(LexString {
585 description: Some(
586 CowStr::new_static(
587 "The primary post content. May be an empty string, if there are embeds.",
588 ),
589 ),
590 max_length: Some(3000usize),
591 max_graphemes: Some(300usize),
592 ..Default::default()
593 }),
594 );
595 map
596 },
597 ..Default::default()
598 }),
599 ..Default::default()
600 }),
601 );
602 map.insert(
603 SmolStr::new_static("replyRef"),
604 LexUserType::Object(LexObject {
605 required: Some(
606 vec![SmolStr::new_static("root"), SmolStr::new_static("parent")],
607 ),
608 properties: {
609 #[allow(unused_mut)]
610 let mut map = BTreeMap::new();
611 map.insert(
612 SmolStr::new_static("parent"),
613 LexObjectProperty::Ref(LexRef {
614 r#ref: CowStr::new_static("com.atproto.repo.strongRef"),
615 ..Default::default()
616 }),
617 );
618 map.insert(
619 SmolStr::new_static("root"),
620 LexObjectProperty::Ref(LexRef {
621 r#ref: CowStr::new_static("com.atproto.repo.strongRef"),
622 ..Default::default()
623 }),
624 );
625 map
626 },
627 ..Default::default()
628 }),
629 );
630 map
631 },
632 ..Default::default()
633 }
634}
635
636pub mod reply_ref_state {
637
638 pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
639 #[allow(unused)]
640 use ::core::marker::PhantomData;
641 mod sealed {
642 pub trait Sealed {}
643 }
644 pub trait State: sealed::Sealed {
646 type Root;
647 type Parent;
648 }
649 pub struct Empty(());
651 impl sealed::Sealed for Empty {}
652 impl State for Empty {
653 type Root = Unset;
654 type Parent = Unset;
655 }
656 pub struct SetRoot<S: State = Empty>(PhantomData<fn() -> S>);
658 impl<S: State> sealed::Sealed for SetRoot<S> {}
659 impl<S: State> State for SetRoot<S> {
660 type Root = Set<members::root>;
661 type Parent = S::Parent;
662 }
663 pub struct SetParent<S: State = Empty>(PhantomData<fn() -> S>);
665 impl<S: State> sealed::Sealed for SetParent<S> {}
666 impl<S: State> State for SetParent<S> {
667 type Root = S::Root;
668 type Parent = Set<members::parent>;
669 }
670 #[allow(non_camel_case_types)]
672 pub mod members {
673 pub struct root(());
675 pub struct parent(());
677 }
678}
679
680pub struct ReplyRefBuilder<'a, S: reply_ref_state::State> {
682 _state: PhantomData<fn() -> S>,
683 _fields: (Option<StrongRef<'a>>, Option<StrongRef<'a>>),
684 _lifetime: PhantomData<&'a ()>,
685}
686
687impl<'a> ReplyRef<'a> {
688 pub fn new() -> ReplyRefBuilder<'a, reply_ref_state::Empty> {
690 ReplyRefBuilder::new()
691 }
692}
693
694impl<'a> ReplyRefBuilder<'a, reply_ref_state::Empty> {
695 pub fn new() -> Self {
697 ReplyRefBuilder {
698 _state: PhantomData,
699 _fields: (None, None),
700 _lifetime: PhantomData,
701 }
702 }
703}
704
705impl<'a, S> ReplyRefBuilder<'a, S>
706where
707 S: reply_ref_state::State,
708 S::Parent: reply_ref_state::IsUnset,
709{
710 pub fn parent(
712 mut self,
713 value: impl Into<StrongRef<'a>>,
714 ) -> ReplyRefBuilder<'a, reply_ref_state::SetParent<S>> {
715 self._fields.0 = Option::Some(value.into());
716 ReplyRefBuilder {
717 _state: PhantomData,
718 _fields: self._fields,
719 _lifetime: PhantomData,
720 }
721 }
722}
723
724impl<'a, S> ReplyRefBuilder<'a, S>
725where
726 S: reply_ref_state::State,
727 S::Root: reply_ref_state::IsUnset,
728{
729 pub fn root(
731 mut self,
732 value: impl Into<StrongRef<'a>>,
733 ) -> ReplyRefBuilder<'a, reply_ref_state::SetRoot<S>> {
734 self._fields.1 = Option::Some(value.into());
735 ReplyRefBuilder {
736 _state: PhantomData,
737 _fields: self._fields,
738 _lifetime: PhantomData,
739 }
740 }
741}
742
743impl<'a, S> ReplyRefBuilder<'a, S>
744where
745 S: reply_ref_state::State,
746 S::Root: reply_ref_state::IsSet,
747 S::Parent: reply_ref_state::IsSet,
748{
749 pub fn build(self) -> ReplyRef<'a> {
751 ReplyRef {
752 parent: self._fields.0.unwrap(),
753 root: self._fields.1.unwrap(),
754 extra_data: Default::default(),
755 }
756 }
757 pub fn build_with_data(
759 self,
760 extra_data: BTreeMap<
761 jacquard_common::deps::smol_str::SmolStr,
762 jacquard_common::types::value::Data<'a>,
763 >,
764 ) -> ReplyRef<'a> {
765 ReplyRef {
766 parent: self._fields.0.unwrap(),
767 root: self._fields.1.unwrap(),
768 extra_data: Some(extra_data),
769 }
770 }
771}