tangled
alpha
login
or
join now
nonbinary.computer
/
jacquard
80
fork
atom
A better Rust ATProto crate
80
fork
atom
overview
issues
9
pulls
pipelines
cleaning up some ungodly type strings
Orual
5 months ago
827a2dac
77fc8ba1
1/1
build.yml
success
1min 36s
+127
-137
6 changed files
expand all
collapse all
unified
split
crates
jacquard
src
client
credential_session.rs
client.rs
jacquard-common
src
xrpc.rs
jacquard-oauth
src
client.rs
dpop.rs
resolver.rs
+33
-36
crates/jacquard-common/src/xrpc.rs
···
232
233
impl<T: HttpClient> XrpcExt for T {}
234
0
0
0
235
/// Stateful XRPC call trait
236
#[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(Send))]
237
pub trait XrpcClient: HttpClient {
···
245
246
/// Send an XRPC request and parse the response
247
#[cfg(not(target_arch = "wasm32"))]
248
-
fn send<R>(
249
-
&self,
250
-
request: R,
251
-
) -> impl Future<Output = XrpcResult<Response<<R as XrpcRequest>::Response>>>
252
where
253
R: XrpcRequest + Send + Sync,
254
<R as XrpcRequest>::Response: Send + Sync,
···
256
257
/// Send an XRPC request and parse the response
258
#[cfg(target_arch = "wasm32")]
259
-
fn send<R>(
260
-
&self,
261
-
request: R,
262
-
) -> impl Future<Output = XrpcResult<Response<<R as XrpcRequest>::Response>>>
263
where
264
R: XrpcRequest + Send + Sync,
265
<R as XrpcRequest>::Response: Send + Sync;
···
270
&self,
271
request: R,
272
opts: CallOptions<'_>,
273
-
) -> impl Future<Output = XrpcResult<Response<<R as XrpcRequest>::Response>>>
274
where
275
R: XrpcRequest + Send + Sync,
276
<R as XrpcRequest>::Response: Send + Sync,
···
282
&self,
283
request: R,
284
opts: CallOptions<'_>,
285
-
) -> impl Future<Output = XrpcResult<Response<<R as XrpcRequest>::Response>>>
286
where
287
R: XrpcRequest + Send + Sync,
288
<R as XrpcRequest>::Response: Send + Sync;
···
517
status: StatusCode,
518
}
519
520
-
impl<Resp> Response<Resp>
521
where
522
-
Resp: XrpcResp,
523
{
524
/// Create a new response from a buffer and status code
525
pub fn new(buffer: Bytes, status: StatusCode) -> Self {
···
541
}
542
543
/// Parse the response, borrowing from the internal buffer
544
-
pub fn parse<'s>(
545
-
&'s self,
546
-
) -> Result<<Resp as XrpcResp>::Output<'s>, XrpcError<<Resp as XrpcResp>::Err<'s>>> {
547
// 200: parse as output
548
if self.status.is_success() {
549
match serde_json::from_slice::<_>(&self.buffer) {
···
558
// Fallback to generic error (InvalidRequest, ExpiredToken, etc.)
559
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
560
Ok(mut generic) => {
561
-
generic.nsid = Resp::NSID;
562
generic.method = ""; // method info only available on request
563
generic.http_status = self.status;
564
// Map auth-related errors to AuthError
···
576
} else {
577
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
578
Ok(mut generic) => {
579
-
generic.nsid = Resp::NSID;
580
generic.method = ""; // method info only available on request
581
generic.http_status = self.status;
582
match generic.error.as_str() {
···
593
/// Parse this as validated, loosely typed atproto data.
594
///
595
/// NOTE: If the response is an error, it will still parse as the matching error type for the request.
596
-
pub fn parse_data<'s>(&'s self) -> Result<Data<'s>, XrpcError<<Resp as XrpcResp>::Err<'s>>> {
597
// 200: parse as output
598
if self.status.is_success() {
599
match serde_json::from_slice::<_>(&self.buffer) {
···
608
// Fallback to generic error (InvalidRequest, ExpiredToken, etc.)
609
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
610
Ok(mut generic) => {
611
-
generic.nsid = Resp::NSID;
612
generic.method = ""; // method info only available on request
613
generic.http_status = self.status;
614
// Map auth-related errors to AuthError
···
626
} else {
627
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
628
Ok(mut generic) => {
629
-
generic.nsid = Resp::NSID;
630
generic.method = ""; // method info only available on request
631
generic.http_status = self.status;
632
match generic.error.as_str() {
···
643
/// Parse this as raw atproto data with minimal validation.
644
///
645
/// NOTE: If the response is an error, it will still parse as the matching error type for the request.
646
-
pub fn parse_raw<'s>(&'s self) -> Result<RawData<'s>, XrpcError<<Resp as XrpcResp>::Err<'s>>> {
647
// 200: parse as output
648
if self.status.is_success() {
649
match serde_json::from_slice::<_>(&self.buffer) {
···
658
// Fallback to generic error (InvalidRequest, ExpiredToken, etc.)
659
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
660
Ok(mut generic) => {
661
-
generic.nsid = Resp::NSID;
662
generic.method = ""; // method info only available on request
663
generic.http_status = self.status;
664
// Map auth-related errors to AuthError
···
676
} else {
677
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
678
Ok(mut generic) => {
679
-
generic.nsid = Resp::NSID;
680
generic.method = ""; // method info only available on request
681
generic.http_status = self.status;
682
match generic.error.as_str() {
···
710
}
711
}
712
713
-
impl<Resp> Response<Resp>
0
0
0
0
0
714
where
715
-
Resp: XrpcResp,
716
{
717
/// Parse the response into an owned output
718
-
pub fn into_output(
719
-
self,
720
-
) -> Result<<Resp as XrpcResp>::Output<'static>, XrpcError<<Resp as XrpcResp>::Err<'static>>>
721
where
722
-
for<'a> <Resp as XrpcResp>::Output<'a>:
723
-
IntoStatic<Output = <Resp as XrpcResp>::Output<'static>>,
724
-
for<'a> <Resp as XrpcResp>::Err<'a>: IntoStatic<Output = <Resp as XrpcResp>::Err<'static>>,
725
{
726
// Use a helper to make lifetime inference work
727
fn parse_output<'b, R: XrpcResp>(
···
736
737
// 200: parse as output
738
if self.status.is_success() {
739
-
match parse_output::<Resp>(&self.buffer) {
740
Ok(output) => {
741
return Ok(output.into_static());
742
}
···
744
}
745
// 400: try typed XRPC error, fallback to generic error
746
} else if self.status.as_u16() == 400 {
747
-
let error = match parse_error::<Resp>(&self.buffer) {
748
Ok(error) => XrpcError::Xrpc(error),
749
Err(_) => {
750
// Fallback to generic error (InvalidRequest, ExpiredToken, etc.)
751
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
752
Ok(mut generic) => {
753
-
generic.nsid = Resp::NSID;
754
generic.method = ""; // method info only available on request
755
generic.http_status = self.status;
756
// Map auth-related errors to AuthError
···
767
Err(error.into_static())
768
// 401: always auth error
769
} else {
770
-
let error: XrpcError<<Resp as XrpcResp>::Err<'_>> =
771
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
772
Ok(mut generic) => {
773
let status = self.status;
774
-
generic.nsid = Resp::NSID;
775
generic.method = ""; // method info only available on request
776
generic.http_status = status;
777
match generic.error.as_ref() {
···
232
233
impl<T: HttpClient> XrpcExt for T {}
234
235
+
/// Nicer alias for Xrpc response type
236
+
pub type XrpcResponse<R> = Response<<R as XrpcRequest>::Response>;
237
+
238
/// Stateful XRPC call trait
239
#[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(Send))]
240
pub trait XrpcClient: HttpClient {
···
248
249
/// Send an XRPC request and parse the response
250
#[cfg(not(target_arch = "wasm32"))]
251
+
fn send<R>(&self, request: R) -> impl Future<Output = XrpcResult<XrpcResponse<R>>>
0
0
0
252
where
253
R: XrpcRequest + Send + Sync,
254
<R as XrpcRequest>::Response: Send + Sync,
···
256
257
/// Send an XRPC request and parse the response
258
#[cfg(target_arch = "wasm32")]
259
+
fn send<R>(&self, request: R) -> impl Future<Output = XrpcResult<XrpcResponse<R>>>
0
0
0
260
where
261
R: XrpcRequest + Send + Sync,
262
<R as XrpcRequest>::Response: Send + Sync;
···
267
&self,
268
request: R,
269
opts: CallOptions<'_>,
270
+
) -> impl Future<Output = XrpcResult<XrpcResponse<R>>>
271
where
272
R: XrpcRequest + Send + Sync,
273
<R as XrpcRequest>::Response: Send + Sync,
···
279
&self,
280
request: R,
281
opts: CallOptions<'_>,
282
+
) -> impl Future<Output = XrpcResult<XrpcResponse<R>>>
283
where
284
R: XrpcRequest + Send + Sync,
285
<R as XrpcRequest>::Response: Send + Sync;
···
514
status: StatusCode,
515
}
516
517
+
impl<R> Response<R>
518
where
519
+
R: XrpcResp,
520
{
521
/// Create a new response from a buffer and status code
522
pub fn new(buffer: Bytes, status: StatusCode) -> Self {
···
538
}
539
540
/// Parse the response, borrowing from the internal buffer
541
+
pub fn parse<'s>(&'s self) -> Result<RespOutput<'s, R>, XrpcError<RespErr<'s, R>>> {
0
0
542
// 200: parse as output
543
if self.status.is_success() {
544
match serde_json::from_slice::<_>(&self.buffer) {
···
553
// Fallback to generic error (InvalidRequest, ExpiredToken, etc.)
554
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
555
Ok(mut generic) => {
556
+
generic.nsid = R::NSID;
557
generic.method = ""; // method info only available on request
558
generic.http_status = self.status;
559
// Map auth-related errors to AuthError
···
571
} else {
572
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
573
Ok(mut generic) => {
574
+
generic.nsid = R::NSID;
575
generic.method = ""; // method info only available on request
576
generic.http_status = self.status;
577
match generic.error.as_str() {
···
588
/// Parse this as validated, loosely typed atproto data.
589
///
590
/// NOTE: If the response is an error, it will still parse as the matching error type for the request.
591
+
pub fn parse_data<'s>(&'s self) -> Result<Data<'s>, XrpcError<RespErr<'s, R>>> {
592
// 200: parse as output
593
if self.status.is_success() {
594
match serde_json::from_slice::<_>(&self.buffer) {
···
603
// Fallback to generic error (InvalidRequest, ExpiredToken, etc.)
604
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
605
Ok(mut generic) => {
606
+
generic.nsid = R::NSID;
607
generic.method = ""; // method info only available on request
608
generic.http_status = self.status;
609
// Map auth-related errors to AuthError
···
621
} else {
622
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
623
Ok(mut generic) => {
624
+
generic.nsid = R::NSID;
625
generic.method = ""; // method info only available on request
626
generic.http_status = self.status;
627
match generic.error.as_str() {
···
638
/// Parse this as raw atproto data with minimal validation.
639
///
640
/// NOTE: If the response is an error, it will still parse as the matching error type for the request.
641
+
pub fn parse_raw<'s>(&'s self) -> Result<RawData<'s>, XrpcError<RespErr<'s, R>>> {
642
// 200: parse as output
643
if self.status.is_success() {
644
match serde_json::from_slice::<_>(&self.buffer) {
···
653
// Fallback to generic error (InvalidRequest, ExpiredToken, etc.)
654
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
655
Ok(mut generic) => {
656
+
generic.nsid = R::NSID;
657
generic.method = ""; // method info only available on request
658
generic.http_status = self.status;
659
// Map auth-related errors to AuthError
···
671
} else {
672
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
673
Ok(mut generic) => {
674
+
generic.nsid = R::NSID;
675
generic.method = ""; // method info only available on request
676
generic.http_status = self.status;
677
match generic.error.as_str() {
···
705
}
706
}
707
708
+
/// doc
709
+
pub type RespOutput<'a, Resp> = <Resp as XrpcResp>::Output<'a>;
710
+
/// doc
711
+
pub type RespErr<'a, Resp> = <Resp as XrpcResp>::Err<'a>;
712
+
713
+
impl<R> Response<R>
714
where
715
+
R: XrpcResp,
716
{
717
/// Parse the response into an owned output
718
+
pub fn into_output(self) -> Result<RespOutput<'static, R>, XrpcError<RespErr<'static, R>>>
0
0
719
where
720
+
for<'a> RespOutput<'a, R>: IntoStatic<Output = RespOutput<'static, R>>,
721
+
for<'a> RespErr<'a, R>: IntoStatic<Output = RespErr<'static, R>>,
0
722
{
723
// Use a helper to make lifetime inference work
724
fn parse_output<'b, R: XrpcResp>(
···
733
734
// 200: parse as output
735
if self.status.is_success() {
736
+
match parse_output::<R>(&self.buffer) {
737
Ok(output) => {
738
return Ok(output.into_static());
739
}
···
741
}
742
// 400: try typed XRPC error, fallback to generic error
743
} else if self.status.as_u16() == 400 {
744
+
let error = match parse_error::<R>(&self.buffer) {
745
Ok(error) => XrpcError::Xrpc(error),
746
Err(_) => {
747
// Fallback to generic error (InvalidRequest, ExpiredToken, etc.)
748
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
749
Ok(mut generic) => {
750
+
generic.nsid = R::NSID;
751
generic.method = ""; // method info only available on request
752
generic.http_status = self.status;
753
// Map auth-related errors to AuthError
···
764
Err(error.into_static())
765
// 401: always auth error
766
} else {
767
+
let error: XrpcError<<R as XrpcResp>::Err<'_>> =
768
match serde_json::from_slice::<GenericXrpcError>(&self.buffer) {
769
Ok(mut generic) => {
770
let status = self.status;
771
+
generic.nsid = R::NSID;
772
generic.method = ""; // method info only available on request
773
generic.http_status = status;
774
match generic.error.as_ref() {
+12
-16
crates/jacquard-oauth/src/client.rs
···
15
http_client::HttpClient,
16
types::{did::Did, string::Handle},
17
xrpc::{
18
-
CallOptions, Response, XrpcClient, XrpcExt, XrpcRequest, XrpcResp, build_http_request,
19
-
process_response,
20
},
21
};
22
-
use jacquard_identity::{JacquardResolver, resolver::IdentityResolver};
0
0
0
23
use jose_jwk::JwkSet;
24
use std::sync::Arc;
25
use tokio::sync::RwLock;
···
434
self.options.read().await.clone()
435
}
436
437
-
async fn send<R>(&self, request: R) -> XrpcResult<Response<<R as XrpcRequest>::Response>>
438
where
439
R: XrpcRequest + Send + Sync,
440
<R as XrpcRequest>::Response: Send + Sync,
···
447
&self,
448
request: R,
449
mut opts: CallOptions<'_>,
450
-
) -> XrpcResult<Response<<R as XrpcRequest>::Response>>
451
where
452
R: XrpcRequest + Send + Sync,
453
<R as XrpcRequest>::Response: Send + Sync,
···
492
.to_str()
493
.is_ok_and(|s| s.starts_with("DPoP ") && s.contains("error=\"invalid_token\"")),
494
Ok(resp) => match resp.parse() {
495
-
Err(jacquard_common::xrpc::XrpcError::Auth(AuthError::InvalidToken)) => true,
496
_ => false,
497
},
498
_ => false,
···
504
S: ClientAuthStore + Send + Sync + 'static,
505
T: OAuthResolver + IdentityResolver + XrpcExt + Send + Sync + 'static,
506
{
507
-
fn options(&self) -> &jacquard_identity::resolver::ResolverOptions {
508
self.client.options()
509
}
510
511
fn resolve_handle(
512
&self,
513
handle: &Handle<'_>,
514
-
) -> impl Future<
515
-
Output = std::result::Result<Did<'static>, jacquard_identity::resolver::IdentityError>,
516
-
> {
517
async { self.client.resolve_handle(handle).await }
518
}
519
520
fn resolve_did_doc(
521
&self,
522
did: &Did<'_>,
523
-
) -> impl Future<
524
-
Output = std::result::Result<
525
-
jacquard_identity::resolver::DidDocResponse,
526
-
jacquard_identity::resolver::IdentityError,
527
-
>,
528
-
> {
529
async { self.client.resolve_did_doc(did).await }
530
}
531
}
···
15
http_client::HttpClient,
16
types::{did::Did, string::Handle},
17
xrpc::{
18
+
CallOptions, Response, XrpcClient, XrpcError, XrpcExt, XrpcRequest, XrpcResp, XrpcResponse,
19
+
build_http_request, process_response,
20
},
21
};
22
+
use jacquard_identity::{
23
+
JacquardResolver,
24
+
resolver::{DidDocResponse, IdentityError, IdentityResolver, ResolverOptions},
25
+
};
26
use jose_jwk::JwkSet;
27
use std::sync::Arc;
28
use tokio::sync::RwLock;
···
437
self.options.read().await.clone()
438
}
439
440
+
async fn send<R>(&self, request: R) -> XrpcResult<XrpcResponse<R>>
441
where
442
R: XrpcRequest + Send + Sync,
443
<R as XrpcRequest>::Response: Send + Sync,
···
450
&self,
451
request: R,
452
mut opts: CallOptions<'_>,
453
+
) -> XrpcResult<XrpcResponse<R>>
454
where
455
R: XrpcRequest + Send + Sync,
456
<R as XrpcRequest>::Response: Send + Sync,
···
495
.to_str()
496
.is_ok_and(|s| s.starts_with("DPoP ") && s.contains("error=\"invalid_token\"")),
497
Ok(resp) => match resp.parse() {
498
+
Err(XrpcError::Auth(AuthError::InvalidToken)) => true,
499
_ => false,
500
},
501
_ => false,
···
507
S: ClientAuthStore + Send + Sync + 'static,
508
T: OAuthResolver + IdentityResolver + XrpcExt + Send + Sync + 'static,
509
{
510
+
fn options(&self) -> &ResolverOptions {
511
self.client.options()
512
}
513
514
fn resolve_handle(
515
&self,
516
handle: &Handle<'_>,
517
+
) -> impl Future<Output = std::result::Result<Did<'static>, IdentityError>> {
0
0
518
async { self.client.resolve_handle(handle).await }
519
}
520
521
fn resolve_did_doc(
522
&self,
523
did: &Did<'_>,
524
+
) -> impl Future<Output = std::result::Result<DidDocResponse, IdentityError>> {
0
0
0
0
0
525
async { self.client.resolve_did_doc(did).await }
526
}
527
}
+14
-3
crates/jacquard-oauth/src/dpop.rs
···
0
0
1
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
2
use chrono::Utc;
3
use http::{Request, Response, header::InvalidHeaderValue};
···
43
44
#[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(Send))]
45
pub trait DpopClient: HttpClient {
46
-
fn dpop_server(&self, request: Request<Vec<u8>>) -> impl std::future::Future<Output = Result<Response<Vec<u8>>>>;
47
-
fn dpop_client(&self, request: Request<Vec<u8>>) -> impl std::future::Future<Output = Result<Response<Vec<u8>>>>;
48
-
fn wrap_request(&self, request: Request<Vec<u8>>) -> impl std::future::Future<Output = Result<Response<Vec<u8>>>>;
0
0
0
0
0
0
0
0
0
49
}
50
51
pub trait DpopExt: HttpClient {
···
1
+
use std::future::Future;
2
+
3
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
4
use chrono::Utc;
5
use http::{Request, Response, header::InvalidHeaderValue};
···
45
46
#[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(Send))]
47
pub trait DpopClient: HttpClient {
48
+
fn dpop_server(
49
+
&self,
50
+
request: Request<Vec<u8>>,
51
+
) -> impl Future<Output = Result<Response<Vec<u8>>>>;
52
+
fn dpop_client(
53
+
&self,
54
+
request: Request<Vec<u8>>,
55
+
) -> impl Future<Output = Result<Response<Vec<u8>>>>;
56
+
fn wrap_request(
57
+
&self,
58
+
request: Request<Vec<u8>>,
59
+
) -> impl Future<Output = Result<Response<Vec<u8>>>>;
60
}
61
62
pub trait DpopExt: HttpClient {
+26
-29
crates/jacquard-oauth/src/resolver.rs
···
0
0
0
1
use crate::types::{OAuthAuthorizationServerMetadata, OAuthProtectedResourceMetadata};
2
use http::{Request, StatusCode};
3
use jacquard_common::CowStr;
···
400
&self,
401
server_metadata: &OAuthAuthorizationServerMetadata<'_>,
402
sub: &Did<'_>,
403
-
) -> impl std::future::Future<Output = Result<Url, ResolverError>> + Send
404
where
405
Self: Sync,
406
{
···
412
&self,
413
server_metadata: &OAuthAuthorizationServerMetadata<'_>,
414
sub: &Did<'_>,
415
-
) -> impl std::future::Future<Output = Result<Url, ResolverError>> {
416
verify_issuer_impl(self, server_metadata, sub)
417
}
418
···
420
fn resolve_oauth(
421
&self,
422
input: &str,
423
-
) -> impl std::future::Future<
424
Output = Result<
425
(
426
OAuthAuthorizationServerMetadata<'static>,
···
439
fn resolve_oauth(
440
&self,
441
input: &str,
442
-
) -> impl std::future::Future<
443
Output = Result<
444
(
445
OAuthAuthorizationServerMetadata<'static>,
···
455
fn resolve_from_service(
456
&self,
457
input: &Url,
458
-
) -> impl std::future::Future<
459
-
Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>,
460
-
> + Send
461
where
462
Self: Sync,
463
{
···
468
fn resolve_from_service(
469
&self,
470
input: &Url,
471
-
) -> impl std::future::Future<
472
-
Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>,
473
-
> {
474
resolve_from_service_impl(self, input)
475
}
476
···
478
fn resolve_from_identity(
479
&self,
480
input: &str,
481
-
) -> impl std::future::Future<
482
Output = Result<
483
(
484
OAuthAuthorizationServerMetadata<'static>,
···
497
fn resolve_from_identity(
498
&self,
499
input: &str,
500
-
) -> impl std::future::Future<
501
Output = Result<
502
(
503
OAuthAuthorizationServerMetadata<'static>,
···
513
fn get_authorization_server_metadata(
514
&self,
515
issuer: &Url,
516
-
) -> impl std::future::Future<
517
-
Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>,
518
-
> + Send
519
where
520
Self: Sync,
521
{
···
526
fn get_authorization_server_metadata(
527
&self,
528
issuer: &Url,
529
-
) -> impl std::future::Future<
530
-
Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>,
531
-
> {
532
get_authorization_server_metadata_impl(self, issuer)
533
}
534
···
536
fn get_resource_server_metadata(
537
&self,
538
pds: &Url,
539
-
) -> impl std::future::Future<
540
-
Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>,
541
-
> + Send
542
where
543
Self: Sync,
544
{
···
549
fn get_resource_server_metadata(
550
&self,
551
pds: &Url,
552
-
) -> impl std::future::Future<
553
-
Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>,
554
-
> {
555
get_resource_server_metadata_impl(self, pds)
556
}
557
}
···
630
631
#[cfg(test)]
632
mod tests {
0
0
0
633
use super::*;
634
use http::{Request as HttpRequest, Response as HttpResponse, StatusCode};
635
use jacquard_common::http_client::HttpClient;
0
636
637
#[derive(Default, Clone)]
638
struct MockHttp {
639
-
next: std::sync::Arc<tokio::sync::Mutex<Option<HttpResponse<Vec<u8>>>>>,
640
}
641
642
impl HttpClient for MockHttp {
643
-
type Error = std::convert::Infallible;
644
fn send_http(
645
&self,
646
_request: HttpRequest<Vec<u8>>,
647
-
) -> impl core::future::Future<
648
-
Output = core::result::Result<HttpResponse<Vec<u8>>, Self::Error>,
649
-
> + Send {
650
let next = self.next.clone();
651
async move { Ok(next.lock().await.take().unwrap()) }
652
}
···
1
+
#[cfg(not(target_arch = "wasm32"))]
2
+
use std::future::Future;
3
+
4
use crate::types::{OAuthAuthorizationServerMetadata, OAuthProtectedResourceMetadata};
5
use http::{Request, StatusCode};
6
use jacquard_common::CowStr;
···
403
&self,
404
server_metadata: &OAuthAuthorizationServerMetadata<'_>,
405
sub: &Did<'_>,
406
+
) -> impl Future<Output = Result<Url, ResolverError>> + Send
407
where
408
Self: Sync,
409
{
···
415
&self,
416
server_metadata: &OAuthAuthorizationServerMetadata<'_>,
417
sub: &Did<'_>,
418
+
) -> impl Future<Output = Result<Url, ResolverError>> {
419
verify_issuer_impl(self, server_metadata, sub)
420
}
421
···
423
fn resolve_oauth(
424
&self,
425
input: &str,
426
+
) -> impl Future<
427
Output = Result<
428
(
429
OAuthAuthorizationServerMetadata<'static>,
···
442
fn resolve_oauth(
443
&self,
444
input: &str,
445
+
) -> impl Future<
446
Output = Result<
447
(
448
OAuthAuthorizationServerMetadata<'static>,
···
458
fn resolve_from_service(
459
&self,
460
input: &Url,
461
+
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>> + Send
0
0
462
where
463
Self: Sync,
464
{
···
469
fn resolve_from_service(
470
&self,
471
input: &Url,
472
+
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>>
473
+
{
0
474
resolve_from_service_impl(self, input)
475
}
476
···
478
fn resolve_from_identity(
479
&self,
480
input: &str,
481
+
) -> impl Future<
482
Output = Result<
483
(
484
OAuthAuthorizationServerMetadata<'static>,
···
497
fn resolve_from_identity(
498
&self,
499
input: &str,
500
+
) -> impl Future<
501
Output = Result<
502
(
503
OAuthAuthorizationServerMetadata<'static>,
···
513
fn get_authorization_server_metadata(
514
&self,
515
issuer: &Url,
516
+
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>> + Send
0
0
517
where
518
Self: Sync,
519
{
···
524
fn get_authorization_server_metadata(
525
&self,
526
issuer: &Url,
527
+
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>>
528
+
{
0
529
get_authorization_server_metadata_impl(self, issuer)
530
}
531
···
533
fn get_resource_server_metadata(
534
&self,
535
pds: &Url,
536
+
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>> + Send
0
0
537
where
538
Self: Sync,
539
{
···
544
fn get_resource_server_metadata(
545
&self,
546
pds: &Url,
547
+
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>, ResolverError>>
548
+
{
0
549
get_resource_server_metadata_impl(self, pds)
550
}
551
}
···
624
625
#[cfg(test)]
626
mod tests {
627
+
use core::future::Future;
628
+
use std::{convert::Infallible, sync::Arc};
629
+
630
use super::*;
631
use http::{Request as HttpRequest, Response as HttpResponse, StatusCode};
632
use jacquard_common::http_client::HttpClient;
633
+
use tokio::sync::Mutex;
634
635
#[derive(Default, Clone)]
636
struct MockHttp {
637
+
next: Arc<Mutex<Option<HttpResponse<Vec<u8>>>>>,
638
}
639
640
impl HttpClient for MockHttp {
641
+
type Error = Infallible;
642
fn send_http(
643
&self,
644
_request: HttpRequest<Vec<u8>>,
645
+
) -> impl Future<Output = core::result::Result<HttpResponse<Vec<u8>>, Self::Error>> + Send
646
+
{
0
647
let next = self.next.clone();
648
async move { Ok(next.lock().await.take().unwrap()) }
649
}
+31
-40
crates/jacquard/src/client.rs
···
40
CowStr, IntoStatic,
41
types::string::{Did, Handle},
42
};
43
-
use jacquard_identity::resolver::IdentityResolver;
0
0
44
use jacquard_oauth::authstore::ClientAuthStore;
45
use jacquard_oauth::client::OAuthSession;
46
use jacquard_oauth::dpop::DpopExt;
···
325
server::{create_session::CreateSessionOutput, refresh_session::RefreshSessionOutput},
326
};
327
0
0
0
0
0
0
0
0
0
328
/// Extension trait providing convenience methods for common repository operations.
329
///
330
/// This trait is automatically implemented for any type that implements both
···
406
&self,
407
record: R,
408
rkey: Option<RecordKey<Rkey<'_>>>,
409
-
) -> impl std::future::Future<Output = Result<CreateRecordOutput<'static>, AgentError>>
410
where
411
R: Collection + serde::Serialize,
412
{
···
474
fn get_record<R>(
475
&self,
476
uri: AtUri<'_>,
477
-
) -> impl std::future::Future<Output = Result<Response<R::Record>, ClientError>>
478
where
479
R: Collection,
480
{
···
550
&self,
551
record_type: R,
552
uri: AtUri<'_>,
553
-
) -> impl std::future::Future<
554
-
Output = Result<<<R as Collection>::Record as XrpcResp>::Output<'static>, ClientError>,
555
-
>
556
where
557
R: Collection,
558
-
for<'a> <<R as Collection>::Record as XrpcResp>::Output<'a>:
559
-
IntoStatic<Output = <<R as Collection>::Record as XrpcResp>::Output<'static>>,
560
-
for<'a> <<R as Collection>::Record as XrpcResp>::Err<'a>:
561
-
IntoStatic<Output = <<R as Collection>::Record as XrpcResp>::Err<'static>>,
562
{
563
let _ = record_type;
564
async move {
···
602
&self,
603
uri: AtUri<'_>,
604
f: impl FnOnce(&mut R),
605
-
) -> impl std::future::Future<Output = Result<PutRecordOutput<'static>, AgentError>>
606
where
607
R: Collection + Serialize,
608
-
R: for<'a> From<<<R as Collection>::Record as XrpcResp>::Output<'a>>,
609
{
610
async move {
611
#[cfg(feature = "tracing")]
···
652
fn delete_record<R>(
653
&self,
654
rkey: RecordKey<Rkey<'_>>,
655
-
) -> impl std::future::Future<Output = Result<DeleteRecordOutput<'static>, AgentError>>
656
where
657
R: Collection,
658
{
···
692
&self,
693
rkey: RecordKey<Rkey<'static>>,
694
record: R,
695
-
) -> impl std::future::Future<Output = Result<PutRecordOutput<'static>, AgentError>>
696
where
697
R: Collection + serde::Serialize,
698
{
···
755
&self,
756
data: impl Into<bytes::Bytes>,
757
mime_type: MimeType<'_>,
758
-
) -> impl std::future::Future<Output = Result<Blob<'static>, AgentError>> {
759
async move {
760
#[cfg(feature = "tracing")]
761
let _span = tracing::debug_span!("upload_blob", mime_type = %mime_type).entered();
···
808
fn update_vec<U>(
809
&self,
810
modify: impl FnOnce(&mut Vec<<U as VecUpdate>::Item>),
811
-
) -> impl std::future::Future<
812
-
Output = Result<
813
-
xrpc::Response<<<U as VecUpdate>::PutRequest as XrpcRequest>::Response>,
814
-
AgentError,
815
-
>,
816
-
>
817
where
818
U: VecUpdate,
819
<U as VecUpdate>::PutRequest: Send + Sync,
820
<U as VecUpdate>::GetRequest: Send + Sync,
821
-
<<U as VecUpdate>::GetRequest as XrpcRequest>::Response: Send + Sync,
822
-
<<U as VecUpdate>::PutRequest as XrpcRequest>::Response: Send + Sync,
823
{
824
async {
825
// Fetch current data
···
863
fn update_vec_item<U>(
864
&self,
865
item: <U as VecUpdate>::Item,
866
-
) -> impl std::future::Future<
867
-
Output = Result<
868
-
xrpc::Response<<<U as VecUpdate>::PutRequest as XrpcRequest>::Response>,
869
-
AgentError,
870
-
>,
871
-
>
872
where
873
U: VecUpdate,
874
<U as VecUpdate>::PutRequest: Send + Sync,
875
<U as VecUpdate>::GetRequest: Send + Sync,
876
-
<<U as VecUpdate>::GetRequest as XrpcRequest>::Response: Send + Sync,
877
-
<<U as VecUpdate>::PutRequest as XrpcRequest>::Response: Send + Sync,
878
{
879
async {
880
self.update_vec::<U>(|vec| {
···
944
}
945
946
impl<A: AgentSession + IdentityResolver> IdentityResolver for Agent<A> {
947
-
fn options(&self) -> &jacquard_identity::resolver::ResolverOptions {
948
self.inner.options()
949
}
950
951
fn resolve_handle(
952
&self,
953
handle: &Handle<'_>,
954
-
) -> impl Future<Output = Result<Did<'static>, jacquard_identity::resolver::IdentityError>>
955
-
{
956
async { self.inner.resolve_handle(handle).await }
957
}
958
959
fn resolve_did_doc(
960
&self,
961
did: &Did<'_>,
962
-
) -> impl Future<
963
-
Output = Result<
964
-
jacquard_identity::resolver::DidDocResponse,
965
-
jacquard_identity::resolver::IdentityError,
966
-
>,
967
-
> {
968
async { self.inner.resolve_did_doc(did).await }
969
}
970
}
···
40
CowStr, IntoStatic,
41
types::string::{Did, Handle},
42
};
43
+
use jacquard_identity::resolver::{
44
+
DidDocResponse, IdentityError, IdentityResolver, ResolverOptions,
45
+
};
46
use jacquard_oauth::authstore::ClientAuthStore;
47
use jacquard_oauth::client::OAuthSession;
48
use jacquard_oauth::dpop::DpopExt;
···
327
server::{create_session::CreateSessionOutput, refresh_session::RefreshSessionOutput},
328
};
329
330
+
/// doc
331
+
pub type CollectionOutput<'a, R> = <<R as Collection>::Record as XrpcResp>::Output<'a>;
332
+
/// doc
333
+
pub type CollectionErr<'a, R> = <<R as Collection>::Record as XrpcResp>::Err<'a>;
334
+
/// doc
335
+
pub type VecGetResponse<U> = <<U as VecUpdate>::GetRequest as XrpcRequest>::Response;
336
+
/// doc
337
+
pub type VecPutResponse<U> = <<U as VecUpdate>::PutRequest as XrpcRequest>::Response;
338
+
339
/// Extension trait providing convenience methods for common repository operations.
340
///
341
/// This trait is automatically implemented for any type that implements both
···
417
&self,
418
record: R,
419
rkey: Option<RecordKey<Rkey<'_>>>,
420
+
) -> impl Future<Output = Result<CreateRecordOutput<'static>, AgentError>>
421
where
422
R: Collection + serde::Serialize,
423
{
···
485
fn get_record<R>(
486
&self,
487
uri: AtUri<'_>,
488
+
) -> impl Future<Output = Result<Response<R::Record>, ClientError>>
489
where
490
R: Collection,
491
{
···
561
&self,
562
record_type: R,
563
uri: AtUri<'_>,
564
+
) -> impl Future<Output = Result<CollectionOutput<'static, R>, ClientError>>
0
0
565
where
566
R: Collection,
567
+
for<'a> CollectionOutput<'a, R>: IntoStatic<Output = CollectionOutput<'static, R>>,
568
+
for<'a> CollectionErr<'a, R>: IntoStatic<Output = CollectionErr<'static, R>>,
0
0
569
{
570
let _ = record_type;
571
async move {
···
609
&self,
610
uri: AtUri<'_>,
611
f: impl FnOnce(&mut R),
612
+
) -> impl Future<Output = Result<PutRecordOutput<'static>, AgentError>>
613
where
614
R: Collection + Serialize,
615
+
R: for<'a> From<CollectionOutput<'a, R>>,
616
{
617
async move {
618
#[cfg(feature = "tracing")]
···
659
fn delete_record<R>(
660
&self,
661
rkey: RecordKey<Rkey<'_>>,
662
+
) -> impl Future<Output = Result<DeleteRecordOutput<'static>, AgentError>>
663
where
664
R: Collection,
665
{
···
699
&self,
700
rkey: RecordKey<Rkey<'static>>,
701
record: R,
702
+
) -> impl Future<Output = Result<PutRecordOutput<'static>, AgentError>>
703
where
704
R: Collection + serde::Serialize,
705
{
···
762
&self,
763
data: impl Into<bytes::Bytes>,
764
mime_type: MimeType<'_>,
765
+
) -> impl Future<Output = Result<Blob<'static>, AgentError>> {
766
async move {
767
#[cfg(feature = "tracing")]
768
let _span = tracing::debug_span!("upload_blob", mime_type = %mime_type).entered();
···
815
fn update_vec<U>(
816
&self,
817
modify: impl FnOnce(&mut Vec<<U as VecUpdate>::Item>),
818
+
) -> impl Future<Output = Result<xrpc::Response<VecPutResponse<U>>, AgentError>>
0
0
0
0
0
819
where
820
U: VecUpdate,
821
<U as VecUpdate>::PutRequest: Send + Sync,
822
<U as VecUpdate>::GetRequest: Send + Sync,
823
+
VecGetResponse<U>: Send + Sync,
824
+
VecPutResponse<U>: Send + Sync,
825
{
826
async {
827
// Fetch current data
···
865
fn update_vec_item<U>(
866
&self,
867
item: <U as VecUpdate>::Item,
868
+
) -> impl Future<Output = Result<xrpc::Response<VecPutResponse<U>>, AgentError>>
0
0
0
0
0
869
where
870
U: VecUpdate,
871
<U as VecUpdate>::PutRequest: Send + Sync,
872
<U as VecUpdate>::GetRequest: Send + Sync,
873
+
VecGetResponse<U>: Send + Sync,
874
+
VecPutResponse<U>: Send + Sync,
875
{
876
async {
877
self.update_vec::<U>(|vec| {
···
941
}
942
943
impl<A: AgentSession + IdentityResolver> IdentityResolver for Agent<A> {
944
+
fn options(&self) -> &ResolverOptions {
945
self.inner.options()
946
}
947
948
fn resolve_handle(
949
&self,
950
handle: &Handle<'_>,
951
+
) -> impl Future<Output = Result<Did<'static>, IdentityError>> {
0
952
async { self.inner.resolve_handle(handle).await }
953
}
954
955
fn resolve_did_doc(
956
&self,
957
did: &Did<'_>,
958
+
) -> impl Future<Output = Result<DidDocResponse, IdentityError>> {
0
0
0
0
0
959
async { self.inner.resolve_did_doc(did).await }
960
}
961
}
+11
-13
crates/jacquard/src/client/credential_session.rs
···
9
http_client::HttpClient,
10
session::SessionStore,
11
types::{did::Did, string::Handle},
12
-
xrpc::{CallOptions, Response, XrpcClient, XrpcError, XrpcExt, XrpcRequest, XrpcResp},
0
0
13
};
14
use tokio::sync::RwLock;
15
use url::Url;
16
17
use crate::client::AtpSession;
18
-
use jacquard_identity::resolver::IdentityResolver;
0
0
19
use std::any::Any;
20
21
/// Storage key for app‑password sessions: `(account DID, session id)`.
···
426
}
427
}
428
429
-
async fn send<R>(&self, request: R) -> XrpcResult<Response<<R as XrpcRequest>::Response>>
430
where
431
R: XrpcRequest + Send + Sync,
432
<R as XrpcRequest>::Response: Send + Sync,
···
439
&self,
440
request: R,
441
mut opts: CallOptions<'_>,
442
-
) -> XrpcResult<Response<<R as XrpcRequest>::Response>>
443
where
444
R: XrpcRequest + Send + Sync,
445
<R as XrpcRequest>::Response: Send + Sync,
···
485
S: SessionStore<SessionKey, AtpSession> + Send + Sync + 'static,
486
T: HttpClient + IdentityResolver + Send + Sync + 'static,
487
{
488
-
fn options(&self) -> &jacquard_identity::resolver::ResolverOptions {
489
self.client.options()
490
}
491
492
fn resolve_handle(
493
&self,
494
handle: &Handle<'_>,
495
-
) -> impl Future<Output = Result<Did<'static>, jacquard_identity::resolver::IdentityError>>
496
-
{
497
async { self.client.resolve_handle(handle).await }
498
}
499
500
fn resolve_did_doc(
501
&self,
502
did: &Did<'_>,
503
-
) -> impl Future<
504
-
Output = Result<
505
-
jacquard_identity::resolver::DidDocResponse,
506
-
jacquard_identity::resolver::IdentityError,
507
-
>,
508
-
> {
509
async { self.client.resolve_did_doc(did).await }
510
}
511
}
···
9
http_client::HttpClient,
10
session::SessionStore,
11
types::{did::Did, string::Handle},
12
+
xrpc::{
13
+
CallOptions, Response, XrpcClient, XrpcError, XrpcExt, XrpcRequest, XrpcResp, XrpcResponse,
14
+
},
15
};
16
use tokio::sync::RwLock;
17
use url::Url;
18
19
use crate::client::AtpSession;
20
+
use jacquard_identity::resolver::{
21
+
DidDocResponse, IdentityError, IdentityResolver, ResolverOptions,
22
+
};
23
use std::any::Any;
24
25
/// Storage key for app‑password sessions: `(account DID, session id)`.
···
430
}
431
}
432
433
+
async fn send<R>(&self, request: R) -> XrpcResult<XrpcResponse<R>>
434
where
435
R: XrpcRequest + Send + Sync,
436
<R as XrpcRequest>::Response: Send + Sync,
···
443
&self,
444
request: R,
445
mut opts: CallOptions<'_>,
446
+
) -> XrpcResult<XrpcResponse<R>>
447
where
448
R: XrpcRequest + Send + Sync,
449
<R as XrpcRequest>::Response: Send + Sync,
···
489
S: SessionStore<SessionKey, AtpSession> + Send + Sync + 'static,
490
T: HttpClient + IdentityResolver + Send + Sync + 'static,
491
{
492
+
fn options(&self) -> &ResolverOptions {
493
self.client.options()
494
}
495
496
fn resolve_handle(
497
&self,
498
handle: &Handle<'_>,
499
+
) -> impl Future<Output = Result<Did<'static>, IdentityError>> {
0
500
async { self.client.resolve_handle(handle).await }
501
}
502
503
fn resolve_did_doc(
504
&self,
505
did: &Did<'_>,
506
+
) -> impl Future<Output = Result<DidDocResponse, IdentityError>> {
0
0
0
0
0
507
async { self.client.resolve_did_doc(did).await }
508
}
509
}