Skip to content

Commit c7e4726

Browse files
henryachenamnn
andauthored
graphql-alt: validate type and field for available range (#24486)
## Description Check that types and fields passed into `AvailableRange` queries are in the schema registry. ## Test plan ``` cargo nextest run -p sui-indexer-alt-e2e-tests -- graphql/ cargo nextest run -p sui-indexer-alt-e2e-tests -- graphql/available_range ``` --- ## 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): - [ ] gRPC: - [ ] JSON-RPC: - [x] GraphQL: Validate types and fields passed into AvailableRange queries. - [ ] CLI: - [ ] Rust SDK: - [ ] Indexing Framework: --------- Co-authored-by: Ashok Menon <[email protected]>
1 parent 0fba932 commit c7e4726

File tree

4 files changed

+235
-6
lines changed

4 files changed

+235
-6
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) Mysten Labs, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//# init --protocol-version 51 --simulator --objects-snapshot-min-checkpoint-lag 2
5+
6+
//# run-graphql
7+
{
8+
happyPath: serviceConfig {
9+
availableRange(type: "Query", field: "checkpoints") {
10+
first {
11+
digest
12+
sequenceNumber
13+
}
14+
last {
15+
digest
16+
sequenceNumber
17+
}
18+
}
19+
}
20+
}
21+
22+
//# run-graphql
23+
{
24+
typeNotFound: serviceConfig {
25+
availableRange(type: "InvalidType", field: "checkpoints") {
26+
first {
27+
digest
28+
sequenceNumber
29+
}
30+
}
31+
}
32+
}
33+
34+
//# run-graphql
35+
{
36+
fieldNotFound: serviceConfig {
37+
availableRange(type: "Query", field: "invalidField") {
38+
first {
39+
digest
40+
sequenceNumber
41+
}
42+
}
43+
}
44+
}
45+
46+
//# run-graphql
47+
{
48+
invalidTypeAndField: serviceConfig {
49+
availableRange(type: "InvalidType", field: "invalidField") {
50+
first {
51+
digest
52+
sequenceNumber
53+
}
54+
}
55+
}
56+
}
57+
58+
//# run-graphql
59+
{
60+
notAnObjectOrInterface: serviceConfig {
61+
availableRange(type: "ZkLoginIntentScope", field: "invalidField") {
62+
first {
63+
digest
64+
sequenceNumber
65+
}
66+
}
67+
}
68+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
---
2+
source: external-crates/move/crates/move-transactional-test-runner/src/framework.rs
3+
---
4+
processed 6 tasks
5+
6+
task 1, lines 6-20:
7+
//# run-graphql
8+
Response: {
9+
"data": {
10+
"happyPath": {
11+
"availableRange": {
12+
"first": {
13+
"digest": "ZQawQqeikA4pRqKnkcuHuMnGZuKJTSt3V3EVmMjG56k",
14+
"sequenceNumber": 0
15+
},
16+
"last": {
17+
"digest": "ZQawQqeikA4pRqKnkcuHuMnGZuKJTSt3V3EVmMjG56k",
18+
"sequenceNumber": 0
19+
}
20+
}
21+
}
22+
}
23+
}
24+
25+
task 2, lines 22-32:
26+
//# run-graphql
27+
Response: {
28+
"data": null,
29+
"errors": [
30+
{
31+
"message": "Type 'InvalidType' not found in schema.",
32+
"locations": [
33+
{
34+
"line": 3,
35+
"column": 5
36+
}
37+
],
38+
"path": [
39+
"typeNotFound",
40+
"availableRange"
41+
],
42+
"extensions": {
43+
"code": "BAD_USER_INPUT"
44+
}
45+
}
46+
]
47+
}
48+
49+
task 3, lines 34-44:
50+
//# run-graphql
51+
Response: {
52+
"data": null,
53+
"errors": [
54+
{
55+
"message": "Field 'invalidField' not found in type 'Query'.",
56+
"locations": [
57+
{
58+
"line": 3,
59+
"column": 5
60+
}
61+
],
62+
"path": [
63+
"fieldNotFound",
64+
"availableRange"
65+
],
66+
"extensions": {
67+
"code": "BAD_USER_INPUT"
68+
}
69+
}
70+
]
71+
}
72+
73+
task 4, lines 46-56:
74+
//# run-graphql
75+
Response: {
76+
"data": null,
77+
"errors": [
78+
{
79+
"message": "Type 'InvalidType' not found in schema.",
80+
"locations": [
81+
{
82+
"line": 3,
83+
"column": 5
84+
}
85+
],
86+
"path": [
87+
"invalidTypeAndField",
88+
"availableRange"
89+
],
90+
"extensions": {
91+
"code": "BAD_USER_INPUT"
92+
}
93+
}
94+
]
95+
}
96+
97+
task 5, lines 58-68:
98+
//# run-graphql
99+
Response: {
100+
"data": null,
101+
"errors": [
102+
{
103+
"message": "'ZkLoginIntentScope' is not an Object or Interface.",
104+
"locations": [
105+
{
106+
"line": 3,
107+
"column": 5
108+
}
109+
],
110+
"path": [
111+
"notAnObjectOrInterface",
112+
"availableRange"
113+
],
114+
"extensions": {
115+
"code": "BAD_USER_INPUT"
116+
}
117+
}
118+
]
119+
}

crates/sui-indexer-alt-graphql/src/api/types/available_range.rs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,32 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
use anyhow::anyhow;
5-
use async_graphql::{Context, Object};
5+
use async_graphql::{
6+
Context, Object,
7+
registry::{MetaType, Registry},
8+
};
69
use std::{collections::BTreeSet, sync::Arc};
710

811
use crate::{
9-
error::{RpcError, feature_unavailable},
12+
error::{RpcError, bad_user_input, feature_unavailable, upcast},
1013
scope::Scope,
1114
task::watermark::Watermarks,
1215
};
1316

1417
use super::checkpoint::Checkpoint;
1518

19+
#[derive(thiserror::Error, Debug)]
20+
pub enum Error {
21+
#[error("'{0}' is not an Object or Interface.")]
22+
NotAnObjectOrInterface(String),
23+
24+
#[error("Type '{0}' not found in schema.")]
25+
TypeNotFound(String),
26+
27+
#[error("Field '{1}' not found in type '{0}'.")]
28+
FieldNotFound(String, String),
29+
}
30+
1631
/// Identifies a GraphQL query component that is used to determine the range of checkpoints for which data is available (for data that can be tied to a particular checkpoint).
1732
///
1833
/// Provides retention information for the type and optional field and filters. If field or filters are not provided we fall back to the available range for the type.
@@ -60,9 +75,10 @@ impl AvailableRange {
6075
ctx: &Context<'_>,
6176
scope: &Scope,
6277
available_range_key: AvailableRangeKey,
63-
) -> Result<Self, RpcError> {
78+
) -> Result<Self, RpcError<Error>> {
79+
available_range_key.validate(&ctx.schema_env.registry)?;
6480
let watermarks: &Arc<Watermarks> = ctx.data()?;
65-
let first = available_range_key.reader_lo(watermarks)?;
81+
let first = available_range_key.reader_lo(watermarks).map_err(upcast)?;
6682

6783
Ok(Self {
6884
scope: scope.clone(),
@@ -84,6 +100,32 @@ impl AvailableRangeKey {
84100
.map(|wm| acc.max(wm.checkpoint()))
85101
})
86102
}
103+
104+
/// Validates that this key references valid types and fields in the GraphQL schema.
105+
fn validate(&self, registry: &Registry) -> Result<(), RpcError<Error>> {
106+
let fields = match registry.types.get(&self.type_) {
107+
Some(MetaType::Object { fields, .. } | MetaType::Interface { fields, .. }) => fields,
108+
Some(_) => {
109+
return Err(bad_user_input(Error::NotAnObjectOrInterface(
110+
self.type_.to_string(),
111+
)));
112+
}
113+
None => {
114+
return Err(bad_user_input(Error::TypeNotFound(self.type_.to_string())));
115+
}
116+
};
117+
118+
if let Some(field_name) = &self.field {
119+
fields.get(field_name).ok_or_else(|| {
120+
bad_user_input(Error::FieldNotFound(
121+
self.type_.to_string(),
122+
field_name.to_string(),
123+
))
124+
})?;
125+
}
126+
127+
Ok(())
128+
}
87129
}
88130

89131
/// Return the appropriate error for the query or filter if the pipeline is not available.

crates/sui-indexer-alt-graphql/src/api/types/service_config.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use async_graphql::{Context, Object, Result};
55

66
use crate::{
7-
api::types::available_range::{AvailableRange, AvailableRangeKey},
7+
api::types::available_range::{AvailableRange, AvailableRangeKey, Error},
88
config::Limits,
99
error::RpcError,
1010
pagination::{PaginationConfig, is_connection},
@@ -212,7 +212,7 @@ impl ServiceConfig {
212212
type_: String,
213213
field: Option<String>,
214214
filters: Option<Vec<String>>,
215-
) -> Result<AvailableRange, RpcError> {
215+
) -> Result<AvailableRange, RpcError<Error>> {
216216
AvailableRange::new(
217217
ctx,
218218
&self.scope,

0 commit comments

Comments
 (0)