Generating GraphQL-Wrappers for REST(-like) APIs

09/21/2018 ∙ by Erik Wittern, et al. ∙ ibm Columbia University 0

GraphQL is a query language and thereupon-based paradigm for implementing web Application Programming Interfaces (APIs) for client-server interactions. Using GraphQL, clients define precise, nested data-requirements in typed queries, which are resolved by servers against (possibly multiple) backend systems, like databases, object storages, or other APIs. Clients receive only the data they care about, in a single request. However, providers of existing REST(-like) APIs need to implement additional GraphQL interfaces to enable these advantages. We here assess the feasibility of automatically generating GraphQL wrappers for existing REST(-like) APIs. A wrapper, upon receiving GraphQL queries, translates them to requests against the target API. We discuss the challenges for creating such wrappers, including dealing with data sanitation, authentication, or handling nested queries. We furthermore present a prototypical implementation of OASGraph. OASGraph takes as input an OpenAPI Specification (OAS) describing an existing REST(-like) web API and generates a GraphQL wrapper for it. We evaluate OASGraph by running it, as well as an existing open source alternative, against 959 publicly available OAS. This experiment shows that OASGraph outperforms the existing alternative and is able to create a GraphQL wrapper for 89.5 in many cases. A subsequent analysis of errors and warnings produced by OASGraph shows that missing or ambiguous information in the assessed OAS hinders creating complete wrappers. Finally, we present a use case of the IBM Watson Language Translator API that shows that small changes to an OAS allow OASGraph to generate more idiomatic and more expressive GraphQL wrappers.

READ FULL TEXT VIEW PDF
POST COMMENT

Comments

There are no comments yet.

Authors

page 1

page 2

page 3

page 4

Code Repositories

oasgraph

Turns APIs described by OpenAPI Specifications (OAS) into GraphQL interfaces.


view repo
This week in AI

Get the week's most popular data science and artificial intelligence research sent straight to your inbox every Saturday.

1 Introduction

Created by Facebook in 2012 and released as open source in 2015, GraphQL is a query language and thereupon-based paradigm for building web Application Programming Interfaces (APIs) for client-server interactions. According to the lore111https://code.facebook.com/posts/1691455094417024/graphql-a-data-query-language/, Facebook created GraphQL in response to observed issues with more conventional ways of building APIs, like the Representational State Transfer (REST) architectural style [4] or one of its variants (we refer to this style of APIs, broadly, as REST(-like), because many such APIs do not fully adhere to the constraints prescribed by REST [11]). In REST(-like) APIs, resources are identified by URIs and are accessed or manipulated via (most commonly) HTTP endpoints. As a result, clients are limited to perform predetermined operations, which may have been designed by API providers irrespective of the clients’ specific requirements. In consequence, clients frequently receive unneeded data, or have to chain multiple requests to obtain desired results. Furthermore, for API clients, changes to a REST(-like) API can have sever implications, and in the worst case lead to application misbehavior or even crashes [8, 2]. Another issue is that REST(-like) APIs amass endpoints when providers add new capabilities to an API to preserve compatibility – for example to react to new client requirement without breaking compatibility with existing clients.

GraphQL’s solution to these problems is a query language that allows clients to specify exact data requirements (within provider-defined constraints) on a data field level, thus avoiding to send superfluous data over the network. Changes to client-specific requirements can be resolved by changing queries, rather than having to add endpoints to the API. A graph-based data abstraction allows providers to add new capabilities to their data model without breaking client code. (Arbitrarily) nested queries allow to combine previously multiple requests, reducing client code complexity and avoiding associated overhead. Due to these advantages, various prominent API providers offer GraphQL interfaces (in addition to their REST(-like) APIs), among them GitHub222https://developer.github.com/v4/, Yelp333https://www.yelp.com/developers/graphql/guides/intro, and The New York Times444https://open.nytimes.com/react-relay-and-graphql-under-the-hood-of-the-times-website-
redesign-22fb62ea9764
.

To offer a GraphQL interface, providers have to implement, operate and evolve it – possibly in addition to existing REST(-like) APIs. To delimit this burden on providers, in this work, we assess the feasibility of leveraging existing REST(-like) APIs and their machine-readable specifications to automatically generate a GraphQL wrapper. The wrapper resolves GraphQL queries by performing requests against the existing API. To the best of our knowledge, this is the first scientific work addressing the generation of GraphQL wrappers for REST(-like) APIs.

In the remainder of this work, we start in Section 2 by providing a more detailed description of GraphQL and REST(-like) APIs definitions, specifically the OpenAPI Specification. In Section 3, we discuss the challenges of generating GraphQL wrappers for existing REST(-like) APIs, and possible solutions. In Section 4, we present a proof-of-concept implementation, OASGraph, that, for a given OpenAPI Specification, generates a GraphQL wrapper. In Section 5, we evaluate OASGraph and an existing open source tool by applying them to public OpenAPI Specifications. We assess the errors produced by both tools, and warnings produced by OASGraph, which indicate partial output generation. The evaluation reveals that OASGraph improves upon the state of the art in generating GraphQL wrappers, and that encountered issues are largely to blame on missing or ambiguous information in the assessed OpenAPI Specifications. In light of these results, we present a use case in Section 6 for a well-specified API and enhance it with small changes to render the resulting GraphQL wrapper more idiomatic and expressive. We present related work in Section 7 before discussing our work and concluding in Section 8.

