-
Notifications
You must be signed in to change notification settings - Fork 258
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(2999): add validation on http query (#3143)
- Loading branch information
Showing
7 changed files
with
217 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
schema @server(port: 8000) { | ||
query: Query | ||
} | ||
type Query { | ||
posts(id: PostData): Int @http(url: "upstream.com", query: [{key: "id", value: "{{.args.id.data}}"}]) | ||
} | ||
type PostData { | ||
author: String | ||
data: PostData | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
use tailcall_valid::{Valid, Validator}; | ||
|
||
use super::BlueprintError; | ||
use crate::core::config::{Config, Field}; | ||
use crate::core::mustache::Segment; | ||
use crate::core::scalar::Scalar; | ||
use crate::core::Mustache; | ||
|
||
// given a path, it follows path till leaf node and provides callback at leaf | ||
// node. | ||
fn path_validator<'a>( | ||
config: &Config, | ||
mut path_iter: impl Iterator<Item = &'a String>, | ||
type_of: &str, | ||
leaf_validator: impl Fn(&str) -> bool, | ||
) -> Valid<(), BlueprintError> { | ||
match config.find_type(type_of) { | ||
Some(type_def) => match path_iter.next() { | ||
Some(field) => match type_def.fields.get(field) { | ||
Some(field_type) => { | ||
path_validator(config, path_iter, field_type.type_of.name(), leaf_validator) | ||
} | ||
None => Valid::fail(BlueprintError::FieldNotFound(field.to_string())), | ||
}, | ||
None => Valid::fail(BlueprintError::ValueIsNotOfScalarType(type_of.to_string())), | ||
}, | ||
None if leaf_validator(type_of) => Valid::succeed(()), | ||
None => Valid::fail(BlueprintError::TypeNotFoundInConfig(type_of.to_string())), | ||
} | ||
} | ||
|
||
/// Function to validate the arguments in the HTTP resolver. | ||
pub fn validate_argument( | ||
config: &Config, | ||
template: Mustache, | ||
field: &Field, | ||
) -> Valid<(), BlueprintError> { | ||
let scalar_validator = | ||
|type_: &str| Scalar::is_predefined(type_) || config.find_enum(type_).is_some(); | ||
|
||
Valid::from_iter(template.segments(), |segment| match segment { | ||
Segment::Expression(expr) if expr.first().map_or(false, |v| v.contains("args")) => { | ||
match expr.get(1) { | ||
Some(arg_name) if field.args.get(arg_name).is_some() => { | ||
let arg_type_of = field.args.get(arg_name).as_ref().unwrap().type_of.name(); | ||
path_validator(config, expr.iter().skip(2), arg_type_of, scalar_validator) | ||
.trace(arg_name) | ||
} | ||
Some(arg_name) => { | ||
Valid::fail(BlueprintError::ArgumentNotFound(arg_name.to_string())) | ||
.trace(arg_name) | ||
} | ||
None => Valid::fail(BlueprintError::TooFewPartsInTemplate), | ||
} | ||
} | ||
_ => Valid::succeed(()), | ||
}) | ||
.unit() | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use tailcall_valid::{Valid, Validator}; | ||
|
||
use super::validate_argument; | ||
use crate::core::blueprint::BlueprintError; | ||
use crate::core::Mustache; | ||
use crate::include_config; | ||
|
||
#[test] | ||
fn test_recursive_case() { | ||
let config = include_config!("../fixture/recursive-arg.graphql"); | ||
let config = config.unwrap(); | ||
let template = Mustache::parse("{{.args.id.data}}"); | ||
let field = config | ||
.find_type("Query") | ||
.and_then(|ty| ty.fields.get("posts")) | ||
.unwrap(); | ||
let validation_result = validate_argument(&config, template, field); | ||
|
||
assert!(validation_result.is_fail()); | ||
assert_eq!( | ||
validation_result, | ||
Valid::fail(BlueprintError::ValueIsNotOfScalarType( | ||
"PostData".to_string() | ||
)) | ||
.trace("id") | ||
); | ||
} | ||
} |
60 changes: 60 additions & 0 deletions
60
tests/core/snapshots/non-scalar-value-in-query.md_error.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
--- | ||
source: tests/core/spec.rs | ||
expression: errors | ||
--- | ||
[ | ||
{ | ||
"message": "too few parts in template", | ||
"trace": [ | ||
"Query", | ||
"invalidArgument", | ||
"@http", | ||
"query" | ||
], | ||
"description": null | ||
}, | ||
{ | ||
"message": "value 'Nested' is not of a scalar type", | ||
"trace": [ | ||
"Query", | ||
"invalidArgumentType", | ||
"@http", | ||
"query", | ||
"criteria" | ||
], | ||
"description": null | ||
}, | ||
{ | ||
"message": "no argument 'criterias' found", | ||
"trace": [ | ||
"Query", | ||
"unknownArgument", | ||
"@http", | ||
"query", | ||
"criterias" | ||
], | ||
"description": null | ||
}, | ||
{ | ||
"message": "Cannot find type Criteria in the config", | ||
"trace": [ | ||
"Query", | ||
"unknownArgumentType", | ||
"@http", | ||
"query", | ||
"criteria" | ||
], | ||
"description": null | ||
}, | ||
{ | ||
"message": "field unknown_field not found", | ||
"trace": [ | ||
"Query", | ||
"unknownField", | ||
"@http", | ||
"query", | ||
"criteria" | ||
], | ||
"description": null | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
--- | ||
error: true | ||
--- | ||
|
||
# test objects in args | ||
|
||
```graphql @config | ||
schema @server @upstream { | ||
query: Query | ||
} | ||
|
||
type Query { | ||
invalidArgumentType(criteria: Nested): [Employee!]! | ||
@http( | ||
url: "http://localhost:8081/family/employees" | ||
query: [{key: "nested", value: "{{.args.criteria}}", skipEmpty: true}] | ||
) | ||
unknownField(criteria: Nested): [Employee!]! | ||
@http( | ||
url: "http://localhost:8081/family/employees" | ||
query: [{key: "nested", value: "{{.args.criteria.unknown_field}}", skipEmpty: true}] | ||
) | ||
unknownArgument(criteria: Nested): [Employee!]! | ||
@http( | ||
url: "http://localhost:8081/family/employees" | ||
query: [{key: "nested", value: "{{.args.criterias}}", skipEmpty: true}] | ||
) | ||
invalidArgument(criteria: Nested): [Employee!]! | ||
@http(url: "http://localhost:8081/family/employees", query: [{key: "nested", value: "{{.args}}", skipEmpty: true}]) | ||
unknownArgumentType(criteria: Criteria): [Employee!]! | ||
@http( | ||
url: "http://localhost:8081/family/employees" | ||
query: [{key: "nested", value: "{{.args.criteria}}", skipEmpty: true}] | ||
) | ||
} | ||
|
||
type Employee { | ||
id: ID! | ||
} | ||
|
||
input Nested { | ||
hasChildren: Boolean | ||
} | ||
``` |
02c7821
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Running 30s test @ http://localhost:8000/graphql
4 threads and 100 connections
743035 requests in 30.02s, 3.72GB read
Requests/sec: 24755.19
Transfer/sec: 127.06MB