A better Rust ATProto crate
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 3.8 kB View raw
1use jacquard_common::CowStr; 2use jacquard_common::types::string::Datetime; 3use jacquard_lexicon::lexicon::{ 4 LexObject, LexObjectProperty, LexRecord, LexRecordRecord, LexString, LexStringFormat, 5 LexUserType, Lexicon, LexiconDoc, 6}; 7use jacquard_lexicon::schema::LexiconSchema; 8use jacquard_lexicon::validation::{ConstraintError, ValidationPath}; 9use std::collections::BTreeMap; 10 11// Simple test type 12#[derive(Debug, Clone)] 13struct SimpleRecord<'a> { 14 text: CowStr<'a>, 15 #[allow(dead_code)] 16 timestamp: Datetime, 17} 18 19impl LexiconSchema for SimpleRecord<'_> { 20 fn nsid() -> &'static str { 21 "com.example.simple" 22 } 23 24 fn lexicon_doc() -> LexiconDoc<'static> { 25 let mut properties = BTreeMap::new(); 26 27 properties.insert( 28 "text".into(), 29 LexObjectProperty::String(LexString { 30 description: None, 31 format: None, 32 default: None, 33 min_length: None, 34 max_length: Some(1000), 35 min_graphemes: None, 36 max_graphemes: None, 37 r#enum: None, 38 r#const: None, 39 known_values: None, 40 }), 41 ); 42 43 properties.insert( 44 "timestamp".into(), 45 LexObjectProperty::String(LexString { 46 description: None, 47 format: Some(LexStringFormat::Datetime), 48 default: None, 49 min_length: None, 50 max_length: None, 51 min_graphemes: None, 52 max_graphemes: None, 53 r#enum: None, 54 r#const: None, 55 known_values: None, 56 }), 57 ); 58 59 let record_obj = LexObject { 60 description: None, 61 required: Some(vec!["text".into(), "timestamp".into()]), 62 nullable: None, 63 properties, 64 }; 65 66 let record = LexRecord { 67 description: Some("Simple record type".into()), 68 key: Some("tid".into()), 69 record: LexRecordRecord::Object(record_obj), 70 }; 71 72 let mut defs = BTreeMap::new(); 73 defs.insert("main".into(), LexUserType::Record(record)); 74 75 LexiconDoc { 76 lexicon: Lexicon::Lexicon1, 77 id: Self::nsid().into(), 78 revision: None, 79 description: Some("Test schema".into()), 80 defs, 81 } 82 } 83 84 fn validate(&self) -> Result<(), ConstraintError> { 85 // Check text length 86 if self.text.len() > 1000 { 87 return Err(ConstraintError::MaxLength { 88 path: ValidationPath::from_field("text"), 89 max: 1000, 90 actual: self.text.len(), 91 }); 92 } 93 94 Ok(()) 95 } 96} 97 98#[test] 99fn test_manual_impl_generates_valid_schema() { 100 let doc = SimpleRecord::lexicon_doc(); 101 102 // Verify structure 103 assert_eq!(doc.id.as_ref(), "com.example.simple"); 104 assert!(doc.defs.contains_key("main")); 105 106 // Serialize to JSON 107 let json = serde_json::to_string_pretty(&doc).expect("serialize"); 108 println!("{}", json); 109 110 // Should be valid lexicon JSON 111 assert!(json.contains("\"lexicon\": 1")); 112 assert!(json.contains("\"id\": \"com.example.simple\"")); 113} 114 115#[test] 116fn test_validation_works() { 117 let record = SimpleRecord { 118 text: "a".repeat(5000).into(), // Too long 119 timestamp: Datetime::now(), 120 }; 121 122 let result = record.validate(); 123 assert!(result.is_err()); 124 125 let err = result.unwrap_err(); 126 assert!(matches!(err, ConstraintError::MaxLength { .. })); 127} 128 129#[test] 130fn test_validation_passes() { 131 let record = SimpleRecord { 132 text: "Hello, world!".into(), 133 timestamp: Datetime::now(), 134 }; 135 136 let result = record.validate(); 137 assert!(result.is_ok()); 138}