Types

The Quickstart presented a first example of a Warpgrapher configuration, shown again here.

static CONFIG: &str = "
version: 1
model:
  - name: User
    props:
      - name: email
        type: String
        required: false
";

Type Configuration

Recall that the version value is used to indicate the configuration file format version to be used. Right now, the only valid value is 1. The next element in the configuration a a data model. The model object is a list of types. The example shown in the Quickstart uses many defaults for simplicity. The definition below shows the full range of options for property definitions. Don't worry about relationships between types for the moment. Those are covered in the next section.

model:
  - name: String
    props:
      - name: String
        uses:
          create: Boolean
          query: Boolean
          update: Boolean
          output: Boolean
        type: String  # Boolean | Float | ID | Int | String
        required: Boolean
        list: Boolean
        resolver: String
        validator: String
    endpoints:
      read: Boolean
      create: Boolean
      update: Boolean
      delete: Boolean

Right under the model object is a list of types. The first attribute describing a type is a name. In the example from the Quickstart, the name of the type is User.

The second attribute describing a type is props. The props attribute is a list of properties that are stored on nodes of that type. Each property is described the several configuration attributes, as follows.

The name attribute is a string that identifies the property. It must be unique within the scope of the type. In the Quickstart example, the sole property on the User type is named email.

The uses attribute is an object that contains four fields within it, create, query, update, and output, each a boolean value. The fields within the uses attribute control whether the property is present in various parts of the GraphQL schema. If the create attribute is true, then the property will be included in the GraphQL input for creation operations. If false, the property will be omitted from creation operations. If the query attribute is true, the property will be included in the GraphQL schema for search query input. If false, the property will be omitted from search query operations. If the update attribute is true, the property will be included in the GraphQL schema input for updating existing nodes. If false, the property will be omitted from the update schema. Lastly, if the output attribute is true, the property will be included in the GraphQL schema for nodes returned to the client. If false, the property will be omitted from the output.

By default, all uses boolean attributes are true, meaning that the property is included in all relevant areas of the GraphQL schema. Selectively setting some of the uses attributes handles uses cases where a property should not be available for some operations. For example, one might set the create attribute to false if a property is a calculated value that should never be set directly. One might set update to false to make an attribute immutable -- for example, the email property of the User type might have update set to false if GraphQL clients should not be able to tamper with the identities of users. One might set output to false for properties that should never be read through the GraphQL interface, such as for keeping people from reading out a password property.

The type attribute of the property definition is a String value that must take on a value of Boolean, Float, ID, Int, or String, defining type of the property.

If the required attribute of the property definition is false, the property is not required (it is optional). By default this attribute is true, which means it must be provided when nodes of this type are created (unless hidden from the create use) and it must be present (non-null) when retrieving the node from Warpgrapher (again, unless hidden from the output use).

If the list attribute of the property definition is true, the property is a list of scalar values of type. If list is false, the property is only a single value of that scalar type.

The resolver attribute is a text key that is used to identify a custom-written resolver function. Warpgrapher allows applications to define custom resolvers that do more or different things than the default CRUD operations automatically provided by Warpgrapher itself. For example, a custom resolver might dynamically calculate a value, such as a total or average, rather than just returning a value from the database. Custom resolvers for dynamic properties are covered in greater detail later in the book.

The validator attribute is a text key that is used to identify a fuction that validates an input. For example, a validation function might check an email against and email validation regex. Validation functions are covered in greater detail later in the book.

Note that the endpoints attribute is on the type definition, not the property definition, as indicated by the indentation in the YAML example above. The endpoints attribute is somewhat similar to the uses boolean, but at the level of the whole type rather than a single property. If the read attribute is true, Warpgrapher will generate a query in the GraphQL schema so that node of this type can be retrieved. If false, no query will be generated. If the create attribute is true, Warpgrapher will generate a node creation mutation in the GraphQL schema. If false, no creation mutation will be generated. If the update attribute is true, Warpgrapher will generate a node update mutation in the GraphQL schema. If false, no update mutation will be generated. Lastly, if the delete attribute is true, Warpgrapher will generate a node deletion mutation in the GraphQL schema. If false, no delete mutation will be generated.

Generated Schema

Warpgrapher uses the configuration described above to automatically generate a GraphQL schema and default resolver to create, read, update, and delete nodes of the types defined in the configuration's model section. The remainder of this section walks through the contents of the schema in detail.

The top level GraphQL Query has two queries within it, as shown below. The _version query returns a scalar String with the version of the GraphQL service. The value returned is set with the with_version method on the EngineBuilder.

type Query {
  User(input: UserQueryInput, options: UserOptions): [User!]
  _version: String
}

The User query, above, is generated by Warpgrapher for the retrieval of User nodes. The query takes two parameters, an input parameter that provides any search parameters that narrow down the set of Users to be retrieved, and an options object. The query returns a User type.

The UserQueryInput, defined in the schema snippet below, is use to provide search parameters to identify the User nodes to return to the client. The User node configuration had only one property, email. Warpgrapher automatically adds an id property that contains a unique identifier for nodes. In the GraphQL schema, the id is always represented as a string. However, in some Gremlin back-ends, the id may be required to be an integer, in which case the id field in the GraphQL schema will be a String that can be successfully parsed into an integer value.

input UserQueryInput {
  email: StringQueryInput
  id: StringQueryInput
}

Note that the types of both email and id are StringQueryInput, not a simple String scalar. This is because the query input allows for more than just an exact match.

