use crate::validation::data_errors::DataValidationError;
use crate::validation::syntax::{validate_at_identifier, validate_nsid, validate_record_key};
pub fn validate_at_uri(value: &str) -> Result<(), DataValidationError> {
if value.is_empty() {
return Err(DataValidationError::StringFormatInvalid {
format: "at-uri".to_string(),
value: value.to_string(),
reason: "AT-URI cannot be empty".to_string(),
});
}
let rest =
value
.strip_prefix("at://")
.ok_or_else(|| DataValidationError::StringFormatInvalid {
format: "at-uri".to_string(),
value: value.to_string(),
reason: "AT-URI must start with 'at://'".to_string(),
})?;
if rest.is_empty() {
return Err(DataValidationError::StringFormatInvalid {
format: "at-uri".to_string(),
value: value.to_string(),
reason: "AT-URI must have an authority".to_string(),
});
}
if value.contains('#') {
return Err(DataValidationError::StringFormatInvalid {
format: "at-uri".to_string(),
value: value.to_string(),
reason: "AT-URI must not contain a fragment".to_string(),
});
}
if value.contains('?') {
return Err(DataValidationError::StringFormatInvalid {
format: "at-uri".to_string(),
value: value.to_string(),
reason: "AT-URI must not contain query parameters".to_string(),
});
}
let segments: Vec<&str> = rest.splitn(3, '/').collect();
let authority = segments[0];
validate_at_identifier(authority).map_err(|_| DataValidationError::StringFormatInvalid {
format: "at-uri".to_string(),
value: value.to_string(),
reason: format!("invalid authority: {}", authority),
})?;
if segments.len() > 1 && !segments[1].is_empty() {
let collection = segments[1];
validate_nsid(collection).map_err(|_| DataValidationError::StringFormatInvalid {
format: "at-uri".to_string(),
value: value.to_string(),
reason: format!("invalid collection NSID: {}", collection),
})?;
if segments.len() > 2 && !segments[2].is_empty() {
let rkey = segments[2];
validate_record_key(rkey).map_err(|_| DataValidationError::StringFormatInvalid {
format: "at-uri".to_string(),
value: value.to_string(),
reason: format!("invalid record key: {}", rkey),
})?;
}
}
if value.len() > 8192 {
return Err(DataValidationError::StringFormatInvalid {
format: "at-uri".to_string(),
value: value.to_string(),
reason: "AT-URI exceeds maximum length of 8192 bytes".to_string(),
});
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_at_uris() {
let valid = [
"at://did:plc:asdf123",
"at://did:plc:asdf123/app.bsky.feed.post",
"at://did:plc:asdf123/app.bsky.feed.post/3jui7kd54zh2y",
"at://user.bsky.social",
"at://user.bsky.social/app.bsky.feed.post",
];
for uri in valid {
assert!(validate_at_uri(uri).is_ok(), "should be valid: {}", uri);
}
}
#[test]
fn test_invalid_at_uris() {
let invalid = [
"",
"http://example.com",
"at://",
"at://did:plc:asdf123#fragment",
"at://did:plc:asdf123?query=1",
"at://invalid",
];
for uri in invalid {
assert!(validate_at_uri(uri).is_err(), "should be invalid: {}", uri);
}
}
}