openzeppelin_relayer/models/relayer/
rpc_config.rs1use crate::constants::DEFAULT_RPC_WEIGHT;
7use eyre::{eyre, Result};
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10use utoipa::ToSchema;
11
12#[derive(Debug, Error, PartialEq)]
13pub enum RpcConfigError {
14 #[error("Invalid weight: {value}. Must be between 0 and 100.")]
15 InvalidWeight { value: u8 },
16}
17
18fn default_rpc_weight() -> u8 {
20 DEFAULT_RPC_WEIGHT
21}
22
23#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, ToSchema)]
25pub struct RpcConfig {
26 pub url: String,
28 #[serde(default = "default_rpc_weight")]
31 pub weight: u8,
32}
33
34impl RpcConfig {
35 pub fn new(url: String) -> Self {
41 Self {
42 url,
43 weight: DEFAULT_RPC_WEIGHT,
44 }
45 }
46
47 pub fn with_weight(url: String, weight: u8) -> Result<Self, RpcConfigError> {
59 if weight > 100 {
60 return Err(RpcConfigError::InvalidWeight { value: weight });
61 }
62 Ok(Self { url, weight })
63 }
64
65 pub fn get_weight(&self) -> u8 {
71 self.weight
72 }
73
74 fn validate_url_scheme(url: &str) -> Result<()> {
77 if !url.starts_with("http://") && !url.starts_with("https://") {
78 return Err(eyre!(
79 "Invalid URL scheme for {}: Only HTTP and HTTPS are supported",
80 url
81 ));
82 }
83 Ok(())
84 }
85
86 pub fn validate_list(configs: &[RpcConfig]) -> Result<()> {
105 for config in configs {
106 Self::validate_url_scheme(&config.url)?;
108 }
109 Ok(())
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use crate::constants::DEFAULT_RPC_WEIGHT;
117
118 #[test]
119 fn test_new_creates_config_with_default_weight() {
120 let url = "https://example.com".to_string();
121 let config = RpcConfig::new(url.clone());
122
123 assert_eq!(config.url, url);
124 assert_eq!(config.weight, DEFAULT_RPC_WEIGHT);
125 }
126
127 #[test]
128 fn test_with_weight_creates_config_with_custom_weight() {
129 let url = "https://example.com".to_string();
130 let weight: u8 = 5;
131 let result = RpcConfig::with_weight(url.clone(), weight);
132 assert!(result.is_ok());
133
134 let config = result.unwrap();
135 assert_eq!(config.url, url);
136 assert_eq!(config.weight, weight);
137 }
138
139 #[test]
140 fn test_get_weight_returns_weight_value() {
141 let url = "https://example.com".to_string();
142 let weight: u8 = 10;
143 let config = RpcConfig { url, weight };
144
145 assert_eq!(config.get_weight(), weight);
146 }
147
148 #[test]
149 fn test_equality_of_configs() {
150 let url = "https://example.com".to_string();
151 let config1 = RpcConfig::new(url.clone());
152 let config2 = RpcConfig::new(url.clone()); let config3 = RpcConfig::with_weight(url.clone(), 5u8).unwrap(); let config4 =
155 RpcConfig::with_weight("https://different.com".to_string(), DEFAULT_RPC_WEIGHT)
156 .unwrap(); assert_eq!(config1, config2);
159 assert_ne!(config1, config3);
160 assert_ne!(config1, config4);
161 }
162
163 #[test]
165 fn test_validate_url_scheme_with_http() {
166 let result = RpcConfig::validate_url_scheme("http://example.com");
167 assert!(result.is_ok(), "HTTP URL should be valid");
168 }
169
170 #[test]
171 fn test_validate_url_scheme_with_https() {
172 let result = RpcConfig::validate_url_scheme("https://secure.example.com");
173 assert!(result.is_ok(), "HTTPS URL should be valid");
174 }
175
176 #[test]
177 fn test_validate_url_scheme_with_query_params() {
178 let result =
179 RpcConfig::validate_url_scheme("https://example.com/api?param=value&other=123");
180 assert!(result.is_ok(), "URL with query parameters should be valid");
181 }
182
183 #[test]
184 fn test_validate_url_scheme_with_port() {
185 let result = RpcConfig::validate_url_scheme("http://localhost:8545");
186 assert!(result.is_ok(), "URL with port should be valid");
187 }
188
189 #[test]
190 fn test_validate_url_scheme_with_ftp() {
191 let result = RpcConfig::validate_url_scheme("ftp://example.com");
192 assert!(result.is_err(), "FTP URL should be invalid");
193 }
194
195 #[test]
196 fn test_validate_url_scheme_with_invalid_url() {
197 let result = RpcConfig::validate_url_scheme("invalid-url");
198 assert!(result.is_err(), "Invalid URL format should be rejected");
199 }
200
201 #[test]
202 fn test_validate_url_scheme_with_empty_string() {
203 let result = RpcConfig::validate_url_scheme("");
204 assert!(result.is_err(), "Empty string should be rejected");
205 }
206
207 #[test]
209 fn test_validate_list_with_empty_vec() {
210 let configs: Vec<RpcConfig> = vec![];
211 let result = RpcConfig::validate_list(&configs);
212 assert!(result.is_ok(), "Empty config vector should be valid");
213 }
214
215 #[test]
216 fn test_validate_list_with_valid_urls() {
217 let configs = vec![
218 RpcConfig::new("https://api.example.com".to_string()),
219 RpcConfig::new("http://localhost:8545".to_string()),
220 ];
221 let result = RpcConfig::validate_list(&configs);
222 assert!(result.is_ok(), "All URLs are valid, should return Ok");
223 }
224
225 #[test]
226 fn test_validate_list_with_one_invalid_url() {
227 let configs = vec![
228 RpcConfig::new("https://api.example.com".to_string()),
229 RpcConfig::new("ftp://invalid-scheme.com".to_string()),
230 RpcConfig::new("http://another-valid.com".to_string()),
231 ];
232 let result = RpcConfig::validate_list(&configs);
233 assert!(result.is_err(), "Should fail on first invalid URL");
234 }
235
236 #[test]
237 fn test_validate_list_with_all_invalid_urls() {
238 let configs = vec![
239 RpcConfig::new("ws://websocket.example.com".to_string()),
240 RpcConfig::new("ftp://invalid-scheme.com".to_string()),
241 ];
242 let result = RpcConfig::validate_list(&configs);
243 assert!(result.is_err(), "Should fail with all invalid URLs");
244 }
245}