2 Background

In this Section, we discuss GraphQL as well as on OpenAPI Specifications, which act as input for creating GraphQL wrappers for existing REST(-like) APIs.

2.1 GraphQL

GraphQL describes itself as a query language “[…] for describing the capabilities and requirements of data models for client-server applications.” [3]. In a condensed form, the interactions of using GraphQL comprise of the following:

  • A GraphQL server implements a GraphQL schema that defines the types and relations of exposed data, including operations to query or mutate data. A schema can define a user data as an object that contains string fields id and name and an object field address, with string fields street and city. Clients can query users by providing a string id argument. Data types can be associated with resolve functions, which implement operations against arbitrary backend systems such as a database.

  • A GraphQL client introspects a server’s schema, i.e., queries it with GraphQL queries, to learn about exposed data types and possible operations. The GraphiQL555https://github.com/graphql/graphiql online-IDE uses introspection to allow developers to familiarize with GraphQL schemas.

  • A client sends queries to the server, whose syntax resembles that of the JavaScript Object Notation (JSON) and which specify desired operations to perform and what data to return (on the level of fields of objects).

  • Upon receiving queries, the server validates them against the schema and executes them by invoking one or more resolve functions to either fetch the requested data or perform desired mutations.

  • Ultimately, the server sends back requested data to the client, or error messages in case the execution failed.

2.2 OpenAPI Specifications

The Open API Specification (OAS), formerly known as Swagger, is “a standard, programming language-agnostic interface description for REST APIs” [10]. OAS is a format used by providers to describe and document APIs in an organized and predictable manner. Both human and machine-based clients use OAS to understand and invoke APIs, including tooling that works for any API described using OAS. OAS breaks an API down into operations identified by a unique combination of URL path and HTTP method, the data in- and output schemas (both for successful responses as well as errors), required parameters (e.g., in headers or query strings), and authentication mechanisms.

3 Generating a GraphQL Schema from an OAS

Within this section, we describe how to create a GraphQL wrapper for a target REST(-like) API discussing the challenges and proposing ways to mitigate them. In general, the presented approach relies on taking as input an OAS describing the target API and outputting a GraphQL schema that, once deployed, forms a GraphQL wrapper around the target API. The GraphQL wrapper translates queries to corresponding requests against the target API. The schema consists of a) the data types expected and exposed by the wrapper and their relations (see Section 3.1) and b) resolve functions responsible for receiving and returning data by making requests to the target API (see Section 3.2). We also discuss the support nested queries (see Section 3.3) and handling authentication requirements of target APIs (see Section 3.4), before describing how all these pieces are ultimately combined to form a GraphQL schema (see Section 3.5).

3.1 Translating Schema Objects to GraphQL Types

A GraphQL schema uses GraphQL types to define the data being sent to or returned from a GraphQL interface [3]. Most notably, (Input) Object types define the structure of JSON objects.666GraphQL is agnostic to the used data serialization format. However, due to its predominance and first-class support in OAS, we assume and speak of JSON as the serialization format throughout this paper. (Input) Object types contain named fields whose values can either be other (Input) Object types, List types, Enum types, or Scalar types (like Int, Float, String, or Boolean). List types contain items of any other type, while Enum types define allowed values of type String.

To define the GraphQL types of a target API, one can make use of the schema objects defined in that API’s OAS (not to be confused with the GraphQL schema). Schema objects largely comply with the JSON Schema Specification [6], a format used to describe the structure of JSON data. Often, schema objects can be directly mapped to GraphQL types. For instance, JSON Schema objects map to GraphQL (Input) Object types, arrays map to GraphQL List types, and enums map to GraphQL Enum types. Similarly, scalar types in JSON Schema like string, boolean, or number / integer match to corresponding GraphQL Scalar types. In consequence, our approach is to iterate through the OAS’ schema objects and instantiate corresponding GraphQL types for each one of them.

3.1.1 Schema Object De-Duplication and Type Naming

One challenge in defining GraphQL types is to avoid duplicate (Input) Objects. (Input) Object types in a single GraphQL schema need to have unique names as identifiers. Furthermore, duplicate types, with different names, lead to bloated GraphQL schemas and possibly cause users confusion. However, the OpenAPI Specification, while providing a reference mechanisms to foster reuse of schema objects defined in a central components object, does not enforce their de-duplication.

Schema object de-duplication can be achieved by creating a types dictionary for a given OAS in a pre-processing phase (i.e., prior to generating GraphQL types). The dictionary contains all schema objects defined across all operations in an OAS. A new schema object is only added if a deep comparison attests it to be unique. The types dictionary further flattens out nested schema objects. That is, if a schema defines an object with properties that are themselves objects, dedicated entries for the latter are created in the types dictionary.

A unique name string is required to identify types in a GraphQL schema, to associate types with the operations that consume/produce data of that type, and to identify entries in the types dictionary. From a given OAS, names can be derived from an explicit reference to the schema object if it appears in the components object of an OAS (e.g., User from "#/components/schema/User"), or from an explicitly set title value in the schema object (if present). If a schema object is not referenced and does not have a title, or if any of these values has already been used in the types dictionary, the following fall-backs can be used: if the schema object was referenced from an operation, a concatenation of this operations HTTP method and URL path is used. If, however, the schema object stems from the definition of another, complex schema object, the key identifying the schema object in that context is used.

