+3
-3
crates/jacquard-api/Cargo.toml
+3
-3
crates/jacquard-api/Cargo.toml
+1
-1
crates/jacquard-common/src/types/value.rs
+1
-1
crates/jacquard-common/src/types/value.rs
+1
-2
crates/jacquard-common/src/xrpc.rs
+1
-2
crates/jacquard-common/src/xrpc.rs
···
28
use crate::http_client::HttpClient;
29
#[cfg(feature = "streaming")]
30
use crate::http_client::HttpClientExt;
31
use crate::types::value::Data;
32
use crate::{AuthorizationToken, error::AuthError};
33
use crate::{CowStr, error::XrpcResult};
···
162
where
163
Self::Output<'de>: Deserialize<'de>,
164
{
165
-
#[allow(deprecated)]
166
let body = serde_json::from_slice(body).map_err(|e| DecodeError::Json(e))?;
167
-
168
Ok(body)
169
}
170
}
···
28
use crate::http_client::HttpClient;
29
#[cfg(feature = "streaming")]
30
use crate::http_client::HttpClientExt;
31
+
use crate::types::nsid::Nsid;
32
use crate::types::value::Data;
33
use crate::{AuthorizationToken, error::AuthError};
34
use crate::{CowStr, error::XrpcResult};
···
163
where
164
Self::Output<'de>: Deserialize<'de>,
165
{
166
let body = serde_json::from_slice(body).map_err(|e| DecodeError::Json(e))?;
167
Ok(body)
168
}
169
}
+56
crates/jacquard-common/src/xrpc/dyn_req.rs
+56
crates/jacquard-common/src/xrpc/dyn_req.rs
···
···
1
+
pub trait DynXrpcRequest {
2
+
fn nsid(&self) -> Nsid<'static>;
3
+
fn method(&self) -> XrpcMethod;
4
+
fn response_type(&self) -> &'static str;
5
+
fn encode_body(&self) -> Result<Vec<u8>, EncodeError>;
6
+
}
7
+
8
+
pub trait DynXrpcResp {
9
+
fn nsid(&self) -> Nsid<'static>;
10
+
fn encoding(&self) -> &'static str;
11
+
fn decode_output(&self, body: &[u8]) -> Result<Data<'_>, DecodeError>;
12
+
}
13
+
14
+
impl<XRPC> DynXrpcRequest for XRPC
15
+
where
16
+
XRPC: XrpcRequest,
17
+
{
18
+
fn nsid(&self) -> Nsid<'static> {
19
+
unsafe { Nsid::new_static(XRPC::NSID).unwrap_unchecked() }
20
+
}
21
+
22
+
fn method(&self) -> XrpcMethod {
23
+
XRPC::METHOD
24
+
}
25
+
26
+
fn response_type(&self) -> &'static str {
27
+
<XRPC::Response as XrpcResp>::ENCODING
28
+
}
29
+
30
+
fn encode_body(&self) -> Result<Vec<u8>, EncodeError> {
31
+
XRPC::encode_body(self)
32
+
}
33
+
}
34
+
35
+
impl<XRPC> DynXrpcResp for XRPC
36
+
where
37
+
XRPC: XrpcResp,
38
+
{
39
+
fn nsid(&self) -> Nsid<'static> {
40
+
unsafe { Nsid::new_static(XRPC::NSID).unwrap_unchecked() }
41
+
}
42
+
43
+
fn encoding(&self) -> &'static str {
44
+
XRPC::ENCODING
45
+
}
46
+
47
+
fn decode_output(&self, body: &[u8]) -> Result<Data<'_>, DecodeError> {
48
+
if self.encoding() == "application/json" {
49
+
Ok(serde_json::from_slice::<Data>(body)?.into_static())
50
+
} else if self.encoding() == "application/vnd.ipld.car" {
51
+
Ok(serde_ipld_dagcbor::from_slice::<Data>(body)?.into_static())
52
+
} else {
53
+
Ok(Data::Bytes(Bytes::copy_from_slice(body)))
54
+
}
55
+
}
56
+
}
+9
crates/jacquard-lexicon/src/validation.rs
+9
crates/jacquard-lexicon/src/validation.rs
···
80
pub fn is_empty(&self) -> bool {
81
self.segments.is_empty()
82
}
83
}
84
85
impl Default for ValidationPath {
···
820
let Some(type_str) = obj.type_discriminator() else {
821
return vec![StructuralError::MissingUnionDiscriminator { path: path.clone() }];
822
};
823
824
// Try to match against refs
825
for variant_ref in &u.refs {
···
80
pub fn is_empty(&self) -> bool {
81
self.segments.is_empty()
82
}
83
+
84
+
pub fn segments(&self) -> &[PathSegment] {
85
+
&self.segments
86
+
}
87
}
88
89
impl Default for ValidationPath {
···
824
let Some(type_str) = obj.type_discriminator() else {
825
return vec![StructuralError::MissingUnionDiscriminator { path: path.clone() }];
826
};
827
+
828
+
// Reject empty $type
829
+
if type_str.is_empty() {
830
+
return vec![StructuralError::MissingUnionDiscriminator { path: path.clone() }];
831
+
}
832
833
// Try to match against refs
834
for variant_ref in &u.refs {
+7
-7
crates/jacquard-oauth/src/atproto.rs
+7
-7
crates/jacquard-oauth/src/atproto.rs
···
274
scope: Some(CowStr::new_static("atproto")),
275
grant_types: None,
276
token_endpoint_auth_method: Some(AuthMethod::None.into()),
277
-
dpop_bound_access_tokens: None,
278
jwks_uri: None,
279
jwks: None,
280
token_endpoint_auth_signing_alg: None,
···
316
scope: Some(CowStr::new_static("account:email atproto transition:generic")),
317
grant_types: None,
318
token_endpoint_auth_method: Some(AuthMethod::None.into()),
319
-
dpop_bound_access_tokens: None,
320
jwks_uri: None,
321
jwks: None,
322
token_endpoint_auth_signing_alg: None,
···
352
scope: Some(CowStr::new_static("atproto")),
353
grant_types: None,
354
token_endpoint_auth_method: Some(AuthMethod::None.into()),
355
-
dpop_bound_access_tokens: None,
356
jwks_uri: None,
357
jwks: None,
358
token_endpoint_auth_signing_alg: None,
···
384
scope: Some(CowStr::new_static("atproto")),
385
grant_types: None,
386
token_endpoint_auth_method: Some(AuthMethod::None.into()),
387
-
dpop_bound_access_tokens: None,
388
jwks_uri: None,
389
jwks: None,
390
token_endpoint_auth_signing_alg: None,
···
416
scope: Some(CowStr::new_static("atproto")),
417
grant_types: None,
418
token_endpoint_auth_method: Some(AuthMethod::None.into()),
419
-
dpop_bound_access_tokens: None,
420
jwks_uri: None,
421
jwks: None,
422
token_endpoint_auth_signing_alg: None,
···
446
{
447
// Non-loopback clients without a keyset should fail (must provide JWKS)
448
let metadata = metadata.clone();
449
-
let err = atproto_client_metadata(metadata, &None).expect_err("expected to fail");
450
-
assert!(matches!(err, Error::EmptyJwks));
451
}
452
{
453
let metadata = metadata.clone();
···
274
scope: Some(CowStr::new_static("atproto")),
275
grant_types: None,
276
token_endpoint_auth_method: Some(AuthMethod::None.into()),
277
+
dpop_bound_access_tokens: Some(true),
278
jwks_uri: None,
279
jwks: None,
280
token_endpoint_auth_signing_alg: None,
···
316
scope: Some(CowStr::new_static("account:email atproto transition:generic")),
317
grant_types: None,
318
token_endpoint_auth_method: Some(AuthMethod::None.into()),
319
+
dpop_bound_access_tokens: Some(true),
320
jwks_uri: None,
321
jwks: None,
322
token_endpoint_auth_signing_alg: None,
···
352
scope: Some(CowStr::new_static("atproto")),
353
grant_types: None,
354
token_endpoint_auth_method: Some(AuthMethod::None.into()),
355
+
dpop_bound_access_tokens: Some(true),
356
jwks_uri: None,
357
jwks: None,
358
token_endpoint_auth_signing_alg: None,
···
384
scope: Some(CowStr::new_static("atproto")),
385
grant_types: None,
386
token_endpoint_auth_method: Some(AuthMethod::None.into()),
387
+
dpop_bound_access_tokens: Some(true),
388
jwks_uri: None,
389
jwks: None,
390
token_endpoint_auth_signing_alg: None,
···
416
scope: Some(CowStr::new_static("atproto")),
417
grant_types: None,
418
token_endpoint_auth_method: Some(AuthMethod::None.into()),
419
+
dpop_bound_access_tokens: Some(true),
420
jwks_uri: None,
421
jwks: None,
422
token_endpoint_auth_signing_alg: None,
···
446
{
447
// Non-loopback clients without a keyset should fail (must provide JWKS)
448
let metadata = metadata.clone();
449
+
let err = atproto_client_metadata(metadata, &None);
450
+
assert!(err.is_ok());
451
}
452
{
453
let metadata = metadata.clone();
+8
-2
crates/jacquard-oauth/src/client.rs
+8
-2
crates/jacquard-oauth/src/client.rs
+7
-2
crates/jacquard-oauth/src/request.rs
+7
-2
crates/jacquard-oauth/src/request.rs
···
473
login_hint: Option<CowStr<'r>>,
474
prompt: Option<AuthorizeOptionPrompt>,
475
metadata: &OAuthMetadata,
476
) -> crate::request::Result<AuthRequestData<'r>> {
477
-
let state = generate_nonce();
478
let (code_challenge, verifier) = generate_pkce();
479
480
let Some(dpop_key) = generate_dpop_key(&metadata.server_metadata) else {
···
958
meta.server_metadata.require_pushed_authorization_requests = Some(true);
959
meta.server_metadata.pushed_authorization_request_endpoint = None;
960
// require_pushed_authorization_requests is true and no endpoint
961
-
let err = super::par(&MockClient::default(), None, None, &meta)
962
.await
963
.unwrap_err();
964
assert!(
···
473
login_hint: Option<CowStr<'r>>,
474
prompt: Option<AuthorizeOptionPrompt>,
475
metadata: &OAuthMetadata,
476
+
state: Option<CowStr<'r>>,
477
) -> crate::request::Result<AuthRequestData<'r>> {
478
+
let state = if let Some(state) = state {
479
+
state
480
+
} else {
481
+
generate_nonce()
482
+
};
483
let (code_challenge, verifier) = generate_pkce();
484
485
let Some(dpop_key) = generate_dpop_key(&metadata.server_metadata) else {
···
963
meta.server_metadata.require_pushed_authorization_requests = Some(true);
964
meta.server_metadata.pushed_authorization_request_endpoint = None;
965
// require_pushed_authorization_requests is true and no endpoint
966
+
let err = super::par(&MockClient::default(), None, None, &meta, None)
967
.await
968
.unwrap_err();
969
assert!(
-3
crates/jacquard/Cargo.toml
-3
crates/jacquard/Cargo.toml
+1
-1
crates/jacquard/tests/oauth_flow.rs
+1
-1
crates/jacquard/tests/oauth_flow.rs
···
237
keyset: None,
238
};
239
let login_hint = identity.map(|_| jacquard::CowStr::from("alice.bsky.social"));
240
-
let auth_req = jacquard_oauth::request::par(client.as_ref(), login_hint, None, &metadata)
241
.await
242
.unwrap();
243
// Construct authorization URL as OAuthClient::start_auth would do
···
237
keyset: None,
238
};
239
let login_hint = identity.map(|_| jacquard::CowStr::from("alice.bsky.social"));
240
+
let auth_req = jacquard_oauth::request::par(client.as_ref(), login_hint, None, &metadata, None)
241
.await
242
.unwrap();
243
// Construct authorization URL as OAuthClient::start_auth would do