From cf0f39f4de86c9bb087447de0bdbb0519eeed50d Mon Sep 17 00:00:00 2001 From: Ashok Menon Date: Mon, 19 Aug 2024 11:40:43 +0100 Subject: [PATCH] [chore][GraphQL] Clean-up unused commands (#18287) ## 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 ## Description Describe the changes or additions included in this PR. ## Test plan How did you test the new or updated feature? ## Stack - #17543 - #17692 - #17693 - #17696 - #17697 --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [x] GraphQL: The GraphQL binary no longer supports generating examples, or exporting its own schema as these commands have been unused for some time. - [ ] CLI: - [ ] Rust SDK: --- 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"); + } } }