3.1.2 Translation Process

Once a types dictionary has been created, every contained schema object can be translated to a GraphQL type. The translation approach depends on the type property of a schema object:

  • If the schema object defines a scalar type (e.g., string, number, or boolean), a corresponding GraphQL Scalar Type is created. As an exception, if the type is string but valid enum values are defined, the translation creates a corresponding GraphQL Enum Type.

  • If the schema object defines an object, all its properties (in JSON Schema terms) are traversed and corresponding fields are added to a new (Input) Object Type. Here, an Input Object Type is created if the schema object defines the payload of any operation, and a normal Object Type is created if the schema object defines the response data of any operation.777GraphQL requires to use dedicated Input Object types to define objects sent as arguments in queries because they delimit otherwise available mechanisms for polymorphism like Union Types or Interfaces, which may not be unambiguously cast to their correct type. The values of the created fields are themselves GraphQL types, reflecting again the JSON schema property types. In consequence, a recursive algorithm is required to translate possibly nested objects and to create equally nested (Input) Object Types. Once created, (Input) Object Types are stored in the types dictionary for possible reuse. In cases where an object’s property is itself of type object, the corresponding type can then either be referenced (if it had already been translated), or the translation of that type is triggered.

  • If the schema object defines an array, first, a new GraphQL type describing the array’s items needs to be created. Once a GraphQL type defining the items has been created, it is wrapped in a GraphQL List Type.

In all of the above cases, properties marked as required in a schema object are wrapped in GraphQL NonNull types to express the same requirement. Key for Input Object types, as it forces users to provide all data required by the target API. Any schema object with human-readable description is exposed in the created GraphQL types, and made available to developers during introspection.

3.1.3 Data Sanitation

Names of types, arguments, and (Input) Object type fields need to follow the GraphQL specification. It requires them to adhere to the regular expression /[_A-Za-z][_0-9A-Za-z]*/, i.e., must start with underscore (“_”) or a letter, and may then contain only underscores and alphanumerics [3]. Because similar restrictions do not exist for REST(-like) APIs defined in an OAS, sanitation requires to remove any non-supported characters. Sanitation, however, has multiple effects: First, it causes the GraphQL wrapper to deviate from the REST(-like) API. Second, the resolve functions of the GraphQL wrapper need to un-sanitize data it sends to the target API, and sanitize responses for them to match with the sanitized GraphQL schema. For this purpose, a mapping between raw and sanitized values needs to be built up during type creation and made accessible to the resolve functions (see Section 3.2).

3.2 Creating Resolve Functions

Resolve functions make requests to the target API in response to GraphQL queries, to either retrieve or mutate data. Resolve functions can be created and returned by a “generator” function for a given operation defined in an OAS.

The generator binds information from an OAS needed by the resolve function to perform requests during their creation. The generator binds the operation’s baseUrl, URL path, HTTP method, and information about supported authentication mechanisms. Furthermore, the generator binds a mapping between the names of arguments that a resolve function may receive as input from a query and the instructions for sending those arguments as parameters of a request to the target API. In REST(-like) APIs, identifiers of resources and other smaller pieces of data is typically sent as path parameters or query parameters or in headers. More complex data is typically sent as payload in the request body of, for example, POST, PUT, or PATCH requests. Finally, resolve functions should be aware of default values, which, if defined, are used if a query does not provide a value for an argument. All this information can be found as part of the OAS.

Resolve functions should be able to receive and process other pieces of information at runtime, including data received from previous, parent resolve functions, this being a default behavior of GraphQL. Resolve functions may also receive security-related information like API keys, credentials, or OAuth / OpenID Connect tokens via a context object that is available across resolve functions, another default behavior of GraphQL. Finally, arguments used in previous requests are also passed down at runtime, so that they only have to be defined once per query, even if used by multiple resolve functions.

A previously created mapping between raw and sanitized values, as described in Section 3.1.3, is used by resolve functions in two ways: Before sending a request, passed argument names are de-sanitized. For example, the received argument {"id": 1} may be de-sanitized to {"$id": 1}, as the target API expects a payload in the latter form. After receiving a response from the target API, resolve functions sanitize received data for it to be properly handled by the GraphQL runtime. (De-) sanitation is of a recursive nature as it covers nested objects and arrays to assign the requested returned values.

3.3 Nested Data via “Links”

A distinguishing feature of GraphQL is querying (deeply) nested data in single request. In REST(-like) APIs, similar operations may involve multiple requests. As of version 3.0.0, introduced in June 2017, an OAS can define possible combinations of requests using links. A link provides design-time information888In that sense, links differ from hypermedia provided by RESTful APIs during runtime as part of Hypermedia as the Engine of Application State (HATEOAS) constraint [4]. about the relationships between the response of a requests and possibly subsequent requests, which depend on this response. For example, Figure (a)a shows a link definition of an OAS written in YAML, which states that the employerId returned in the payload when invoking GET ../user/{id} can be used to instantiate the companyName parameter in a request to the getCompanyById operation.

