limitless-exchange-rust-sdk 1.0.13

Rust SDK for Limitless Exchange CLOB and NegRisk trading
Documentation
#![allow(dead_code)]

use std::{env, io, sync::Arc};

use limitless_exchange_rust_sdk::{Client, ConsoleLogger, HmacCredentials, LogLevel, SharedLogger};

pub fn require_env(key: &str) -> String {
    let value = env::var(key).unwrap_or_default().trim().to_string();
    if value.is_empty() {
        panic!("{key} environment variable is required");
    }
    value
}

pub fn optional_env(key: &str) -> Option<String> {
    let value = env::var(key).unwrap_or_default().trim().to_string();
    if value.is_empty() {
        None
    } else {
        Some(value)
    }
}

pub fn optional_env_with_fallback(key: &str, fallback: &str) -> String {
    optional_env(key).unwrap_or_else(|| fallback.to_string())
}

pub fn env_flag(key: &str, fallback: bool) -> bool {
    match optional_env(key)
        .unwrap_or_else(|| if fallback { "true" } else { "false" }.to_string())
        .to_lowercase()
        .as_str()
    {
        "1" | "true" | "yes" | "on" => true,
        "0" | "false" | "no" | "off" => false,
        _ => panic!("{key} must be one of 1,true,yes,on,0,false,no,off"),
    }
}

pub fn optional_positive_i32(key: &str) -> Option<i32> {
    optional_env(key).map(|value| {
        value
            .parse::<i32>()
            .ok()
            .filter(|parsed| *parsed > 0)
            .unwrap_or_else(|| panic!("{key} must be a positive integer"))
    })
}

pub fn optional_non_negative_u64(key: &str, fallback: u64) -> u64 {
    optional_env(key)
        .map(|value| {
            value
                .parse::<u64>()
                .unwrap_or_else(|_| panic!("{key} must be a zero-or-positive integer"))
        })
        .unwrap_or(fallback)
}

pub fn logger() -> SharedLogger {
    Arc::new(ConsoleLogger::new(LogLevel::Info))
}

pub fn public_client() -> Result<Client, Box<dyn std::error::Error>> {
    Ok(Client::from_http_client(
        Client::builder().logger(logger()).build()?,
    )?)
}

pub fn api_key_client() -> Result<Client, Box<dyn std::error::Error>> {
    Ok(Client::from_http_client(
        Client::builder()
            .api_key(require_env("LIMITLESS_API_KEY"))
            .logger(logger())
            .build()?,
    )?)
}

pub fn hmac_client() -> Result<Client, Box<dyn std::error::Error>> {
    Ok(Client::from_http_client(
        Client::builder()
            .hmac_credentials(HmacCredentials {
                token_id: require_env("LIMITLESS_API_TOKEN_ID"),
                secret: require_env("LIMITLESS_API_TOKEN_SECRET"),
            })
            .logger(logger())
            .build()?,
    )?)
}

pub fn hmac_or_api_key_client() -> Result<Client, Box<dyn std::error::Error>> {
    if optional_env("LIMITLESS_API_TOKEN_ID").is_some()
        || optional_env("LIMITLESS_API_TOKEN_SECRET").is_some()
    {
        if optional_env("LIMITLESS_API_TOKEN_ID").is_none()
            || optional_env("LIMITLESS_API_TOKEN_SECRET").is_none()
        {
            return Err(io::Error::other(
                "both LIMITLESS_API_TOKEN_ID and LIMITLESS_API_TOKEN_SECRET are required for scoped API-key auth",
            )
            .into());
        }
        hmac_client()
    } else {
        api_key_client()
    }
}

pub fn required_market_tokens(
    market: &limitless_exchange_rust_sdk::Market,
) -> Result<limitless_exchange_rust_sdk::MarketTokens, Box<dyn std::error::Error>> {
    market
        .tokens
        .clone()
        .ok_or_else(|| io::Error::other("market has no tokens").into())
}

pub fn wait_for_ctrl_c() -> Result<(), Box<dyn std::error::Error>> {
    let runtime = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()?;
    runtime.block_on(async {
        tokio::signal::ctrl_c().await?;
        Ok::<(), Box<dyn std::error::Error>>(())
    })
}

pub fn empty_fallback(value: Option<&str>, fallback: &str) -> String {
    value
        .filter(|value| !value.is_empty())
        .unwrap_or(fallback)
        .to_string()
}