diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 000000000..a1a424c65 --- /dev/null +++ b/.clippy.toml @@ -0,0 +1,2 @@ +allow-unwrap-in-tests = true +enum-variant-size-threshold = 500 diff --git a/Cargo.toml b/Cargo.toml index 9423d146c..02e38660e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,10 +21,14 @@ as_conversions = "deny" [workspace.dependencies] assert_matches = "1.5.0" axum = "0.6.12" +clap = { version = "4.3.10" } hyper = "0.13.9" rstest = "0.17.0" serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0" starknet_api = { git = "https://github.com/starkware-libs/starknet-api.git", branch = "mohammad/transaction/implement-external-transactions" } +papyrus_config = "0.3.0" thiserror = "1.0" tokio = { version = "1", features = ["full"] } +validator = "0.12" + diff --git a/crates/gateway/Cargo.toml b/crates/gateway/Cargo.toml index fc57f5cba..8008c10b0 100644 --- a/crates/gateway/Cargo.toml +++ b/crates/gateway/Cargo.toml @@ -11,11 +11,14 @@ workspace = true [dependencies] assert_matches.workspace = true axum.workspace = true +clap.workspace = true hyper.workspace = true serde.workspace = true serde_json.workspace = true starknet_api.workspace = true thiserror.workspace = true +papyrus_config.workspace = true +validator.workspace = true [dev-dependencies] rstest.workspace = true diff --git a/crates/gateway/src/gateway.rs b/crates/gateway/src/gateway.rs index 2ca66cbd4..1169ec2e2 100644 --- a/crates/gateway/src/gateway.rs +++ b/crates/gateway/src/gateway.rs @@ -1,4 +1,5 @@ use crate::errors::{GatewayConfigError, GatewayError}; +use crate::GatewayConfig; use axum::response::IntoResponse; use axum::routing::{get, post}; use axum::{Json, Router}; @@ -38,10 +39,6 @@ impl Gateway { } } -pub struct GatewayConfig { - pub bind_address: String, -} - async fn is_alive() -> impl IntoResponse { unimplemented!("Future handling should be implemented here."); } diff --git a/crates/gateway/src/gateway_test.rs b/crates/gateway/src/gateway_test.rs index 2e374c1ed..9b55aadc4 100644 --- a/crates/gateway/src/gateway_test.rs +++ b/crates/gateway/src/gateway_test.rs @@ -1,9 +1,14 @@ use crate::gateway::add_transaction; +use crate::GatewayConfig; use axum::{body::HttpBody, response::IntoResponse}; +use clap::Command; +use papyrus_config::loading::load_and_process_config; use rstest::rstest; use starknet_api::external_transaction::ExternalTransaction; use std::fs::File; use std::io::BufReader; +use std::path::Path; +use validator::Validate; // TODO(Ayelet): Replace the use of the JSON files with generated instances, then serialize these // into JSON for testing. @@ -26,3 +31,47 @@ async fn test_add_transaction(#[case] json_file_path: &str, #[case] expected_res expected_response ); } + +const DEFAULT_GOOD_CONFIG_PATH: &str = "./src/json_files_for_testing/good_gateway_config.json"; +const DEFAULT_BAD_CONFIG_PATH: &str = "./src/json_files_for_testing/bad_gateway_config.json"; +const DEFAULT_BAD_ADDRESS_CONFIG_PATH: &str = + "./src/json_files_for_testing/bad_gateway_address_config.json"; + +#[test] +fn good_config_test() { + // Read the good config file and validate it. + let config = GatewayConfig::default(); + + let config_file = File::open(Path::new(DEFAULT_GOOD_CONFIG_PATH)).unwrap(); + let load_config = + load_and_process_config::(config_file, Command::new(""), vec![]).unwrap(); + assert!(load_config.validate().is_ok()); + assert_eq!(load_config.bind_address, config.bind_address); +} + +#[test] +fn bad_config_test() { + // Read the config file with the bad field path and validate it. + let config_file = std::fs::File::open(Path::new(DEFAULT_BAD_CONFIG_PATH)).unwrap(); + let load_config = + load_and_process_config::(config_file, Command::new(""), vec![]); + match load_config { + Ok(_) => panic!("Expected an error, but got a config."), + Err(e) => assert_eq!(e.to_string(), "missing field `bind_address`".to_owned()), + } +} + +#[test] +fn bad_config_address_test() { + // Read the config file with the bad bind_address values and validate it + let config_file = std::fs::File::open(Path::new(DEFAULT_BAD_ADDRESS_CONFIG_PATH)).unwrap(); + let load_config = + load_and_process_config::(config_file, Command::new(""), vec![]).unwrap(); + match load_config.validate() { + Ok(_) => panic!("Expected an error, but got a config."), + Err(e) => assert_eq!( + e.field_errors()["bind_address"][0].code, + "Invalid Socket address.".to_owned() + ), + } +} diff --git a/crates/gateway/src/json_files_for_testing/bad_gateway_address_config.json b/crates/gateway/src/json_files_for_testing/bad_gateway_address_config.json new file mode 100644 index 000000000..3d7bc78b4 --- /dev/null +++ b/crates/gateway/src/json_files_for_testing/bad_gateway_address_config.json @@ -0,0 +1,7 @@ +{ + "bind_address": { + "description": "IP:PORT of the gateway.", + "value": "bad_address:8080", + "privacy": "Public" + } +} \ No newline at end of file diff --git a/crates/gateway/src/json_files_for_testing/bad_gateway_config.json b/crates/gateway/src/json_files_for_testing/bad_gateway_config.json new file mode 100644 index 000000000..0e5a22116 --- /dev/null +++ b/crates/gateway/src/json_files_for_testing/bad_gateway_config.json @@ -0,0 +1,7 @@ +{ + "bnd_address": { + "description": "IP:PORT of the gateway.", + "value": "0.0.0.0:8080", + "privacy": "Public" + } +} \ No newline at end of file diff --git a/crates/gateway/src/json_files_for_testing/good_gateway_config.json b/crates/gateway/src/json_files_for_testing/good_gateway_config.json new file mode 100644 index 000000000..46667e530 --- /dev/null +++ b/crates/gateway/src/json_files_for_testing/good_gateway_config.json @@ -0,0 +1,7 @@ +{ + "bind_address": { + "description": "IP:PORT of the gateway.", + "value": "0.0.0.0:8080", + "privacy": "Public" + } +} \ No newline at end of file diff --git a/crates/gateway/src/lib.rs b/crates/gateway/src/lib.rs index 5cf135927..b2e828fac 100644 --- a/crates/gateway/src/lib.rs +++ b/crates/gateway/src/lib.rs @@ -1,2 +1,47 @@ pub mod errors; pub mod gateway; + +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use std::str::FromStr; + +use papyrus_config::dumping::{ser_param, SerializeConfig}; +use papyrus_config::{ParamPath, ParamPrivacyInput, SerializedParam}; +use std::net::SocketAddr; +use validator::{Validate, ValidationError}; + +/// Configuration for a gateway. +#[derive(Clone, Debug, Serialize, Deserialize, Validate, PartialEq)] +pub struct GatewayConfig { + /// The gateway bind address. + #[validate(custom = "validate_ip_address")] + pub bind_address: String, +} + +impl SerializeConfig for GatewayConfig { + fn dump(&self) -> BTreeMap { + BTreeMap::from_iter([ser_param( + "gateway.server_bind_address", + &self.bind_address, + "The server bind addres of a gateway.", + ParamPrivacyInput::Public, + )]) + } +} + +impl Default for GatewayConfig { + fn default() -> Self { + Self { + bind_address: String::from("0.0.0.0:8080"), + } + } +} + +pub fn validate_ip_address(ip_address: &str) -> Result<(), ValidationError> { + if SocketAddr::from_str(ip_address).is_err() { + let mut error = ValidationError::new("Invalid Socket address."); + error.message = Some("Please provide valid Socket address in the configuration.".into()); + return Err(error); + } + Ok(()) +}