(a) Subfigure 1 list of figures text
(b) Subfigure 2 list of figures text
Figure 3: Link examples.

A link defined in an OAS operation may create an additional field in the operation’s response GraphQL Object type. The name of that field is the (sanitized) identifier of the link (e.g., EmployerCompany) and the type of that field is the type of the response data of the linked operation. In the example query in Figure (b)b, a client fetches data on a user with id “erik” and uses the field employerCompany, created based on the link, to also fetch the employer’s companyName.

For such queries to work, resolve functions need to be able to receive parameters from previous, parent resolve functions. For example, to resolve the GraphQL query in Figure (b)b, one resolve function invokes the getUserById operation and passes the received employerId field to a second resolve function that uses this data to invoke the getCompanyById operation.

3.4 Authentication

A GraphQL wrapper needs to implement required API authentication mechanisms. We here present ways to support a) API key and basic authentication and b) OAuth 2 or OpenID Connect in GraphQL wrappers.

3.4.1 API Keys and Basic Authentication

Authentication viewers provide a mechanism to allow users to pass authentication information (API keys, or username and password). Viewers are special GraphQL (Input) Object types that wrap all other GraphQL (Input) Object types whose resolve functions require authentication. Viewers define mandatory arguments apiKey or username and password and propagate their values to the resolve functions of all wrapped child types (see Section 3.2). Viewers place sensitive credentials in GraphQL queries, requiring dedicated security mechanisms (i.e., transport encryption).

3.4.2 OAuth 2 and OpenID Connect

OAuth 2 is an authorization framework where users rely on a third party service’s OAUTH server to authenticate and authorize certain actions of an application. Applications are registered with the OAUTH server, and subsequently forward users to the server. Users authenticate themselves with the OAUTH server, which returns access tokens to the application. The application uses the tokens to interact with protected resources.

OpenID Connect is a layer on top of OAuth 2 prescribing the use of JSON Web Token, equivalent to OAuth 2 for the purpose of a GraphQL wrapper.

The flow outlined above is independent of a GraphQL wrapper itself, rather, it relies on the application (e.g., the server hosting the GraphQL wrapper) to obtain the necessary tokens. The resolve functions of a GraphQL wrapper, need to be able to a) obtain access tokens from an application and b) send these tokens within requests to a target REST(-like) API, typically by including them in an Authorization header. We describe one way of passing tokens to a GraphQL wrapper when presenting our proof-of-concept implementation in Section 4.

3.5 Building up the GraphQL Schema

Having translated schema objects to GraphQL types (relying on a types dictionary; possibly considering links) and having defined resolve functions for every operation, an overall GraphQL schema can be created, defining all possible queries and mutations. If authentication viewers were created, they are added as root elements to the queries and mutations fields. Then, for every operation defined in the OAS, the created response type, input types (forming arguments), and resolve function are collectively added to the query or mutation fields of the schema, depending on whether the HTTP method of the operation is GET or not, either directly, or within the previously added authentication viewers they depend on. The resulting GraphQL schema can be passed to a GraphQL server implementation and deployed to start receiving queries.

4 Implementation

We created a proof-of-concept called OASGraph implementing the concepts for automatically wrapping REST(-like) APIs with GraphQL as described in Section 3. OASGraph is written in JavaScript using the Flow static type checker999https://flow.org, and relying on the GraphQL reference implementation GraphQL.js.101010http://graphql.org/graphql-js OASGraph further relies on a third party library swagger2openapi111111https://github.com/Mermade/swagger2openapi to translate given OAS 2.0 (Swagger) specifications to OAS 3.0.0121212Because version 3.0.0 is a superset of Swagger, no information is lost during this translation., and to validate that provided specifications are syntactically correct.

As proposed in Section 3, OASGraph performs a pre-processing phase to de-duplicate and name schema objects (see Section 3.1.1), before recursively translating them to GraphQL types (see Section 3.1.2) while sanitizing type, argument, and field names (see Section 3.1.3). During the translation, OASGraph resolves possibly encountered references within the given OAS (e.g., $ref: "#/components
/schemas/User"
) as well as allOf definitions131313For details, see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#schema-object, and enriches Object types with fields stemming from link definitions (see Section 3.3). OASGraph further generates authentication viewers for passing API keys and basic authentication credentials (see Section 3.4), as well as an any auth viewer that takes as arguments multiple authentication information at once, allowing nested queries to rely on more than one authentication mechanism. After creating resolve functions per operation in the given OAS (see Section 3.2), OASGraph combines them with the generated types and authentication viewers to form a GraphQL schema (see Section 3.5).

OASGraph further allows to pass a JsonPath [5] option that points resolve functions to the location of authentication tokens in the global context object (see Section 3.2). This option is used to provide resolve functions access to OAuth 2 or OpenID Connect tokens made available in the context by the application deploying the GraphQL interface.

