1#[allow(unused_imports)]
9use alloc::collections::BTreeMap;
10
11#[allow(unused_imports)]
12use core::marker::PhantomData;
13use jacquard_common::{BosStr, CowStr, 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
28use crate::app_bsky::graph::ListPurpose;
29use crate::app_bsky::richtext::facet::Facet;
30use crate::com_atproto::label::SelfLabels;
31#[allow(unused_imports)]
32use jacquard_lexicon::validation::{ConstraintError, ValidationPath};
33use serde::{Deserialize, Serialize};
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.iter().any(|pattern| {
133 if *pattern == "*/*" {
134 true
135 } else if pattern.ends_with("/*") {
136 let prefix = &pattern[..pattern.len() - 2];
137 mime.starts_with(prefix) && mime.as_bytes().get(prefix.len()) == Some(&b'/')
138 } else {
139 mime == *pattern
140 }
141 });
142 if !matched {
143 return Err(ConstraintError::BlobMimeTypeNotAccepted {
144 path: ValidationPath::from_field("avatar"),
145 accepted: vec!["image/png".to_string(), "image/jpeg".to_string()],
146 actual: mime.to_string(),
147 });
148 }
149 }
150 }
151 if let Some(ref value) = self.description {
152 #[allow(unused_comparisons)]
153 if <str>::len(value.as_ref()) > 3000usize {
154 return Err(ConstraintError::MaxLength {
155 path: ValidationPath::from_field("description"),
156 max: 3000usize,
157 actual: <str>::len(value.as_ref()),
158 });
159 }
160 }
161 if let Some(ref value) = self.description {
162 {
163 let count = UnicodeSegmentation::graphemes(value.as_ref(), true).count();
164 if count > 300usize {
165 return Err(ConstraintError::MaxGraphemes {
166 path: ValidationPath::from_field("description"),
167 max: 300usize,
168 actual: count,
169 });
170 }
171 }
172 }
173 {
174 let value = &self.name;
175 #[allow(unused_comparisons)]
176 if <str>::len(value.as_ref()) > 64usize {
177 return Err(ConstraintError::MaxLength {
178 path: ValidationPath::from_field("name"),
179 max: 64usize,
180 actual: <str>::len(value.as_ref()),
181 });
182 }
183 }
184 {
185 let value = &self.name;
186 #[allow(unused_comparisons)]
187 if <str>::len(value.as_ref()) < 1usize {
188 return Err(ConstraintError::MinLength {
189 path: ValidationPath::from_field("name"),
190 min: 1usize,
191 actual: <str>::len(value.as_ref()),
192 });
193 }
194 }
195 Ok(())
196 }
197}
198
199pub mod list_state {
200
201 pub use crate::builder_types::{IsSet, IsUnset, Set, Unset};
202 #[allow(unused)]
203 use ::core::marker::PhantomData;
204 mod sealed {
205 pub trait Sealed {}
206 }
207 pub trait State: sealed::Sealed {
209 type CreatedAt;
210 type Name;
211 type Purpose;
212 }
213 pub struct Empty(());
215 impl sealed::Sealed for Empty {}
216 impl State for Empty {
217 type CreatedAt = Unset;
218 type Name = Unset;
219 type Purpose = Unset;
220 }
221 pub struct SetCreatedAt<St: State = Empty>(PhantomData<fn() -> St>);
223 impl<St: State> sealed::Sealed for SetCreatedAt<St> {}
224 impl<St: State> State for SetCreatedAt<St> {
225 type CreatedAt = Set<members::created_at>;
226 type Name = St::Name;
227 type Purpose = St::Purpose;
228 }
229 pub struct SetName<St: State = Empty>(PhantomData<fn() -> St>);
231 impl<St: State> sealed::Sealed for SetName<St> {}
232 impl<St: State> State for SetName<St> {
233 type CreatedAt = St::CreatedAt;
234 type Name = Set<members::name>;
235 type Purpose = St::Purpose;
236 }
237 pub struct SetPurpose<St: State = Empty>(PhantomData<fn() -> St>);
239 impl<St: State> sealed::Sealed for SetPurpose<St> {}
240 impl<St: State> State for SetPurpose<St> {
241 type CreatedAt = St::CreatedAt;
242 type Name = St::Name;
243 type Purpose = Set<members::purpose>;
244 }
245 #[allow(non_camel_case_types)]
247 pub mod members {
248 pub struct created_at(());
250 pub struct name(());
252 pub struct purpose(());
254 }
255}
256
257pub struct ListBuilder<St: list_state::State, S: BosStr = DefaultStr> {
259 _state: PhantomData<fn() -> St>,
260 _fields: (
261 Option<BlobRef<S>>,
262 Option<Datetime>,
263 Option<S>,
264 Option<Vec<Facet<S>>>,
265 Option<SelfLabels<S>>,
266 Option<S>,
267 Option<ListPurpose<S>>,
268 ),
269 _type: PhantomData<fn() -> S>,
270}
271
272impl List<DefaultStr> {
273 pub fn new() -> ListBuilder<list_state::Empty, DefaultStr> {
275 ListBuilder::new()
276 }
277}
278
279impl<S: BosStr> List<S> {
280 pub fn builder() -> ListBuilder<list_state::Empty, S> {
282 ListBuilder::builder()
283 }
284}
285
286impl ListBuilder<list_state::Empty, DefaultStr> {
287 pub fn new() -> Self {
289 ListBuilder {
290 _state: PhantomData,
291 _fields: (None, None, None, None, None, None, None),
292 _type: PhantomData,
293 }
294 }
295}
296
297impl<S: BosStr> ListBuilder<list_state::Empty, S> {
298 pub fn builder() -> Self {
300 ListBuilder {
301 _state: PhantomData,
302 _fields: (None, None, None, None, None, None, None),
303 _type: PhantomData,
304 }
305 }
306}
307
308impl<St: list_state::State, S: BosStr> ListBuilder<St, S> {
309 pub fn avatar(mut self, value: impl Into<Option<BlobRef<S>>>) -> Self {
311 self._fields.0 = value.into();
312 self
313 }
314 pub fn maybe_avatar(mut self, value: Option<BlobRef<S>>) -> Self {
316 self._fields.0 = value;
317 self
318 }
319}
320
321impl<St, S: BosStr> ListBuilder<St, S>
322where
323 St: list_state::State,
324 St::CreatedAt: list_state::IsUnset,
325{
326 pub fn created_at(
328 mut self,
329 value: impl Into<Datetime>,
330 ) -> ListBuilder<list_state::SetCreatedAt<St>, S> {
331 self._fields.1 = Option::Some(value.into());
332 ListBuilder {
333 _state: PhantomData,
334 _fields: self._fields,
335 _type: PhantomData,
336 }
337 }
338}
339
340impl<St: list_state::State, S: BosStr> ListBuilder<St, S> {
341 pub fn description(mut self, value: impl Into<Option<S>>) -> Self {
343 self._fields.2 = value.into();
344 self
345 }
346 pub fn maybe_description(mut self, value: Option<S>) -> Self {
348 self._fields.2 = value;
349 self
350 }
351}
352
353impl<St: list_state::State, S: BosStr> ListBuilder<St, S> {
354 pub fn description_facets(mut self, value: impl Into<Option<Vec<Facet<S>>>>) -> Self {
356 self._fields.3 = value.into();
357 self
358 }
359 pub fn maybe_description_facets(mut self, value: Option<Vec<Facet<S>>>) -> Self {
361 self._fields.3 = value;
362 self
363 }
364}
365
366impl<St: list_state::State, S: BosStr> ListBuilder<St, S> {
367 pub fn labels(mut self, value: impl Into<Option<SelfLabels<S>>>) -> Self {
369 self._fields.4 = value.into();
370 self
371 }
372 pub fn maybe_labels(mut self, value: Option<SelfLabels<S>>) -> Self {
374 self._fields.4 = value;
375 self
376 }
377}
378
379impl<St, S: BosStr> ListBuilder<St, S>
380where
381 St: list_state::State,
382 St::Name: list_state::IsUnset,
383{
384 pub fn name(mut self, value: impl Into<S>) -> ListBuilder<list_state::SetName<St>, S> {
386 self._fields.5 = Option::Some(value.into());
387 ListBuilder {
388 _state: PhantomData,
389 _fields: self._fields,
390 _type: PhantomData,
391 }
392 }
393}
394
395impl<St, S: BosStr> ListBuilder<St, S>
396where
397 St: list_state::State,
398 St::Purpose: list_state::IsUnset,
399{
400 pub fn purpose(
402 mut self,
403 value: impl Into<ListPurpose<S>>,
404 ) -> ListBuilder<list_state::SetPurpose<St>, S> {
405 self._fields.6 = Option::Some(value.into());
406 ListBuilder {
407 _state: PhantomData,
408 _fields: self._fields,
409 _type: PhantomData,
410 }
411 }
412}
413
414impl<St, S: BosStr> ListBuilder<St, S>
415where
416 St: list_state::State,
417 St::CreatedAt: list_state::IsSet,
418 St::Name: list_state::IsSet,
419 St::Purpose: list_state::IsSet,
420{
421 pub fn build(self) -> List<S> {
423 List {
424 avatar: self._fields.0,
425 created_at: self._fields.1.unwrap(),
426 description: self._fields.2,
427 description_facets: self._fields.3,
428 labels: self._fields.4,
429 name: self._fields.5.unwrap(),
430 purpose: self._fields.6.unwrap(),
431 extra_data: Default::default(),
432 }
433 }
434 pub fn build_with_data(self, extra_data: BTreeMap<SmolStr, Data<S>>) -> List<S> {
436 List {
437 avatar: self._fields.0,
438 created_at: self._fields.1.unwrap(),
439 description: self._fields.2,
440 description_facets: self._fields.3,
441 labels: self._fields.4,
442 name: self._fields.5.unwrap(),
443 purpose: self._fields.6.unwrap(),
444 extra_data: Some(extra_data),
445 }
446 }
447}
448
449fn lexicon_doc_app_bsky_graph_list() -> LexiconDoc<'static> {
450 use alloc::collections::BTreeMap;
451 #[allow(unused_imports)]
452 use jacquard_common::{CowStr, deps::smol_str::SmolStr, types::blob::MimeType};
453 use jacquard_lexicon::lexicon::*;
454 LexiconDoc {
455 lexicon: Lexicon::Lexicon1,
456 id: CowStr::new_static("app.bsky.graph.list"),
457 defs: {
458 let mut map = BTreeMap::new();
459 map.insert(
460 SmolStr::new_static("main"),
461 LexUserType::Record(LexRecord {
462 description: Some(
463 CowStr::new_static(
464 "Record representing a list of accounts (actors). Scope includes both moderation-oriented lists and curration-oriented lists.",
465 ),
466 ),
467 key: Some(CowStr::new_static("tid")),
468 record: LexRecordRecord::Object(LexObject {
469 required: Some(
470 vec![
471 SmolStr::new_static("name"), SmolStr::new_static("purpose"),
472 SmolStr::new_static("createdAt")
473 ],
474 ),
475 properties: {
476 #[allow(unused_mut)]
477 let mut map = BTreeMap::new();
478 map.insert(
479 SmolStr::new_static("avatar"),
480 LexObjectProperty::Blob(LexBlob { ..Default::default() }),
481 );
482 map.insert(
483 SmolStr::new_static("createdAt"),
484 LexObjectProperty::String(LexString {
485 format: Some(LexStringFormat::Datetime),
486 ..Default::default()
487 }),
488 );
489 map.insert(
490 SmolStr::new_static("description"),
491 LexObjectProperty::String(LexString {
492 max_length: Some(3000usize),
493 max_graphemes: Some(300usize),
494 ..Default::default()
495 }),
496 );
497 map.insert(
498 SmolStr::new_static("descriptionFacets"),
499 LexObjectProperty::Array(LexArray {
500 items: LexArrayItem::Ref(LexRef {
501 r#ref: CowStr::new_static("app.bsky.richtext.facet"),
502 ..Default::default()
503 }),
504 ..Default::default()
505 }),
506 );
507 map.insert(
508 SmolStr::new_static("labels"),
509 LexObjectProperty::Union(LexRefUnion {
510 refs: vec![
511 CowStr::new_static("com.atproto.label.defs#selfLabels")
512 ],
513 ..Default::default()
514 }),
515 );
516 map.insert(
517 SmolStr::new_static("name"),
518 LexObjectProperty::String(LexString {
519 description: Some(
520 CowStr::new_static(
521 "Display name for list; can not be empty.",
522 ),
523 ),
524 min_length: Some(1usize),
525 max_length: Some(64usize),
526 ..Default::default()
527 }),
528 );
529 map.insert(
530 SmolStr::new_static("purpose"),
531 LexObjectProperty::Ref(LexRef {
532 r#ref: CowStr::new_static(
533 "app.bsky.graph.defs#listPurpose",
534 ),
535 ..Default::default()
536 }),
537 );
538 map
539 },
540 ..Default::default()
541 }),
542 ..Default::default()
543 }),
544 );
545 map
546 },
547 ..Default::default()
548 }
549}