+3
-3
crates/jacquard-api/Cargo.toml
+3
-3
crates/jacquard-api/Cargo.toml
···
42
42
43
43
streaming = ["jacquard-common/websocket"]
44
44
45
+
# --- generated ---
46
+
# Generated namespace features
47
+
45
48
app_blebbit = []
46
49
app_bsky = []
47
50
app_ocho = []
···
98
101
uk_skyblur = []
99
102
us_polhem = []
100
103
win_tomo_x = []
101
-
102
-
# --- generated ---
103
-
# Generated namespace features
+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
28
use crate::http_client::HttpClient;
29
29
#[cfg(feature = "streaming")]
30
30
use crate::http_client::HttpClientExt;
31
+
use crate::types::nsid::Nsid;
31
32
use crate::types::value::Data;
32
33
use crate::{AuthorizationToken, error::AuthError};
33
34
use crate::{CowStr, error::XrpcResult};
···
162
163
where
163
164
Self::Output<'de>: Deserialize<'de>,
164
165
{
165
-
#[allow(deprecated)]
166
166
let body = serde_json::from_slice(body).map_err(|e| DecodeError::Json(e))?;
167
-
168
167
Ok(body)
169
168
}
170
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
80
pub fn is_empty(&self) -> bool {
81
81
self.segments.is_empty()
82
82
}
83
+
84
+
pub fn segments(&self) -> &[PathSegment] {
85
+
&self.segments
86
+
}
83
87
}
84
88
85
89
impl Default for ValidationPath {
···
820
824
let Some(type_str) = obj.type_discriminator() else {
821
825
return vec![StructuralError::MissingUnionDiscriminator { path: path.clone() }];
822
826
};
827
+
828
+
// Reject empty $type
829
+
if type_str.is_empty() {
830
+
return vec![StructuralError::MissingUnionDiscriminator { path: path.clone() }];
831
+
}
823
832
824
833
// Try to match against refs
825
834
for variant_ref in &u.refs {
+7
-7
crates/jacquard-oauth/src/atproto.rs
+7
-7
crates/jacquard-oauth/src/atproto.rs
···
274
274
scope: Some(CowStr::new_static("atproto")),
275
275
grant_types: None,
276
276
token_endpoint_auth_method: Some(AuthMethod::None.into()),
277
-
dpop_bound_access_tokens: None,
277
+
dpop_bound_access_tokens: Some(true),
278
278
jwks_uri: None,
279
279
jwks: None,
280
280
token_endpoint_auth_signing_alg: None,
···
316
316
scope: Some(CowStr::new_static("account:email atproto transition:generic")),
317
317
grant_types: None,
318
318
token_endpoint_auth_method: Some(AuthMethod::None.into()),
319
-
dpop_bound_access_tokens: None,
319
+
dpop_bound_access_tokens: Some(true),
320
320
jwks_uri: None,
321
321
jwks: None,
322
322
token_endpoint_auth_signing_alg: None,
···
352
352
scope: Some(CowStr::new_static("atproto")),
353
353
grant_types: None,
354
354
token_endpoint_auth_method: Some(AuthMethod::None.into()),
355
-
dpop_bound_access_tokens: None,
355
+
dpop_bound_access_tokens: Some(true),
356
356
jwks_uri: None,
357
357
jwks: None,
358
358
token_endpoint_auth_signing_alg: None,
···
384
384
scope: Some(CowStr::new_static("atproto")),
385
385
grant_types: None,
386
386
token_endpoint_auth_method: Some(AuthMethod::None.into()),
387
-
dpop_bound_access_tokens: None,
387
+
dpop_bound_access_tokens: Some(true),
388
388
jwks_uri: None,
389
389
jwks: None,
390
390
token_endpoint_auth_signing_alg: None,
···
416
416
scope: Some(CowStr::new_static("atproto")),
417
417
grant_types: None,
418
418
token_endpoint_auth_method: Some(AuthMethod::None.into()),
419
-
dpop_bound_access_tokens: None,
419
+
dpop_bound_access_tokens: Some(true),
420
420
jwks_uri: None,
421
421
jwks: None,
422
422
token_endpoint_auth_signing_alg: None,
···
446
446
{
447
447
// Non-loopback clients without a keyset should fail (must provide JWKS)
448
448
let metadata = metadata.clone();
449
-
let err = atproto_client_metadata(metadata, &None).expect_err("expected to fail");
450
-
assert!(matches!(err, Error::EmptyJwks));
449
+
let err = atproto_client_metadata(metadata, &None);
450
+
assert!(err.is_ok());
451
451
}
452
452
{
453
453
let metadata = metadata.clone();
+8
-2
crates/jacquard-oauth/src/client.rs
+8
-2
crates/jacquard-oauth/src/client.rs
···
175
175
keyset: self.registry.client_data.keyset.clone(),
176
176
};
177
177
178
-
let auth_req_info =
179
-
par(self.client.as_ref(), login_hint, options.prompt, &metadata).await?;
178
+
let auth_req_info = par(
179
+
self.client.as_ref(),
180
+
login_hint,
181
+
options.prompt,
182
+
&metadata,
183
+
options.state,
184
+
)
185
+
.await?;
180
186
181
187
// Persist state for callback handling
182
188
self.registry
+7
-2
crates/jacquard-oauth/src/request.rs
+7
-2
crates/jacquard-oauth/src/request.rs
···
473
473
login_hint: Option<CowStr<'r>>,
474
474
prompt: Option<AuthorizeOptionPrompt>,
475
475
metadata: &OAuthMetadata,
476
+
state: Option<CowStr<'r>>,
476
477
) -> crate::request::Result<AuthRequestData<'r>> {
477
-
let state = generate_nonce();
478
+
let state = if let Some(state) = state {
479
+
state
480
+
} else {
481
+
generate_nonce()
482
+
};
478
483
let (code_challenge, verifier) = generate_pkce();
479
484
480
485
let Some(dpop_key) = generate_dpop_key(&metadata.server_metadata) else {
···
958
963
meta.server_metadata.require_pushed_authorization_requests = Some(true);
959
964
meta.server_metadata.pushed_authorization_request_endpoint = None;
960
965
// require_pushed_authorization_requests is true and no endpoint
961
-
let err = super::par(&MockClient::default(), None, None, &meta)
966
+
let err = super::par(&MockClient::default(), None, None, &meta, None)
962
967
.await
963
968
.unwrap_err();
964
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
237
keyset: None,
238
238
};
239
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)
240
+
let auth_req = jacquard_oauth::request::par(client.as_ref(), login_hint, None, &metadata, None)
241
241
.await
242
242
.unwrap();
243
243
// Construct authorization URL as OAuthClient::start_auth would do