Finally, OASGraph provides two modes of operation: In strict mode, OASGraph will throw errors in light of missing or ambiguous information in a given OAS. Strict mode aims to create a GraphQL wrapper that is complete and closely aligned with the target API, or no wrapper at all if that is impossible. In contrast, in non-strict mode, OASGraph attempts to mitigate lacking or ambiguous information in a given OAS, leading to a working GraphQL wrapper, that may slightly deviate from the target API. In non-strict mode, OASGraph tracks causes for such cases and performed mitigations as warnings in a report, which is made accessible to applications or developers. We present the types of warnings (as well as their occurrences) and the mitigations performed by OASGraph as part of the quantitative evaluation in Section 5.3.2.

The GraphQL schema produced by OASGraph can be used by any GraphQL-compliant JavaScript framework, like the express-graphql141414https://github.com/graphql/express-graphql library to run the GraphQL wrapper as an Express.js application.

5 Quantitative Evaluation

The goal of this section is to investigate the feasibility of automatically wrapping any REST-like API with GraphQL. To answer this question, we applied OASGraph as well as Swagger2GraphQL, an open source tool with the same goal, to a large number of publicly available OAS. For both tools, we assess the causes of errors that occurred during the experiments. In addition, for OASGraph, we assess warnings produced in non-strict mode to analyze to what degree the created GraphQL wrappers cover the target REST(-like) APIs.

5.1 Data Collection

For the evaluation, we obtained OAS made available in the APIs.guru OpenAPI Directory151515https://apis.guru/openapi-directory. In this directory, third parties maintain OAS 2.0 of popular APIs. These OAS are created by dedicated scripts that either translate other API specification formats to OAS, or extract required information from (human-readable) API documentations (typically written in HTML). APIs.guru runs these scripts weekly, and manually checks detected differences for correctness before committing them. In addition, error-fixes can be contributed by a larger community through pull requests on the directory’s GitHub repository161616https://github.com/APIs-guru/openapi-directory. We collected the evaluation data on January 11th 2018.

5.2 Experiment Execution

We ran OASGraph, once in strict and once in non-strict mode, on all OAS contained in the APIs.guru dataset. We repeated the experiment with Swagger2GraphQL, an existing open source tool that, in the same way as OASGraph, aims to automatically generate GraphQL wrappers for existing REST(-like) APIs. Like OASGraph, Swagger2GraphQL iterates through the operations defined in a given OAS and creates corresponding GraphQL types, including mutations for non-GET operations. In contrast to OASGraph, Swagger2GraphQL relies on OAS in version 2.0 (“Swagger”, hence the eponymous name) as input, meaning it does not consider links for nested API requests (links do not exist in OAS 2.0). Swagger2GraphQL does not de-duplicate schemas objects, does not sanitize type, argument, and field names, does not consider enum types, and does not provide viewer types for basic authentication and API keys. Furthermore, Swagger2GraphQL has a different approach to dealing with the risk of duplicate GraphQL type names: rather than inferring unique names for types (see Section 3.1.1), Swagger2GraphQL relies on the operationId provided in a given OAS or falls back to combining the HTTP method and path of an operation. In consequence, query types have names like “getUserById”, which are unique but arguably less idiomatic for GraphQL.

5.3 Results

We evaluate the results of the quantitative evaluation in terms of the errors and warnings produced by OASGraph (and Swagger2GraphQL) during our experiments.

5.3.1 Errors

Table 1 summarizes the number of cases with and without errors when applying OASGraph (both in strict and non-strict mode) and Swagger2GraphQL to the OAS from the APIs.guru dataset.

Succ. (%) Errors (%)
OASGraph (non-strict) 930 (97%) 29 (3%)
OASGraph (strict) 260 (27.1%) 699 (72.9%)
Swagger2GraphQL 591 (61.6%) 368 (38.4%)
Table 1: Overall Results

As can be seen from Table 1, OASGraph produces significantly more errors in strict mode, in which case only just over a quarter of cases succeeds. On the other hand, in non-strict mode, wrapping an API succeeds in over of cases. The difference in these values motivates a detailed investigation into the mitigations performed by OASGraph and thus the deviation of created GraphQL interfaces from their target APIs.

The result of Swagger2GraphQL, on first sight, falls between those of OASGraph in strict and non-strict mode. Swagger2GraphQL performs better than OASGraph in strict mode because it (silently) mitigates issues with the input OAS that cause OASGraph to fail in strict mode. For example, Swagger2GraphQL silently creates a “dummy” GraphQL Object type if an operation does not define a valid response schema object, or it arbitrarily selects one HTTP status code for which to define a response type in case multiple codes are available. In both cases, OASGraph throws an error in strict mode (and mitigates these issues with a warning in non-strict mode). As such, the number of APIs for which Swagger2GraphQL succeeds to create a GraphQL wrapper would be smaller, if these cases were explicitly exposed. On the other hand, Swagger2GraphQL performs worse than OASGraph in non-strict mode, as it, for example, does neither sanitize field, argument, and type names nor de-duplicate type names.

Error type OASGraph (non-strict) Swagger2GraphQL
Invalid OAS 6 0
Sanitation Error 16 0
Missing Ref 7 7
Name Conflict 0 2
Unknown Schema Type 0 35
No Get Operation 0 20
Unsanitized Name 0 252
Invalid Schema Type 0 25
Stack Overflow 0 27
Overall 29 368
Table 2: Breakdown of errors produced by OASGraph (in non-strict mode) and Swagger2GraphQL.

