Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target
/Cargo.lock
your_output_file.wav
your_output_file.wav
.DS_Store
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,38 @@ This SDK implements the Deepgram API found at [https://developers.deepgram.com](

Documentation and examples can be found on our [Docs.rs page](https://docs.rs/deepgram/latest/deepgram/).

## Getting an API Key
## Quick Start

Check out the [examples folder](./examples/) for practical code examples showing how to use the SDK.

## Authentication

🔑 To access the Deepgram API you will need a [free Deepgram API Key](https://console.deepgram.com/signup?jump=keys).

There are two ways to authenticate with the Deepgram API:

1. **API Key**: This is the simplest method. You can get a free API key from the
[Deepgram Console](https://console.deepgram.com/signup?jump=keys).

```rust
use deepgram::Deepgram;

let dg = Deepgram::new("YOUR_DEEPGRAM_API_KEY");
```

2. **Temporary Tokens**: If you are building an application where you need to
grant temporary access to the Deepgram API, you can use temporary tokens.
This is useful for client-side applications where you don't want to expose
your API key.

You can create temporary tokens using the Deepgram API. Learn more about
[token-based authentication](https://developers.deepgram.com/guides/fundamentals/token-based-authentication).

```rust
use deepgram::Deepgram;

let dg = Deepgram::with_temp_token("YOUR_TEMPORARY_TOKEN");
```

## Current Status

Expand Down
101 changes: 93 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ impl Transcription<'_> {
}

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
/// A string wrapper that redacts its contents when formatted with `Debug`.
pub(crate) struct RedactedString(pub String);

impl fmt::Debug for RedactedString {
Expand All @@ -105,13 +106,35 @@ impl Deref for RedactedString {
}
}

/// Authentication method for Deepgram API requests.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum AuthMethod {
/// Use an API key with "Token" prefix (e.g., "Token dg_xxx").
/// This is for permanent API keys created in the Deepgram console.
ApiKey(RedactedString),

/// Use a temporary token with "Bearer" prefix (e.g., "Bearer dg_xxx").
/// This is for temporary tokens obtained via token-based authentication.
TempToken(RedactedString),
}

impl AuthMethod {
/// Get the authorization header value for this authentication method.
pub(crate) fn header_value(&self) -> String {
match self {
AuthMethod::ApiKey(key) => format!("Token {}", key.0),
AuthMethod::TempToken(token) => format!("Bearer {}", token.0),
}
}
}

/// A client for the Deepgram API.
///
/// Make transcriptions requests using [`Deepgram::transcription`].
#[derive(Debug, Clone)]
pub struct Deepgram {
#[cfg_attr(not(feature = "listen"), allow(unused))]
api_key: Option<RedactedString>,
auth: Option<AuthMethod>,
#[cfg_attr(not(feature = "listen"), allow(unused))]
base_url: Url,
#[cfg_attr(not(feature = "listen"), allow(unused))]
Expand Down Expand Up @@ -200,11 +223,20 @@ impl Deepgram {
///
/// Errors under the same conditions as [`reqwest::ClientBuilder::build`].
pub fn new<K: AsRef<str>>(api_key: K) -> Result<Self> {
let api_key = Some(api_key.as_ref().to_owned());
let auth = AuthMethod::ApiKey(RedactedString(api_key.as_ref().to_owned()));
// This cannot panic because we are converting a static value
// that is known-good.
let base_url = DEEPGRAM_BASE_URL.try_into().unwrap();
Self::inner_constructor(base_url, api_key)
Self::inner_constructor(base_url, Some(auth))
}

/// Construct a new Deepgram client with a temporary token.
///
/// This uses the "Bearer" prefix for authentication, suitable for temporary tokens.
pub fn with_temp_token<T: AsRef<str>>(temp_token: T) -> Result<Self> {
let auth = AuthMethod::TempToken(RedactedString(temp_token.as_ref().to_owned()));
let base_url = DEEPGRAM_BASE_URL.try_into().unwrap();
Self::inner_constructor(base_url, Some(auth))
}

/// Construct a new Deepgram client with the specified base URL.
Expand Down Expand Up @@ -281,10 +313,23 @@ impl Deepgram {
K: AsRef<str>,
{
let base_url = base_url.try_into().map_err(|_| DeepgramError::InvalidUrl)?;
Self::inner_constructor(base_url, Some(api_key.as_ref().to_owned()))
let auth = AuthMethod::ApiKey(RedactedString(api_key.as_ref().to_owned()));
Self::inner_constructor(base_url, Some(auth))
}

/// Construct a new Deepgram client with the specified base URL and temp token.
pub fn with_base_url_and_temp_token<U, T>(base_url: U, temp_token: T) -> Result<Self>
where
U: TryInto<Url>,
U::Error: std::fmt::Debug,
T: AsRef<str>,
{
let base_url = base_url.try_into().map_err(|_| DeepgramError::InvalidUrl)?;
let auth = AuthMethod::TempToken(RedactedString(temp_token.as_ref().to_owned()));
Self::inner_constructor(base_url, Some(auth))
}

fn inner_constructor(base_url: Url, api_key: Option<String>) -> Result<Self> {
fn inner_constructor(base_url: Url, auth: Option<AuthMethod>) -> Result<Self> {
static USER_AGENT: &str = concat!(
env!("CARGO_PKG_NAME"),
"/",
Expand All @@ -297,16 +342,17 @@ impl Deepgram {
}
let authorization_header = {
let mut header = HeaderMap::new();
if let Some(api_key) = &api_key {
if let Ok(value) = HeaderValue::from_str(&format!("Token {}", api_key)) {
if let Some(auth) = &auth {
let header_value = auth.header_value();
if let Ok(value) = HeaderValue::from_str(&header_value) {
header.insert("Authorization", value);
}
}
header
};

Ok(Deepgram {
api_key: api_key.map(RedactedString),
auth,
base_url,
client: reqwest::Client::builder()
.user_agent(USER_AGENT)
Expand Down Expand Up @@ -334,3 +380,42 @@ async fn send_and_translate_response<R: DeserializeOwned>(
}),
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_auth_method_header_value() {
let api_key = AuthMethod::ApiKey(RedactedString("test_api_key".to_string()));
assert_eq!(api_key.header_value(), "Token test_api_key".to_string());

let temp_token = AuthMethod::TempToken(RedactedString("test_temp_token".to_string()));
assert_eq!(
temp_token.header_value(),
"Bearer test_temp_token".to_string()
);
}

#[test]
fn test_deepgram_new_with_temp_token() {
let client = Deepgram::with_temp_token("test_temp_token").unwrap();
assert_eq!(
client.auth,
Some(AuthMethod::TempToken(RedactedString(
"test_temp_token".to_string()
)))
);
}

#[test]
fn test_deepgram_new_with_api_key() {
let client = Deepgram::new("test_api_key").unwrap();
assert_eq!(
client.auth,
Some(AuthMethod::ApiKey(RedactedString(
"test_api_key".to_string()
)))
);
}
}
4 changes: 2 additions & 2 deletions src/listen/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -666,8 +666,8 @@ impl WebsocketHandle {
.header("upgrade", "websocket")
.header("sec-websocket-version", "13");

let builder = if let Some(api_key) = builder.deepgram.api_key.as_deref() {
http_builder.header("authorization", format!("Token {}", api_key))
let builder = if let Some(auth) = &builder.deepgram.auth {
http_builder.header("authorization", auth.header_value())
} else {
http_builder
};
Expand Down
Loading