use serde::Deserialize;
use crate::client::{ApiError, Call, Transport, read};
use crate::utils::ids::whole;
use crate::utils::lenient::{Done, list, text};
use crate::utils::{AccountIndex, ApiKeyIndex};
pub const READ_SCOPE: &str = "read.*";
pub const MIN_READ_TOKEN_S: u64 = 5 * 60;
pub const MAX_READ_TOKEN_S: u64 = 10 * 365 * 24 * 60 * 60;
#[derive(Clone, PartialEq, Eq, Deserialize)]
pub struct ReadToken {
#[serde(deserialize_with = "whole")]
pub token_id: u64,
#[serde(default, deserialize_with = "text")]
pub api_token: String,
#[serde(default, deserialize_with = "text")]
pub name: String,
pub account_index: AccountIndex,
#[serde(default, deserialize_with = "whole")]
pub expiry: u64,
#[serde(default)]
pub sub_account_access: bool,
#[serde(default)]
pub revoked: bool,
#[serde(default, deserialize_with = "text")]
pub scopes: String,
}
impl std::fmt::Debug for ReadToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReadToken")
.field("token_id", &self.token_id)
.field("name", &self.name)
.field("account_index", &self.account_index)
.field("expiry", &self.expiry)
.field("sub_account_access", &self.sub_account_access)
.field("revoked", &self.revoked)
.field("scopes", &self.scopes)
.finish_non_exhaustive()
}
}
impl ReadToken {
#[must_use]
pub const fn is_live(&self, now_s: u64) -> bool {
!self.revoked && self.expiry > now_s
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewReadToken {
pub account: AccountIndex,
pub name: String,
pub expiry: u64,
pub sub_account_access: bool,
}
#[derive(Deserialize)]
struct Tokens {
#[serde(default, deserialize_with = "list")]
api_tokens: Vec<ReadToken>,
}
#[derive(Deserialize)]
struct Revoked {
#[serde(default)]
revoked: bool,
}
#[derive(Deserialize)]
struct MakerOnly {
#[serde(default, deserialize_with = "list")]
api_key_indexes: Vec<u64>,
}
#[must_use]
pub fn read_tokens_call(account: AccountIndex) -> Call {
Call::get("tokens").field("account_index", account).authed()
}
pub async fn read_tokens(transport: &impl Transport, account: AccountIndex) -> Result<Vec<ReadToken>, ApiError> {
Ok(read::<Tokens>(transport, read_tokens_call(account)).await?.api_tokens)
}
#[must_use]
pub fn create_read_token_call(token: &NewReadToken) -> Call {
Call::post("tokens/create")
.field("name", &token.name)
.field("account_index", token.account)
.field("expiry", token.expiry)
.field("sub_account_access", token.sub_account_access)
.field("scopes", READ_SCOPE)
.authed()
}
pub async fn create_read_token(transport: &impl Transport, token: &NewReadToken) -> Result<ReadToken, ApiError> {
read(transport, create_read_token_call(token)).await
}
#[must_use]
pub fn revoke_read_token_call(account: AccountIndex, token_id: u64) -> Call {
Call::post("tokens/revoke").field("token_id", token_id).field("account_index", account).authed()
}
pub async fn revoke_read_token(
transport: &impl Transport,
account: AccountIndex,
token_id: u64,
) -> Result<bool, ApiError> {
Ok(read::<Revoked>(transport, revoke_read_token_call(account, token_id)).await?.revoked)
}
#[must_use]
pub fn maker_only_api_keys_call(account: AccountIndex) -> Call {
Call::get("getMakerOnlyApiKeys").field("account_index", account).authed()
}
pub async fn maker_only_api_keys(
transport: &impl Transport,
account: AccountIndex,
) -> Result<Vec<ApiKeyIndex>, ApiError> {
let listed = read::<MakerOnly>(transport, maker_only_api_keys_call(account)).await?.api_key_indexes;
Ok(listed
.into_iter()
.filter_map(|slot| u8::try_from(slot).ok())
.filter_map(|slot| ApiKeyIndex::new(slot).ok())
.collect())
}
#[must_use]
pub fn set_maker_only_api_keys_call(account: AccountIndex, slots: &[ApiKeyIndex]) -> Call {
let listed: Vec<u8> = slots.iter().map(|slot| slot.get()).collect();
Call::post("setMakerOnlyApiKeys")
.field("account_index", account)
.field("api_key_indexes", serde_json::to_string(&listed).unwrap_or_else(|_| "[]".to_owned()))
.authed()
}
pub async fn set_maker_only_api_keys(
transport: &impl Transport,
account: AccountIndex,
slots: &[ApiKeyIndex],
) -> Result<(), ApiError> {
read::<Done>(transport, set_maker_only_api_keys_call(account, slots)).await.map(drop)
}
#[cfg(test)]
#[path = "tests/tokens.rs"]
mod tests;