Highly ambitious ATProtocol AppView service and sdks
at main 168 lines 5.6 kB view raw
1//! GraphQL type definitions and mappings from AT Protocol lexicons 2 3use serde_json::Value; 4 5/// Represents a mapped GraphQL field from a lexicon property 6#[derive(Debug, Clone)] 7pub struct GraphQLField { 8 pub name: String, 9 pub field_type: GraphQLType, 10 pub is_required: bool, 11 pub format: Option<String>, // e.g., "at-uri", "uri", "datetime" 12} 13 14/// GraphQL type representation mapped from lexicon types 15#[derive(Debug, Clone)] 16pub enum GraphQLType { 17 String, 18 Int, 19 Boolean, 20 Float, 21 /// Reference to another record (for strongRef) 22 Ref, 23 /// Reference to a lexicon type definition (e.g., community.lexicon.location.hthree) 24 LexiconRef(String), 25 /// Array of a type 26 Array(Box<GraphQLType>), 27 /// Object with nested fields 28 Object(Vec<GraphQLField>), 29 /// Union of multiple types 30 Union, 31 /// Blob reference with CDN URL support 32 Blob, 33 /// Any JSON value 34 Json, 35} 36 37/// Maps AT Protocol lexicon type to GraphQL type 38pub fn map_lexicon_type_to_graphql(type_name: &str, lexicon_def: &Value) -> GraphQLType { 39 match type_name { 40 "string" => GraphQLType::String, 41 "integer" => GraphQLType::Int, 42 "boolean" => GraphQLType::Boolean, 43 "number" => GraphQLType::Float, 44 "bytes" => GraphQLType::String, // Base64 encoded 45 "cid-link" => GraphQLType::String, 46 "blob" => GraphQLType::Blob, 47 "unknown" => GraphQLType::Json, 48 "null" => GraphQLType::Json, 49 "ref" => { 50 // Check if this is a strongRef (link to another record) or a lexicon type ref 51 let ref_name = lexicon_def 52 .get("ref") 53 .and_then(|r| r.as_str()) 54 .unwrap_or(""); 55 56 if ref_name == "com.atproto.repo.strongRef" { 57 GraphQLType::Ref 58 } else if !ref_name.is_empty() { 59 // This is a reference to a lexicon type definition 60 GraphQLType::LexiconRef(ref_name.to_string()) 61 } else { 62 GraphQLType::Json 63 } 64 } 65 "array" => { 66 let items = lexicon_def.get("items"); 67 let item_type = if let Some(items_obj) = items { 68 let item_type_name = items_obj 69 .get("type") 70 .and_then(|t| t.as_str()) 71 .unwrap_or("unknown"); 72 map_lexicon_type_to_graphql(item_type_name, items_obj) 73 } else { 74 GraphQLType::Json 75 }; 76 GraphQLType::Array(Box::new(item_type)) 77 } 78 "object" => { 79 let properties = lexicon_def.get("properties").and_then(|p| p.as_object()); 80 81 let required_fields: Vec<String> = lexicon_def 82 .get("required") 83 .and_then(|r| r.as_array()) 84 .map(|arr| { 85 arr.iter() 86 .filter_map(|v| v.as_str().map(|s| s.to_string())) 87 .collect() 88 }) 89 .unwrap_or_default(); 90 91 if let Some(props) = properties { 92 let fields = props 93 .iter() 94 .map(|(field_name, field_def)| { 95 let field_type_name = field_def 96 .get("type") 97 .and_then(|t| t.as_str()) 98 .unwrap_or("unknown"); 99 100 let format = field_def 101 .get("format") 102 .and_then(|f| f.as_str()) 103 .map(|s| s.to_string()); 104 105 GraphQLField { 106 name: field_name.clone(), 107 format, 108 field_type: map_lexicon_type_to_graphql(field_type_name, field_def), 109 is_required: required_fields.contains(&field_name.to_string()), 110 } 111 }) 112 .collect(); 113 114 GraphQLType::Object(fields) 115 } else { 116 GraphQLType::Json 117 } 118 } 119 "union" => GraphQLType::Union, 120 _ => GraphQLType::Json, 121 } 122} 123 124/// Extract collection schema from lexicon definitions 125pub fn extract_collection_fields(lexicon_defs: &Value) -> Vec<GraphQLField> { 126 let main_def = lexicon_defs 127 .get("main") 128 .or_else(|| lexicon_defs.get("record")); 129 130 if let Some(main) = main_def { 131 let type_name = main 132 .get("type") 133 .and_then(|t| t.as_str()) 134 .unwrap_or("object"); 135 136 // For "record" type, the actual object definition is nested under "record" field 137 let object_def = if type_name == "record" { 138 main.get("record").unwrap_or(main) 139 } else { 140 main 141 }; 142 143 if type_name == "record" || type_name == "object" { 144 let object_type_name = object_def 145 .get("type") 146 .and_then(|t| t.as_str()) 147 .unwrap_or("object"); 148 149 if let GraphQLType::Object(fields) = 150 map_lexicon_type_to_graphql(object_type_name, object_def) 151 { 152 return fields; 153 } 154 } 155 } 156 157 vec![] 158} 159 160/// Extract the record key type from lexicon definitions 161/// Returns Some("tid"), Some("literal:self"), Some("any"), or None 162pub fn extract_record_key(lexicon_defs: &Value) -> Option<String> { 163 lexicon_defs 164 .get("main")? 165 .get("key") 166 .and_then(|k| k.as_str()) 167 .map(|s| s.to_string()) 168}