A library for ATProtocol identities.

refactor: linting and formatting

Changed files
+194 -85
crates
atproto-client
atproto-jetstream
atproto-oauth
atproto-oauth-aip
+1 -1
crates/atproto-client/src/client.rs
··· 42 /// DPoP authentication with proof-of-possession tokens and OAuth access token 43 DPoP(DPoPAuth), 44 /// App password authentication using JWT bearer tokens 45 - AppPassword(AppPasswordAuth) 46 } 47 48 /// Performs an unauthenticated HTTP GET request and parses the response as JSON.
··· 42 /// DPoP authentication with proof-of-possession tokens and OAuth access token 43 DPoP(DPoPAuth), 44 /// App password authentication using JWT bearer tokens 45 + AppPassword(AppPasswordAuth), 46 } 47 48 /// Performs an unauthenticated HTTP GET request and parses the response as JSON.
+2 -2
crates/atproto-client/src/com_atproto_identity.rs
··· 9 use serde::{Deserialize, de::DeserializeOwned}; 10 11 use crate::{ 12 - client::{get_apppassword_json, get_dpop_json, get_json, Auth}, 13 errors::SimpleError, 14 - url::URLBuilder 15 }; 16 17 /// Response from the com.atproto.identity.resolveHandle XRPC method.
··· 9 use serde::{Deserialize, de::DeserializeOwned}; 10 11 use crate::{ 12 + client::{Auth, get_apppassword_json, get_dpop_json, get_json}, 13 errors::SimpleError, 14 + url::URLBuilder, 15 }; 16 17 /// Response from the com.atproto.identity.resolveHandle XRPC method.
+4 -1
crates/atproto-client/src/com_atproto_repo.rs
··· 29 use serde::{Deserialize, Serialize, de::DeserializeOwned}; 30 31 use crate::{ 32 - client::{Auth, get_apppassword_json, get_bytes, get_dpop_json, get_json, post_apppassword_json, post_dpop_json, post_json}, 33 errors::SimpleError, 34 url::URLBuilder, 35 };
··· 29 use serde::{Deserialize, Serialize, de::DeserializeOwned}; 30 31 use crate::{ 32 + client::{ 33 + Auth, get_apppassword_json, get_bytes, get_dpop_json, get_json, post_apppassword_json, 34 + post_dpop_json, post_json, 35 + }, 36 errors::SimpleError, 37 url::URLBuilder, 38 };
+1 -1
crates/atproto-client/src/lib.rs
··· 22 pub mod errors; 23 pub mod url; 24 25 mod com_atproto_repo; 26 mod com_atproto_server; 27 - mod com_atproto_identity; 28 29 /// AT Protocol namespace modules. 30 pub mod com {
··· 22 pub mod errors; 23 pub mod url; 24 25 + mod com_atproto_identity; 26 mod com_atproto_repo; 27 mod com_atproto_server; 28 29 /// AT Protocol namespace modules. 30 pub mod com {
+149 -48
crates/atproto-jetstream/src/consumer.rs
··· 456 #[test] 457 fn test_parse_account_event() { 458 let json_str = r#"{"did":"did:plc:yn72uqr4ihkjfbz7us7buqsq","time_us":1757517640675638,"kind":"account","account":{"active":false,"did":"did:plc:yn72uqr4ihkjfbz7us7buqsq","seq":13206502767,"status":"takendown","time":"2025-09-10T15:20:40.439Z"}}"#; 459 - 460 let event = serde_json::from_str::<JetstreamEvent>(json_str) 461 .expect("Failed to parse account event JSON"); 462 - 463 match event { 464 - JetstreamEvent::Account { did, time_us, kind, account: identity } => { 465 assert_eq!(did, "did:plc:yn72uqr4ihkjfbz7us7buqsq"); 466 assert_eq!(time_us, 1757517640675638); 467 assert_eq!(kind, "account"); 468 - 469 // Verify the account data structure 470 assert!(identity.is_object()); 471 let account_obj = identity.as_object().unwrap(); 472 - assert_eq!(account_obj.get("active").unwrap(), &serde_json::json!(false)); 473 - assert_eq!(account_obj.get("did").unwrap(), &serde_json::json!("did:plc:yn72uqr4ihkjfbz7us7buqsq")); 474 - assert_eq!(account_obj.get("seq").unwrap(), &serde_json::json!(13206502767i64)); 475 - assert_eq!(account_obj.get("status").unwrap(), &serde_json::json!("takendown")); 476 - assert_eq!(account_obj.get("time").unwrap(), &serde_json::json!("2025-09-10T15:20:40.439Z")); 477 } 478 _ => panic!("Expected JetstreamEvent::Account variant, got {:?}", event), 479 } ··· 482 #[test] 483 fn test_parse_identity_event() { 484 let json_str = r#"{"did":"did:plc:mbuadp4xzlbmc2ncqp3pmtox","time_us":1757517628039893,"kind":"identity","identity":{"did":"did:plc:mbuadp4xzlbmc2ncqp3pmtox","handle":"nhieothv.bsky.social","seq":13206497272,"time":"2025-09-10T15:20:27.610Z"}}"#; 485 - 486 let event = serde_json::from_str::<JetstreamEvent>(json_str) 487 .expect("Failed to parse identity event JSON"); 488 - 489 match event { 490 - JetstreamEvent::Identity { did, time_us, kind, identity } => { 491 assert_eq!(did, "did:plc:mbuadp4xzlbmc2ncqp3pmtox"); 492 assert_eq!(time_us, 1757517628039893); 493 assert_eq!(kind, "identity"); 494 - 495 // Verify the identity data structure 496 assert!(identity.is_object()); 497 let identity_obj = identity.as_object().unwrap(); 498 - assert_eq!(identity_obj.get("did").unwrap(), &serde_json::json!("did:plc:mbuadp4xzlbmc2ncqp3pmtox")); 499 - assert_eq!(identity_obj.get("handle").unwrap(), &serde_json::json!("nhieothv.bsky.social")); 500 - assert_eq!(identity_obj.get("seq").unwrap(), &serde_json::json!(13206497272i64)); 501 - assert_eq!(identity_obj.get("time").unwrap(), &serde_json::json!("2025-09-10T15:20:27.610Z")); 502 } 503 _ => panic!("Expected JetstreamEvent::Identity variant, got {:?}", event), 504 } ··· 507 #[test] 508 fn test_parse_delete_event() { 509 let json_str = r#"{"did":"did:plc:5ozthefrqdo5kqnxzfgthhpp","time_us":1757519323847323,"kind":"commit","commit":{"rev":"3lyileto4q52k","operation":"delete","collection":"app.bsky.graph.follow","rkey":"3lxqxntaew32z"}}"#; 510 - 511 let event = serde_json::from_str::<JetstreamEvent>(json_str) 512 .expect("Failed to parse delete event JSON"); 513 - 514 match event { 515 - JetstreamEvent::Delete { did, time_us, kind, commit } => { 516 assert_eq!(did, "did:plc:5ozthefrqdo5kqnxzfgthhpp"); 517 assert_eq!(time_us, 1757519323847323); 518 assert_eq!(kind, "commit"); 519 - 520 // Verify the delete operation details 521 assert_eq!(commit.rev, "3lyileto4q52k"); 522 assert_eq!(commit.operation, "delete"); ··· 530 #[test] 531 fn test_parse_commit_event() { 532 let json_str = r#"{"did":"did:plc:suq5ijgyqmsawwf5tskf654x","time_us":1757519323848962,"kind":"commit","commit":{"rev":"3lyiletdopl2c","operation":"create","collection":"app.bsky.feed.like","rkey":"3lyiletddxt2c","record":{"$type":"app.bsky.feed.like","createdAt":"2025-09-10T15:47:13.086Z","subject":{"cid":"bafyreib2pygab7z5l7nkqf6bchcvgt4jwsqiaenpf3sr65lugum2uvzzf4","uri":"at://did:plc:yw65rktdby2chplqdytqzcao/app.bsky.feed.post/3lyildyjxgs2o"}},"cid":"bafyreigroo6vhxt62ufcndhaxzas6btq4jmniuz4egszbwuqgiyisqwqoy"}}"#; 533 - 534 let event = serde_json::from_str::<JetstreamEvent>(json_str) 535 .expect("Failed to parse commit event JSON"); 536 - 537 match event { 538 - JetstreamEvent::Commit { did, time_us, kind, commit } => { 539 assert_eq!(did, "did:plc:suq5ijgyqmsawwf5tskf654x"); 540 assert_eq!(time_us, 1757519323848962); 541 assert_eq!(kind, "commit"); 542 - 543 // Verify the commit operation details 544 assert_eq!(commit.rev, "3lyiletdopl2c"); 545 assert_eq!(commit.operation, "create"); 546 assert_eq!(commit.collection, "app.bsky.feed.like"); 547 assert_eq!(commit.rkey, "3lyiletddxt2c"); 548 - assert_eq!(commit.cid, "bafyreigroo6vhxt62ufcndhaxzas6btq4jmniuz4egszbwuqgiyisqwqoy"); 549 - 550 // Verify the record data structure 551 assert!(commit.record.is_object()); 552 let record_obj = commit.record.as_object().unwrap(); 553 - assert_eq!(record_obj.get("$type").unwrap(), &serde_json::json!("app.bsky.feed.like")); 554 - assert_eq!(record_obj.get("createdAt").unwrap(), &serde_json::json!("2025-09-10T15:47:13.086Z")); 555 - 556 // Verify the subject within the record 557 let subject = record_obj.get("subject").unwrap().as_object().unwrap(); 558 - assert_eq!(subject.get("cid").unwrap(), &serde_json::json!("bafyreib2pygab7z5l7nkqf6bchcvgt4jwsqiaenpf3sr65lugum2uvzzf4")); 559 - assert_eq!(subject.get("uri").unwrap(), &serde_json::json!("at://did:plc:yw65rktdby2chplqdytqzcao/app.bsky.feed.post/3lyildyjxgs2o")); 560 } 561 _ => panic!("Expected JetstreamEvent::Commit variant, got {:?}", event), 562 } ··· 565 #[test] 566 fn test_parse_commit_update_event() { 567 let json_str = r#"{"did":"did:plc:mek6cpladv2xrlu2zdykoxgz","time_us":1757519523286358,"kind":"commit","commit":{"rev":"3lyilmalk762z","operation":"update","collection":"app.bsky.actor.profile","rkey":"self","record":{"$type":"app.bsky.actor.profile","avatar":{"$type":"blob","ref":{"$link":"bafkreibmn7xi5iwugioov463wux62dg4m4w6qqrbsnileaobrzgxdwbsqy"},"mimeType":"image/jpeg","size":289838},"banner":{"$type":"blob","ref":{"$link":"bafkreicjgdlfs6fyyddjklfzrf6w2boychodkdebtjaiwavhcffjtuavsi"},"mimeType":"image/jpeg","size":676693},"description":"ela/dela | parte da fauna fantástica do céu azul | praticamente inofensiva","displayName":"la mucura mística","pinnedPost":{"cid":"bafyreihn2t4efvipbcignd6rlybmoecb7hx4jgntsojhpibjzxno3zhbuq","uri":"at://did:plc:mek6cpladv2xrlu2zdykoxgz/app.bsky.feed.post/3lxarfbd4ts2j"}},"cid":"bafyreifpmgw3podvvm4raq6zewn6jhoa73t7mlgf3f7hty2adb6f2ga7j4"}}"#; 568 - 569 let event = serde_json::from_str::<JetstreamEvent>(json_str) 570 .expect("Failed to parse commit update event JSON"); 571 - 572 match event { 573 - JetstreamEvent::Commit { did, time_us, kind, commit } => { 574 assert_eq!(did, "did:plc:mek6cpladv2xrlu2zdykoxgz"); 575 assert_eq!(time_us, 1757519523286358); 576 assert_eq!(kind, "commit"); 577 - 578 // Verify the commit operation details 579 assert_eq!(commit.rev, "3lyilmalk762z"); 580 assert_eq!(commit.operation, "update"); 581 assert_eq!(commit.collection, "app.bsky.actor.profile"); 582 assert_eq!(commit.rkey, "self"); 583 - assert_eq!(commit.cid, "bafyreifpmgw3podvvm4raq6zewn6jhoa73t7mlgf3f7hty2adb6f2ga7j4"); 584 - 585 // Verify the record data structure 586 assert!(commit.record.is_object()); 587 let record_obj = commit.record.as_object().unwrap(); 588 - assert_eq!(record_obj.get("$type").unwrap(), &serde_json::json!("app.bsky.actor.profile")); 589 - assert_eq!(record_obj.get("description").unwrap(), &serde_json::json!("ela/dela | parte da fauna fantástica do céu azul | praticamente inofensiva")); 590 - assert_eq!(record_obj.get("displayName").unwrap(), &serde_json::json!("la mucura mística")); 591 - 592 // Verify avatar blob 593 let avatar = record_obj.get("avatar").unwrap().as_object().unwrap(); 594 assert_eq!(avatar.get("$type").unwrap(), &serde_json::json!("blob")); 595 - assert_eq!(avatar.get("mimeType").unwrap(), &serde_json::json!("image/jpeg")); 596 assert_eq!(avatar.get("size").unwrap(), &serde_json::json!(289838)); 597 - 598 // Verify banner blob 599 let banner = record_obj.get("banner").unwrap().as_object().unwrap(); 600 assert_eq!(banner.get("$type").unwrap(), &serde_json::json!("blob")); 601 - assert_eq!(banner.get("mimeType").unwrap(), &serde_json::json!("image/jpeg")); 602 assert_eq!(banner.get("size").unwrap(), &serde_json::json!(676693)); 603 - 604 // Verify pinned post 605 let pinned_post = record_obj.get("pinnedPost").unwrap().as_object().unwrap(); 606 - assert_eq!(pinned_post.get("cid").unwrap(), &serde_json::json!("bafyreihn2t4efvipbcignd6rlybmoecb7hx4jgntsojhpibjzxno3zhbuq")); 607 - assert_eq!(pinned_post.get("uri").unwrap(), &serde_json::json!("at://did:plc:mek6cpladv2xrlu2zdykoxgz/app.bsky.feed.post/3lxarfbd4ts2j")); 608 } 609 _ => panic!("Expected JetstreamEvent::Commit variant, got {:?}", event), 610 }
··· 456 #[test] 457 fn test_parse_account_event() { 458 let json_str = r#"{"did":"did:plc:yn72uqr4ihkjfbz7us7buqsq","time_us":1757517640675638,"kind":"account","account":{"active":false,"did":"did:plc:yn72uqr4ihkjfbz7us7buqsq","seq":13206502767,"status":"takendown","time":"2025-09-10T15:20:40.439Z"}}"#; 459 + 460 let event = serde_json::from_str::<JetstreamEvent>(json_str) 461 .expect("Failed to parse account event JSON"); 462 + 463 match event { 464 + JetstreamEvent::Account { 465 + did, 466 + time_us, 467 + kind, 468 + account: identity, 469 + } => { 470 assert_eq!(did, "did:plc:yn72uqr4ihkjfbz7us7buqsq"); 471 assert_eq!(time_us, 1757517640675638); 472 assert_eq!(kind, "account"); 473 + 474 // Verify the account data structure 475 assert!(identity.is_object()); 476 let account_obj = identity.as_object().unwrap(); 477 + assert_eq!( 478 + account_obj.get("active").unwrap(), 479 + &serde_json::json!(false) 480 + ); 481 + assert_eq!( 482 + account_obj.get("did").unwrap(), 483 + &serde_json::json!("did:plc:yn72uqr4ihkjfbz7us7buqsq") 484 + ); 485 + assert_eq!( 486 + account_obj.get("seq").unwrap(), 487 + &serde_json::json!(13206502767i64) 488 + ); 489 + assert_eq!( 490 + account_obj.get("status").unwrap(), 491 + &serde_json::json!("takendown") 492 + ); 493 + assert_eq!( 494 + account_obj.get("time").unwrap(), 495 + &serde_json::json!("2025-09-10T15:20:40.439Z") 496 + ); 497 } 498 _ => panic!("Expected JetstreamEvent::Account variant, got {:?}", event), 499 } ··· 502 #[test] 503 fn test_parse_identity_event() { 504 let json_str = r#"{"did":"did:plc:mbuadp4xzlbmc2ncqp3pmtox","time_us":1757517628039893,"kind":"identity","identity":{"did":"did:plc:mbuadp4xzlbmc2ncqp3pmtox","handle":"nhieothv.bsky.social","seq":13206497272,"time":"2025-09-10T15:20:27.610Z"}}"#; 505 + 506 let event = serde_json::from_str::<JetstreamEvent>(json_str) 507 .expect("Failed to parse identity event JSON"); 508 + 509 match event { 510 + JetstreamEvent::Identity { 511 + did, 512 + time_us, 513 + kind, 514 + identity, 515 + } => { 516 assert_eq!(did, "did:plc:mbuadp4xzlbmc2ncqp3pmtox"); 517 assert_eq!(time_us, 1757517628039893); 518 assert_eq!(kind, "identity"); 519 + 520 // Verify the identity data structure 521 assert!(identity.is_object()); 522 let identity_obj = identity.as_object().unwrap(); 523 + assert_eq!( 524 + identity_obj.get("did").unwrap(), 525 + &serde_json::json!("did:plc:mbuadp4xzlbmc2ncqp3pmtox") 526 + ); 527 + assert_eq!( 528 + identity_obj.get("handle").unwrap(), 529 + &serde_json::json!("nhieothv.bsky.social") 530 + ); 531 + assert_eq!( 532 + identity_obj.get("seq").unwrap(), 533 + &serde_json::json!(13206497272i64) 534 + ); 535 + assert_eq!( 536 + identity_obj.get("time").unwrap(), 537 + &serde_json::json!("2025-09-10T15:20:27.610Z") 538 + ); 539 } 540 _ => panic!("Expected JetstreamEvent::Identity variant, got {:?}", event), 541 } ··· 544 #[test] 545 fn test_parse_delete_event() { 546 let json_str = r#"{"did":"did:plc:5ozthefrqdo5kqnxzfgthhpp","time_us":1757519323847323,"kind":"commit","commit":{"rev":"3lyileto4q52k","operation":"delete","collection":"app.bsky.graph.follow","rkey":"3lxqxntaew32z"}}"#; 547 + 548 let event = serde_json::from_str::<JetstreamEvent>(json_str) 549 .expect("Failed to parse delete event JSON"); 550 + 551 match event { 552 + JetstreamEvent::Delete { 553 + did, 554 + time_us, 555 + kind, 556 + commit, 557 + } => { 558 assert_eq!(did, "did:plc:5ozthefrqdo5kqnxzfgthhpp"); 559 assert_eq!(time_us, 1757519323847323); 560 assert_eq!(kind, "commit"); 561 + 562 // Verify the delete operation details 563 assert_eq!(commit.rev, "3lyileto4q52k"); 564 assert_eq!(commit.operation, "delete"); ··· 572 #[test] 573 fn test_parse_commit_event() { 574 let json_str = r#"{"did":"did:plc:suq5ijgyqmsawwf5tskf654x","time_us":1757519323848962,"kind":"commit","commit":{"rev":"3lyiletdopl2c","operation":"create","collection":"app.bsky.feed.like","rkey":"3lyiletddxt2c","record":{"$type":"app.bsky.feed.like","createdAt":"2025-09-10T15:47:13.086Z","subject":{"cid":"bafyreib2pygab7z5l7nkqf6bchcvgt4jwsqiaenpf3sr65lugum2uvzzf4","uri":"at://did:plc:yw65rktdby2chplqdytqzcao/app.bsky.feed.post/3lyildyjxgs2o"}},"cid":"bafyreigroo6vhxt62ufcndhaxzas6btq4jmniuz4egszbwuqgiyisqwqoy"}}"#; 575 + 576 let event = serde_json::from_str::<JetstreamEvent>(json_str) 577 .expect("Failed to parse commit event JSON"); 578 + 579 match event { 580 + JetstreamEvent::Commit { 581 + did, 582 + time_us, 583 + kind, 584 + commit, 585 + } => { 586 assert_eq!(did, "did:plc:suq5ijgyqmsawwf5tskf654x"); 587 assert_eq!(time_us, 1757519323848962); 588 assert_eq!(kind, "commit"); 589 + 590 // Verify the commit operation details 591 assert_eq!(commit.rev, "3lyiletdopl2c"); 592 assert_eq!(commit.operation, "create"); 593 assert_eq!(commit.collection, "app.bsky.feed.like"); 594 assert_eq!(commit.rkey, "3lyiletddxt2c"); 595 + assert_eq!( 596 + commit.cid, 597 + "bafyreigroo6vhxt62ufcndhaxzas6btq4jmniuz4egszbwuqgiyisqwqoy" 598 + ); 599 + 600 // Verify the record data structure 601 assert!(commit.record.is_object()); 602 let record_obj = commit.record.as_object().unwrap(); 603 + assert_eq!( 604 + record_obj.get("$type").unwrap(), 605 + &serde_json::json!("app.bsky.feed.like") 606 + ); 607 + assert_eq!( 608 + record_obj.get("createdAt").unwrap(), 609 + &serde_json::json!("2025-09-10T15:47:13.086Z") 610 + ); 611 + 612 // Verify the subject within the record 613 let subject = record_obj.get("subject").unwrap().as_object().unwrap(); 614 + assert_eq!( 615 + subject.get("cid").unwrap(), 616 + &serde_json::json!( 617 + "bafyreib2pygab7z5l7nkqf6bchcvgt4jwsqiaenpf3sr65lugum2uvzzf4" 618 + ) 619 + ); 620 + assert_eq!( 621 + subject.get("uri").unwrap(), 622 + &serde_json::json!( 623 + "at://did:plc:yw65rktdby2chplqdytqzcao/app.bsky.feed.post/3lyildyjxgs2o" 624 + ) 625 + ); 626 } 627 _ => panic!("Expected JetstreamEvent::Commit variant, got {:?}", event), 628 } ··· 631 #[test] 632 fn test_parse_commit_update_event() { 633 let json_str = r#"{"did":"did:plc:mek6cpladv2xrlu2zdykoxgz","time_us":1757519523286358,"kind":"commit","commit":{"rev":"3lyilmalk762z","operation":"update","collection":"app.bsky.actor.profile","rkey":"self","record":{"$type":"app.bsky.actor.profile","avatar":{"$type":"blob","ref":{"$link":"bafkreibmn7xi5iwugioov463wux62dg4m4w6qqrbsnileaobrzgxdwbsqy"},"mimeType":"image/jpeg","size":289838},"banner":{"$type":"blob","ref":{"$link":"bafkreicjgdlfs6fyyddjklfzrf6w2boychodkdebtjaiwavhcffjtuavsi"},"mimeType":"image/jpeg","size":676693},"description":"ela/dela | parte da fauna fantástica do céu azul | praticamente inofensiva","displayName":"la mucura mística","pinnedPost":{"cid":"bafyreihn2t4efvipbcignd6rlybmoecb7hx4jgntsojhpibjzxno3zhbuq","uri":"at://did:plc:mek6cpladv2xrlu2zdykoxgz/app.bsky.feed.post/3lxarfbd4ts2j"}},"cid":"bafyreifpmgw3podvvm4raq6zewn6jhoa73t7mlgf3f7hty2adb6f2ga7j4"}}"#; 634 + 635 let event = serde_json::from_str::<JetstreamEvent>(json_str) 636 .expect("Failed to parse commit update event JSON"); 637 + 638 match event { 639 + JetstreamEvent::Commit { 640 + did, 641 + time_us, 642 + kind, 643 + commit, 644 + } => { 645 assert_eq!(did, "did:plc:mek6cpladv2xrlu2zdykoxgz"); 646 assert_eq!(time_us, 1757519523286358); 647 assert_eq!(kind, "commit"); 648 + 649 // Verify the commit operation details 650 assert_eq!(commit.rev, "3lyilmalk762z"); 651 assert_eq!(commit.operation, "update"); 652 assert_eq!(commit.collection, "app.bsky.actor.profile"); 653 assert_eq!(commit.rkey, "self"); 654 + assert_eq!( 655 + commit.cid, 656 + "bafyreifpmgw3podvvm4raq6zewn6jhoa73t7mlgf3f7hty2adb6f2ga7j4" 657 + ); 658 + 659 // Verify the record data structure 660 assert!(commit.record.is_object()); 661 let record_obj = commit.record.as_object().unwrap(); 662 + assert_eq!( 663 + record_obj.get("$type").unwrap(), 664 + &serde_json::json!("app.bsky.actor.profile") 665 + ); 666 + assert_eq!( 667 + record_obj.get("description").unwrap(), 668 + &serde_json::json!( 669 + "ela/dela | parte da fauna fantástica do céu azul | praticamente inofensiva" 670 + ) 671 + ); 672 + assert_eq!( 673 + record_obj.get("displayName").unwrap(), 674 + &serde_json::json!("la mucura mística") 675 + ); 676 + 677 // Verify avatar blob 678 let avatar = record_obj.get("avatar").unwrap().as_object().unwrap(); 679 assert_eq!(avatar.get("$type").unwrap(), &serde_json::json!("blob")); 680 + assert_eq!( 681 + avatar.get("mimeType").unwrap(), 682 + &serde_json::json!("image/jpeg") 683 + ); 684 assert_eq!(avatar.get("size").unwrap(), &serde_json::json!(289838)); 685 + 686 // Verify banner blob 687 let banner = record_obj.get("banner").unwrap().as_object().unwrap(); 688 assert_eq!(banner.get("$type").unwrap(), &serde_json::json!("blob")); 689 + assert_eq!( 690 + banner.get("mimeType").unwrap(), 691 + &serde_json::json!("image/jpeg") 692 + ); 693 assert_eq!(banner.get("size").unwrap(), &serde_json::json!(676693)); 694 + 695 // Verify pinned post 696 let pinned_post = record_obj.get("pinnedPost").unwrap().as_object().unwrap(); 697 + assert_eq!( 698 + pinned_post.get("cid").unwrap(), 699 + &serde_json::json!( 700 + "bafyreihn2t4efvipbcignd6rlybmoecb7hx4jgntsojhpibjzxno3zhbuq" 701 + ) 702 + ); 703 + assert_eq!( 704 + pinned_post.get("uri").unwrap(), 705 + &serde_json::json!( 706 + "at://did:plc:mek6cpladv2xrlu2zdykoxgz/app.bsky.feed.post/3lxarfbd4ts2j" 707 + ) 708 + ); 709 } 710 _ => panic!("Expected JetstreamEvent::Commit variant, got {:?}", event), 711 }
+11 -4
crates/atproto-oauth-aip/src/workflow.rs
··· 112 //! and protocol violations. 113 114 use anyhow::Result; 115 - use atproto_oauth::workflow::{OAuthRequest, OAuthRequestState, ParResponse, TokenResponse}; 116 use serde::Deserialize; 117 118 use crate::errors::OAuthWorkflowError; ··· 176 #[cfg_attr(feature = "zeroize", zeroize(skip))] 177 pub pds_endpoint: String, 178 179 /// The DPoP (Demonstration of Proof-of-Possession) key in JWK format. 180 #[cfg_attr(feature = "zeroize", zeroize(skip))] 181 - pub dpop_key: String, 182 183 /// Unix timestamp indicating when this session expires. 184 #[cfg_attr(feature = "zeroize", zeroize(skip))] ··· 189 #[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))] 190 #[serde(untagged)] 191 enum WrappedATProtocolSession { 192 - ATProtocolSession(ATProtocolSession), 193 194 #[cfg_attr(feature = "zeroize", zeroize(skip))] 195 Error { ··· 428 .map_err(OAuthWorkflowError::SessionResponseParseFailed)?; 429 430 match response { 431 - WrappedATProtocolSession::ATProtocolSession(ref value) => Ok(value.clone()), 432 WrappedATProtocolSession::Error { 433 ref error, 434 ref error_description,
··· 112 //! and protocol violations. 113 114 use anyhow::Result; 115 + use atproto_oauth::{ 116 + jwk::WrappedJsonWebKey, 117 + workflow::{OAuthRequest, OAuthRequestState, ParResponse, TokenResponse}, 118 + }; 119 use serde::Deserialize; 120 121 use crate::errors::OAuthWorkflowError; ··· 179 #[cfg_attr(feature = "zeroize", zeroize(skip))] 180 pub pds_endpoint: String, 181 182 + /// The DPoP (Demonstration of Proof-of-Possession) key in string serialized format. 183 + #[cfg_attr(feature = "zeroize", zeroize(skip))] 184 + pub dpop_key: Option<String>, 185 + 186 /// The DPoP (Demonstration of Proof-of-Possession) key in JWK format. 187 #[cfg_attr(feature = "zeroize", zeroize(skip))] 188 + pub dpop_jwk: Option<WrappedJsonWebKey>, 189 190 /// Unix timestamp indicating when this session expires. 191 #[cfg_attr(feature = "zeroize", zeroize(skip))] ··· 196 #[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))] 197 #[serde(untagged)] 198 enum WrappedATProtocolSession { 199 + ATProtocolSession(Box<ATProtocolSession>), 200 201 #[cfg_attr(feature = "zeroize", zeroize(skip))] 202 Error { ··· 435 .map_err(OAuthWorkflowError::SessionResponseParseFailed)?; 436 437 match response { 438 + WrappedATProtocolSession::ATProtocolSession(ref value) => Ok(*value.clone()), 439 WrappedATProtocolSession::Error { 440 ref error, 441 ref error_description,
+2 -2
crates/atproto-oauth/src/lib.rs
··· 20 pub mod pkce; 21 /// OAuth resource and authorization server management. 22 pub mod resources; 23 /// OAuth request storage abstraction for CRUD operations. 24 pub mod storage; 25 /// LRU-based implementation of OAuth request storage (requires `lru` feature). ··· 27 pub mod storage_lru; 28 /// OAuth workflow implementation for AT Protocol authorization flows. 29 pub mod workflow; 30 - /// OAuth 2.0 scope definitions and parsing for AT Protocol. 31 - pub mod scopes;
··· 20 pub mod pkce; 21 /// OAuth resource and authorization server management. 22 pub mod resources; 23 + /// OAuth 2.0 scope definitions and parsing for AT Protocol. 24 + pub mod scopes; 25 /// OAuth request storage abstraction for CRUD operations. 26 pub mod storage; 27 /// LRU-based implementation of OAuth request storage (requires `lru` feature). ··· 29 pub mod storage_lru; 30 /// OAuth workflow implementation for AT Protocol authorization flows. 31 pub mod workflow;
+24 -26
crates/atproto-oauth/src/scopes.rs
··· 182 } 183 184 let mut scopes = Vec::new(); 185 - for scope_str in s.trim().split_whitespace() { 186 scopes.push(Self::parse(scope_str)?); 187 } 188 ··· 318 let mut suffix = None; 319 320 for prefix in &prefixes { 321 - if s.starts_with(prefix) { 322 - let remainder = &s[prefix.len()..]; 323 - if remainder.is_empty() || remainder.starts_with(':') || remainder.starts_with('?') 324 - { 325 - found_prefix = Some(*prefix); 326 - if remainder.starts_with(':') { 327 - suffix = Some(&remainder[1..]); 328 - } else if remainder.starts_with('?') { 329 - suffix = Some(remainder); 330 - } else { 331 - suffix = None; 332 - } 333 - break; 334 } 335 } 336 } 337 ··· 744 (_, Scope::Email) => false, 745 (Scope::Account(a), Scope::Account(b)) => { 746 a.resource == b.resource 747 - && match (a.action, b.action) { 748 - (AccountAction::Manage, _) => true, 749 - (AccountAction::Read, AccountAction::Read) => true, 750 - _ => false, 751 - } 752 } 753 - (Scope::Identity(a), Scope::Identity(b)) => match (a, b) { 754 - (IdentityScope::All, _) => true, 755 - (IdentityScope::Handle, IdentityScope::Handle) => true, 756 - _ => false, 757 - }, 758 (Scope::Blob(a), Scope::Blob(b)) => { 759 for b_pattern in &b.accept { 760 let mut granted = false; ··· 833 fn from_str(s: &str) -> Result<Self, Self::Err> { 834 if s == "*/*" { 835 Ok(MimePattern::All) 836 - } else if s.ends_with("/*") { 837 - Ok(MimePattern::TypeWildcard(s[..s.len() - 2].to_string())) 838 } else if s.contains('/') { 839 Ok(MimePattern::Exact(s.to_string())) 840 } else {
··· 182 } 183 184 let mut scopes = Vec::new(); 185 + for scope_str in s.split_whitespace() { 186 scopes.push(Self::parse(scope_str)?); 187 } 188 ··· 318 let mut suffix = None; 319 320 for prefix in &prefixes { 321 + if let Some(remainder) = s.strip_prefix(prefix) 322 + && (remainder.is_empty() 323 + || remainder.starts_with(':') 324 + || remainder.starts_with('?')) 325 + { 326 + found_prefix = Some(*prefix); 327 + if let Some(stripped) = remainder.strip_prefix(':') { 328 + suffix = Some(stripped); 329 + } else if remainder.starts_with('?') { 330 + suffix = Some(remainder); 331 + } else { 332 + suffix = None; 333 } 334 + break; 335 } 336 } 337 ··· 744 (_, Scope::Email) => false, 745 (Scope::Account(a), Scope::Account(b)) => { 746 a.resource == b.resource 747 + && matches!( 748 + (a.action, b.action), 749 + (AccountAction::Manage, _) | (AccountAction::Read, AccountAction::Read) 750 + ) 751 } 752 + (Scope::Identity(a), Scope::Identity(b)) => matches!( 753 + (a, b), 754 + (IdentityScope::All, _) | (IdentityScope::Handle, IdentityScope::Handle) 755 + ), 756 (Scope::Blob(a), Scope::Blob(b)) => { 757 for b_pattern in &b.accept { 758 let mut granted = false; ··· 831 fn from_str(s: &str) -> Result<Self, Self::Err> { 832 if s == "*/*" { 833 Ok(MimePattern::All) 834 + } else if let Some(stripped) = s.strip_suffix("/*") { 835 + Ok(MimePattern::TypeWildcard(stripped.to_string())) 836 } else if s.contains('/') { 837 Ok(MimePattern::Exact(s.to_string())) 838 } else {