diff --git a/crates/settings/src/deployer.rs b/crates/settings/src/deployer.rs index 5f040929c..dac3eb5af 100644 --- a/crates/settings/src/deployer.rs +++ b/crates/settings/src/deployer.rs @@ -100,44 +100,53 @@ impl DeployerConfigSource { impl YamlParsableHash for Deployer { fn parse_all_from_yaml( - document: Arc>, + documents: Vec>>, ) -> Result, YamlError> { - let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - let deployers_hash = require_hash( - &document_read, - Some("deployers"), - Some("missing field: deployers".to_string()), - )?; - - deployers_hash - .iter() - .map(|(key_yaml, deployer_yaml)| { - let deployer_key = key_yaml.as_str().unwrap_or_default().to_string(); - - let address = Deployer::validate_address(&require_string( - deployer_yaml, - Some("address"), - Some(format!( - "address string missing in deployer: {deployer_key}" - )), - )?)?; - - let network_name = match optional_string(deployer_yaml, "network") { - Some(network_name) => network_name, - None => deployer_key.clone(), - }; - let network = Network::parse_from_yaml(document.clone(), &network_name)?; - - let deployer = Deployer { - document: document.clone(), - key: deployer_key.clone(), - address, - network: Arc::new(network), - }; - - Ok((deployer_key, deployer)) - }) - .collect() + let mut deployers = HashMap::new(); + + for document in &documents { + let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; + + if let Ok(deployers_hash) = require_hash(&document_read, Some("deployers"), None) { + for (key_yaml, deployer_yaml) in deployers_hash { + let deployer_key = key_yaml.as_str().unwrap_or_default().to_string(); + + let address = Deployer::validate_address(&require_string( + deployer_yaml, + Some("address"), + Some(format!( + "address string missing in deployer: {deployer_key}" + )), + )?)?; + + let network_name = match optional_string(deployer_yaml, "network") { + Some(network_name) => network_name, + None => deployer_key.clone(), + }; + let network = Network::parse_from_yaml(documents.clone(), &network_name)?; + + let deployer = Deployer { + document: document.clone(), + key: deployer_key.clone(), + address, + network: Arc::new(network), + }; + + if deployers.contains_key(&deployer_key) { + return Err(YamlError::KeyShadowing(deployer_key)); + } + deployers.insert(deployer_key, deployer); + } + } + } + + if deployers.is_empty() { + return Err(YamlError::ParseError( + "missing field: deployers".to_string(), + )); + } + + Ok(deployers) } } @@ -208,47 +217,66 @@ mod tests { } #[test] - fn test_parse_deployers_from_yaml() { - let yaml = r#" -test: test + fn test_parse_deployers_from_yaml_multiple_files() { + let yaml_one = r#" +networks: + TestNetwork: + rpc: https://rpc.com + chain-id: 1 +deployers: + DeployerOne: + address: 0x1234567890123456789012345678901234567890 + network: TestNetwork "#; - let error = Deployer::parse_all_from_yaml(get_document(yaml)).unwrap_err(); - assert_eq!( - error, - YamlError::ParseError("missing field: deployers".to_string()) - ); - - let yaml = r#" + let yaml_two = r#" deployers: - TestDeployer: + DeployerTwo: + address: 0x0987654321098765432109876543210987654321 + network: TestNetwork "#; - let error = Deployer::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + + let documents = vec![get_document(yaml_one), get_document(yaml_two)]; + let deployers = Deployer::parse_all_from_yaml(documents).unwrap(); + + assert_eq!(deployers.len(), 2); + assert!(deployers.contains_key("DeployerOne")); + assert!(deployers.contains_key("DeployerTwo")); + assert_eq!( - error, - YamlError::ParseError("address string missing in deployer: TestDeployer".to_string()) + deployers.get("DeployerOne").unwrap().address.to_string(), + "0x1234567890123456789012345678901234567890" ); + assert_eq!( + deployers.get("DeployerTwo").unwrap().address.to_string(), + "0x0987654321098765432109876543210987654321" + ); + } - let yaml = r#" -deployers: - TestDeployer: - address: not_a_valid_address -"#; - let error = Deployer::parse_all_from_yaml(get_document(yaml)); - assert!(error.is_err()); - - let error = Deployer::parse_all_from_yaml(get_document( - r#" + #[test] + fn test_parse_deployers_from_yaml_duplicate_key() { + let yaml_one = r#" networks: TestNetwork: rpc: https://rpc.com chain-id: 1 deployers: - TestDeployer: - address: "0x1234567890123456789012345678901234567890" - network: SomeNetwork -"#, - )) - .unwrap_err(); - assert_eq!(error, YamlError::KeyNotFound("SomeNetwork".to_string())); + DuplicateDeployer: + address: 0x1234567890123456789012345678901234567890 + network: TestNetwork +"#; + let yaml_two = r#" +deployers: + DuplicateDeployer: + address: 0x0987654321098765432109876543210987654321 + network: TestNetwork +"#; + + let documents = vec![get_document(yaml_one), get_document(yaml_two)]; + let error = Deployer::parse_all_from_yaml(documents).unwrap_err(); + + assert_eq!( + error, + YamlError::KeyShadowing("DuplicateDeployer".to_string()) + ); } } diff --git a/crates/settings/src/deployment.rs b/crates/settings/src/deployment.rs index 247a11c3c..d4c034356 100644 --- a/crates/settings/src/deployment.rs +++ b/crates/settings/src/deployment.rs @@ -30,60 +30,69 @@ impl_all_wasm_traits!(Deployment); impl YamlParsableHash for Deployment { fn parse_all_from_yaml( - document: Arc>, + documents: Vec>>, ) -> Result, YamlError> { - let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - let deployments_hash = require_hash( - &document_read, - Some("deployments"), - Some("missing field: deployments".to_string()), - )?; - - deployments_hash - .iter() - .map(|(key_yaml, deployment_yaml)| { - let deployment_key = key_yaml.as_str().unwrap_or_default().to_string(); - - let scenario = Scenario::parse_from_yaml( - document.clone(), - &require_string( - deployment_yaml, - Some("scenario"), - Some(format!( - "scenario string missing in deployment: {deployment_key}" - )), - )?, - )?; - let order = Order::parse_from_yaml( - document.clone(), - &require_string( - deployment_yaml, - Some("order"), - Some(format!( - "order string missing in deployment: {deployment_key}" - )), - )?, - )?; - - if let Some(deployer) = &order.deployer { - if deployer != &scenario.deployer { - return Err(YamlError::ParseDeploymentConfigSourceError( - ParseDeploymentConfigSourceError::NoMatch, - )); + let mut deployments = HashMap::new(); + + for document in &documents { + let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; + + if let Ok(deployments_hash) = require_hash(&document_read, Some("deployments"), None) { + for (key_yaml, deployment_yaml) in deployments_hash { + let deployment_key = key_yaml.as_str().unwrap_or_default().to_string(); + + let scenario = Scenario::parse_from_yaml( + documents.clone(), + &require_string( + deployment_yaml, + Some("scenario"), + Some(format!( + "scenario string missing in deployment: {deployment_key}" + )), + )?, + )?; + + let order = Order::parse_from_yaml( + documents.clone(), + &require_string( + deployment_yaml, + Some("order"), + Some(format!( + "order string missing in deployment: {deployment_key}" + )), + )?, + )?; + + if let Some(deployer) = &order.deployer { + if deployer != &scenario.deployer { + return Err(YamlError::ParseDeploymentConfigSourceError( + ParseDeploymentConfigSourceError::NoMatch, + )); + } } - } - Ok(( - deployment_key.clone(), - Deployment { + let deployment = Deployment { document: document.clone(), - key: deployment_key, + key: deployment_key.clone(), scenario: Arc::new(scenario), order: Arc::new(order), - }, - )) - }) - .collect() + }; + + if deployments.contains_key(&deployment_key) { + return Err(YamlError::KeyShadowing(deployment_key)); + } + deployments.insert(deployment_key, deployment); + } + } + } + + if deployments.is_empty() { + return Err(YamlError::ParseError( + "missing field: deployments".to_string(), + )); + } + + Ok(deployments) } } @@ -229,7 +238,7 @@ mod tests { let yaml = r#" test: test "#; - let error = Deployment::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Deployment::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: deployments".to_string()) @@ -240,7 +249,7 @@ deployments: deployment1: test: test "#; - let error = Deployment::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Deployment::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("scenario string missing in deployment: deployment1".to_string()) @@ -265,7 +274,7 @@ deployments: scenario: scenario1 test: test "#; - let error = Deployment::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Deployment::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("order string missing in deployment: deployment1".to_string()) @@ -307,11 +316,119 @@ deployments: scenario: scenario1 order: order1 "#; - let error = Deployment::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Deployment::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error.to_string(), YamlError::ParseDeploymentConfigSourceError(ParseDeploymentConfigSourceError::NoMatch) .to_string() ); } + + const PREFIX: &str = r#" +networks: + network1: + rpc: https://eth.llamarpc.com + chain-id: 1 + network2: + rpc: https://test.com + chain-id: 2 +deployers: + deployer1: + address: 0x0000000000000000000000000000000000000000 + network: network1 + deployer2: + address: 0x0000000000000000000000000000000000000000 + network: network2 +scenarios: + scenario1: + bindings: + test: test + deployer: deployer1 + scenario2: + bindings: + test: test + deployer: deployer2 +tokens: + token1: + address: 0x0000000000000000000000000000000000000000 + network: network1 + token2: + address: 0x0000000000000000000000000000000000000000 + network: network2 +orders: + order1: + inputs: + - token: token1 + outputs: + - token: token1 + deployer: deployer1 + order2: + inputs: + - token: token2 + outputs: + - token: token2 + deployer: deployer2 +"#; + + #[test] + fn test_parse_deployments_from_yaml_multiple_files() { + let yaml_one = r#" +deployments: + DeploymentOne: + scenario: scenario1 + order: order1 +"#; + let yaml_two = r#" +deployments: + DeploymentTwo: + scenario: scenario2 + order: order2 +"#; + + let deployments = Deployment::parse_all_from_yaml(vec![ + get_document(&format!("{PREFIX}{yaml_one}")), + get_document(yaml_two), + ]) + .unwrap(); + + assert_eq!(deployments.len(), 2); + assert!(deployments.contains_key("DeploymentOne")); + assert!(deployments.contains_key("DeploymentTwo")); + + assert_eq!( + deployments.get("DeploymentOne").unwrap().key, + "DeploymentOne" + ); + assert_eq!( + deployments.get("DeploymentTwo").unwrap().key, + "DeploymentTwo" + ); + } + + #[test] + fn test_parse_deployments_from_yaml_duplicate_key() { + let yaml_one = r#" +deployments: + DuplicateDeployment: + scenario: scenario1 + order: order1 +"#; + let yaml_two = r#" +deployments: + DuplicateDeployment: + scenario: scenario2 + order: order2 +"#; + + let error = Deployment::parse_all_from_yaml(vec![ + get_document(&format!("{PREFIX}{yaml_one}")), + get_document(yaml_two), + ]) + .unwrap_err(); + + assert_eq!( + error, + YamlError::KeyShadowing("DuplicateDeployment".to_string()) + ); + } } diff --git a/crates/settings/src/gui.rs b/crates/settings/src/gui.rs index c1ba4bf07..2dc53067d 100644 --- a/crates/settings/src/gui.rs +++ b/crates/settings/src/gui.rs @@ -251,48 +251,59 @@ impl_all_wasm_traits!(Gui); impl Gui {} impl YamlParseableValue for Gui { - fn parse_from_yaml(_: Arc>) -> Result { + fn parse_from_yaml(_: Vec>>) -> Result { Err(YamlError::InvalidTraitFunction) } fn parse_from_yaml_optional( - document: Arc>, + documents: Vec>>, ) -> Result, YamlError> { - let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - - if let Some(gui) = optional_hash(&document_read, "gui") { - let name = require_string( - get_hash_value(gui, "name", Some("name field missing in gui".to_string()))?, - None, - Some("name field must be a string in gui".to_string()), - )?; - - let description = require_string( - get_hash_value( - gui, - "description", - Some("description field missing in gui".to_string()), - )?, - None, - Some("description field must be a string in gui".to_string()), - )?; - - let deployments = gui - .get(&StrictYaml::String("deployments".to_string())) - .ok_or(YamlError::ParseError( - "deployments field missing in gui".to_string(), - ))? - .as_hash() - .ok_or(YamlError::ParseError( - "deployments field must be a map in gui".to_string(), - ))?; - let gui_deployments = deployments - .iter() - .map(|(deployment_name, deployment_yaml)| { + let mut gui_res: Option = None; + let mut gui_deployments_res: HashMap = HashMap::new(); + + for document in &documents { + let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; + + if let Some(gui) = optional_hash(&document_read, "gui") { + let name = require_string( + get_hash_value(gui, "name", Some("name field missing in gui".to_string()))?, + None, + Some("name field must be a string in gui".to_string()), + )?; + + let description = require_string( + get_hash_value( + gui, + "description", + Some("description field missing in gui".to_string()), + )?, + None, + Some("description field must be a string in gui".to_string()), + )?; + + if gui_res.is_none() { + gui_res = Some(Gui { + name, + description, + deployments: gui_deployments_res.clone(), + }); + } + + let deployments = gui + .get(&StrictYaml::String("deployments".to_string())) + .ok_or(YamlError::ParseError( + "deployments field missing in gui".to_string(), + ))? + .as_hash() + .ok_or(YamlError::ParseError( + "deployments field must be a map in gui".to_string(), + ))?; + + for (deployment_name, deployment_yaml) in deployments { let deployment_name = deployment_name.as_str().unwrap_or_default().to_string(); let deployment = - Deployment::parse_from_yaml(document.clone(), &deployment_name)?; + Deployment::parse_from_yaml(documents.clone(), &deployment_name)?; let name = require_string( deployment_yaml, @@ -317,7 +328,7 @@ impl YamlParseableValue for Gui { "deposits list missing in gui deployment: {deployment_name}", )), )?.iter().enumerate().map(|(deposit_index, deposit_value)| { - let token = Token::parse_from_yaml(document.clone(), &require_string( + let token = Token::parse_from_yaml(documents.clone(), &require_string( deposit_value, Some("token"), Some(format!( @@ -431,19 +442,19 @@ impl YamlParseableValue for Gui { fields, select_tokens, }; - Ok((deployment_name, gui_deployment)) - }) - .collect::, YamlError>>()?; - - let gui: Gui = Gui { - name, - description, - deployments: gui_deployments, - }; - Ok(Some(gui)) - } else { - Ok(None) + + if gui_deployments_res.contains_key(&deployment_name) { + return Err(YamlError::KeyShadowing(deployment_name)); + } + gui_deployments_res.insert(deployment_name, gui_deployment); + } + if let Some(gui) = &mut gui_res { + gui.deployments.clone_from(&gui_deployments_res); + } + } } + + Ok(gui_res) } } @@ -611,7 +622,7 @@ mod tests { gui: test: test "#; - let error = Gui::parse_from_yaml_optional(get_document(yaml)).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("name field missing in gui".to_string()) @@ -621,7 +632,7 @@ gui: name: - test "#; - let error = Gui::parse_from_yaml_optional(get_document(yaml)).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("name field must be a string in gui".to_string()) @@ -631,7 +642,7 @@ gui: name: - test: test "#; - let error = Gui::parse_from_yaml_optional(get_document(yaml)).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("name field must be a string in gui".to_string()) @@ -641,7 +652,7 @@ gui: gui: name: test "#; - let error = Gui::parse_from_yaml_optional(get_document(yaml)).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("description field missing in gui".to_string()) @@ -652,7 +663,7 @@ gui: description: - test "#; - let error = Gui::parse_from_yaml_optional(get_document(yaml)).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("description field must be a string in gui".to_string()) @@ -663,7 +674,7 @@ gui: description: - test: test "#; - let error = Gui::parse_from_yaml_optional(get_document(yaml)).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("description field must be a string in gui".to_string()) @@ -674,7 +685,7 @@ gui: name: test description: test "#; - let error = Gui::parse_from_yaml_optional(get_document(yaml)).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("deployments field missing in gui".to_string()) @@ -685,7 +696,7 @@ gui: description: test deployments: test "#; - let error = Gui::parse_from_yaml_optional(get_document(yaml)).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("deployments field must be a map in gui".to_string()) @@ -697,7 +708,7 @@ gui: deployments: - test: test "#; - let error = Gui::parse_from_yaml_optional(get_document(yaml)).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("deployments field must be a map in gui".to_string()) @@ -711,7 +722,7 @@ gui: deployment1: test: test "#; - let error = Gui::parse_from_yaml_optional(get_document(yaml)).unwrap_err(); + let error = Gui::parse_from_yaml_optional(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: deployments".to_string()) @@ -759,8 +770,9 @@ gui: deployment1: test: test "#; - let error = Gui::parse_from_yaml_optional(get_document(&format!("{yaml_prefix}{yaml}"))) - .unwrap_err(); + let error = + Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) + .unwrap_err(); assert_eq!( error, YamlError::ParseError("name string missing in gui deployment: deployment1".to_string()) @@ -774,8 +786,9 @@ gui: deployment1: name: deployment1 "#; - let error = Gui::parse_from_yaml_optional(get_document(&format!("{yaml_prefix}{yaml}"))) - .unwrap_err(); + let error = + Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -792,8 +805,9 @@ gui: name: some name description: some description "#; - let error = Gui::parse_from_yaml_optional(get_document(&format!("{yaml_prefix}{yaml}"))) - .unwrap_err(); + let error = + Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -812,8 +826,9 @@ gui: deposits: - test: test "#; - let error = Gui::parse_from_yaml_optional(get_document(&format!("{yaml_prefix}{yaml}"))) - .unwrap_err(); + let error = + Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -833,8 +848,9 @@ gui: deposits: - token: test "#; - let error = Gui::parse_from_yaml_optional(get_document(&format!("{yaml_prefix}{yaml}"))) - .unwrap_err(); + let error = + Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) + .unwrap_err(); assert_eq!(error, YamlError::KeyNotFound("test".to_string())); let yaml = r#" @@ -848,8 +864,9 @@ gui: deposits: - token: token1 "#; - let error = Gui::parse_from_yaml_optional(get_document(&format!("{yaml_prefix}{yaml}"))) - .unwrap_err(); + let error = + Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -871,8 +888,9 @@ gui: presets: - test: test "#; - let error = Gui::parse_from_yaml_optional(get_document(&format!("{yaml_prefix}{yaml}"))) - .unwrap_err(); + let error = + Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -894,8 +912,9 @@ gui: presets: - "1" "#; - let error = Gui::parse_from_yaml_optional(get_document(&format!("{yaml_prefix}{yaml}"))) - .unwrap_err(); + let error = + Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) + .unwrap_err(); assert_eq!( error, YamlError::ParseError("fields list missing in gui deployment: deployment1".to_string()) @@ -916,8 +935,9 @@ gui: fields: - test: test "#; - let error = Gui::parse_from_yaml_optional(get_document(&format!("{yaml_prefix}{yaml}"))) - .unwrap_err(); + let error = + Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -941,8 +961,9 @@ gui: fields: - binding: test "#; - let error = Gui::parse_from_yaml_optional(get_document(&format!("{yaml_prefix}{yaml}"))) - .unwrap_err(); + let error = + Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -969,8 +990,9 @@ gui: - value: - test "#; - let error = Gui::parse_from_yaml_optional(get_document(&format!("{yaml_prefix}{yaml}"))) - .unwrap_err(); + let error = + Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -999,8 +1021,9 @@ gui: select-tokens: - test: test "#; - let error = Gui::parse_from_yaml_optional(get_document(&format!("{yaml_prefix}{yaml}"))) - .unwrap_err(); + let error = + Gui::parse_from_yaml_optional(vec![get_document(&format!("{yaml_prefix}{yaml}"))]) + .unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -1009,4 +1032,173 @@ gui: ) ); } + + #[test] + fn test_parse_gui_from_yaml_multiple_files() { + let yaml_one = r#" +networks: + network1: + rpc: https://eth.llamarpc.com + chain-id: 1 +deployers: + deployer1: + address: 0x0000000000000000000000000000000000000000 + network: network1 +scenarios: + scenario1: + bindings: + test: test + deployer: deployer1 +tokens: + token1: + address: 0x0000000000000000000000000000000000000001 + network: network1 + token2: + address: 0x0000000000000000000000000000000000000002 + network: network1 +orders: + order1: + inputs: + - token: token1 + outputs: + - token: token2 + deployer: deployer1 +deployments: + deployment1: + scenario: scenario1 + order: order1 + deployment2: + scenario: scenario1 + order: order1 +gui: + name: test + description: test + deployments: + deployment1: + name: test + description: test + deposits: + - token: token1 + presets: + - "1" + fields: + - binding: test + name: test + presets: + - value: test +"#; + let yaml_two = r#" +gui: + name: test + description: test + deployments: + deployment2: + name: test another + description: test another + deposits: + - token: token2 + presets: + - "1" + fields: + - binding: test + name: test + presets: + - value: test +"#; + let res = + Gui::parse_from_yaml_optional(vec![get_document(yaml_one), get_document(yaml_two)]) + .unwrap(); + + let gui = res.unwrap(); + assert_eq!(gui.deployments.len(), 2); + + let deployment = gui.deployments.get("deployment1").unwrap(); + assert_eq!(deployment.name, "test"); + assert_eq!(deployment.description, "test"); + assert_eq!(deployment.deposits[0].token.key, "token1"); + + let deployment = gui.deployments.get("deployment2").unwrap(); + assert_eq!(deployment.name, "test another"); + assert_eq!(deployment.description, "test another"); + assert_eq!(deployment.deposits[0].token.key, "token2"); + } + + #[test] + fn test_parse_gui_from_yaml_duplicate_key() { + let yaml_one = r#" +networks: + network1: + rpc: https://eth.llamarpc.com + chain-id: 1 +deployers: + deployer1: + address: 0x0000000000000000000000000000000000000000 + network: network1 +scenarios: + scenario1: + bindings: + test: test + deployer: deployer1 +tokens: + token1: + address: 0x0000000000000000000000000000000000000001 + network: network1 + token2: + address: 0x0000000000000000000000000000000000000002 + network: network1 +orders: + order1: + inputs: + - token: token1 + outputs: + - token: token2 + deployer: deployer1 +deployments: + deployment1: + scenario: scenario1 + order: order1 + deployment2: + scenario: scenario1 + order: order1 +gui: + name: test + description: test + deployments: + deployment1: + name: test + description: test + deposits: + - token: token1 + presets: + - "1" + fields: + - binding: test + name: test + presets: + - value: test +"#; + let yaml_two = r#" +gui: + name: test + description: test + deployments: + deployment1: + name: test + description: test + deposits: + - token: token1 + presets: + - "1" + fields: + - binding: test + name: test + presets: + - value: test +"#; + let error = + Gui::parse_from_yaml_optional(vec![get_document(yaml_one), get_document(yaml_two)]) + .unwrap_err(); + + assert_eq!(error, YamlError::KeyShadowing("deployment1".to_string())); + } } diff --git a/crates/settings/src/metaboard.rs b/crates/settings/src/metaboard.rs index e57307795..786d30658 100644 --- a/crates/settings/src/metaboard.rs +++ b/crates/settings/src/metaboard.rs @@ -27,37 +27,46 @@ impl Metaboard { impl YamlParsableHash for Metaboard { fn parse_all_from_yaml( - document: Arc>, + documents: Vec>>, ) -> Result, YamlError> { - let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - let metaboards_hash = require_hash( - &document_read, - Some("metaboards"), - Some("missing field: metaboards".to_string()), - )?; - - metaboards_hash - .iter() - .map(|(key_yaml, metaboard_yaml)| { - let metaboard_key = key_yaml.as_str().unwrap_or_default().to_string(); - - let url = Metaboard::validate_url(&require_string( - metaboard_yaml, - None, - Some(format!( - "metaboard value must be a string for key: {metaboard_key}" - )), - )?)?; - - let metaboard = Metaboard { - document: document.clone(), - key: metaboard_key.clone(), - url, - }; - - Ok((metaboard_key, metaboard)) - }) - .collect() + let mut metaboards = HashMap::new(); + + for document in documents { + let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; + + if let Ok(metaboards_hash) = require_hash(&document_read, Some("metaboards"), None) { + for (key_yaml, metaboard_yaml) in metaboards_hash { + let metaboard_key = key_yaml.as_str().unwrap_or_default().to_string(); + + let url = Metaboard::validate_url(&require_string( + metaboard_yaml, + None, + Some(format!( + "metaboard value must be a string for key: {metaboard_key}" + )), + )?)?; + + let metaboard = Metaboard { + document: document.clone(), + key: metaboard_key.clone(), + url, + }; + + if metaboards.contains_key(&metaboard_key) { + return Err(YamlError::KeyShadowing(metaboard_key)); + } + metaboards.insert(metaboard_key, metaboard); + } + } + } + + if metaboards.is_empty() { + return Err(YamlError::ParseError( + "missing field: metaboards".to_string(), + )); + } + + Ok(metaboards) } } @@ -83,47 +92,50 @@ mod test { use crate::yaml::tests::get_document; #[test] - fn test_parse_metaboards_from_yaml() { - let yaml = r#" -test: test + fn test_parse_metaboards_from_yaml_multiple_files() { + let yaml_one = r#" +metaboards: + MetaboardOne: https://metaboard-one.com "#; - let error = Metaboard::parse_all_from_yaml(get_document(yaml)).unwrap_err(); - assert_eq!( - error, - YamlError::ParseError("missing field: metaboards".to_string()) - ); - - let yaml = r#" + let yaml_two = r#" metaboards: - TestMetaboard: - test: https://metaboard.com + MetaboardTwo: https://metaboard-two.com "#; - let error = Metaboard::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + + let documents = vec![get_document(yaml_one), get_document(yaml_two)]; + let metaboards = Metaboard::parse_all_from_yaml(documents).unwrap(); + + assert_eq!(metaboards.len(), 2); + assert!(metaboards.contains_key("MetaboardOne")); + assert!(metaboards.contains_key("MetaboardTwo")); + assert_eq!( - error, - YamlError::ParseError( - "metaboard value must be a string for key: TestMetaboard".to_string() - ) + metaboards.get("MetaboardOne").unwrap().url, + Url::parse("https://metaboard-one.com").unwrap() ); + assert_eq!( + metaboards.get("MetaboardTwo").unwrap().url, + Url::parse("https://metaboard-two.com").unwrap() + ); + } - let yaml = r#" + #[test] + fn test_parse_metaboards_from_yaml_duplicate_key() { + let yaml_one = r#" metaboards: - TestMetaboard: - - https://metaboard.com + DuplicateMetaboard: https://metaboard-one.com "#; - let error = Metaboard::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let yaml_two = r#" +metaboards: + DuplicateMetaboard: https://metaboard-two.com +"#; + + let documents = vec![get_document(yaml_one), get_document(yaml_two)]; + let error = Metaboard::parse_all_from_yaml(documents).unwrap_err(); + assert_eq!( error, - YamlError::ParseError( - "metaboard value must be a string for key: TestMetaboard".to_string() - ) + YamlError::KeyShadowing("DuplicateMetaboard".to_string()) ); - - let yaml = r#" -metaboards: - TestMetaboard: invalid-url -"#; - let res = Metaboard::parse_all_from_yaml(get_document(yaml)); - assert!(res.is_err()); } } diff --git a/crates/settings/src/network.rs b/crates/settings/src/network.rs index 6a2a0e345..0e19212fa 100644 --- a/crates/settings/src/network.rs +++ b/crates/settings/src/network.rs @@ -98,55 +98,62 @@ impl_all_wasm_traits!(Network); impl YamlParsableHash for Network { fn parse_all_from_yaml( - document: Arc>, + documents: Vec>>, ) -> Result, YamlError> { - let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - let networks_hash = require_hash( - &document_read, - Some("networks"), - Some("missing field: networks".to_string()), - )?; - - networks_hash - .into_iter() - .map(|(key_yaml, network_yaml)| { - let network_key = key_yaml.as_str().unwrap_or_default().to_string(); - - let rpc_url = Network::validate_rpc(&require_string( - network_yaml, - Some("rpc"), - Some(format!("rpc string missing in network: {network_key}")), - )?)?; - - let chain_id = Network::validate_chain_id(&require_string( - network_yaml, - Some("chain-id"), - Some(format!( - "chain-id number as string missing in network: {network_key}" - )), - )?)?; - - let label = optional_string(network_yaml, "label"); - - let network_id = optional_string(network_yaml, "network-id") - .map(|id| Network::validate_network_id(&id)) - .transpose()?; - - let currency = optional_string(network_yaml, "currency"); - - let network = Network { - document: document.clone(), - key: network_key.clone(), - rpc: rpc_url, - chain_id, - label, - network_id, - currency, - }; - - Ok((network_key, network)) - }) - .collect() + let mut networks = HashMap::new(); + + for document in documents { + let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; + + if let Ok(networks_hash) = require_hash(&document_read, Some("networks"), None) { + for (key_yaml, network_yaml) in networks_hash { + let network_key = key_yaml.as_str().unwrap_or_default().to_string(); + + let rpc_url = Network::validate_rpc(&require_string( + network_yaml, + Some("rpc"), + Some(format!("rpc string missing in network: {network_key}")), + )?)?; + + let chain_id = Network::validate_chain_id(&require_string( + network_yaml, + Some("chain-id"), + Some(format!( + "chain-id number as string missing in network: {network_key}" + )), + )?)?; + + let label = optional_string(network_yaml, "label"); + + let network_id = optional_string(network_yaml, "network-id") + .map(|id| Network::validate_network_id(&id)) + .transpose()?; + + let currency = optional_string(network_yaml, "currency"); + + let network = Network { + document: document.clone(), + key: network_key.clone(), + rpc: rpc_url, + chain_id, + label, + network_id, + currency, + }; + + if networks.contains_key(&network_key) { + return Err(YamlError::KeyShadowing(network_key)); + } + networks.insert(network_key, network); + } + } + } + + if networks.is_empty() { + return Err(YamlError::ParseError("missing field: networks".to_string())); + } + + Ok(networks) } } @@ -223,7 +230,7 @@ mod tests { let yaml = r#" test: test "#; - let error = Network::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Network::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: networks".to_string()) @@ -233,7 +240,7 @@ test: test networks: mainnet: "#; - let error = Network::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Network::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("rpc string missing in network: mainnet".to_string()) @@ -244,7 +251,7 @@ networks: mainnet: rpc: https://mainnet.infura.io "#; - let error = Network::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Network::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -252,4 +259,70 @@ networks: ) ); } + + #[test] + fn test_parse_networks_from_yaml_multiple_files() { + let yaml_one = r#" +networks: + mainnet: + rpc: https://mainnet.infura.io + chain-id: 1 + testnet: + rpc: https://testnet.infura.io + chain-id: 2 +"#; + let yaml_two = r#" +networks: + network-one: + rpc: https://network-one.infura.io + chain-id: 3 + network-two: + rpc: https://network-two.infura.io + chain-id: 4 +"#; + let networks = + Network::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) + .unwrap(); + + assert_eq!(networks.len(), 4); + assert_eq!( + networks.get("mainnet").unwrap().rpc, + Url::parse("https://mainnet.infura.io").unwrap() + ); + assert_eq!( + networks.get("testnet").unwrap().rpc, + Url::parse("https://testnet.infura.io").unwrap() + ); + assert_eq!( + networks.get("network-one").unwrap().rpc, + Url::parse("https://network-one.infura.io").unwrap() + ); + assert_eq!( + networks.get("network-two").unwrap().rpc, + Url::parse("https://network-two.infura.io").unwrap() + ); + } + + #[test] + fn test_parse_networks_from_yaml_duplicate_key() { + let yaml_one = r#" +networks: + mainnet: + rpc: https://mainnet.infura.io + chain-id: 1 + testnet: + rpc: https://mainnet.infura.io + chain-id: 2 +"#; + let yaml_two = r#" +networks: + mainnet: + rpc: https://mainnet.infura.io + chain-id: 1 +"#; + let error = + Network::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) + .unwrap_err(); + assert_eq!(error, YamlError::KeyShadowing("mainnet".to_string())); + } } diff --git a/crates/settings/src/order.rs b/crates/settings/src/order.rs index c72a32d9f..caec89ab9 100644 --- a/crates/settings/src/order.rs +++ b/crates/settings/src/order.rs @@ -67,153 +67,162 @@ impl Order { impl YamlParsableHash for Order { fn parse_all_from_yaml( - document: Arc>, + documents: Vec>>, ) -> Result, YamlError> { - let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - let orders_hash = require_hash( - &document_read, - Some("orders"), - Some("missing field: orders".to_string()), - )?; - - orders_hash - .into_iter() - .map(|(key_yaml, order_yaml)| { - let order_key = key_yaml.as_str().unwrap_or_default().to_string(); + let mut orders = HashMap::new(); + + for document in &documents { + let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; + + if let Ok(orders_hash) = require_hash(&document_read, Some("orders"), None) { + for (key_yaml, order_yaml) in orders_hash { + let order_key = key_yaml.as_str().unwrap_or_default().to_string(); + + let mut network: Option> = None; + + let deployer = match optional_string(order_yaml, "deployer") { + Some(deployer_name) => { + let deployer = Arc::new(Deployer::parse_from_yaml( + documents.clone(), + &deployer_name, + )?); + if let Some(n) = &network { + if deployer.network != *n { + return Err(YamlError::ParseOrderConfigSourceError( + ParseOrderConfigSourceError::NetworkNotMatch, + )); + } + } else { + network = Some(deployer.network.clone()); + } + Some(deployer) + } + None => None, + }; - let mut network: Option> = None; + let orderbook = match optional_string(order_yaml, "orderbook") { + Some(orderbook_name) => { + let orderbook = Arc::new(Orderbook::parse_from_yaml( + documents.clone(), + &orderbook_name, + )?); + if let Some(n) = &network { + if orderbook.network != *n { + return Err(YamlError::ParseOrderConfigSourceError( + ParseOrderConfigSourceError::NetworkNotMatch, + )); + } + } else { + network = Some(orderbook.network.clone()); + } + Some(orderbook) + } + None => None, + }; + + let inputs = require_vec( + order_yaml, + "inputs", + Some(format!("inputs list missing in order: {order_key}")), + )? + .iter() + .enumerate() + .map(|(i, input)| { + let token_name = require_string( + input, + Some("token"), + Some(format!( + "token string missing in input index: {i} in order: {order_key}" + )), + )?; + let token = Token::parse_from_yaml(documents.clone(), &token_name)?; - let deployer = match optional_string(order_yaml, "deployer") { - Some(deployer_name) => { - let deployer = - Arc::new(Deployer::parse_from_yaml(document.clone(), &deployer_name)?); if let Some(n) = &network { - if deployer.network != *n { + if token.network != *n { return Err(YamlError::ParseOrderConfigSourceError( ParseOrderConfigSourceError::NetworkNotMatch, )); } } else { - network = Some(deployer.network.clone()); + network = Some(token.network.clone()); } - Some(deployer) - } - None => None, - }; - - let orderbook = match optional_string(order_yaml, "orderbook") { - Some(orderbook_name) => { - let orderbook = Arc::new(Orderbook::parse_from_yaml( - document.clone(), - &orderbook_name, - )?); + + let vault_id = match optional_string(input, "vault-id") { + Some(id) => Some(Order::validate_vault_id(&id)?), + None => None, + }; + + Ok(OrderIO { + token: Arc::new(token), + vault_id, + }) + }) + .collect::, YamlError>>()?; + + let outputs = require_vec( + order_yaml, + "outputs", + Some(format!("outputs list missing in order: {order_key}")), + )? + .iter() + .enumerate() + .map(|(i, output)| { + let token_name = require_string( + output, + Some("token"), + Some(format!( + "token string missing in output index: {i} in order: {order_key}" + )), + )?; + let token = Token::parse_from_yaml(documents.clone(), &token_name)?; + if let Some(n) = &network { - if orderbook.network != *n { + if token.network != *n { return Err(YamlError::ParseOrderConfigSourceError( ParseOrderConfigSourceError::NetworkNotMatch, )); } } else { - network = Some(orderbook.network.clone()); - } - Some(orderbook) - } - None => None, - }; - - let inputs = require_vec( - order_yaml, - "inputs", - Some(format!("inputs list missing in order: {order_key}")), - )? - .iter() - .enumerate() - .map(|(i, input)| { - let token_name = require_string( - input, - Some("token"), - Some(format!( - "token string missing in input index: {i} in order: {order_key}" - )), - )?; - let token = Token::parse_from_yaml(document.clone(), &token_name)?; - - if let Some(n) = &network { - if token.network != *n { - return Err(YamlError::ParseOrderConfigSourceError( - ParseOrderConfigSourceError::NetworkNotMatch, - )); + network = Some(token.network.clone()); } - } else { - network = Some(token.network.clone()); - } - let vault_id = match optional_string(input, "vault-id") { - Some(id) => Some(Order::validate_vault_id(&id)?), - None => None, - }; + let vault_id = match optional_string(output, "vault-id") { + Some(id) => Some(Order::validate_vault_id(&id)?), + None => None, + }; - Ok(OrderIO { - token: Arc::new(token), - vault_id, + Ok(OrderIO { + token: Arc::new(token), + vault_id, + }) }) - }) - .collect::, YamlError>>()?; - - let outputs = require_vec( - order_yaml, - "outputs", - Some(format!("outputs list missing in order: {order_key}")), - )? - .iter() - .enumerate() - .map(|(i, output)| { - let token_name = require_string( - output, - Some("token"), - Some(format!( - "token string missing in output index: {i} in order: {order_key}" - )), - )?; - let token = Token::parse_from_yaml(document.clone(), &token_name)?; - - if let Some(n) = &network { - if token.network != *n { - return Err(YamlError::ParseOrderConfigSourceError( - ParseOrderConfigSourceError::NetworkNotMatch, - )); - } - } else { - network = Some(token.network.clone()); + .collect::, YamlError>>()?; + + let order = Order { + document: document.clone(), + key: order_key.clone(), + inputs, + outputs, + network: network.ok_or( + ParseOrderConfigSourceError::NetworkNotFoundError(String::new()), + )?, + deployer, + orderbook, + }; + + if orders.contains_key(&order_key) { + return Err(YamlError::KeyShadowing(order_key)); } + orders.insert(order_key, order); + } + } + } - let vault_id = match optional_string(output, "vault-id") { - Some(id) => Some(Order::validate_vault_id(&id)?), - None => None, - }; + if orders.is_empty() { + return Err(YamlError::ParseError("missing field: orders".to_string())); + } - Ok(OrderIO { - token: Arc::new(token), - vault_id, - }) - }) - .collect::, YamlError>>()?; - - let order = Order { - document: document.clone(), - key: order_key.clone(), - inputs, - outputs, - network: network.ok_or(ParseOrderConfigSourceError::NetworkNotFoundError( - String::new(), - ))?, - deployer, - orderbook, - }; - - Ok((order_key, order)) - }) - .collect() + Ok(orders) } } @@ -532,7 +541,7 @@ mod tests { let yaml = r#" test: test "#; - let error = Order::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Order::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: orders".to_string()) @@ -542,7 +551,7 @@ test: test orders: order1: "#; - let error = Order::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Order::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("inputs list missing in order: order1".to_string()) @@ -554,7 +563,7 @@ orders: inputs: - test: test "#; - let error = Order::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Order::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -568,7 +577,7 @@ orders: inputs: - token: eth "#; - let error = Order::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Order::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: tokens".to_string()) @@ -588,7 +597,7 @@ orders: inputs: - token: eth "#; - let error = Order::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Order::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("outputs list missing in order: order1".to_string()) @@ -610,7 +619,7 @@ orders: outputs: - test: test "#; - let error = Order::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Order::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -618,4 +627,81 @@ orders: ) ); } + + #[test] + fn test_parse_orders_from_yaml_multiple_files() { + let yaml_one = r#" +networks: + mainnet: + rpc: "https://mainnet.infura.io" + chain-id: "1" +tokens: + token-one: + network: mainnet + address: 0x1234567890123456789012345678901234567890 + token-two: + network: mainnet + address: 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +orders: + OrderOne: + inputs: + - token: token-one + outputs: + - token: token-two +"#; + let yaml_two = r#" +orders: + OrderTwo: + inputs: + - token: token-one + outputs: + - token: token-two +"#; + + let documents = vec![get_document(yaml_one), get_document(yaml_two)]; + let orders = Order::parse_all_from_yaml(documents).unwrap(); + + assert_eq!(orders.len(), 2); + assert!(orders.contains_key("OrderOne")); + assert!(orders.contains_key("OrderTwo")); + + assert_eq!(orders.get("OrderOne").unwrap().key, "OrderOne"); + assert_eq!(orders.get("OrderTwo").unwrap().key, "OrderTwo"); + } + + #[test] + fn test_parse_orders_from_yaml_duplicate_key() { + let yaml_one = r#" +networks: + mainnet: + rpc: "https://mainnet.infura.io" + chain-id: "1" +tokens: + token-one: + network: mainnet + address: 0x1234567890123456789012345678901234567890 + token-two: + network: mainnet + address: 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +orders: + DuplicateOrder: + inputs: + - token: token-one + outputs: + - token: token-two +"#; + let yaml_two = r#" +orders: + DuplicateOrder: + inputs: + - token: token-one + outputs: + - token: token-two +"#; + + let documents = vec![get_document(yaml_one), get_document(yaml_two)]; + let error = Order::parse_all_from_yaml(documents).unwrap_err(); + + assert_eq!(error, YamlError::KeyShadowing("DuplicateOrder".to_string())); + } } diff --git a/crates/settings/src/orderbook.rs b/crates/settings/src/orderbook.rs index 3c730a8b1..e8d092a5f 100644 --- a/crates/settings/src/orderbook.rs +++ b/crates/settings/src/orderbook.rs @@ -37,18 +37,20 @@ impl Orderbook { impl YamlParsableHash for Orderbook { fn parse_all_from_yaml( - document: Arc>, + documents: Vec>>, ) -> Result, YamlError> { - let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - let orderbooks_hash = require_hash( - &document_read, - Some("orderbooks"), - Some("missing field: orderbooks".to_string()), - )?; - - orderbooks_hash - .into_iter() - .map(|(key_yaml, orderbook_yaml)| { + let mut orderbooks = HashMap::new(); + + for document in &documents { + let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; + + let orderbooks_hash = require_hash( + &document_read, + Some("orderbooks"), + Some("missing field: orderbooks".to_string()), + )?; + + for (key_yaml, orderbook_yaml) in orderbooks_hash { let orderbook_key = key_yaml.as_str().unwrap_or_default().to_string(); let address = Orderbook::validate_address(&require_string( @@ -63,14 +65,16 @@ impl YamlParsableHash for Orderbook { Some(network_name) => network_name, None => orderbook_key.clone(), }; - let network = Network::parse_from_yaml(document.clone(), &network_name)?; + let network = Network::parse_from_yaml(documents.clone(), &network_name)?; let subgraph_name = match optional_string(orderbook_yaml, "subgraph") { Some(subgraph_name) => subgraph_name, None => orderbook_key.clone(), }; - let subgraph = - Arc::new(Subgraph::parse_from_yaml(document.clone(), &subgraph_name)?); + let subgraph = Arc::new(Subgraph::parse_from_yaml( + documents.clone(), + &subgraph_name, + )?); let label = optional_string(orderbook_yaml, "label"); @@ -83,9 +87,14 @@ impl YamlParsableHash for Orderbook { label, }; - Ok((orderbook_key, orderbook)) - }) - .collect() + if orderbooks.contains_key(&orderbook_key) { + return Err(YamlError::KeyShadowing(orderbook_key)); + } + orderbooks.insert(orderbook_key, orderbook); + } + } + + Ok(orderbooks) } } @@ -275,7 +284,7 @@ mod tests { let yaml = r#" test: test "#; - let error = Orderbook::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: orderbooks".to_string()) @@ -285,7 +294,7 @@ test: test orderbooks: TestOrderbook: "#; - let error = Orderbook::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("address string missing in orderbook: TestOrderbook".to_string()) @@ -297,7 +306,7 @@ orderbooks: address: 0x1234567890123456789012345678901234567890 network: TestNetwork "#; - let error = Orderbook::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: networks".to_string()) @@ -313,7 +322,7 @@ orderbooks: address: 0x1234567890123456789012345678901234567890 network: TestNetwork "#; - let error = Orderbook::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!(error, YamlError::KeyNotFound("TestNetwork".to_string())); let yaml = r#" @@ -326,7 +335,7 @@ orderbooks: address: 0x1234567890123456789012345678901234567890 network: TestNetwork "#; - let error = Orderbook::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: subgraphs".to_string()) @@ -345,7 +354,79 @@ orderbooks: network: TestNetwork subgraph: TestSubgraph "#; - let error = Orderbook::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Orderbook::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!(error, YamlError::KeyNotFound("TestSubgraph".to_string())); } + + #[test] + fn test_parse_orderbooks_from_yaml_multiple_files() { + let yaml_one = r#" +networks: + TestNetwork: + rpc: https://rpc.com + chain-id: 1 +subgraphs: + TestSubgraph: https://subgraph.com +orderbooks: + OrderbookOne: + address: 0x1234567890123456789012345678901234567890 + network: TestNetwork + subgraph: TestSubgraph +"#; + let yaml_two = r#" +orderbooks: + OrderbookTwo: + address: 0x0987654321098765432109876543210987654321 + network: TestNetwork + subgraph: TestSubgraph +"#; + + let documents = vec![get_document(yaml_one), get_document(yaml_two)]; + let orderbooks = Orderbook::parse_all_from_yaml(documents).unwrap(); + + assert_eq!(orderbooks.len(), 2); + assert!(orderbooks.contains_key("OrderbookOne")); + assert!(orderbooks.contains_key("OrderbookTwo")); + + assert_eq!( + orderbooks.get("OrderbookOne").unwrap().address.to_string(), + "0x1234567890123456789012345678901234567890" + ); + assert_eq!( + orderbooks.get("OrderbookTwo").unwrap().address.to_string(), + "0x0987654321098765432109876543210987654321" + ); + } + + #[test] + fn test_parse_orderbooks_from_yaml_duplicate_key() { + let yaml_one = r#" +networks: + TestNetwork: + rpc: https://rpc.com + chain-id: 1 +subgraphs: + TestSubgraph: https://subgraph.com +orderbooks: + DuplicateOrderbook: + address: 0x1234567890123456789012345678901234567890 + network: TestNetwork + subgraph: TestSubgraph +"#; + let yaml_two = r#" +orderbooks: + DuplicateOrderbook: + address: 0x0987654321098765432109876543210987654321 + network: TestNetwork + subgraph: TestSubgraph +"#; + + let documents = vec![get_document(yaml_one), get_document(yaml_two)]; + let error = Orderbook::parse_all_from_yaml(documents).unwrap_err(); + + assert_eq!( + error, + YamlError::KeyShadowing("DuplicateOrderbook".to_string()) + ); + } } diff --git a/crates/settings/src/scenario.rs b/crates/settings/src/scenario.rs index 848cf7ec6..35e677e26 100644 --- a/crates/settings/src/scenario.rs +++ b/crates/settings/src/scenario.rs @@ -54,7 +54,8 @@ impl Scenario { } pub fn validate_scenario( - document: Arc>, + documents: Vec>>, + current_document: Arc>, deployer: &mut Option>, scenarios: &mut HashMap, parent_scenario: ScenarioParent, @@ -106,7 +107,7 @@ impl Scenario { .transpose()?; if let Some(deployer_name) = optional_string(scenario_yaml, "deployer") { - let current_deployer = Deployer::parse_from_yaml(document.clone(), &deployer_name)?; + let current_deployer = Deployer::parse_from_yaml(documents.clone(), &deployer_name)?; if let Some(parent_deployer) = parent_scenario.deployer.as_ref() { if current_deployer.key != parent_deployer.key { @@ -121,10 +122,13 @@ impl Scenario { *deployer = Some(Arc::new(current_deployer)); } + if scenarios.contains_key(&scenario_key) { + return Err(YamlError::KeyShadowing(scenario_key)); + } scenarios.insert( scenario_key.clone(), Scenario { - document: document.clone(), + document: current_document.clone(), key: scenario_key.clone(), bindings: bindings.clone(), runs, @@ -139,7 +143,8 @@ impl Scenario { for (child_key, child_scenario_yaml) in scenarios_yaml { let child_key = child_key.as_str().unwrap_or_default().to_string(); Self::validate_scenario( - document.clone(), + documents.clone(), + current_document.clone(), deployer, scenarios, ScenarioParent { @@ -158,41 +163,47 @@ impl Scenario { impl YamlParsableHash for Scenario { fn parse_all_from_yaml( - document: Arc>, + documents: Vec>>, ) -> Result, YamlError> { - let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - let scenarios_hash = require_hash( - &document_read, - Some("scenarios"), - Some("missing field: scenarios".to_string()), - )?; - let mut scenarios = HashMap::new(); - for (key_yaml, scenario_yaml) in scenarios_hash { - let scenario_key = key_yaml.as_str().unwrap_or_default().to_string(); - - let mut deployer: Option> = None; - - Self::validate_scenario( - document.clone(), - &mut deployer, - &mut scenarios, - ScenarioParent { - bindings: None, - deployer: None, - }, - scenario_key.clone(), - scenario_yaml, - )?; - - if deployer.is_none() { - return Err(YamlError::ParseScenarioConfigSourceError( - ParseScenarioConfigSourceError::DeployerNotFound(scenario_key), - )); + for document in &documents { + let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; + + if let Ok(scenarios_hash) = require_hash(&document_read, Some("scenarios"), None) { + for (key_yaml, scenario_yaml) in scenarios_hash { + let scenario_key = key_yaml.as_str().unwrap_or_default().to_string(); + + let mut deployer: Option> = None; + + Self::validate_scenario( + documents.clone(), + document.clone(), + &mut deployer, + &mut scenarios, + ScenarioParent { + bindings: None, + deployer: None, + }, + scenario_key.clone(), + scenario_yaml, + )?; + + if deployer.is_none() { + return Err(YamlError::ParseScenarioConfigSourceError( + ParseScenarioConfigSourceError::DeployerNotFound(scenario_key), + )); + } + } } } + if scenarios.is_empty() { + return Err(YamlError::ParseError( + "missing field: scenarios".to_string(), + )); + } + Ok(scenarios) } } @@ -497,7 +508,7 @@ mod tests { let yaml = r#" test: test "#; - let error = Scenario::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: scenarios".to_string()) @@ -508,7 +519,7 @@ scenarios: scenario1: test: test "#; - let error = Scenario::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("bindings map missing in scenario: scenario1".to_string()) @@ -521,7 +532,7 @@ scenarios: key1: - value1 "#; - let error = Scenario::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -536,7 +547,7 @@ scenarios: key1: - value1: value2 "#; - let error = Scenario::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -563,7 +574,7 @@ scenarios: bindings: key1: value "#; - let error = Scenario::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error.to_string(), YamlError::ParseScenarioConfigSourceError( @@ -598,7 +609,7 @@ scenarios: key2: value deployer: testnet "#; - let error = Scenario::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Scenario::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error.to_string(), YamlError::ParseScenarioConfigSourceError( @@ -607,4 +618,119 @@ scenarios: .to_string() ); } + + #[test] + fn test_parse_scenarios_from_yaml_multiple_files() { + let yaml_one = r#" +networks: + mainnet: + rpc: https://rpc.com + chain-id: 1 +deployers: + mainnet: + address: 0x1234567890123456789012345678901234567890 + network: mainnet +scenarios: + scenario1: + deployer: mainnet + bindings: + key1: binding1 + scenarios: + scenario2: + bindings: + key2: binding2 +"#; + let yaml_two = r#" +scenarios: + scenario3: + deployer: mainnet + bindings: + key3: binding3 + scenarios: + scenario4: + bindings: + key4: binding4 +"#; + let scenarios = + Scenario::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) + .unwrap(); + + assert_eq!(scenarios.len(), 4); + assert!(scenarios.contains_key("scenario1")); + assert!(scenarios.contains_key("scenario2")); + assert!(scenarios.contains_key("scenario3")); + assert!(scenarios.contains_key("scenario4")); + + assert_eq!( + scenarios + .get("scenario1") + .unwrap() + .bindings + .get("key1") + .unwrap(), + "binding1" + ); + assert_eq!( + scenarios + .get("scenario2") + .unwrap() + .bindings + .get("key2") + .unwrap(), + "binding2" + ); + assert_eq!( + scenarios + .get("scenario3") + .unwrap() + .bindings + .get("key3") + .unwrap(), + "binding3" + ); + assert_eq!( + scenarios + .get("scenario4") + .unwrap() + .bindings + .get("key4") + .unwrap(), + "binding4" + ); + } + + #[test] + fn test_parse_scenarios_from_yaml_duplicate_key() { + let yaml_one = r#" +networks: + mainnet: + rpc: https://rpc.com + chain-id: 1 +deployers: + mainnet: + address: 0x1234567890123456789012345678901234567890 + network: mainnet +scenarios: + DuplicateScenario: + deployer: mainnet + bindings: + key1: binding1 +"#; + let yaml_two = r#" +scenarios: + DuplicateScenario: + deployer: mainnet + bindings: + key1: binding2 +"#; + + let error = + Scenario::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) + .unwrap_err(); + + assert_eq!( + error, + YamlError::KeyShadowing("DuplicateScenario".to_string()) + ); + } } diff --git a/crates/settings/src/subgraph.rs b/crates/settings/src/subgraph.rs index f6103dfde..a07bca47d 100644 --- a/crates/settings/src/subgraph.rs +++ b/crates/settings/src/subgraph.rs @@ -27,37 +27,46 @@ impl Subgraph { impl YamlParsableHash for Subgraph { fn parse_all_from_yaml( - document: Arc>, + documents: Vec>>, ) -> Result, YamlError> { - let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - let subgraphs_hash = require_hash( - &document_read, - Some("subgraphs"), - Some("missing field: subgraphs".to_string()), - )?; - - subgraphs_hash - .iter() - .map(|(key_yaml, subgraph_yaml)| { - let subgraph_key = key_yaml.as_str().unwrap_or_default().to_string(); - - let url = Subgraph::validate_url(&require_string( - subgraph_yaml, - None, - Some(format!( - "subgraph value must be a string for key: {subgraph_key}" - )), - )?)?; - - let subgraph = Subgraph { - document: document.clone(), - key: subgraph_key.clone(), - url, - }; - - Ok((subgraph_key, subgraph)) - }) - .collect() + let mut subgraphs = HashMap::new(); + + for document in documents { + let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; + + if let Ok(subgraphs_hash) = require_hash(&document_read, Some("subgraphs"), None) { + for (key_yaml, subgraph_yaml) in subgraphs_hash { + let subgraph_key = key_yaml.as_str().unwrap_or_default().to_string(); + + let url = Subgraph::validate_url(&require_string( + subgraph_yaml, + None, + Some(format!( + "subgraph value must be a string for key: {subgraph_key}" + )), + )?)?; + + let subgraph = Subgraph { + document: document.clone(), + key: subgraph_key.clone(), + url, + }; + + if subgraphs.contains_key(&subgraph_key) { + return Err(YamlError::KeyShadowing(subgraph_key)); + } + subgraphs.insert(subgraph_key, subgraph); + } + } + } + + if subgraphs.is_empty() { + return Err(YamlError::ParseError( + "missing field: subgraphs".to_string(), + )); + } + + Ok(subgraphs) } } @@ -87,7 +96,7 @@ mod test { let yaml = r#" test: test "#; - let error = Subgraph::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Subgraph::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: subgraphs".to_string()) @@ -98,7 +107,7 @@ subgraphs: TestSubgraph: test: https://subgraph.com "#; - let error = Subgraph::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Subgraph::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -111,7 +120,7 @@ subgraphs: TestSubgraph: - https://subgraph.com "#; - let error = Subgraph::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + let error = Subgraph::parse_all_from_yaml(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::ParseError( @@ -123,8 +132,60 @@ subgraphs: subgraphs: TestSubgraph: https://subgraph.com "#; - let result = Subgraph::parse_all_from_yaml(get_document(yaml)).unwrap(); + let result = Subgraph::parse_all_from_yaml(vec![get_document(yaml)]).unwrap(); assert_eq!(result.len(), 1); assert!(result.contains_key("TestSubgraph")); } + + #[test] + fn test_parse_subgraphs_from_yaml_multiple_files() { + let yaml_one = r#" +subgraphs: + mainnet: https://api.thegraph.com/subgraphs/name/mainnet + testnet: https://api.thegraph.com/subgraphs/name/testnet +"#; + let yaml_two = r#" +subgraphs: + subgraph-one: https://api.thegraph.com/subgraphs/name/one + subgraph-two: https://api.thegraph.com/subgraphs/name/two +"#; + let subgraphs = + Subgraph::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) + .unwrap(); + + assert_eq!(subgraphs.len(), 4); + assert_eq!( + subgraphs.get("mainnet").unwrap().url, + Url::parse("https://api.thegraph.com/subgraphs/name/mainnet").unwrap() + ); + assert_eq!( + subgraphs.get("testnet").unwrap().url, + Url::parse("https://api.thegraph.com/subgraphs/name/testnet").unwrap() + ); + assert_eq!( + subgraphs.get("subgraph-one").unwrap().url, + Url::parse("https://api.thegraph.com/subgraphs/name/one").unwrap() + ); + assert_eq!( + subgraphs.get("subgraph-two").unwrap().url, + Url::parse("https://api.thegraph.com/subgraphs/name/two").unwrap() + ); + } + + #[test] + fn test_parse_subgraphs_from_yaml_duplicate_key() { + let yaml_one = r#" +subgraphs: + mainnet: https://api.thegraph.com/subgraphs/name/mainnet + testnet: https://api.thegraph.com/subgraphs/name/testnet +"#; + let yaml_two = r#" +subgraphs: + mainnet: https://api.thegraph.com/subgraphs/name/mainnet +"#; + let error = + Subgraph::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) + .unwrap_err(); + assert_eq!(error, YamlError::KeyShadowing("mainnet".to_string())); + } } diff --git a/crates/settings/src/token.rs b/crates/settings/src/token.rs index 616e05dee..14b8e3f9e 100644 --- a/crates/settings/src/token.rs +++ b/crates/settings/src/token.rs @@ -75,58 +75,65 @@ impl Token { } impl YamlParsableHash for Token { fn parse_all_from_yaml( - document: Arc>, + documents: Vec>>, ) -> Result, YamlError> { - let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - let tokens_hash = require_hash( - &document_read, - Some("tokens"), - Some("missing field: tokens".to_string()), - )?; - - tokens_hash - .into_iter() - .map(|(key_yaml, token_yaml)| { - let token_key = key_yaml.as_str().unwrap_or_default().to_string(); - - let network = Network::parse_from_yaml( - document.clone(), - &require_string( + let mut tokens = HashMap::new(); + + for document in &documents { + let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; + + if let Ok(tokens_hash) = require_hash(&document_read, Some("tokens"), None) { + for (key_yaml, token_yaml) in tokens_hash { + let token_key = key_yaml.as_str().unwrap_or_default().to_string(); + + let network = Network::parse_from_yaml( + documents.clone(), + &require_string( + token_yaml, + Some("network"), + Some(format!("network string missing in token: {token_key}")), + )?, + ) + .map_err(|_| { + ParseTokenConfigSourceError::NetworkNotFoundError(token_key.clone()) + })?; + + let address = Token::validate_address(&require_string( token_yaml, - Some("network"), - Some(format!("network string missing in token: {token_key}")), - )?, - ) - .map_err(|_| { - ParseTokenConfigSourceError::NetworkNotFoundError(token_key.clone()) - })?; - - let address = Token::validate_address(&require_string( - token_yaml, - Some("address"), - Some(format!("address string missing in token: {token_key}")), - )?)?; - - let decimals = optional_string(token_yaml, "decimals") - .map(|d| Token::validate_decimals(&d)) - .transpose()?; - - let label = optional_string(token_yaml, "label"); - let symbol = optional_string(token_yaml, "symbol"); - - let token = Token { - document: document.clone(), - key: token_key.clone(), - network: Arc::new(network), - address, - decimals, - label, - symbol, - }; - - Ok((token_key, token)) - }) - .collect() + Some("address"), + Some(format!("address string missing in token: {token_key}")), + )?)?; + + let decimals = optional_string(token_yaml, "decimals") + .map(|d| Token::validate_decimals(&d)) + .transpose()?; + + let label = optional_string(token_yaml, "label"); + let symbol = optional_string(token_yaml, "symbol"); + + let token = Token { + document: document.clone(), + key: token_key.clone(), + network: Arc::new(network), + address, + decimals, + label, + symbol, + }; + + if tokens.contains_key(&token_key) { + return Err(YamlError::KeyShadowing(token_key)); + } + tokens.insert(token_key, token); + } + } + } + + if tokens.is_empty() { + return Err(YamlError::ParseError("missing field: tokens".to_string())); + } + + Ok(tokens) } } @@ -282,18 +289,18 @@ mod tests { #[test] fn test_parse_tokens_errors() { - let error = Token::parse_all_from_yaml(get_document( + let error = Token::parse_all_from_yaml(vec![get_document( r#" test: test "#, - )) + )]) .unwrap_err(); assert_eq!( error, YamlError::ParseError("missing field: tokens".to_string()) ); - let error = Token::parse_all_from_yaml(get_document( + let error = Token::parse_all_from_yaml(vec![get_document( r#" networks: mainnet: @@ -303,14 +310,14 @@ tokens: token1: address: "0x1234567890123456789012345678901234567890" "#, - )) + )]) .unwrap_err(); assert_eq!( error, YamlError::ParseError("network string missing in token: token1".to_string()) ); - let error = Token::parse_all_from_yaml(get_document( + let error = Token::parse_all_from_yaml(vec![get_document( r#" networks: mainnet: @@ -321,7 +328,7 @@ tokens: network: "nonexistent" address: "0x1234567890123456789012345678901234567890" "#, - )) + )]) .unwrap_err(); assert_eq!( error, @@ -330,7 +337,7 @@ tokens: ) ); - let error = Token::parse_all_from_yaml(get_document( + let error = Token::parse_all_from_yaml(vec![get_document( r#" networks: mainnet: @@ -340,14 +347,14 @@ tokens: token1: network: "mainnet" "#, - )) + )]) .unwrap_err(); assert_eq!( error, YamlError::ParseError("address string missing in token: token1".to_string()) ); - let error = Token::parse_all_from_yaml(get_document( + let error = Token::parse_all_from_yaml(vec![get_document( r#" networks: mainnet: @@ -358,10 +365,10 @@ tokens: network: "mainnet" address: "not_a_valid_address" "#, - )); + )]); assert!(error.is_err()); - let error = Token::parse_all_from_yaml(get_document( + let error = Token::parse_all_from_yaml(vec![get_document( r#" networks: mainnet: @@ -373,7 +380,89 @@ tokens: address: "0x1234567890123456789012345678901234567890" decimals: "not_a_number" "#, - )); + )]); assert!(error.is_err()); } + + #[test] + fn test_parse_tokens_from_yaml_multiple_files() { + let yaml_one = r#" +networks: + mainnet: + rpc: "https://mainnet.infura.io" + chain-id: "1" +tokens: + dai: + network: mainnet + address: "0x6b175474e89094c44da98b954eedeac495271d0f" + decimals: "18" + usdc: + network: mainnet + address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + decimals: "6" +"#; + let yaml_two = r#" +tokens: + weth: + network: mainnet + address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + decimals: "18" + usdt: + network: mainnet + address: "0xdac17f958d2ee523a2206206994597c13d831ec7" + decimals: "6" +"#; + let tokens = + Token::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) + .unwrap(); + + assert_eq!(tokens.len(), 4); + assert_eq!( + tokens.get("dai").unwrap().address, + Address::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap() + ); + assert_eq!(tokens.get("dai").unwrap().decimals, Some(18)); + assert_eq!( + tokens.get("usdc").unwrap().address, + Address::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap() + ); + assert_eq!(tokens.get("usdc").unwrap().decimals, Some(6)); + assert_eq!( + tokens.get("weth").unwrap().address, + Address::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap() + ); + assert_eq!(tokens.get("weth").unwrap().decimals, Some(18)); + assert_eq!( + tokens.get("usdt").unwrap().address, + Address::from_str("0xdac17f958d2ee523a2206206994597c13d831ec7").unwrap() + ); + assert_eq!(tokens.get("usdt").unwrap().decimals, Some(6)); + } + + #[test] + fn test_parse_tokens_from_yaml_duplicate_key() { + let yaml_one = r#" +networks: + mainnet: + rpc: "https://mainnet.infura.io" + chain-id: "1" +tokens: + dai: + network: mainnet + address: "0x6b175474e89094c44da98b954eedeac495271d0f" + usdc: + network: mainnet + address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" +"#; + let yaml_two = r#" +tokens: + dai: + network: mainnet + address: "0x6b175474e89094c44da98b954eedeac495271d0f" +"#; + let error = + Token::parse_all_from_yaml(vec![get_document(yaml_one), get_document(yaml_two)]) + .unwrap_err(); + assert_eq!(error, YamlError::KeyShadowing("dai".to_string())); + } } diff --git a/crates/settings/src/yaml/dotrain.rs b/crates/settings/src/yaml/dotrain.rs index b95386aee..4350dbadc 100644 --- a/crates/settings/src/yaml/dotrain.rs +++ b/crates/settings/src/yaml/dotrain.rs @@ -4,53 +4,59 @@ use std::sync::{Arc, RwLock}; #[derive(Debug, Clone)] pub struct DotrainYaml { - pub document: Arc>, + pub documents: Vec>>, } impl YamlParsable for DotrainYaml { - fn new(source: String, validate: bool) -> Result { - let docs = StrictYamlLoader::load_from_str(&source)?; - if docs.is_empty() { - return Err(YamlError::EmptyFile); + fn new(sources: Vec, validate: bool) -> Result { + let mut documents = Vec::new(); + + for source in sources { + let docs = StrictYamlLoader::load_from_str(&source)?; + if docs.is_empty() { + return Err(YamlError::EmptyFile); + } + let doc = docs[0].clone(); + let document = Arc::new(RwLock::new(doc)); + + documents.push(document); } - let doc = docs[0].clone(); - let document = Arc::new(RwLock::new(doc)); if validate { - Order::parse_all_from_yaml(document.clone())?; + Order::parse_all_from_yaml(documents.clone())?; } - Ok(DotrainYaml { document }) + Ok(DotrainYaml { documents }) } } impl DotrainYaml { pub fn get_order_keys(&self) -> Result, YamlError> { - let orders = Order::parse_all_from_yaml(self.document.clone())?; + let orders = Order::parse_all_from_yaml(self.documents.clone())?; Ok(orders.keys().cloned().collect()) } pub fn get_order(&self, key: &str) -> Result { - Order::parse_from_yaml(self.document.clone(), key) + Order::parse_from_yaml(self.documents.clone(), key) } pub fn get_scenario_keys(&self) -> Result, YamlError> { - let scenarios = Scenario::parse_all_from_yaml(self.document.clone())?; + let scenarios = Scenario::parse_all_from_yaml(self.documents.clone())?; Ok(scenarios.keys().cloned().collect()) } pub fn get_scenario(&self, key: &str) -> Result { - Scenario::parse_from_yaml(self.document.clone(), key) + Scenario::parse_from_yaml(self.documents.clone(), key) } pub fn get_deployment_keys(&self) -> Result, YamlError> { - let deployments = Deployment::parse_all_from_yaml(self.document.clone())?; + let deployments = Deployment::parse_all_from_yaml(self.documents.clone())?; Ok(deployments.keys().cloned().collect()) } pub fn get_deployment(&self, key: &str) -> Result { - Deployment::parse_from_yaml(self.document.clone(), key) + Deployment::parse_from_yaml(self.documents.clone(), key) } pub fn get_gui(&self) -> Result, YamlError> { - Gui::parse_from_yaml_optional(self.document.clone()) + Gui::parse_from_yaml_optional(self.documents.clone()) } } @@ -133,8 +139,8 @@ mod tests { #[test] fn test_full_yaml() { - let ob_yaml = OrderbookYaml::new(FULL_YAML.to_string(), false).unwrap(); - let dotrain_yaml = DotrainYaml::new(FULL_YAML.to_string(), false).unwrap(); + let ob_yaml = OrderbookYaml::new(vec![FULL_YAML.to_string()], false).unwrap(); + let dotrain_yaml = DotrainYaml::new(vec![FULL_YAML.to_string()], false).unwrap(); assert_eq!(dotrain_yaml.get_order_keys().unwrap().len(), 1); let order = dotrain_yaml.get_order("order1").unwrap(); diff --git a/crates/settings/src/yaml/mod.rs b/crates/settings/src/yaml/mod.rs index aa01e7a0f..b082b0a57 100644 --- a/crates/settings/src/yaml/mod.rs +++ b/crates/settings/src/yaml/mod.rs @@ -19,7 +19,7 @@ use thiserror::Error; use url::ParseError as UrlParseError; pub trait YamlParsable: Sized { - fn new(source: String, validate: bool) -> Result; + fn new(sources: Vec, validate: bool) -> Result; fn get_yaml_string(document: Arc>) -> Result { let document = document.read().unwrap(); @@ -32,11 +32,14 @@ pub trait YamlParsable: Sized { pub trait YamlParsableHash: Sized + Clone { fn parse_all_from_yaml( - document: Arc>, + documents: Vec>>, ) -> Result, YamlError>; - fn parse_from_yaml(document: Arc>, key: &str) -> Result { - let all = Self::parse_all_from_yaml(document)?; + fn parse_from_yaml( + documents: Vec>>, + key: &str, + ) -> Result { + let all = Self::parse_all_from_yaml(documents)?; all.get(key) .ok_or_else(|| YamlError::KeyNotFound(key.to_string())) .cloned() @@ -56,10 +59,10 @@ pub trait YamlParsableString { } pub trait YamlParseableValue: Sized { - fn parse_from_yaml(document: Arc>) -> Result; + fn parse_from_yaml(documents: Vec>>) -> Result; fn parse_from_yaml_optional( - document: Arc>, + documents: Vec>>, ) -> Result, YamlError>; } @@ -93,6 +96,8 @@ pub enum YamlError { WriteLockError, #[error("Invalid trait function")] InvalidTraitFunction, + #[error("Key shadowing found: {0}")] + KeyShadowing(String), #[error(transparent)] ParseNetworkConfigSourceError(#[from] ParseNetworkConfigSourceError), #[error(transparent)] @@ -119,6 +124,7 @@ impl PartialEq for YamlError { (Self::ConvertError, Self::ConvertError) => true, (Self::ReadLockError, Self::ReadLockError) => true, (Self::WriteLockError, Self::WriteLockError) => true, + (Self::KeyShadowing(a), Self::KeyShadowing(b)) => a == b, (Self::ParseNetworkConfigSourceError(a), Self::ParseNetworkConfigSourceError(b)) => { a == b } diff --git a/crates/settings/src/yaml/orderbook.rs b/crates/settings/src/yaml/orderbook.rs index 4ae39a6fd..a5e8aa0a8 100644 --- a/crates/settings/src/yaml/orderbook.rs +++ b/crates/settings/src/yaml/orderbook.rs @@ -6,76 +6,83 @@ use std::sync::{Arc, RwLock}; #[derive(Debug, Clone)] pub struct OrderbookYaml { - pub document: Arc>, + pub documents: Vec>>, } impl YamlParsable for OrderbookYaml { - fn new(source: String, validate: bool) -> Result { - let docs = StrictYamlLoader::load_from_str(&source)?; - if docs.is_empty() { - return Err(YamlError::EmptyFile); + fn new(sources: Vec, validate: bool) -> Result { + let mut documents = Vec::new(); + + for source in sources { + let docs = StrictYamlLoader::load_from_str(&source)?; + if docs.is_empty() { + return Err(YamlError::EmptyFile); + } + let doc = docs[0].clone(); + let document = Arc::new(RwLock::new(doc)); + + documents.push(document); } - let doc = docs[0].clone(); - let document = Arc::new(RwLock::new(doc)); if validate { - Network::parse_all_from_yaml(document.clone())?; + Network::parse_all_from_yaml(documents.clone())?; } - Ok(OrderbookYaml { document }) + + Ok(OrderbookYaml { documents }) } } impl OrderbookYaml { pub fn get_network_keys(&self) -> Result, YamlError> { - let networks = Network::parse_all_from_yaml(self.document.clone())?; + let networks = Network::parse_all_from_yaml(self.documents.clone())?; Ok(networks.keys().cloned().collect()) } pub fn get_network(&self, key: &str) -> Result { - Network::parse_from_yaml(self.document.clone(), key) + Network::parse_from_yaml(self.documents.clone(), key) } pub fn get_token_keys(&self) -> Result, YamlError> { - let tokens = Token::parse_all_from_yaml(self.document.clone())?; + let tokens = Token::parse_all_from_yaml(self.documents.clone())?; Ok(tokens.keys().cloned().collect()) } pub fn get_token(&self, key: &str) -> Result { - Token::parse_from_yaml(self.document.clone(), key) + Token::parse_from_yaml(self.documents.clone(), key) } pub fn get_subgraph_keys(&self) -> Result, YamlError> { - let subgraphs = Subgraph::parse_all_from_yaml(self.document.clone())?; + let subgraphs = Subgraph::parse_all_from_yaml(self.documents.clone())?; Ok(subgraphs.keys().cloned().collect()) } pub fn get_subgraph(&self, key: &str) -> Result { - Subgraph::parse_from_yaml(self.document.clone(), key) + Subgraph::parse_from_yaml(self.documents.clone(), key) } pub fn get_orderbook_keys(&self) -> Result, YamlError> { - let orderbooks = Orderbook::parse_all_from_yaml(self.document.clone())?; + let orderbooks = Orderbook::parse_all_from_yaml(self.documents.clone())?; Ok(orderbooks.keys().cloned().collect()) } pub fn get_orderbook(&self, key: &str) -> Result { - Orderbook::parse_from_yaml(self.document.clone(), key) + Orderbook::parse_from_yaml(self.documents.clone(), key) } pub fn get_metaboard_keys(&self) -> Result, YamlError> { - let metaboards = Metaboard::parse_all_from_yaml(self.document.clone())?; + let metaboards = Metaboard::parse_all_from_yaml(self.documents.clone())?; Ok(metaboards.keys().cloned().collect()) } pub fn get_metaboard(&self, key: &str) -> Result { - Metaboard::parse_from_yaml(self.document.clone(), key) + Metaboard::parse_from_yaml(self.documents.clone(), key) } pub fn get_deployer_keys(&self) -> Result, YamlError> { - let deployers = Deployer::parse_all_from_yaml(self.document.clone())?; + let deployers = Deployer::parse_all_from_yaml(self.documents.clone())?; Ok(deployers.keys().cloned().collect()) } pub fn get_deployer(&self, key: &str) -> Result { - Deployer::parse_from_yaml(self.document.clone(), key) + Deployer::parse_from_yaml(self.documents.clone(), key) } pub fn get_sentry(&self) -> Result { - let value = Sentry::parse_from_yaml_optional(self.document.clone())?; + let value = Sentry::parse_from_yaml_optional(self.documents[0].clone())?; Ok(value.map_or(false, |v| v == "true")) } } @@ -147,7 +154,7 @@ mod tests { #[test] fn test_full_yaml() { - let ob_yaml = OrderbookYaml::new(FULL_YAML.to_string(), false).unwrap(); + let ob_yaml = OrderbookYaml::new(vec![FULL_YAML.to_string()], false).unwrap(); assert_eq!(ob_yaml.get_network_keys().unwrap().len(), 1); let network = ob_yaml.get_network("mainnet").unwrap(); @@ -210,7 +217,7 @@ mod tests { #[test] fn test_update_network_rpc() { - let ob_yaml = OrderbookYaml::new(FULL_YAML.to_string(), false).unwrap(); + let ob_yaml = OrderbookYaml::new(vec![FULL_YAML.to_string()], false).unwrap(); let mut network = ob_yaml.get_network("mainnet").unwrap(); assert_eq!( @@ -235,7 +242,7 @@ mod tests { #[test] fn test_update_token_address() { - let ob_yaml = OrderbookYaml::new(FULL_YAML.to_string(), false).unwrap(); + let ob_yaml = OrderbookYaml::new(vec![FULL_YAML.to_string()], false).unwrap(); let mut token = ob_yaml.get_token("token1").unwrap(); assert_eq!(