A better Rust ATProto crate
1mod atproto;
2mod git;
3mod http;
4mod jsonfile;
5mod local;
6mod slices;
7
8pub use atproto::AtProtoSource;
9pub use git::GitSource;
10pub use http::HttpSource;
11use jacquard_common::IntoStatic;
12pub use jsonfile::JsonFileSource;
13pub use local::LocalSource;
14pub use slices::SlicesSource;
15
16use jacquard_lexicon::lexicon::LexiconDoc;
17use miette::{IntoDiagnostic, Result};
18use std::collections::HashMap;
19use std::future::Future;
20
21#[derive(Debug, Clone)]
22pub struct Source {
23 pub name: String,
24 pub source_type: SourceType,
25 pub explicit_priority: Option<i32>,
26}
27
28impl Source {
29 /// Get effective priority based on type and explicit override
30 pub fn priority(&self) -> i32 {
31 if let Some(p) = self.explicit_priority {
32 return p;
33 }
34
35 // Default priorities
36 match &self.source_type {
37 SourceType::Local(_) => 100, // Highest - dev work
38 SourceType::JsonFile(_) => 75, // High - bundled exports
39 SourceType::Slices(_) => 60, // High-middle - slices network
40 SourceType::AtProto(_) => 50, // Middle - canonical published
41 SourceType::Http(_) => 25, // Lower middle - indexed samples
42 SourceType::Git(_) => 0, // Lowest - might be stale
43 }
44 }
45
46 pub async fn fetch(&self) -> Result<HashMap<String, LexiconDoc<'_>>> {
47 self.source_type.fetch().await
48 }
49}
50
51#[derive(Debug, Clone)]
52pub enum SourceType {
53 AtProto(AtProtoSource),
54 Git(GitSource),
55 Http(HttpSource),
56 JsonFile(JsonFileSource),
57 Local(LocalSource),
58 Slices(SlicesSource),
59}
60
61pub trait LexiconSource {
62 fn fetch(&self) -> impl Future<Output = Result<HashMap<String, LexiconDoc<'_>>>> + Send;
63}
64
65impl LexiconSource for SourceType {
66 async fn fetch(&self) -> Result<HashMap<String, LexiconDoc<'_>>> {
67 match self {
68 SourceType::AtProto(s) => s.fetch().await,
69 SourceType::Git(s) => s.fetch().await,
70 SourceType::Http(s) => s.fetch().await,
71 SourceType::JsonFile(s) => s.fetch().await,
72 SourceType::Local(s) => s.fetch().await,
73 SourceType::Slices(s) => s.fetch().await,
74 }
75 }
76}
77
78pub fn parse_from_index_or_lexicon_file(
79 content: &str,
80) -> miette::Result<(String, LexiconDoc<'static>)> {
81 let value: serde_json::Value = serde_json::from_str(content).into_diagnostic()?;
82 if let Some(map) = value.as_object() {
83 if map.contains_key("schema") && map.contains_key("authority") {
84 if let Some(schema) = map.get("schema") {
85 let schema = serde_json::to_string(schema).into_diagnostic()?;
86 match serde_json::from_str::<LexiconDoc>(&schema) {
87 Ok(doc) => {
88 let nsid = doc.id.to_string();
89 let doc = doc.into_static();
90 Ok((nsid, doc))
91 }
92 Err(e) => {
93 // Not a lexicon, skip
94 Err(miette::miette!("Invalid lexicon file: {e}"))
95 }
96 }
97 } else {
98 Err(miette::miette!("Invalid lexicon file"))
99 }
100 } else if map.contains_key("id") && map.contains_key("lexicon") {
101 match serde_json::from_str::<LexiconDoc>(&content) {
102 Ok(doc) => {
103 let nsid = doc.id.to_string();
104 let doc = doc.into_static();
105 Ok((nsid, doc))
106 }
107 Err(e) => {
108 // Not a lexicon, skip
109 Err(miette::miette!("Invalid lexicon file: {e}"))
110 }
111 }
112 } else {
113 Err(miette::miette!("Invalid lexicon file"))
114 }
115 } else {
116 Err(miette::miette!("Invalid lexicon file"))
117 }
118}