Table 2 breaks down the types of errors produced by OASGraph in non-strict mode171717We do not cover errors thrown by OASGraph in strict mode here, because they are captured as warnings in non-strict mode, which we break down later in this section. and by Swagger2GraphQL. The explanation for the error codes in Table 2 is as follows:

  • Invalid OAS: The input OAS could not be successfully validated (by the third party library swagger2openapi used by OASGraph). Because Swagger2GraphQL does not perform validation, it does not produce such errors.

  • Sanitation Error: Sanitation of type, argument, or field names fails (see Section 3.1.3). The errors thrown by OASGraph result from attempts to sanitize enum values of type boolean. While such enumeration values are valid in schemas objects, they are not valid in GraphQL enum types, specifically, the strings true and false are forbidden as enum values [3]. Because Swagger2GraphQL does not perform sanitation, it does not produce such errors.

  • Missing Ref: A reference cannot be resolved because it refers to relative documents181818For details, see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#relative-schema-document-example which are not provided by APIs.guru.

  • Name Conflict: GraphQL.js throws an error because multiple types share the same name. Because OASGraph ensures unique names, it produces no such errors.

  • Unknown Schema Type: A schema object defines a type that does not match any (scalar) GraphQL type (e.g., undefined or file). In OASGraph, such cases produce a warning rather than an error in non-strict mode, and the type is assumed to be string as a mitigation.

  • No Get Operation: The given OAS does not contain any GET endpoints. OASGraph does not produce an error in such cases because it falls back to defining an empty root query type if no GET operation is present.

  • Unsanitized Name: GraphQL.js throws an error due to unsanitized type or field names. Because OASGraph performs sanitation, it produces no such errors.

  • Invalid Schema Type: A given schema object cannot be translated to a GraphQL type. We are uncertain about the origin of these errors in Swagger2GraphQL.

  • Stack Overflow: JavaScript’s maximum call stack is exceeded. This error seems to be a bug in Swagger2GraphQL’s implementation.

As can be seen, by far the most errors produced by Swagger2GraphQL are caused by GraphQL.js throwing errors due to invalid type, argument, or field names. This finding underlines the importance of name sanitation as discussed in Section 3.1.3.

5.3.2 Warnings

The stark difference of results from running OASGraph in strict vs. non-strict mode motivates a detailed look into the types of mitigations OASGraph performs in non-strict mode. Overall, OASGraph reports warnings across all APIs that a GraphQL wrapper could be created for (called “wrappable“ in the following). Of these, APIs could be wrapped without any warning (i.e., in strict mode) and the other APIs could be wrapped with at least one warning. The produced warnings are of the following types:

  • 5178 Missing Response Schema warnings: An operation in the input OAS lacks a definition of a response or payload schema object. OASGraph’s workaround is to ignore the operation. Swagger2GraphQL silently swallows such cases by generating a dummy “empty” default field of type string.

  • 2502 Multiple Responses warnings: An operation defines more than one response where the HTTP status code indicates success (i.e., is between 200 and 299). OASGraph’s workaround is to select the lowest HTTP status code. Note that Swagger2GraphQL silently swallows such cases by randomly selecting the last status code between 200 and 299 defined in the given OAS.

  • 2950 Invalid Schema Type warnings: A payload or response schema object defined in the given OAS lacks a type, or is incomplete (for example, the properties definition of an object is empty). OASGraph’s workaround is to fall back to assuming the type to be string. In consequence, clients can still receive such data as stringified JSON.

  • 43 Unknown Schema Type warnings: A schema object has a type, but that type is unknown to OASGraph (i.e., not object, array, string, number, or boolean). OASGraph’s workaround, again, is to fall back to assuming the type to be string.

Looking into the distribution of warnings across APIs, we found that the majority of wrappable APIs have either no or few warnings of any particular type. Specifically, for every type of warning, half of the wrappable APIs have at most one warning of said type. While warnings are relatively concentrated to certain APIs when considered in isolation, they are less so when considered collectively: half of the wrappable APIs have over four warnings, and around one quarter of APIs have over 10 warnings of any type. In other words, it is not true that warnings overall are concentrated to few APIs, but rather that different APIs tend to have different warnings. This conclusion aligns with the previous observation that only () of wrappable APIs produce no warning at all.

Depending on the type of warning, OASGraph’s mitigation strategies impact how complete the generated GraphQL wrappers are. We skip an operation on Missing Response Schema warnings to ensure that we have a fully usable wrapper rather than assume return codes and types and create a complete wrapper that will not behave properly. For nearly half () of the wrappable APIs, all operations are translated. About a quarter () of wrappable APIs skip under 25% of operations, while around 12% () skip 50% or more of operations. Finally, 7.7% (72) of wrappable APIs skip all operations – i.e., the resulting GraphQL wrappers are completely unusable and should be counted in addition to the APIs that OASGraph threw errors for even in non-strict mode.

5.3.3 Discussion

The quantitative evaluation reveals how challenging it is to wrap REST(-like) APIs automatically with GraphQL, given the state of existing, machine-readable API specifications. Ultimately, of the assessed APIs, only around a quarter () could be completely translated without warnings. In contrast, for 10.5% () of APIs, translation completely failed ( resulted in an error thrown by OASGraph, and for OASGraph could not wrap a single operation due to missing response schemas objects in the OAS). For the remaining of APIs (), the completeness of the automatically generated GraphQL wrapper varies depending on the quality of the given OAS.

