rfcs/openapi-to-hasura-single-action.md
We want to translate OpenAPI specifications into GraqhQL to facilitate the creation of actions. The translation process presents several issues; you can explore them in this paper: https://arxiv.org/abs/1809.08319.
I find it interesting to point out the following aspects of the translation process:
The authors of the paper also tested their implementation (the one we used) against many OpenAPI specifications, and they found those results:
The errors/warings they found are the following:
In addition, I will report the most common problems that we found when translating OpenAPI operations to Hasura Actions:
default instead of 200.In this document, we explore our translation proposal directly into the console. We want to create a tool for importing a single action. The input is the GraphQL specification and the operation's name to translate, and the result is the complete pre-compilation of the action creation form.
We need several operations to generate Hasura actions from OpenAPI specifications.
For the first two points, the choice seems inevitable to fall on the openapi-to-graphql library https://github.com/IBM/openapi-to-graphql, created by the authors of the article cited above.
An alternative might be swagger-to-graphql https://github.com/yarax/swagger-to-graphql. However, this project is much less widely used https://npmtrends.com/openapi-to-graphql-vs-swagger-to-graphql and is less robust than the other https://npmcompare.com/compare/openapi-to-graphql,swagger-to-graphql
For the third point, the choice fell on microfiber (https://github.com/anvilco/graphql-introspection-tools), a tool for manipulating GraphQL schemas. There does not seem to be any other tool that performs this task https://graphql.org/code/#javascript-tools
The bundle size (minified + gzipped) for the two tools is 6.2kb for microfiber https://bundlephobia.com/package/[email protected] and 331kb for openapi-to-graphql https://bundlephobia.com/package/[email protected]. We could consider checking if tree-shaking is available or maintaining a lighter version ourselves for the latter.
To achieve complete action, we need to:
As an example, we will simulate the generation of the updatePet action of the https://petstore3.swagger.io/api/v3/openapi.json specification.
The process of generating Graphql types is the following:
The schema generated by openapi-to-graphql, after point 2, is the following:
scalar BigInt
type Category {
id: BigInt
name: String
}
input CategoryInput {
id: BigInt
name: String
}
type Mutation {
updatePet(petInput: PetInput!): Pet
}
type Pet {
category: Category
id: BigInt
name: String!
photoUrls: [String]!
status: Status
tags: [Tag]
}
input PetInput {
category: CategoryInput
id: BigInt
name: String!
photoUrls: [String]!
status: Status
tags: [TagInput]
}
type Query
enum Status {
available
pending
sold
}
type Tag {
id: BigInt
name: String
}
input TagInput {
id: BigInt
name: String
}
then, since updatePet is a POST operation, the action is defined in the Mutation type. We can extract the action definition of the updatePet action by removing all the types except Mutation.
type Mutation {
updatePet(petInput: PetInput!): Pet
}
the remaining are the types used by the action. We can extract them by removing all the types except Query and Mutation.
While openapi-to-graphql sometimes fails, we could try a best-effort approach to fix those errors in the original specification and then translate it again. The proposal approach is to make some midifications to the OpenAPI specification before translating it to GraphQL. We can have two possible outcomes
The errors type and corresponding solutions are the following:
GraphQL does not support boolean enum types. Since this is most likely done to restrict the values to only true or false (e.g., in some API the deleted response can be only true), we can replace the enum type with a boolean type, and translate the action successfully.
GraphQL does not support empty types, which sometimes are present in the OpenAPI specification. We can change the empty response to another type (e.g. string) or create a fake non-empty object type with a nullable fake field and translate the action successfully.
We can derive all the Hasura action configurations by the openapi-to-graphql metadata for the selected operation in a straightforward way.
If there were a one-to-one relationship between REST and GraphQL types, there would be no need for any request or response transformation. But, as is stated in the IBM article, to generate GrahpQL types, some names could be sanitized and hence be different from the REST ones. This could lead to broken Hasura action calls.
To solve this problem, a layer of request and response transformation is needed to perform the translation of types between the REST and GraphQL worlds.
While in the article this is is done in the generated resolvers, in Hasura action kriti templates must be generated by recursively traversing the GraphQL schema and the OpenAPI specification and used as request and response transformation.
This is an example of PetInput request and response kriti transformation. We artificially renamed the name field to in OpenAPI spefication to $name to simulate the incompatibility.
{
"id": {{$body.input.petInput?.id}},
"$name": {{$body.input.petInput?.name}},
"category": {
"id": {{$body.input.petInput?.category?.id}},
"name": {{$body.input.petInput?.category?.name}}
},
"photoUrls": {{$body.input.petInput?.photoUrls}},
"tags": {{if inverse(empty($body.input.petInput?.tags))}} {{ range _, tags := $body.input.petInput?.tags}} {
"id": {{tags?.id}},
"name": {{tags?.name}}
} {{end}} {{else}} null {{end}},
"status": {{$body.input.petInput?.status}}
}
and the opposite:
{
"id": {{$body?.id}},
"name": {{$body?.$name}},
"category": {
"id": {{$body?.category?.id}},
"name": {{$body?.category?.name}}
},
"photoUrls": {{$body?.photoUrls}},
"tags": {{if inverse(empty($body?.tags))}} {{ range _, tags := $body?.tags}} {
"id": {{tags?.id}},
"name": {{tags?.name}}
} {{end}} {{else}} null {{end}},
"status": {{$body?.status}}
}
note how objects and arrays are handled.
OpenAPI specification allows to specify authentication methods in the security section and should be managed case by case. In the paper, IBM folks manage authentication through GraphQL Viewers. In our case, for the first release, we will enable the flag Forward client headers to webhook, which will probably be enough for most cases (e.g., users can pass the JWT token in the headers).