Skip to main content

cow_sdk_core/config/
protocol.rs

1use serde::{Deserialize, Serialize};
2
3use crate::{
4    errors::{CoreError, ValidationError},
5    redaction::Redacted,
6    types::ChainId,
7};
8
9use super::{
10    chains::SupportedChainId,
11    env::{AddressPerChain, ApiBaseUrls, CowEnv, default_api_base_urls},
12    http::validate_header_value,
13};
14
15/// Protocol-wide address and environment overrides.
16#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct ProtocolOptions {
19    #[serde(skip_serializing_if = "Option::is_none")]
20    /// Explicit deployment environment override.
21    pub env: Option<CowEnv>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    /// Settlement contract overrides keyed by numeric chain id.
24    pub settlement_contract_override: Option<AddressPerChain>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    /// `EthFlow` contract overrides keyed by numeric chain id.
27    pub eth_flow_contract_override: Option<AddressPerChain>,
28}
29
30impl ProtocolOptions {
31    /// Creates an empty options bundle.
32    ///
33    /// Callers typically attach overrides through [`ProtocolOptions::with_env`],
34    /// [`ProtocolOptions::with_settlement_contract_override`], and
35    /// [`ProtocolOptions::with_eth_flow_contract_override`].
36    #[must_use]
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    /// Returns a copy of these options with an explicit environment override.
42    #[must_use]
43    pub const fn with_env(mut self, env: CowEnv) -> Self {
44        self.env = Some(env);
45        self
46    }
47
48    /// Returns a copy of these options with explicit settlement-contract overrides.
49    #[must_use]
50    pub fn with_settlement_contract_override(mut self, overrides: AddressPerChain) -> Self {
51        self.settlement_contract_override = Some(overrides);
52        self
53    }
54
55    /// Returns a copy of these options with explicit `EthFlow`-contract overrides.
56    #[must_use]
57    pub fn with_eth_flow_contract_override(mut self, overrides: AddressPerChain) -> Self {
58        self.eth_flow_contract_override = Some(overrides);
59        self
60    }
61}
62
63/// API routing context used by transport-owning crates.
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct ApiContext {
67    /// Target chain id for endpoint resolution.
68    pub chain_id: SupportedChainId,
69    /// Target environment for endpoint resolution.
70    pub env: CowEnv,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    /// Optional explicit base URLs keyed by numeric chain id.
73    pub base_urls: Option<ApiBaseUrls>,
74    #[serde(skip_serializing_if = "Option::is_none")]
75    /// Optional partner API key that switches resolution to partner endpoints.
76    pub api_key: Option<Redacted<String>>,
77}
78
79impl Default for ApiContext {
80    fn default() -> Self {
81        Self {
82            chain_id: SupportedChainId::Mainnet,
83            env: CowEnv::Prod,
84            base_urls: None,
85            api_key: None,
86        }
87    }
88}
89
90impl ApiContext {
91    /// Creates a routing context for the supplied chain and environment.
92    ///
93    /// Every optional field defaults to `None`; callers that need to override
94    /// the base-URL map or attach a partner API key can chain
95    /// [`ApiContext::with_base_urls`] and [`ApiContext::with_api_key`].
96    #[must_use]
97    pub const fn new(chain_id: SupportedChainId, env: CowEnv) -> Self {
98        Self {
99            chain_id,
100            env,
101            base_urls: None,
102            api_key: None,
103        }
104    }
105
106    /// Returns a copy of this context with an explicit base-URL override map.
107    #[must_use]
108    pub fn with_base_urls(mut self, base_urls: impl Into<ApiBaseUrls>) -> Self {
109        self.base_urls = Some(base_urls.into());
110        self
111    }
112
113    /// Returns a copy of this context with an attached partner API key.
114    #[must_use]
115    pub fn with_api_key(mut self, api_key: Redacted<String>) -> Self {
116        self.api_key = Some(api_key);
117        self
118    }
119
120    /// Returns the configured partner API key after local header validation.
121    ///
122    /// # Errors
123    ///
124    /// Returns [`ValidationError::InvalidHttpHeaderValue`] when the configured
125    /// API key cannot be encoded as an HTTP header value.
126    pub fn validated_api_key(&self) -> Result<Option<&str>, ValidationError> {
127        self.api_key
128            .as_ref()
129            .map(|api_key| {
130                let value = api_key.as_inner().as_str();
131                validate_header_value(value, "api_key")?;
132                Ok(value)
133            })
134            .transpose()
135    }
136
137    /// Resolves the effective base URL for the current chain and environment.
138    ///
139    /// # Errors
140    ///
141    /// Returns [`CoreError::MissingBaseUrl`] when the chain id has no configured
142    /// URL in either the explicit override map or the default map, or
143    /// [`CoreError::Validation`] when the configured partner API key is not a
144    /// valid HTTP header value.
145    pub fn resolved_base_url(&self) -> Result<String, CoreError> {
146        let chain_id: ChainId = self.chain_id.into();
147        let partner_api = self.validated_api_key()?.is_some();
148        // Build the per-chain default map only when no override is supplied, so
149        // an override path never formats the full default set just to discard it.
150        let resolved = self.base_urls.as_ref().map_or_else(
151            || {
152                default_api_base_urls(self.env, partner_api)
153                    .as_inner()
154                    .get(&chain_id)
155                    .cloned()
156            },
157            |base_urls| base_urls.as_inner().get(&chain_id).cloned(),
158        );
159
160        resolved.ok_or(CoreError::MissingBaseUrl {
161            chain_id,
162            env: self.env,
163            partner_api,
164        })
165    }
166}