Skip to main content

maple_proxy/
config.rs

1use clap::Parser;
2use serde::{Deserialize, Serialize};
3use std::net::SocketAddr;
4
5#[derive(Parser, Debug, Clone)]
6#[command(name = "maple-proxy")]
7#[command(about = "Lightweight OpenAI-compatible proxy server for Maple/OpenSecret")]
8pub struct Config {
9    /// Host to bind the server to
10    #[arg(long, env = "MAPLE_HOST", default_value = "127.0.0.1")]
11    pub host: String,
12
13    /// Port to bind the server to
14    #[arg(short, long, env = "MAPLE_PORT", default_value = "8080")]
15    pub port: u16,
16
17    /// OpenSecret/Maple backend URL
18    #[arg(
19        long,
20        env = "MAPLE_BACKEND_URL",
21        default_value = "https://enclave.trymaple.ai"
22    )]
23    pub backend_url: String,
24
25    /// Default API key for Maple/OpenSecret (can be overridden by client Authorization header)
26    #[arg(long, env = "MAPLE_API_KEY")]
27    pub default_api_key: Option<String>,
28
29    /// Enable debug logging
30    #[arg(short, long, env = "MAPLE_DEBUG")]
31    pub debug: bool,
32
33    /// Enable CORS for all origins (useful for web clients)
34    #[arg(long, env = "MAPLE_ENABLE_CORS")]
35    pub enable_cors: bool,
36}
37
38impl Config {
39    pub fn socket_addr(&self) -> anyhow::Result<SocketAddr> {
40        let addr = format!("{}:{}", self.host, self.port);
41        addr.parse()
42            .map_err(|e| anyhow::anyhow!("Invalid socket address '{}': {}", addr, e))
43    }
44
45    pub fn load() -> Self {
46        // Load from .env file if it exists
47        let _ = dotenvy::dotenv();
48
49        Config::parse()
50    }
51
52    /// Create a new Config programmatically (for library usage)
53    pub fn new(host: String, port: u16, backend_url: String) -> Self {
54        Self {
55            host,
56            port,
57            backend_url,
58            default_api_key: None,
59            debug: false,
60            enable_cors: false,
61        }
62    }
63
64    /// Builder-style method to set the API key
65    pub fn with_api_key(mut self, api_key: String) -> Self {
66        self.default_api_key = Some(api_key);
67        self
68    }
69
70    /// Builder-style method to enable debug mode
71    pub fn with_debug(mut self, debug: bool) -> Self {
72        self.debug = debug;
73        self
74    }
75
76    /// Builder-style method to enable CORS
77    pub fn with_cors(mut self, enable_cors: bool) -> Self {
78        self.enable_cors = enable_cors;
79        self
80    }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct OpenAIError {
85    pub error: OpenAIErrorDetails,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct OpenAIErrorDetails {
90    pub message: String,
91    #[serde(rename = "type")]
92    pub error_type: String,
93    pub param: Option<String>,
94    pub code: Option<String>,
95}
96
97impl OpenAIError {
98    pub fn new(message: impl Into<String>, error_type: impl Into<String>) -> Self {
99        Self {
100            error: OpenAIErrorDetails {
101                message: message.into(),
102                error_type: error_type.into(),
103                param: None,
104                code: None,
105            },
106        }
107    }
108
109    pub fn authentication_error(message: impl Into<String>) -> Self {
110        Self::new(message, "invalid_request_error")
111    }
112
113    pub fn server_error(message: impl Into<String>) -> Self {
114        Self::new(message, "server_error")
115    }
116}