Skip to content

Commit eda352a

Browse files
committed
Minor updates
1 parent 6ea617e commit eda352a

4 files changed

Lines changed: 91 additions & 84 deletions

File tree

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,11 @@ client.start_webhook_listener().await?;
172172

173173
## 🌐 API Endpoints
174174

175-
This SDK connects to the LicenseChain API at `https://api.licensechain.app` using the `/v1` API version prefix.
175+
Use the canonical LicenseChain API base URL `https://api.licensechain.app/v1`. The SDK also accepts the root host and normalizes requests to the same API version.
176176

177177
### Base URL
178178
```
179-
https://api.licensechain.app
179+
https://api.licensechain.app/v1
180180
```
181181

182182
### Available Endpoints
@@ -211,7 +211,7 @@ All endpoints use the `/v1` prefix:
211211
- **Analytics**:
212212
- `GET /v1/analytics/stats` - Get analytics data
213213

214-
> **Note**: The SDK automatically prepends `/v1` to all endpoint calls. You don't need to include it manually.
214+
> **Note**: The SDK accepts either the root host or the canonical `/v1` base and normalizes endpoint calls automatically.
215215
216216
## 📚 API Reference
217217

@@ -224,7 +224,7 @@ let config = LicenseChainConfig::new()
224224
.api_key("your-api-key")
225225
.app_name("your-app-name")
226226
.version("1.0.0")
227-
.base_url("https://api.licensechain.app"); // Optional
227+
.base_url("https://api.licensechain.app/v1"); // Optional
228228

229229
let mut client = LicenseChainClient::new(config);
230230
```
@@ -334,7 +334,7 @@ export LICENSECHAIN_APP_NAME=your-app-name
334334
export LICENSECHAIN_APP_VERSION=1.0.0
335335

336336
# Optional
337-
export LICENSECHAIN_BASE_URL=https://api.licensechain.app
337+
export LICENSECHAIN_BASE_URL=https://api.licensechain.app/v1
338338
export LICENSECHAIN_DEBUG=true
339339
```
340340

@@ -345,7 +345,7 @@ let config = LicenseChainConfig::new()
345345
.api_key("your-api-key")
346346
.app_name("your-app-name")
347347
.version("1.0.0")
348-
.base_url("https://api.licensechain.app")
348+
.base_url("https://api.licensechain.app/v1")
349349
.timeout(Duration::from_secs(30)) // Request timeout
350350
.retries(3) // Number of retry attempts
351351
.debug(false) // Enable debug logging

examples/basic_usage.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
1111
// Initialize the client
1212
let config = LicenseChainConfig {
1313
api_key: "your-api-key-here".to_string(),
14-
base_url: "https://api.licensechain.app".to_string(),
14+
base_url: "https://api.licensechain.app/v1".to_string(),
1515
timeout: Duration::from_secs(30),
1616
retries: 3,
1717
};

examples/license_assertion_jwks.rs

Lines changed: 60 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,60 @@
1-
//! Verify a license, then validate `license_token` with JWKS (RS256).
2-
//!
3-
//! ```bash
4-
//! export LICENSECHAIN_API_KEY=...
5-
//! export LICENSECHAIN_LICENSE_KEY=...
6-
//! cargo run --example license_assertion_jwks
7-
//! ```
8-
9-
use licensechain::{LicenseChainClient, LicenseChainConfig};
10-
use std::time::Duration;
11-
12-
#[tokio::main]
13-
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
14-
let api_key = std::env::var("LICENSECHAIN_API_KEY").unwrap_or_default();
15-
let license_key = std::env::var("LICENSECHAIN_LICENSE_KEY").unwrap_or_default();
16-
if api_key.is_empty() || license_key.is_empty() {
17-
eprintln!("Set LICENSECHAIN_API_KEY and LICENSECHAIN_LICENSE_KEY");
18-
std::process::exit(1);
19-
}
20-
21-
let config = LicenseChainConfig {
22-
api_key,
23-
base_url: std::env::var("LICENSECHAIN_BASE_URL")
24-
.unwrap_or_else(|_| "https://api.licensechain.app".to_string()),
25-
timeout: Duration::from_secs(30),
26-
retries: 2,
27-
};
28-
let client = LicenseChainClient::new(config);
29-
30-
let details = client
31-
.verify_license_with_details(&license_key, None)
32-
.await?;
33-
let valid = details
34-
.get("valid")
35-
.and_then(|v| v.as_bool())
36-
.unwrap_or(false);
37-
if !valid {
38-
eprintln!("License not valid: {details}");
39-
std::process::exit(1);
40-
}
41-
42-
let token = details
43-
.get("license_token")
44-
.and_then(|v| v.as_str())
45-
.unwrap_or("");
46-
let jwks = details
47-
.get("license_jwks_uri")
48-
.and_then(|v| v.as_str())
49-
.unwrap_or("");
50-
if token.is_empty() || jwks.is_empty() {
51-
println!("No license_token or license_jwks_uri — enable LICENSE_JWT_* on Core API for this seller.");
52-
return Ok(());
53-
}
54-
55-
let claims = client
56-
.verify_license_assertion_jwt(token, jwks, None)
57-
.await?;
58-
println!("verified claims: {claims}");
59-
Ok(())
60-
}
1+
//! Verify a license, then validate `license_token` with JWKS (RS256).
2+
//!
3+
//! ```bash
4+
//! export LICENSECHAIN_API_KEY=...
5+
//! export LICENSECHAIN_LICENSE_KEY=...
6+
//! cargo run --example license_assertion_jwks
7+
//! ```
8+
9+
use licensechain::{LicenseChainClient, LicenseChainConfig};
10+
use std::time::Duration;
11+
12+
#[tokio::main]
13+
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
14+
let api_key = std::env::var("LICENSECHAIN_API_KEY").unwrap_or_default();
15+
let license_key = std::env::var("LICENSECHAIN_LICENSE_KEY").unwrap_or_default();
16+
if api_key.is_empty() || license_key.is_empty() {
17+
eprintln!("Set LICENSECHAIN_API_KEY and LICENSECHAIN_LICENSE_KEY");
18+
std::process::exit(1);
19+
}
20+
21+
let config = LicenseChainConfig {
22+
api_key,
23+
base_url: std::env::var("LICENSECHAIN_BASE_URL")
24+
.unwrap_or_else(|_| "https://api.licensechain.app/v1".to_string()),
25+
timeout: Duration::from_secs(30),
26+
retries: 2,
27+
};
28+
let client = LicenseChainClient::new(config);
29+
30+
let details = client
31+
.verify_license_with_details(&license_key, None)
32+
.await?;
33+
let valid = details
34+
.get("valid")
35+
.and_then(|v| v.as_bool())
36+
.unwrap_or(false);
37+
if !valid {
38+
eprintln!("License not valid: {details}");
39+
std::process::exit(1);
40+
}
41+
42+
let token = details
43+
.get("license_token")
44+
.and_then(|v| v.as_str())
45+
.unwrap_or("");
46+
let jwks = details
47+
.get("license_jwks_uri")
48+
.and_then(|v| v.as_str())
49+
.unwrap_or("");
50+
if token.is_empty() || jwks.is_empty() {
51+
println!("No license_token or license_jwks_uri — enable LICENSE_JWT_* on Core API for this seller.");
52+
return Ok(());
53+
}
54+
55+
let claims = client
56+
.verify_license_assertion_jwt(token, jwks, None)
57+
.await?;
58+
println!("verified claims: {claims}");
59+
Ok(())
60+
}

