From 3a714842b3393f8f0cabf18e536701e490b3ced1 Mon Sep 17 00:00:00 2001 From: Ashok Menon Date: Mon, 17 Jun 2024 16:04:40 +0100 Subject: [PATCH] [chore][GraphQL] Clean-up unused commands ## Description Remove commands to generate examples, markdown and schema from the main binary as we do not use them: - Instead of generating examples, we have hand-crafted examples in our docs. Removing this code also removes a test that forces regeneration of a markdown file from these docs (which we also were not using). - We also never used the output from the `generate-schema` sub-command, because the schema was always available as a file, or via introspection commands from the running service. - Logic for gathering examples to test has been moved into the test file, to avoid including test-only code in the main library. ## Test plan CI --- Cargo.lock | 7 - Cargo.toml | 1 - crates/sui-graphql-rpc/Cargo.toml | 1 - crates/sui-graphql-rpc/docs/examples.md | 1700 ----------------- crates/sui-graphql-rpc/src/commands.rs | 11 - crates/sui-graphql-rpc/src/examples.rs | 240 --- crates/sui-graphql-rpc/src/lib.rs | 1 - crates/sui-graphql-rpc/src/main.rs | 34 - .../tests/examples_validation_tests.rs | 228 ++- 9 files changed, 134 insertions(+), 2089 deletions(-) delete mode 100644 crates/sui-graphql-rpc/docs/examples.md delete mode 100644 crates/sui-graphql-rpc/src/examples.rs diff --git a/Cargo.lock b/Cargo.lock index c766d515b415e..4449deae1a1d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6716,12 +6716,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" -[[package]] -name = "markdown-gen" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8034621d7f1258317ca1dfb9205e3925d27ee4aa2a46620a09c567daf0310562" - [[package]] name = "match_opt" version = "0.1.2" @@ -13353,7 +13347,6 @@ dependencies = [ "insta", "itertools 0.10.5", "lru 0.10.0", - "markdown-gen", "move-binary-format", "move-bytecode-utils", "move-core-types", diff --git a/Cargo.toml b/Cargo.toml index 55f30a7447f6b..78b95b57b41e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -369,7 +369,6 @@ jsonrpsee = { git = "https://github.com/wlmyng/jsonrpsee.git", rev = "b1b3007847 json_to_table = { git = "https://github.com/zhiburt/tabled/", rev = "e449317a1c02eb6b29e409ad6617e5d9eb7b3bd4" } leb128 = "0.2.5" lru = "0.10" -markdown-gen = "1.2.1" match_opt = "0.1.2" miette = { version = "7", features = ["fancy"] } mime = "0.3" diff --git a/crates/sui-graphql-rpc/Cargo.toml b/crates/sui-graphql-rpc/Cargo.toml index 87c8a1571a9ef..12414fd4888c7 100644 --- a/crates/sui-graphql-rpc/Cargo.toml +++ b/crates/sui-graphql-rpc/Cargo.toml @@ -32,7 +32,6 @@ lru.workspace = true move-binary-format.workspace = true move-disassembler.workspace = true move-ir-types.workspace = true -markdown-gen.workspace = true mysten-metrics.workspace = true mysten-network.workspace = true move-core-types.workspace = true diff --git a/crates/sui-graphql-rpc/docs/examples.md b/crates/sui-graphql-rpc/docs/examples.md deleted file mode 100644 index b227665131112..0000000000000 --- a/crates/sui-graphql-rpc/docs/examples.md +++ /dev/null @@ -1,1700 +0,0 @@ -# Sui GraphQL Examples -### [Address](#0) -####   [Address](#0) -####   [Transaction Block Connection](#1) -### [Balance Connection](#1) -####   [Balance Connection](#65535) -### [Chain Id](#2) -####   [Chain Id](#131070) -### [Checkpoint](#3) -####   [At Digest](#196605) -####   [At Seq Num](#196606) -####   [First Two Tx Blocks For Checkpoint](#196607) -####   [Latest Checkpoint](#196608) -####   [Multiple Selections](#196609) -####   [With Timestamp Tx Block Live Objects](#196610) -####   [With Tx Sent Addr Filter](#196611) -### [Checkpoint Connection](#4) -####   [Ascending Fetch](#262140) -####   [First Ten After Checkpoint](#262141) -####   [Last Ten After Checkpoint](#262142) -### [Coin Connection](#5) -####   [Coin Connection](#327675) -### [Coin Metadata](#6) -####   [Coin Metadata](#393210) -### [Epoch](#7) -####   [Latest Epoch](#458745) -####   [Specific Epoch](#458746) -####   [With Checkpoint Connection](#458747) -####   [With Tx Block Connection](#458748) -####   [With Tx Block Connection Latest Epoch](#458749) -### [Event Connection](#8) -####   [Event Connection](#524280) -####   [Filter By Emitting Package Module And Event Type](#524281) -####   [Filter By Sender](#524282) -### [Name Service](#9) -####   [Name Service](#589815) -### [Object](#10) -####   [Object](#655350) -### [Object Connection](#11) -####   [Filter Object Ids](#720885) -####   [Filter On Generic Type](#720886) -####   [Filter On Type](#720887) -####   [Filter Owner](#720888) -####   [Object Connection](#720889) -### [Owner](#12) -####   [Dynamic Field](#786420) -####   [Dynamic Field Connection](#786421) -####   [Dynamic Object Field](#786422) -####   [Owner](#786423) -### [Protocol Configs](#13) -####   [Key Value](#851955) -####   [Key Value Feature Flag](#851956) -####   [Specific Config](#851957) -####   [Specific Feature Flag](#851958) -### [Service Config](#14) -####   [Service Config](#917490) -### [Stake Connection](#15) -####   [Stake Connection](#983025) -### [Sui System State Summary](#16) -####   [Sui System State Summary](#1048560) -### [Transaction Block](#17) -####   [Transaction Block](#1114095) -####   [Transaction Block Kind](#1114096) -### [Transaction Block Connection](#18) -####   [Before After Checkpoint](#1179630) -####   [Changed Object Filter](#1179631) -####   [Input Object Filter](#1179632) -####   [Input Object Sign Addr Filter](#1179633) -####   [Package Filter](#1179634) -####   [Package Module Filter](#1179635) -####   [Package Module Func Filter](#1179636) -####   [Recv Addr Filter](#1179637) -####   [Sign Addr Filter](#1179638) -####   [Tx Ids Filter](#1179639) -####   [Tx Kind Filter](#1179640) -####   [With Defaults Ascending](#1179641) -### [Transaction Block Effects](#19) -####   [Transaction Block Effects](#1245165) -## -## Address -### -### Address -#### Get the address' balance and its coins' id and type - ->
{
->  address(
->    address: "0x5094652429957619e6efa79a404a6714d1126e63f551f4b6c7fb76440f8118c9"
->  ) {
->    address
->    balance {
->      coinType {
->        repr
->      }
->      coinObjectCount
->      totalBalance
->    }
->    coins {
->      nodes {
->        contents {
->          type {
->            repr
->          }
->        }
->      }
->    }
->  }
->}
- -### -### Transaction Block Connection -#### See examples in Query::transactionBlocks as this is similar behavior -#### to the `transactionBlocks` in Query but supports additional -#### `AddressTransactionBlockRelationship` filter -#### Filtering on package where the signer of the TX is the current -#### address and displaying the transaction's sender and the gas price -#### and budget. - ->
# See examples in Query::transactionBlocks as this is similar behavior
-># to the `transactionBlocks` in Query but supports additional
-># `AddressTransactionBlockRelationship` filter
->
-># Filtering on package where the signer of the TX is the current
-># address and displaying the transaction's sender and the gas price
-># and budget.
->query transaction_block_with_relation_filter {
->  address(address: "0x2") {
->    transactionBlocks(relation: SIGN, filter: { function: "0x2" }) {
->      nodes {
->        sender {
->          address
->        }
->        gasInput {
->          gasPrice
->          gasBudget
->        }
->      }
->    }
->  }
->}
- -## -## Balance Connection -### -### Balance Connection -#### Query the balance for objects of type COIN and then for each coin -#### get the coin type, the number of objects, and the total balance - ->
{
->  address(
->    address: "0x5094652429957619e6efa79a404a6714d1126e63f551f4b6c7fb76440f8118c9"
->  ) {
->    balance(
->      type: "0xc060006111016b8a020ad5b33834984a437aaa7d3c74c18e09a95d48aceab08c::coin::COIN"
->    ) {
->      coinObjectCount
->      totalBalance
->    }
->    balances {
->      nodes {
->        coinType {
->          repr
->        }
->        coinObjectCount
->        totalBalance
->      }
->      pageInfo {
->        endCursor
->      }
->    }
->  }
->}
- -## -## Chain Id -### -### Chain Id -#### Returns the chain identifier for the chain that the server is tracking - ->
{
->  chainIdentifier
->}
- -## -## Checkpoint -### -### At Digest -#### Get the checkpoint's information at a particular digest - ->
{
->  checkpoint(id: { digest: "GaDeWEfbSQCQ8FBQHUHVdm4KjrnbgMqEZPuhStoq5njU" }) {
->    digest
->    sequenceNumber
->    validatorSignatures
->    previousCheckpointDigest
->    networkTotalTransactions
->    rollingGasSummary {
->      computationCost
->      storageCost
->      storageRebate
->      nonRefundableStorageFee
->    }
->    epoch {
->      epochId
->      referenceGasPrice
->      startTimestamp
->      endTimestamp
->    }
->  }
->}
- -### -### At Seq Num -#### Get the checkpoint's information at a particular sequence number - ->
{
->  checkpoint(id: { sequenceNumber: 10 }) {
->    digest
->    sequenceNumber
->    validatorSignatures
->    previousCheckpointDigest
->    networkTotalTransactions
->    rollingGasSummary {
->      computationCost
->      storageCost
->      storageRebate
->      nonRefundableStorageFee
->    }
->    epoch {
->      epochId
->      referenceGasPrice
->      startTimestamp
->      endTimestamp
->    }
->  }
->}
- -### -### First Two Tx Blocks For Checkpoint -#### Get data for the first two transaction blocks of checkpoint at sequence number 10 - ->
{
->  checkpoint(id: { sequenceNumber: 10 }) {
->    transactionBlocks(first: 2) {
->      edges {
->        node {
->          kind {
->            __typename
->          }
->          digest
->          sender {
->            address
->          }
->          expiration {
->            epochId
->          }
->        }
->      }
->      pageInfo {
->        startCursor
->        hasNextPage
->        hasPreviousPage
->        endCursor
->      }
->    }
->  }
->}
- -### -### Latest Checkpoint -#### Latest checkpoint's data - ->
{
->  checkpoint {
->    digest
->    sequenceNumber
->    validatorSignatures
->    previousCheckpointDigest
->    networkTotalTransactions
->    rollingGasSummary {
->      computationCost
->      storageCost
->      storageRebate
->      nonRefundableStorageFee
->    }
->    epoch {
->      epochId
->      referenceGasPrice
->      startTimestamp
->      endTimestamp
->    }
->  }
->}
- -### -### Multiple Selections -#### Get the checkpoint at sequence 9769 and show -#### its transactions - ->
{
->  checkpoint(id: { sequenceNumber: 9769 }) {
->    digest
->    sequenceNumber
->    timestamp
->    validatorSignatures
->    previousCheckpointDigest
->    networkTotalTransactions
->    rollingGasSummary {
->      computationCost
->      storageCost
->      storageRebate
->      nonRefundableStorageFee
->    }
->    epoch {
->      epochId
->      liveObjectSetDigest
->    }
->    transactionBlocks {
->      edges {
->        node {
->          digest
->          sender {
->            address
->          }
->          expiration {
->            epochId
->          }
->        }
->      }
->    }
->  }
->}
- -### -### With Timestamp Tx Block Live Objects -#### Latest checkpoint's timestamp, and transaction block data - ->
{
->  checkpoint {
->    digest
->    sequenceNumber
->    timestamp
->    transactionBlocks {
->      edges {
->        node {
->          digest
->          sender {
->            address
->          }
->          expiration {
->            epochId
->          }
->        }
->      }
->    }
->  }
->}
- -### -### With Tx Sent Addr Filter -#### Select checkpoint at sequence number 14830285 for transactions from signAddress - ->
{
->  checkpoint(id: { sequenceNumber: 14830285 }) {
->    digest
->    sequenceNumber
->    timestamp
->    transactionBlocks(
->      filter: {
->        signAddress: "0x0000000000000000000000000000000000000000000000000000000000000000"
->      }
->    ) {
->      edges {
->        node {
->          digest
->          sender {
->            address
->          }
->          expiration {
->            epochId
->          }
->        }
->      }
->    }
->  }
->}
- -## -## Checkpoint Connection -### -### Ascending Fetch -#### Use the checkpoint connection to fetch some default amount of checkpoints in an ascending order - ->
{
->  checkpoints {
->    nodes {
->      digest
->      sequenceNumber
->      validatorSignatures
->      previousCheckpointDigest
->      networkTotalTransactions
->      rollingGasSummary {
->        computationCost
->        storageCost
->        storageRebate
->        nonRefundableStorageFee
->      }
->      epoch {
->        epochId
->        referenceGasPrice
->        startTimestamp
->        endTimestamp
->      }
->    }
->  }
->}
- -### -### First Ten After Checkpoint -#### Fetch the digest and sequence number of the first 10 checkpoints after the cursor, which in this example is set to be checkpoint 0. Note that the cursor is opaque. - ->
{
->  checkpoints(first: 10, after: "eyJjIjoyMjgwMDU4MCwicyI6MH0") {
->    nodes {
->      sequenceNumber
->      digest
->    }
->  }
->}
- -### -### Last Ten After Checkpoint -#### Fetch the digest and the sequence number of the last 20 checkpoints before the cursor - ->
{
->  checkpoints(last: 20, before: "eyJjIjoyMjgwMDY1MSwicyI6MjI4MDA2MzJ9") {
->    nodes {
->      sequenceNumber
->      digest
->    }
->  }
->}
- -## -## Coin Connection -### -### Coin Connection -#### Get last 3 coins owned by `0x0`. - ->
{
->  address(
->    address: "0x0000000000000000000000000000000000000000000000000000000000000000"
->  ) {
->    coins(last: 3) {
->      nodes {
->        coinBalance
->      }
->      pageInfo {
->        endCursor
->        hasNextPage
->      }
->    }
->  }
->}
- -## -## Coin Metadata -### -### Coin Metadata - ->
query CoinMetadata {
->  coinMetadata(coinType: "0x2::sui::SUI") {
->    decimals
->    name
->    symbol
->    description
->    iconUrl
->    supply
->    hasPublicTransfer
->  }
->}
- -## -## Epoch -### -### Latest Epoch -#### Latest epoch, since epoch omitted - ->
{
->  epoch {
->    protocolConfigs {
->      protocolVersion
->    }
->    epochId
->    referenceGasPrice
->    startTimestamp
->    endTimestamp
->  }
->}
- -### -### Specific Epoch -#### Selecting all fields for epoch 100 - ->
{
->  epoch(id: 100) {
->    protocolConfigs {
->      protocolVersion
->    }
->    epochId
->    referenceGasPrice
->    startTimestamp
->    endTimestamp
->    validatorSet {
->      totalStake
->      pendingActiveValidatorsSize
->      stakingPoolMappingsSize
->      inactivePoolsSize
->      validatorCandidatesSize
->      activeValidators {
->        nodes {
->          name
->          description
->          imageUrl
->          projectUrl
->          exchangeRates {
->            storageRebate
->            bcs
->            hasPublicTransfer
->          }
->          exchangeRatesSize
->          stakingPoolActivationEpoch
->          stakingPoolSuiBalance
->          rewardsPool
->          poolTokenBalance
->          pendingStake
->          pendingTotalSuiWithdraw
->          pendingPoolTokenWithdraw
->          votingPower
->          gasPrice
->          commissionRate
->          nextEpochStake
->          nextEpochGasPrice
->          nextEpochCommissionRate
->          atRisk
->        }
->      }
->    }
->  }
->}
- -### -### With Checkpoint Connection - ->
{
->  epoch {
->    checkpoints {
->      nodes {
->        transactionBlocks(first: 10) {
->          pageInfo {
->            hasNextPage
->            endCursor
->          }
->          edges {
->            cursor
->            node {
->              sender {
->                address
->              }
->              effects {
->                gasEffects {
->                  gasObject {
->                    address
->                  }
->                }
->              }
->              gasInput {
->                gasPrice
->                gasBudget
->              }
->            }
->          }
->        }
->      }
->    }
->  }
->}
- -### -### With Tx Block Connection -#### Fetch the first 20 transactions after tx 231220153 (encoded as a -#### cursor) in epoch 97. - ->
{
->  epoch(id: 97) {
->    transactionBlocks(first: 20, after:"eyJjIjoyNjkzMzc3OCwidCI6MjMxMjIwMTUzLCJ0YyI6ODAxMDg4NH0") {
->      pageInfo {
->        hasNextPage
->        endCursor
->      }
->      edges {
->        cursor
->        node {
->          digest
->          sender {
->            address
->          }
->          effects {
->            gasEffects {
->              gasObject {
->                address
->              }
->            }
->          }
->          gasInput {
->            gasPrice
->            gasBudget
->          }
->        }
->      }
->    }
->  }
->}
- -### -### With Tx Block Connection Latest Epoch - ->
{
->  epoch {
->    transactionBlocks(first: 20, after: "eyJjIjoyNjkzMzMyNCwidCI6MTEwMTYxMDQ4MywidGMiOjI2ODUxMjQ4fQ") {
->      pageInfo {
->        hasNextPage
->        endCursor
->      }
->      edges {
->        cursor
->        node {
->          sender {
->            address
->          }
->          effects {
->            gasEffects {
->              gasObject {
->                address
->              }
->            }
->          }
->          gasInput {
->            gasPrice
->            gasBudget
->          }
->        }
->      }
->    }
->  }
->}
- -## -## Event Connection -### -### Event Connection - ->
{
->  events(
->    filter: {
->      eventType: "0x3164fcf73eb6b41ff3d2129346141bd68469964c2d95a5b1533e8d16e6ea6e13::Market::ChangePriceEvent<0x2::sui::SUI>"
->    }
->  ) {
->    nodes {
->      sendingModule {
->        name
->        package { digest }
->      }
->      type {
->        repr
->      }
->      sender {
->        address
->      }
->      timestamp
->      json
->      bcs
->    }
->  }
->}
- -### -### Filter By Emitting Package Module And Event Type - ->
query ByEmittingPackageModuleAndEventType {
->  events(
->    first: 1
->    after: "eyJ0eCI6Njc2MywiZSI6MCwiYyI6MjI4MDA3NDJ9"
->    filter: {
->      emittingModule: "0x3::sui_system",
->      eventType: "0x3::validator::StakingRequestEvent"
->    }
->  ) {
->    pageInfo {
->      hasNextPage
->      endCursor
->    }
->    nodes {
->      sendingModule {
->        name
->      }
->      type {
->        repr
->      }
->      sender {
->        address
->      }
->      timestamp
->      json
->      bcs
->    }
->  }
->}
- -### -### Filter By Sender - ->
query ByTxSender {
->  events(
->    first: 1
->    filter: {
->      sender: "0xdff57c401e125a7e0e06606380560b459a179aacd08ed396d0162d57dbbdadfb"
->    }
->  ) {
->    pageInfo {
->      hasNextPage
->      endCursor
->    }
->    nodes {
->      sendingModule {
->        name
->      }
->      type {
->        repr
->      }
->      sender {
->        address
->      }
->      timestamp
->      json
->      bcs
->    }
->  }
->}
- -## -## Name Service -### -### Name Service - ->
{
->  resolveSuinsAddress(domain: "example.sui") {
->    address
->  }
->  address(
->    address: "0x0b86be5d779fac217b41d484b8040ad5145dc9ba0cba099d083c6cbda50d983e"
->  ) {
->    address
->    balance(type: "0x2::sui::SUI") {
->      coinType {
->        repr
->      }
->      coinObjectCount
->      totalBalance
->    }
->    defaultSuinsName
->  }
->}
- -## -## Object -### -### Object - ->
{
->  object(
->    address: "0x04e20ddf36af412a4096f9014f4a565af9e812db9a05cc40254846cf6ed0ad91"
->  ) {
->    address
->    version
->    digest
->    storageRebate
->    owner {
->      __typename
->      ... on Shared {
->        initialSharedVersion
->      }
->      __typename
->      ... on Parent {
->        parent {
->          address
->        }
->      }
->      __typename
->      ... on AddressOwner {
->        owner {
->          address
->        }
->      }
->    }
->    previousTransactionBlock {
->      digest
->    }
->  }
->}
- -## -## Object Connection -### -### Filter Object Ids -#### Filter on objectIds - ->
{
->  objects(filter: { objectIds: [
->    "0x4bba2c7b9574129c272bca8f58594eba933af8001257aa6e0821ad716030f149"
->  ]}) {
->    edges {
->      node {
->        storageRebate
->        owner {
->          __typename
->          ... on Shared {
->            initialSharedVersion
->          }
->          __typename
->          ... on Parent {
->            parent {
->              address
->            }
->          }
->          __typename
->          ... on AddressOwner {
->            owner {
->              address
->            }
->          }
->        }
->      }
->    }
->  }
->}
- -### -### Filter On Generic Type - ->
{
->  objects(filter: {type: "0x2::coin::Coin"}) {
->    edges {
->      node {
->        asMoveObject {
->          contents {
->            type { repr }
->          }
->        }
->      }
->    }
->  }
->}
- -### -### Filter On Type - ->
{
->  objects(filter: {type: "0x3::staking_pool::StakedSui"}) {
->    edges {
->      node {
->        asMoveObject {
->          contents {
->            type {
->              repr
->            }
->          }
->        }
->      }
->    }
->  }
->}
- -### -### Filter Owner -#### Filter on owner - ->
{
->  objects(filter: {
->    owner: "0x23b7b0e2badb01581ba9b3ab55587d8d9fdae087e0cfc79f2c72af36f5059439"
->  }) {
->    edges {
->      node {
->        storageRebate
->        owner {
->          __typename
->          ... on Shared {
->            initialSharedVersion
->          }
->          __typename
->          ... on Parent {
->            parent {
->              address
->            }
->          }
->          __typename
->          ... on AddressOwner {
->            owner {
->              address
->            }
->          }
->        }
->      }
->    }
->  }
->}
- -### -### Object Connection - ->
{
->  objects {
->    nodes {
->      version
->      digest
->      storageRebate
->      previousTransactionBlock {
->        digest
->        sender { defaultSuinsName }
->        gasInput {
->          gasPrice
->          gasBudget
->        }
->      }
->    }
->    pageInfo {
->      endCursor
->    }
->  }
->}
- -## -## Owner -### -### Dynamic Field - ->
fragment DynamicFieldValueSelection on DynamicFieldValue {
->  ... on MoveValue {
->    type {
->      repr
->    }
->    data
->    __typename
->  }
->  ... on MoveObject {
->    hasPublicTransfer
->    contents {
->      type {
->        repr
->      }
->      data
->    }
->    __typename
->  }
->}
->
->fragment DynamicFieldNameSelection on MoveValue {
->  type {
->    repr
->  }
->  data
->  bcs
->}
->
->fragment DynamicFieldSelect on DynamicField {
->  name {
->    ...DynamicFieldNameSelection
->  }
->  value {
->    ...DynamicFieldValueSelection
->  }
->}
->
->query DynamicField {
->  object(
->    address: "0xb57fba584a700a5bcb40991e1b2e6bf68b0f3896d767a0da92e69de73de226ac"
->  ) {
->    dynamicField(
->      name: {
->        type: "0x2::kiosk::Listing",
->        bcs: "NLArx1UJguOUYmXgNG8Pv8KbKXLjWtCi6i0Yeq1VhfwA",
->      }
->    ) {
->      ...DynamicFieldSelect
->    }
->  }
->}
- -### -### Dynamic Field Connection - ->
fragment DynamicFieldValueSelection on DynamicFieldValue {
->  ... on MoveValue {
->    type {
->      repr
->    }
->    data
->  }
->  ... on MoveObject {
->    hasPublicTransfer
->    contents {
->      type {
->        repr
->      }
->      data
->    }
->  }
->}
->
->fragment DynamicFieldNameSelection on MoveValue {
->  type {
->    repr
->  }
->  data
->  bcs
->}
->
->fragment DynamicFieldSelect on DynamicField {
->  name {
->    ...DynamicFieldNameSelection
->  }
->  value {
->    ...DynamicFieldValueSelection
->  }
->}
->
->query DynamicFields {
->  object(
->    address: "0xb57fba584a700a5bcb40991e1b2e6bf68b0f3896d767a0da92e69de73de226ac"
->  ) {
->    dynamicFields {
->      pageInfo {
->        hasNextPage
->        endCursor
->      }
->      edges {
->        cursor
->        node {
->          ...DynamicFieldSelect
->        }
->      }
->    }
->  }
->}
- -### -### Dynamic Object Field - ->
fragment DynamicFieldValueSelection on DynamicFieldValue {
->  ... on MoveValue {
->    type {
->      repr
->    }
->    data
->    __typename
->  }
->  ... on MoveObject {
->    hasPublicTransfer
->    contents {
->      type {
->        repr
->      }
->      data
->    }
->    __typename
->  }
->}
->
->fragment DynamicFieldNameSelection on MoveValue {
->  type {
->    repr
->  }
->  data
->  bcs
->}
->
->fragment DynamicFieldSelect on DynamicField {
->  name {
->    ...DynamicFieldNameSelection
->  }
->  value {
->    ...DynamicFieldValueSelection
->  }
->}
->
->query DynamicObjectField {
->  object(
->    address: "0xb57fba584a700a5bcb40991e1b2e6bf68b0f3896d767a0da92e69de73de226ac"
->  ) {
->    dynamicObjectField(
->      name: {type: "0x2::kiosk::Item", bcs: "NLArx1UJguOUYmXgNG8Pv8KbKXLjWtCi6i0Yeq1Vhfw="}
->    ) {
->      ...DynamicFieldSelect
->    }
->  }
->}
- -### -### Owner - ->
{
->  owner(
->    address: "0x931f293ce7f65fd5ebe9542653e1fd92fafa03dda563e13b83be35da8a2eecbe"
->  ) {
->    address
->  }
->}
- -## -## Protocol Configs -### -### Key Value -#### Select the key and value of the protocol configuration - ->
{
->  protocolConfig {
->    configs {
->      key
->      value
->    }
->  }
->}
- -### -### Key Value Feature Flag -#### Select the key and value of the feature flag - ->
{
->  protocolConfig {
->    featureFlags {
->      key
->      value
->    }
->  }
->}
- -### -### Specific Config -#### Select the key and value of the specific protocol configuration, in this case `max_move_identifier_len` - ->
{
->  protocolConfig {
->    config(key: "max_move_identifier_len") {
->      key
->      value
->    }
->  }
->}
- -### -### Specific Feature Flag - ->
{
->  protocolConfig {
->    protocolVersion
->    featureFlag(key: "advance_epoch_start_time_in_safe_mode") {
->      value
->    }
->  }
->}
- -## -## Service Config -### -### Service Config -#### Get the configuration of the running service - ->
{
->  serviceConfig {
->    isEnabled(feature: ANALYTICS)
->    enabledFeatures
->    maxQueryDepth
->    maxQueryNodes
->    maxDbQueryCost
->    defaultPageSize
->    maxPageSize
->    requestTimeoutMs
->    maxQueryPayloadSize
->  }
->}
- -## -## Stake Connection -### -### Stake Connection -#### Get all the staked objects for this address and all the active validators at the epoch when the stake became active - ->
{
->  address(
->    address: "0xc0a5b916d0e406ddde11a29558cd91b29c49e644eef597b7424a622955280e1e"
->  ) {
->    address
->    balance(type: "0x2::sui::SUI") {
->      coinType {
->        repr
->      }
->      totalBalance
->    }
->    stakedSuis {
->      nodes {
->        status
->        principal
->        estimatedReward
->        activatedEpoch {
->          epochId
->          referenceGasPrice
->          validatorSet {
->            activeValidators {
->              nodes {
->                name
->                description
->                exchangeRatesSize
->              }
->            }
->            totalStake
->          }
->        }
->        requestedEpoch {
->          epochId
->        }
->      }
->    }
->  }
->}
- -## -## Sui System State Summary -### -### Sui System State Summary -#### Get the latest sui system state data - ->
{
->  epoch {
->    storageFund {
->      totalObjectStorageRebates
->      nonRefundableBalance
->    }
->    safeMode {
->      enabled
->      gasSummary {
->         computationCost
->         storageCost
->         storageRebate
->         nonRefundableStorageFee
->      }
->    }
->    systemStateVersion
->    systemParameters {
->      durationMs
->      stakeSubsidyStartEpoch
->      minValidatorCount
->      maxValidatorCount
->      minValidatorJoiningStake
->      validatorLowStakeThreshold
->      validatorVeryLowStakeThreshold
->      validatorLowStakeGracePeriod
->    }
->    systemStakeSubsidy {
->      balance
->      distributionCounter
->      currentDistributionAmount
->      periodLength
->      decreaseRate
->
->    }
->  }
->}
- -## -## Transaction Block -### -### Transaction Block -#### Get the data for a TransactionBlock by its digest - ->
{
->  transactionBlock(digest: "HvTjk3ELg8gRofmB1GgrpLHBFeA53QKmUKGEuhuypezg") {
->    sender {
->      address
->    }
->    gasInput {
->      gasSponsor {
->        address
->      }
->      gasPayment {
->        nodes {
->          address
->        }
->      }
->      gasPrice
->      gasBudget
->    }
->    kind {
->      __typename
->    }
->    signatures
->    digest
->    expiration {
->      epochId
->    }
->    effects {
->      timestamp
->    }
->  }
->}
- -### -### Transaction Block Kind - ->
{
->  object(
->    address: "0xd6b9c261ab53d636760a104e4ab5f46c2a3e9cda58bd392488fc4efa6e43728c"
->  ) {
->    previousTransactionBlock {
->      sender {
->        address
->      }
->      kind {
->        __typename
->        ... on ConsensusCommitPrologueTransaction {
->          epoch {
->            epochId
->            referenceGasPrice
->          }
->          round
->          commitTimestamp
->          consensusCommitDigest
->        }
->        ... on ChangeEpochTransaction {
->          computationCharge
->          storageCharge
->          startTimestamp
->          storageRebate
->        }
->        ... on GenesisTransaction {
->          objects {
->            nodes { address }
->          }
->        }
->      }
->    }
->  }
->}
- -## -## Transaction Block Connection -### -### Before After Checkpoint -#### Filter on before_ and after_checkpoint. If both are provided, before must be greater than after - ->
{
->  transactionBlocks(
->    filter: { afterCheckpoint: 10, beforeCheckpoint: 20 }
->  ) {
->    nodes {
->      sender {
->        address
->      }
->      gasInput {
->        gasPrice
->        gasBudget
->      }
->    }
->  }
->}
- -### -### Changed Object Filter -#### Filter on changedObject - ->
{
->  transactionBlocks(
->    filter: {
->      changedObject: "0x0000000000000000000000000000000000000000000000000000000000000006"
->    }
->  ) {
->    nodes {
->      sender {
->        address
->      }
->      gasInput {
->        gasPrice
->        gasBudget
->      }
->    }
->  }
->}
- -### -### Input Object Filter -#### Filter on inputObject - ->
{
->  transactionBlocks(
->    filter: {
->      inputObject: "0x0000000000000000000000000000000000000000000000000000000000000006"
->    }
->  ) {
->    nodes {
->      sender {
->        address
->      }
->      gasInput {
->        gasPrice
->        gasBudget
->      }
->    }
->  }
->}
- -### -### Input Object Sign Addr Filter -#### multiple filters - ->
{
->  transactionBlocks(
->    filter: {
->      inputObject: "0x0000000000000000000000000000000000000000000000000000000000000006"
->      signAddress: "0x0000000000000000000000000000000000000000000000000000000000000000"
->    }
->  ) {
->    nodes {
->      sender {
->        address
->      }
->      effects {
->        gasEffects {
->          gasObject {
->            address
->          }
->        }
->      }
->      gasInput {
->        gasPrice
->        gasBudget
->      }
->    }
->  }
->}
- -### -### Package Filter -#### Filtering on package - ->
{
->  transactionBlocks(filter: { function: "0x3" }) {
->    nodes {
->      sender {
->        address
->      }
->      gasInput {
->        gasPrice
->        gasBudget
->      }
->    }
->  }
->}
- -### -### Package Module Filter -#### Filtering on package and module - ->
{
->  transactionBlocks(
->    filter: {
->      function: "0x3::sui_system"
->    }
->  ) {
->    nodes {
->      sender {
->        address
->      }
->      gasInput {
->        gasPrice
->        gasBudget
->      }
->    }
->  }
->}
- -### -### Package Module Func Filter -#### Filtering on package, module and function - ->
{
->  transactionBlocks(
->    filter: {
->      function: "0x3::sui_system::request_withdraw_stake"
->    }
->  ) {
->    nodes {
->      sender {
->        address
->      }
->      gasInput {
->        gasPrice
->        gasBudget
->      }
->    }
->  }
->}
- -### -### Recv Addr Filter -#### Filter on recvAddress - ->
{
->  transactionBlocks(
->    filter: {
->      recvAddress: "0x0000000000000000000000000000000000000000000000000000000000000000"
->    }
->  ) {
->    nodes {
->      sender {
->        address
->      }
->      gasInput {
->        gasPrice
->        gasBudget
->      }
->    }
->  }
->}
- -### -### Sign Addr Filter -#### Filter on signing address - ->
{
->  transactionBlocks(
->    filter: {
->      signAddress: "0x0000000000000000000000000000000000000000000000000000000000000000"
->    }
->  ) {
->    nodes {
->      sender {
->        address
->      }
->      gasInput {
->        gasPrice
->        gasBudget
->      }
->    }
->  }
->}
- -### -### Tx Ids Filter -#### Filter on transactionIds - ->
{
->  transactionBlocks(
->    filter: { transactionIds: ["DtQ6v6iJW4wMLgadENPUCEUS5t8AP7qvdG5jX84T1akR"] }
->  ) {
->    nodes {
->      sender {
->        address
->      }
->      gasInput {
->        gasPrice
->        gasBudget
->      }
->    }
->  }
->}
- -### -### Tx Kind Filter -#### Filter on TransactionKind (only SYSTEM_TX or PROGRAMMABLE_TX) - ->
{
->  transactionBlocks(filter: { kind: SYSTEM_TX }) {
->    nodes {
->      sender {
->        address
->      }
->      gasInput {
->        gasPrice
->        gasBudget
->      }
->    }
->  }
->}
- -### -### With Defaults Ascending -#### Fetch some default amount of transactions, ascending - ->
{
->  transactionBlocks {
->    nodes {
->      digest
->      effects {
->        gasEffects {
->          gasObject {
->            version
->            digest
->          }
->          gasSummary {
->            computationCost
->            storageCost
->            storageRebate
->            nonRefundableStorageFee
->          }
->        }
->        errors
->      }
->      sender {
->        address
->      }
->      gasInput {
->        gasPrice
->        gasBudget
->      }
->    }
->    pageInfo {
->      endCursor
->    }
->  }
->}
- -## -## Transaction Block Effects -### -### Transaction Block Effects - ->
{
->  object(
->    address: "0x0bba1e7d907dc2832edfc3bf4468b6deacd9a2df435a35b17e640e135d2d5ddc"
->  ) {
->    version
->    owner {
->      __typename
->      ... on Shared {
->        initialSharedVersion
->      }
->      __typename
->      ... on Parent {
->        parent {
->          address
->        }
->      }
->      __typename
->      ... on AddressOwner {
->        owner {
->          address
->        }
->      }
->    }
->    previousTransactionBlock {
->      effects {
->        status
->        checkpoint {
->          sequenceNumber
->        }
->        lamportVersion
->        gasEffects {
->          gasSummary {
->            computationCost
->            storageCost
->            storageRebate
->            nonRefundableStorageFee
->          }
->        }
->        balanceChanges {
->          nodes {
->            owner {
->              address
->              balance(type: "0x2::sui::SUI") {
->                totalBalance
->              }
->            }
->            amount
->            coinType {
->              repr
->              signature
->              layout
->            }
->          }
->        }
->        dependencies {
->          nodes {
->            sender {
->              address
->            }
->          }
->        }
->      }
->    }
->  }
->}
- diff --git a/crates/sui-graphql-rpc/src/commands.rs b/crates/sui-graphql-rpc/src/commands.rs index f605efd735946..bda0c2f561fab 100644 --- a/crates/sui-graphql-rpc/src/commands.rs +++ b/crates/sui-graphql-rpc/src/commands.rs @@ -13,17 +13,6 @@ use std::path::PathBuf; version )] pub enum Command { - GenerateDocsExamples, - GenerateSchema { - /// Path to output GraphQL schema to, in SDL format. - #[clap(short, long)] - file: Option, - }, - GenerateExamples { - /// Path to output examples docs. - #[clap(short, long)] - file: Option, - }, StartServer { /// The title to display at the top of the page #[clap(short, long)] diff --git a/crates/sui-graphql-rpc/src/examples.rs b/crates/sui-graphql-rpc/src/examples.rs deleted file mode 100644 index 56f86ca428180..0000000000000 --- a/crates/sui-graphql-rpc/src/examples.rs +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use anyhow::anyhow; -use markdown_gen::markdown::{AsMarkdown, Markdown}; -use std::io::{BufWriter, Read}; -use std::path::PathBuf; - -#[derive(Debug)] -pub struct ExampleQuery { - pub name: String, - pub contents: String, - pub path: PathBuf, -} - -#[derive(Debug)] -pub struct ExampleQueryGroup { - pub name: String, - pub queries: Vec, - pub _path: PathBuf, -} - -const QUERY_EXT: &str = "graphql"; - -fn regularize_string(s: &str) -> String { - // Replace underscore with space and make every word first letter uppercase - s.replace('_', " ") - .split_whitespace() - .map(|word| { - let mut chars = word.chars(); - match chars.next() { - None => String::new(), - Some(f) => f.to_uppercase().chain(chars).collect(), - } - }) - .collect::>() - .join(" ") -} - -pub fn load_examples() -> anyhow::Result> { - let mut buf: PathBuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - buf.push("examples"); - - let mut groups = vec![]; - for entry in std::fs::read_dir(buf).map_err(|e| anyhow::anyhow!(e))? { - let entry = entry.map_err(|e| anyhow::anyhow!(e))?; - let path = entry.path(); - let group_name = path - .file_stem() - .ok_or(anyhow::anyhow!("File stem cannot be read"))? - .to_str() - .ok_or(anyhow::anyhow!("File stem cannot be read"))? - .to_string(); - - let mut group = ExampleQueryGroup { - name: group_name.clone(), - queries: vec![], - _path: path.clone(), - }; - - for file in std::fs::read_dir(path).map_err(|e| anyhow::anyhow!(e))? { - assert!(file.is_ok()); - let file = file.map_err(|e| anyhow::anyhow!(e))?; - assert!(file.path().extension().is_some()); - let ext = file - .path() - .extension() - .ok_or(anyhow!("File extension cannot be read"))? - .to_str() - .ok_or(anyhow!("File extension cannot be read to string"))? - .to_string(); - assert_eq!(ext, QUERY_EXT, "wrong file extension for example"); - - let file_path = file.path(); - let query_name = file_path - .file_stem() - .ok_or(anyhow!("File stem cannot be read"))? - .to_str() - .ok_or(anyhow!("File extension cannot be read to string"))? - .to_string(); - - let mut contents = String::new(); - let mut fp = std::fs::File::open(file_path.clone()).map_err(|e| anyhow!(e))?; - fp.read_to_string(&mut contents).map_err(|e| anyhow!(e))?; - group.queries.push(ExampleQuery { - name: query_name, - contents, - path: file_path, - }); - } - group.queries.sort_by(|x, y| x.name.cmp(&y.name)); - - groups.push(group); - } - - groups.sort_by(|x, y| x.name.cmp(&y.name)); - Ok(groups) -} - -/// This generates a markdown page with all the examples, to be used in the docs site -pub fn generate_examples_for_docs() -> anyhow::Result { - let groups = load_examples()?; - - let mut output = BufWriter::new(Vec::new()); - let mut md = Markdown::new(&mut output); - md.write( - r#"--- -title: Examples -description: Query examples for working with the Sui GraphQL RPC. ---- -"#, - )?; - md.write("This page showcases a number of queries to interact with the network. These examples can also be found in the [repository](https://github.com/MystenLabs/sui/tree/main/crates/sui-graphql-rpc/examples). You can use the [interactive online IDE](https://mainnet.sui.io/rpc/graphql) to run these examples.")?; - for group in groups.iter() { - let group_name = regularize_string(&group.name); - md.write(group_name.heading(2)) - .map_err(|e| anyhow::anyhow!(e))?; - for query in group.queries.iter() { - let name = regularize_string(&query.name); - md.write(name.heading(3)).map_err(|e| anyhow::anyhow!(e))?; - let query = query.contents.lines().collect::>().join("\n"); - let content = format!("```graphql\n{}\n```", query); - md.write(content.as_str()).map_err(|e| anyhow::anyhow!(e))?; - } - } - let bytes = output.into_inner().map_err(|e| anyhow::anyhow!(e))?; - Ok(String::from_utf8(bytes) - .map_err(|e| anyhow::anyhow!(e))? - .replace('\\', "")) -} - -pub fn generate_markdown() -> anyhow::Result { - let groups = load_examples()?; - - let mut output = BufWriter::new(Vec::new()); - let mut md = Markdown::new(&mut output); - - md.write("Sui GraphQL Examples".heading(1)) - .map_err(|e| anyhow!(e))?; - - // TODO: reduce multiple loops - // Generate the table of contents - for (id, group) in groups.iter().enumerate() { - let group_name = regularize_string(&group.name); - let group_name_toc = format!("[{}](#{})", group_name, id); - md.write(group_name_toc.heading(3)) - .map_err(|e| anyhow!(e))?; - - for (inner, query) in group.queries.iter().enumerate() { - let inner_id = inner + 0xFFFF * id; - let inner_name = regularize_string(&query.name); - let inner_name_toc = format!("  [{}](#{})", inner_name, inner_id); - md.write(inner_name_toc.heading(4)) - .map_err(|e| anyhow!(e))?; - } - } - - for (id, group) in groups.iter().enumerate() { - let group_name = regularize_string(&group.name); - - let id_tag = format!("", id); - md.write(id_tag.heading(2)) - .map_err(|e| anyhow::anyhow!(e))?; - md.write(group_name.heading(2)) - .map_err(|e| anyhow::anyhow!(e))?; - for (inner, query) in group.queries.iter().enumerate() { - let inner_id = inner + 0xFFFF * id; - let name = regularize_string(&query.name); - - let id_tag = format!("", inner_id); - md.write(id_tag.heading(3)) - .map_err(|e| anyhow::anyhow!(e))?; - md.write(name.heading(3)).map_err(|e| anyhow::anyhow!(e))?; - - // Extract all lines that start with `#` and use them as headers - let mut headers = vec![]; - let mut query_start = 0; - for (idx, line) in query.contents.lines().enumerate() { - let line = line.trim(); - if line.starts_with('#') { - headers.push(line.trim_start_matches('#')); - } else if line.starts_with('{') { - query_start = idx; - break; - } - } - - // Remove headers from query - let query = query - .contents - .lines() - .skip(query_start) - .collect::>() - .join("\n"); - - let content = format!("
{}
", query); - for header in headers { - md.write(header.heading(4)) - .map_err(|e| anyhow::anyhow!(e))?; - } - md.write(content.quote()).map_err(|e| anyhow::anyhow!(e))?; - } - } - let bytes = output.into_inner().map_err(|e| anyhow::anyhow!(e))?; - Ok(String::from_utf8(bytes) - .map_err(|e| anyhow::anyhow!(e))? - .replace('\\', "")) -} - -#[test] -fn test_generate_markdown() { - use similar::*; - use std::fs::File; - - let mut buf: PathBuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - buf.push("docs"); - buf.push("examples.md"); - let mut out_file: File = File::open(buf).expect("Could not open examples.md"); - - // Read the current content of `out_file` - let mut current_content = String::new(); - out_file - .read_to_string(&mut current_content) - .expect("Could not read examples.md"); - let new_content: String = generate_markdown().expect("Generating examples markdown failed"); - - if current_content != new_content { - let mut res = vec![]; - let diff = TextDiff::from_lines(¤t_content, &new_content); - for change in diff.iter_all_changes() { - let sign = match change.tag() { - ChangeTag::Delete => "---", - ChangeTag::Insert => "+++", - ChangeTag::Equal => " ", - }; - res.push(format!("{}{}", sign, change)); - } - panic!("Doc examples have changed. Please run `sui-graphql-rpc generate-examples` to update the docs. Diff: {}", res.join("")); - } -} diff --git a/crates/sui-graphql-rpc/src/lib.rs b/crates/sui-graphql-rpc/src/lib.rs index baea0d2ce2ce8..c2f7cd3f8687b 100644 --- a/crates/sui-graphql-rpc/src/lib.rs +++ b/crates/sui-graphql-rpc/src/lib.rs @@ -8,7 +8,6 @@ pub(crate) mod consistency; pub mod context_data; pub(crate) mod data; mod error; -pub mod examples; pub mod extensions; pub(crate) mod functional_group; mod metrics; diff --git a/crates/sui-graphql-rpc/src/main.rs b/crates/sui-graphql-rpc/src/main.rs index 6e552a09e92e8..349ef0f0f74a4 100644 --- a/crates/sui-graphql-rpc/src/main.rs +++ b/crates/sui-graphql-rpc/src/main.rs @@ -9,7 +9,6 @@ use sui_graphql_rpc::commands::Command; use sui_graphql_rpc::config::{ ConnectionConfig, Ide, ServerConfig, ServiceConfig, TxExecFullNodeConfig, Version, }; -use sui_graphql_rpc::server::builder::export_schema; use sui_graphql_rpc::server::graphiql_server::start_graphiql_server; use tokio_util::sync::CancellationToken; use tokio_util::task::TaskTracker; @@ -38,39 +37,6 @@ static VERSION: Version = Version { async fn main() { let cmd: Command = Command::parse(); match cmd { - Command::GenerateDocsExamples => { - let mut buf: PathBuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - // we are looking to put examples content in - // sui/docs/content/references/sui-graphql/examples.mdx - let filename = "docs/content/references/sui-graphql/examples.mdx"; - buf.pop(); - buf.pop(); - buf.push(filename); - let content = sui_graphql_rpc::examples::generate_examples_for_docs() - .expect("Generating examples markdown file for docs failed"); - std::fs::write(buf, content).expect("Writing examples markdown failed"); - println!("Generated the docs example.mdx file and copied it to {filename}."); - } - Command::GenerateSchema { file } => { - let out = export_schema(); - if let Some(file) = file { - println!("Write schema to file: {:?}", file); - std::fs::write(file, &out).unwrap(); - } else { - println!("{}", &out); - } - } - Command::GenerateExamples { file } => { - let new_content: String = sui_graphql_rpc::examples::generate_markdown() - .expect("Generating examples markdown failed"); - let mut buf: PathBuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - buf.push("docs"); - buf.push("examples.md"); - let file = file.unwrap_or(buf); - - std::fs::write(file.clone(), new_content).expect("Writing examples markdown failed"); - println!("Written examples to file: {:?}", file); - } Command::StartServer { ide_title, db_url, diff --git a/crates/sui-graphql-rpc/tests/examples_validation_tests.rs b/crates/sui-graphql-rpc/tests/examples_validation_tests.rs index fc2a95d21c90b..205c0e1407b5d 100644 --- a/crates/sui-graphql-rpc/tests/examples_validation_tests.rs +++ b/crates/sui-graphql-rpc/tests/examples_validation_tests.rs @@ -3,105 +3,147 @@ #[cfg(feature = "pg_integration")] mod tests { + use anyhow::{anyhow, Context, Result}; use rand::rngs::StdRng; use rand::SeedableRng; use serial_test::serial; use simulacrum::Simulacrum; use std::cmp::max; + use std::collections::BTreeMap; + use std::fs; use std::path::PathBuf; use std::sync::Arc; use sui_graphql_rpc::config::{ConnectionConfig, Limits}; - use sui_graphql_rpc::examples::{load_examples, ExampleQuery, ExampleQueryGroup}; use sui_graphql_rpc::test_infra::cluster::ExecutorCluster; use sui_graphql_rpc::test_infra::cluster::DEFAULT_INTERNAL_DATA_SOURCE_PORT; use tempfile::tempdir; - fn bad_examples() -> ExampleQueryGroup { - ExampleQueryGroup { - name: "bad_examples".to_string(), - queries: vec![ - ExampleQuery { - name: "multiple_queries".to_string(), + struct Example { + contents: String, + path: Option, + } + + fn good_examples() -> Result> { + let examples = PathBuf::from(&env!("CARGO_MANIFEST_DIR")).join("examples"); + + let mut dirs = vec![examples.clone()]; + let mut queries = BTreeMap::new(); + while let Some(dir) = dirs.pop() { + let entries = + fs::read_dir(&dir).with_context(|| format!("Looking in {}", dir.display()))?; + + for entry in entries { + let entry = entry.with_context(|| format!("Entry in {}", dir.display()))?; + let path = entry.path(); + let typ_ = entry + .file_type() + .with_context(|| format!("Metadata for {}", path.display()))?; + + if typ_.is_dir() { + dirs.push(entry.path()); + continue; + } + + if path.ends_with(".graphql") { + let contents = fs::read_to_string(&path) + .with_context(|| format!("Reading {}", path.display()))?; + + let rel_path = path + .strip_prefix(&examples) + .with_context(|| format!("Generating name from {}", path.display()))? + .with_extension(""); + + let name = rel_path + .to_str() + .ok_or_else(|| anyhow!("Generating name from {}", path.display()))?; + + queries.insert( + name.to_string(), + Example { + contents, + path: Some(path), + }, + ); + } + } + } + + Ok(queries) + } + + fn bad_examples() -> BTreeMap { + BTreeMap::from_iter([ + ( + "multiple_queries".to_string(), + Example { contents: "{ chainIdentifier } { chainIdentifier }".to_string(), - path: PathBuf::from("multiple_queries.graphql"), + path: None, }, - ExampleQuery { - name: "malformed".to_string(), + ), + ( + "malformed".to_string(), + Example { contents: "query { }}".to_string(), - path: PathBuf::from("malformed.graphql"), + path: None, }, - ExampleQuery { - name: "invalid".to_string(), + ), + ( + "invalid".to_string(), + Example { contents: "djewfbfo".to_string(), - path: PathBuf::from("invalid.graphql"), + path: None, }, - ExampleQuery { - name: "empty".to_string(), + ), + ( + "empty".to_string(), + Example { contents: " ".to_string(), - path: PathBuf::from("empty.graphql"), + path: None, }, - ], - _path: PathBuf::from("bad_examples"), - } + ), + ]) } - async fn validate_example_query_group( + async fn test_query( cluster: &ExecutorCluster, - group: &ExampleQueryGroup, + name: &str, + query: &Example, max_nodes: &mut u64, max_output_nodes: &mut u64, max_depth: &mut u64, max_payload: &mut u64, ) -> Vec { - let mut errors = vec![]; - for query in &group.queries { - let resp = cluster - .graphql_client - .execute_to_graphql(query.contents.clone(), true, vec![], vec![]) - .await - .unwrap(); - resp.errors().iter().for_each(|err| { - errors.push(format!( - "Query failed: {}: {} at: {}\nError: {}", - group.name, - query.name, - query.path.display(), - err - )) - }); - if resp.errors().is_empty() { - let usage = resp - .usage() - .expect("Usage fetch should succeed") - .unwrap_or_else(|| panic!("Usage should be present for query: {}", query.name)); - - let nodes = *usage.get("inputNodes").unwrap_or_else(|| { - panic!("Node usage should be present for query: {}", query.name) - }); - let output_nodes = *usage.get("outputNodes").unwrap_or_else(|| { - panic!( - "Output node usage should be present for query: {}", - query.name - ) - }); - let depth = *usage.get("depth").unwrap_or_else(|| { - panic!("Depth usage should be present for query: {}", query.name) - }); - let payload = *usage.get("queryPayload").unwrap_or_else(|| { - panic!("Payload usage should be present for query: {}", query.name) - }); - *max_nodes = max(*max_nodes, nodes); - *max_output_nodes = max(*max_output_nodes, output_nodes); - *max_depth = max(*max_depth, depth); - *max_payload = max(*max_payload, payload); - } + let resp = cluster + .graphql_client + .execute_to_graphql(query.contents.clone(), true, vec![], vec![]) + .await + .unwrap(); + + let errors = resp.errors(); + if errors.is_empty() { + let usage = resp + .usage() + .expect("Usage not found") + .expect("Usage not found"); + *max_nodes = max(*max_nodes, usage["inputNodes"]); + *max_output_nodes = max(*max_output_nodes, usage["outputNodes"]); + *max_depth = max(*max_depth, usage["depth"]); + *max_payload = max(*max_payload, usage["queryPayload"]); + return vec![]; } + errors + .into_iter() + .map(|e| match &query.path { + Some(p) => format!("Query {name:?} at {} failed: {e}", p.display()), + None => format!("Query {name:?} failed: {e}"), + }) + .collect() } #[tokio::test] #[serial] - async fn test_single_all_examples_structure_valid() { + async fn good_examples_within_limits() { let rng = StdRng::from_seed([12; 32]); let data_ingestion_path = tempdir().unwrap().into_path(); let mut sim = Simulacrum::new_with_rng(rng); @@ -119,20 +161,20 @@ mod tests { ) .await; - let groups = load_examples().expect("Could not load examples"); - let mut errors = vec![]; - for group in groups { - let group_errors = validate_example_query_group( - &cluster, - &group, - &mut max_nodes, - &mut max_output_nodes, - &mut max_depth, - &mut max_payload, - ) - .await; - errors.extend(group_errors); + for (name, example) in good_examples().expect("Could not load examples") { + errors.extend( + test_query( + &cluster, + &name, + &example, + &mut max_nodes, + &mut max_output_nodes, + &mut max_depth, + &mut max_payload, + ) + .await, + ); } // Check that our examples can run with our usage limits @@ -167,7 +209,7 @@ mod tests { #[tokio::test] #[serial] - async fn test_bad_examples_fail() { + async fn bad_examples_fail() { let rng = StdRng::from_seed([12; 32]); let data_ingestion_path = tempdir().unwrap().into_path(); let mut sim = Simulacrum::new_with_rng(rng); @@ -185,21 +227,19 @@ mod tests { ) .await; - let bad_examples = bad_examples(); - let errors = validate_example_query_group( - &cluster, - &bad_examples, - &mut max_nodes, - &mut max_output_nodes, - &mut max_depth, - &mut max_payload, - ) - .await; + for (name, example) in bad_examples() { + let errors = test_query( + &cluster, + &name, + &example, + &mut max_nodes, + &mut max_output_nodes, + &mut max_depth, + &mut max_payload, + ) + .await; - assert_eq!( - errors.len(), - bad_examples.queries.len(), - "all examples should fail" - ); + assert!(!errors.is_empty(), "Query {name:?} should have failed"); + } } }