input StringQueryInput {
  CONTAINS: String
  EQ: String
  GT: String
  GTE: String
  IN: [String!]
  LT: String
  LTE: String
  NOTCONTAINS: String
  NOTEQ: String
  NOTIN: [String!]
}

The StringQueryInput has various options for matching a String more flexibly than an exact match. The CONTAINS operator looks for the associated String value anywhere in the target property (e.g. the email or id properties of a User node). EQ looks for an exact match. GT and GTE are greater-than and great-than-or-equals, which are useful for searching for ranges based on alphabetization, as do LT and LTE. The IN operators allows for searching for any string that is within a given set of Strings. NOTCONTAINS is the opposite of CONTAINS, looking for property values that do not contain the provided String. NOTEQ looks for non-matching Strings. And finally, NOTIN matches property values that do not appear in the provided set of Strings.

The options argument, described back above as an argument for the User query as a whole, is of type UserOptions. The UserOptions type has a single property, called sort, which is a list of zero or more UserSort objects. Each UserSort object has two enumeration properties, direction and orderBy.

type UserOptions {
  sort: [UserSort!]
}

type UserSort {
  direction: DirectionEnum
  orderBy: UserOrderByEnum!
}

enum DirectionEnum {
  ascending
  descending
}

enum UserOrderByEnum {
  id
  email
}

The UserOrderByEnum has variant values for each of the properties (but not relationships) on a User. By including one or more values in the sort array provided to UserOptions, it is possible to sort results coming back from Warpgrapher. The direction property determines whether the results are returned in ascending or descending sort order. The orderBy field determines on which property the results are sorted. If the sort array contains more than one value, then resorts groups of results with the same first sort key are further sorted by the second key, and so on. For example, a sort array might have entries for joinDate and then name to sort first by the date someone joined, and alphabetically for all people who joined on the same date.

The results of the query are returned in a User type, shown below.

type User { email: String id: ID! }


The `User` type is the definition of the output type for the `User` GraphQL query. The names are the same, but these are two distinct things in the GraphQL schema -- the `User` query returns an array of zero or more `User` types.  The `User` type is two fields, and `id` and an `email`.  The id is a unique identifier for that node, which may be an integer or a UUID, depending on the graph database used. The `email` string is the single property that was defined on the example schema.

type Mutation { UserCreate(input: UserCreateMutationInput!, options: UserOptions): User UserDelete(input: UserDeleteInput!, options: UserOptions): Int UserUpdate(input: UserUpdateInput!, options: UserOptions): [User!] }


In addition to providing queries to retrieve existing nodes, Warpgrapher also automatically generates GraphQL schema elements and resolvers for create, update, and delete operations. The schema snippet above shows the mutations that are generated for the `User` node in the example configuration.  All three of the mutations take an `options` argument, which was described in the section on queries, above. Additionally, all three mutations take an `input` value, that provides the information necessary to complete the create, update, or delete operation, respectively.  Creation operations return the created node. Update operations return all the nodes that were matched and updated.  Lastly, the delete operation returns the number of nodes that were deleted.  The input arguments are detailed below.

input UserCreateMutationInput { email: String id: ID }


The `UserCreateMutationInput` mutation input includes the email property defined in the example configuration. It also includes an `id` property. Note that the `id` property is optional. If not provided by the client, it will be set to a unique identifier by the Warpgrapher server. The reason that clients are permitted to set the `id` when creating nodes is to allow for offline mode support, which may require the creation of identifiers within local caches that should remain the same after synchronization with the server.

input UserDeleteInput { DELETE: UserDeleteMutationInput MATCH: UserQueryInput }

input UserDeleteMutationInput


The `UserDeleteInput` input is used to identify which nodes to delete. Note that the `MATCH` part of the argument is the very same `UserQueryInput` type used in the `User` query schema element above. So searching for which nodes to delete is the same input format used to search for nodes to return in a read query.  The `UserDeleteMutationInput` is empty right now, and may be omitted. It will become relevant later, in the discussion on relationships between nodes.

input UserUpdateInput { MATCH: UserQueryInput SET: UserUpdateMutationInput }

input UserUpdateMutationInput { email: String }


Lastly, the `UserUpdateInput` input is provided to the udpate mutation in order to select the nodes that need to be updated and describe the update to be applied.  The `MATCH` attribute is used to identify what nodes require the update. Note that the type of the `MATCH` attribute is `UserQueryInput`, which is the same type used for searching for nodes in the GraphQL query above.  The `SET` attribute is used to provide the new values to which the matching nodes should be set. In this example, it is a single String value for the `email` of the `User`. Note that `id`s are set only at creation. They cannot be updated later.

## Full Schema Listing

The full schema, described in pieces above, is included below:

input UserDeleteInput { DELETE: UserDeleteMutationInput MATCH: UserQueryInput }

input UserQueryInput { email: StringQueryInput id: StringQueryInput }

type Mutation { UserCreate(input: UserCreateMutationInput!, options: UserOptions): User UserDelete(input: UserDeleteInput!, options: UserOptions): Int UserUpdate(input: UserUpdateInput!, options: UserOptions): [User!] }

input UserUpdateMutationInput { email: String }

type Subscription

input UserUpdateInput { MATCH: UserQueryInput SET: UserUpdateMutationInput }

type Query { User(input: UserQueryInput, options: UserOptions): [User!] _version: String }

input UserDeleteMutationInput

type User { email: String id: ID! }

input UserCreateMutationInput { email: String id: ID }

input StringQueryInput { CONTAINS: String EQ: String GT: String GTE: String IN: [String!] LT: String LTE: String NOTCONTAINS: String NOTEQ: String NOTIN: [String!] }