On this page, we provide an excerpt from the official GraphQL documentation (Schemas and Types), enriched with details about our implementation and mapping of Livebase classes to GraphQL. In the Glossary section at the bottom, we have collected a cheat sheet with the GraphQL vocabulary we will use in this documentation.
Structure of the schema #
The schema is at the core of the GraphQL server implementation and describes the functionality available to clients connecting to the API. When the server receives a request, it is validated and executed according to the schema. The schema defines the type system_, which is the set of data that can be validated, queried, and executed on the API.
Basically, we can divide the type system into two categories:
- types that describe the data model handled by the API (objects, scalar and enum);
- types that identify the permitted operations on the data model (services and input objects).
As stated in the introduction, the syntax chosen to write a schema takes the name Schema Definition Language (SDL), is similar to query language and, like the latter, is independent of the server implementation.
Objects and fields #
At the base of the data model, we find objects (GraphQL Object Type, or simply object type); in SDL, an object is identified by the keyword type
and simply represents a set of fields that you can retrieve from the server in an aggregate structure.
type Employee {
_id: ID
full_name: String
age: Int
team: Team
}
type Team {
_id: ID
name: String
}
A name characterizes each field of an object and a type; a field may itself contain an object (e.g., the team
field of Employee
is of type Team
), or resolve into a simple piece of information (e.g., full_name
and age
of Employee
). We will discuss primitive data types later.
The following modifiers can be applied to the field type:
!
: the field is non-nullable. The server promises always to return a value when requested;[<TypeName>]
: the field returns a list of values of that type.
Finally, a field can be characterized by zero or more arguments, collected in round brackets as in the example:
type Foo {
campoSemplice: String
campoNonNull: String!
campoConArgomenti(arg1: String!, arg2: Int = 42): String
}
An argument can also be marked with !
to make it mandatory; in that case, the call is invalid if you request the field without specifying a value for that argument. Conversely, if the argument is not mandatory, it will take the default value as defined in the schema (e.g. 42
for arg2
).
Services and Input Objects #
Allowed operations are collected in two special types: Query
and Mutation
. Both define the input point of each GraphQL call. By requesting one or more fields of Query
, the client can navigate the object graph to read the information present; similarly, the fields of Mutation
allow to send data to the server, modify the graph, and read the result of the write.
schema {
query: Query
mutation: Mutation
}
type Query {
# una query
Employee___get(_id: ID!): Employee
}
type Mutation {
# una mutation
Employee___create(data: EmployeeCreate!): Employee
}
As the example suggests, all services are parametric and have at least one mandatory argument in our implementation. What we didn’t mention by introducing arguments in the previous paragraph is that it is possible to pass objects as arguments (e.g., the Employee___create
service requires the data
argument of type EmployeeCreate
). An input object of this type is called an input type, and is identified in the schema by the keyword input
:
input EmployeeCreate {
full_name: String
age: Int
team: Team
}
Service naming conventions #
In practice, Query
and Mutation
are normal object type, and their fields have as their type the data structure returned by the server upon execution of the operation. In the example above, both Employee___get
and Employee___create
return an Employee
object, but the former already exists on the server and is requested to be read, while the latter is read contextually to its creation.
But what then are Employee___get
and Employee___create
? By convention, we use the term service in this documentation to refer to the fields of Query
and Mutation
:
- with read service or query type service, we refer to the name of a field in
Query
; - with write service or mutation service, we refer to the name of a field of
Mutation
;
Primitive data types #
Primitive data types are named scalar; they represent the “leaves” of a query, always resolve concrete data (such as strings or numbers), and have no subfields.
System Scalar #
GraphQL handles the following types of scalar:
String
: alphanumeric sequences of characters expressed in UTF-8 encoding;Int
: 32-bit signed integers;Float
: double-precision signed floating-point numbers;Boolean
: logical valuestrue
andfalse
;ID
: an identifier used to identify resources and objects uniquely. It is intended to be human-readable.
All homonymous data types handled by Livebase are mapped in GraphQL to the corresponding scalar type (e.g., an attribute of type string in the model is a String
in GraphQL). The platform attribute __id
is mapped to the ID
type, while the Float
type is not used.
Livebase scalar #
Our implementation also adds the following scalar to map the remaining data types:
Real
: analogous to the real type;Date
: analogous to the date type;Time
: analog of the time type;Datetime
: analog of the datetime type;- Text`: analogous of text;
- Serial: analog of serial;
- Year: analog of year;
All of the types listed are serialized as strings, except for Year
which is serialized as an integer (like Int
).
Dates and numbers formats #
The representation format of fields of type Date
, Time
, Datetime
or Real
depends on the Cloudlet user’s custom display settings and the Cloudlet’s own default settings.
In read services that return Date
, Time
, or Datetime
, the optional format
argument, of type String
, is available on fields of this type, allowing the user to specify a format (among those supported by the Cloudlet) other than the one set for the user.
type Employee {
_id: ID
date_of_birth(format: String = "default"): Date
# altri campi
}
type Query {
Employee___get(_id: ID!): Employee
}
query {
Employee___get(id: "10101") {
date_of_birth(format: "MMM-d-yyyy")
}
}
Enumerated #
We will also find another type of object in the generated schema are enumerates (enum). An enum is a special type of scalar, limited to a given set of allowed values.
Model - schema mapping #
For each application view (Application Schema) defined in the engine model, a separate GraphQL API is generated. The schema of each API respects the constraints, manageability, filters and permissions defined in the engine model for that specific application view.
Each class <ClassName>
of the engine model (among those enabled for that application view and reachable), is mapped to the schema in the type <ClassName>
structure, to access which several services are generated, whose names follow the format <ClassName>___<ServiceType>>
. As it is true for all generated application interfaces, in GraphQL, each service works on the graph of objects reachable from that class; for this reason, services are generated only for main classes and not for part classes, since the management of these objects is subordinated to the whole classes.
In addition to <ClassName>
, additional object type and input type are generated to support services on that class; the names of these structures follow the format type <ClassName><TypeName>
or input <ClassName><InputName>
(e.g., type EmployeePage
or input EmployeeCreate
).
Attributes on <ClassName>
are mapped as follows:
- the name of the attribute matches the name defined on the engine model, while the type follows the mapping shown in Primitive data types;
- by default, the platform attribute
__id
is made visible for all classes, with name_id
of typeID
(in GraphQL, the double underscore__
is a prefix reserved for metadata); - in input structures, required attributes in the engine model are marked with
!
;
An optional field exclusive to the GraphQL schema is also enabled on both ClassName
and its supporting structures: _clientId: ID
. In write services, clients may fill this field with arbitrary values (e.g. hash or timestamp).
The outgoing roles on <ClassName>
are mapped differently depending on the type of service; see Read services and Write services for more information.
Response Structure #
GraphQL emphasizes consistency and predictability of results, so server responses always have a predictable structure. Let’s now look at the common format of all GraphQL responses (the query format is described here: Formulating Query and Mutation).
The JSON object returned by the server can take two forms:
If the request was executed with success, there will be a
data
field, containing, for each service invoked by the query, a key with the name of the service and as value, the object returned by the service.{ "data": { "className___ServiceType": { ... } } }
If the server encountered errors in resolving the request, there will be a
errors
field, containing a list of the errors encountered.{ "errors": [ ... ], "data": { "ClassName___ServiceType": null } }
According to the specification, if no errors are returned, the errors
field is absent, while if no data is returned for a service, the data
field will still contain the service name, with value null.
The elements of the errors
field have, in turn, a defined structure. Each error contains the following fields:
message
: a string with the description of the error. It is the only field that is always present;locations
: array of locations, each characterized by the fieldsline
andcolumn
, where an error occurred;path
: array of paths, represented by strings (in case of fields) and integers (in case of indexes);extensions
: additional information provided by the server (seeIssue
).
The error types fall into two categories: errors in the request and errors occurring on the server. In the first case, the client has sent a request that is syntactically invalid or violates type checking, i.e. GraphQL schema validation. In the second case, the client sent a correct request, but errors occurred on the server during its resolution.
Detail: a request that violates type checking.
Server-side errors (Issues
). #
When it receives a valid call, the server checks that all constraints defined on the engine model for the elements involved (in the context of the application view on which the schema is defined) are met. For example, in a write, the server checks if constraints on attributes are respected and if the user has a profile that allows him to write to that attribute.
In detail, the server analyzes the call in three steps, checking for each step a given category of constraints:
- Grant: constraints related to the rights of the user profile on the engine model elements available in the schema, defined at the Profile Schema and Permission Schema level. Grants are controlled for both reads and writes, as a engine model element may be on in an application but off for a profile accessing it.
- Veto Action: constraints on Class Warnings (defined at the Application Schema level). GraphQL “simulates” an action when invoking a writing service.
- Data Validation: constraints related to input validation, checked only in writes. They include constraints related to Class Warnings evaluated due to the persist on the database and the same Database Schema level constraints on attribute domains, role cardinality, and unique constraints.
If an error occurs in any of the listed steps, the server stops retrieving data and returns an error. The structure to represent the error is the Issue
object, placed in the extensions
field:
type Issue {
userMessage: String
issueLevel: IssueLevel
issueReferenceType: IssueReferenceType
issueType: IssueType
entityName: String
entityID: ID
attributeNames: [String!]
roleNames: [String!]
applicationName: String!
profileName: String!
traceId: String
}
Let’s examine it field by field:
userMessage
is a message designed for display in the client to notify the user of the problem;issueLevel
,issueReferenceType
, andissueType
describe hierarchically the type of problem that occurred. The related structures are of type enum. More information in detail;entityName
andentityID
refer to the Entity (intended as a model class), or the particular instance on which the problem was encountered;attributeNames
androleNames
enrich the context, indicating which attributes/roles of the entity are involved. They are valued only ifissueReferenceType
isENTITY_ATTRIBUTE
orENTITY_ROLE
;applicationName
andprofileName
indicate on which application view and with which profile the problem was encountered, respectively;traceId
is an internal support identifier useful for tracing the request made for a bug report.
Detail: IssueLevel
, IssueReferenceType
, and IssueType
.
Debug a request #
All generated services have an optional insight
argument. This, if included, allows you to ask the server for information about the data retrieval process (e.g., execution time); this is useful for identifying possible bottlenecks or bugs in the GraphQL server.
query {
# allowed values: FULL, LIGHT
Employee___get(id: "10101", insight: FULL) {
full_name
}
}
In the response, the required insight information is in an additional extensions
field, at the same level as data
and errors
.
{
"data": { "Employee___get": { ... }},
"extensions": { ... }
}
Introspection #
In scenarios where GraphiQL cannot be accessed, the generated API still supports introspection as an alternative tool to discover information about the schema. This means that the following standard read services are available:
__schema
: retrieves metadata about all types defined on the schema and the schema itself;__type(name: String!)
: retrieves metadata about a type on the schema, given its name.
For more information about introspection service fields, please refer to the official documentation: Introspection.
Glossary #
Below we have collected, in alphabetical order, the GraphQL terms used in this guide.
- Argument #
- Key-value pair associated with a specific field. A field can have zero or more arguments. All services require at least one input type as an argument.
- Enum #
- Special type of scalar, restricted to a given set of allowed values, identified by the keyword
enum
. - Field #
- Unit of data that belongs to a type in the schema. Each GraphQL call requires one or more fields on the root object.
- Input type #
- Special type of object type used as an argument, identified by the keyword
input
. - Mutation #
- A GraphQL operation that creates, modifies, or destroys data.
- Object type #
- A type in the GraphQL schema containing fields. It is identified by the keyword
type
. - Operation #
- A single query or mutation that the GraphQL server can interpret.
- Operation name #
- An arbitrary name can be assigned to an operation to distinguish it from others. In GraphiQL, it is mandatory to give a name to each operation if there is more than one query in the same window.
- Operation type #
- The type of the GraphQL call. It can be either
query
ormutation
. If omitted, the default isquery
. - Type system #
- A collection of types that characterizes the set of data that can be validated, queried and executed on the API.
- Query #
- Read-only retrieval operation to request data from the GraphQL API.
- Root object #
- Entry point to the schema containing all available services of the type chosen for the operation.
- Scalar #
- Type that qualifies data resolved from a GraphQL field, such as strings or numbers. It represents the “leaf” of a query. More information: System Scalar and Livebase Scalar.
- Schema #
- The schema is at the core of the GraphQL server implementation and describes the functionality available to clients connecting to the API and defines the type system of the GraphQL API.
The schema resides on the server and is used to validate and execute client calls. A client can request information about the schema via introspection. - Service #
- A field of type
Query
or of typeMutation
. In the former case, it is a read service, in the latter case a write service. Multiple services of the same type as the operation can be merged in an operation (e.g., aquery
may require multiple read services). - Variable #
- A value that can be passed as an argument to an operation. Variables can be used to replace arguments or be passed as Directives.
Reference #
Types of Issues
#
Below is the complete list of IssueTypes
returned by the server.
Category: generic error #
IssueType | Description |
---|---|
SERVER_ERROR | Generic server error |
MALFORMED_REQUEST | The call is valid against the GraphQL schema, but has not passed server validation. Examples: the field |
DATA_TYPE | A mutation contains a scalar field valued with an invalid string with respect to the data type. Example: invalid strings for fields of type ID, date, time, datetime or real ( |
ENTITY_NOT_FOUND | A mutation mentions the ID of a non-existent object on the database, via the _id field (derived from the SPI EntityNotFoundException exception). |
ENTITY_ATTRIBUTE_NOT_FOUND | The filter_exp field of the <ClassName>PageOptions input contains a Livebase expression mentioning a non-existent attribute on the class (derived from the EntityAttributeNotFoundException exception of the SPI). |
SERVICE_HANDLER_ERROR | Server error raised by the code of a Plugin, invoked through a formAction type mutation. |
ENTITY_LOCK_EDIT | A mutation mentions the ID of an object of which another Cloudlet user has acquired a temporary lock on the database. |
Category: Data validation #
IssueType | Description |
---|---|
ENTITY_UNIQUE | A mutation violates a unique constraint on the class affected by the write. |
ENTITY_DOMAIN | A mutation violates a constraint defined with a Class warning, configured to be evaluated following a persist action. |
ATTRIBUTE_REQUIRED | A mutation omits a field related to a required attribute. |
ATTRIBUTE_RANGE | A mutation contains a value for a scalar field outside the allowed range of values. Example: out-of-range value for attributes of type integer, real, year, date, time, or datetime. |
ATTRIBUTE_REAL_DECIMAL_DIGITS | A mutation contains a value for a field of type real with an invalid number of decimal places. Example: |
ATTRIBUTE_STRING_LENGTH | A mutation contains a value for a field of type string that violates the maximum or minimum allowed length. Example: |
ATTRIBUTE_FILE_SIZE | A mutation attempts to associate, with a field of type file, a pending file that exceeds the maximum allowed file size for that attribute. |
ATTRIBUTE_FILE_TYPE | A mutation attempts to associate, with a field of type file, a pending file whose type is not among those allowed for that attribute. |
ROLE_CARDINALITY | A mutation exceeds the minimum or maximum cardinality allowed for a role involved in the write. _Example: The |
Category: Veto action #
IssueType | Description |
---|---|
ENTITY_EDIT_VETO | A mutation of type create , update or save violates a constraint defined with a Class warning, configured to be evaluated following a Create or Edit event. |
ENTITY_DELETE_VETO | A mutation of type delete violates a constraint defined with a Class warning, configured to be evaluated following a Delete event. |
Category: Grant #
IssueType | Description |
---|---|
APPLICATION_ACCESS_FORBIDDEN | The user does not have an enabled profile to interact with the Application Schema / GraphQL endpoint that the call was directed to. Example: A user with profile “staff manager” has called on the |
ENTITY_GRANT_READ | The user does not have a profile enabled to read a class among those involved in the query. |
ENTITY_GRANT_CREATE | The user does not have a profile enabled to read a class among those involved in the query. |
ENTITY_GRANT_EDIT | The user does not have a profile enabled to modify objects of a class in a mutation of type update or save . |
ENTITY_GRANT_DELETE | The user does not have a profile enabled to delete objects of a class in a mutation of type delete . |
ATTRIBUTE_GRANT_READ | The user does not have a profile enabled to read one or more of the attributes involved in the query. |
ATTRIBUTE_GRANT_EDIT | The user does not have a profile enabled to write one or more of the attributes involved in the mutation. |
ROLE_GRANT_READ | The user does not have a profile enabled to expand one or more of the roles involved in the query. |
ROLE_GRANT_CREATE | The user does not have a profile enabled to create part / associate objects on a role in a mutation of type create or save . |
ROLE_GRANT_EDIT | The user does not have a profile enabled to edit a role in an update or save mutation. |
ROLE_GRANT_DELETE | The user does not have a profile enabled to delete part / remove associations in a delete mutation. |