diff --git a/.gitignore b/.gitignore index 46221986..70938ee1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ qr-code.svg /target /venv +.pre-commit-config.yaml diff --git a/Cargo.lock b/Cargo.lock index 1e18b9d2..357eadfe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,7 +478,7 @@ dependencies = [ "chain-vote", "color-eyre", "csv", - "env_logger 0.9.0", + "fraction", "futures", "gag", "governor", @@ -491,7 +491,6 @@ dependencies = [ "jormungandr-integration-tests", "jormungandr-lib", "jortestkit", - "log", "once_cell", "predicates 1.0.8", "proptest 1.0.0 (git+https://github.com/input-output-hk/proptest?branch=master)", @@ -517,6 +516,8 @@ dependencies = [ "thor", "time 0.3.11", "tokio", + "tracing", + "tracing-subscriber", "url", "versionisator", "vit-servicing-station-lib", @@ -1426,19 +1427,6 @@ dependencies = [ "regex", ] -[[package]] -name = "env_logger" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - [[package]] name = "environmental" version = "1.1.3" @@ -4081,7 +4069,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" dependencies = [ - "env_logger 0.7.1", + "env_logger", "log", "rand 0.7.3", "rand_core 0.5.1", diff --git a/catalyst-toolbox/Cargo.toml b/catalyst-toolbox/Cargo.toml index d9ce8c5e..144fbfa0 100644 --- a/catalyst-toolbox/Cargo.toml +++ b/catalyst-toolbox/Cargo.toml @@ -37,7 +37,6 @@ rayon = "1.5" rust_decimal = "1.16" rust_decimal_macros = "1" futures = "0.3" -log = "0.4" once_cell = "1.8" reqwest = { version = "0.11", features = ["blocking", "json"] } rand = "0.8.3" @@ -61,8 +60,10 @@ symmetric-cipher = { git = "https://github.com/input-output-hk/chain-wallet-libs graphql_client = { version = "0.10" } gag = "1" vit-servicing-station-lib = { git = "https://github.com/input-output-hk/vit-servicing-station.git", branch = "master" } -env_logger = "0.9" snapshot-lib = { path = "../snapshot-lib" } +fraction = "0.10" +tracing = "0.1" +tracing-subscriber = "0.3" [dev-dependencies] rand_chacha = "0.3" diff --git a/catalyst-toolbox/src/bin/catalyst-toolbox.rs b/catalyst-toolbox/src/bin/catalyst-toolbox.rs index eb7a124e..3e6c4765 100644 --- a/catalyst-toolbox/src/bin/catalyst-toolbox.rs +++ b/catalyst-toolbox/src/bin/catalyst-toolbox.rs @@ -3,7 +3,7 @@ use structopt::StructOpt; pub mod cli; fn main() -> color_eyre::Result<()> { - env_logger::try_init()?; + tracing_subscriber::fmt().init(); color_eyre::install()?; cli::Cli::from_args().exec()?; Ok(()) diff --git a/catalyst-toolbox/src/bin/cli/recovery/votes.rs b/catalyst-toolbox/src/bin/cli/recovery/votes.rs index 5dcf6065..2e0482aa 100644 --- a/catalyst-toolbox/src/bin/cli/recovery/votes.rs +++ b/catalyst-toolbox/src/bin/cli/recovery/votes.rs @@ -81,7 +81,7 @@ fn group_by_voter)>>( .or_insert_with(Vec::new) .push(vote_cast); } - Err(e) => log::error!("Invalid transaction: {}", e), + Err(e) => tracing::error!("Invalid transaction: {}", e), } } } diff --git a/catalyst-toolbox/src/http/mod.rs b/catalyst-toolbox/src/http/mod.rs index f078fc6a..a549b054 100644 --- a/catalyst-toolbox/src/http/mod.rs +++ b/catalyst-toolbox/src/http/mod.rs @@ -2,8 +2,8 @@ use std::marker::PhantomData; use ::reqwest::{blocking::Response, StatusCode}; use color_eyre::eyre::Result; -use log::warn; use serde::Deserialize; +use tracing::warn; use self::{rate_limit::RateLimitClient, reqwest::ReqwestClient}; diff --git a/catalyst-toolbox/src/http/rate_limit.rs b/catalyst-toolbox/src/http/rate_limit.rs index 1be7e52d..8772a5f6 100644 --- a/catalyst-toolbox/src/http/rate_limit.rs +++ b/catalyst-toolbox/src/http/rate_limit.rs @@ -7,11 +7,12 @@ use governor::{ }; use color_eyre::Report; -use log::debug; use serde::Deserialize; +use tracing::debug; use super::{HttpClient, HttpResponse}; +#[derive(Debug)] pub struct RateLimitClient { inner: T, limiter: Option>, diff --git a/catalyst-toolbox/src/http/reqwest.rs b/catalyst-toolbox/src/http/reqwest.rs index f6a3c292..1fd883ed 100644 --- a/catalyst-toolbox/src/http/reqwest.rs +++ b/catalyst-toolbox/src/http/reqwest.rs @@ -10,6 +10,7 @@ use super::{HttpClient, HttpResponse}; const BASE_IDEASCALE_URL: &str = "https://cardano.ideascale.com/a/rest/v1/"; +#[derive(Debug)] pub struct ReqwestClient { client: Client, base_url: Url, diff --git a/catalyst-toolbox/src/ideascale/fetch.rs b/catalyst-toolbox/src/ideascale/fetch.rs index 783f6ebf..cf4bd2fb 100644 --- a/catalyst-toolbox/src/ideascale/fetch.rs +++ b/catalyst-toolbox/src/ideascale/fetch.rs @@ -9,6 +9,7 @@ pub type Scores = HashMap; pub type Sponsors = HashMap; pub fn get_funds_data(client: &impl HttpClient) -> Result, Report> { + info!("getting funds"); client.get("campaigns/groups")?.json() } @@ -25,10 +26,12 @@ pub fn get_proposals_data( client: &impl HttpClient, challenge_id: u32, ) -> Result, Report> { + info!("getting proposal data"); let path = &format!("campaigns/{}/ideas/0/100000", challenge_id); client.get(path)?.json() } pub fn get_funnels_data_for_fund(client: &impl HttpClient) -> Result, Report> { + info!("getting funnels"); client.get("funnels")?.json() } diff --git a/catalyst-toolbox/src/ideascale/mod.rs b/catalyst-toolbox/src/ideascale/mod.rs index b91edbe9..4e282274 100644 --- a/catalyst-toolbox/src/ideascale/mod.rs +++ b/catalyst-toolbox/src/ideascale/mod.rs @@ -79,7 +79,7 @@ pub fn fetch_all( // TODO: Handle error better here .flat_map(Result::unwrap) // filter out non approved or staged proposals - .filter(|p| p.approved && filter_proposal_by_stage_type(&p.stage_type, &matches)) + .filter(|p| p.approved.as_bool() && filter_proposal_by_stage_type(&p.stage_type, &matches)) .filter(|p| !excluded_proposals.contains(&p.proposal_id)) .collect(); @@ -139,7 +139,7 @@ pub fn build_challenges( id: i.to_string(), rewards_total: c.rewards.to_string(), proposers_rewards: c.rewards.to_string(), - title: c.title.clone(), + title: c.title.as_str().to_string(), highlight: sponsors.get(&c.challenge_url).map(|sponsor| { models::se::Highlight { sponsor: sponsor.clone(), diff --git a/catalyst-toolbox/src/ideascale/models/de.rs b/catalyst-toolbox/src/ideascale/models/de.rs deleted file mode 100644 index 521f818b..00000000 --- a/catalyst-toolbox/src/ideascale/models/de.rs +++ /dev/null @@ -1,188 +0,0 @@ -use serde::de::Error; -use serde::{Deserialize, Deserializer}; -use std::fmt::{Display, Formatter}; - -#[derive(Debug, Deserialize, Clone, Ord, PartialOrd, Eq, PartialEq)] -pub struct AdaRewards(#[serde(deserialize_with = "deserialize_rewards")] u64); - -#[derive(Debug, Deserialize, Clone)] -pub struct Challenge { - pub id: u32, - #[serde(alias = "name", deserialize_with = "deserialize_clean_challenge_title")] - pub title: String, - #[serde(alias = "tagline")] - pub rewards: AdaRewards, - pub description: CleanString, - #[serde(alias = "groupId")] - pub fund_id: u32, - #[serde(alias = "funnelId")] - pub funnel_id: u32, - #[serde(alias = "campaignUrl")] - pub challenge_url: String, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Funnel { - pub id: u32, - #[serde(alias = "name")] - pub title: CleanString, - pub description: CleanString, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Fund { - pub id: u32, - pub name: CleanString, - #[serde(alias = "campaigns")] - pub challenges: Vec, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Proposal { - #[serde(alias = "id")] - pub proposal_id: u32, - pub proposal_category: Option, - #[serde(alias = "title")] - pub proposal_title: CleanString, - #[serde(alias = "text")] - pub proposal_summary: CleanString, - - #[serde(alias = "url")] - pub proposal_url: String, - #[serde(default)] - pub proposal_files_url: String, - - #[serde(alias = "customFieldsByKey")] - pub custom_fields: ProposalCustomFieldsByKey, - - #[serde(alias = "authorInfo")] - pub proposer: Proposer, - - #[serde(alias = "stageId")] - pub stage_id: u32, - - #[serde(alias = "stageLabel")] - pub stage_type: String, - - #[serde(alias = "campaignId")] - pub challenge_id: u32, - - #[serde(alias = "flag", deserialize_with = "deserialize_approved")] - pub approved: bool, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Proposer { - pub name: String, - #[serde(alias = "email")] - pub contact: String, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct ProposalCustomFieldsByKey { - #[serde(flatten)] - pub fields: serde_json::Value, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct Stage { - #[serde(default)] - pub label: String, - #[serde(alias = "funnelId", default)] - pub funnel_id: u32, - #[serde(alias = "assessmentId", default)] - pub assessment_id: u32, -} - -#[derive(Debug, Deserialize, Clone)] -pub struct CleanString(#[serde(deserialize_with = "deserialize_clean_string")] String); - -impl Funnel { - pub fn is_community(&self) -> bool { - self.title.as_ref().contains("Community Setting") - } -} - -impl From for AdaRewards { - fn from(v: u64) -> Self { - Self(v) - } -} - -impl ToString for CleanString { - fn to_string(&self) -> String { - self.0.clone() - } -} - -impl AsRef for CleanString { - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl From for u64 { - fn from(rewards: AdaRewards) -> Self { - rewards.0 - } -} - -impl Display for AdaRewards { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -fn deserialize_approved<'de, D: Deserializer<'de>>(deserializer: D) -> Result { - let approved = String::deserialize(deserializer)?; - Ok(matches!(approved.as_str(), "approved")) -} - -pub fn clean_str(s: &str) -> String { - let mut result = s.to_string(); - result.retain(|c| !matches!(c, '*' | '-' | '/')); - result -} - -fn deserialize_clean_string<'de, D: Deserializer<'de>>( - deserializer: D, -) -> Result { - let rewards_str = String::deserialize(deserializer)?; - Ok(clean_str(&rewards_str)) -} - -fn deserialize_clean_challenge_title<'de, D: Deserializer<'de>>( - deserializer: D, -) -> Result { - let mut rewards_str = String::deserialize(deserializer)?; - // Remove leading `FX: ` - if rewards_str.starts_with('F') { - if let Some(first_space) = rewards_str.find(' ') { - let (_, content) = rewards_str.split_at(first_space + 1); - rewards_str = content.to_string(); - } - } - Ok(rewards_str) -} - -fn deserialize_rewards<'de, D: Deserializer<'de>>(deserializer: D) -> Result { - let rewards_str = String::deserialize(deserializer)?; - - // input is not standarized, hack an early return if it is just 0 ada - if rewards_str.starts_with("0 ada") { - return Ok(0); - } - sscanf::scanf!(rewards_str.trim_end(), "${} in {}", String, String) - // trim all . or , in between numbers - .map(|(mut amount, _currency)| { - amount.retain(|c: char| c.is_numeric() && !(matches!(c, '.') || matches!(c, ','))); - amount - }) - .and_then(|s| s.parse().ok()) - .ok_or_else(|| { - D::Error::custom(&format!( - "Unable to read malformed value: '{}'", - rewards_str - )) - }) -} diff --git a/catalyst-toolbox/src/ideascale/models/de/ada_rewards.rs b/catalyst-toolbox/src/ideascale/models/de/ada_rewards.rs new file mode 100644 index 00000000..a7c6cf67 --- /dev/null +++ b/catalyst-toolbox/src/ideascale/models/de/ada_rewards.rs @@ -0,0 +1,104 @@ +use std::{ + fmt::{Display, Formatter}, + num::ParseIntError, +}; + +use once_cell::sync::Lazy; +use regex::Regex; +use serde::{ + de::{Error, Visitor}, + Deserialize, Deserializer, +}; + +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub struct AdaRewards(pub u64); + +impl<'de> Deserialize<'de> for AdaRewards { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let num = deserializer.deserialize_str(V)?; + Ok(Self(num)) + } +} + +static REGEX: Lazy = Lazy::new(|| Regex::new(r#"\$([0-9]+) in ada"#).unwrap()); + +struct V; + +impl<'a> Visitor<'a> for V { + type Value = u64; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str( + "a string of the form: `$N in ada`, where `N` is a u64 (e.g. \"$123 in ada\")", + ) + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + // input is not standarized, hack an early return if it is just 0 ada + if v.starts_with("0 ada") { + return Ok(0); + } + + let bad_pattern = || E::custom("didn't match `$N in ada` pattern"); + let bad_u64 = |e: ParseIntError| E::custom(format!("unvalid u64: {e}")); + + // ignore the first capture, since this is the whole string + let capture = REGEX.captures_iter(v).next().ok_or_else(bad_pattern)?; + let value = capture.get(1).ok_or_else(bad_pattern)?; + value.as_str().parse().map_err(bad_u64) + } +} + +impl From for AdaRewards { + fn from(v: u64) -> Self { + Self(v) + } +} + +impl From for u64 { + fn from(rewards: AdaRewards) -> Self { + rewards.0 + } +} + +impl Display for AdaRewards { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "${} in ada", self.0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn parse(s: &str) -> Result { + let s = format!(r#""{s}""#); + serde_json::from_str(&s) + } + + #[test] + fn can_parse_good_values() { + assert_eq!(parse("$123 in ada").unwrap(), AdaRewards(123)); + assert_eq!(parse("0 ada").unwrap(), AdaRewards(0)); + assert_eq!( + parse("0 ada with some stuff at the end").unwrap(), + AdaRewards(0) + ); + } + + #[test] + fn fails_to_parse_bad_values() { + // missing dollar sign + assert!(parse("123 in ada").is_err()); + // negative number + assert!(parse("$-123 in ada").is_err()); + // fraction + assert!(parse("$123.0 in ada").is_err()); + } +} diff --git a/catalyst-toolbox/src/ideascale/models/de/approval.rs b/catalyst-toolbox/src/ideascale/models/de/approval.rs new file mode 100644 index 00000000..87c92291 --- /dev/null +++ b/catalyst-toolbox/src/ideascale/models/de/approval.rs @@ -0,0 +1,60 @@ +use serde::{de::Visitor, Deserialize, Deserializer}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Approval { + Approved, + NotApproved, +} + +impl Approval { + pub fn as_bool(&self) -> bool { + (*self).into() + } +} + +impl From for Approval { + fn from(b: bool) -> Self { + match b { + true => Approval::Approved, + false => Approval::NotApproved, + } + } +} + +impl From for bool { + fn from(b: Approval) -> Self { + match b { + Approval::Approved => true, + Approval::NotApproved => false, + } + } +} + +impl<'de> Deserialize<'de> for Approval { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(V) + } +} + +struct V; + +impl Visitor<'_> for V { + type Value = Approval; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a case-insensitive string representing an approval status (with this string \"approved\" meaning `Approved`, and all other strings being `NotApproved`)") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + match v.to_lowercase().as_ref() { + "approved" => Ok(Approval::Approved), + _ => Ok(Approval::NotApproved), + } + } +} diff --git a/catalyst-toolbox/src/ideascale/models/de/challenge_title.rs b/catalyst-toolbox/src/ideascale/models/de/challenge_title.rs new file mode 100644 index 00000000..b30be218 --- /dev/null +++ b/catalyst-toolbox/src/ideascale/models/de/challenge_title.rs @@ -0,0 +1,68 @@ +use serde::{de::Visitor, Deserialize}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ChallengeTitle(String); + +impl<'de> Deserialize<'de> for ChallengeTitle { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_str(V) + } +} + +struct V; + +impl Visitor<'_> for V { + type Value = ChallengeTitle; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str( + "a string representing the title of a challenge (ignoring any leading `FX: `)", + ) + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Ok(ChallengeTitle::new(v)) + } +} + +impl ChallengeTitle { + pub fn new(s: &str) -> Self { + let s = s.trim_start_matches("FX: "); + Self(s.to_string()) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl From for String { + fn from(ChallengeTitle(inner): ChallengeTitle) -> Self { + inner + } +} + +#[cfg(test)] +mod tests { + use serde_json::json; + + use super::*; + + fn parse(s: &str) -> ChallengeTitle { + let json = json!(s); + serde_json::from_value(json).unwrap() + } + + #[test] + fn strips_leading_fx() { + assert_eq!(parse("hello"), ChallengeTitle("hello".into())); + assert_eq!(parse("FX: hello"), ChallengeTitle("hello".into())); + assert_eq!(parse("FX:hello"), ChallengeTitle("FX:hello".into())); + } +} diff --git a/catalyst-toolbox/src/ideascale/models/de/clean_string.rs b/catalyst-toolbox/src/ideascale/models/de/clean_string.rs new file mode 100644 index 00000000..07af2978 --- /dev/null +++ b/catalyst-toolbox/src/ideascale/models/de/clean_string.rs @@ -0,0 +1,110 @@ +use std::fmt::Formatter; + +use once_cell::sync::Lazy; +use regex::Regex; +use serde::{ + de::{Error, Visitor}, + Deserialize, Deserializer, +}; + +/// A newtype wrapper around `String` +/// +/// When deserialized, the following characters are removed: `-`, `*`, `/` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CleanString(pub String); + +impl<'de> Deserialize<'de> for CleanString { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(V) + } +} + +struct V; + +impl<'a> Visitor<'a> for V { + type Value = CleanString; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("a string") + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + let s = clean_str(v); + Ok(CleanString(s)) + } +} + +impl From<&str> for CleanString { + fn from(s: &str) -> Self { + CleanString(s.to_string()) + } +} + +impl From for CleanString { + fn from(s: String) -> Self { + CleanString(s) + } +} + +impl ToString for CleanString { + fn to_string(&self) -> String { + self.0.clone() + } +} + +impl AsRef for CleanString { + fn as_ref(&self) -> &str { + &self.0 + } +} + +static REGEX: Lazy = Lazy::new(|| Regex::new("[-*/]").unwrap()); + +pub fn clean_str(s: &str) -> String { + REGEX.replace_all(s, "").to_string() +} + +#[cfg(any(test, feature = "property-test-api"))] +mod tests { + use proptest::{ + arbitrary::{Arbitrary, StrategyFor}, + prelude::*, + strategy::Map, + }; + use serde_json::json; + use test_strategy::proptest; + + use super::*; + + impl Arbitrary for CleanString { + type Parameters = (); + type Strategy = Map, fn(String) -> Self>; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + any::().prop_map(|s| CleanString(clean_str(&s))) + } + } + + fn parse(s: &str) -> CleanString { + let s = format!(r#""{s}""#); + serde_json::from_str(&s).unwrap() + } + + #[test] + fn correctly_formats_strings() { + assert_eq!(parse("hello"), CleanString::from("hello")); + assert_eq!(parse("h*e-l/lo"), CleanString::from("hello")); + } + + #[proptest] + fn any_string_deserializes_to_clean_string(s: String) { + let json = json!(s); + let _: CleanString = serde_json::from_value(json).unwrap(); + } +} diff --git a/catalyst-toolbox/src/ideascale/models/de/mod.rs b/catalyst-toolbox/src/ideascale/models/de/mod.rs new file mode 100644 index 00000000..a2a241d4 --- /dev/null +++ b/catalyst-toolbox/src/ideascale/models/de/mod.rs @@ -0,0 +1,108 @@ +use serde::Deserialize; + +mod ada_rewards; +pub use ada_rewards::AdaRewards; + +mod clean_string; +pub use clean_string::{clean_str, CleanString}; + +mod approval; +pub use approval::Approval; + +mod challenge_title; +pub use challenge_title::ChallengeTitle; + +#[derive(Debug, Deserialize, Clone)] +pub struct Challenge { + pub id: u32, + #[serde(alias = "name")] + pub title: ChallengeTitle, + #[serde(alias = "tagline")] + pub rewards: AdaRewards, + pub description: CleanString, + #[serde(alias = "groupId")] + pub fund_id: u32, + #[serde(alias = "funnelId")] + pub funnel_id: u32, + #[serde(alias = "campaignUrl")] + pub challenge_url: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Funnel { + pub id: u32, + #[serde(alias = "name")] + pub title: CleanString, + pub description: CleanString, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Fund { + pub id: u32, + pub name: CleanString, + #[serde(alias = "campaigns")] + pub challenges: Vec, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Proposal { + #[serde(alias = "id")] + pub proposal_id: u32, + pub proposal_category: Option, + #[serde(alias = "title")] + pub proposal_title: CleanString, + #[serde(alias = "text")] + pub proposal_summary: CleanString, + + #[serde(alias = "url")] + pub proposal_url: String, + #[serde(default)] + pub proposal_files_url: String, + + #[serde(alias = "customFieldsByKey")] + pub custom_fields: ProposalCustomFieldsByKey, + + #[serde(alias = "authorInfo")] + pub proposer: Proposer, + + #[serde(alias = "stageId")] + pub stage_id: u32, + + #[serde(alias = "stageLabel")] + pub stage_type: String, + + #[serde(alias = "campaignId")] + pub challenge_id: u32, + + #[serde(alias = "flag")] + pub approved: Approval, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Proposer { + pub name: String, + #[serde(alias = "email")] + pub contact: String, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct ProposalCustomFieldsByKey { + #[serde(flatten)] + pub fields: serde_json::Value, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Stage { + #[serde(default)] + pub label: String, + #[serde(alias = "funnelId", default)] + pub funnel_id: u32, + #[serde(alias = "assessmentId", default)] + pub assessment_id: u32, +} + +impl Funnel { + pub fn is_community(&self) -> bool { + self.title.0.contains("Community Setting") + } +} diff --git a/catalyst-toolbox/src/lib.rs b/catalyst-toolbox/src/lib.rs index 4a6dac4c..2c4cdebf 100644 --- a/catalyst-toolbox/src/lib.rs +++ b/catalyst-toolbox/src/lib.rs @@ -14,3 +14,6 @@ pub mod vote_check; pub mod http; #[cfg(feature = "test-api")] pub mod testing; + +#[macro_use] +extern crate tracing; diff --git a/catalyst-toolbox/src/recovery/replay.rs b/catalyst-toolbox/src/recovery/replay.rs index d5a52fb7..afbd5fd1 100644 --- a/catalyst-toolbox/src/recovery/replay.rs +++ b/catalyst-toolbox/src/recovery/replay.rs @@ -8,9 +8,9 @@ pub use jcli_lib::utils::{ use jormungandr_lib::interfaces::{ load_persistent_fragments_logs_from_folder_path, VotePlanStatus, }; -use log::warn; use std::io::Write; use std::path::PathBuf; +use tracing::warn; /// Recover the tally from fragment log files and the initial preloaded block0 binary file. pub struct Replay { diff --git a/catalyst-toolbox/src/recovery/tally.rs b/catalyst-toolbox/src/recovery/tally.rs index 9b6922ee..54bf329c 100644 --- a/catalyst-toolbox/src/recovery/tally.rs +++ b/catalyst-toolbox/src/recovery/tally.rs @@ -26,10 +26,10 @@ use jormungandr_lib::{ }, time::SecondsSinceUnixEpoch, }; -use log::{debug, error, trace, warn}; use std::collections::{HashMap, HashSet}; use std::ops::{Add, Range}; use std::time::{Duration, SystemTime}; +use tracing::{debug, error, trace, warn}; use wallet::{Settings, TransactionBuilder, Wallet}; #[allow(clippy::large_enum_variant)]