diff --git a/crates/settings/src/config.rs b/crates/settings/src/config.rs index b8f25a010..a5340e6fc 100644 --- a/crates/settings/src/config.rs +++ b/crates/settings/src/config.rs @@ -2,8 +2,10 @@ use super::config_source::ConfigSourceError; use crate::*; use alloy::primitives::U256; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::sync::Arc; +use std::{collections::HashMap, sync::RwLock}; +use strict_yaml_rust::StrictYaml; +use subgraph::Subgraph; use thiserror::Error; use typeshare::typeshare; use url::Url; @@ -45,7 +47,6 @@ pub struct Config { #[cfg(target_family = "wasm")] impl_all_wasm_traits!(Config); -pub type Subgraph = Url; pub type Metaboard = Url; pub type Vault = U256; @@ -95,7 +96,16 @@ impl TryFrom for Config { let subgraphs = item .subgraphs .into_iter() - .map(|(name, subgraph)| Ok((name, Arc::new(subgraph)))) + .map(|(name, subgraph)| { + Ok(( + name.clone(), + Arc::new(Subgraph { + document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), + key: name.clone(), + url: subgraph.clone(), + }), + )) + }) .collect::>, ParseConfigSourceError>>()?; let metaboards = item @@ -343,7 +353,7 @@ mod tests { // Verify subgraphs assert_eq!(config.subgraphs.len(), 1); let mainnet_subgraph = config.subgraphs.get("mainnet").unwrap(); - assert_eq!(mainnet_subgraph.as_str(), "https://mainnet.subgraph/"); + assert_eq!(mainnet_subgraph.url.as_str(), "https://mainnet.subgraph/"); // Verify orderbooks assert_eq!(config.orderbooks.len(), 1); diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index c82ca0edb..61d78260a 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -6,6 +6,7 @@ pub mod deployer; pub mod deployment; pub mod gui; pub mod merge; +pub mod metaboard; pub mod network; pub mod order; pub mod orderbook; diff --git a/crates/settings/src/metaboard.rs b/crates/settings/src/metaboard.rs new file mode 100644 index 000000000..80c9c1f25 --- /dev/null +++ b/crates/settings/src/metaboard.rs @@ -0,0 +1,130 @@ +use crate::yaml::{require_hash, require_string, YamlError, YamlParsableHash}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, +}; +use strict_yaml_rust::StrictYaml; +use typeshare::typeshare; +use url::{ParseError, Url}; + +#[typeshare] +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "kebab-case")] +#[serde(default)] +pub struct Metaboard { + #[serde(skip)] + pub document: Arc>, + pub key: String, + #[typeshare(typescript(type = "string"))] + pub url: Url, +} + +impl Metaboard { + pub fn validate_url(value: &str) -> Result { + Url::parse(value) + } +} + +impl YamlParsableHash for Metaboard { + fn parse_all_from_yaml( + document: Arc>, + ) -> 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() + } +} + +impl Default for Metaboard { + fn default() -> Self { + Self { + document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), + key: "".to_string(), + url: Url::parse("https://metaboard.com").unwrap(), + } + } +} + +impl PartialEq for Metaboard { + fn eq(&self, other: &Self) -> bool { + self.key == other.key && self.url == other.url + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::yaml::tests::get_document; + + #[test] + fn test_parse_metaboards_from_yaml() { + let yaml = r#" +test: test +"#; + 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#" +metaboards: + TestMetaboard: + test: https://metaboard.com +"#; + let error = Metaboard::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + assert_eq!( + error, + YamlError::ParseError( + "metaboard value must be a string for key: TestMetaboard".to_string() + ) + ); + + let yaml = r#" +metaboards: + TestMetaboard: + - https://metaboard.com +"#; + let error = Metaboard::parse_all_from_yaml(get_document(yaml)).unwrap_err(); + assert_eq!( + error, + YamlError::ParseError( + "metaboard value must be a string for key: TestMetaboard".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/orderbook.rs b/crates/settings/src/orderbook.rs index b63b95329..ec6a838c9 100644 --- a/crates/settings/src/orderbook.rs +++ b/crates/settings/src/orderbook.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use std::str::FromStr; use std::sync::{Arc, RwLock}; use strict_yaml_rust::StrictYaml; +use subgraph::Subgraph; use thiserror::Error; use typeshare::typeshare; use yaml::{optional_string, require_hash, require_string, YamlError, YamlParsableHash}; @@ -67,7 +68,8 @@ impl YamlParsableHash for Orderbook { Some(subgraph_name) => subgraph_name, None => orderbook_key.clone(), }; - let subgraph = Subgraph::parse_from_yaml(document.clone(), &subgraph_name)?; + let subgraph = + Arc::new(Subgraph::parse_from_yaml(document.clone(), &subgraph_name)?); let label = optional_string(orderbook_yaml, "label"); @@ -76,7 +78,7 @@ impl YamlParsableHash for Orderbook { key: orderbook_key.clone(), address, network: Arc::new(network), - subgraph: Arc::new(subgraph), + subgraph, label, }; @@ -93,7 +95,7 @@ impl Default for Orderbook { key: "".to_string(), address: Address::ZERO, network: Arc::new(Network::default()), - subgraph: Arc::new(Subgraph::parse("https://subgraph.com").unwrap()), + subgraph: Arc::new(Subgraph::default()), label: None, } } diff --git a/crates/settings/src/subgraph.rs b/crates/settings/src/subgraph.rs index c7f82f1e0..a3f4b9822 100644 --- a/crates/settings/src/subgraph.rs +++ b/crates/settings/src/subgraph.rs @@ -1,16 +1,35 @@ -use crate::config::Subgraph; use crate::yaml::{require_hash, require_string, YamlError, YamlParsableHash}; +use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, sync::{Arc, RwLock}, }; use strict_yaml_rust::StrictYaml; -use url::Url; +use typeshare::typeshare; +use url::{ParseError, Url}; + +#[typeshare] +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "kebab-case")] +#[serde(default)] +pub struct Subgraph { + #[serde(skip)] + pub document: Arc>, + pub key: String, + #[typeshare(typescript(type = "string"))] + pub url: Url, +} + +impl Subgraph { + pub fn validate_url(value: &str) -> Result { + Url::parse(value) + } +} impl YamlParsableHash for Subgraph { fn parse_all_from_yaml( document: Arc>, - ) -> Result, YamlError> { + ) -> Result, YamlError> { let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; let subgraphs_hash = require_hash( &document_read, @@ -23,7 +42,7 @@ impl YamlParsableHash for Subgraph { .map(|(key_yaml, subgraph_yaml)| { let subgraph_key = key_yaml.as_str().unwrap_or_default().to_string(); - let url = Url::parse(&require_string( + let url = Subgraph::validate_url(&require_string( subgraph_yaml, None, Some(format!( @@ -31,12 +50,34 @@ impl YamlParsableHash for Subgraph { )), )?)?; - Ok((subgraph_key, url)) + let subgraph = Subgraph { + document: document.clone(), + key: subgraph_key.clone(), + url, + }; + + Ok((subgraph_key, subgraph)) }) .collect() } } +impl Default for Subgraph { + fn default() -> Self { + Self { + document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), + key: "".to_string(), + url: Url::parse("https://subgraph.com").unwrap(), + } + } +} + +impl PartialEq for Subgraph { + fn eq(&self, other: &Self) -> bool { + self.key == other.key && self.url == other.url + } +} + #[cfg(test)] mod test { use super::*; @@ -78,5 +119,13 @@ subgraphs: "subgraph value must be a string for key: TestSubgraph".to_string() ) ); + + let yaml = r#" +subgraphs: + TestSubgraph: https://subgraph.com +"#; + let result = Subgraph::parse_all_from_yaml(get_document(yaml)).unwrap(); + assert_eq!(result.len(), 1); + assert!(result.contains_key("TestSubgraph")); } } diff --git a/crates/settings/src/test.rs b/crates/settings/src/test.rs index 232c95c8f..6795d9bbb 100644 --- a/crates/settings/src/test.rs +++ b/crates/settings/src/test.rs @@ -2,6 +2,7 @@ use crate::*; use alloy::primitives::Address; use std::sync::{Arc, RwLock}; use strict_yaml_rust::StrictYaml; +use subgraph::Subgraph; // Helper function to create a mock network pub fn mock_network() -> Arc { @@ -32,7 +33,11 @@ pub fn mock_orderbook() -> Arc { key: "".to_string(), label: Some("Orderbook1".into()), address: Address::repeat_byte(0x04), - subgraph: Arc::new("https://subgraph.com".parse().unwrap()), + subgraph: Arc::new(Subgraph { + document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), + key: "".to_string(), + url: "https://subgraph.com".parse().unwrap(), + }), network: mock_network(), }) } @@ -77,5 +82,9 @@ pub fn mock_plot(name: &str) -> (String, Plot) { } pub fn mock_subgraph() -> Arc { - Arc::new("http://subgraph.com".parse().unwrap()) + Arc::new(Subgraph { + document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))), + key: "".to_string(), + url: "https://subgraph.com".parse().unwrap(), + }) } diff --git a/crates/settings/src/yaml/orderbook.rs b/crates/settings/src/yaml/orderbook.rs index 477f7e77c..34c83ada7 100644 --- a/crates/settings/src/yaml/orderbook.rs +++ b/crates/settings/src/yaml/orderbook.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{Network, Orderbook, Subgraph, Token}; +use crate::{metaboard::Metaboard, subgraph::Subgraph, Network, Orderbook, Token}; use std::sync::{Arc, RwLock}; use strict_yaml_rust::StrictYamlEmitter; @@ -62,17 +62,23 @@ impl OrderbookYaml { pub fn get_orderbook(&self, key: &str) -> Result { Orderbook::parse_from_yaml(self.document.clone(), key) } + + pub fn get_metaboard_keys(&self) -> Result, YamlError> { + let metaboards = Metaboard::parse_all_from_yaml(self.document.clone())?; + Ok(metaboards.keys().cloned().collect()) + } + pub fn get_metaboard(&self, key: &str) -> Result { + Metaboard::parse_from_yaml(self.document.clone(), key) + } } #[cfg(test)] mod tests { - use std::str::FromStr; - + use super::*; use alloy::primitives::Address; + use std::str::FromStr; use url::Url; - use super::*; - const FULL_YAML: &str = r#" networks: mainnet: @@ -160,7 +166,7 @@ mod tests { assert_eq!(ob_yaml.get_subgraph_keys().unwrap().len(), 2); let subgraph = ob_yaml.get_subgraph("mainnet").unwrap(); assert_eq!( - subgraph, + subgraph.url, Url::parse("https://api.thegraph.com/subgraphs/name/xyz").unwrap() ); @@ -173,6 +179,16 @@ mod tests { assert_eq!(orderbook.network, network.into()); assert_eq!(orderbook.subgraph, subgraph.into()); assert_eq!(orderbook.label, Some("Primary Orderbook".to_string())); + + assert_eq!(ob_yaml.get_metaboard_keys().unwrap().len(), 2); + assert_eq!( + ob_yaml.get_metaboard("board1").unwrap().url, + Url::parse("https://meta.example.com/board1").unwrap() + ); + assert_eq!( + ob_yaml.get_metaboard("board2").unwrap().url, + Url::parse("https://meta.example.com/board2").unwrap() + ); } #[test] diff --git a/packages/orderbook/test/js_api/gui.test.ts b/packages/orderbook/test/js_api/gui.test.ts index 52a08ef3f..4ad4a8efc 100644 --- a/packages/orderbook/test/js_api/gui.test.ts +++ b/packages/orderbook/test/js_api/gui.test.ts @@ -592,7 +592,7 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () describe('state management tests', async () => { let serializedState = - 'H4sIAAAAAAAA_3WNSQoCUQxEu1VEb-FaUPLHJDuP4BX-kC-N0IL2wuMrmHYhWJuXoag6dR9x4ALZsmuEJgZbqFJsXohLEQOVs22QRJBd8uJtxrdJCiasGMjRQnO2yjyMdRgvB9PrAfqNTue7PGTamf38eRrrfIhIDCmXKu3f_htuu1lLpQGYC9fK6XaV0XydK2WAY3wBw3-Y7v0AAAA='; + 'H4sIAAAAAAAA_3WNTQrCMBCFWxXRW7gWlJlMkk52HsErmGRGilBBu_D4Ik5dCH2bb37fOzVfaVHJWn1i7lg4FMKioB5B2JGmqEgZOPnQxQwukwQPSgE-b4nqwny2xtwPtR-uB2xtAO3GqvNDnjLucD9tXujIh9hxgksuVXSu_zd3zaSlEQGmwLVxvN9kwN_lyhjgGN-eYsVu_QAAAA=='; let gui: DotrainOrderGui; beforeAll(async () => { mockServer