From d052e5314c5063d94b5fea489fcf07cf25e50253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Thu, 5 Oct 2023 12:04:20 +0100 Subject: [PATCH 01/13] generate schema from json abi --- Cargo.lock | 14 +- packages/fuel-indexer-lib/Cargo.toml | 2 + .../fuel-indexer-lib/src/graphql/constants.rs | 23 ++- packages/fuel-indexer-lib/src/graphql/mod.rs | 1 + .../src/graphql/schema_gen.rs | 148 ++++++++++++++++++ packages/fuel-indexer-lib/src/helpers.rs | 54 +++++++ packages/fuel-indexer-lib/src/lib.rs | 1 + packages/fuel-indexer-macros/src/helpers.rs | 53 ------- packages/fuel-indexer-macros/src/indexer.rs | 8 +- plugins/forc-index/src/commands/new.rs | 4 + plugins/forc-index/src/ops/forc_index_new.rs | 14 +- 11 files changed, 259 insertions(+), 63 deletions(-) create mode 100644 packages/fuel-indexer-lib/src/graphql/schema_gen.rs create mode 100644 packages/fuel-indexer-lib/src/helpers.rs diff --git a/Cargo.lock b/Cargo.lock index 115a1dd3d..55da0a8e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3342,7 +3342,9 @@ dependencies = [ "async-graphql-parser 5.0.10", "bincode", "clap 3.2.25", + "fuel-abi-types 0.3.0", "fuel-indexer-types", + "fuels-code-gen", "http", "lazy_static", "proc-macro2", @@ -3506,7 +3508,7 @@ dependencies = [ "sqlx", "thiserror", "tokio", - "toml 0.8.0", + "toml 0.8.1", "tracing", "tracing-subscriber 0.2.25", "trybuild", @@ -7636,14 +7638,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c226a7bba6d859b63c92c4b4fe69c5b6b72d0cb897dbc8e6012298e6154cb56e" +checksum = "1bc1433177506450fe920e46a4f9812d0c211f5dd556da10e731a0a3dfa151f0" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.0", + "toml_edit 0.20.1", ] [[package]] @@ -7668,9 +7670,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ff63e60a958cefbb518ae1fd6566af80d9d4be430a33f3723dfc47d1d411d95" +checksum = "ca676d9ba1a322c1b64eb8045a5ec5c0cfb0c9d08e15e9ff622589ad5221c8fe" dependencies = [ "indexmap 2.0.0", "serde", diff --git a/packages/fuel-indexer-lib/Cargo.toml b/packages/fuel-indexer-lib/Cargo.toml index 3c993320a..2232e1caa 100644 --- a/packages/fuel-indexer-lib/Cargo.toml +++ b/packages/fuel-indexer-lib/Cargo.toml @@ -14,7 +14,9 @@ anyhow = "1.0" async-graphql-parser = { workspace = true } bincode = { workspace = true } clap = { features = ["cargo", "derive", "env"], workspace = true } +fuel-abi-types = "0.3" fuel-indexer-types = { workspace = true } +fuels-code-gen = { version = "0.46", default-features = false } http = { version = "0.2", default-features = false } lazy_static = { version = "1.4" } proc-macro2 = "1.0" diff --git a/packages/fuel-indexer-lib/src/graphql/constants.rs b/packages/fuel-indexer-lib/src/graphql/constants.rs index 5905bbf31..c0db43da2 100644 --- a/packages/fuel-indexer-lib/src/graphql/constants.rs +++ b/packages/fuel-indexer-lib/src/graphql/constants.rs @@ -1,5 +1,5 @@ use lazy_static::lazy_static; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; lazy_static! { @@ -61,4 +61,25 @@ lazy_static! { "Option", "Option", ]); + + pub static ref ABI_TYPE_MAP: HashMap<&'static str, &'static str> = HashMap::from_iter([ + ("u128", "U128"), + ("u64", "U64"), + ("u32", "U32"), + ("u8", "U8"), + ("i128", "I128"), + ("i64", "I64"), + ("i32", "I32"), + ("i8", "I8"), + ("bool", "Boolean"), + ("u8[64]", "Bytes64"), + ("u8[32]", "Bytes32"), + ("u8[8]", "Bytes8"), + ("u8[4]", "Bytes4"), + ("Vec", "Bytes"), + ("SizedAsciiString<64>", "ID"), + ("String", "String"), + ("str[32]", "Bytes32"), + ("str[64]", "Bytes64"), + ]); } diff --git a/packages/fuel-indexer-lib/src/graphql/mod.rs b/packages/fuel-indexer-lib/src/graphql/mod.rs index 21558cfe4..935a035e8 100644 --- a/packages/fuel-indexer-lib/src/graphql/mod.rs +++ b/packages/fuel-indexer-lib/src/graphql/mod.rs @@ -1,5 +1,6 @@ pub mod constants; pub mod parser; +pub mod schema_gen; pub mod types; pub mod validator; diff --git a/packages/fuel-indexer-lib/src/graphql/schema_gen.rs b/packages/fuel-indexer-lib/src/graphql/schema_gen.rs new file mode 100644 index 000000000..13a845604 --- /dev/null +++ b/packages/fuel-indexer-lib/src/graphql/schema_gen.rs @@ -0,0 +1,148 @@ +use crate::{ + graphql::constants::ABI_TYPE_MAP, + helpers::{is_unit_type, strip_callpath_from_type_field}, +}; +use fuel_abi_types::abi::program::{ProgramABI, TypeDeclaration}; +use std::collections::HashMap; + +// Given a `TypeDeclaration` for an ABI enum, generate a corresponding GraphQL +// `enum`. +// +// We can only decode enums with all variants of type (). For example: +// +// pub enum SimpleEnum { +// One: (), +// Two: (), +// Three: (), +// } +// +// can be converted to GraphQL: +// +// enum SimpleEnumEntity { +// ONE +// TWO +// THREE +// } +fn decode_enum(types: &Vec, ty: &TypeDeclaration) -> Option { + let name = ty.type_field.strip_prefix("enum ").unwrap(); + + let mut fields: Vec = vec![]; + if let Some(ref components) = ty.components { + for c in components { + let ty = &types.get(c.type_id)?; + if is_unit_type(ty) { + fields.push(format!("{}", c.name.to_uppercase())); + } else { + return None; + } + } + } + + let fields = fields + .into_iter() + .map(|s| " ".to_string() + &s) + .collect::>() + .join("\n"); + + let output = format!("enum {name}Entity {{\n{fields}\n}}"); + + Some(output) +} + +// Given a `TypeDeclaration` for an ABI struct, generate a corresponding GraphQL `type`. +fn decode_struct( + scalar_types: &HashMap<&str, &str>, + abi_types: &Vec, + ty: &TypeDeclaration, +) -> Option { + let name = ty.type_field.strip_prefix("struct ")?; + + let mut fields: Vec = vec!["id: ID!".to_string()]; + if let Some(ref components) = ty.components { + for c in components { + // Skip the `id` field since we are inserting out own. + if c.name.as_str() == "id" { + continue; + } + + let ty = &abi_types.get(c.type_id)?.type_field; + + // Enum field. + if let Some(ty) = ty.strip_prefix("enum ") { + if crate::constants::RESERVED_TYPEDEF_NAMES.contains(ty) { + // For reserved type names, we take the type as is. + fields.push(format!("{}: {}!", c.name, ty)); + } else { + // For generated types, we add a suffix -Entity. + fields.push(format!("{}: {}Entity!", c.name, ty)) + } + // Struct field. + } else if let Some(ty) = ty.strip_prefix("struct ") { + if crate::constants::RESERVED_TYPEDEF_NAMES.contains(ty) { + // For reserved type names, we take the type as is. + fields.push(format!("{}: {}!", c.name, ty)); + } else { + // For generated types, we add a suffix -Entity. + fields.push(format!("{}: {}Entity!", c.name, ty)) + } + // Scalar field. + } else if let Some(ty) = scalar_types.get(&ty.as_str()) { + fields.push(format!("{}: {}!", c.name, ty)); + } + } + } + + let fields = fields + .into_iter() + .map(|s| " ".to_string() + &s) + .collect::>() + .join("\n"); + + let output = format!("type {name}Entity {{\n{fields}\n}}"); + + Some(output) +} + +// Generate a GraphQL schema from JSON ABI. +pub fn generate_schema(json_abi: &std::path::PathBuf) -> Option { + let source = fuels_code_gen::utils::Source::parse(json_abi.to_str()?).unwrap(); + let source = source.get().unwrap(); + let abi: ProgramABI = serde_json::from_str(&source).unwrap(); + let abi_types: Vec = abi + .types + .into_iter() + .map(strip_callpath_from_type_field) + .collect(); + + let mut output: Vec = vec![]; + + for ty in abi_types.iter() { + // Skip all generic types + if crate::constants::IGNORED_GENERIC_METADATA.contains(ty.type_field.as_str()) { + continue; + } + + // Only generate schema types for structs and enums + if let Some(name) = ty.type_field.strip_prefix("struct ") { + if crate::constants::RESERVED_TYPEDEF_NAMES.contains(name) + || crate::constants::GENERIC_STRUCTS.contains(name) + { + continue; + } + if let Some(result) = decode_struct(&ABI_TYPE_MAP, &abi_types, ty) { + output.push(result); + } + } else if let Some(name) = ty.type_field.strip_prefix("enum ") { + if crate::constants::RESERVED_TYPEDEF_NAMES.contains(name) + || crate::constants::GENERIC_STRUCTS.contains(name) + { + continue; + } + if let Some(result) = decode_enum(&abi_types, ty) { + output.push(result); + } + } + } + + Some(output.join("\n")) +} diff --git a/packages/fuel-indexer-lib/src/helpers.rs b/packages/fuel-indexer-lib/src/helpers.rs new file mode 100644 index 000000000..ad21a20f0 --- /dev/null +++ b/packages/fuel-indexer-lib/src/helpers.rs @@ -0,0 +1,54 @@ +use fuel_abi_types::abi::program::TypeDeclaration; + +/// Whether a `TypeDeclaration` is tuple type +pub fn is_tuple_type(typ: &TypeDeclaration) -> bool { + let mut type_field_chars = typ.type_field.chars(); + type_field_chars.next().is_some_and(|c| c == '(') + && type_field_chars.next().is_some_and(|c| c != ')') +} + +/// Whether a `TypeDeclaration` is a unit type +pub fn is_unit_type(typ: &TypeDeclaration) -> bool { + let mut type_field_chars = typ.type_field.chars(); + type_field_chars.next().is_some_and(|c| c == '(') + && type_field_chars.next().is_some_and(|c| c == ')') +} + +fn is_array_type(typ: &TypeDeclaration) -> bool { + typ.type_field.starts_with('[') + && typ.type_field.ends_with(']') + && typ.type_field.contains(';') +} + +/// Whether the TypeDeclaration should be used to build struct fields and decoders +pub fn is_non_decodable_type(typ: &TypeDeclaration) -> bool { + is_tuple_type(typ) + || is_unit_type(typ) + || crate::constants::IGNORED_GENERIC_METADATA.contains(typ.type_field.as_str()) + || is_array_type(typ) +} + +/// Strip the call path from the type field of a `TypeDeclaration`. +/// +/// It is possible that the type field for a `TypeDeclaration` contains a +/// fully-qualified path (e.g. `std::address::Address` as opposed to `Address`). +/// Path separators are not allowed to be used as part of an identifier, so this +/// function removes the qualifying path while keeping the type keyword. +pub fn strip_callpath_from_type_field(mut typ: TypeDeclaration) -> TypeDeclaration { + if is_non_decodable_type(&typ) { + return typ; + } + + let mut s = typ.type_field.split_whitespace(); + typ.type_field = + if let (Some(keyword), Some(fully_qualified_type_path)) = (s.next(), s.last()) { + if let Some(slug) = fully_qualified_type_path.split("::").last() { + [keyword, slug].join(" ") + } else { + unreachable!("All types should be formed with a keyword and call path") + } + } else { + typ.type_field + }; + typ +} diff --git a/packages/fuel-indexer-lib/src/lib.rs b/packages/fuel-indexer-lib/src/lib.rs index e246c79c0..a9819b82b 100644 --- a/packages/fuel-indexer-lib/src/lib.rs +++ b/packages/fuel-indexer-lib/src/lib.rs @@ -7,6 +7,7 @@ pub mod config; pub mod constants; pub mod defaults; pub mod graphql; +pub mod helpers; pub mod manifest; pub mod utils; diff --git a/packages/fuel-indexer-macros/src/helpers.rs b/packages/fuel-indexer-macros/src/helpers.rs index cf0f502b5..b13241131 100644 --- a/packages/fuel-indexer-macros/src/helpers.rs +++ b/packages/fuel-indexer-macros/src/helpers.rs @@ -76,28 +76,6 @@ pub fn get_json_abi(abi_path: Option) -> Option { } } -/// Whether a `TypeDeclaration` is tuple type -pub fn is_tuple_type(typ: &TypeDeclaration) -> bool { - let mut type_field_chars = typ.type_field.chars(); - type_field_chars.next().is_some_and(|c| c == '(') - && type_field_chars.next().is_some_and(|c| c != ')') -} - -/// Whether a `TypeDeclaration` is a unit type -pub fn is_unit_type(typ: &TypeDeclaration) -> bool { - let mut type_field_chars = typ.type_field.chars(); - type_field_chars.next().is_some_and(|c| c == '(') - && type_field_chars.next().is_some_and(|c| c == ')') -} - -/// Whether the TypeDeclaration should be used to build struct fields and decoders -pub fn is_non_decodable_type(typ: &TypeDeclaration) -> bool { - is_tuple_type(typ) - || is_unit_type(typ) - || IGNORED_GENERIC_METADATA.contains(typ.type_field.as_str()) - || is_array_type(typ) -} - /// Derive Ident for decoded type /// /// These idents are used as fields for the `Decoder` struct. @@ -747,31 +725,6 @@ pub fn can_derive_id(field_set: &HashSet, field_name: &str) -> bool { && field_name != IdCol::to_lowercase_str() } -/// Strip the call path from the type field of a `TypeDeclaration`. -/// -/// It is possible that the type field for a `TypeDeclaration` contains a -/// fully-qualified path (e.g. `std::address::Address` as opposed to `Address`). -/// Path separators are not allowed to be used as part of an identifier, so this -/// function removes the qualifying path while keeping the type keyword. -pub fn strip_callpath_from_type_field(mut typ: TypeDeclaration) -> TypeDeclaration { - if is_non_decodable_type(&typ) { - return typ; - } - - let mut s = typ.type_field.split_whitespace(); - typ.type_field = - if let (Some(keyword), Some(fully_qualified_type_path)) = (s.next(), s.last()) { - if let Some(slug) = fully_qualified_type_path.split("::").last() { - [keyword, slug].join(" ") - } else { - unreachable!("All types should be formed with a keyword and call path") - } - } else { - typ.type_field - }; - typ -} - /// Simply represents a value for a generic type. #[derive(Debug)] pub enum GenericType { @@ -1117,12 +1070,6 @@ pub fn typed_path_components( (name, tokens) } -fn is_array_type(typ: &TypeDeclaration) -> bool { - typ.type_field.starts_with('[') - && typ.type_field.ends_with(']') - && typ.type_field.contains(';') -} - /// Determine whether or not the given type name is an unsupported type. /// /// Since we allow unsupported types in the ABI JSON, this check is only diff --git a/packages/fuel-indexer-macros/src/indexer.rs b/packages/fuel-indexer-macros/src/indexer.rs index 18b60bd25..f9d71e6c2 100644 --- a/packages/fuel-indexer-macros/src/indexer.rs +++ b/packages/fuel-indexer-macros/src/indexer.rs @@ -7,8 +7,12 @@ use crate::{ }; use fuel_abi_types::abi::program::TypeDeclaration; use fuel_indexer_lib::{ - constants::*, manifest::ContractIds, manifest::Manifest, - utils::workspace_manifest_prefix, ExecutionSource, + constants::*, + helpers::{is_non_decodable_type, is_tuple_type, strip_callpath_from_type_field}, + manifest::ContractIds, + manifest::Manifest, + utils::workspace_manifest_prefix, + ExecutionSource, }; use fuel_indexer_types::{type_id, FUEL_TYPES_NAMESPACE}; use fuels::{core::codec::resolve_fn_selector, types::param_types::ParamType}; diff --git a/plugins/forc-index/src/commands/new.rs b/plugins/forc-index/src/commands/new.rs index 1a23b3063..dcbcdc9f3 100644 --- a/plugins/forc-index/src/commands/new.rs +++ b/plugins/forc-index/src/commands/new.rs @@ -25,6 +25,10 @@ pub struct Command { #[clap(long, help = "Resolve indexer asset filepaths using absolute paths.")] pub absolute_paths: bool, + /// Path to JSON ABI for automatic GraphQL schema generation. + #[clap(long, help = "Path to JSON ABI for automatic schema generation.")] + pub json_abi: Option, + /// Enable verbose output. #[clap(short, long, help = "Enable verbose output.")] pub verbose: bool, diff --git a/plugins/forc-index/src/ops/forc_index_new.rs b/plugins/forc-index/src/ops/forc_index_new.rs index 65376f6bf..b7832bd26 100644 --- a/plugins/forc-index/src/ops/forc_index_new.rs +++ b/plugins/forc-index/src/ops/forc_index_new.rs @@ -71,6 +71,7 @@ pub fn create_indexer(command: NewCommand) -> anyhow::Result<()> { native, absolute_paths, verbose, + json_abi, } = command; std::fs::create_dir_all(&project_dir)?; @@ -151,9 +152,20 @@ pub fn create_indexer(command: NewCommand) -> anyhow::Result<()> { // Write index schema fs::create_dir_all(Path::new(&project_dir).join("schema"))?; + let schema_contents = { + if let Some(json_abi) = json_abi { + if json_abi.is_file() { + fuel_indexer_lib::graphql::schema_gen::generate_schema(&json_abi).unwrap() + } else { + anyhow::bail!("❌ '{json_abi:?}' is not a file."); + } + } else { + defaults::default_indexer_schema() + } + }; fs::write( Path::new(&project_dir).join("schema").join(schema_filename), - defaults::default_indexer_schema(), + schema_contents, )?; // What content are we writing? From 9e46ac2a552c5613ef79621b1ebc1adccfa0bab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Fri, 6 Oct 2023 11:36:13 +0100 Subject: [PATCH 02/13] clippy --- .../fuel-indexer-lib/src/graphql/schema_gen.rs | 8 ++++---- packages/fuel-indexer-types/src/fuel.rs | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/fuel-indexer-lib/src/graphql/schema_gen.rs b/packages/fuel-indexer-lib/src/graphql/schema_gen.rs index 13a845604..7a561384d 100644 --- a/packages/fuel-indexer-lib/src/graphql/schema_gen.rs +++ b/packages/fuel-indexer-lib/src/graphql/schema_gen.rs @@ -23,7 +23,7 @@ use std::collections::HashMap; // TWO // THREE // } -fn decode_enum(types: &Vec, ty: &TypeDeclaration) -> Option { +fn decode_enum(types: &[TypeDeclaration], ty: &TypeDeclaration) -> Option { let name = ty.type_field.strip_prefix("enum ").unwrap(); let mut fields: Vec = vec![]; @@ -31,7 +31,7 @@ fn decode_enum(types: &Vec, ty: &TypeDeclaration) -> Option, ty: &TypeDeclaration) -> Option, - abi_types: &Vec, + abi_types: &[TypeDeclaration], ty: &TypeDeclaration, ) -> Option { let name = ty.type_field.strip_prefix("struct ")?; @@ -104,7 +104,7 @@ fn decode_struct( } // Generate a GraphQL schema from JSON ABI. -pub fn generate_schema(json_abi: &std::path::PathBuf) -> Option { +pub fn generate_schema(json_abi: &std::path::Path) -> Option { let source = fuels_code_gen::utils::Source::parse(json_abi.to_str()?).unwrap(); let source = source.get().unwrap(); let abi: ProgramABI = serde_json::from_str(&source).unwrap(); diff --git a/packages/fuel-indexer-types/src/fuel.rs b/packages/fuel-indexer-types/src/fuel.rs index 04f1b173d..fb4738371 100644 --- a/packages/fuel-indexer-types/src/fuel.rs +++ b/packages/fuel-indexer-types/src/fuel.rs @@ -214,7 +214,7 @@ impl From for Input { amount: message_signed.amount, nonce: message_signed.nonce, witness_index: message_signed.witness_index, - data: message_signed.data.into(), + data: message_signed.data, predicate: "".into(), predicate_data: "".into(), }) @@ -228,9 +228,9 @@ impl From for Input { amount: message_predicate.amount, nonce: message_predicate.nonce, witness_index: 0, - data: message_predicate.data.into(), - predicate: message_predicate.predicate.into(), - predicate_data: message_predicate.predicate_data.into(), + data: message_predicate.data, + predicate: message_predicate.predicate, + predicate_data: message_predicate.predicate_data, }) } ClientInput::CoinSigned(coin_signed) => Input::Coin(InputCoin { @@ -252,8 +252,8 @@ impl From for Input { tx_pointer: coin_predicate.tx_pointer.into(), witness_index: 0, maturity: coin_predicate.maturity, - predicate: coin_predicate.predicate.into(), - predicate_data: coin_predicate.predicate_data.into(), + predicate: coin_predicate.predicate, + predicate_data: coin_predicate.predicate_data, }), ClientInput::Contract(contract) => Input::Contract(InputContract { utxo_id: contract.utxo_id, @@ -282,8 +282,8 @@ impl From for Input { nonce: message_coin.nonce, witness_index: 0, data: "".into(), - predicate: message_coin.predicate.into(), - predicate_data: message_coin.predicate_data.into(), + predicate: message_coin.predicate, + predicate_data: message_coin.predicate_data, }) } } From 72976dbfaa3c43ab304d23e428c9658481e65564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Fri, 6 Oct 2023 13:12:53 +0100 Subject: [PATCH 03/13] update test output --- .../integration_tests__commands__forc_index_new_help_output.snap | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__forc_index_new_help_output.snap b/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__forc_index_new_help_output.snap index e87962b75..27248c2b9 100644 --- a/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__forc_index_new_help_output.snap +++ b/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__forc_index_new_help_output.snap @@ -13,6 +13,7 @@ ARGS: OPTIONS: --absolute-paths Resolve indexer asset filepaths using absolute paths. -h, --help Print help information + --json-abi Path to JSON ABI for automatic schema generation. --name Name of indexer. --namespace Namespace to which indexer belongs. --native Initialize an indexer with native execution enabled. From 67ec2720ff2bea21ce1894c776c380589fd0e13b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Thu, 12 Oct 2023 12:33:02 +0100 Subject: [PATCH 04/13] document forc index new --json-abi flag --- docs/src/designing-a-schema/index.md | 142 +++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/docs/src/designing-a-schema/index.md b/docs/src/designing-a-schema/index.md index 9921ec675..9c94a8f63 100644 --- a/docs/src/designing-a-schema/index.md +++ b/docs/src/designing-a-schema/index.md @@ -37,3 +37,145 @@ Legend: | Enums | 🟨 | | | Interfaces | ⛔ | | | Input Types| ⛔ | | + +# Automatically generating GraphQL schema from JSON ABI + +`forc index new` supports automatically generating GraphQL schema from a contract JSON ABI. + +Sway `struct`s are translated into GrapQL `type`s, and the following `struct` field types are supported: + +| Sway Type | GraphQL Type | +|-----------|--------------| +| u128 | U128 | +| u64 | U64 | +| u32 | U32 | +| u8 | U8 | +| i128 | I128 | +| i64 | I64 | +| i32 | I32 | +| i8 | I8 | +| bool | Boolean | +| u8[64] | Bytes64 | +| u8[32] | Bytes32 | +| u8[8] | Bytes8 | +| u8[4] | Bytes4 | +| Vec| Bytes | +| SizedAsciiString<64> | ID | +| String | String | +| str[32] | Bytes32 | +| str[64] | Bytes64 | + +Sway `enum` types can also be translated. However, all enum variants must have `()` type. For example: + +``` +pub enum SimpleEnum { + One: (), + Two: (), + Three: (), +} +``` + +Will be translated to GraphQL as: + +``` +enum SimpleEnumEntity { + ONE + TWO + THREE +} +``` + +## Example + +Using the `DAO-contract-abi.json`, which can be found in the `fuel-indexer` repository: + +``` +forc index new --json-abi ./packages/fuel-indexer-tests/trybuild/abi/DAO-contract-abi.json dao-indexer +``` + +We get the following schema: + +``` +enum CreationErrorEntity { + DURATIONCANNOTBEZERO + INVALIDACCEPTANCEPERCENTAGE +} +enum InitializationErrorEntity { + CANNOTREINITIALIZE + CONTRACTNOTINITIALIZED +} +enum ProposalErrorEntity { + INSUFFICIENTAPPROVALS + PROPOSALEXECUTED + PROPOSALEXPIRED + PROPOSALSTILLACTIVE +} +enum UserErrorEntity { + AMOUNTCANNOTBEZERO + INCORRECTASSETSENT + INSUFFICIENTBALANCE + INVALIDID + VOTEAMOUNTCANNOTBEZERO +} +type CallDataEntity { + id: ID! + arguments: U64! + function_selector: U64! +} +type CreateProposalEventEntity { + id: ID! + proposal_info: ProposalInfoEntity! +} +type DepositEventEntity { + id: ID! + amount: U64! + user: Identity! +} +type ExecuteEventEntity { + id: ID! + acceptance_percentage: U64! + user: Identity! +} +type InitializeEventEntity { + id: ID! + author: Identity! + token: ContractId! +} +type ProposalEntity { + id: ID! + amount: U64! + asset: ContractId! + call_data: CallDataEntity! + gas: U64! +} +type ProposalInfoEntity { + id: ID! + acceptance_percentage: U64! + author: Identity! + deadline: U64! + executed: Boolean! + no_votes: U64! + proposal_transaction: ProposalEntity! + yes_votes: U64! +} +type UnlockVotesEventEntity { + id: ID! + user: Identity! + vote_amount: U64! +} +type VoteEventEntity { + id: ID! + user: Identity! + vote_amount: U64! +} +type VotesEntity { + id: ID! + no_votes: U64! + yes_votes: U64! +} +type WithdrawEventEntity { + id: ID! + amount: U64! + user: Identity! +} +``` \ No newline at end of file From 5d43dcb51ec2b768fc6d79e65cde787beb520573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Thu, 12 Oct 2023 15:05:45 +0100 Subject: [PATCH 05/13] update the docs --- docs/src/SUMMARY.md | 1 + docs/src/designing-a-schema/index.md | 142 -------------------------- docs/src/generating-a-schema/index.md | 141 +++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 142 deletions(-) create mode 100644 docs/src/generating-a-schema/index.md diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 26cd29c76..336741184 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -21,6 +21,7 @@ - [Scalars](./designing-a-schema/scalars.md) - [Directives](./designing-a-schema/directives.md) - [Relationships](./designing-a-schema/relationships.md) +- [Generating a Schema](./generating-a-schema/index.md) - [Indexing Fuel Types](./indexing-fuel-types/index.md) - [Blocks](./indexing-fuel-types/blocks.md) - [Transactions](./indexing-fuel-types/transactions.md) diff --git a/docs/src/designing-a-schema/index.md b/docs/src/designing-a-schema/index.md index 9c94a8f63..9921ec675 100644 --- a/docs/src/designing-a-schema/index.md +++ b/docs/src/designing-a-schema/index.md @@ -37,145 +37,3 @@ Legend: | Enums | 🟨 | | | Interfaces | ⛔ | | | Input Types| ⛔ | | - -# Automatically generating GraphQL schema from JSON ABI - -`forc index new` supports automatically generating GraphQL schema from a contract JSON ABI. - -Sway `struct`s are translated into GrapQL `type`s, and the following `struct` field types are supported: - -| Sway Type | GraphQL Type | -|-----------|--------------| -| u128 | U128 | -| u64 | U64 | -| u32 | U32 | -| u8 | U8 | -| i128 | I128 | -| i64 | I64 | -| i32 | I32 | -| i8 | I8 | -| bool | Boolean | -| u8[64] | Bytes64 | -| u8[32] | Bytes32 | -| u8[8] | Bytes8 | -| u8[4] | Bytes4 | -| Vec| Bytes | -| SizedAsciiString<64> | ID | -| String | String | -| str[32] | Bytes32 | -| str[64] | Bytes64 | - -Sway `enum` types can also be translated. However, all enum variants must have `()` type. For example: - -``` -pub enum SimpleEnum { - One: (), - Two: (), - Three: (), -} -``` - -Will be translated to GraphQL as: - -``` -enum SimpleEnumEntity { - ONE - TWO - THREE -} -``` - -## Example - -Using the `DAO-contract-abi.json`, which can be found in the `fuel-indexer` repository: - -``` -forc index new --json-abi ./packages/fuel-indexer-tests/trybuild/abi/DAO-contract-abi.json dao-indexer -``` - -We get the following schema: - -``` -enum CreationErrorEntity { - DURATIONCANNOTBEZERO - INVALIDACCEPTANCEPERCENTAGE -} -enum InitializationErrorEntity { - CANNOTREINITIALIZE - CONTRACTNOTINITIALIZED -} -enum ProposalErrorEntity { - INSUFFICIENTAPPROVALS - PROPOSALEXECUTED - PROPOSALEXPIRED - PROPOSALSTILLACTIVE -} -enum UserErrorEntity { - AMOUNTCANNOTBEZERO - INCORRECTASSETSENT - INSUFFICIENTBALANCE - INVALIDID - VOTEAMOUNTCANNOTBEZERO -} -type CallDataEntity { - id: ID! - arguments: U64! - function_selector: U64! -} -type CreateProposalEventEntity { - id: ID! - proposal_info: ProposalInfoEntity! -} -type DepositEventEntity { - id: ID! - amount: U64! - user: Identity! -} -type ExecuteEventEntity { - id: ID! - acceptance_percentage: U64! - user: Identity! -} -type InitializeEventEntity { - id: ID! - author: Identity! - token: ContractId! -} -type ProposalEntity { - id: ID! - amount: U64! - asset: ContractId! - call_data: CallDataEntity! - gas: U64! -} -type ProposalInfoEntity { - id: ID! - acceptance_percentage: U64! - author: Identity! - deadline: U64! - executed: Boolean! - no_votes: U64! - proposal_transaction: ProposalEntity! - yes_votes: U64! -} -type UnlockVotesEventEntity { - id: ID! - user: Identity! - vote_amount: U64! -} -type VoteEventEntity { - id: ID! - user: Identity! - vote_amount: U64! -} -type VotesEntity { - id: ID! - no_votes: U64! - yes_votes: U64! -} -type WithdrawEventEntity { - id: ID! - amount: U64! - user: Identity! -} -``` \ No newline at end of file diff --git a/docs/src/generating-a-schema/index.md b/docs/src/generating-a-schema/index.md new file mode 100644 index 000000000..2d6394571 --- /dev/null +++ b/docs/src/generating-a-schema/index.md @@ -0,0 +1,141 @@ +# Automatically generating GraphQL schema from JSON ABI + +`forc index new` supports automatically generating GraphQL schema from a contract JSON ABI. + +Sway `struct`s are translated into GrapQL `type`s, and the following `struct` field types are supported: + +| Sway Type | GraphQL Type | +|-----------|--------------| +| u128 | U128 | +| u64 | U64 | +| u32 | U32 | +| u8 | U8 | +| i128 | I128 | +| i64 | I64 | +| i32 | I32 | +| i8 | I8 | +| bool | Boolean | +| u8[64] | Bytes64 | +| u8[32] | Bytes32 | +| u8[8] | Bytes8 | +| u8[4] | Bytes4 | +| Vec| Bytes | +| SizedAsciiString<64> | ID | +| String | String | +| str[32] | Bytes32 | +| str[64] | Bytes64 | + +Sway `enum` types can also be translated. However, all enum variants must have `()` type. For example: + +```rust +pub enum SimpleEnum { + One: (), + Two: (), + Three: (), +} +``` + +Will be translated to GraphQL as: + +```GraphQL +enum SimpleEnumEntity { + ONE + TWO + THREE +} +``` + +## Example + +Using the `DAO-contract-abi.json`, which can be found in the `fuel-indexer` repository: + +```bash +forc index new --json-abi ./packages/fuel-indexer-tests/trybuild/abi/DAO-contract-abi.json dao-indexer +``` + +We get the following schema: + +```GraphQL +enum CreationErrorEntity { + DURATIONCANNOTBEZERO + INVALIDACCEPTANCEPERCENTAGE +} +enum InitializationErrorEntity { + CANNOTREINITIALIZE + CONTRACTNOTINITIALIZED +} +enum ProposalErrorEntity { + INSUFFICIENTAPPROVALS + PROPOSALEXECUTED + PROPOSALEXPIRED + PROPOSALSTILLACTIVE +} +enum UserErrorEntity { + AMOUNTCANNOTBEZERO + INCORRECTASSETSENT + INSUFFICIENTBALANCE + INVALIDID + VOTEAMOUNTCANNOTBEZERO +} +type CallDataEntity { + id: ID! + arguments: U64! + function_selector: U64! +} +type CreateProposalEventEntity { + id: ID! + proposal_info: ProposalInfoEntity! +} +type DepositEventEntity { + id: ID! + amount: U64! + user: Identity! +} +type ExecuteEventEntity { + id: ID! + acceptance_percentage: U64! + user: Identity! +} +type InitializeEventEntity { + id: ID! + author: Identity! + token: ContractId! +} +type ProposalEntity { + id: ID! + amount: U64! + asset: ContractId! + call_data: CallDataEntity! + gas: U64! +} +type ProposalInfoEntity { + id: ID! + acceptance_percentage: U64! + author: Identity! + deadline: U64! + executed: Boolean! + no_votes: U64! + proposal_transaction: ProposalEntity! + yes_votes: U64! +} +type UnlockVotesEventEntity { + id: ID! + user: Identity! + vote_amount: U64! +} +type VoteEventEntity { + id: ID! + user: Identity! + vote_amount: U64! +} +type VotesEntity { + id: ID! + no_votes: U64! + yes_votes: U64! +} +type WithdrawEventEntity { + id: ID! + amount: U64! + user: Identity! +} +``` From d20f959e8729a3ca7bdefa34be97d07cda9e0185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Wed, 18 Oct 2023 14:45:06 +0100 Subject: [PATCH 06/13] tweaks --- docs/src/generating-a-schema/index.md | 86 +++++++++++-------- .../src/graphql/schema_gen.rs | 14 +-- 2 files changed, 57 insertions(+), 43 deletions(-) diff --git a/docs/src/generating-a-schema/index.md b/docs/src/generating-a-schema/index.md index 2d6394571..41f3a6226 100644 --- a/docs/src/generating-a-schema/index.md +++ b/docs/src/generating-a-schema/index.md @@ -38,10 +38,10 @@ pub enum SimpleEnum { Will be translated to GraphQL as: ```GraphQL -enum SimpleEnumEntity { - ONE - TWO - THREE +enum SimpleEnum { + One + Two + Three } ``` @@ -56,59 +56,69 @@ forc index new --json-abi ./packages/fuel-indexer-tests/trybuild/abi/DAO-contrac We get the following schema: ```GraphQL -enum CreationErrorEntity { - DURATIONCANNOTBEZERO - INVALIDACCEPTANCEPERCENTAGE -} -enum InitializationErrorEntity { - CANNOTREINITIALIZE - CONTRACTNOTINITIALIZED -} -enum ProposalErrorEntity { - INSUFFICIENTAPPROVALS - PROPOSALEXECUTED - PROPOSALEXPIRED - PROPOSALSTILLACTIVE -} -enum UserErrorEntity { - AMOUNTCANNOTBEZERO - INCORRECTASSETSENT - INSUFFICIENTBALANCE - INVALIDID - VOTEAMOUNTCANNOTBEZERO -} -type CallDataEntity { +enum CreationError { + DurationCannotBeZero + InvalidAcceptancePercentage +} + +enum InitializationError { + CannotReinitialize + ContractNotInitialized +} + +enum ProposalError { + InsufficientApprovals + ProposalExecuted + ProposalExpired + ProposalStillActive +} + +enum UserError { + AmountCannotBeZero + IncorrectAssetSent + InsufficientBalance + InvalidId + VoteAmountCannotBeZero +} + +type CallDataEntity @entity { id: ID! arguments: U64! function_selector: U64! } -type CreateProposalEventEntity { + +type CreateProposalEventEntity @entity { id: ID! proposal_info: ProposalInfoEntity! } -type DepositEventEntity { + +type DepositEventEntity @entity { id: ID! amount: U64! user: Identity! } -type ExecuteEventEntity { + +type ExecuteEventEntity @entity { id: ID! acceptance_percentage: U64! user: Identity! } -type InitializeEventEntity { + +type InitializeEventEntity @entity { id: ID! author: Identity! token: ContractId! } -type ProposalEntity { + +type ProposalEntity @entity { id: ID! amount: U64! asset: ContractId! call_data: CallDataEntity! gas: U64! } -type ProposalInfoEntity { + +type ProposalInfoEntity @entity { id: ID! acceptance_percentage: U64! author: Identity! @@ -118,22 +128,26 @@ type ProposalInfoEntity { proposal_transaction: ProposalEntity! yes_votes: U64! } -type UnlockVotesEventEntity { + +type UnlockVotesEventEntity @entity { id: ID! user: Identity! vote_amount: U64! } -type VoteEventEntity { + +type VoteEventEntity @entity { id: ID! user: Identity! vote_amount: U64! } -type VotesEntity { + +type VotesEntity @entity { id: ID! no_votes: U64! yes_votes: U64! } -type WithdrawEventEntity { + +type WithdrawEventEntity @entity { id: ID! amount: U64! user: Identity! diff --git a/packages/fuel-indexer-lib/src/graphql/schema_gen.rs b/packages/fuel-indexer-lib/src/graphql/schema_gen.rs index 7a561384d..9ef17bd6e 100644 --- a/packages/fuel-indexer-lib/src/graphql/schema_gen.rs +++ b/packages/fuel-indexer-lib/src/graphql/schema_gen.rs @@ -19,9 +19,9 @@ use std::collections::HashMap; // can be converted to GraphQL: // // enum SimpleEnumEntity { -// ONE -// TWO -// THREE +// One +// Two +// Three // } fn decode_enum(types: &[TypeDeclaration], ty: &TypeDeclaration) -> Option { let name = ty.type_field.strip_prefix("enum ").unwrap(); @@ -31,7 +31,7 @@ fn decode_enum(types: &[TypeDeclaration], ty: &TypeDeclaration) -> Option Option>() .join("\n"); - let output = format!("enum {name}Entity {{\n{fields}\n}}"); + let output = format!("enum {name} {{\n{fields}\n}}"); Some(output) } @@ -98,7 +98,7 @@ fn decode_struct( .collect::>() .join("\n"); - let output = format!("type {name}Entity {{\n{fields}\n}}"); + let output = format!("type {name}Entity @entity {{\n{fields}\n}}"); Some(output) } @@ -144,5 +144,5 @@ pub fn generate_schema(json_abi: &std::path::Path) -> Option { } } - Some(output.join("\n")) + Some(output.join("\n\n")) } From a866090cb3c8bdbc3b67e1edc2a693a64d877cdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Wed, 18 Oct 2023 15:18:52 +0100 Subject: [PATCH 07/13] cleanup --- packages/fuel-indexer-lib/src/graphql/schema_gen.rs | 7 ++++--- plugins/forc-index/src/ops/forc_index_new.rs | 6 +----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/fuel-indexer-lib/src/graphql/schema_gen.rs b/packages/fuel-indexer-lib/src/graphql/schema_gen.rs index 9ef17bd6e..172a32963 100644 --- a/packages/fuel-indexer-lib/src/graphql/schema_gen.rs +++ b/packages/fuel-indexer-lib/src/graphql/schema_gen.rs @@ -104,8 +104,9 @@ fn decode_struct( } // Generate a GraphQL schema from JSON ABI. -pub fn generate_schema(json_abi: &std::path::Path) -> Option { - let source = fuels_code_gen::utils::Source::parse(json_abi.to_str()?).unwrap(); +pub fn generate_schema(json_abi: &std::path::Path) -> String { + let source = + fuels_code_gen::utils::Source::parse(json_abi.to_string_lossy()).unwrap(); let source = source.get().unwrap(); let abi: ProgramABI = serde_json::from_str(&source).unwrap(); let abi_types: Vec = abi @@ -144,5 +145,5 @@ pub fn generate_schema(json_abi: &std::path::Path) -> Option { } } - Some(output.join("\n\n")) + output.join("\n\n") } diff --git a/plugins/forc-index/src/ops/forc_index_new.rs b/plugins/forc-index/src/ops/forc_index_new.rs index 9b684d443..995df1298 100644 --- a/plugins/forc-index/src/ops/forc_index_new.rs +++ b/plugins/forc-index/src/ops/forc_index_new.rs @@ -156,11 +156,7 @@ pub fn create_indexer(command: NewCommand) -> anyhow::Result<()> { fs::create_dir_all(Path::new(&project_dir).join("schema"))?; let schema_contents = { if let Some(json_abi) = json_abi { - if json_abi.is_file() { - fuel_indexer_lib::graphql::schema_gen::generate_schema(&json_abi).unwrap() - } else { - anyhow::bail!("❌ '{json_abi:?}' is not a file."); - } + fuel_indexer_lib::graphql::schema_gen::generate_schema(&json_abi) } else { defaults::default_indexer_schema() } From 813b67ae33a362078bfb9c7f7c6721d1cf5e503a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Fri, 27 Oct 2023 11:36:21 +0100 Subject: [PATCH 08/13] move metrics macro to fuel-indexer-macros --- .../fuel-indexer-database/postgres/Cargo.toml | 4 +- .../fuel-indexer-database/postgres/src/lib.rs | 2 +- .../macro-utils/Cargo.toml | 3 - .../macro-utils/src/lib.rs | 55 ------------------- packages/fuel-indexer-macros/src/lib.rs | 7 +++ .../src/prometheus_metrics.rs | 49 +++++++++++++++++ 6 files changed, 59 insertions(+), 61 deletions(-) create mode 100644 packages/fuel-indexer-macros/src/prometheus_metrics.rs diff --git a/packages/fuel-indexer-database/postgres/Cargo.toml b/packages/fuel-indexer-database/postgres/Cargo.toml index cad581022..8ab368af1 100644 --- a/packages/fuel-indexer-database/postgres/Cargo.toml +++ b/packages/fuel-indexer-database/postgres/Cargo.toml @@ -14,7 +14,7 @@ bigdecimal = { version = "0.3" } chrono = "0.4.24" fuel-indexer-database-types = { workspace = true } fuel-indexer-lib = { workspace = true } -fuel-indexer-macro-utils = { workspace = true, optional = true } +fuel-indexer-macros = { workspace = true, optional = true } fuel-indexer-metrics = { workspace = true, optional = true } sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "postgres", "offline", "time", "chrono", "bigdecimal"] } tracing = { workspace = true } @@ -22,4 +22,4 @@ uuid = { version = "1.3", features = ["v4"] } [features] default = ["metrics"] -metrics = ["fuel-indexer-macro-utils", "fuel-indexer-metrics"] +metrics = ["fuel-indexer-macros", "fuel-indexer-metrics"] diff --git a/packages/fuel-indexer-database/postgres/src/lib.rs b/packages/fuel-indexer-database/postgres/src/lib.rs index d49ab269b..fba00e681 100644 --- a/packages/fuel-indexer-database/postgres/src/lib.rs +++ b/packages/fuel-indexer-database/postgres/src/lib.rs @@ -15,7 +15,7 @@ use std::time::Instant; use fuel_indexer_metrics::METRICS; #[cfg(feature = "metrics")] -use fuel_indexer_macro_utils::metrics; +use fuel_indexer_macros::metrics; use chrono::{DateTime, NaiveDateTime, Utc}; diff --git a/packages/fuel-indexer-macros/macro-utils/Cargo.toml b/packages/fuel-indexer-macros/macro-utils/Cargo.toml index 1ec9242ad..57b841a61 100644 --- a/packages/fuel-indexer-macros/macro-utils/Cargo.toml +++ b/packages/fuel-indexer-macros/macro-utils/Cargo.toml @@ -9,9 +9,6 @@ repository = { workspace = true } rust-version = { workspace = true } description = "Fuel Indexer Macro Utils" -[lib] -proc-macro = true - [dependencies] proc-macro-error = "1.0" proc-macro2 = "1.0" diff --git a/packages/fuel-indexer-macros/macro-utils/src/lib.rs b/packages/fuel-indexer-macros/macro-utils/src/lib.rs index 8c9e37f89..e69de29bb 100644 --- a/packages/fuel-indexer-macros/macro-utils/src/lib.rs +++ b/packages/fuel-indexer-macros/macro-utils/src/lib.rs @@ -1,55 +0,0 @@ -extern crate proc_macro; - -use proc_macro::TokenStream; -use proc_macro_error::proc_macro_error; -use quote::quote; -use syn::{parse_macro_input, ItemFn}; - -fn process_with_prometheus_metrics(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as ItemFn); - let fn_name = &ast.sig.ident; - let label = fn_name.to_string(); - let fn_inputs = &ast.sig.inputs; - let fn_output = &ast.sig.output; - let fn_vis = &ast.vis; - let block = &ast.block; - - let (asyncness, awaitness) = if ast.sig.asyncness.is_none() { - (quote! {}, quote! {}) - } else { - (quote! {async}, quote! {.await}) - }; - - let input_idents = fn_inputs - .iter() - .map(|input| match input { - syn::FnArg::Typed(typed) => typed.pat.clone(), - syn::FnArg::Receiver(_) => panic!("`self` arguments are not supported"), - }) - .collect::>(); - - let gen = quote! { - #fn_vis #asyncness fn #fn_name(#fn_inputs) #fn_output { - let result = { - let start_time = Instant::now(); - #asyncness fn inner(#fn_inputs) #fn_output #block - let res = inner(#(#input_idents),*)#awaitness; - - METRICS - .db - .postgres - .record(#label, start_time.elapsed().as_millis() as f64); - res - }; - result - } - }; - - gen.into() -} - -#[proc_macro_error] -#[proc_macro_attribute] -pub fn metrics(_attrs: TokenStream, input: TokenStream) -> TokenStream { - process_with_prometheus_metrics(input) -} diff --git a/packages/fuel-indexer-macros/src/lib.rs b/packages/fuel-indexer-macros/src/lib.rs index ad6d2282c..3b63ca532 100644 --- a/packages/fuel-indexer-macros/src/lib.rs +++ b/packages/fuel-indexer-macros/src/lib.rs @@ -10,6 +10,7 @@ pub(crate) mod schema; pub(crate) mod wasm; use indexer::process_indexer_module; +use prometheus_metrics::process_with_prometheus_metrics use proc_macro::TokenStream; #[proc_macro_error::proc_macro_error] @@ -17,3 +18,9 @@ use proc_macro::TokenStream; pub fn indexer(attrs: TokenStream, item: TokenStream) -> TokenStream { process_indexer_module(attrs, item) } + +#[proc_macro_error::proc_macro_error] +#[proc_macro_attribute] +pub fn metrics(_attrs: TokenStream, input: TokenStream) -> TokenStream { + process_with_prometheus_metrics(input) +} \ No newline at end of file diff --git a/packages/fuel-indexer-macros/src/prometheus_metrics.rs b/packages/fuel-indexer-macros/src/prometheus_metrics.rs new file mode 100644 index 000000000..a2f6d1979 --- /dev/null +++ b/packages/fuel-indexer-macros/src/prometheus_metrics.rs @@ -0,0 +1,49 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use proc_macro_error::proc_macro_error; +use quote::quote; +use syn::{parse_macro_input, ItemFn}; + +fn process_with_prometheus_metrics(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as ItemFn); + let fn_name = &ast.sig.ident; + let label = fn_name.to_string(); + let fn_inputs = &ast.sig.inputs; + let fn_output = &ast.sig.output; + let fn_vis = &ast.vis; + let block = &ast.block; + + let (asyncness, awaitness) = if ast.sig.asyncness.is_none() { + (quote! {}, quote! {}) + } else { + (quote! {async}, quote! {.await}) + }; + + let input_idents = fn_inputs + .iter() + .map(|input| match input { + syn::FnArg::Typed(typed) => typed.pat.clone(), + syn::FnArg::Receiver(_) => panic!("`self` arguments are not supported"), + }) + .collect::>(); + + let gen = quote! { + #fn_vis #asyncness fn #fn_name(#fn_inputs) #fn_output { + let result = { + let start_time = Instant::now(); + #asyncness fn inner(#fn_inputs) #fn_output #block + let res = inner(#(#input_idents),*)#awaitness; + + METRICS + .db + .postgres + .record(#label, start_time.elapsed().as_millis() as f64); + res + }; + result + } + }; + + gen.into() +} \ No newline at end of file From 226b1636d6eed53afa826acb8b55368ae4799c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Fri, 27 Oct 2023 11:42:52 +0100 Subject: [PATCH 09/13] comment --- packages/fuel-indexer-lib/src/graphql/constants.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/fuel-indexer-lib/src/graphql/constants.rs b/packages/fuel-indexer-lib/src/graphql/constants.rs index c0db43da2..a74922bb7 100644 --- a/packages/fuel-indexer-lib/src/graphql/constants.rs +++ b/packages/fuel-indexer-lib/src/graphql/constants.rs @@ -62,6 +62,7 @@ lazy_static! { "Option", ]); + /// The mapping of Sway types to GraphQL types used in automatic GraphQL schema generation. pub static ref ABI_TYPE_MAP: HashMap<&'static str, &'static str> = HashMap::from_iter([ ("u128", "U128"), ("u64", "U64"), From efaea477977f18a2113f5c56ebceb1fd1da27757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Fri, 27 Oct 2023 14:27:16 +0100 Subject: [PATCH 10/13] add fuel-indexer-metrics-macros crate --- Cargo.lock | 23 ++++++++++++++++-- Cargo.toml | 5 +++- .../fuel-indexer-database/postgres/Cargo.toml | 4 ++-- .../fuel-indexer-database/postgres/src/lib.rs | 2 +- packages/fuel-indexer-lib/Cargo.toml | 1 - packages/fuel-indexer-lib/src/graphql/mod.rs | 1 - .../macro-utils/Cargo.toml | 11 ++++++++- .../macro-utils/src/lib.rs | 1 + .../macro-utils/src/schema.rs} | 24 ++++++++++--------- packages/fuel-indexer-macros/src/lib.rs | 7 ------ .../fuel-indexer-metrics-macros/Cargo.toml | 20 ++++++++++++++++ .../src/lib.rs} | 6 +++-- plugins/forc-index/Cargo.toml | 1 + plugins/forc-index/src/ops/forc_index_new.rs | 2 +- 14 files changed, 78 insertions(+), 30 deletions(-) rename packages/{fuel-indexer-lib/src/graphql/schema_gen.rs => fuel-indexer-macros/macro-utils/src/schema.rs} (86%) create mode 100644 packages/fuel-indexer-metrics-macros/Cargo.toml rename packages/{fuel-indexer-macros/src/prometheus_metrics.rs => fuel-indexer-metrics-macros/src/lib.rs} (92%) diff --git a/Cargo.lock b/Cargo.lock index c3df8143d..325243b19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2683,6 +2683,7 @@ dependencies = [ "forc-util", "fuel-indexer-database-types", "fuel-indexer-lib", + "fuel-indexer-macro-utils", "fuel-tx 0.35.3", "fuels", "hex", @@ -3346,7 +3347,6 @@ dependencies = [ "clap 3.2.25", "fuel-abi-types 0.3.0", "fuel-indexer-types", - "fuels-code-gen", "http", "lazy_static", "proc-macro2", @@ -3367,9 +3367,18 @@ dependencies = [ name = "fuel-indexer-macro-utils" version = "0.21.1" dependencies = [ + "async-graphql-parser 5.0.10", + "async-graphql-value 5.0.10", + "fuel-abi-types 0.3.0", + "fuel-indexer-lib", + "fuels", + "fuels-code-gen", + "lazy_static", "proc-macro-error", "proc-macro2", "quote", + "serde_json", + "sha2 0.10.7", "syn 2.0.29", ] @@ -3405,6 +3414,16 @@ dependencies = [ "prometheus-client 0.20.0", ] +[[package]] +name = "fuel-indexer-metrics-macros" +version = "0.21.1" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "fuel-indexer-plugin" version = "0.21.1" @@ -3437,8 +3456,8 @@ dependencies = [ "chrono", "fuel-indexer-database-types", "fuel-indexer-lib", - "fuel-indexer-macro-utils", "fuel-indexer-metrics", + "fuel-indexer-metrics-macros", "sqlx", "tracing", "uuid 1.4.1", diff --git a/Cargo.toml b/Cargo.toml index a78c0a2a1..4f7ed4f39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "examples/greetings/greetings-fuel-client", "examples/greetings/greetings-indexer", "examples/hello-world/hello-world", + "packages/fuel-indexer", "packages/fuel-indexer-api-server", "packages/fuel-indexer-benchmarks", "packages/fuel-indexer-database", @@ -18,6 +19,7 @@ members = [ "packages/fuel-indexer-macros", "packages/fuel-indexer-macros/macro-utils", "packages/fuel-indexer-metrics", + "packages/fuel-indexer-metrics-macros", "packages/fuel-indexer-plugin", "packages/fuel-indexer-schema", "packages/fuel-indexer-tests", @@ -27,7 +29,6 @@ members = [ "packages/fuel-indexer-tests/indexers/simple-wasm/simple-wasm", "packages/fuel-indexer-types", "packages/fuel-indexer-utils", - "packages/fuel-indexer", "plugins/forc-index", "plugins/forc-postgres", ] @@ -41,6 +42,7 @@ default-members = [ "packages/fuel-indexer-graphql", "packages/fuel-indexer-lib", "packages/fuel-indexer-macros", + "packages/fuel-indexer-metrics-macros", "packages/fuel-indexer-metrics", "packages/fuel-indexer-plugin", "packages/fuel-indexer-schema", @@ -80,6 +82,7 @@ fuel-indexer-lib = { version = "0.21.1", path = "./packages/fuel-indexer-lib" } fuel-indexer-macro-utils = { version = "0.21.1", path = "./packages/fuel-indexer-macros/macro-utils" } fuel-indexer-macros = { version = "0.21.1", path = "./packages/fuel-indexer-macros", default-features = false } fuel-indexer-metrics = { version = "0.21.1", path = "./packages/fuel-indexer-metrics" } +fuel-indexer-metrics-macros = { version = "0.21.1", path = "./packages/fuel-indexer-metrics-macros" } fuel-indexer-plugin = { version = "0.21.1", path = "./packages/fuel-indexer-plugin", default-features = false } fuel-indexer-postgres = { version = "0.21.1", path = "./packages/fuel-indexer-database/postgres" } fuel-indexer-schema = { version = "0.21.1", path = "./packages/fuel-indexer-schema", default-features = false } diff --git a/packages/fuel-indexer-database/postgres/Cargo.toml b/packages/fuel-indexer-database/postgres/Cargo.toml index 8ab368af1..c44e3ac77 100644 --- a/packages/fuel-indexer-database/postgres/Cargo.toml +++ b/packages/fuel-indexer-database/postgres/Cargo.toml @@ -14,12 +14,12 @@ bigdecimal = { version = "0.3" } chrono = "0.4.24" fuel-indexer-database-types = { workspace = true } fuel-indexer-lib = { workspace = true } -fuel-indexer-macros = { workspace = true, optional = true } fuel-indexer-metrics = { workspace = true, optional = true } +fuel-indexer-metrics-macros = { workspace = true, optional = true } sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "postgres", "offline", "time", "chrono", "bigdecimal"] } tracing = { workspace = true } uuid = { version = "1.3", features = ["v4"] } [features] default = ["metrics"] -metrics = ["fuel-indexer-macros", "fuel-indexer-metrics"] +metrics = ["fuel-indexer-metrics-macros", "fuel-indexer-metrics"] diff --git a/packages/fuel-indexer-database/postgres/src/lib.rs b/packages/fuel-indexer-database/postgres/src/lib.rs index fba00e681..e7b33249e 100644 --- a/packages/fuel-indexer-database/postgres/src/lib.rs +++ b/packages/fuel-indexer-database/postgres/src/lib.rs @@ -15,7 +15,7 @@ use std::time::Instant; use fuel_indexer_metrics::METRICS; #[cfg(feature = "metrics")] -use fuel_indexer_macros::metrics; +use fuel_indexer_metrics_macros::metrics; use chrono::{DateTime, NaiveDateTime, Utc}; diff --git a/packages/fuel-indexer-lib/Cargo.toml b/packages/fuel-indexer-lib/Cargo.toml index ab554e45f..fd61fe719 100644 --- a/packages/fuel-indexer-lib/Cargo.toml +++ b/packages/fuel-indexer-lib/Cargo.toml @@ -17,7 +17,6 @@ bincode = { workspace = true } clap = { features = ["cargo", "derive", "env"], workspace = true } fuel-abi-types = "0.3" fuel-indexer-types = { workspace = true } -fuels-code-gen = { version = "0.46", default-features = false } http = { version = "0.2", default-features = false } lazy_static = { version = "1.4" } proc-macro2 = "1.0" diff --git a/packages/fuel-indexer-lib/src/graphql/mod.rs b/packages/fuel-indexer-lib/src/graphql/mod.rs index 6d2e5bc7e..69bed9628 100644 --- a/packages/fuel-indexer-lib/src/graphql/mod.rs +++ b/packages/fuel-indexer-lib/src/graphql/mod.rs @@ -1,6 +1,5 @@ pub mod constants; pub mod parser; -pub mod schema_gen; pub mod types; pub mod validator; diff --git a/packages/fuel-indexer-macros/macro-utils/Cargo.toml b/packages/fuel-indexer-macros/macro-utils/Cargo.toml index 57b841a61..6913f303c 100644 --- a/packages/fuel-indexer-macros/macro-utils/Cargo.toml +++ b/packages/fuel-indexer-macros/macro-utils/Cargo.toml @@ -10,7 +10,16 @@ rust-version = { workspace = true } description = "Fuel Indexer Macro Utils" [dependencies] +async-graphql-parser = "5.0" +async-graphql-value = "5.0" +fuel-abi-types = "0.3" +fuel-indexer-lib = { workspace = true, default-features = true } +fuels = { workspace = true } +fuels-code-gen = { version = "0.46", default-features = false } +lazy_static = "1.4" proc-macro-error = "1.0" proc-macro2 = "1.0" -quote = "1" +quote = "1.0" +serde_json = { workspace = true } +sha2 = "0.10" syn = { version = "2.0", features = ["full"] } diff --git a/packages/fuel-indexer-macros/macro-utils/src/lib.rs b/packages/fuel-indexer-macros/macro-utils/src/lib.rs index e69de29bb..7a507868e 100644 --- a/packages/fuel-indexer-macros/macro-utils/src/lib.rs +++ b/packages/fuel-indexer-macros/macro-utils/src/lib.rs @@ -0,0 +1 @@ +pub mod schema; \ No newline at end of file diff --git a/packages/fuel-indexer-lib/src/graphql/schema_gen.rs b/packages/fuel-indexer-macros/macro-utils/src/schema.rs similarity index 86% rename from packages/fuel-indexer-lib/src/graphql/schema_gen.rs rename to packages/fuel-indexer-macros/macro-utils/src/schema.rs index 172a32963..af08e64c8 100644 --- a/packages/fuel-indexer-lib/src/graphql/schema_gen.rs +++ b/packages/fuel-indexer-macros/macro-utils/src/schema.rs @@ -1,8 +1,8 @@ -use crate::{ - graphql::constants::ABI_TYPE_MAP, +use fuel_abi_types::abi::program::{ProgramABI, TypeDeclaration}; +use fuel_indexer_lib::{ + constants, graphql, helpers::{is_unit_type, strip_callpath_from_type_field}, }; -use fuel_abi_types::abi::program::{ProgramABI, TypeDeclaration}; use std::collections::HashMap; // Given a `TypeDeclaration` for an ABI enum, generate a corresponding GraphQL @@ -69,7 +69,7 @@ fn decode_struct( // Enum field. if let Some(ty) = ty.strip_prefix("enum ") { - if crate::constants::RESERVED_TYPEDEF_NAMES.contains(ty) { + if constants::RESERVED_TYPEDEF_NAMES.contains(ty) { // For reserved type names, we take the type as is. fields.push(format!("{}: {}!", c.name, ty)); } else { @@ -78,7 +78,7 @@ fn decode_struct( } // Struct field. } else if let Some(ty) = ty.strip_prefix("struct ") { - if crate::constants::RESERVED_TYPEDEF_NAMES.contains(ty) { + if constants::RESERVED_TYPEDEF_NAMES.contains(ty) { // For reserved type names, we take the type as is. fields.push(format!("{}: {}!", c.name, ty)); } else { @@ -119,23 +119,25 @@ pub fn generate_schema(json_abi: &std::path::Path) -> String { for ty in abi_types.iter() { // Skip all generic types - if crate::constants::IGNORED_GENERIC_METADATA.contains(ty.type_field.as_str()) { + if constants::IGNORED_GENERIC_METADATA.contains(ty.type_field.as_str()) { continue; } // Only generate schema types for structs and enums if let Some(name) = ty.type_field.strip_prefix("struct ") { - if crate::constants::RESERVED_TYPEDEF_NAMES.contains(name) - || crate::constants::GENERIC_STRUCTS.contains(name) + if constants::RESERVED_TYPEDEF_NAMES.contains(name) + || constants::GENERIC_STRUCTS.contains(name) { continue; } - if let Some(result) = decode_struct(&ABI_TYPE_MAP, &abi_types, ty) { + if let Some(result) = + decode_struct(&graphql::constants::ABI_TYPE_MAP, &abi_types, ty) + { output.push(result); } } else if let Some(name) = ty.type_field.strip_prefix("enum ") { - if crate::constants::RESERVED_TYPEDEF_NAMES.contains(name) - || crate::constants::GENERIC_STRUCTS.contains(name) + if constants::RESERVED_TYPEDEF_NAMES.contains(name) + || constants::GENERIC_STRUCTS.contains(name) { continue; } diff --git a/packages/fuel-indexer-macros/src/lib.rs b/packages/fuel-indexer-macros/src/lib.rs index 3b63ca532..14f0a50b3 100644 --- a/packages/fuel-indexer-macros/src/lib.rs +++ b/packages/fuel-indexer-macros/src/lib.rs @@ -10,17 +10,10 @@ pub(crate) mod schema; pub(crate) mod wasm; use indexer::process_indexer_module; -use prometheus_metrics::process_with_prometheus_metrics use proc_macro::TokenStream; #[proc_macro_error::proc_macro_error] #[proc_macro_attribute] pub fn indexer(attrs: TokenStream, item: TokenStream) -> TokenStream { process_indexer_module(attrs, item) -} - -#[proc_macro_error::proc_macro_error] -#[proc_macro_attribute] -pub fn metrics(_attrs: TokenStream, input: TokenStream) -> TokenStream { - process_with_prometheus_metrics(input) } \ No newline at end of file diff --git a/packages/fuel-indexer-metrics-macros/Cargo.toml b/packages/fuel-indexer-metrics-macros/Cargo.toml new file mode 100644 index 000000000..c563d16bf --- /dev/null +++ b/packages/fuel-indexer-metrics-macros/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "fuel-indexer-metrics-macros" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +proc-macro-error = "1.0" +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full"] } \ No newline at end of file diff --git a/packages/fuel-indexer-macros/src/prometheus_metrics.rs b/packages/fuel-indexer-metrics-macros/src/lib.rs similarity index 92% rename from packages/fuel-indexer-macros/src/prometheus_metrics.rs rename to packages/fuel-indexer-metrics-macros/src/lib.rs index a2f6d1979..b6ce6c8f9 100644 --- a/packages/fuel-indexer-macros/src/prometheus_metrics.rs +++ b/packages/fuel-indexer-metrics-macros/src/lib.rs @@ -5,7 +5,9 @@ use proc_macro_error::proc_macro_error; use quote::quote; use syn::{parse_macro_input, ItemFn}; -fn process_with_prometheus_metrics(input: TokenStream) -> TokenStream { +#[proc_macro_error] +#[proc_macro_attribute] +pub fn metrics(_attrs: TokenStream, input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as ItemFn); let fn_name = &ast.sig.ident; let label = fn_name.to_string(); @@ -46,4 +48,4 @@ fn process_with_prometheus_metrics(input: TokenStream) -> TokenStream { }; gen.into() -} \ No newline at end of file +} diff --git a/plugins/forc-index/Cargo.toml b/plugins/forc-index/Cargo.toml index 767516404..76edb146a 100644 --- a/plugins/forc-index/Cargo.toml +++ b/plugins/forc-index/Cargo.toml @@ -20,6 +20,7 @@ forc-tracing = { version = "0.31", default-features = false } forc-util = { version = "0.35.0" } fuel-indexer-database-types = { workspace = true } fuel-indexer-lib = { workspace = true } +fuel-indexer-macro-utils = { workspace = true } fuel-tx = { features = ["builder"], workspace = true } fuels = { default-features = false, workspace = true } hex = "0.4.3" diff --git a/plugins/forc-index/src/ops/forc_index_new.rs b/plugins/forc-index/src/ops/forc_index_new.rs index 995df1298..854c4e42a 100644 --- a/plugins/forc-index/src/ops/forc_index_new.rs +++ b/plugins/forc-index/src/ops/forc_index_new.rs @@ -156,7 +156,7 @@ pub fn create_indexer(command: NewCommand) -> anyhow::Result<()> { fs::create_dir_all(Path::new(&project_dir).join("schema"))?; let schema_contents = { if let Some(json_abi) = json_abi { - fuel_indexer_lib::graphql::schema_gen::generate_schema(&json_abi) + fuel_indexer_macro_utils::schema::generate_schema(&json_abi) } else { defaults::default_indexer_schema() } From 7bea7ec1dc5cb093d5fc309dc6f0971451435cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Fri, 27 Oct 2023 14:54:45 +0100 Subject: [PATCH 11/13] move code --- Cargo.lock | 11 +---- Cargo.toml | 2 +- packages/fuel-indexer-lib/Cargo.toml | 1 - packages/fuel-indexer-lib/src/helpers.rs | 47 ------------------- packages/fuel-indexer-lib/src/lib.rs | 1 - packages/fuel-indexer-macros/Cargo.toml | 1 + .../macro-utils/Cargo.toml | 9 ---- .../macro-utils/src/lib.rs | 4 +- .../macro-utils/src/schema.rs | 7 +-- packages/fuel-indexer-macros/src/indexer.rs | 8 ++-- packages/fuel-indexer-macros/src/lib.rs | 2 +- 11 files changed, 13 insertions(+), 80 deletions(-) delete mode 100644 packages/fuel-indexer-lib/src/helpers.rs diff --git a/Cargo.lock b/Cargo.lock index 3c6c49008..d0e56c179 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3352,7 +3352,6 @@ dependencies = [ "async-graphql-value 5.0.10", "bincode", "clap 3.2.25", - "fuel-abi-types 0.3.0", "fuel-indexer-types", "http", "lazy_static", @@ -3372,19 +3371,10 @@ dependencies = [ name = "fuel-indexer-macro-utils" version = "0.22.0" dependencies = [ - "async-graphql-parser 5.0.10", - "async-graphql-value 5.0.10", "fuel-abi-types 0.3.0", "fuel-indexer-lib", - "fuels", "fuels-code-gen", - "lazy_static", - "proc-macro-error", - "proc-macro2", - "quote", "serde_json", - "sha2 0.10.7", - "syn 2.0.29", ] [[package]] @@ -3396,6 +3386,7 @@ dependencies = [ "fuel-abi-types 0.3.0", "fuel-indexer-database-types", "fuel-indexer-lib", + "fuel-indexer-macro-utils", "fuel-indexer-schema", "fuel-indexer-types", "fuels", diff --git a/Cargo.toml b/Cargo.toml index a7c24bf9e..0fe98621b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ default-members = [ "packages/fuel-indexer-graphql", "packages/fuel-indexer-lib", "packages/fuel-indexer-macros", - "packages/fuel-indexer-metrics-macros", + "packages/fuel-indexer-metrics-macros", "packages/fuel-indexer-metrics", "packages/fuel-indexer-plugin", "packages/fuel-indexer-schema", diff --git a/packages/fuel-indexer-lib/Cargo.toml b/packages/fuel-indexer-lib/Cargo.toml index f4a58f220..b58276d79 100644 --- a/packages/fuel-indexer-lib/Cargo.toml +++ b/packages/fuel-indexer-lib/Cargo.toml @@ -15,7 +15,6 @@ async-graphql-parser = { workspace = true } async-graphql-value = { workspace = true } bincode = { workspace = true } clap = { features = ["cargo", "derive", "env"], workspace = true } -fuel-abi-types = "0.3" fuel-indexer-types = { workspace = true } http = { version = "0.2", default-features = false } lazy_static = { version = "1.4" } diff --git a/packages/fuel-indexer-lib/src/helpers.rs b/packages/fuel-indexer-lib/src/helpers.rs deleted file mode 100644 index 10d87c493..000000000 --- a/packages/fuel-indexer-lib/src/helpers.rs +++ /dev/null @@ -1,47 +0,0 @@ -use fuel_abi_types::abi::program::TypeDeclaration; - -/// Whether a `TypeDeclaration` is tuple type -pub fn is_tuple_type(typ: &TypeDeclaration) -> bool { - let mut type_field_chars = typ.type_field.chars(); - type_field_chars.next().is_some_and(|c| c == '(') - && type_field_chars.next().is_some_and(|c| c != ')') -} - -/// Whether a `TypeDeclaration` is a unit type -pub fn is_unit_type(typ: &TypeDeclaration) -> bool { - let mut type_field_chars = typ.type_field.chars(); - type_field_chars.next().is_some_and(|c| c == '(') - && type_field_chars.next().is_some_and(|c| c == ')') -} - -/// Whether the `TypeDeclaration`` should be used to build struct fields and decoders -pub fn is_non_decodable_type(typ: &TypeDeclaration) -> bool { - is_tuple_type(typ) - || is_unit_type(typ) - || crate::constants::IGNORED_GENERIC_METADATA.contains(typ.type_field.as_str()) -} - -/// Strip the call path from the type field of a `TypeDeclaration`. -/// -/// It is possible that the type field for a `TypeDeclaration` contains a -/// fully-qualified path (e.g. `std::address::Address` as opposed to `Address`). -/// Path separators are not allowed to be used as part of an identifier, so this -/// function removes the qualifying path while keeping the type keyword. -pub fn strip_callpath_from_type_field(mut typ: TypeDeclaration) -> TypeDeclaration { - if is_non_decodable_type(&typ) { - return typ; - } - - let mut s = typ.type_field.split_whitespace(); - typ.type_field = - if let (Some(keyword), Some(fully_qualified_type_path)) = (s.next(), s.last()) { - if let Some(slug) = fully_qualified_type_path.split("::").last() { - [keyword, slug].join(" ") - } else { - unreachable!("All types should be formed with a keyword and call path") - } - } else { - typ.type_field - }; - typ -} diff --git a/packages/fuel-indexer-lib/src/lib.rs b/packages/fuel-indexer-lib/src/lib.rs index dc627e32c..0b6c58e4b 100644 --- a/packages/fuel-indexer-lib/src/lib.rs +++ b/packages/fuel-indexer-lib/src/lib.rs @@ -7,7 +7,6 @@ pub mod config; pub mod constants; pub mod defaults; pub mod graphql; -pub mod helpers; pub mod manifest; pub mod utils; diff --git a/packages/fuel-indexer-macros/Cargo.toml b/packages/fuel-indexer-macros/Cargo.toml index 6a193bc45..60e291759 100644 --- a/packages/fuel-indexer-macros/Cargo.toml +++ b/packages/fuel-indexer-macros/Cargo.toml @@ -18,6 +18,7 @@ async-graphql-value = "5.0" fuel-abi-types = "0.3" fuel-indexer-database-types = { workspace = true } fuel-indexer-lib = { workspace = true, default-features = true } +fuel-indexer-macro-utils = { workspace = true } fuel-indexer-schema = { workspace = true, default-features = false } fuel-indexer-types = { workspace = true } fuels = { workspace = true } diff --git a/packages/fuel-indexer-macros/macro-utils/Cargo.toml b/packages/fuel-indexer-macros/macro-utils/Cargo.toml index 6913f303c..a65e95c02 100644 --- a/packages/fuel-indexer-macros/macro-utils/Cargo.toml +++ b/packages/fuel-indexer-macros/macro-utils/Cargo.toml @@ -10,16 +10,7 @@ rust-version = { workspace = true } description = "Fuel Indexer Macro Utils" [dependencies] -async-graphql-parser = "5.0" -async-graphql-value = "5.0" fuel-abi-types = "0.3" fuel-indexer-lib = { workspace = true, default-features = true } -fuels = { workspace = true } fuels-code-gen = { version = "0.46", default-features = false } -lazy_static = "1.4" -proc-macro-error = "1.0" -proc-macro2 = "1.0" -quote = "1.0" serde_json = { workspace = true } -sha2 = "0.10" -syn = { version = "2.0", features = ["full"] } diff --git a/packages/fuel-indexer-macros/macro-utils/src/lib.rs b/packages/fuel-indexer-macros/macro-utils/src/lib.rs index 7a507868e..1e9347378 100644 --- a/packages/fuel-indexer-macros/macro-utils/src/lib.rs +++ b/packages/fuel-indexer-macros/macro-utils/src/lib.rs @@ -1 +1,3 @@ -pub mod schema; \ No newline at end of file +#![deny(unused_crate_dependencies)] +pub mod helpers; +pub mod schema; diff --git a/packages/fuel-indexer-macros/macro-utils/src/schema.rs b/packages/fuel-indexer-macros/macro-utils/src/schema.rs index af08e64c8..651b38e36 100644 --- a/packages/fuel-indexer-macros/macro-utils/src/schema.rs +++ b/packages/fuel-indexer-macros/macro-utils/src/schema.rs @@ -1,8 +1,5 @@ use fuel_abi_types::abi::program::{ProgramABI, TypeDeclaration}; -use fuel_indexer_lib::{ - constants, graphql, - helpers::{is_unit_type, strip_callpath_from_type_field}, -}; +use fuel_indexer_lib::{constants, graphql}; use std::collections::HashMap; // Given a `TypeDeclaration` for an ABI enum, generate a corresponding GraphQL @@ -112,7 +109,7 @@ pub fn generate_schema(json_abi: &std::path::Path) -> String { let abi_types: Vec = abi .types .into_iter() - .map(strip_callpath_from_type_field) + .map(crate::helpers::strip_callpath_from_type_field) .collect(); let mut output: Vec = vec![]; diff --git a/packages/fuel-indexer-macros/src/indexer.rs b/packages/fuel-indexer-macros/src/indexer.rs index 91dda95ac..c06612612 100644 --- a/packages/fuel-indexer-macros/src/indexer.rs +++ b/packages/fuel-indexer-macros/src/indexer.rs @@ -4,12 +4,12 @@ use crate::{ }; use fuel_abi_types::abi::program::TypeDeclaration; use fuel_indexer_lib::{ - constants::*, - helpers::{is_non_decodable_type, is_tuple_type, strip_callpath_from_type_field}, - manifest::ContractIds, - manifest::Manifest, + constants::*, manifest::ContractIds, manifest::Manifest, utils::workspace_manifest_prefix, }; +use fuel_indexer_macro_utils::helpers::{ + is_non_decodable_type, is_tuple_type, strip_callpath_from_type_field, +}; use fuel_indexer_types::{type_id, FUEL_TYPES_NAMESPACE}; use fuels::{core::codec::resolve_fn_selector, types::param_types::ParamType}; use fuels_code_gen::{Abigen, AbigenTarget, ProgramType}; diff --git a/packages/fuel-indexer-macros/src/lib.rs b/packages/fuel-indexer-macros/src/lib.rs index 18965bf6a..d7f941b3e 100644 --- a/packages/fuel-indexer-macros/src/lib.rs +++ b/packages/fuel-indexer-macros/src/lib.rs @@ -15,4 +15,4 @@ use proc_macro::TokenStream; #[proc_macro_attribute] pub fn indexer(attrs: TokenStream, item: TokenStream) -> TokenStream { process_indexer_module(attrs, item) -} \ No newline at end of file +} From fbd9522faf30d91399926b2938207d27f71741a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Fri, 27 Oct 2023 18:45:24 +0100 Subject: [PATCH 12/13] fix imports --- packages/fuel-indexer-macros/macro-utils/src/schema.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/fuel-indexer-macros/macro-utils/src/schema.rs b/packages/fuel-indexer-macros/macro-utils/src/schema.rs index 651b38e36..d20cdb09a 100644 --- a/packages/fuel-indexer-macros/macro-utils/src/schema.rs +++ b/packages/fuel-indexer-macros/macro-utils/src/schema.rs @@ -2,6 +2,8 @@ use fuel_abi_types::abi::program::{ProgramABI, TypeDeclaration}; use fuel_indexer_lib::{constants, graphql}; use std::collections::HashMap; +use crate::helpers::{is_unit_type, strip_callpath_from_type_field}; + // Given a `TypeDeclaration` for an ABI enum, generate a corresponding GraphQL // `enum`. // @@ -109,7 +111,7 @@ pub fn generate_schema(json_abi: &std::path::Path) -> String { let abi_types: Vec = abi .types .into_iter() - .map(crate::helpers::strip_callpath_from_type_field) + .map(strip_callpath_from_type_field) .collect(); let mut output: Vec = vec![]; From 42cbc8b4b8ac7b5c8b0d4bc070e2b1ba1028aa56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Wo=C5=9B?= Date: Fri, 27 Oct 2023 18:49:06 +0100 Subject: [PATCH 13/13] add missing file --- .../macro-utils/src/helpers.rs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 packages/fuel-indexer-macros/macro-utils/src/helpers.rs diff --git a/packages/fuel-indexer-macros/macro-utils/src/helpers.rs b/packages/fuel-indexer-macros/macro-utils/src/helpers.rs new file mode 100644 index 000000000..125301841 --- /dev/null +++ b/packages/fuel-indexer-macros/macro-utils/src/helpers.rs @@ -0,0 +1,48 @@ +use fuel_abi_types::abi::program::TypeDeclaration; +use fuel_indexer_lib::constants::IGNORED_GENERIC_METADATA; + +/// Strip the call path from the type field of a `TypeDeclaration`. +/// +/// It is possible that the type field for a `TypeDeclaration` contains a +/// fully-qualified path (e.g. `std::address::Address` as opposed to `Address`). +/// Path separators are not allowed to be used as part of an identifier, so this +/// function removes the qualifying path while keeping the type keyword. +pub fn strip_callpath_from_type_field(mut typ: TypeDeclaration) -> TypeDeclaration { + if is_non_decodable_type(&typ) { + return typ; + } + + let mut s = typ.type_field.split_whitespace(); + typ.type_field = + if let (Some(keyword), Some(fully_qualified_type_path)) = (s.next(), s.last()) { + if let Some(slug) = fully_qualified_type_path.split("::").last() { + [keyword, slug].join(" ") + } else { + unreachable!("All types should be formed with a keyword and call path") + } + } else { + typ.type_field + }; + typ +} + +/// Whether a `TypeDeclaration` is tuple type +pub fn is_tuple_type(typ: &TypeDeclaration) -> bool { + let mut type_field_chars = typ.type_field.chars(); + type_field_chars.next().is_some_and(|c| c == '(') + && type_field_chars.next().is_some_and(|c| c != ')') +} + +/// Whether a `TypeDeclaration` is a unit type +pub fn is_unit_type(typ: &TypeDeclaration) -> bool { + let mut type_field_chars = typ.type_field.chars(); + type_field_chars.next().is_some_and(|c| c == '(') + && type_field_chars.next().is_some_and(|c| c == ')') +} + +/// Whether the `TypeDeclaration` should be used to build struct fields and decoders +pub fn is_non_decodable_type(typ: &TypeDeclaration) -> bool { + is_tuple_type(typ) + || is_unit_type(typ) + || IGNORED_GENERIC_METADATA.contains(typ.type_field.as_str()) +}