6 Use Case: A GraphQL Wrapper for the IBM Watson Language Translator API

We present a use case showcasing how OASGraph can be applied for a well-specified API. Our use case centers around the IBM Watson Language Translator API191919https://www.ibm.com/watson/services/language-translator. The API’s main features include identifying the language of a given text (by POSTing the text to .../v2/identify), and translating a text between a number of languages (by POSTing the text to .../v2/translate). The public OAS202020https://watson-api-explorer.mybluemix.net/listings/language-translator-v2.json of the API is of version 2.0. OASGraph created a GraphQL wrapper for this API without warnings, as its OAS is complete and unambiguous.

To improve the GraphQL wrapper we loss-lessly translated it to version 3.0.0 using an online tool 212121https://mermade.org.uk/openapi-converter. Figure (a)a shows a link definition we added, and Figure (b)b shows the definition of operations that have access to this link. In this case, the translate operation (labeled as translateGet) can use the first entry of the list of identified languages (thus, the most likely language of a text), and use it to define the required source argument.

(a) Subfigure 1 list of figures text
(b) Subfigure 2 list of figures text
Figure 6: Link definition in the Watson Language Translator OAS.

The resulting wrapper is used as shown in Figure 7. A query on the left hand side indicates basic authentication, a string to be translated, and Spanish, "es", as the target language selection. The right hand side of the figure shows the results of this query, which the GraphQL wrapper produced by composing two (authenticated) requests: one to identify the languages of the given text, and one to translate the text from the identified language to Spanish. The described changes, neither require changes to the target REST(-like) API, nor they adapt the OAS in a way that breaks its functionality in other contexts.

Figure 7: GraphQL query and results to detect the language of and translate a text.

7 Related Work

We are not aware of any scientific work that, as we do here, addresses the problem of wrapping REST(-like) APIs using GraphQL. We are only aware of one existing open source tool with the same goal, Swagger2GraphQL222222https://github.com/yarax/swagger-to-graphql, which we compare against in the quantitative evaluation in Section 5. In addition, various open source tools generate GraphQL schemas based on given database schemas, for example PostGraphile for PostgreSQL232323https://www.graphile.org/postgraphile, tuql for sqlite242424https://github.com/bradleyboy/tuql, or sql-to-graphql for SQL databases in general252525https://github.com/rexxars/sql-to-graphql. Given the lack of similar scientific work, in the following, we discuss work that either complements ours or follows a similar goal in a broader sense.

Previous work has attempted to automate the generation of REST(-like) API specifications, like OAS. One approach is to infer specifications from observed dynamic traces of a web server hosting an API [14]. Another approach is infer specifications from proxied HTTP requests, which can be done by parties other than the provider of an API [13]. While these works do not have the same goal of generating GraphQL wrappers, they help making specifications more broadly available and thus complement the here presented work.

Another branch of previous work addresses the generation of REST(-like) API implementations from different types of specifications. For one, modeling approaches have been proposed that, using methods of model-driven engineering, produce REST(-like) API implementations [7, 16], and in some cases additionally client code [1]. To generate REST(-like) API clients, which a GraphQL wrapper ultimately is as well, related work has proposed to rely on domain-specific languages, which also allow to generate clients that compose requests across APIs [9]. The here presented work addresses composition of requests for a single API relying on link definitions in an OAS (see Section 3.3), but could be extended to compose requests across multiple APIs in the future. Other related work discusses advantages of using meta-programming vs. meta-modeling for generating API clients [12], or the creation of chat bots based on API specifications [15].

In summary, scientific work has not yet addressed the generation of GraphQL wrappers for REST(-like) APIs.

8 Discussion and Conclusion

Within this work we presented means to generate GraphQL wrappers for existing REST(-like) APIs based on machine-readable specifications of those APIs (e.g., OAS). We outlined the resulting challenges, like de-duplicating (Input) Object types, sanitizing type, arguments, and field names, dealing with authentication, and enabling nested queries using link definitions.

In experiments with our proof-of-concept implementation, OASGraph, and an open source alternative, Swagger2GraphQL, we assessed how well APIs with publicly available OAS can be wrapped by GraphQL. We find that many OAS, while syntactically correct, have missing or ambiguous information that hinders a complete or exact wrapping. Many of these issues can easily be fixed, though, by completing or correcting the OAS.

Beyond determining whether REST(-like) APIs can at all be wrapped by GraphQL, the question arises of how usable the generated wrappers are. For one, GraphQL interfaces should arguably enable nested queries. We describe how nesting can be enabled based on link definitions in an OAS (see Section 3.3). The majority of publicly available OAS, like the ones in APIs.guru, are using OAS version 2.0, though, which lacks support for link definitions. In our use case, we exemplify adding link definitions to an OAS and the effect they have for the resulting GraphQL wrapper (see Section 6). Another aspect regarding usability is the question how “natural” the GraphQL interface feels. In an idiomatic GraphQL query interface, for example, field names should refer to names of types (e.g., User) rather than the name of an operation (e.g., getUser). OASGraph attempts to adhere to these practices by relying on references and schema object titles to name types, arguments, and fields if possible (see Section 3.1.1). We demonstrate how small changes to an OAS can help achieve this goal in the use case (see Section 6). We consider a more extensive evaluation of the usability of GraphQL wrappers to be future work. Another aspect of usability concerns the application and evolution of created wrappers. OASGraph builds wrappers in memory, and relies on other tools or libraries to use them. Another option to explore in the future is for OASGraph to output the whole source code of a GraphQL interface, or at least the GraphQL type definitions. On the one hand, generated source code allows developers to customize generated GraphQL interfaces. On the other hand, customizations typically make it hard to re-generate the interface later on, for example as the input API specification evolves.

