import ballerina/graphql;
import ballerina/http;
import ballerina/lang.value;

@graphql:ServiceConfig {
    // Initialization of the `graphqlContext` should be provided to the
    // `contextInit` field.
    contextInit: isolated function (http:RequestContext requestContext,
                                    http:Request request)
                                    returns graphql:Context|error {

        // Initialize the `graphql:Context` object.
        graphql:Context context = new;

        // Retrieve the header named `scope` and set it to the context with the
        // `scope` key. If the header does not exist, this will return an
        // `error`, and thereby, the request will not be processed.
        context.set("scope", check request.getHeader("scope"));

        // Finally, the context object has to be returned.
        return context;

    }
}
service /graphql on new graphql:Listener(4000) {

    // Define a `Person` object when the service is initialized.
    private final Person person;

    function init() {
        // Initialize the `person` value.
        self.person = new("Walter White", 51, 737000.00);

    }

    // Resource functions can be defined without a context parameter.
    resource function get greet() returns string {
        return "Hello, world";
    }

    // If the context is needed, it should be defined as the first paramter of
    // the resolver function.
    resource function get profile(graphql:Context context)
    returns Person|error {

        // Retrieve the `scope` attribute from the context. This will return
        // a `graphql:Error` if the `scope` is not
        // found in the context.
        value:Cloneable|isolated object {} scope = check context.get("scope");

        // The profile information will be returned for the scope of either
        // `admin` or `user`.
        if scope is string {
            if scope == "admin" || scope == "user" {
                return self.person;
            }
        }

        // Return an `error` if the required scope is not found.
        return error("Permission denied");
    }
}

// Define a service class to use as an object in the GraphQL service.
public service class Person {

    private final string name;
    private final int age;
    private final float salary;

    function init(string name, int age, float salary) {
        self.name = name;
        self.age = age;
        self.salary = salary;
    }

    resource function get name() returns string {
        return self.name;
    }

    resource function get age() returns int {
        return self.age;
    }

    resource function get salary(graphql:Context context) returns float|error {

        // Retrieve the `scope` attribute from the context.
        value:Cloneable|isolated object {} scope = check context.get("scope");

        // The `salary` value will only be returned if the `scope` is `admin`.
        if scope is string {
            if scope == "admin" {
                return self.salary;
            }
        }

        // Return an `error` if the required scope is not found.
        return error("Permission denied");

    }
}

Context

The graphql:Context object can be used to pass meta information between the resolver functions. An init function should be provided using the graphql:ServiceConfig parameter named contextInit. Inside the init function, the graphql:Context can be initialized. Values from the http:RequestContext and http:Request can be added as well as other values. These values are stored as key-value pairs. The key is a string and the value can be any readonly value or an isolated object. If the init function is not provided, an empty context object will be created. The context can be accessed by defining it as the first parameter of any resolver (resource/remote) function.

For more information on the underlying package, see the graphql package.

import ballerina/graphql;
import ballerina/http;
import ballerina/lang.value;
@graphql:ServiceConfig {
    contextInit: isolated function (http:RequestContext requestContext,
                                    http:Request request)
                                    returns graphql:Context|error {

Initialization of the graphqlContext should be provided to the contextInit field.

        graphql:Context context = new;

Initialize the graphql:Context object.

        context.set("scope", check request.getHeader("scope"));

Retrieve the header named scope and set it to the context with the scope key. If the header does not exist, this will return an error, and thereby, the request will not be processed.

        return context;

Finally, the context object has to be returned.

    }
}
service /graphql on new graphql:Listener(4000) {
    private final Person person;

Define a Person object when the service is initialized.

    function init() {
        self.person = new("Walter White", 51, 737000.00);

Initialize the person value.

    }
    resource function get greet() returns string {
        return "Hello, world";
    }

Resource functions can be defined without a context parameter.

    resource function get profile(graphql:Context context)
    returns Person|error {

If the context is needed, it should be defined as the first paramter of the resolver function.

        value:Cloneable|isolated object {} scope = check context.get("scope");

Retrieve the scope attribute from the context. This will return a graphql:Error if the scope is not found in the context.

        if scope is string {
            if scope == "admin" || scope == "user" {
                return self.person;
            }
        }

The profile information will be returned for the scope of either admin or user.

        return error("Permission denied");
    }
}

Return an error if the required scope is not found.

public service class Person {

Define a service class to use as an object in the GraphQL service.

    private final string name;
    private final int age;
    private final float salary;
    function init(string name, int age, float salary) {
        self.name = name;
        self.age = age;
        self.salary = salary;
    }
    resource function get name() returns string {
        return self.name;
    }
    resource function get age() returns int {
        return self.age;
    }
    resource function get salary(graphql:Context context) returns float|error {
        value:Cloneable|isolated object {} scope = check context.get("scope");

Retrieve the scope attribute from the context.

        if scope is string {
            if scope == "admin" {
                return self.salary;
            }
        }

The salary value will only be returned if the scope is admin.

        return error("Permission denied");

Return an error if the required scope is not found.

    }
}
bal run graphql_context.bal
# Send a query to the GraphQL endpoint using a cURL command. Set the `scope` header value to `admin`.
 # The query used: { profile { name salary } }
 curl -X POST -H "Content-type: application/json" -H "scope: admin" -d '{ "query": "{ profile { name salary } }" }' 'http://localhost:4000/graphql'
 {"data":{"profile":{"name":"Walter White", "salary":737000.0}}}
 # Now, send a query with the `scope` header value set to `user`. This will return an error in the `salary` field.
 # The query used: { profile { name salary } }
 curl -X POST -H "Content-type: application/json" -H "scope: user" -d '{ "query": "{ profile { name salary } }" }' 'http://localhost:4000/graphql'
 {"errors":[{"message":"Permission denied", "locations":[{"line":1, "column":18}], "path":["profile", "salary"]}], "data":null}
 # Now, send a query with the `scope` header value set to `unknown`. This will return an error in the `profile` field.
 # The query used: { profile { name salary } }
 curl -X POST -H "Content-type: application/json" -H "scope: unknown" -d '{ "query": "{ profile { name salary } }" }' 'http://localhost:4000/graphql'
 {"errors":[{"message":"Permission denied", "locations":[{"line":1, "column":3}], "path":["profile"]}], "data":null}