Skip to content

Commit

Permalink
feat: create @call operator
Browse files Browse the repository at this point in the history
  • Loading branch information
ologbonowiwi committed Jan 29, 2024
1 parent eac8120 commit 7dc62fd
Show file tree
Hide file tree
Showing 24 changed files with 919 additions and 24 deletions.
2 changes: 1 addition & 1 deletion examples/jsonplaceholder.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ type Post {
userId: Int!
title: String!
body: String!
user: User @http(path: "/users/{{value.userId}}")
user: User @call(query: "user", args: {id: "{{value.userId}}"})
}
18 changes: 18 additions & 0 deletions generated/.tailcallrc.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,24 @@ directive @cache(
maxAge: Int!
) on FIELD_DEFINITION

"""
The @call operator is used to reference a resolver operator (available resolvers are `@http`, `@grpc` and `@graphQL`).
"""
directive @call(
"""
The name of the field that has the resolver to be called.
"""
query: String!
# """
# The name of the field that has the resolver to be called.
# """
# mutation: String!
"""
The arguments to be replace the values on the actual resolver.
"""
args: JSON
) on FIELD_DEFINITION

"""
Allows composing operators as simple expressions
"""
Expand Down
54 changes: 54 additions & 0 deletions generated/.tailcallrc.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,36 @@
],
"type": "object"
},
"Call": {
"description": "For instance, if you have a `user(id: Int!): User @http(path: \"/users/{{args.id}}\")` field on the `Query` type, you can reference it from another field on the `Query` type using the `@call` operator. So, on `Post.user` you can declare `user: User @call(query: \"user\", args: {id: \"{{value.userId}}\"})`, and this will replace the `{{args.id}}` used in the `@http` operator with the value of `userId` from the `Post` type.\n\nIn case you have a `user(input: UserInput!): User @http(path: \"/users\")` field on the `Mutation` type, you can reference it from another field on the `Mutation` type. So, on `Post.user` you can declare `user: User @call(mutation: \"user\", args: {input: \"{{value.userInput}}\"})`, and this will replace the `{{args.input}}` used in the `@http` operator with the value of `userInput` from the `Post` type.",
"properties": {
"args": {
"additionalProperties": {
"type": "string"
},
"description": "The arguments of the field on the `Query` type that you want to call. For instance `{id: \"{{value.userId}}\"}`.",
"type": "object"
},
"mutation": {
"description": "The name of the field on the `Mutation` type that you want to call. For instance `createUser`.",
"type": [
"string",
"null"
]
},
"query": {
"description": "The name of the field on the `Query` type that you want to call. For instance `user`.",
"type": [
"string",
"null"
]
}
},
"required": [
"args"
],
"type": "object"
},
"Const": {
"description": "The `@const` operators allows us to embed a constant response for the schema.",
"properties": {
Expand Down Expand Up @@ -171,6 +201,19 @@
],
"type": "object"
},
{
"additionalProperties": false,
"description": "Reuses a resolver pre-defined on type `Query`",
"properties": {
"call": {
"$ref": "#/definitions/Call"
}
},
"required": [
"call"
],
"type": "object"
},
{
"additionalProperties": false,
"description": "Evaluate to constant data",
Expand Down Expand Up @@ -868,6 +911,17 @@
],
"description": "Sets the cache configuration for a field"
},
"call": {
"anyOf": [
{
"$ref": "#/definitions/Call"
},
{
"type": "null"
}
],
"description": "Inserts a call resolver for the field."
},
"const": {
"anyOf": [
{
Expand Down
1 change: 1 addition & 0 deletions src/blueprint/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ fn to_fields(
.and(update_graphql(&operation_type).trace(config::GraphQL::trace_name().as_str()))
.and(update_expr(&operation_type).trace(config::Expr::trace_name().as_str()))
.and(update_modify().trace(config::Modify::trace_name().as_str()))
.and(update_call(&operation_type).trace(config::Call::trace_name().as_str()))
.and(update_nested_resolvers())
.try_fold(&(config, field, type_of, name), FieldDefinition::default())
};
Expand Down
5 changes: 3 additions & 2 deletions src/blueprint/mustache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,14 @@ impl<'a> MustachePartsValidator<'a> {
}

impl FieldDefinition {
pub fn validate_field(&self, type_of: &config::Type, config: &Config) -> Valid<(), String> {
pub fn validate_field(self, type_of: &config::Type, config: &Config) -> Valid<Self, String> {
// XXX we could use `Mustache`'s `render` method with a mock
// struct implementing the `PathString` trait encapsulating `validation_map`
// but `render` simply falls back to the default value for a given
// type if it doesn't exist, so we wouldn't be able to get enough
// context from that method alone
// So we must duplicate some of that logic here :(
let parts_validator = MustachePartsValidator::new(type_of, config, self);
let parts_validator = MustachePartsValidator::new(type_of, config, &self);

match &self.resolver {
Some(Expression::IO(IO::Http { req_template, .. })) => {
Expand Down Expand Up @@ -168,5 +168,6 @@ impl FieldDefinition {
}
_ => Valid::succeed(()),
}
.map_to(self)
}
}
Loading

0 comments on commit 7dc62fd

Please sign in to comment.