1#[allow(unused_imports)]
9use alloc::collections::BTreeMap;
10
11#[allow(unused_imports)]
12use core::marker::PhantomData;
13use jacquard_common::{CowStr, BosStr, DefaultStr, FromStaticStr};
14
15#[allow(unused_imports)]
16use jacquard_common::deps::codegen::unicode_segmentation::UnicodeSegmentation;
17use jacquard_common::deps::smol_str::SmolStr;
18use jacquard_common::types::blob::BlobRef;
19use jacquard_common::types::collection::{Collection, RecordError};
20use jacquard_common::types::string::{AtUri, Cid, Datetime};
21use jacquard_common::types::uri::{RecordUri, UriError};
22use jacquard_common::types::value::Data;
23use jacquard_common::xrpc::XrpcResp;
24use jacquard_derive::{IntoStatic, lexicon};
25use jacquard_lexicon::lexicon::LexiconDoc;
26use jacquard_lexicon::schema::LexiconSchema;
27
28#[allow(unused_imports)]
29use jacquard_lexicon::validation::{ConstraintError, ValidationPath};
30use serde::{Serialize, Deserialize};
31use crate::app_bsky::graph::ListPurpose;
32use crate::app_bsky::richtext::facet::Facet;
33use crate::com_atproto::label::SelfLabels;
34#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)]
37#[serde(
38 rename_all = "camelCase",
39 rename = "app.bsky.graph.list",
40 tag = "$type",
41 bound(deserialize = "S: Deserialize<'de> + BosStr")
42)]
43pub struct List<S: BosStr = DefaultStr> {
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub avatar: Option<BlobRef<S>>,
46 pub created_at: Datetime,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub description: Option<S>,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub description_facets: Option<Vec<Facet<S>>>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub labels: Option<SelfLabels<S>>,
53 pub name: S,
55 pub purpose: ListPurpose<S>,
57 #[serde(flatten, default, skip_serializing_if = "Option::is_none")]
58 pub extra_data: Option<BTreeMap<SmolStr, Data<S>>>,
59}
60
61#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, IntoStatic)]
64#[serde(rename_all = "camelCase")]
65pub struct ListGetRecordOutput<S: BosStr = DefaultStr> {
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub cid: Option<Cid<S>>,
68 pub uri: AtUri<S>,
69 pub value: List<S>,
70}
71
72impl<S: BosStr> List<S> {
73 pub fn uri(uri: S) -> Result<RecordUri<S, ListRecord>, UriError> {
74 RecordUri::try_from_uri(AtUri::new(uri)?)
75 }
76}
77
78#[derive(Debug, Serialize, Deserialize)]
81pub struct ListRecord;
82impl XrpcResp for ListRecord {
83 const NSID: &'static str = "app.bsky.graph.list";
84 const ENCODING: &'static str = "application/json";
85 type Output<S: BosStr> = ListGetRecordOutput<S>;
86 type Err = RecordError;
87}
88
89impl<S: BosStr> From<ListGetRecordOutput<S>> for List<S> {
90 fn from(output: ListGetRecordOutput<S>) -> Self {
91 output.value
92 }
93}
94
95impl<S: BosStr> Collection for List<S> {
96 const NSID: &'static str = "app.bsky.graph.list";
97 type Record = ListRecord;
98}
99
100impl Collection for ListRecord {
101 const NSID: &'static str = "app.bsky.graph.list";
102 type Record = ListRecord;
103}
104
105impl<S: BosStr> LexiconSchema for List<S> {
106 fn nsid() -> &'static str {
107 "app.bsky.graph.list"
108 }
109 fn def_name() -> &'static str {
110 "main"
111 }
112 fn lexicon_doc() -> LexiconDoc<'static> {
113 lexicon_doc_app_bsky_graph_list()
114 }
115 fn validate(&self) -> Result<(), ConstraintError> {
116 if let Some(ref value) = self.avatar {
117 {
118 let size = value.blob().size;
119 if size > 1000000usize {
120 return Err(ConstraintError::BlobTooLarge {
121 path: ValidationPath::from_field("avatar"),
122 max: 1000000usize,
123 actual: size,
124 });
125 }
126 }
127 }
128 if let Some(ref value) = self.avatar {
129 {
130 let mime = value.blob().mime_type.as_str();
131 let accepted: &[&str] = &["image/png", "image/jpeg"];
132 let matched = accepted
133 .iter()
134 .any(|pattern| {
135 if *pattern == "*/*" {
136 true
137 } else if pattern.ends_with("/*") {
138 let prefix = &pattern[..pattern.len() - 2];
139 mime.starts_with(prefix)
140 && mime.as_bytes().get(prefix.len()) == Some(&b'/')
141 } else {
142 mime == *pattern
143 }
144 });
145 if !matched {
146 return Err(ConstraintError::BlobMimeTypeNotAccepted {
147 path: ValidationPath::from_field("avatar"),
148 accepted: vec![
149 "image/png".to_string(), "image/jpeg".to_string()
150 ],
151 actual: mime.to_string(),
152 });
153 }
154 }
155 }
156 if let Some(ref value) = self.description {
157 #[allow(unused_comparisons)]
158 if <str>::len(value.as_ref()) > 3000usize {
159 return Err(ConstraintError::MaxLength {
160 path: ValidationPath::from_field("description"),
161 max: 3000usize,
162 actual: <str>::len(value.as_ref()),
163 });
164 }
165 }
166 if let Some(ref value) = self.description {
167 {
168 let count = UnicodeSegmentation::graphemes(value.as_ref(), true).count();
169 if count > 300usize {
170 return Err(ConstraintError::MaxGraphemes {
171 path: ValidationPath::from_field("description"),
172 max: 300usize,
173 actual: count,
174 });
175 }
176 }
177 }
178 {
179 let value = &self.name;
180 #[allow(unused_comparisons)]
181 if <str>::len(value.as_ref()) > 64usize {
182 return Err(ConstraintError::MaxLength {
183 path: ValidationPath::from_field("name"),
184 max: 64usize,
185 actual: <str>::len(value.as_ref()),
186 });
187 }
188 }
189 {
190 let value = &self.name;
191 #[allow(unused_comparisons)]
192 if <str>::len(value.as_ref()) < 1usize {
193 return Err(ConstraintError::MinLength {
194 path: ValidationPath::from_field("name"),
195 min: 1usize,
196 actual: <str>::len(value.as_ref()),
197 });
198 }
199 }
200 Ok(())
201 }
202}
203
204pub mod list_state {
205
206 pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
207 #[allow(unused)]
208 use ::core::marker::PhantomData;
209 mod sealed {
210 pub trait Sealed {}
211 }
212 pub trait State: sealed::Sealed {
214 type CreatedAt;
215 type Name;
216 type Purpose;
217 }
218 pub struct Empty(());
220 impl sealed::Sealed for Empty {}
221 impl State for Empty {
222 type CreatedAt = Unset;
223 type Name = Unset;
224 type Purpose = Unset;
225 }
226 pub struct SetCreatedAt<St: State = Empty>(PhantomData<fn() -> St>);
228 impl<St: State> sealed::Sealed for SetCreatedAt<St> {}
229 impl<St: State> State for SetCreatedAt<St> {
230 type CreatedAt = Set<members::created_at>;
231 type Name = St::Name;
232 type Purpose = St::Purpose;
233 }
234 pub struct SetName<St: State = Empty>(PhantomData<fn() -> St>);
236 impl<St: State> sealed::Sealed for SetName<St> {}
237 impl<St: State> State for SetName<St> {
238 type CreatedAt = St::CreatedAt;
239 type Name = Set<members::name>;
240 type Purpose = St::Purpose;
241 }
242 pub struct SetPurpose<St: State = Empty>(PhantomData<fn() -> St>);
244 impl<St: State> sealed::Sealed for SetPurpose<St> {}
245 impl<St: State> State for SetPurpose<St> {
246 type CreatedAt = St::CreatedAt;
247 type Name = St::Name;
248 type Purpose = Set<members::purpose>;
249 }
250 #[allow(non_camel_case_types)]
252 pub mod members {
253 pub struct created_at(());
255 pub struct name(());
257 pub struct purpose(());
259 }
260}
261
262pub struct ListBuilder<St: list_state::State, S: BosStr = DefaultStr> {
264 _state: PhantomData<fn() -> St>,
265 _fields: (
266 Option<BlobRef<S>>,
267 Option<Datetime>,
268 Option<S>,
269 Option<Vec<Facet<S>>>,
270 Option<SelfLabels<S>>,
271 Option<S>,
272 Option<ListPurpose<S>>,
273 ),
274 _type: PhantomData<fn() -> S>,
275}
276
277impl List<DefaultStr> {
278 pub fn new() -> ListBuilder<list_state::Empty, DefaultStr> {
280 ListBuilder::new()
281 }
282}
283
284impl<S: BosStr> List<S> {
285 pub fn builder() -> ListBuilder<list_state::Empty, S> {
287 ListBuilder::builder()
288 }
289}
290
291impl ListBuilder<list_state::Empty, DefaultStr> {
292 pub fn new() -> Self {
294 ListBuilder {
295 _state: PhantomData,
296 _fields: (None, None, None, None, None, None, None),
297 _type: PhantomData,
298 }
299 }
300}
301
302impl<S: BosStr> ListBuilder<list_state::Empty, S> {
303 pub fn builder() -> Self {
305 ListBuilder {
306 _state: PhantomData,
307 _fields: (None, None, None, None, None, None, None),
308 _type: PhantomData,
309 }
310 }
311}
312
313impl<St: list_state::State, S: BosStr> ListBuilder<St, S> {
314 pub fn avatar(mut self, value: impl Into<Option<BlobRef<S>>>) -> Self {
316 self._fields.0 = value.into();
317 self
318 }
319 pub fn maybe_avatar(mut self, value: Option<BlobRef<S>>) -> Self {
321 self._fields.0 = value;
322 self
323 }
324}
325
326impl<St, S: BosStr> ListBuilder<St, S>
327where
328 St: list_state::State,
329 St::CreatedAt: list_state::IsUnset,
330{
331 pub fn created_at(
333 mut self,
334 value: impl Into<Datetime>,
335 ) -> ListBuilder<list_state::SetCreatedAt<St>, S> {
336 self._fields.1 = Option::Some(value.into());
337 ListBuilder {
338 _state: PhantomData,
339 _fields: self._fields,
340 _type: PhantomData,
341 }
342 }
343}
344
345impl<St: list_state::State, S: BosStr> ListBuilder<St, S> {
346 pub fn description(mut self, value: impl Into<Option<S>>) -> Self {
348 self._fields.2 = value.into();
349 self
350 }
351 pub fn maybe_description(mut self, value: Option<S>) -> Self {
353 self._fields.2 = value;
354 self
355 }
356}
357
358impl<St: list_state::State, S: BosStr> ListBuilder<St, S> {
359 pub fn description_facets(
361 mut self,
362 value: impl Into<Option<Vec<Facet<S>>>>,
363 ) -> Self {
364 self._fields.3 = value.into();
365 self
366 }
367 pub fn maybe_description_facets(mut self, value: Option<Vec<Facet<S>>>) -> Self {
369 self._fields.3 = value;
370 self
371 }
372}
373
374impl<St: list_state::State, S: BosStr> ListBuilder<St, S> {
375 pub fn labels(mut self, value: impl Into<Option<SelfLabels<S>>>) -> Self {
377 self._fields.4 = value.into();
378 self
379 }
380 pub fn maybe_labels(mut self, value: Option<SelfLabels<S>>) -> Self {
382 self._fields.4 = value;
383 self
384 }
385}
386
387impl<St, S: BosStr> ListBuilder<St, S>
388where
389 St: list_state::State,
390 St::Name: list_state::IsUnset,
391{
392 pub fn name(
394 mut self,
395 value: impl Into<S>,
396 ) -> ListBuilder<list_state::SetName<St>, S> {
397 self._fields.5 = Option::Some(value.into());
398 ListBuilder {
399 _state: PhantomData,
400 _fields: self._fields,
401 _type: PhantomData,
402 }
403 }
404}
405
406impl<St, S: BosStr> ListBuilder<St, S>
407where
408 St: list_state::State,
409 St::Purpose: list_state::IsUnset,
410{
411 pub fn purpose(
413 mut self,
414 value: impl Into<ListPurpose<S>>,
415 ) -> ListBuilder<list_state::SetPurpose<St>, S> {
416 self._fields.6 = Option::Some(value.into());
417 ListBuilder {
418 _state: PhantomData,
419 _fields: self._fields,
420 _type: PhantomData,
421 }
422 }
423}
424
425impl<St, S: BosStr> ListBuilder<St, S>
426where
427 St: list_state::State,
428 St::CreatedAt: list_state::IsSet,
429 St::Name: list_state::IsSet,
430 St::Purpose: list_state::IsSet,
431{
432 pub fn build(self) -> List<S> {
434 List {
435 avatar: self._fields.0,
436 created_at: self._fields.1.unwrap(),
437 description: self._fields.2,
438 description_facets: self._fields.3,
439 labels: self._fields.4,
440 name: self._fields.5.unwrap(),
441 purpose: self._fields.6.unwrap(),
442 extra_data: Default::default(),
443 }
444 }
445 pub fn build_with_data(self, extra_data: BTreeMap<SmolStr, Data<S>>) -> List<S> {
447 List {
448 avatar: self._fields.0,
449 created_at: self._fields.1.unwrap(),
450 description: self._fields.2,
451 description_facets: self._fields.3,
452 labels: self._fields.4,
453 name: self._fields.5.unwrap(),
454 purpose: self._fields.6.unwrap(),
455 extra_data: Some(extra_data),
456 }
457 }
458}
459
460fn lexicon_doc_app_bsky_graph_list() -> LexiconDoc<'static> {
461 #[allow(unused_imports)]
462 use jacquard_common::{CowStr, deps::smol_str::SmolStr, types::blob::MimeType};
463 use jacquard_lexicon::lexicon::*;
464 use alloc::collections::BTreeMap;
465 LexiconDoc {
466 lexicon: Lexicon::Lexicon1,
467 id: CowStr::new_static("app.bsky.graph.list"),
468 defs: {
469 let mut map = BTreeMap::new();
470 map.insert(
471 SmolStr::new_static("main"),
472 LexUserType::Record(LexRecord {
473 description: Some(
474 CowStr::new_static(
475 "Record representing a list of accounts (actors). Scope includes both moderation-oriented lists and curration-oriented lists.",
476 ),
477 ),
478 key: Some(CowStr::new_static("tid")),
479 record: LexRecordRecord::Object(LexObject {
480 required: Some(
481 vec![
482 SmolStr::new_static("name"), SmolStr::new_static("purpose"),
483 SmolStr::new_static("createdAt")
484 ],
485 ),
486 properties: {
487 #[allow(unused_mut)]
488 let mut map = BTreeMap::new();
489 map.insert(
490 SmolStr::new_static("avatar"),
491 LexObjectProperty::Blob(LexBlob { ..Default::default() }),
492 );
493 map.insert(
494 SmolStr::new_static("createdAt"),
495 LexObjectProperty::String(LexString {
496 format: Some(LexStringFormat::Datetime),
497 ..Default::default()
498 }),
499 );
500 map.insert(
501 SmolStr::new_static("description"),
502 LexObjectProperty::String(LexString {
503 max_length: Some(3000usize),
504 max_graphemes: Some(300usize),
505 ..Default::default()
506 }),
507 );
508 map.insert(
509 SmolStr::new_static("descriptionFacets"),
510 LexObjectProperty::Array(LexArray {
511 items: LexArrayItem::Ref(LexRef {
512 r#ref: CowStr::new_static("app.bsky.richtext.facet"),
513 ..Default::default()
514 }),
515 ..Default::default()
516 }),
517 );
518 map.insert(
519 SmolStr::new_static("labels"),
520 LexObjectProperty::Union(LexRefUnion {
521 refs: vec![
522 CowStr::new_static("com.atproto.label.defs#selfLabels")
523 ],
524 ..Default::default()
525 }),
526 );
527 map.insert(
528 SmolStr::new_static("name"),
529 LexObjectProperty::String(LexString {
530 description: Some(
531 CowStr::new_static(
532 "Display name for list; can not be empty.",
533 ),
534 ),
535 min_length: Some(1usize),
536 max_length: Some(64usize),
537 ..Default::default()
538 }),
539 );
540 map.insert(
541 SmolStr::new_static("purpose"),
542 LexObjectProperty::Ref(LexRef {
543 r#ref: CowStr::new_static(
544 "app.bsky.graph.defs#listPurpose",
545 ),
546 ..Default::default()
547 }),
548 );
549 map
550 },
551 ..Default::default()
552 }),
553 ..Default::default()
554 }),
555 );
556 map
557 },
558 ..Default::default()
559 }
560}