A major thread of future work for us is to further improve our proof-of-concept implementation. For one, OASGraph currently lacks means to support pagination, if it is not handled by the REST(-like) target API itself. Especially for nested queries, pagination is important to avoid causing excessive numbers of requests to the REST(-like) API, thus wasting API quotas, hitting rate limits, or even inducing cost. We would further like to add a caching layer to OASGraph, which can again delimit the strain on the REST(-like) target API.

Finally, two further directions for future work include creating GraphQL wrappers across more than one API, as well as enabling developers to build upon generated GraphQL wrappers and modify them to their liking.

References

  • [1] Ed-douibi, H., Izquierdo, J.L.C., Gómez, A., Tisi, M., Cabot, J.: EMF-REST: Generation of RESTful APIs from Models. In: Proc. of the 31st Annual ACM Symposium on Applied Computing. pp. 1446–1453. ACM, NY, USA (2016)
  • [2] Espinha, T., Zaidman, A., Gross, H.G.: Web API growing pains: Stories from client developers and their code. In: 2014 Software Evolution Week - IEEE Conf. on Software Maintenance, Reengineering, and Reverse Engineering. pp. 84–93. CSMR-WCRE, IEEE (Feb 2014)
  • [3] Facebook Inc.: GraphQL (specification, working draft October 2016). http://facebook.github.io/graphql/October2016, accessed: 2017-11-02
  • [4] Fielding, R.T., Taylor, R.N.: Architectural Styles and the Design of Network-based Software Architectures. University of California, Irvine Doctoral dissertation (2000)
  • [5] Gössner, S.: JSONPath - XPath for JSON. http://goessner.net/articles/JsonPath, accessed: 2017-12-12
  • [6] Internet Engineering Task Force, I.: JSON Schema: A Media Type for Describing JSON Documents. https://datatracker.ietf.org/doc/draft-handrews-json-schema/, accessed: 2017-12-01
  • [7] Laitkorpi, M., Selonen, P., Systa, T.: Towards a Model-Driven Process for Designing ReSTful Web Services. In: 2009 IEEE Int. Conf. on Web Services. pp. 173–180. ICWS, IEEE (July 2009)
  • [8] Li, J., Xiong, Y., Liu, X., Zhang, L.: How Does Web Service API Evolution Affect Clients? In: 2013 IEEE 20th Int. Conf. on Web Services. pp. 300–307. IEEE (June 2013)
  • [9] Maximilien, E.M., Wilkinson, H., Desai, N., Tai, S.: A Domain-Specific Language for Web APIs and Services Mashups. In: Service-Oriented Computing. pp. 13–26. ICSOC, Springer Berlin Heidelberg (2007)
  • [10] OpenAPI Initiative, O.: OpenAPI Specification (Version 3.0.0). https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md/, accessed: 2017-12-01
  • [11] Rodríguez, C., Baez, M., Daniel, F., Casati, F., Trabucco, J.C., Canali, L., Percannella, G.: REST APIs: A Large-Scale Analysis of Compliance with Principles and Best Practices. In: Web Engineering. pp. 21–39. ICWE, Springer (2016)
  • [12] Scheidgen, M., Efftinge, S., Marticke, F.: Metamodeling vs Metaprogramming: A Case Study on Developing Client Libraries for REST APIs. In: Wąsowski, A., Lönn, H. (eds.) Modelling Foundations and Applications. pp. 205–216. ECMFA, Springer Int. Publishing (2016)
  • [13] Sohan, S.M., Anslow, C., Maurer, F.: SpyREST: Automated RESTful API Documentation Using an HTTP Proxy Server. In: Proc. of the 30th IEEE/ACM Int. Conf. on Automated Software Engineering. pp. 271–276. ASE, IEEE (2015)
  • [14] Suter, P., Wittern, E.: Inferring web API descriptions from usage data. In: Proc. of the Third IEEE Workshop on Hot Topics in Web Systems and Technologies. pp. 7–12. HotWeb, IEEE (2015)
  • [15] Vaziri, M., Mandel, L., Shinnar, A., Siméon, J., Hirzel, M.: Generating Chat Bots from Web API Specifications. In: Proc. of the 2017 ACM SIGPLAN Int. Symposium on New Ideas, New Paradigms, and Reflections on Programming and Software. pp. 44–57. Onward!, ACM (2017)
  • [16] Zolotas, C., Diamantopoulos, T., Chatzidimitriou, K.C., Symeonidis, A.L.: From requirements to source code: a Model-Driven Engineering approach for RESTful web services. Automated Software Engineering 24(4), 791–838 (Dec 2017)