Something went wrong. Try again.
atproto made easy crates.io/crates/jacquard
atproto rust
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250//! Generate LexiconSchema trait implementations for generated types
use crate::lexicon::{ LexInteger, LexObject, LexObjectProperty, LexRecordRecord, LexString, LexStringFormat, LexUserType, LexiconDoc,};use crate::schema::from_ast::{ConstraintCheck, ValidationCheck};
/// Extract validation checks from a LexiconDoc////// Walks the lexicon structure and builds ValidationCheck structs for all/// constraint fields (max_length, max_graphemes, minimum, maximum, etc.)pub(crate) fn extract_validation_checks(doc: &LexiconDoc, def_name: &str) -> Vec<ValidationCheck> { let mut checks = Vec::new();
// Get the specified def if let Some(def) = doc.defs.get(def_name) { match def { LexUserType::Record(rec) => match &rec.record { LexRecordRecord::Object(obj) => { checks.extend(extract_object_validations(obj)); } }, LexUserType::Object(obj) => { checks.extend(extract_object_validations(obj)); } // XRPC types, tokens, etc. don't need validation _ => {} } }
checks}
/// Extract validation checks from an object's propertiesfn extract_object_validations(obj: &LexObject) -> Vec<ValidationCheck> { let mut checks = Vec::new();
for (schema_name, prop) in &obj.properties { // Convert schema name to field name (snake_case, with r# prefix for keywords) let field_name = field_name_from_schema(schema_name);
// Check if required let is_required = obj .required .as_ref() .map(|req| req.iter().any(|r| r == schema_name)) .unwrap_or(false);
// Extract checks from property checks.extend(extract_property_validations( &field_name, schema_name.as_ref(), prop, is_required, )); }
checks}
/// Extract validation checks from a single propertyfn extract_property_validations( field_name: &str, schema_name: &str, prop: &LexObjectProperty, is_required: bool,) -> Vec<ValidationCheck> { let mut checks = Vec::new();
match prop { LexObjectProperty::String(s) => { checks.extend(extract_string_validations( field_name, schema_name, s, is_required, )); } LexObjectProperty::Integer(i) => { checks.extend(extract_integer_validations( field_name, schema_name, i, is_required, )); } LexObjectProperty::Array(arr) => { if let Some(max) = arr.max_length { checks.push(ValidationCheck { field_name: field_name.to_string(), schema_name: schema_name.to_string(), field_type: "Vec<_>".to_string(), is_required, is_array: true, check: ConstraintCheck::MaxLength { max }, }); } if let Some(min) = arr.min_length { checks.push(ValidationCheck { field_name: field_name.to_string(), schema_name: schema_name.to_string(), field_type: "Vec<_>".to_string(), is_required, is_array: true, check: ConstraintCheck::MinLength { min }, }); } } LexObjectProperty::Blob(b) => { if let Some(max) = b.max_size { checks.push(ValidationCheck { field_name: field_name.to_string(), schema_name: schema_name.to_string(), field_type: "BlobRef".to_string(), is_required, is_array: false, check: ConstraintCheck::BlobMaxSize { max }, }); } if let Some(accept) = &b.accept { if !accept.is_empty() { checks.push(ValidationCheck { field_name: field_name.to_string(), schema_name: schema_name.to_string(), field_type: "BlobRef".to_string(), is_required, is_array: false, check: ConstraintCheck::BlobAccept { accept: accept.iter().map(|m| m.as_str().to_string()).collect(), }, }); } } } _ => { // Other types don't have runtime validations in the current impl. } }
checks}
/// Extract validation checks from a string propertyfn extract_string_validations( field_name: &str, schema_name: &str, string: &LexString, is_required: bool,) -> Vec<ValidationCheck> { let mut checks = Vec::new();
// Datetime maps to `chrono::DateTime<FixedOffset>` which does not implement // `AsRef<str>`, so length checks cannot be emitted for it. All other formats // (did, handle, at-uri, cid, nsid, tid, record-key, language, uri, etc.) use // string-backed wrapper types that do implement `AsRef<str>`. if matches!(string.format, Some(LexStringFormat::Datetime)) { return checks; }
if let Some(max) = string.max_length { checks.push(ValidationCheck { field_name: field_name.to_string(), schema_name: schema_name.to_string(), field_type: "String".to_string(), is_required, is_array: false, check: ConstraintCheck::MaxLength { max }, }); }
if let Some(min) = string.min_length { checks.push(ValidationCheck { field_name: field_name.to_string(), schema_name: schema_name.to_string(), field_type: "String".to_string(), is_required, is_array: false, check: ConstraintCheck::MinLength { min }, }); }
if let Some(max) = string.max_graphemes { checks.push(ValidationCheck { field_name: field_name.to_string(), schema_name: schema_name.to_string(), field_type: "String".to_string(), is_required, is_array: false, check: ConstraintCheck::MaxGraphemes { max }, }); }
if let Some(min) = string.min_graphemes { checks.push(ValidationCheck { field_name: field_name.to_string(), schema_name: schema_name.to_string(), field_type: "String".to_string(), is_required, is_array: false, check: ConstraintCheck::MinGraphemes { min }, }); }
checks}
/// Extract validation checks from an integer propertyfn extract_integer_validations( field_name: &str, schema_name: &str, integer: &LexInteger, is_required: bool,) -> Vec<ValidationCheck> { let mut checks = Vec::new();
if let Some(max) = integer.maximum { checks.push(ValidationCheck { field_name: field_name.to_string(), schema_name: schema_name.to_string(), field_type: "i64".to_string(), is_required, is_array: false, check: ConstraintCheck::Maximum { max }, }); }
if let Some(min) = integer.minimum { checks.push(ValidationCheck { field_name: field_name.to_string(), schema_name: schema_name.to_string(), field_type: "i64".to_string(), is_required, is_array: false, check: ConstraintCheck::Minimum { min }, }); }
checks}
/// Convert schema field name to the Rust field identifier////// Returns snake_case field name without r# prefix/// (the r# will be added by make_ident when generating tokens)fn field_name_from_schema(schema_name: &str) -> String { use heck::ToSnakeCase; schema_name.to_snake_case()}