use graphql_client::{reqwest::post_graphql, GraphQLQuery}; use sp_core::Bytes; use crate::*; use identity_info::*; // type used in parameters query // #[allow(non_camel_case_types)] // type jsonb = serde_json::Value; // index → identity #[derive(GraphQLQuery)] #[graphql( schema_path = "res/indexer-schema.json", query_path = "res/indexer-queries.graphql" )] pub struct IdentityNameByIndex; // index → identity info #[derive(GraphQLQuery)] #[graphql( schema_path = "res/indexer-schema.json", query_path = "res/indexer-queries.graphql" )] pub struct IdentityInfo; // pubkey → identity #[derive(GraphQLQuery)] #[graphql( schema_path = "res/indexer-schema.json", query_path = "res/indexer-queries.graphql" )] pub struct IdentityNameByPubkey; #[derive(GraphQLQuery)] #[graphql( schema_path = "res/indexer-schema.json", query_path = "res/indexer-queries.graphql" )] pub struct LatestBlock; #[derive(GraphQLQuery, Debug)] #[graphql( schema_path = "res/indexer-schema.json", query_path = "res/indexer-queries.graphql" )] pub struct GenesisHash; #[derive(Clone, Debug)] pub struct Indexer { pub gql_client: reqwest::Client, pub gql_url: String, } impl Indexer { /// index → name pub async fn username_by_index(&self, index: u32) -> anyhow::Result<Option<String>> { Ok(post_graphql::<IdentityNameByIndex, _>( &self.gql_client, &self.gql_url, identity_name_by_index::Variables { index: index.into(), }, ) .await? .data .and_then(move |mut data| data.identities.pop().map(|idty| idty.name))) } /// pubkey → name pub async fn username_by_pubkey(&self, pubkey: &str) -> anyhow::Result<Option<String>> { Ok(post_graphql::<IdentityNameByPubkey, _>( &self.gql_client, &self.gql_url, identity_name_by_pubkey::Variables { pubkey: pubkey.to_string(), }, ) .await? .data .and_then(move |mut data| data.identities.pop().map(|idty| idty.name))) } /// index → info pub async fn identity_info(&self, index: u32) -> Option<IdentityInfoIdentities> { post_graphql::<IdentityInfo, _>( &self.gql_client, &self.gql_url, identity_info::Variables { index: index.into(), }, ) .await .expect("problem") .data .and_then(move |mut data| data.identities.pop()) } /// fetch latest block number pub async fn fetch_latest_block(&self) -> Result<u64, anyhow::Error> { Ok(post_graphql::<LatestBlock, _>( &self.gql_client, self.gql_url.clone(), latest_block::Variables {}, ) .await? .data .unwrap() // must have a data field .blocks .first() .unwrap() // must have one and only one parameter matching request .height .try_into() .unwrap()) } /// fetch genesis hash // since this is always called before any other indexer request, check errors pub async fn fetch_genesis_hash(&self) -> Result<Hash, GcliError> { // try to connect to indexer let response = post_graphql::<GenesisHash, _>( &self.gql_client, self.gql_url.clone(), genesis_hash::Variables {}, ) .await .map_err(|e| anyhow!(e))?; // debug errors if any response.errors.map_or_else(Vec::new, |e| dbg!(e)); // extract hash let hash = response .data .ok_or(GcliError::Indexer( "no field 'data' when getting genesis hash".to_string(), ))? .blocks .first() .unwrap() // must have one and only one block matching request .hash .clone(); // convert it let hash = TryInto::<[u8; 32]>::try_into(hash.as_ref()).unwrap(); Ok(hash.into()) } } /// define indexer subcommands #[derive(Clone, Default, Debug, clap::Parser)] pub enum Subcommand { #[default] /// Check that indexer and node are on the same network (same genesis hash) Check, /// Fetch latest indexed block LatestBlock, } /// handle indexer commands pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliError> { // build indexer because it is needed for all subcommands let mut data = data.build_indexer().await?; // match subcommand match command { Subcommand::Check => { data = data.build_client().await?; if data.genesis_hash == data.indexer_genesis_hash { println!( "{} and {} have the same genesis hash: {}", data.cfg.duniter_endpoint, data.indexer().gql_url, data.genesis_hash ); } else { println!( "⚠️ {} ({}) and {} ({}) do not share same genesis", data.cfg.duniter_endpoint, data.genesis_hash, data.indexer().gql_url, data.indexer_genesis_hash ); } } Subcommand::LatestBlock => { println!( "latest block indexed by {} is: {}", data.cfg.indexer_endpoint, data.indexer().fetch_latest_block().await? ); } }; Ok(()) }