api: validated newtypes from their checked constructors

Lewis: May this revision serve well! <lu5a@proton.me>
This commit is contained in:
Lewis
2026-07-25 08:27:40 +03:00
committed by Tangled
parent 2a96a8f420
commit b6274bb3c4
30 changed files with 181 additions and 111 deletions
+2 -1
View File
@@ -200,7 +200,8 @@ pub fn create_self_hosted_did_web(handle: &str) -> Result<Did, ApiError> {
return Err(ApiError::SelfHostedDidWebDisabled);
}
let encoded_handle = handle.replace(':', "%3A");
Ok(Did::from(format!("did:web:{}", encoded_handle)))
Did::new(format!("did:web:{}", encoded_handle))
.map_err(|_| ApiError::InvalidHandle(Some("Handle is not a valid did:web".into())))
}
pub enum CredentialMatch {
+1 -1
View File
@@ -392,7 +392,7 @@ pub async fn create_delegated_account(
controller_scopes: input.controller_scopes.as_str().to_string(),
encrypted_key_bytes: repo.encrypted_key_bytes,
encryption_version: tranquil_pds::config::ENCRYPTION_VERSION,
commit_cid: CidLink::from(repo.commit_cid.to_string()),
commit_cid: CidLink::from(&repo.commit_cid),
repo_rev: repo.repo_rev.clone(),
genesis_block_cids: repo.genesis_block_cids,
};
+14 -11
View File
@@ -149,17 +149,21 @@ async fn handle_command(state: AppState, interaction: Interaction) -> Response {
}
};
let handle = parse_start_handle(interaction.data.as_ref().and_then(|d| d.options.as_deref()));
if let Some(ref h) = handle
&& Handle::new(h).is_err()
let handle = match parse_start_handle(
interaction.data.as_ref().and_then(|d| d.options.as_deref()),
)
.map(Handle::new)
.transpose()
{
return Json(json!({
"type": 4,
"data": {"content": "Invalid handle format. Handle should look like: alice.example.com", "flags": 64}
}))
.into_response();
}
Ok(h) => h,
Err(_) => {
return Json(json!({
"type": 4,
"data": {"content": "Invalid handle format. Handle should look like: nel.oyster.cafe", "flags": 64}
}))
.into_response();
}
};
debug!(
discord_username = %discord_username,
@@ -168,7 +172,6 @@ async fn handle_command(state: AppState, interaction: Interaction) -> Response {
"Received /start from Discord user"
);
let handle = handle.map(Handle::from);
match state
.repos
.user
+3 -1
View File
@@ -51,7 +51,9 @@ pub async fn resolve_handle(
}
};
let cache_key = tranquil_pds::cache_keys::handle_key(&handle);
if let Some(did) = state.cache.get(&cache_key).await {
if let Some(cached) = state.cache.get(&cache_key).await
&& let Ok(did) = tranquil_pds::Did::new(cached)
{
return DidResponse::response(did).into_response();
}
let user = state.repos.user.get_by_handle(&handle).await;
@@ -109,7 +109,7 @@ pub async fn init_genesis_repo(
encrypted_key_bytes,
commit_cid,
mst_root_cid: mst_root,
repo_rev: Tid::from(rev.as_ref().to_string()),
repo_rev: Tid::from(rev.clone()),
genesis_block_cids: vec![mst_root.to_bytes(), commit_cid.to_bytes()],
})
}
@@ -198,7 +198,7 @@ pub async fn sequence_new_account(
if let Err(e) = tranquil_pds::repo_ops::sequence_sync_event(
state,
did,
&CidLink::from(repo.commit_cid.to_string()),
&CidLink::from(&repo.commit_cid),
Some(&repo.repo_rev),
)
.await
+5 -2
View File
@@ -6,6 +6,7 @@ use axum::{
};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use std::sync::LazyLock;
use tracing::{error, info, warn};
use tranquil_pds::api::ApiError;
use tranquil_pds::api::proxy_client::{is_ssrf_safe, proxy_client};
@@ -13,6 +14,9 @@ use tranquil_pds::auth::{AnyUser, Auth};
use tranquil_pds::state::AppState;
use tranquil_pds::types::{Did, Nsid};
static CREATE_REPORT_NSID: LazyLock<Nsid> =
LazyLock::new(|| "com.atproto.moderation.createReport".parse().unwrap());
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ReportReasonType {
#[serde(rename = "com.atproto.moderation.defs#reasonSpam")]
@@ -145,11 +149,10 @@ async fn proxy_to_report_service(
},
};
let report_lxm = Nsid::from("com.atproto.moderation.createReport".to_string());
let service_token = match tranquil_pds::auth::create_service_token(
&auth_user.did,
service_did,
Some(&report_lxm),
Some(&CREATE_REPORT_NSID),
&key_bytes,
) {
Ok(t) => t,
+5 -1
View File
@@ -11,6 +11,7 @@ use multihash::Multihash;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::pin::Pin;
use std::sync::LazyLock;
use tracing::{debug, error, info, warn};
use tranquil_pds::api::error::{ApiError, DbResultExt};
use tranquil_pds::auth::{Auth, AuthAny, NotTakendown, Permissive, VerifyScope};
@@ -19,6 +20,9 @@ use tranquil_pds::state::AppState;
use tranquil_pds::types::{CidLink, Did, Nsid};
use tranquil_pds::util::get_header_str;
static UPLOAD_BLOB_NSID: LazyLock<Nsid> =
LazyLock::new(|| "com.atproto.repo.uploadBlob".parse().unwrap());
fn detect_mime_type(data: &[u8], client_hint: &str) -> String {
if let Some(kind) = infer::get(data) {
let detected = kind.mime_type().to_string();
@@ -48,7 +52,7 @@ pub async fn upload_blob(
) -> Result<Response, ApiError> {
let (did, controller_did): (Did, Option<Did>) = match &auth {
AuthAny::Service(service) => {
service.require_lxm(&Nsid::from("com.atproto.repo.uploadBlob".to_string()))?;
service.require_lxm(&UPLOAD_BLOB_NSID)?;
(service.did.clone(), None)
}
AuthAny::User(user) => {
@@ -69,7 +69,8 @@ pub async fn create_passkey_account(
let token = extracted.token;
if is_service_token(&token) {
let verifier = ServiceTokenVerifier::new();
let create_account_lxm = Nsid::from("com.atproto.server.createAccount".to_string());
let create_account_lxm = Nsid::new("com.atproto.server.createAccount")
.expect("com.atproto.server.createAccount is a valid NSID");
match verifier
.verify_service_token(&token, Some(&create_account_lxm))
.await
@@ -313,7 +314,7 @@ pub async fn create_passkey_account(
encrypted_key_bytes: repo.encrypted_key_bytes,
encryption_version: tranquil_pds::config::ENCRYPTION_VERSION,
reserved_key_id,
commit_cid: tranquil_types::CidLink::from(repo.commit_cid.to_string()),
commit_cid: tranquil_types::CidLink::from(&repo.commit_cid),
repo_rev: repo.repo_rev.clone(),
genesis_block_cids: repo.genesis_block_cids,
invite_code: invite_registration.into_invite_code(),
+17 -9
View File
@@ -45,15 +45,23 @@ pub async fn request_password_reset(
None
};
let user_id = match state
.repos
.user
.get_id_by_email_or_handle(
normalized,
&Handle::from(normalized_handle.as_str().to_string()),
)
.await
{
let lookup = match Handle::new(normalized_handle.as_str()) {
Ok(handle) => {
state
.repos
.user
.get_id_by_email_or_handle(normalized, &handle)
.await
}
Err(_) => state
.repos
.user
.get_by_email(normalized)
.await
.map(|user| user.map(|user| user.id)),
};
let user_id = match lookup {
Ok(Some(id)) => id,
Ok(None) => {
info!("Password reset requested for unknown identifier");
@@ -21,7 +21,8 @@ fn public_key_to_did_key(signing_key: &SigningKey) -> Did {
multicodec_key.extend_from_slice(&SECP256K1_MULTICODEC_PREFIX);
multicodec_key.extend_from_slice(&compressed_pubkey);
let encoded = multibase::encode(multibase::Base::Base58Btc, &multicodec_key);
Did::from(format!("did:key:{}", encoded))
Did::new(format!("did:key:{}", encoded))
.expect("did:key with a multibase-encoded secp256k1 key is a valid DID")
}
#[derive(Deserialize)]
+14 -2
View File
@@ -62,8 +62,20 @@ pub async fn handle_telegram_webhook(
&& let Some(from) = message.from
&& let Some(username) = from.username
{
let handle =
parse_start_handle(message.text.as_deref()).map(tranquil_types::Handle::from);
let handle = match parse_start_handle(message.text.as_deref())
.map(tranquil_types::Handle::new)
.transpose()
{
Ok(h) => h,
Err(e) => {
warn!(
telegram_username = %username,
error = %e,
"Ignoring /start with an invalid handle"
);
return StatusCode::OK;
}
};
debug!(
telegram_username = %username,
+10 -11
View File
@@ -58,9 +58,16 @@ pub async fn dereference_scope(
for part in scope_parts {
if let Some(cid_str) = part.strip_prefix("ref:") {
let cache_key = tranquil_pds::cache_keys::scope_ref_key(
&tranquil_types::CidLink::from(cid_str.to_string()),
);
let cid = match Cid::from_str(cid_str) {
Ok(c) => c,
Err(_) => {
tracing::warn!("Invalid CID in scope ref: {}", cid_str);
continue;
}
};
let cache_key =
tranquil_pds::cache_keys::scope_ref_key(&tranquil_types::CidLink::from(&cid));
if let Some(cached) = state.cache.get(&cache_key).await {
for s in cached.split_whitespace() {
if !resolved_scopes.contains(&s.to_string()) {
@@ -70,14 +77,6 @@ pub async fn dereference_scope(
continue;
}
let cid = match Cid::from_str(cid_str) {
Ok(c) => c,
Err(_) => {
tracing::warn!("Invalid CID in scope ref: {}", cid_str);
continue;
}
};
let block_bytes = match state.block_store.get(&cid).await {
Ok(Some(b)) => b,
Ok(None) => {
@@ -12,7 +12,16 @@ pub async fn register_complete(
_rate_limit: OAuthRateLimited<OAuthRegisterCompleteLimit>,
Json(form): Json<RegisterCompleteInput>,
) -> Response {
let did = Did::from(form.did.clone());
let Ok(did) = Did::new(form.did.as_str()) else {
return (
StatusCode::BAD_REQUEST,
Json(serde_json::json!({
"error": "invalid_request",
"error_description": "Invalid DID."
})),
)
.into_response();
};
let request_id = RequestId::from(form.request_uri.clone());
let request_data = match state
@@ -97,12 +106,12 @@ pub async fn register_complete(
}
if let Some(existing_did) = &request_data.did
&& existing_did != &form.did
&& existing_did != &did
{
tracing::warn!(
request_uri = %form.request_uri,
existing_did = %existing_did,
attempted_did = %form.did,
attempted_did = %did,
"register_complete attempted with different DID than already bound"
);
return (
@@ -1023,7 +1023,9 @@ pub async fn complete_registration(
}
let encoded_handle = handle.replace(':', "%3A");
let self_hosted_did =
tranquil_pds::types::Did::from(format!("did:web:{}", encoded_handle));
tranquil_pds::types::Did::new(format!("did:web:{}", encoded_handle)).map_err(
|_| ApiError::InvalidHandle(Some("Handle is not a valid did:web".into())),
)?;
tracing::info!(did = %self_hosted_did, "Creating self-hosted did:web SSO account");
self_hosted_did
}
@@ -1151,8 +1153,8 @@ pub async fn complete_registration(
.filter(|s| !s.is_empty()),
encrypted_key_bytes: encrypted_key_bytes.clone(),
encryption_version: tranquil_pds::config::ENCRYPTION_VERSION,
commit_cid: tranquil_types::CidLink::from(commit_cid.to_string()),
repo_rev: tranquil_types::Tid::from(rev.as_ref().to_string()),
commit_cid: tranquil_types::CidLink::from_cid(&commit_cid),
repo_rev: tranquil_types::Tid::from(rev.clone()),
genesis_block_cids,
invite_code: invite_registration.into_invite_code(),
birthdate_pref,
+2 -2
View File
@@ -28,8 +28,8 @@ pub struct DidResponse {
}
impl DidResponse {
pub fn response(did: impl Into<Did>) -> impl IntoResponse {
Json(Self { did: did.into() })
pub fn response(did: Did) -> impl IntoResponse {
Json(Self { did })
}
}
+8 -8
View File
@@ -189,7 +189,7 @@ mod tests {
#[tokio::test]
async fn test_create_and_validate_token() {
let cache = MockCache::new();
let did = Did::from("did:plc:teq".to_string());
let did = Did::new("did:plc:teq").expect("valid DID");
let token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail)
.await
@@ -206,7 +206,7 @@ mod tests {
#[tokio::test]
async fn test_token_consumed_after_use() {
let cache = MockCache::new();
let did = Did::from("did:plc:teq".to_string());
let did = Did::new("did:plc:teq").expect("valid DID");
let token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail)
.await
@@ -224,7 +224,7 @@ mod tests {
#[tokio::test]
async fn test_invalid_token_rejected() {
let cache = MockCache::new();
let did = Did::from("did:plc:teq".to_string());
let did = Did::new("did:plc:teq").expect("valid DID");
let _token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail)
.await
@@ -238,7 +238,7 @@ mod tests {
#[tokio::test]
async fn test_wrong_purpose_rejected() {
let cache = MockCache::new();
let did = Did::from("did:plc:teq".to_string());
let did = Did::new("did:plc:teq").expect("valid DID");
let token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail)
.await
@@ -253,7 +253,7 @@ mod tests {
async fn test_token_format() {
// The emitted token is the display form: uppercase `XXXXX-XXXXX`.
let cache = MockCache::new();
let did = Did::from("did:plc:teq".to_string());
let did = Did::new("did:plc:teq").expect("valid DID");
(0..50).for_each(|_| {
let token = futures::executor::block_on(create_email_token(
&cache,
@@ -270,7 +270,7 @@ mod tests {
#[tokio::test]
async fn test_case_insensitive_validation() {
let cache = MockCache::new();
let did = Did::from("did:plc:teq".to_string());
let did = Did::new("did:plc:teq").expect("valid DID");
let token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail)
.await
@@ -285,7 +285,7 @@ mod tests {
#[tokio::test]
async fn test_hyphen_insensitive_validation() {
let cache = MockCache::new();
let did = Did::from("did:plc:teq".to_string());
let did = Did::new("did:plc:teq").expect("valid DID");
let token = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail)
.await
@@ -300,7 +300,7 @@ mod tests {
#[tokio::test]
async fn test_noop_cache_returns_unavailable() {
let cache = crate::cache::NoOpCache;
let did = Did::from("did:plc:whelk".to_string());
let did = Did::new("did:plc:whelk").expect("valid DID");
let result = create_email_token(&cache, &did, EmailTokenPurpose::UpdateEmail).await;
assert_eq!(result.unwrap_err(), TokenError::CacheUnavailable);
+1 -1
View File
@@ -624,7 +624,7 @@ mod tests {
use super::*;
fn n(s: &str) -> crate::types::Nsid {
crate::types::Nsid::from(s.to_string())
crate::types::Nsid::new(s).expect("valid NSID")
}
#[test]
+3 -2
View File
@@ -561,7 +561,8 @@ pub fn did_for_genesis_op(signed_op: &Value) -> Result<Did, PlcError> {
let hash = hasher.finalize();
let encoded = base32::encode(Alphabet::Rfc4648Lower { padding: false }, &hash);
let truncated = &encoded[..24];
Ok(Did::from(format!("did:plc:{}", truncated)))
Ok(Did::new(format!("did:plc:{}", truncated))
.expect("did:plc with 24 base32 characters of a sha256 digest is a valid DID"))
}
pub fn validate_plc_operation(op: &Value) -> Result<PlcOpType, PlcError> {
@@ -784,7 +785,7 @@ mod tests {
let result = create_genesis_operation(
&key,
Some(operator_key),
&crate::types::Handle::from("whelk.nel.pet".to_string()),
&crate::types::Handle::new("whelk.nel.pet").expect("valid handle"),
"https://nel.pet",
)
.unwrap();
+13 -5
View File
@@ -227,15 +227,23 @@ fn walk_mst_node(
{
let blob_refs = find_blob_refs_ipld(&record_value, 0);
let parts: Vec<&str> = full_key.split('/').collect();
if parts.len() >= 2 {
let collection = Nsid::from(parts[..parts.len() - 1].join("/"));
let rkey = Rkey::from(parts[parts.len() - 1].to_string());
records.push(ImportedRecord {
let parsed = match parts.len() >= 2 {
true => Nsid::new(parts[..parts.len() - 1].join("/"))
.ok()
.zip(Rkey::new(parts[parts.len() - 1].to_string()).ok()),
false => None,
};
match parsed {
Some((collection, rkey)) => records.push(ImportedRecord {
collection,
rkey,
cid: record_cid,
blob_refs,
});
}),
None => tracing::warn!(
key = %full_key,
"skipping a CAR record whose MST key isn't a valid collection/rkey pair"
),
}
}
+2 -1
View File
@@ -100,7 +100,8 @@ impl CarVerifier {
.ok_or_else(|| VerifyError::BlockNotFound(root_cid.to_string()))?;
let commit =
Commit::from_cbor(root_block).map_err(|e| VerifyError::InvalidCommit(e.to_string()))?;
let commit_did = commit.did().to_string().into();
let commit_did = tranquil_types::Did::new(commit.did().to_string())
.map_err(|e| VerifyError::InvalidCommit(e.to_string()))?;
let data_cid = commit.data();
self.verify_mst_structure(data_cid, blocks)?;
debug!("MST structure verified for commit: {:?}", commit);
@@ -145,7 +145,7 @@ async fn sync_event_carries_inline_commit_block() {
let commit_bytes = b"synthetic sync commit block payload".to_vec();
let commit_cid = synthetic_cid(&commit_bytes);
let cid_link: CidLink = (&commit_cid).into();
let rev = tranquil_types::Tid::from("3kabcdefghij2".to_string());
let rev = tranquil_types::Tid::new("3kabcdefghij2").expect("valid TID");
let baseline = repos.repo.get_max_seq().await.expect("get_max_seq");
repos
@@ -192,7 +192,7 @@ async fn genesis_commit_event_carries_inline_blocks() {
let mst_root_cid = synthetic_cid(&mst_root_bytes);
let commit_link: CidLink = (&commit_cid).into();
let mst_link: CidLink = (&mst_root_cid).into();
let rev = tranquil_types::Tid::from("3kabcdefghij3".to_string());
let rev = tranquil_types::Tid::new("3kabcdefghij3").expect("valid TID");
let baseline = repos.repo.get_max_seq().await.expect("get_max_seq");
repos
+7 -7
View File
@@ -40,7 +40,7 @@ fn create_unsigned_jwt(header: &Value, claims: &Value) -> String {
#[test]
fn test_signature_attacks() {
let key_bytes = generate_user_key();
let did = Did::from("did:plc:whelk".to_string());
let did = Did::new("did:plc:whelk").expect("valid DID");
let token = create_access_token(&did, &key_bytes).expect("create token");
let parts: Vec<&str> = token.split('.').collect();
@@ -143,7 +143,7 @@ fn test_algorithm_substitution_attacks() {
#[test]
fn test_token_type_confusion() {
let key_bytes = generate_user_key();
let did = Did::from("did:plc:whelk".to_string());
let did = Did::new("did:plc:whelk").expect("valid DID");
let refresh_token = create_refresh_token(&did, &key_bytes).expect("create refresh token");
let result = verify_access_token(&refresh_token, &key_bytes);
@@ -169,8 +169,8 @@ fn test_token_type_confusion() {
let service_token = create_service_token(
&did,
&Did::from("did:web:nel.pet".to_string()),
Some(&Nsid::from("cafe.oyster.method".to_string())),
&Did::new("did:web:nel.pet").expect("valid DID"),
Some(&Nsid::new("cafe.oyster.method").expect("valid NSID")),
&key_bytes,
)
.unwrap();
@@ -434,7 +434,7 @@ fn test_claim_validation() {
#[test]
fn test_did_and_jti_extraction() {
let key_bytes = generate_user_key();
let did = Did::from("did:plc:limpet".to_string());
let did = Did::new("did:plc:limpet").expect("valid DID");
let token = create_access_token(&did, &key_bytes).expect("create token");
assert_eq!(get_did_from_token(&token).unwrap(), did);
@@ -459,7 +459,7 @@ fn test_did_and_jti_extraction() {
#[test]
fn test_header_injection_and_constant_time() {
let key_bytes = generate_user_key();
let did = Did::from("did:plc:whelk".to_string());
let did = Did::new("did:plc:whelk").expect("valid DID");
let header = json!({
"alg": "ES256K", "typ": TokenType::Access.as_str(),
@@ -501,7 +501,7 @@ async fn test_server_rejects_invalid_tokens() {
let key_bytes = generate_user_key();
let forged_token =
create_access_token(&Did::from("did:plc:lyna".to_string()), &key_bytes).unwrap();
create_access_token(&Did::new("did:plc:lyna").expect("valid DID"), &key_bytes).unwrap();
let res = http_client
.get(format!("{}/xrpc/com.atproto.server.getSession", url))
.header("Authorization", format!("Bearer {}", forged_token))
@@ -10,7 +10,7 @@ fn now() -> String {
}
fn c(s: &str) -> Nsid {
Nsid::from(s.to_string())
Nsid::new(s).expect("valid NSID")
}
#[test]
+3 -3
View File
@@ -512,9 +512,9 @@ async fn follow_uris_pointing_to(repo_id: uuid::Uuid, target_did: &str) -> Vec<S
let repos = get_test_repos().await;
let probe = Backlink {
uri: AtUri::from_parts(
&Did::from("did:plc:periwinkle".to_string()),
&Nsid::from("app.bsky.graph.follow".to_string()),
&Rkey::from("probe".to_string()),
&Did::new("did:plc:periwinkle").expect("valid DID"),
&Nsid::new("app.bsky.graph.follow").expect("valid NSID"),
&Rkey::new("probe").expect("valid rkey"),
),
path: BacklinkPath::Subject,
link_to: target_did.to_string(),
@@ -6,7 +6,7 @@ use tranquil_pds::oauth::scopes::{
use tranquil_types::Nsid;
fn c(s: &str) -> Nsid {
Nsid::from(s.to_string())
Nsid::new(s).expect("valid NSID")
}
#[test]
+9 -3
View File
@@ -48,7 +48,9 @@ async fn test_reserve_signing_key_with_did() {
assert!(signing_key.starts_with("did:key:z"));
let row = repos
.infra
.get_reserved_signing_key_full(&tranquil_types::Did::from(signing_key.to_string()))
.get_reserved_signing_key_full(
&tranquil_types::Did::new(signing_key.to_string()).expect("valid DID"),
)
.await
.expect("db error")
.expect("Reserved key not found in database");
@@ -75,7 +77,9 @@ async fn test_reserve_signing_key_stores_private_key() {
let signing_key = body["signingKey"].as_str().unwrap();
let row = repos
.infra
.get_reserved_signing_key_full(&tranquil_types::Did::from(signing_key.to_string()))
.get_reserved_signing_key_full(
&tranquil_types::Did::new(signing_key.to_string()).expect("valid DID"),
)
.await
.expect("db error")
.expect("Reserved key not found in database");
@@ -185,7 +189,9 @@ async fn test_create_account_with_reserved_signing_key() {
assert!(!access_jwt.is_empty());
let reserved = repos
.infra
.get_reserved_signing_key_full(&tranquil_types::Did::from(signing_key.to_string()))
.get_reserved_signing_key_full(
&tranquil_types::Did::new(signing_key.to_string()).expect("valid DID"),
)
.await
.expect("db error")
.expect("Reserved key not found");
@@ -174,7 +174,8 @@ impl BacklinkOps {
MetastoreError::CorruptData("corrupt backlink value in indexes partition"),
)?;
let uri: AtUri = val.source_uri.into();
let uri = AtUri::new(val.source_uri)
.map_err(|_| MetastoreError::CorruptData("corrupt backlink source_uri"))?;
let matches_collection = uri.collection().is_some_and(|c| c == collection_str);
let matches_path = match discriminant_to_path(val.path) {
Some(p) => p == bl.path,
@@ -236,13 +237,6 @@ mod tests {
}
}
fn test_cid_link(seed: u8) -> tranquil_types::CidLink {
let digest: [u8; 32] = std::array::from_fn(|i| seed.wrapping_add(i as u8));
let mh = multihash::Multihash::<64>::wrap(0x12, &digest).unwrap();
let c = cid::Cid::new_v1(0x71, mh);
tranquil_types::CidLink::from_cid(&c)
}
fn test_rev(seq: u64) -> tranquil_types::Tid {
const ALPHABET: &[u8] = b"234567abcdefghijklmnopqrstuvwxyz";
let s: String = (0..13)
@@ -252,10 +246,17 @@ mod tests {
tranquil_types::Tid::new(s).expect("generated TID is valid")
}
fn test_cid_link(seed: u8) -> tranquil_types::CidLink {
let digest: [u8; 32] = std::array::from_fn(|i| seed.wrapping_add(i as u8));
let mh = multihash::Multihash::<64>::wrap(0x12, &digest).unwrap();
let c = cid::Cid::new_v1(0x71, mh);
tranquil_types::CidLink::from_cid(&c)
}
fn create_repo(h: &TestHarness, name: &str, seed: u8) -> (Uuid, UserHash) {
let user_id = Uuid::new_v4();
let did = Did::from(format!("did:plc:{name}"));
let handle = Handle::from(format!("{name}.test.invalid"));
let did = Did::new(format!("did:plc:{name}")).expect("test DID is well-formed");
let handle = Handle::new(format!("{name}.oyster.cafe")).expect("test handle is valid");
let cid = test_cid_link(seed);
h.metastore
.repo_ops()
@@ -414,7 +415,7 @@ mod tests {
link_to: "at://did:plc:someone/app.bsky.feed.post/3k2p1".to_string(),
}];
let collection = Nsid::from("app.bsky.feed.like".to_string());
let collection = Nsid::new("app.bsky.feed.like").expect("test collection is a valid NSID");
let conflicts = ops
.get_backlink_conflicts(user_id, &collection, &proposed)
.unwrap();
@@ -449,7 +450,8 @@ mod tests {
link_to: "at://did:plc:someone/app.bsky.feed.post/3k2p1".to_string(),
}];
let collection = Nsid::from("app.bsky.feed.repost".to_string());
let collection =
Nsid::new("app.bsky.feed.repost").expect("test collection is a valid NSID");
let conflicts = ops
.get_backlink_conflicts(user_id, &collection, &proposed)
.unwrap();
@@ -480,7 +482,8 @@ mod tests {
link_to: "did:plc:target".to_string(),
}];
let collection = Nsid::from("app.bsky.graph.follow".to_string());
let collection =
Nsid::new("app.bsky.graph.follow").expect("test collection is a valid NSID");
let conflicts = ops
.get_backlink_conflicts(user_id, &collection, &proposed)
.unwrap();
@@ -512,7 +515,7 @@ mod tests {
link_to: "at://did:plc:target/app.bsky.feed.post/3k2p1".to_string(),
}];
let collection = Nsid::from("app.bsky.feed.like".to_string());
let collection = Nsid::new("app.bsky.feed.like").expect("test collection is a valid NSID");
let conflicts = ops
.get_backlink_conflicts(user_id_b, &collection, &proposed)
.unwrap();
@@ -543,7 +546,7 @@ mod tests {
link_to: "at://did:plc:someone/app.bsky.feed.post/3k2p1".to_string(),
}];
let collection = Nsid::from("app.bsky.feed.like".to_string());
let collection = Nsid::new("app.bsky.feed.like").expect("test collection is a valid NSID");
let conflicts = ops
.get_backlink_conflicts(user_id, &collection, &proposed)
.unwrap();
@@ -557,7 +560,7 @@ mod tests {
let ops = h.metastore.backlink_ops();
let (user_id, _user_hash) = create_repo(&h, "bailey", 9);
let collection = Nsid::from("app.bsky.feed.like".to_string());
let collection = Nsid::new("app.bsky.feed.like").expect("test collection is a valid NSID");
let conflicts = ops
.get_backlink_conflicts(user_id, &collection, &[])
.unwrap();
@@ -647,7 +650,7 @@ mod tests {
},
];
let collection = Nsid::from("app.bsky.feed.like".to_string());
let collection = Nsid::new("app.bsky.feed.like").expect("test collection is a valid NSID");
let conflicts = ops
.get_backlink_conflicts(user_id, &collection, &proposed)
.unwrap();
@@ -363,14 +363,19 @@ impl BlobOps {
})?;
let start = cursor.map_or(Bound::Unbounded, Bound::Excluded);
Ok(missing
missing
.range::<str, _>((start, Bound::Unbounded))
.take(limit)
.map(|(cid_str, uri)| tranquil_db_traits::MissingBlobInfo {
blob_cid: CidLink::from(cid_str.clone()),
record_uri: tranquil_types::AtUri::from(uri.clone()),
.map(|(cid_str, uri)| {
Ok(tranquil_db_traits::MissingBlobInfo {
blob_cid: CidLink::new(cid_str.clone()).map_err(|_| {
MetastoreError::CorruptData("corrupt record_blobs blob cid")
})?,
record_uri: tranquil_types::AtUri::new(uri.clone())
.map_err(|_| MetastoreError::CorruptData("corrupt record_blobs uri"))?,
})
})
.collect())
.collect()
}
fn collect_referenced_cid_bytes(
@@ -445,7 +450,7 @@ fn parse_blob_cid_from_key(key: &[u8]) -> Result<CidLink, MetastoreError> {
))?;
reader
.string()
.map(CidLink::from)
.and_then(|s| CidLink::new(s).ok())
.ok_or(MetastoreError::CorruptData("corrupt blob key: missing cid"))
}
@@ -211,7 +211,8 @@ impl SessionOps {
}
Ok(RefreshGraceLookup::Compromised {
did: Did::from(session.did.clone()),
did: Did::new(session.did.clone())
.map_err(|_| MetastoreError::CorruptData("corrupt session did"))?,
session_id: SessionId::new(session_id),
key_bytes: user.key_bytes,
encryption_version: user.encryption_version,
+1 -1
View File
@@ -799,7 +799,7 @@ mod tests {
let mut user = UserValue {
id: uuid::Uuid::new_v4(),
did: "did:plc:test".to_owned(),
handle: "t.invalid".to_owned(),
handle: "teq.oyster.cafe".to_owned(),
email: None,
email_verified: false,
password_hash: None,