src/client.rs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ impl Default for LicenseChainConfig {
1717
fn default() -> Self {
1818
Self {
1919
api_key: String::new(),
20-
base_url: "https://api.licensechain.app".to_string(),
20+
base_url: "https://api.licensechain.app/v1".to_string(),
2121
timeout: Duration::from_secs(30),
2222
retries: 3,
2323
}
@@ -453,14 +453,7 @@ impl LicenseChainClient {
453453
endpoint: &str,
454454
body: Option<serde_json::Value>,
455455
) -> Result<reqwest::Response, Box<dyn std::error::Error + Send + Sync>> {
456-
// Ensure endpoint starts with /v1 prefix
457-
let normalized_endpoint = if endpoint.starts_with("/v1/") {
458-
endpoint.to_string()
459-
} else if endpoint.starts_with('/') {
460-
format!("/v1{}", endpoint)
461-
} else {
462-
format!("/v1/{}", endpoint)
463-
};
456+
let normalized_endpoint = normalize_endpoint(&self.config.base_url, endpoint);
464457

465458
let url = format!("{}{}", self.config.base_url, normalized_endpoint);
466459

@@ -494,14 +487,7 @@ impl LicenseChainClient {
494487
body: Option<serde_json::Value>,
495488
params: Vec<(&str, String)>,
496489
) -> Result<reqwest::Response, Box<dyn std::error::Error + Send + Sync>> {
497-
// Ensure endpoint starts with /v1 prefix
498-
let normalized_endpoint = if endpoint.starts_with("/v1/") {
499-
endpoint.to_string()
500-
} else if endpoint.starts_with('/') {
501-
format!("/v1{}", endpoint)
502-
} else {
503-
format!("/v1/{}", endpoint)
504-
};
490+
let normalized_endpoint = normalize_endpoint(&self.config.base_url, endpoint);
505491

506492
let mut url = format!("{}{}", self.config.base_url, normalized_endpoint);
507493

@@ -548,6 +534,27 @@ impl LicenseChainClient {
548534
}
549535
}
550536

537+
fn normalize_endpoint(base_url: &str, endpoint: &str) -> String {
538+
let base_has_v1 = base_url.ends_with("/v1");
539+
if endpoint.starts_with("/v1/") {
540+
if base_has_v1 {
541+
endpoint.trim_start_matches("/v1").to_string()
542+
} else {
543+
endpoint.to_string()
544+
}
545+
} else if endpoint.starts_with('/') {
546+
if base_has_v1 {
547+
endpoint.to_string()
548+
} else {
549+
format!("/v1{}", endpoint)
550+
}
551+
} else if base_has_v1 {
552+
format!("/{}", endpoint)
553+
} else {
554+
format!("/v1/{}", endpoint)
555+
}
556+
}
557+
551558
#[cfg(test)]
552559
mod hwuid_hash_spec_tests {
553560
use super::*;

0 commit comments

Comments
 (0)