the0_sdk/
lib.rs

1//! the0 SDK for Rust trading bots
2//!
3//! This crate provides utilities for building trading bots on the0 platform.
4//!
5//! # Example
6//!
7//! ```rust,no_run
8//! use the0_sdk::input;
9//! use the0_sdk::state;
10//!
11//! fn main() {
12//!     let (bot_id, config) = input::parse().expect("Failed to parse bot config");
13//!     println!("Bot {} starting with config: {:?}", bot_id, config);
14//!
15//!     // Load persistent state
16//!     let trade_count: i32 = state::get_or("trade_count", 0);
17//!
18//!     // Your trading logic here
19//!
20//!     // Save state
21//!     state::set("trade_count", &(trade_count + 1)).ok();
22//!
23//!     input::success("Bot executed successfully");
24//! }
25//! ```
26
27pub mod query;
28pub mod state;
29
30use serde_json::Value;
31use std::collections::HashMap;
32use std::env;
33use std::fs;
34use std::fmt;
35
36/// Errors that can occur when parsing bot configuration
37#[derive(Debug)]
38pub enum ParseError {
39    /// BOT_ID environment variable is not set
40    MissingBotId,
41    /// BOT_CONFIG environment variable is not set
42    MissingBotConfig,
43    /// BOT_CONFIG contains invalid JSON
44    InvalidJson(serde_json::Error),
45}
46
47impl fmt::Display for ParseError {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        match self {
50            ParseError::MissingBotId => write!(f, "BOT_ID environment variable not set"),
51            ParseError::MissingBotConfig => write!(f, "BOT_CONFIG environment variable not set"),
52            ParseError::InvalidJson(e) => write!(f, "Failed to parse BOT_CONFIG as JSON: {}", e),
53        }
54    }
55}
56
57impl std::error::Error for ParseError {
58    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
59        match self {
60            ParseError::InvalidJson(e) => Some(e),
61            _ => None,
62        }
63    }
64}
65
66/// Input parsing and output formatting utilities
67pub mod input {
68    use super::*;
69
70    // Re-export ParseError for convenience
71    pub use crate::ParseError;
72
73    /// Get the path to the result file
74    fn result_file_path() -> String {
75        let mount_dir = env::var("CODE_MOUNT_DIR").unwrap_or_else(|_| "bot".to_string());
76        format!("/{}/result.json", mount_dir)
77    }
78
79    /// Write result to the result file
80    fn write_result(content: &str) {
81        let path = result_file_path();
82        if let Err(e) = fs::write(&path, content) {
83            eprintln!("RESULT_ERROR: Failed to write result file: {}", e);
84        }
85    }
86
87    /// Parse bot configuration from environment variables.
88    ///
89    /// Returns a tuple of (bot_id, config) where config is a serde_json::Value.
90    ///
91    /// # Errors
92    /// Returns an error if BOT_ID or BOT_CONFIG environment variables are not set,
93    /// or if BOT_CONFIG is not valid JSON.
94    pub fn parse() -> Result<(String, Value), ParseError> {
95        let id = env::var("BOT_ID").map_err(|_| ParseError::MissingBotId)?;
96        let config_str = env::var("BOT_CONFIG").map_err(|_| ParseError::MissingBotConfig)?;
97        let config: Value = serde_json::from_str(&config_str).map_err(ParseError::InvalidJson)?;
98        Ok((id, config))
99    }
100
101    /// Parse bot configuration with typed config as HashMap.
102    ///
103    /// Returns a tuple of (bot_id, config) where config is a HashMap<String, Value>.
104    ///
105    /// # Errors
106    /// Returns an error if BOT_ID or BOT_CONFIG environment variables are not set,
107    /// or if BOT_CONFIG is not valid JSON.
108    pub fn parse_as_map() -> Result<(String, HashMap<String, Value>), ParseError> {
109        let id = env::var("BOT_ID").map_err(|_| ParseError::MissingBotId)?;
110        let config_str = env::var("BOT_CONFIG").map_err(|_| ParseError::MissingBotConfig)?;
111        let config: HashMap<String, Value> =
112            serde_json::from_str(&config_str).map_err(ParseError::InvalidJson)?;
113        Ok((id, config))
114    }
115
116    /// Output a success result to the result file.
117    ///
118    /// Writes a JSON object with status "success" and the provided message.
119    pub fn success(message: &str) {
120        let escaped = message.replace('\\', "\\\\").replace('"', "\\\"");
121        write_result(&format!(r#"{{"status":"success","message":"{}"}}"#, escaped));
122    }
123
124    /// Output an error result to the result file and exit with code 1.
125    ///
126    /// Writes a JSON object with status "error" and the provided message,
127    /// then terminates the process with exit code 1.
128    pub fn error(message: &str) -> ! {
129        let escaped = message.replace('\\', "\\\\").replace('"', "\\\"");
130        write_result(&format!(r#"{{"status":"error","message":"{}"}}"#, escaped));
131        std::process::exit(1);
132    }
133
134    /// Output a custom JSON result to the result file.
135    ///
136    /// Serializes the provided Value as JSON and writes it.
137    pub fn result(data: &Value) {
138        write_result(&serde_json::to_string(data).expect("Failed to serialize result"));
139    }
140
141    /// Emit a metric to stdout.
142    ///
143    /// Outputs a JSON object with `_metric` field and timestamp.
144    pub fn metric(metric_type: &str, data: &Value) {
145        let timestamp = current_timestamp();
146        let mut output = data.as_object().cloned().unwrap_or_default();
147        output.insert("_metric".to_string(), Value::String(metric_type.to_string()));
148        output.insert("timestamp".to_string(), Value::Number(serde_json::Number::from(timestamp)));
149        println!("{}", serde_json::to_string(&output).expect("Failed to serialize metric"));
150    }
151
152    /// Log levels supported by the platform
153    #[derive(Debug, Clone, Copy)]
154    pub enum LogLevel {
155        Info,
156        Warn,
157        Error,
158    }
159
160    impl LogLevel {
161        fn as_str(&self) -> &'static str {
162            match self {
163                LogLevel::Info => "info",
164                LogLevel::Warn => "warn",
165                LogLevel::Error => "error",
166            }
167        }
168    }
169
170    /// Log a structured message to stderr.
171    ///
172    /// Outputs a JSON object with `level`, `message`, `timestamp`, and any additional fields.
173    ///
174    /// # Examples
175    ///
176    /// ```rust,no_run
177    /// use the0_sdk::input::{log, LogLevel};
178    /// use serde_json::json;
179    ///
180    /// // Simple log (defaults to info level)
181    /// log("Starting trade execution", None, None);
182    ///
183    /// // Log with level
184    /// log("Connection lost", None, Some(LogLevel::Warn));
185    ///
186    /// // Log with structured data
187    /// log("Order placed", Some(&json!({"order_id": "12345", "symbol": "BTC/USD"})), None);
188    ///
189    /// // Log with data and level
190    /// log("Order failed", Some(&json!({"order_id": "12345"})), Some(LogLevel::Error));
191    /// ```
192    pub fn log(message: &str, data: Option<&Value>, level: Option<LogLevel>) {
193        let log_level = level.unwrap_or(LogLevel::Info);
194        let timestamp = current_timestamp();
195
196        let mut output = match data {
197            Some(v) => v.as_object().cloned().unwrap_or_default(),
198            None => serde_json::Map::new(),
199        };
200
201        output.insert("level".to_string(), Value::String(log_level.as_str().to_string()));
202        output.insert("message".to_string(), Value::String(message.to_string()));
203        output.insert("timestamp".to_string(), Value::Number(serde_json::Number::from(timestamp)));
204
205        eprintln!("{}", serde_json::to_string(&output).expect("Failed to serialize log"));
206    }
207
208    /// Convenience function: log an info message
209    pub fn log_info(message: &str, data: Option<&Value>) {
210        log(message, data, Some(LogLevel::Info));
211    }
212
213    /// Convenience function: log a warning message
214    pub fn log_warn(message: &str, data: Option<&Value>) {
215        log(message, data, Some(LogLevel::Warn));
216    }
217
218    /// Convenience function: log an error message
219    pub fn log_error(message: &str, data: Option<&Value>) {
220        log(message, data, Some(LogLevel::Error));
221    }
222
223    /// Get current timestamp as milliseconds since Unix epoch.
224    fn current_timestamp() -> u64 {
225        use std::time::{SystemTime, UNIX_EPOCH};
226        let duration = SystemTime::now()
227            .duration_since(UNIX_EPOCH)
228            .unwrap();
229        duration.as_secs() * 1000 + duration.subsec_millis() as u64
230    }
231}