From 293114a3fdf614274242ea7b92f3ea4a2be6ce9d Mon Sep 17 00:00:00 2001 From: pgray Date: Tue, 20 Jan 2026 23:17:55 +0000 Subject: [PATCH 1/3] ndld-debug --- .github/workflows/release.yml | 2 -- Cargo.lock | 1 + ndl-core/Cargo.toml | 1 + ndl-core/src/oauth.rs | 24 +++++++++++++++--------- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eca742a..a98f8ed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -224,8 +224,6 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max release: name: Create Release diff --git a/Cargo.lock b/Cargo.lock index 72ec7e3..4cb7260 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2083,6 +2083,7 @@ version = "0.2.10" dependencies = [ "reqwest", "serde", + "serde_json", "thiserror 1.0.69", ] diff --git a/ndl-core/Cargo.toml b/ndl-core/Cargo.toml index da511e3..d28f43d 100644 --- a/ndl-core/Cargo.toml +++ b/ndl-core/Cargo.toml @@ -10,4 +10,5 @@ description = "Shared library for ndl and ndld" [dependencies] reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } serde = { version = "1", features = ["derive"] } +serde_json = "1" thiserror = "1" diff --git a/ndl-core/src/oauth.rs b/ndl-core/src/oauth.rs index 8966ee7..f50e5de 100644 --- a/ndl-core/src/oauth.rs +++ b/ndl-core/src/oauth.rs @@ -74,10 +74,12 @@ pub async fn exchange_code( return Err(TokenExchangeError::Http { status, body }); } - response - .json::() + let body = response + .text() .await - .map_err(|e| TokenExchangeError::Parse(e.to_string())) + .map_err(|e| TokenExchangeError::Parse(e.to_string()))?; + + serde_json::from_str(&body).map_err(|e| TokenExchangeError::Parse(format!("{}: {}", e, body))) } /// Exchange a short-lived access token for a long-lived one (60 days) @@ -104,10 +106,12 @@ pub async fn exchange_for_long_lived_token( return Err(TokenExchangeError::Http { status, body }); } - response - .json::() + let body = response + .text() .await - .map_err(|e| TokenExchangeError::Parse(e.to_string())) + .map_err(|e| TokenExchangeError::Parse(e.to_string()))?; + + serde_json::from_str(&body).map_err(|e| TokenExchangeError::Parse(format!("{}: {}", e, body))) } /// Refresh a long-lived access token (extends validity by another 60 days) @@ -133,8 +137,10 @@ pub async fn refresh_access_token( return Err(TokenExchangeError::Http { status, body }); } - response - .json::() + let body = response + .text() .await - .map_err(|e| TokenExchangeError::Parse(e.to_string())) + .map_err(|e| TokenExchangeError::Parse(e.to_string()))?; + + serde_json::from_str(&body).map_err(|e| TokenExchangeError::Parse(format!("{}: {}", e, body))) } From 825593c019ef5c1741d8cdf6f56dbe749580a4f5 Mon Sep 17 00:00:00 2001 From: pgray Date: Tue, 20 Jan 2026 23:34:00 +0000 Subject: [PATCH 2/3] Option:user_id --- ndl-core/src/oauth.rs | 16 +++++++++------- ndl/src/oauth.rs | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ndl-core/src/oauth.rs b/ndl-core/src/oauth.rs index f50e5de..1b398b7 100644 --- a/ndl-core/src/oauth.rs +++ b/ndl-core/src/oauth.rs @@ -5,8 +5,8 @@ pub const TOKEN_URL: &str = "https://graph.threads.net/oauth/access_token"; pub const OAUTH_SCOPES: &str = "threads_basic,threads_read_replies,threads_manage_replies,threads_content_publish"; -/// Deserialize user_id from either a string or number (Threads API returns both) -fn deserialize_user_id<'de, D>(deserializer: D) -> Result +/// Deserialize user_id from either a string or number (Threads API returns both), or None if missing +fn deserialize_user_id_opt<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { @@ -17,9 +17,11 @@ where Number(u64), } - match StringOrNumber::deserialize(deserializer)? { - StringOrNumber::String(s) => s.parse().map_err(de::Error::custom), - StringOrNumber::Number(n) => Ok(n), + let opt: Option = Option::deserialize(deserializer)?; + match opt { + Some(StringOrNumber::String(s)) => s.parse().map(Some).map_err(de::Error::custom), + Some(StringOrNumber::Number(n)) => Ok(Some(n)), + None => Ok(None), } } @@ -27,8 +29,8 @@ where pub struct TokenResponse { pub access_token: String, #[allow(dead_code)] - #[serde(deserialize_with = "deserialize_user_id")] - pub user_id: u64, + #[serde(default, deserialize_with = "deserialize_user_id_opt")] + pub user_id: Option, /// Number of seconds until the token expires (3600 for short-lived, 5184000 for long-lived) #[serde(default)] pub expires_in: Option, diff --git a/ndl/src/oauth.rs b/ndl/src/oauth.rs index e7b3d3f..3ea8b47 100644 --- a/ndl/src/oauth.rs +++ b/ndl/src/oauth.rs @@ -296,7 +296,7 @@ pub async fn hosted_login(auth_server: &str) -> Result Date: Tue, 20 Jan 2026 23:41:57 +0000 Subject: [PATCH 3/3] debug-tokens --- Cargo.lock | 9 +++++---- ndl-core/Cargo.toml | 1 + ndl-core/src/oauth.rs | 36 ++++++++++++++++++------------------ 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4cb7260..e4649c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -972,9 +972,9 @@ dependencies = [ [[package]] name = "euclid" -version = "0.22.11" +version = "0.22.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63" dependencies = [ "num-traits", ] @@ -2085,6 +2085,7 @@ dependencies = [ "serde", "serde_json", "thiserror 1.0.69", + "tracing", ] [[package]] @@ -4632,6 +4633,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" diff --git a/ndl-core/Cargo.toml b/ndl-core/Cargo.toml index d28f43d..60afabc 100644 --- a/ndl-core/Cargo.toml +++ b/ndl-core/Cargo.toml @@ -12,3 +12,4 @@ reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-feature serde = { version = "1", features = ["derive"] } serde_json = "1" thiserror = "1" +tracing = "0.1" diff --git a/ndl-core/src/oauth.rs b/ndl-core/src/oauth.rs index 1b398b7..3cd20c6 100644 --- a/ndl-core/src/oauth.rs +++ b/ndl-core/src/oauth.rs @@ -46,6 +46,21 @@ pub enum TokenExchangeError { Parse(String), } +/// Parse response body as TokenResponse, logging body at debug level on failure +async fn parse_token_response( + response: reqwest::Response, +) -> Result { + let body = response + .text() + .await + .map_err(|e| TokenExchangeError::Parse(e.to_string()))?; + + serde_json::from_str(&body).map_err(|e| { + tracing::debug!(response_body = %body, "Failed to parse token response"); + TokenExchangeError::Parse(e.to_string()) + }) +} + /// Exchange an authorization code for an access token pub async fn exchange_code( client_id: &str, @@ -76,12 +91,7 @@ pub async fn exchange_code( return Err(TokenExchangeError::Http { status, body }); } - let body = response - .text() - .await - .map_err(|e| TokenExchangeError::Parse(e.to_string()))?; - - serde_json::from_str(&body).map_err(|e| TokenExchangeError::Parse(format!("{}: {}", e, body))) + parse_token_response(response).await } /// Exchange a short-lived access token for a long-lived one (60 days) @@ -108,12 +118,7 @@ pub async fn exchange_for_long_lived_token( return Err(TokenExchangeError::Http { status, body }); } - let body = response - .text() - .await - .map_err(|e| TokenExchangeError::Parse(e.to_string()))?; - - serde_json::from_str(&body).map_err(|e| TokenExchangeError::Parse(format!("{}: {}", e, body))) + parse_token_response(response).await } /// Refresh a long-lived access token (extends validity by another 60 days) @@ -139,10 +144,5 @@ pub async fn refresh_access_token( return Err(TokenExchangeError::Http { status, body }); } - let body = response - .text() - .await - .map_err(|e| TokenExchangeError::Parse(e.to_string()))?; - - serde_json::from_str(&body).map_err(|e| TokenExchangeError::Parse(format!("{}: {}", e, body))) + parse_token_response(response).await }