openzeppelin_relayer/models/network/
response.rs

1//! API response models for network endpoints.
2//!
3//! This module provides response structures for network operations, converting
4//! internal repository models to API-friendly formats.
5
6use crate::models::{
7    network::{NetworkConfigData, NetworkRepoModel},
8    NetworkType, RpcConfig,
9};
10use serde::{Deserialize, Serialize};
11use utoipa::ToSchema;
12
13/// Network response model for API endpoints.
14///
15/// This flattens the internal NetworkRepoModel structure for API responses,
16/// making network-type-specific fields available at the top level.
17#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, ToSchema)]
18pub struct NetworkResponse {
19    /// Unique identifier composed of network_type and name, e.g., "evm:mainnet"
20    pub id: String,
21    /// Name of the network (e.g., "mainnet", "sepolia")
22    pub name: String,
23    /// Type of the network (EVM, Solana, Stellar)
24    pub network_type: NetworkType,
25    /// List of RPC endpoint configurations
26    #[serde(skip_serializing_if = "Option::is_none")]
27    #[schema(nullable = false)]
28    pub rpc_urls: Option<Vec<RpcConfig>>,
29    /// List of Explorer endpoint URLs
30    #[serde(skip_serializing_if = "Option::is_none")]
31    #[schema(nullable = false)]
32    pub explorer_urls: Option<Vec<String>>,
33    /// Estimated average time between blocks in milliseconds
34    #[serde(skip_serializing_if = "Option::is_none")]
35    #[schema(nullable = false)]
36    pub average_blocktime_ms: Option<u64>,
37    /// Flag indicating if the network is a testnet
38    #[serde(skip_serializing_if = "Option::is_none")]
39    #[schema(nullable = false)]
40    pub is_testnet: Option<bool>,
41    /// List of arbitrary tags for categorizing or filtering networks
42    #[serde(skip_serializing_if = "Option::is_none")]
43    #[schema(nullable = false)]
44    pub tags: Option<Vec<String>>,
45    /// EVM-specific: Chain ID
46    #[serde(skip_serializing_if = "Option::is_none")]
47    #[schema(nullable = false)]
48    pub chain_id: Option<u64>,
49    /// EVM-specific: Required confirmations
50    #[serde(skip_serializing_if = "Option::is_none")]
51    #[schema(nullable = false)]
52    pub required_confirmations: Option<u64>,
53    /// EVM-specific: Network features (e.g., "eip1559")
54    #[serde(skip_serializing_if = "Option::is_none")]
55    #[schema(nullable = false)]
56    pub features: Option<Vec<String>>,
57    /// EVM-specific: Native token symbol
58    #[serde(skip_serializing_if = "Option::is_none")]
59    #[schema(nullable = false)]
60    pub symbol: Option<String>,
61    /// Stellar-specific: Network passphrase
62    #[serde(skip_serializing_if = "Option::is_none")]
63    #[schema(nullable = false)]
64    pub passphrase: Option<String>,
65    /// Stellar-specific: Horizon URL
66    #[serde(skip_serializing_if = "Option::is_none")]
67    #[schema(nullable = false)]
68    pub horizon_url: Option<String>,
69}
70
71impl From<NetworkRepoModel> for NetworkResponse {
72    fn from(model: NetworkRepoModel) -> Self {
73        let id = model.id.clone();
74        let name = model.name.clone();
75        let network_type = model.network_type;
76        let common = model.common();
77        let mut response = NetworkResponse {
78            id,
79            name,
80            network_type,
81            rpc_urls: common.rpc_urls.clone(),
82            explorer_urls: common.explorer_urls.clone(),
83            average_blocktime_ms: common.average_blocktime_ms,
84            is_testnet: common.is_testnet,
85            tags: common.tags.clone(),
86            chain_id: None,
87            required_confirmations: None,
88            features: None,
89            symbol: None,
90            passphrase: None,
91            horizon_url: None,
92        };
93
94        // Add network-type-specific fields
95        match model.config {
96            NetworkConfigData::Evm(evm_config) => {
97                response.chain_id = evm_config.chain_id;
98                response.required_confirmations = evm_config.required_confirmations;
99                response.features = evm_config.features.clone();
100                response.symbol = evm_config.symbol.clone();
101            }
102            NetworkConfigData::Solana(_) => {
103                // Solana doesn't have additional fields beyond common
104            }
105            NetworkConfigData::Stellar(stellar_config) => {
106                response.passphrase = stellar_config.passphrase.clone();
107                response.horizon_url = stellar_config.horizon_url.clone();
108            }
109        }
110
111        response
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use crate::models::RpcConfig;
119
120    fn create_test_evm_network() -> NetworkRepoModel {
121        use crate::config::EvmNetworkConfig;
122        use crate::config::NetworkConfigCommon;
123        NetworkRepoModel::new_evm(EvmNetworkConfig {
124            common: NetworkConfigCommon {
125                network: "mainnet".to_string(),
126                from: None,
127                rpc_urls: Some(vec![RpcConfig::new("https://rpc.example.com".to_string())]),
128                explorer_urls: Some(vec!["https://explorer.example.com".to_string()]),
129                average_blocktime_ms: Some(12000),
130                is_testnet: Some(false),
131                tags: Some(vec!["mainnet".to_string()]),
132            },
133            chain_id: Some(1),
134            required_confirmations: Some(12),
135            features: Some(vec!["eip1559".to_string()]),
136            symbol: Some("ETH".to_string()),
137            gas_price_cache: None,
138        })
139    }
140
141    #[test]
142    fn test_from_network_repo_model_evm() {
143        let model = create_test_evm_network();
144        let response = NetworkResponse::from(model);
145
146        assert_eq!(response.id, "evm:mainnet");
147        assert_eq!(response.name, "mainnet");
148        assert_eq!(response.network_type, NetworkType::Evm);
149        assert_eq!(response.chain_id, Some(1));
150        assert_eq!(response.required_confirmations, Some(12));
151        assert_eq!(response.symbol, Some("ETH".to_string()));
152        assert_eq!(response.features, Some(vec!["eip1559".to_string()]));
153    }
154
155    #[test]
156    fn test_from_network_repo_model_common_fields() {
157        let model = create_test_evm_network();
158        let response = NetworkResponse::from(model);
159
160        assert!(response.rpc_urls.is_some());
161        assert!(response.explorer_urls.is_some());
162        assert_eq!(response.average_blocktime_ms, Some(12000));
163        assert_eq!(response.is_testnet, Some(false));
164        assert!(response.tags.is_some());
165    }
166}