+15
-4
codegen_plan.md
+15
-4
codegen_plan.md
···
129
129
130
130
**Tasks**:
131
131
1. Create `LexiconCorpus` struct
132
-
- `HashMap<SmolStr, LexiconDoc<'static>>` - NSID → doc
132
+
- `BTreeMap<SmolStr, LexiconDoc<'static>>` - NSID → doc
133
133
- Methods: `load_from_dir()`, `get()`, `resolve_ref()`
134
134
2. Load all `.json` files from lexicon directory
135
135
3. Parse into `LexiconDoc` and insert into registry
···
261
261
// ... fields
262
262
}
263
263
264
-
impl Collection for Post<'_> {
264
+
impl Collection for Post<'p> {
265
265
const NSID: &'static str = "app.bsky.feed.post";
266
-
type Record = Post<'static>;
266
+
type Record = Post<'p>;
267
267
}
268
268
```
269
269
···
275
275
/// The NSID for this XRPC method
276
276
const NSID: &'static str;
277
277
278
-
/// HTTP method (GET for queries, POST for procedures)
278
+
/// XRPC method (query/GET, procedure/POST)
279
279
const METHOD: XrpcMethod;
280
280
281
281
/// Input encoding (MIME type, e.g., "application/json")
···
290
290
291
291
/// Response output type
292
292
type Output: Deserialize<'x>;
293
+
294
+
type Err: Error;
293
295
}
294
296
295
297
pub enum XrpcMethod {
···
298
300
}
299
301
```
300
302
303
+
304
+
301
305
**Generated implementation:**
302
306
```rust
303
307
pub struct GetAuthorFeedParams<'a> {
···
319
323
320
324
type Params = Self;
321
325
type Output = GetAuthorFeedOutput<'static>;
326
+
type Err = GetAuthorFeedError;
322
327
}
323
328
```
324
329
···
332
337
- Allows monomorphization (static dispatch) for performance
333
338
- Also supports `dyn XrpcRequest` for dynamic dispatch if needed
334
339
- Client code can be generic over `impl XrpcRequest`
340
+
341
+
342
+
#### XRPC Errors
343
+
Lexicons contain information on the kind of errors they can return.
344
+
Trait contains an associated error type. Error enum with thiserror::Error and
345
+
miette:Diagnostic derives and appropriate content generated based on lexicon info.
335
346
336
347
### Subscriptions
337
348
WebSocket streams - defer for now. Will need separate trait with message types.
+34
-1
crates/jacquard-common/src/into_static.rs
+34
-1
crates/jacquard-common/src/into_static.rs
···
1
1
use std::borrow::Cow;
2
+
use std::collections::BTreeMap;
2
3
use std::collections::HashMap;
3
4
use std::collections::HashSet;
4
5
use std::collections::VecDeque;
···
67
68
}
68
69
69
70
impl_into_static_passthru!(
70
-
String, u128, u64, u32, u16, u8, i128, i64, i32, i16, i8, bool, char, usize, isize, f32, f64
71
+
String,
72
+
u128,
73
+
u64,
74
+
u32,
75
+
u16,
76
+
u8,
77
+
i128,
78
+
i64,
79
+
i32,
80
+
i16,
81
+
i8,
82
+
bool,
83
+
char,
84
+
usize,
85
+
isize,
86
+
f32,
87
+
f64,
88
+
crate::smol_str::SmolStr
71
89
);
72
90
73
91
impl<T: IntoStatic> IntoStatic for Box<T> {
···
111
129
K::Output: Eq + Hash,
112
130
{
113
131
type Output = HashMap<K::Output, V::Output, S>;
132
+
133
+
fn into_static(self) -> Self::Output {
134
+
self.into_iter()
135
+
.map(|(k, v)| (k.into_static(), v.into_static()))
136
+
.collect()
137
+
}
138
+
}
139
+
140
+
impl<K, V> IntoStatic for BTreeMap<K, V>
141
+
where
142
+
K: IntoStatic + Ord,
143
+
V: IntoStatic,
144
+
K::Output: Ord,
145
+
{
146
+
type Output = BTreeMap<K::Output, V::Output>;
114
147
115
148
fn into_static(self) -> Self::Output {
116
149
self.into_iter()
+7
-11
crates/jacquard-common/src/types/value/convert.rs
+7
-11
crates/jacquard-common/src/types/value/convert.rs
···
1
-
use core::{any::TypeId, fmt};
2
-
use std::{borrow::ToOwned, boxed::Box, collections::BTreeMap, vec::Vec};
3
-
4
-
use crate::{
5
-
CowStr,
6
-
types::{
7
-
DataModelType,
8
-
cid::Cid,
9
-
string::AtprotoStr,
10
-
value::{Array, Data, Object},
11
-
},
1
+
use crate::types::{
2
+
DataModelType,
3
+
cid::Cid,
4
+
string::AtprotoStr,
5
+
value::{Array, Data, Object},
12
6
};
13
7
use bytes::Bytes;
8
+
use core::{any::TypeId, fmt};
14
9
use smol_str::SmolStr;
10
+
use std::{borrow::ToOwned, boxed::Box, collections::BTreeMap, vec::Vec};
15
11
16
12
/// Error used for converting from and into [`crate::types::value::Data`].
17
13
#[derive(Clone, Debug)]
+162
crates/jacquard-lexicon/src/corpus.rs
+162
crates/jacquard-lexicon/src/corpus.rs
···
1
+
use crate::lexicon::{LexiconDoc, LexUserType};
2
+
use jacquard_common::{into_static::IntoStatic, smol_str::SmolStr};
3
+
use std::collections::BTreeMap;
4
+
use std::fs;
5
+
use std::io;
6
+
use std::path::Path;
7
+
8
+
/// Registry of all loaded lexicons for reference resolution
9
+
#[derive(Debug, Clone)]
10
+
pub struct LexiconCorpus {
11
+
/// Map from NSID to lexicon document
12
+
docs: BTreeMap<SmolStr, LexiconDoc<'static>>,
13
+
}
14
+
15
+
impl LexiconCorpus {
16
+
/// Create an empty corpus
17
+
pub fn new() -> Self {
18
+
Self {
19
+
docs: BTreeMap::new(),
20
+
}
21
+
}
22
+
23
+
/// Load all lexicons from a directory
24
+
pub fn load_from_dir(path: impl AsRef<Path>) -> io::Result<Self> {
25
+
let mut corpus = Self::new();
26
+
27
+
let schemas = crate::fs::find_schemas(path.as_ref())?;
28
+
for schema_path in schemas {
29
+
let content = fs::read_to_string(schema_path.as_ref())?;
30
+
let doc: LexiconDoc = serde_json::from_str(&content).map_err(|e| {
31
+
io::Error::new(
32
+
io::ErrorKind::InvalidData,
33
+
format!("Failed to parse {}: {}", schema_path.as_ref().display(), e),
34
+
)
35
+
})?;
36
+
37
+
let nsid = SmolStr::from(doc.id.to_string());
38
+
corpus.docs.insert(nsid, doc.into_static());
39
+
}
40
+
41
+
Ok(corpus)
42
+
}
43
+
44
+
/// Get a lexicon document by NSID
45
+
pub fn get(&self, nsid: &str) -> Option<&LexiconDoc<'static>> {
46
+
self.docs.get(nsid)
47
+
}
48
+
49
+
/// Resolve a reference, handling fragments
50
+
///
51
+
/// Examples:
52
+
/// - `app.bsky.feed.post` → main def from that lexicon
53
+
/// - `app.bsky.feed.post#replyRef` → replyRef def from that lexicon
54
+
pub fn resolve_ref(&self, ref_str: &str) -> Option<(&LexiconDoc<'static>, &LexUserType<'static>)> {
55
+
let (nsid, def_name) = if let Some((nsid, fragment)) = ref_str.split_once('#') {
56
+
(nsid, fragment)
57
+
} else {
58
+
(ref_str, "main")
59
+
};
60
+
61
+
let doc = self.get(nsid)?;
62
+
let def = doc.defs.get(def_name)?;
63
+
Some((doc, def))
64
+
}
65
+
66
+
/// Check if a reference exists
67
+
pub fn ref_exists(&self, ref_str: &str) -> bool {
68
+
self.resolve_ref(ref_str).is_some()
69
+
}
70
+
71
+
/// Iterate over all documents
72
+
pub fn iter(&self) -> impl Iterator<Item = (&SmolStr, &LexiconDoc<'static>)> {
73
+
self.docs.iter()
74
+
}
75
+
76
+
/// Number of loaded lexicons
77
+
pub fn len(&self) -> usize {
78
+
self.docs.len()
79
+
}
80
+
81
+
/// Check if corpus is empty
82
+
pub fn is_empty(&self) -> bool {
83
+
self.docs.is_empty()
84
+
}
85
+
}
86
+
87
+
impl Default for LexiconCorpus {
88
+
fn default() -> Self {
89
+
Self::new()
90
+
}
91
+
}
92
+
93
+
#[cfg(test)]
94
+
mod tests {
95
+
use super::*;
96
+
use crate::lexicon::LexUserType;
97
+
98
+
#[test]
99
+
fn test_empty_corpus() {
100
+
let corpus = LexiconCorpus::new();
101
+
assert!(corpus.is_empty());
102
+
assert_eq!(corpus.len(), 0);
103
+
}
104
+
105
+
#[test]
106
+
fn test_load_real_lexicons() {
107
+
let corpus = LexiconCorpus::load_from_dir("tests/fixtures/lexicons")
108
+
.expect("failed to load lexicons");
109
+
110
+
assert!(!corpus.is_empty());
111
+
assert_eq!(corpus.len(), 10);
112
+
113
+
// Check that we loaded the expected lexicons
114
+
assert!(corpus.get("app.bsky.feed.post").is_some());
115
+
assert!(corpus.get("app.bsky.feed.getAuthorFeed").is_some());
116
+
assert!(corpus.get("app.bsky.richtext.facet").is_some());
117
+
assert!(corpus.get("app.bsky.embed.images").is_some());
118
+
assert!(corpus.get("com.atproto.repo.strongRef").is_some());
119
+
assert!(corpus.get("com.atproto.label.defs").is_some());
120
+
}
121
+
122
+
#[test]
123
+
fn test_resolve_ref_without_fragment() {
124
+
let corpus = LexiconCorpus::load_from_dir("tests/fixtures/lexicons")
125
+
.expect("failed to load lexicons");
126
+
127
+
// Without fragment should resolve to main def
128
+
let (doc, def) = corpus
129
+
.resolve_ref("app.bsky.feed.post")
130
+
.expect("should resolve");
131
+
assert_eq!(doc.id.as_ref(), "app.bsky.feed.post");
132
+
assert!(matches!(def, LexUserType::Record(_)));
133
+
}
134
+
135
+
#[test]
136
+
fn test_resolve_ref_with_fragment() {
137
+
let corpus = LexiconCorpus::load_from_dir("tests/fixtures/lexicons")
138
+
.expect("failed to load lexicons");
139
+
140
+
// With fragment should resolve to specific def
141
+
let (doc, def) = corpus
142
+
.resolve_ref("app.bsky.richtext.facet#mention")
143
+
.expect("should resolve");
144
+
assert_eq!(doc.id.as_ref(), "app.bsky.richtext.facet");
145
+
assert!(matches!(def, LexUserType::Object(_)));
146
+
}
147
+
148
+
#[test]
149
+
fn test_ref_exists() {
150
+
let corpus = LexiconCorpus::load_from_dir("tests/fixtures/lexicons")
151
+
.expect("failed to load lexicons");
152
+
153
+
// Existing refs
154
+
assert!(corpus.ref_exists("app.bsky.feed.post"));
155
+
assert!(corpus.ref_exists("app.bsky.feed.post#main"));
156
+
assert!(corpus.ref_exists("app.bsky.richtext.facet#mention"));
157
+
158
+
// Non-existing refs
159
+
assert!(!corpus.ref_exists("com.example.fake"));
160
+
assert!(!corpus.ref_exists("app.bsky.feed.post#nonexistent"));
161
+
}
162
+
}
+433
-1
crates/jacquard-lexicon/src/lexicon.rs
+433
-1
crates/jacquard-lexicon/src/lexicon.rs
···
2
2
// https://github.com/atrium-rs/atrium/blob/main/lexicon/atrium-lex/src/lexicon.rs
3
3
// https://github.com/atrium-rs/atrium/blob/main/lexicon/atrium-lex/src/lib.rs
4
4
5
-
use jacquard_common::{CowStr, smol_str::SmolStr, types::blob::MimeType};
5
+
use jacquard_common::{into_static::IntoStatic, smol_str::SmolStr, types::blob::MimeType, CowStr};
6
6
use serde::{Deserialize, Serialize};
7
7
use serde_repr::{Deserialize_repr, Serialize_repr};
8
8
use serde_with::skip_serializing_none;
···
402
402
CidLink(LexCidLink<'s>),
403
403
// lexUnknown
404
404
Unknown(LexUnknown<'s>),
405
+
}
406
+
407
+
// IntoStatic implementations for all lexicon types
408
+
// These enable converting borrowed lexicon docs to owned 'static versions
409
+
410
+
macro_rules! impl_into_static_for_lex_struct {
411
+
($($ty:ident),+ $(,)?) => {
412
+
$(
413
+
impl IntoStatic for $ty<'_> {
414
+
type Output = $ty<'static>;
415
+
416
+
fn into_static(self) -> Self::Output {
417
+
let Self {
418
+
$(description,)?
419
+
..$fields
420
+
} = self;
421
+
Self::Output {
422
+
$(description: description.into_static(),)?
423
+
..$fields.into_static()
424
+
}
425
+
}
426
+
}
427
+
)+
428
+
};
429
+
}
430
+
431
+
// Simpler approach: just clone and convert each field
432
+
impl IntoStatic for Lexicon {
433
+
type Output = Lexicon;
434
+
fn into_static(self) -> Self::Output {
435
+
self
436
+
}
437
+
}
438
+
439
+
impl IntoStatic for LexStringFormat {
440
+
type Output = LexStringFormat;
441
+
fn into_static(self) -> Self::Output {
442
+
self
443
+
}
444
+
}
445
+
446
+
impl IntoStatic for LexiconDoc<'_> {
447
+
type Output = LexiconDoc<'static>;
448
+
fn into_static(self) -> Self::Output {
449
+
LexiconDoc {
450
+
lexicon: self.lexicon,
451
+
id: self.id.into_static(),
452
+
revision: self.revision,
453
+
description: self.description.into_static(),
454
+
defs: self.defs.into_static(),
455
+
}
456
+
}
457
+
}
458
+
459
+
impl IntoStatic for LexBoolean<'_> {
460
+
type Output = LexBoolean<'static>;
461
+
fn into_static(self) -> Self::Output {
462
+
LexBoolean {
463
+
description: self.description.into_static(),
464
+
default: self.default,
465
+
r#const: self.r#const,
466
+
}
467
+
}
468
+
}
469
+
470
+
impl IntoStatic for LexInteger<'_> {
471
+
type Output = LexInteger<'static>;
472
+
fn into_static(self) -> Self::Output {
473
+
LexInteger {
474
+
description: self.description.into_static(),
475
+
default: self.default,
476
+
minimum: self.minimum,
477
+
maximum: self.maximum,
478
+
r#enum: self.r#enum,
479
+
r#const: self.r#const,
480
+
}
481
+
}
482
+
}
483
+
484
+
impl IntoStatic for LexString<'_> {
485
+
type Output = LexString<'static>;
486
+
fn into_static(self) -> Self::Output {
487
+
LexString {
488
+
description: self.description.into_static(),
489
+
format: self.format,
490
+
default: self.default.into_static(),
491
+
min_length: self.min_length,
492
+
max_length: self.max_length,
493
+
min_graphemes: self.min_graphemes,
494
+
max_graphemes: self.max_graphemes,
495
+
r#enum: self.r#enum.into_static(),
496
+
r#const: self.r#const.into_static(),
497
+
known_values: self.known_values.into_static(),
498
+
}
499
+
}
500
+
}
501
+
502
+
impl IntoStatic for LexUnknown<'_> {
503
+
type Output = LexUnknown<'static>;
504
+
fn into_static(self) -> Self::Output {
505
+
LexUnknown {
506
+
description: self.description.into_static(),
507
+
}
508
+
}
509
+
}
510
+
511
+
impl IntoStatic for LexBytes<'_> {
512
+
type Output = LexBytes<'static>;
513
+
fn into_static(self) -> Self::Output {
514
+
LexBytes {
515
+
description: self.description.into_static(),
516
+
max_length: self.max_length,
517
+
min_length: self.min_length,
518
+
}
519
+
}
520
+
}
521
+
522
+
impl IntoStatic for LexCidLink<'_> {
523
+
type Output = LexCidLink<'static>;
524
+
fn into_static(self) -> Self::Output {
525
+
LexCidLink {
526
+
description: self.description.into_static(),
527
+
}
528
+
}
529
+
}
530
+
531
+
impl IntoStatic for LexRef<'_> {
532
+
type Output = LexRef<'static>;
533
+
fn into_static(self) -> Self::Output {
534
+
LexRef {
535
+
description: self.description.into_static(),
536
+
r#ref: self.r#ref.into_static(),
537
+
}
538
+
}
539
+
}
540
+
541
+
impl IntoStatic for LexRefUnion<'_> {
542
+
type Output = LexRefUnion<'static>;
543
+
fn into_static(self) -> Self::Output {
544
+
LexRefUnion {
545
+
description: self.description.into_static(),
546
+
refs: self.refs.into_static(),
547
+
closed: self.closed,
548
+
}
549
+
}
550
+
}
551
+
552
+
impl IntoStatic for LexBlob<'_> {
553
+
type Output = LexBlob<'static>;
554
+
fn into_static(self) -> Self::Output {
555
+
LexBlob {
556
+
description: self.description.into_static(),
557
+
accept: self.accept.into_static(),
558
+
max_size: self.max_size,
559
+
}
560
+
}
561
+
}
562
+
563
+
impl IntoStatic for LexArrayItem<'_> {
564
+
type Output = LexArrayItem<'static>;
565
+
fn into_static(self) -> Self::Output {
566
+
match self {
567
+
Self::Boolean(x) => LexArrayItem::Boolean(x.into_static()),
568
+
Self::Integer(x) => LexArrayItem::Integer(x.into_static()),
569
+
Self::String(x) => LexArrayItem::String(x.into_static()),
570
+
Self::Unknown(x) => LexArrayItem::Unknown(x.into_static()),
571
+
Self::Bytes(x) => LexArrayItem::Bytes(x.into_static()),
572
+
Self::CidLink(x) => LexArrayItem::CidLink(x.into_static()),
573
+
Self::Blob(x) => LexArrayItem::Blob(x.into_static()),
574
+
Self::Ref(x) => LexArrayItem::Ref(x.into_static()),
575
+
Self::Union(x) => LexArrayItem::Union(x.into_static()),
576
+
}
577
+
}
578
+
}
579
+
580
+
impl IntoStatic for LexArray<'_> {
581
+
type Output = LexArray<'static>;
582
+
fn into_static(self) -> Self::Output {
583
+
LexArray {
584
+
description: self.description.into_static(),
585
+
items: self.items.into_static(),
586
+
min_length: self.min_length,
587
+
max_length: self.max_length,
588
+
}
589
+
}
590
+
}
591
+
592
+
impl IntoStatic for LexPrimitiveArrayItem<'_> {
593
+
type Output = LexPrimitiveArrayItem<'static>;
594
+
fn into_static(self) -> Self::Output {
595
+
match self {
596
+
Self::Boolean(x) => LexPrimitiveArrayItem::Boolean(x.into_static()),
597
+
Self::Integer(x) => LexPrimitiveArrayItem::Integer(x.into_static()),
598
+
Self::String(x) => LexPrimitiveArrayItem::String(x.into_static()),
599
+
Self::Unknown(x) => LexPrimitiveArrayItem::Unknown(x.into_static()),
600
+
}
601
+
}
602
+
}
603
+
604
+
impl IntoStatic for LexPrimitiveArray<'_> {
605
+
type Output = LexPrimitiveArray<'static>;
606
+
fn into_static(self) -> Self::Output {
607
+
LexPrimitiveArray {
608
+
description: self.description.into_static(),
609
+
items: self.items.into_static(),
610
+
min_length: self.min_length,
611
+
max_length: self.max_length,
612
+
}
613
+
}
614
+
}
615
+
616
+
impl IntoStatic for LexToken<'_> {
617
+
type Output = LexToken<'static>;
618
+
fn into_static(self) -> Self::Output {
619
+
LexToken {
620
+
description: self.description.into_static(),
621
+
}
622
+
}
623
+
}
624
+
625
+
impl IntoStatic for LexObjectProperty<'_> {
626
+
type Output = LexObjectProperty<'static>;
627
+
fn into_static(self) -> Self::Output {
628
+
match self {
629
+
Self::Ref(x) => LexObjectProperty::Ref(x.into_static()),
630
+
Self::Union(x) => LexObjectProperty::Union(x.into_static()),
631
+
Self::Bytes(x) => LexObjectProperty::Bytes(x.into_static()),
632
+
Self::CidLink(x) => LexObjectProperty::CidLink(x.into_static()),
633
+
Self::Array(x) => LexObjectProperty::Array(x.into_static()),
634
+
Self::Blob(x) => LexObjectProperty::Blob(x.into_static()),
635
+
Self::Boolean(x) => LexObjectProperty::Boolean(x.into_static()),
636
+
Self::Integer(x) => LexObjectProperty::Integer(x.into_static()),
637
+
Self::String(x) => LexObjectProperty::String(x.into_static()),
638
+
Self::Unknown(x) => LexObjectProperty::Unknown(x.into_static()),
639
+
}
640
+
}
641
+
}
642
+
643
+
impl IntoStatic for LexObject<'_> {
644
+
type Output = LexObject<'static>;
645
+
fn into_static(self) -> Self::Output {
646
+
LexObject {
647
+
description: self.description.into_static(),
648
+
required: self.required,
649
+
nullable: self.nullable,
650
+
properties: self.properties.into_static(),
651
+
}
652
+
}
653
+
}
654
+
655
+
impl IntoStatic for LexXrpcParametersProperty<'_> {
656
+
type Output = LexXrpcParametersProperty<'static>;
657
+
fn into_static(self) -> Self::Output {
658
+
match self {
659
+
Self::Boolean(x) => LexXrpcParametersProperty::Boolean(x.into_static()),
660
+
Self::Integer(x) => LexXrpcParametersProperty::Integer(x.into_static()),
661
+
Self::String(x) => LexXrpcParametersProperty::String(x.into_static()),
662
+
Self::Unknown(x) => LexXrpcParametersProperty::Unknown(x.into_static()),
663
+
Self::Array(x) => LexXrpcParametersProperty::Array(x.into_static()),
664
+
}
665
+
}
666
+
}
667
+
668
+
impl IntoStatic for LexXrpcParameters<'_> {
669
+
type Output = LexXrpcParameters<'static>;
670
+
fn into_static(self) -> Self::Output {
671
+
LexXrpcParameters {
672
+
description: self.description.into_static(),
673
+
required: self.required,
674
+
properties: self.properties.into_static(),
675
+
}
676
+
}
677
+
}
678
+
679
+
impl IntoStatic for LexXrpcBodySchema<'_> {
680
+
type Output = LexXrpcBodySchema<'static>;
681
+
fn into_static(self) -> Self::Output {
682
+
match self {
683
+
Self::Ref(x) => LexXrpcBodySchema::Ref(x.into_static()),
684
+
Self::Union(x) => LexXrpcBodySchema::Union(x.into_static()),
685
+
Self::Object(x) => LexXrpcBodySchema::Object(x.into_static()),
686
+
}
687
+
}
688
+
}
689
+
690
+
impl IntoStatic for LexXrpcBody<'_> {
691
+
type Output = LexXrpcBody<'static>;
692
+
fn into_static(self) -> Self::Output {
693
+
LexXrpcBody {
694
+
description: self.description.into_static(),
695
+
encoding: self.encoding.into_static(),
696
+
schema: self.schema.into_static(),
697
+
}
698
+
}
699
+
}
700
+
701
+
impl IntoStatic for LexXrpcSubscriptionMessageSchema<'_> {
702
+
type Output = LexXrpcSubscriptionMessageSchema<'static>;
703
+
fn into_static(self) -> Self::Output {
704
+
match self {
705
+
Self::Ref(x) => LexXrpcSubscriptionMessageSchema::Ref(x.into_static()),
706
+
Self::Union(x) => LexXrpcSubscriptionMessageSchema::Union(x.into_static()),
707
+
Self::Object(x) => LexXrpcSubscriptionMessageSchema::Object(x.into_static()),
708
+
}
709
+
}
710
+
}
711
+
712
+
impl IntoStatic for LexXrpcSubscriptionMessage<'_> {
713
+
type Output = LexXrpcSubscriptionMessage<'static>;
714
+
fn into_static(self) -> Self::Output {
715
+
LexXrpcSubscriptionMessage {
716
+
description: self.description.into_static(),
717
+
schema: self.schema.into_static(),
718
+
}
719
+
}
720
+
}
721
+
722
+
impl IntoStatic for LexXrpcError<'_> {
723
+
type Output = LexXrpcError<'static>;
724
+
fn into_static(self) -> Self::Output {
725
+
LexXrpcError {
726
+
description: self.description.into_static(),
727
+
name: self.name.into_static(),
728
+
}
729
+
}
730
+
}
731
+
732
+
impl IntoStatic for LexXrpcQueryParameter<'_> {
733
+
type Output = LexXrpcQueryParameter<'static>;
734
+
fn into_static(self) -> Self::Output {
735
+
match self {
736
+
Self::Params(x) => LexXrpcQueryParameter::Params(x.into_static()),
737
+
}
738
+
}
739
+
}
740
+
741
+
impl IntoStatic for LexXrpcQuery<'_> {
742
+
type Output = LexXrpcQuery<'static>;
743
+
fn into_static(self) -> Self::Output {
744
+
LexXrpcQuery {
745
+
description: self.description.into_static(),
746
+
parameters: self.parameters.into_static(),
747
+
output: self.output.into_static(),
748
+
errors: self.errors.into_static(),
749
+
}
750
+
}
751
+
}
752
+
753
+
impl IntoStatic for LexXrpcProcedureParameter<'_> {
754
+
type Output = LexXrpcProcedureParameter<'static>;
755
+
fn into_static(self) -> Self::Output {
756
+
match self {
757
+
Self::Params(x) => LexXrpcProcedureParameter::Params(x.into_static()),
758
+
}
759
+
}
760
+
}
761
+
762
+
impl IntoStatic for LexXrpcProcedure<'_> {
763
+
type Output = LexXrpcProcedure<'static>;
764
+
fn into_static(self) -> Self::Output {
765
+
LexXrpcProcedure {
766
+
description: self.description.into_static(),
767
+
parameters: self.parameters.into_static(),
768
+
input: self.input.into_static(),
769
+
output: self.output.into_static(),
770
+
errors: self.errors.into_static(),
771
+
}
772
+
}
773
+
}
774
+
775
+
impl IntoStatic for LexXrpcSubscriptionParameter<'_> {
776
+
type Output = LexXrpcSubscriptionParameter<'static>;
777
+
fn into_static(self) -> Self::Output {
778
+
match self {
779
+
Self::Params(x) => LexXrpcSubscriptionParameter::Params(x.into_static()),
780
+
}
781
+
}
782
+
}
783
+
784
+
impl IntoStatic for LexXrpcSubscription<'_> {
785
+
type Output = LexXrpcSubscription<'static>;
786
+
fn into_static(self) -> Self::Output {
787
+
LexXrpcSubscription {
788
+
description: self.description.into_static(),
789
+
parameters: self.parameters.into_static(),
790
+
message: self.message.into_static(),
791
+
infos: self.infos.into_static(),
792
+
errors: self.errors.into_static(),
793
+
}
794
+
}
795
+
}
796
+
797
+
impl IntoStatic for LexRecordRecord<'_> {
798
+
type Output = LexRecordRecord<'static>;
799
+
fn into_static(self) -> Self::Output {
800
+
match self {
801
+
Self::Object(x) => LexRecordRecord::Object(x.into_static()),
802
+
}
803
+
}
804
+
}
805
+
806
+
impl IntoStatic for LexRecord<'_> {
807
+
type Output = LexRecord<'static>;
808
+
fn into_static(self) -> Self::Output {
809
+
LexRecord {
810
+
description: self.description.into_static(),
811
+
key: self.key.into_static(),
812
+
record: self.record.into_static(),
813
+
}
814
+
}
815
+
}
816
+
817
+
impl IntoStatic for LexUserType<'_> {
818
+
type Output = LexUserType<'static>;
819
+
fn into_static(self) -> Self::Output {
820
+
match self {
821
+
Self::Record(x) => LexUserType::Record(x.into_static()),
822
+
Self::XrpcQuery(x) => LexUserType::XrpcQuery(x.into_static()),
823
+
Self::XrpcProcedure(x) => LexUserType::XrpcProcedure(x.into_static()),
824
+
Self::XrpcSubscription(x) => LexUserType::XrpcSubscription(x.into_static()),
825
+
Self::Blob(x) => LexUserType::Blob(x.into_static()),
826
+
Self::Array(x) => LexUserType::Array(x.into_static()),
827
+
Self::Token(x) => LexUserType::Token(x.into_static()),
828
+
Self::Object(x) => LexUserType::Object(x.into_static()),
829
+
Self::Boolean(x) => LexUserType::Boolean(x.into_static()),
830
+
Self::Integer(x) => LexUserType::Integer(x.into_static()),
831
+
Self::String(x) => LexUserType::String(x.into_static()),
832
+
Self::Bytes(x) => LexUserType::Bytes(x.into_static()),
833
+
Self::CidLink(x) => LexUserType::CidLink(x.into_static()),
834
+
Self::Unknown(x) => LexUserType::Unknown(x.into_static()),
835
+
}
836
+
}
405
837
}
406
838
407
839
#[cfg(test)]
+1
crates/jacquard-lexicon/src/lib.rs
+1
crates/jacquard-lexicon/src/lib.rs
+156
crates/jacquard-lexicon/tests/fixtures/lexicons/defs.json
+156
crates/jacquard-lexicon/tests/fixtures/lexicons/defs.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "com.atproto.label.defs",
4
+
"defs": {
5
+
"label": {
6
+
"type": "object",
7
+
"description": "Metadata tag on an atproto resource (eg, repo or record).",
8
+
"required": ["src", "uri", "val", "cts"],
9
+
"properties": {
10
+
"ver": {
11
+
"type": "integer",
12
+
"description": "The AT Protocol version of the label object."
13
+
},
14
+
"src": {
15
+
"type": "string",
16
+
"format": "did",
17
+
"description": "DID of the actor who created this label."
18
+
},
19
+
"uri": {
20
+
"type": "string",
21
+
"format": "uri",
22
+
"description": "AT URI of the record, repository (account), or other resource that this label applies to."
23
+
},
24
+
"cid": {
25
+
"type": "string",
26
+
"format": "cid",
27
+
"description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to."
28
+
},
29
+
"val": {
30
+
"type": "string",
31
+
"maxLength": 128,
32
+
"description": "The short string name of the value or type of this label."
33
+
},
34
+
"neg": {
35
+
"type": "boolean",
36
+
"description": "If true, this is a negation label, overwriting a previous label."
37
+
},
38
+
"cts": {
39
+
"type": "string",
40
+
"format": "datetime",
41
+
"description": "Timestamp when this label was created."
42
+
},
43
+
"exp": {
44
+
"type": "string",
45
+
"format": "datetime",
46
+
"description": "Timestamp at which this label expires (no longer applies)."
47
+
},
48
+
"sig": {
49
+
"type": "bytes",
50
+
"description": "Signature of dag-cbor encoded label."
51
+
}
52
+
}
53
+
},
54
+
"selfLabels": {
55
+
"type": "object",
56
+
"description": "Metadata tags on an atproto record, published by the author within the record.",
57
+
"required": ["values"],
58
+
"properties": {
59
+
"values": {
60
+
"type": "array",
61
+
"items": { "type": "ref", "ref": "#selfLabel" },
62
+
"maxLength": 10
63
+
}
64
+
}
65
+
},
66
+
"selfLabel": {
67
+
"type": "object",
68
+
"description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.",
69
+
"required": ["val"],
70
+
"properties": {
71
+
"val": {
72
+
"type": "string",
73
+
"maxLength": 128,
74
+
"description": "The short string name of the value or type of this label."
75
+
}
76
+
}
77
+
},
78
+
"labelValueDefinition": {
79
+
"type": "object",
80
+
"description": "Declares a label value and its expected interpretations and behaviors.",
81
+
"required": ["identifier", "severity", "blurs", "locales"],
82
+
"properties": {
83
+
"identifier": {
84
+
"type": "string",
85
+
"description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).",
86
+
"maxLength": 100,
87
+
"maxGraphemes": 100
88
+
},
89
+
"severity": {
90
+
"type": "string",
91
+
"description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.",
92
+
"knownValues": ["inform", "alert", "none"]
93
+
},
94
+
"blurs": {
95
+
"type": "string",
96
+
"description": "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.",
97
+
"knownValues": ["content", "media", "none"]
98
+
},
99
+
"defaultSetting": {
100
+
"type": "string",
101
+
"description": "The default setting for this label.",
102
+
"knownValues": ["ignore", "warn", "hide"],
103
+
"default": "warn"
104
+
},
105
+
"adultOnly": {
106
+
"type": "boolean",
107
+
"description": "Does the user need to have adult content enabled in order to configure this label?"
108
+
},
109
+
"locales": {
110
+
"type": "array",
111
+
"items": { "type": "ref", "ref": "#labelValueDefinitionStrings" }
112
+
}
113
+
}
114
+
},
115
+
"labelValueDefinitionStrings": {
116
+
"type": "object",
117
+
"description": "Strings which describe the label in the UI, localized into a specific language.",
118
+
"required": ["lang", "name", "description"],
119
+
"properties": {
120
+
"lang": {
121
+
"type": "string",
122
+
"description": "The code of the language these strings are written in.",
123
+
"format": "language"
124
+
},
125
+
"name": {
126
+
"type": "string",
127
+
"description": "A short human-readable name for the label.",
128
+
"maxGraphemes": 64,
129
+
"maxLength": 640
130
+
},
131
+
"description": {
132
+
"type": "string",
133
+
"description": "A longer description of what the label means and why it might be applied.",
134
+
"maxGraphemes": 10000,
135
+
"maxLength": 100000
136
+
}
137
+
}
138
+
},
139
+
"labelValue": {
140
+
"type": "string",
141
+
"knownValues": [
142
+
"!hide",
143
+
"!no-promote",
144
+
"!warn",
145
+
"!no-unauthenticated",
146
+
"dmca-violation",
147
+
"doxxing",
148
+
"porn",
149
+
"sexual",
150
+
"nudity",
151
+
"nsfl",
152
+
"gore"
153
+
]
154
+
}
155
+
}
156
+
}
+51
crates/jacquard-lexicon/tests/fixtures/lexicons/external.json
+51
crates/jacquard-lexicon/tests/fixtures/lexicons/external.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "app.bsky.embed.external",
4
+
"defs": {
5
+
"main": {
6
+
"type": "object",
7
+
"description": "A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post).",
8
+
"required": ["external"],
9
+
"properties": {
10
+
"external": {
11
+
"type": "ref",
12
+
"ref": "#external"
13
+
}
14
+
}
15
+
},
16
+
"external": {
17
+
"type": "object",
18
+
"required": ["uri", "title", "description"],
19
+
"properties": {
20
+
"uri": { "type": "string", "format": "uri" },
21
+
"title": { "type": "string" },
22
+
"description": { "type": "string" },
23
+
"thumb": {
24
+
"type": "blob",
25
+
"accept": ["image/*"],
26
+
"maxSize": 1000000
27
+
}
28
+
}
29
+
},
30
+
"view": {
31
+
"type": "object",
32
+
"required": ["external"],
33
+
"properties": {
34
+
"external": {
35
+
"type": "ref",
36
+
"ref": "#viewExternal"
37
+
}
38
+
}
39
+
},
40
+
"viewExternal": {
41
+
"type": "object",
42
+
"required": ["uri", "title", "description"],
43
+
"properties": {
44
+
"uri": { "type": "string", "format": "uri" },
45
+
"title": { "type": "string" },
46
+
"description": { "type": "string" },
47
+
"thumb": { "type": "string", "format": "uri" }
48
+
}
49
+
}
50
+
}
51
+
}
+51
crates/jacquard-lexicon/tests/fixtures/lexicons/facet.json
+51
crates/jacquard-lexicon/tests/fixtures/lexicons/facet.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "app.bsky.richtext.facet",
4
+
"defs": {
5
+
"main": {
6
+
"type": "object",
7
+
"description": "Annotation of a sub-string within rich text.",
8
+
"required": ["index", "features"],
9
+
"properties": {
10
+
"index": { "type": "ref", "ref": "#byteSlice" },
11
+
"features": {
12
+
"type": "array",
13
+
"items": { "type": "union", "refs": ["#mention", "#link", "#tag"] }
14
+
}
15
+
}
16
+
},
17
+
"mention": {
18
+
"type": "object",
19
+
"description": "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.",
20
+
"required": ["did"],
21
+
"properties": {
22
+
"did": { "type": "string", "format": "did" }
23
+
}
24
+
},
25
+
"link": {
26
+
"type": "object",
27
+
"description": "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.",
28
+
"required": ["uri"],
29
+
"properties": {
30
+
"uri": { "type": "string", "format": "uri" }
31
+
}
32
+
},
33
+
"tag": {
34
+
"type": "object",
35
+
"description": "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags').",
36
+
"required": ["tag"],
37
+
"properties": {
38
+
"tag": { "type": "string", "maxLength": 640, "maxGraphemes": 64 }
39
+
}
40
+
},
41
+
"byteSlice": {
42
+
"type": "object",
43
+
"description": "Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets.",
44
+
"required": ["byteStart", "byteEnd"],
45
+
"properties": {
46
+
"byteStart": { "type": "integer", "minimum": 0 },
47
+
"byteEnd": { "type": "integer", "minimum": 0 }
48
+
}
49
+
}
50
+
}
51
+
}
+58
crates/jacquard-lexicon/tests/fixtures/lexicons/getAuthorFeed.json
+58
crates/jacquard-lexicon/tests/fixtures/lexicons/getAuthorFeed.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "app.bsky.feed.getAuthorFeed",
4
+
"defs": {
5
+
"main": {
6
+
"type": "query",
7
+
"description": "Get a view of an actor's 'author feed' (post and reposts by the author). Does not require auth.",
8
+
"parameters": {
9
+
"type": "params",
10
+
"required": ["actor"],
11
+
"properties": {
12
+
"actor": { "type": "string", "format": "at-identifier" },
13
+
"limit": {
14
+
"type": "integer",
15
+
"minimum": 1,
16
+
"maximum": 100,
17
+
"default": 50
18
+
},
19
+
"cursor": { "type": "string" },
20
+
"filter": {
21
+
"type": "string",
22
+
"description": "Combinations of post/repost types to include in response.",
23
+
"knownValues": [
24
+
"posts_with_replies",
25
+
"posts_no_replies",
26
+
"posts_with_media",
27
+
"posts_and_author_threads",
28
+
"posts_with_video"
29
+
],
30
+
"default": "posts_with_replies"
31
+
},
32
+
"includePins": {
33
+
"type": "boolean",
34
+
"default": false
35
+
}
36
+
}
37
+
},
38
+
"output": {
39
+
"encoding": "application/json",
40
+
"schema": {
41
+
"type": "object",
42
+
"required": ["feed"],
43
+
"properties": {
44
+
"cursor": { "type": "string" },
45
+
"feed": {
46
+
"type": "array",
47
+
"items": {
48
+
"type": "ref",
49
+
"ref": "app.bsky.feed.defs#feedViewPost"
50
+
}
51
+
}
52
+
}
53
+
}
54
+
},
55
+
"errors": [{ "name": "BlockedActor" }, { "name": "BlockedByActor" }]
56
+
}
57
+
}
58
+
}
+72
crates/jacquard-lexicon/tests/fixtures/lexicons/images.json
+72
crates/jacquard-lexicon/tests/fixtures/lexicons/images.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "app.bsky.embed.images",
4
+
"description": "A set of images embedded in a Bluesky record (eg, a post).",
5
+
"defs": {
6
+
"main": {
7
+
"type": "object",
8
+
"required": ["images"],
9
+
"properties": {
10
+
"images": {
11
+
"type": "array",
12
+
"items": { "type": "ref", "ref": "#image" },
13
+
"maxLength": 4
14
+
}
15
+
}
16
+
},
17
+
"image": {
18
+
"type": "object",
19
+
"required": ["image", "alt"],
20
+
"properties": {
21
+
"image": {
22
+
"type": "blob",
23
+
"accept": ["image/*"],
24
+
"maxSize": 1000000
25
+
},
26
+
"alt": {
27
+
"type": "string",
28
+
"description": "Alt text description of the image, for accessibility."
29
+
},
30
+
"aspectRatio": {
31
+
"type": "ref",
32
+
"ref": "app.bsky.embed.defs#aspectRatio"
33
+
}
34
+
}
35
+
},
36
+
"view": {
37
+
"type": "object",
38
+
"required": ["images"],
39
+
"properties": {
40
+
"images": {
41
+
"type": "array",
42
+
"items": { "type": "ref", "ref": "#viewImage" },
43
+
"maxLength": 4
44
+
}
45
+
}
46
+
},
47
+
"viewImage": {
48
+
"type": "object",
49
+
"required": ["thumb", "fullsize", "alt"],
50
+
"properties": {
51
+
"thumb": {
52
+
"type": "string",
53
+
"format": "uri",
54
+
"description": "Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View."
55
+
},
56
+
"fullsize": {
57
+
"type": "string",
58
+
"format": "uri",
59
+
"description": "Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View."
60
+
},
61
+
"alt": {
62
+
"type": "string",
63
+
"description": "Alt text description of the image, for accessibility."
64
+
},
65
+
"aspectRatio": {
66
+
"type": "ref",
67
+
"ref": "app.bsky.embed.defs#aspectRatio"
68
+
}
69
+
}
70
+
}
71
+
}
72
+
}
+96
crates/jacquard-lexicon/tests/fixtures/lexicons/post.json
+96
crates/jacquard-lexicon/tests/fixtures/lexicons/post.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "app.bsky.feed.post",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "Record containing a Bluesky post.",
8
+
"key": "tid",
9
+
"record": {
10
+
"type": "object",
11
+
"required": ["text", "createdAt"],
12
+
"properties": {
13
+
"text": {
14
+
"type": "string",
15
+
"maxLength": 3000,
16
+
"maxGraphemes": 300,
17
+
"description": "The primary post content. May be an empty string, if there are embeds."
18
+
},
19
+
"entities": {
20
+
"type": "array",
21
+
"description": "DEPRECATED: replaced by app.bsky.richtext.facet.",
22
+
"items": { "type": "ref", "ref": "#entity" }
23
+
},
24
+
"facets": {
25
+
"type": "array",
26
+
"description": "Annotations of text (mentions, URLs, hashtags, etc)",
27
+
"items": { "type": "ref", "ref": "app.bsky.richtext.facet" }
28
+
},
29
+
"reply": { "type": "ref", "ref": "#replyRef" },
30
+
"embed": {
31
+
"type": "union",
32
+
"refs": [
33
+
"app.bsky.embed.images",
34
+
"app.bsky.embed.video",
35
+
"app.bsky.embed.external",
36
+
"app.bsky.embed.record",
37
+
"app.bsky.embed.recordWithMedia"
38
+
]
39
+
},
40
+
"langs": {
41
+
"type": "array",
42
+
"description": "Indicates human language of post primary text content.",
43
+
"maxLength": 3,
44
+
"items": { "type": "string", "format": "language" }
45
+
},
46
+
"labels": {
47
+
"type": "union",
48
+
"description": "Self-label values for this post. Effectively content warnings.",
49
+
"refs": ["com.atproto.label.defs#selfLabels"]
50
+
},
51
+
"tags": {
52
+
"type": "array",
53
+
"description": "Additional hashtags, in addition to any included in post text and facets.",
54
+
"maxLength": 8,
55
+
"items": { "type": "string", "maxLength": 640, "maxGraphemes": 64 }
56
+
},
57
+
"createdAt": {
58
+
"type": "string",
59
+
"format": "datetime",
60
+
"description": "Client-declared timestamp when this post was originally created."
61
+
}
62
+
}
63
+
}
64
+
},
65
+
"replyRef": {
66
+
"type": "object",
67
+
"required": ["root", "parent"],
68
+
"properties": {
69
+
"root": { "type": "ref", "ref": "com.atproto.repo.strongRef" },
70
+
"parent": { "type": "ref", "ref": "com.atproto.repo.strongRef" }
71
+
}
72
+
},
73
+
"entity": {
74
+
"type": "object",
75
+
"description": "Deprecated: use facets instead.",
76
+
"required": ["index", "type", "value"],
77
+
"properties": {
78
+
"index": { "type": "ref", "ref": "#textSlice" },
79
+
"type": {
80
+
"type": "string",
81
+
"description": "Expected values are 'mention' and 'link'."
82
+
},
83
+
"value": { "type": "string" }
84
+
}
85
+
},
86
+
"textSlice": {
87
+
"type": "object",
88
+
"description": "Deprecated. Use app.bsky.richtext instead -- A text segment. Start is inclusive, end is exclusive. Indices are for utf16-encoded strings.",
89
+
"required": ["start", "end"],
90
+
"properties": {
91
+
"start": { "type": "integer", "minimum": 0 },
92
+
"end": { "type": "integer", "minimum": 0 }
93
+
}
94
+
}
95
+
}
96
+
}
+96
crates/jacquard-lexicon/tests/fixtures/lexicons/record.json
+96
crates/jacquard-lexicon/tests/fixtures/lexicons/record.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "app.bsky.embed.record",
4
+
"description": "A representation of a record embedded in a Bluesky record (eg, a post). For example, a quote-post, or sharing a feed generator record.",
5
+
"defs": {
6
+
"main": {
7
+
"type": "object",
8
+
"required": ["record"],
9
+
"properties": {
10
+
"record": { "type": "ref", "ref": "com.atproto.repo.strongRef" }
11
+
}
12
+
},
13
+
"view": {
14
+
"type": "object",
15
+
"required": ["record"],
16
+
"properties": {
17
+
"record": {
18
+
"type": "union",
19
+
"refs": [
20
+
"#viewRecord",
21
+
"#viewNotFound",
22
+
"#viewBlocked",
23
+
"#viewDetached",
24
+
"app.bsky.feed.defs#generatorView",
25
+
"app.bsky.graph.defs#listView",
26
+
"app.bsky.labeler.defs#labelerView",
27
+
"app.bsky.graph.defs#starterPackViewBasic"
28
+
]
29
+
}
30
+
}
31
+
},
32
+
"viewRecord": {
33
+
"type": "object",
34
+
"required": ["uri", "cid", "author", "value", "indexedAt"],
35
+
"properties": {
36
+
"uri": { "type": "string", "format": "at-uri" },
37
+
"cid": { "type": "string", "format": "cid" },
38
+
"author": {
39
+
"type": "ref",
40
+
"ref": "app.bsky.actor.defs#profileViewBasic"
41
+
},
42
+
"value": {
43
+
"type": "unknown",
44
+
"description": "The record data itself."
45
+
},
46
+
"labels": {
47
+
"type": "array",
48
+
"items": { "type": "ref", "ref": "com.atproto.label.defs#label" }
49
+
},
50
+
"replyCount": { "type": "integer" },
51
+
"repostCount": { "type": "integer" },
52
+
"likeCount": { "type": "integer" },
53
+
"quoteCount": { "type": "integer" },
54
+
"embeds": {
55
+
"type": "array",
56
+
"items": {
57
+
"type": "union",
58
+
"refs": [
59
+
"app.bsky.embed.images#view",
60
+
"app.bsky.embed.video#view",
61
+
"app.bsky.embed.external#view",
62
+
"app.bsky.embed.record#view",
63
+
"app.bsky.embed.recordWithMedia#view"
64
+
]
65
+
}
66
+
},
67
+
"indexedAt": { "type": "string", "format": "datetime" }
68
+
}
69
+
},
70
+
"viewNotFound": {
71
+
"type": "object",
72
+
"required": ["uri", "notFound"],
73
+
"properties": {
74
+
"uri": { "type": "string", "format": "at-uri" },
75
+
"notFound": { "type": "boolean", "const": true }
76
+
}
77
+
},
78
+
"viewBlocked": {
79
+
"type": "object",
80
+
"required": ["uri", "blocked", "author"],
81
+
"properties": {
82
+
"uri": { "type": "string", "format": "at-uri" },
83
+
"blocked": { "type": "boolean", "const": true },
84
+
"author": { "type": "ref", "ref": "app.bsky.feed.defs#blockedAuthor" }
85
+
}
86
+
},
87
+
"viewDetached": {
88
+
"type": "object",
89
+
"required": ["uri", "detached"],
90
+
"properties": {
91
+
"uri": { "type": "string", "format": "at-uri" },
92
+
"detached": { "type": "boolean", "const": true }
93
+
}
94
+
}
95
+
}
96
+
}
+43
crates/jacquard-lexicon/tests/fixtures/lexicons/recordWithMedia.json
+43
crates/jacquard-lexicon/tests/fixtures/lexicons/recordWithMedia.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "app.bsky.embed.recordWithMedia",
4
+
"description": "A representation of a record embedded in a Bluesky record (eg, a post), alongside other compatible embeds. For example, a quote post and image, or a quote post and external URL card.",
5
+
"defs": {
6
+
"main": {
7
+
"type": "object",
8
+
"required": ["record", "media"],
9
+
"properties": {
10
+
"record": {
11
+
"type": "ref",
12
+
"ref": "app.bsky.embed.record"
13
+
},
14
+
"media": {
15
+
"type": "union",
16
+
"refs": [
17
+
"app.bsky.embed.images",
18
+
"app.bsky.embed.video",
19
+
"app.bsky.embed.external"
20
+
]
21
+
}
22
+
}
23
+
},
24
+
"view": {
25
+
"type": "object",
26
+
"required": ["record", "media"],
27
+
"properties": {
28
+
"record": {
29
+
"type": "ref",
30
+
"ref": "app.bsky.embed.record#view"
31
+
},
32
+
"media": {
33
+
"type": "union",
34
+
"refs": [
35
+
"app.bsky.embed.images#view",
36
+
"app.bsky.embed.video#view",
37
+
"app.bsky.embed.external#view"
38
+
]
39
+
}
40
+
}
41
+
}
42
+
}
43
+
}
+15
crates/jacquard-lexicon/tests/fixtures/lexicons/strongRef.json
+15
crates/jacquard-lexicon/tests/fixtures/lexicons/strongRef.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "com.atproto.repo.strongRef",
4
+
"description": "A URI with a content-hash fingerprint.",
5
+
"defs": {
6
+
"main": {
7
+
"type": "object",
8
+
"required": ["uri", "cid"],
9
+
"properties": {
10
+
"uri": { "type": "string", "format": "at-uri" },
11
+
"cid": { "type": "string", "format": "cid" }
12
+
}
13
+
}
14
+
}
15
+
}
+67
crates/jacquard-lexicon/tests/fixtures/lexicons/video.json
+67
crates/jacquard-lexicon/tests/fixtures/lexicons/video.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "app.bsky.embed.video",
4
+
"description": "A video embedded in a Bluesky record (eg, a post).",
5
+
"defs": {
6
+
"main": {
7
+
"type": "object",
8
+
"required": ["video"],
9
+
"properties": {
10
+
"video": {
11
+
"type": "blob",
12
+
"description": "The mp4 video file. May be up to 100mb, formerly limited to 50mb.",
13
+
"accept": ["video/mp4"],
14
+
"maxSize": 100000000
15
+
},
16
+
"captions": {
17
+
"type": "array",
18
+
"items": { "type": "ref", "ref": "#caption" },
19
+
"maxLength": 20
20
+
},
21
+
"alt": {
22
+
"type": "string",
23
+
"description": "Alt text description of the video, for accessibility.",
24
+
"maxGraphemes": 1000,
25
+
"maxLength": 10000
26
+
},
27
+
"aspectRatio": {
28
+
"type": "ref",
29
+
"ref": "app.bsky.embed.defs#aspectRatio"
30
+
}
31
+
}
32
+
},
33
+
"caption": {
34
+
"type": "object",
35
+
"required": ["lang", "file"],
36
+
"properties": {
37
+
"lang": {
38
+
"type": "string",
39
+
"format": "language"
40
+
},
41
+
"file": {
42
+
"type": "blob",
43
+
"accept": ["text/vtt"],
44
+
"maxSize": 20000
45
+
}
46
+
}
47
+
},
48
+
"view": {
49
+
"type": "object",
50
+
"required": ["cid", "playlist"],
51
+
"properties": {
52
+
"cid": { "type": "string", "format": "cid" },
53
+
"playlist": { "type": "string", "format": "uri" },
54
+
"thumbnail": { "type": "string", "format": "uri" },
55
+
"alt": {
56
+
"type": "string",
57
+
"maxGraphemes": 1000,
58
+
"maxLength": 10000
59
+
},
60
+
"aspectRatio": {
61
+
"type": "ref",
62
+
"ref": "app.bsky.embed.defs#aspectRatio"
63
+
}
64
+
}
65
+
}
66
+
}
67
+
}