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 crate::lexicon::LexiconDoc;
17use async_trait::async_trait;
18use miette::{IntoDiagnostic, Result};
19use std::collections::HashMap;
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
61#[async_trait]
62pub trait LexiconSource {
63 fn fetch(&self) -> impl Future<Output = Result<HashMap<String, LexiconDoc<'_>>>>;
64}
65
66impl LexiconSource for SourceType {
67 async fn fetch(&self) -> Result<HashMap<String, LexiconDoc<'_>>> {
68 match self {
69 SourceType::AtProto(s) => s.fetch().await,
70 SourceType::Git(s) => s.fetch().await,
71 SourceType::Http(s) => s.fetch().await,
72 SourceType::JsonFile(s) => s.fetch().await,
73 SourceType::Local(s) => s.fetch().await,
74 SourceType::Slices(s) => s.fetch().await,
75 }
76 }
77}
78
79pub fn parse_from_index_or_lexicon_file(
80 content: &str,
81) -> miette::Result<(String, LexiconDoc<'static>)> {
82 let value: serde_json::Value = serde_json::from_str(content).into_diagnostic()?;
83 if let Some(map) = value.as_object() {
84 if map.contains_key("schema") && map.contains_key("authority") {
85 if let Some(schema) = map.get("schema") {
86 let schema = serde_json::to_string(schema).into_diagnostic()?;
87 match serde_json::from_str::<LexiconDoc>(&schema) {
88 Ok(doc) => {
89 let nsid = doc.id.to_string();
90 let doc = doc.into_static();
91 Ok((nsid, doc))
92 }
93 Err(_) => {
94 // Not a lexicon, skip
95 Err(miette::miette!("Invalid lexicon file"))
96 }
97 }
98 } else {
99 Err(miette::miette!("Invalid lexicon file"))
100 }
101 } else if map.contains_key("id") && map.contains_key("lexicon") {
102 match serde_json::from_str::<LexiconDoc>(&content) {
103 Ok(doc) => {
104 let nsid = doc.id.to_string();
105 let doc = doc.into_static();
106 Ok((nsid, doc))
107 }
108 Err(_) => {
109 // Not a lexicon, skip
110 Err(miette::miette!("Invalid lexicon file"))
111 }
112 }
113 } else {
114 Err(miette::miette!("Invalid lexicon file"))
115 }
116 } else {
117 Err(miette::miette!("Invalid lexicon file"))
118 }
119}