Expand description
§Prawn - Rust Client Library for the Tidal API
Prawns (shrimp, really) are one of the most common crustacean you will see in a tide pool :)
Prawn is a Rust library exposing a thin wrapper client around generated API clients and models for the Tidal V2 API. The wrapper client is intended to make managing authenticating via OAuth 2.0 with Tidal easier by exposing a few simple additional methods and managing refresh tokens. The OAuth methods rely on the oauth2 crate.
The API clients/models/docs were (mostly, with some modifications) generated by the OpenAPI Generator project.
Prawn currently supports the Authorization Code flow and the Client Credentials flow, which are the only ones documented as supported in Tidal’s public docs
I developed this library because the other most comprehensive Tidal library for Rust is tidalrs. However, tidalrs only supports device authorization, does not support the authorization code or client credentials flow, and only interacts with the V1 Tidal API. Changing these assumptions felt big enough to warrant its own library in the end.
§Examples
§Generate an authorize URL
use prawn::client::{TidalClient, TidalClientConfig, OAuthConfig};
let client_id = "<your client id>";
let redirect_uri = "https://example.com/callback";
let config = TidalClientConfig {
oauth_config: OAuthConfig {
redirect_uri: redirect_uri.to_string(),
client_id: client_id.to_string(),
client_secret: None
},
auth_token: None, // we haven't authorized yet, so we don't have this.
retry_config: None,
};
let client = TidalClient::new(config)?;
let (challenge, verifier) = client.generate_pkce_challenge_and_verifier();
let scopes = vec!["user.read"];
let (auth_url, state) = client.get_authorize_url_and_state(challenge, scopes.to_vec());
println!("visit {} to authorize", auth_url);
§Exchange url for token and call API, retry rate limited requests
use prawn::client::{TidalClient, TidalClientConfig, OAuthConfig, Token, RetryConfig};
// ... /callback implementation ...
let code = "<extract from query params>";
let verifier = "<stored verifier somewher>";
let client_id = "<your client id>";
let redirect_uri = "https://example.com/callback";
let config = TidalClientConfig {
oauth_config: OAuthConfig {
redirect_uri: redirect_uri.to_string(),
client_id: client_id.to_string(),
client_secret: None
},
auth_token: None, // we haven't authorized yet, so we don't have this.
retry_config: Some(RetryConfig{}), // enables retries with exponential backoff when we get 429 response codes from Tidal.
};
let client = TidalClient::new(config)?;
let token: Token = client.exchange_code_for_token(verifier.to_string(), code.to_string()).await?;
let client_with_token = client.with_token(token)?;
let resp = client_with_token.tracks_api().get_track("<some track id>", None, None, None).await;§Client Credentials flow
use prawn::client::{TidalClient, TidalClientConfig, OAuthConfig};
let client_id = "<your client id>";
let client_secret = "<your client secret>";
let redirect_uri = "https://example.com/callback";
let config = TidalClientConfig {
oauth_config: OAuthConfig {
redirect_uri: redirect_uri.to_string(),
client_id: client_id.to_string(),
client_secret: Some(client_secret.to_string()),
},
auth_token: None, // we haven't authorized yet, so we don't have this.
retry_config: None
};
let client = TidalClient::new(config)?;
let scopes = vec!["user.read"];
let token = client.exchange_client_credentials_for_token(scopes.to_vec()).await?;
let client_with_token = client.with_token(token)?;
let resp = client_with_token.tracks_api().get_track("some track id", None, None, None).await;