Skip to content

Draft: Allow defining REST routes with GraphQL documents

Alex Kalderimis requested to merge ajk-v5-graphql-adapter-pattern into master

What does this MR do and why?

See: #363795

Proof-of-concept for defining REST routes from GraphQL documents.

Given a GraphQL document, we parse it, find the declared variables, and the required RESULT field, and mount it as a GET endpoint.

Mutations are also supported (we have examples here for POST merge_requests and DELETE merge_requests).

Implementation

This is implemented as a remountable Grape::API which (at mount time) finds suitable GraphQL files in a specified directory, and mounts them at the specified paths. It is entirely configuration driven, with the main configuration being the GraphQL files themselves, from which we determine the parameters, the path to mount at, and how to transform the GQL response into a REST response.

Name

This POC is currently being developed under the name raisin, since we get Grape routes from concise, almost dessicated GraphQL files - and there is the nice pun on rails.

There is an existing gem under that name (see https://github.com/ccocchi/raisin) which has not seen new releases since 2017, we might want to bear that in mind in the future.

Examples

With this branch, we now have the following new routes:

GET /api/v5/ci/runners/:id/groups(.:format) -
GET /api/v5/ci/runners(.:format) -
GET /api/v5/ci/runners/:id(.:format) - Get runner's details
GET /api/v5/ci/runners/:id/jobs(.:format) - Get details of jobs for a specific runner
GET /api/v5/projects/:project/merge_requests(.:format) - Paginated list of merge requests in a project
GET /api/v5/projects/:project/merge_requests/:iid(.:format) - Details of a merge request

These are normal REST routes that behave as follows:

❯ curl 'http://kaki.local:3000/api/v5/projects/flightjs%2Fflight/merge_requests'
Returns:
[
  {
    "iid": "10",
    "title": "Molestiae earum provident autem iusto sunt rerum.",
    "description": "Placeat tempore totam vero dolor.",
    "author": {
      "username": "vergie"
    }
  },
  {
    "iid": "9",
    "title": "Sit quo enim iste sint et ab aliquam eos.",
    "description": "Omnis quia et quod autem non.",
    "author": {
      "username": "nguyet_green"
    }
  },
  {
    "iid": "8",
    "title": "Voluptatem ab ut distinctio velit rerum nesciunt praesentium sunt.",
    "description": "Sit quibusdam corporis deleniti vitae illo.",
    "author": {
      "username": "herschel.lowe"
    }
  },
  {
    "iid": "7",
    "title": "Ut sunt voluptatem iste et omnis voluptatum rerum quia facilis.",
    "description": "Dolorem perferendis unde et itaque quis repellat distinctio sequi.",
    "author": {
      "username": "vergie"
    }
  },
  {
    "iid": "6",
    "title": "Delectus voluptas qui reprehenderit culpa voluptatum cupiditate.",
    "description": "Assumenda omnis explicabo aperiam sed ipsam quia.",
    "author": {
      "username": "vergie"
    }
  },
  {
    "iid": "5",
    "title": "Aspernatur dolorum autem earum perferendis dolores ut maxime laborum velit quam.",
    "description": "Deserunt cupiditate ab quo sed ad.",
    "author": {
      "username": "nguyet_green"
    }
  },
  {
    "iid": "4",
    "title": "Consequatur praesentium recusandae ex nulla non dolor voluptatum consectetur earum.",
    "description": "Repudiandae mollitia deleniti accusantium officia consequatur magni et numquam. Quod illo totam ut occaecati sapiente. Explicabo architecto ex tempore voluptas molestiae.",
    "author": {
      "username": "herschel.lowe"
    }
  },
  {
    "iid": "3",
    "title": "Aliquid animi et modi eaque sint cum voluptates nulla.",
    "description": "Nesciunt atque eligendi aut et asperiores dolor. Praesentium beatae quia maiores omnis voluptas repellat. Corrupti aspernatur dolorem amet aut ad exercitationem ea.",
    "author": {
      "username": "herschel.lowe"
    }
  },
  {
    "iid": "2",
    "title": "Iure vel sit deserunt iste qui eligendi in quis quis voluptatem.",
    "description": "Repellat veniam consequatur fuga corrupti praesentium doloribus non et. Mollitia nulla nisi maxime ullam est vel. Ea voluptas dolores sunt quae.",
    "author": {
      "username": "herschel.lowe"
    }
  },
  {
    "iid": "1",
    "title": "Optio ipsa et magnam est repellat rerum dignissimos.",
    "description": "Qui ut non non magni in. Odit rerum atque reprehenderit iusto saepe. Similique eum cumque neque voluptatum explicabo dolorum numquam.",
    "author": {
      "username": "herschel.lowe"
    }
  }
]

And for a single resource:

❯ curl 'http://kaki.local:3000/api/v5/projects/flightjs%2Fflight/merge_requests/2'
Returns:
{
  "iid": "2",
  "title": "Iure vel sit deserunt iste qui eligendi in quis quis voluptatem.",
  "description": "Repellat veniam consequatur fuga corrupti praesentium doloribus non et. Mollitia nulla nisi maxime ullam est vel. Ea voluptas dolores sunt quae.",
  "author": {
    "username": "herschel.lowe"
  }
}

Using optional parameters:

❯ curl 'http://kaki.local:3000/api/v5/projects/flightjs%2Fflight/merge_requests?sort=CREATED_ASC'
Returns:
[
  {
    "iid": "1",
    "title": "Optio ipsa et magnam est repellat rerum dignissimos.",
    "description": "Qui ut non non magni in. Odit rerum atque reprehenderit iusto saepe. Similique eum cumque neque voluptatum explicabo dolorum numquam.",
    "author": {
      "username": "herschel.lowe"
    }
  },
  {
    "iid": "2",
    "title": "Iure vel sit deserunt iste qui eligendi in quis quis voluptatem.",
    "description": "Repellat veniam consequatur fuga corrupti praesentium doloribus non et. Mollitia nulla nisi maxime ullam est vel. Ea voluptas dolores sunt quae.",
    "author": {
      "username": "herschel.lowe"
    }
  },
  {
    "iid": "3",
    "title": "Aliquid animi et modi eaque sint cum voluptates nulla.",
    "description": "Nesciunt atque eligendi aut et asperiores dolor. Praesentium beatae quia maiores omnis voluptas repellat. Corrupti aspernatur dolorem amet aut ad exercitationem ea.",
    "author": {
      "username": "herschel.lowe"
    }
  },
  {
    "iid": "4",
    "title": "Consequatur praesentium recusandae ex nulla non dolor voluptatum consectetur earum.",
    "description": "Repudiandae mollitia deleniti accusantium officia consequatur magni et numquam. Quod illo totam ut occaecati sapiente. Explicabo architecto ex tempore voluptas molestiae.",
    "author": {
      "username": "herschel.lowe"
    }
  },
  {
    "iid": "5",
    "title": "Aspernatur dolorum autem earum perferendis dolores ut maxime laborum velit quam.",
    "description": "Deserunt cupiditate ab quo sed ad.",
    "author": {
      "username": "nguyet_green"
    }
  },
  {
    "iid": "6",
    "title": "Delectus voluptas qui reprehenderit culpa voluptatum cupiditate.",
    "description": "Assumenda omnis explicabo aperiam sed ipsam quia.",
    "author": {
      "username": "vergie"
    }
  },
  {
    "iid": "7",
    "title": "Ut sunt voluptatem iste et omnis voluptatum rerum quia facilis.",
    "description": "Dolorem perferendis unde et itaque quis repellat distinctio sequi.",
    "author": {
      "username": "vergie"
    }
  },
  {
    "iid": "8",
    "title": "Voluptatem ab ut distinctio velit rerum nesciunt praesentium sunt.",
    "description": "Sit quibusdam corporis deleniti vitae illo.",
    "author": {
      "username": "herschel.lowe"
    }
  },
  {
    "iid": "9",
    "title": "Sit quo enim iste sint et ab aliquam eos.",
    "description": "Omnis quia et quod autem non.",
    "author": {
      "username": "nguyet_green"
    }
  },
  {
    "iid": "10",
    "title": "Molestiae earum provident autem iusto sunt rerum.",
    "description": "Placeat tempore totam vero dolor.",
    "author": {
      "username": "vergie"
    }
  }
]
❯ curl 'http://kaki.local:3000/api/v5/projects/flightjs%2Fflight/merge_requests?sort=CREATED_DESC'
Returns:
[
  {
    "iid": "10",
    "title": "Molestiae earum provident autem iusto sunt rerum.",
    "description": "Placeat tempore totam vero dolor.",
    "author": {
      "username": "vergie"
    }
  },
  {
    "iid": "9",
    "title": "Sit quo enim iste sint et ab aliquam eos.",
    "description": "Omnis quia et quod autem non.",
    "author": {
      "username": "nguyet_green"
    }
  },
  {
    "iid": "8",
    "title": "Voluptatem ab ut distinctio velit rerum nesciunt praesentium sunt.",
    "description": "Sit quibusdam corporis deleniti vitae illo.",
    "author": {
      "username": "herschel.lowe"
    }
  },
  {
    "iid": "7",
    "title": "Ut sunt voluptatem iste et omnis voluptatum rerum quia facilis.",
    "description": "Dolorem perferendis unde et itaque quis repellat distinctio sequi.",
    "author": {
      "username": "vergie"
    }
  },
  {
    "iid": "6",
    "title": "Delectus voluptas qui reprehenderit culpa voluptatum cupiditate.",
    "description": "Assumenda omnis explicabo aperiam sed ipsam quia.",
    "author": {
      "username": "vergie"
    }
  },
  {
    "iid": "5",
    "title": "Aspernatur dolorum autem earum perferendis dolores ut maxime laborum velit quam.",
    "description": "Deserunt cupiditate ab quo sed ad.",
    "author": {
      "username": "nguyet_green"
    }
  },
  {
    "iid": "4",
    "title": "Consequatur praesentium recusandae ex nulla non dolor voluptatum consectetur earum.",
    "description": "Repudiandae mollitia deleniti accusantium officia consequatur magni et numquam. Quod illo totam ut occaecati sapiente. Explicabo architecto ex tempore voluptas molestiae.",
    "author": {
      "username": "herschel.lowe"
    }
  },
  {
    "iid": "3",
    "title": "Aliquid animi et modi eaque sint cum voluptates nulla.",
    "description": "Nesciunt atque eligendi aut et asperiores dolor. Praesentium beatae quia maiores omnis voluptas repellat. Corrupti aspernatur dolorem amet aut ad exercitationem ea.",
    "author": {
      "username": "herschel.lowe"
    }
  },
  {
    "iid": "2",
    "title": "Iure vel sit deserunt iste qui eligendi in quis quis voluptatem.",
    "description": "Repellat veniam consequatur fuga corrupti praesentium doloribus non et. Mollitia nulla nisi maxime ullam est vel. Ea voluptas dolores sunt quae.",
    "author": {
      "username": "herschel.lowe"
    }
  },
  {
    "iid": "1",
    "title": "Optio ipsa et magnam est repellat rerum dignissimos.",
    "description": "Qui ut non non magni in. Odit rerum atque reprehenderit iusto saepe. Similique eum cumque neque voluptatum explicabo dolorum numquam.",
    "author": {
      "username": "herschel.lowe"
    }
  }
]

Error handling

Certain error states are recognized and transformed into status codes. Example:

❯ curl -i 'http://kaki.local:3000/api/v5/projects/flightjs%2Fflight/merge_requests?sort=CREATED_FOO'
Produces:
HTTP/1.1 400 Bad Request
{
  "error": [
    {
      "message": "Variable $sort of type MergeRequestSort was provided invalid value",
      "explanation": [
        "Expected \"CREATED_FOO\" to be one of: updated_desc, updated_asc, created_desc, created_asc, UPDATED_DESC, UPDATED_ASC, CREATED_DESC, CREATED_ASC, PRIORITY_ASC, PRIORITY_DESC, LABEL_PRIORITY_ASC, LABEL_PRIORITY_DESC, MILESTONE_DUE_ASC, MILESTONE_DUE_DESC, MERGED_AT_ASC, MERGED_AT_DESC, CLOSED_AT_ASC, CLOSED_AT_DESC, TITLE_ASC, TITLE_DESC"
      ]
    }
  ]
}

Pagination

Pagination is supported on connections. If the nodes of a connection are the RESULT, then we can read the pageInfo to generate pagination headers:

❯ curl -i 'http://kaki.local:3000/api/v5/projects/flightjs%2Fflight/merge_requests?first=2'

Produces:

HTTP/1.1 200 OK
Cache-Control: max-age=0, private, must-revalidate
Content-Type: application/json
Etag: W/"d849de618252cb6a294255088372be25"
Link: </api/v5/projects/flightjs%2Fflight/merge_requests?first=2&after=eyJjcmVhdGVkX2F0IjoiMjAyMi0wNS0yNSAyMDo1Mzo1Mi41NDIzMTIwMDAgVVRDIiwiaWQiOiI1NSJ9>; rel="next"
Vary: Origin
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Request-Id: 01G40FE0X7FMPN7QH4AKHQH806
X-Runtime: 0.655874
Date: Thu, 26 May 2022 15:23:17 GMT
Content-Length: 302

With results:

[
  {
    "iid": "10",
    "title": "Molestiae earum provident autem iusto sunt rerum.",
    "description": "Placeat tempore totam vero dolor.",
    "author": {
      "username": "vergie"
    }
  },
  {
    "iid": "9",
    "title": "Sit quo enim iste sint et ab aliquam eos.",
    "description": "Omnis quia et quod autem non.",
    "author": {
      "username": "nguyet_green"
    }
  }
]

Using the next link from the response headers produces:

HTTP/1.1 200 OK
Cache-Control: max-age=0, private, must-revalidate
Content-Type: application/json
Etag: W/"27aea2cd7e7e7b45676919c2572e68f3"
Link: </api/v5/projects/flightjs%2Fflight/merge_requests?first=2&after=eyJjcmVhdGVkX2F0IjoiMjAyMi0wNS0yNSAyMDo1Mzo1Mi4zMTYwNTkwMDAgVVRDIiwiaWQiOiI1MyJ9>; rel="next"
Vary: Origin
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Request-Id: 01G40FQBSEY3A45VCRH9C5GQV7
X-Runtime: 0.225957
Date: Thu, 26 May 2022 15:28:23 GMT
Content-Length: 388

[
  {
    "iid": "8",
    "title": "Voluptatem ab ut distinctio velit rerum nesciunt praesentium sunt.",
    "description": "Sit quibusdam corporis deleniti vitae illo.",
    "author": {
      "username": "herschel.lowe"
    }
  },
  {
    "iid": "7",
    "title": "Ut sunt voluptatem iste et omnis voluptatum rerum quia facilis.",
    "description": "Dolorem perferendis unde et itaque quis repellat distinctio sequi.",
    "author": {
      "username": "vergie"
    }
  }
]

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Alex Kalderimis

Merge request reports