···6565data-encoding = "2.5"
6666dirs = "6"
6767ecdsa = { version = "0.16", features = ["std"] }
6868+ed25519-dalek = { version = "2.1", features = ["std", "rand_core"] }
6869elliptic-curve = { version = "0.13", features = ["jwk", "serde"] }
6970futures = "0.3"
7071hickory-resolver = { version = "0.25" }
7272+idna = "1.0"
7173http = "1.3"
7274indexmap = { version = "2", features = ["serde"] }
7375itoa = "1.0"
+2-2
crates/atproto-attestation/src/cid.rs
···226226227227 // Define test record type with createdAt and text fields
228228 #[derive(Serialize, Deserialize, PartialEq, Clone)]
229229- #[cfg_attr(debug_assertions, derive(Debug))]
229229+ #[cfg_attr(any(debug_assertions, test), derive(Debug))]
230230 struct TestRecord {
231231 #[serde(rename = "createdAt", with = "datetime_format")]
232232 created_at: DateTime<Utc>,
···241241242242 // Define test metadata type
243243 #[derive(Serialize, Deserialize, PartialEq, Clone)]
244244- #[cfg_attr(debug_assertions, derive(Debug))]
244244+ #[cfg_attr(any(debug_assertions, test), derive(Debug))]
245245 struct TestMetadata {
246246 #[serde(rename = "createdAt", with = "datetime_format")]
247247 created_at: DateTime<Utc>,
+1-1
crates/atproto-client/src/com_atproto_identity.rs
···1818///
1919/// This enum represents either a successful handle resolution containing a DID,
2020/// or an error response from the server.
2121-#[cfg_attr(debug_assertions, derive(Debug))]
2121+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
2222#[derive(Deserialize, Clone)]
2323#[serde(untagged)]
2424pub enum ResolveHandleResponse {
+9-9
crates/atproto-client/src/com_atproto_repo.rs
···3939};
40404141/// Response from getting a record from an AT Protocol repository.
4242-#[cfg_attr(debug_assertions, derive(Debug))]
4242+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
4343#[derive(Deserialize, Clone)]
4444#[serde(untagged)]
4545pub enum GetRecordResponse {
···133133}
134134135135/// A single record in a list records response.
136136-#[cfg_attr(debug_assertions, derive(Debug))]
136136+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
137137#[derive(Deserialize, Clone)]
138138pub struct ListRecord<T> {
139139 /// AT-URI of the record
···145145}
146146147147/// Response from listing records in an AT Protocol repository.
148148-#[cfg_attr(debug_assertions, derive(Debug))]
148148+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
149149#[derive(Deserialize, Clone)]
150150pub struct ListRecordsResponse<T> {
151151 /// Pagination cursor for retrieving more records
···251251}
252252253253/// Request to create a new record in an AT Protocol repository.
254254-#[cfg_attr(debug_assertions, derive(Debug))]
254254+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
255255#[derive(Serialize, Deserialize, Clone)]
256256#[serde(bound = "T: Serialize + DeserializeOwned")]
257257pub struct CreateRecordRequest<T: DeserializeOwned> {
···280280}
281281282282/// Response from creating a record in an AT Protocol repository.
283283-#[cfg_attr(debug_assertions, derive(Debug))]
283283+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
284284#[derive(Deserialize, Clone)]
285285#[serde(untagged)]
286286pub enum CreateRecordResponse {
···340340}
341341342342/// Request to update an existing record in an AT Protocol repository.
343343-#[cfg_attr(debug_assertions, derive(Debug))]
343343+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
344344#[derive(Serialize, Deserialize, Clone)]
345345#[serde(bound = "T: Serialize + DeserializeOwned")]
346346pub struct PutRecordRequest<T: DeserializeOwned> {
···377377}
378378379379/// Response from updating a record in an AT Protocol repository.
380380-#[cfg_attr(debug_assertions, derive(Debug))]
380380+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
381381#[derive(Serialize, Deserialize, Clone)]
382382#[serde(untagged)]
383383pub enum PutRecordResponse {
···437437}
438438439439/// Request to delete a record from an AT Protocol repository.
440440-#[cfg_attr(debug_assertions, derive(Debug))]
440440+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
441441#[derive(Serialize, Deserialize, Clone)]
442442pub struct DeleteRecordRequest {
443443 /// Repository identifier (DID)
···467467}
468468469469/// Response from deleting a record in an AT Protocol repository.
470470-#[cfg_attr(debug_assertions, derive(Debug))]
470470+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
471471#[derive(Deserialize, Clone)]
472472#[serde(untagged)]
473473pub enum DeleteRecordResponse {
+4-4
crates/atproto-client/src/com_atproto_server.rs
···2929};
30303131/// Request to create a new authentication session.
3232-#[cfg_attr(debug_assertions, derive(Debug))]
3232+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
3333#[derive(Serialize, Deserialize, Clone)]
3434pub struct CreateSessionRequest {
3535 /// Handle or other identifier supported by the server for the authenticating user
···4242}
43434444/// App password session data returned from successful authentication.
4545-#[cfg_attr(debug_assertions, derive(Debug))]
4545+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
4646#[derive(Deserialize, Clone)]
4747pub struct AppPasswordSession {
4848 /// Distributed identifier for the authenticated account
···6060}
61616262/// Response from refreshing an authentication session.
6363-#[cfg_attr(debug_assertions, derive(Debug))]
6363+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
6464#[derive(Deserialize, Clone)]
6565pub struct RefreshSessionResponse {
6666 /// Distributed identifier for the authenticated account
···8282}
83838484/// Response from creating a new app password.
8585-#[cfg_attr(debug_assertions, derive(Debug))]
8585+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
8686#[derive(Deserialize, Clone)]
8787pub struct AppPasswordResponse {
8888 /// Name of the app password
+1-1
crates/atproto-client/src/errors.rs
···2424/// This structure represents the standard error response format used by AT Protocol
2525/// services, allowing for flexible error reporting with optional fields and
2626/// extension points for additional error context.
2727-#[cfg_attr(debug_assertions, derive(Debug))]
2727+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
2828#[derive(Clone, Serialize, Deserialize)]
2929pub struct SimpleError {
3030 /// The error code identifier
···1717//!
1818//! All errors use the standardized format: `error-atproto-identity-{domain}-{number} {message}: {details}`
19192020+use serde::{Deserialize, Serialize};
2021use thiserror::Error;
21222223/// Error types that can occur when working with Web DIDs
···383384 /// The underlying conversion error
384385 error: String,
385386 },
387387+388388+ /// Occurs when Ed25519 key operations fail
389389+ #[error("error-atproto-identity-key-13 Ed25519 key operation failed: {error}")]
390390+ Ed25519Error {
391391+ /// Description of the Ed25519 error
392392+ error: String,
393393+ },
394394+}
395395+396396+/// Error types that can occur when working with WebVH DIDs
397397+#[derive(Debug, Error)]
398398+pub enum WebVHDIDError {
399399+ /// Occurs when the DID is missing the 'did:webvh:' prefix
400400+ #[error("error-atproto-identity-webvh-1 Invalid DID format: missing 'did:webvh:' prefix")]
401401+ InvalidDIDPrefix,
402402+403403+ /// Occurs when the DID is missing a SCID component
404404+ #[error("error-atproto-identity-webvh-2 Invalid DID format: missing SCID component")]
405405+ MissingSCID,
406406+407407+ /// Occurs when the DID is missing a hostname component
408408+ #[error("error-atproto-identity-webvh-3 Invalid DID format: missing hostname component")]
409409+ MissingHostname,
410410+411411+ /// Occurs when the HTTP request to fetch the DID log fails
412412+ #[error("error-atproto-identity-webvh-4 HTTP request failed: {url} {error}")]
413413+ HttpRequestFailed {
414414+ /// The URL that was requested
415415+ url: String,
416416+ /// The underlying HTTP error
417417+ error: reqwest::Error,
418418+ },
419419+420420+ /// Occurs when a log entry cannot be parsed from the JSONL response
421421+ #[error("error-atproto-identity-webvh-5 Failed to parse log entry at line {line}: {details}")]
422422+ LogEntryParseFailed {
423423+ /// The line number (1-indexed) that failed to parse
424424+ line: usize,
425425+ /// Details about the parse failure
426426+ details: String,
427427+ },
428428+429429+ /// Occurs when the DID log file is empty
430430+ #[error("error-atproto-identity-webvh-6 Empty log file")]
431431+ EmptyLog,
432432+433433+ /// Occurs when a version ID is invalid or has incorrect format
434434+ #[error("error-atproto-identity-webvh-7 Invalid version ID at entry {entry}: {details}")]
435435+ InvalidVersionId {
436436+ /// The entry number (1-indexed) with the invalid version ID
437437+ entry: usize,
438438+ /// Details about the validation failure
439439+ details: String,
440440+ },
441441+442442+ /// Occurs when version times are not monotonically increasing
443443+ #[error(
444444+ "error-atproto-identity-webvh-8 Version time not monotonically increasing at entry {entry}"
445445+ )]
446446+ VersionTimeNotMonotonic {
447447+ /// The entry number (1-indexed) with the non-monotonic timestamp
448448+ entry: usize,
449449+ },
450450+451451+ /// Occurs when the SCID does not match the computed value from the genesis entry
452452+ #[error("error-atproto-identity-webvh-9 SCID verification failed on genesis entry")]
453453+ SCIDVerificationFailed,
454454+455455+ /// Occurs when an entry's hash does not match the expected value in the version ID
456456+ #[error("error-atproto-identity-webvh-10 Entry hash verification failed at entry {entry}")]
457457+ EntryHashVerificationFailed {
458458+ /// The entry number (1-indexed) with the hash mismatch
459459+ entry: usize,
460460+ },
461461+462462+ /// Occurs when a Data Integrity proof cannot be verified
463463+ #[error(
464464+ "error-atproto-identity-webvh-11 Proof verification failed at entry {entry}: {details}"
465465+ )]
466466+ ProofVerificationFailed {
467467+ /// The entry number (1-indexed) with the failed proof
468468+ entry: usize,
469469+ /// Details about the verification failure
470470+ details: String,
471471+ },
472472+473473+ /// Occurs when pre-rotation key hashes do not match
474474+ #[error("error-atproto-identity-webvh-12 Pre-rotation key hash mismatch at entry {entry}")]
475475+ PreRotationKeyMismatch {
476476+ /// The entry number (1-indexed) with the key mismatch
477477+ entry: usize,
478478+ },
479479+480480+ /// Occurs when log entry parameters are invalid
481481+ #[error("error-atproto-identity-webvh-13 Invalid parameters at entry {entry}: {details}")]
482482+ InvalidParameters {
483483+ /// The entry number (1-indexed) with the invalid parameters
484484+ entry: usize,
485485+ /// Details about the validation failure
486486+ details: String,
487487+ },
488488+489489+ /// Occurs when witness verification fails
490490+ #[error(
491491+ "error-atproto-identity-webvh-14 Witness verification failed at entry {entry}: {details}"
492492+ )]
493493+ WitnessVerificationFailed {
494494+ /// The entry number (1-indexed) with the failed witness verification
495495+ entry: usize,
496496+ /// Details about the verification failure
497497+ details: String,
498498+ },
499499+500500+ /// Occurs when the DID document cannot be extracted from the log state
501501+ #[error("error-atproto-identity-webvh-15 DID document extraction failed: {details}")]
502502+ DocumentExtractionFailed {
503503+ /// Details about the extraction failure
504504+ details: String,
505505+ },
506506+507507+ /// Occurs when the SCID format is invalid
508508+ #[error("error-atproto-identity-webvh-16 Invalid SCID format: {details}")]
509509+ InvalidSCIDFormat {
510510+ /// Details about the format violation
511511+ details: String,
512512+ },
513513+514514+ /// Occurs when the resolved DID has been deactivated
515515+ #[error("error-atproto-identity-webvh-17 Deactivated DID: {did}")]
516516+ DeactivatedDID {
517517+ /// The deactivated DID
518518+ did: String,
519519+ },
520520+521521+ /// Occurs when a requested version is not found in the log
522522+ #[error("error-atproto-identity-webvh-18 Version not found: {details}")]
523523+ VersionNotFound {
524524+ /// Details about the lookup that failed
525525+ details: String,
526526+ },
527527+528528+ /// Occurs when a version time is in the future
529529+ #[error(
530530+ "error-atproto-identity-webvh-19 Version time is in the future at entry {entry}: {version_time}"
531531+ )]
532532+ VersionTimeInFuture {
533533+ /// The entry number (1-indexed) with the future timestamp
534534+ entry: usize,
535535+ /// The future timestamp value
536536+ version_time: String,
537537+ },
538538+539539+ /// Occurs when the DIDDoc id does not match the DID
540540+ #[error(
541541+ "error-atproto-identity-webvh-20 DIDDoc id mismatch at entry {entry}: expected '{expected}', found '{found}'"
542542+ )]
543543+ DIDDocIdMismatch {
544544+ /// The entry number (1-indexed) with the mismatch
545545+ entry: usize,
546546+ /// The expected DID
547547+ expected: String,
548548+ /// The actual id found in the DIDDoc
549549+ found: String,
550550+ },
551551+552552+ /// Occurs when the hash algorithm in the SCID is not supported
553553+ #[error("error-atproto-identity-webvh-21 Unsupported hash algorithm in SCID: {details}")]
554554+ UnsupportedHashAlgorithm {
555555+ /// Details about the unsupported algorithm
556556+ details: String,
557557+ },
558558+559559+ /// Occurs when an unknown parameter is found in a log entry
560560+ #[error("error-atproto-identity-webvh-22 Unknown parameter at entry {entry}: {parameter}")]
561561+ UnknownParameter {
562562+ /// The entry number (1-indexed) with the unknown parameter
563563+ entry: usize,
564564+ /// The unknown parameter name
565565+ parameter: String,
566566+ },
567567+568568+ /// Occurs when hostname normalization fails
569569+ #[error("error-atproto-identity-webvh-23 Hostname normalization failed: {details}")]
570570+ HostnameNormalizationFailed {
571571+ /// Details about the normalization failure
572572+ details: String,
573573+ },
574574+}
575575+576576+/// Resolution error codes as defined by the did:webvh specification.
577577+#[derive(Debug, Clone, Serialize, Deserialize)]
578578+#[serde(rename_all = "camelCase")]
579579+pub struct ResolutionError {
580580+ /// Error code: "notFound" or "invalidDid".
581581+ pub error: ResolutionErrorCode,
582582+ /// Optional RFC 9457 problem details.
583583+ #[serde(skip_serializing_if = "Option::is_none")]
584584+ pub problem_details: Option<ProblemDetails>,
585585+}
586586+587587+/// Resolution error code per the did:webvh specification.
588588+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
589589+pub enum ResolutionErrorCode {
590590+ /// The DID was not found.
591591+ #[serde(rename = "notFound")]
592592+ NotFound,
593593+ /// The DID is invalid or failed verification.
594594+ #[serde(rename = "invalidDid")]
595595+ InvalidDid,
596596+}
597597+598598+/// RFC 9457 Problem Details structure.
599599+#[derive(Debug, Clone, Serialize, Deserialize)]
600600+pub struct ProblemDetails {
601601+ /// Problem type URI.
602602+ #[serde(skip_serializing_if = "Option::is_none")]
603603+ pub r#type: Option<String>,
604604+ /// Short human-readable summary.
605605+ pub title: String,
606606+ /// Detailed explanation.
607607+ #[serde(skip_serializing_if = "Option::is_none")]
608608+ pub detail: Option<String>,
609609+}
610610+611611+impl WebVHDIDError {
612612+ /// Converts this error to a spec-compliant resolution error.
613613+ pub fn to_resolution_error(&self) -> ResolutionError {
614614+ match self {
615615+ WebVHDIDError::HttpRequestFailed { .. } | WebVHDIDError::EmptyLog => ResolutionError {
616616+ error: ResolutionErrorCode::NotFound,
617617+ problem_details: Some(ProblemDetails {
618618+ r#type: None,
619619+ title: "DID not found".to_string(),
620620+ detail: Some(self.to_string()),
621621+ }),
622622+ },
623623+ WebVHDIDError::InvalidDIDPrefix
624624+ | WebVHDIDError::MissingSCID
625625+ | WebVHDIDError::MissingHostname
626626+ | WebVHDIDError::InvalidSCIDFormat { .. } => ResolutionError {
627627+ error: ResolutionErrorCode::InvalidDid,
628628+ problem_details: Some(ProblemDetails {
629629+ r#type: None,
630630+ title: "Invalid DID format".to_string(),
631631+ detail: Some(self.to_string()),
632632+ }),
633633+ },
634634+ _ => ResolutionError {
635635+ error: ResolutionErrorCode::InvalidDid,
636636+ problem_details: Some(ProblemDetails {
637637+ r#type: None,
638638+ title: "DID verification failed".to_string(),
639639+ detail: Some(self.to_string()),
640640+ }),
641641+ },
642642+ }
643643+ }
386644}
387645388646/// Error types that can occur when working with storage operations
···433433/// Configuration for DPoP JWT validation.
434434///
435435/// This struct allows callers to specify what aspects of the DPoP JWT should be validated.
436436-#[cfg_attr(debug_assertions, derive(Debug))]
436436+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
437437#[derive(Clone)]
438438pub struct DpopValidationConfig {
439439 /// Expected HTTP method (e.g., "POST", "GET"). If None, method validation is skipped.
···19192020/// JWT header containing algorithm and key metadata.
2121#[derive(Clone, Default, PartialEq, Serialize, Deserialize)]
2222-#[cfg_attr(debug_assertions, derive(Debug))]
2222+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
2323#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
2424pub struct Header {
2525 /// Algorithm used for signing (e.g., "ES256", "ES384", "ES256K").
···5050 KeyType::P384Private => Some("ES384".to_string()),
5151 KeyType::K256Public => Some("ES256K".to_string()),
5252 KeyType::K256Private => Some("ES256K".to_string()),
5353+ KeyType::Ed25519Public | KeyType::Ed25519Private => Some("EdDSA".to_string()),
5354 };
54555556 let public_key = to_public(&value)?;
···6566}
66676768/// JWT claims combining standard JOSE claims with custom private claims.
6868-#[cfg_attr(debug_assertions, derive(Debug))]
6969+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
6970#[derive(Clone, Default, PartialEq, Serialize, Deserialize)]
7071pub struct Claims {
7172 /// Standard JOSE claims.
···9091pub type SecondsSinceEpoch = u64;
91929293/// Standard JOSE claims for JWT tokens.
9393-#[cfg_attr(debug_assertions, derive(Debug))]
9494+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
9495#[derive(Clone, Default, PartialEq, Serialize, Deserialize)]
9596pub struct JoseClaims {
9697 /// Issuer of the token.
+1-1
crates/atproto-oauth/src/resources.rs
···2828/// OAuth 2.0 authorization server metadata from RFC 8414 oauth-authorization-server endpoint.
2929///
3030/// AT Protocol requires specific grant types, scopes, authentication methods, and security features.
3131-#[cfg_attr(debug_assertions, derive(Debug))]
3131+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
3232#[derive(Clone, Deserialize, Default)]
3333pub struct AuthorizationServer {
3434 /// URL of the authorization server's token introspection endpoint (optional).
+2-2
crates/atproto-record/src/aturi.rs
···4646///
4747/// This struct provides validated access to these components after successful parsing
4848/// and implements `Display` for reconstructing the original URI format.
4949-#[cfg_attr(debug_assertions, derive(Debug))]
4949+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
5050#[derive(Clone)]
5151pub struct ATURI {
5252 /// The authority component as a DID (e.g., "did:plc:abc123")
···89899090 let did = match parse_input(authority) {
9191 Ok(InputType::Handle(_)) => return Err(AturiError::HandleNotSupported),
9292- Ok(InputType::Plc(did)) | Ok(InputType::Web(did)) => did,
9292+ Ok(InputType::Plc(did)) | Ok(InputType::Web(did)) | Ok(InputType::WebVH(did)) => did,
9393 Err(error) => return Err(AturiError::AuthorityParsingFailed { error }),
9494 };
9595
···3535/// let typed_ref = TypedStrongRef::new(strong_ref);
3636/// ```
3737#[derive(Serialize, Deserialize, PartialEq, Clone)]
3838-#[cfg_attr(debug_assertions, derive(Debug))]
3838+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
3939pub struct StrongRef {
4040 /// AT URI pointing to a specific record
4141 /// Format: `at://[did]/[collection]/[rkey]`
···2424///
2525/// Represents the current status of a calendar event.
2626#[derive(Serialize, Deserialize, PartialEq, Clone, Default)]
2727-#[cfg_attr(debug_assertions, derive(Debug))]
2727+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
2828pub enum Status {
2929 /// Event is scheduled and confirmed
3030 #[default]
···5252///
5353/// Represents how attendees can participate in the event.
5454#[derive(Serialize, Deserialize, PartialEq, Clone, Default)]
5555-#[cfg_attr(debug_assertions, derive(Debug))]
5555+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
5656pub enum Mode {
5757 /// In-person attendance only
5858 #[default]
···7878/// Represents a URI with an optional human-readable name.
7979/// Used for linking to external resources related to an event.
8080#[derive(Serialize, Deserialize, PartialEq, Clone)]
8181-#[cfg_attr(debug_assertions, derive(Debug))]
8181+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
8282pub struct NamedUri {
8383 /// The URI/URL
8484 pub uri: String,
···111111/// Similar to NamedUri but kept as a separate type for semantic clarity
112112/// and type safety when dealing with event-specific links.
113113#[derive(Serialize, Deserialize, PartialEq, Clone)]
114114-#[cfg_attr(debug_assertions, derive(Debug))]
114114+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
115115pub struct EventLink {
116116 /// The URI/URL for the event link
117117 pub uri: String,
···143143///
144144/// Represents the width-to-height ratio of visual media.
145145#[derive(Serialize, Deserialize, PartialEq, Clone)]
146146-#[cfg_attr(debug_assertions, derive(Debug))]
146146+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
147147pub struct AspectRatio {
148148 /// Width component of the ratio
149149 pub width: u64,
···160160///
161161/// Represents images, videos, or other media associated with an event.
162162#[derive(Serialize, Deserialize, PartialEq, Clone)]
163163-#[cfg_attr(debug_assertions, derive(Debug))]
163163+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
164164pub struct Media {
165165 /// The media content as a blob reference
166166 pub content: TypedBlob,
···198198///
199199/// Extends `LocationOrRef` with URI location support specific to calendar events.
200200#[derive(Deserialize, Serialize, Clone, PartialEq)]
201201-#[cfg_attr(debug_assertions, derive(Debug))]
201201+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
202202#[serde(untagged)]
203203pub enum EventLocation {
204204 /// An inline URI location
···241241/// let typed_event = TypedEvent::new(event);
242242/// ```
243243#[derive(Serialize, Deserialize, PartialEq, Clone)]
244244-#[cfg_attr(debug_assertions, derive(Debug))]
244244+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
245245pub struct Event {
246246 /// Name/title of the event
247247 pub name: String,
···3434/// let typed_blob = TypedBlob::new(blob);
3535/// ```
3636#[derive(Serialize, Deserialize, PartialEq, Clone)]
3737-#[cfg_attr(debug_assertions, derive(Debug))]
3737+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
3838pub struct Blob {
3939 /// Link to the blob content via CID
4040 #[serde(rename = "ref")]
···6363/// Represents a content-addressed link using a CID (Content Identifier).
6464/// This is used to reference immutable content in the AT Protocol network.
6565#[derive(Serialize, Deserialize, PartialEq, Clone)]
6666-#[cfg_attr(debug_assertions, derive(Debug))]
6666+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
6767pub struct Link {
6868 /// The CID (Content Identifier) as a string
6969 #[serde(rename = "$link")]
···8585/// // Serializes to: {"$bytes": "c2lnbmF0dXJlIGRhdGE="}
8686/// ```
8787#[derive(Serialize, Deserialize, PartialEq, Clone)]
8888-#[cfg_attr(debug_assertions, derive(Debug))]
8888+#[cfg_attr(any(debug_assertions, test), derive(Debug))]
8989pub struct Bytes {
9090 /// The raw bytes, serialized as base64 in JSON
9191 #[serde(rename = "$bytes", with = "bytes_format")]