{"id":15022249,"url":"https://github.com/tyrchen/quenya","last_synced_at":"2025-04-09T19:21:13.701Z","repository":{"id":55626614,"uuid":"316165371","full_name":"tyrchen/quenya","owner":"tyrchen","description":"Quenya is a framework to build high-quality REST API applications based on extended OpenAPI spec","archived":false,"fork":false,"pushed_at":"2020-12-17T06:37:37.000Z","size":6628,"stargazers_count":146,"open_issues_count":1,"forks_count":14,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-02T17:07:48.639Z","etag":null,"topics":["code-generation","elixir","openapi","openapi-generator","openapi3","rest-api-framework"],"latest_commit_sha":null,"homepage":"https://github.com/tyrchen/quenya","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tyrchen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-11-26T08:20:54.000Z","updated_at":"2025-01-20T10:19:02.000Z","dependencies_parsed_at":"2022-08-15T04:50:29.521Z","dependency_job_id":null,"html_url":"https://github.com/tyrchen/quenya","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tyrchen%2Fquenya","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tyrchen%2Fquenya/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tyrchen%2Fquenya/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tyrchen%2Fquenya/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tyrchen","download_url":"https://codeload.github.com/tyrchen/quenya/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248095157,"owners_count":21046799,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["code-generation","elixir","openapi","openapi-generator","openapi3","rest-api-framework"],"created_at":"2024-09-24T19:57:41.898Z","updated_at":"2025-04-09T19:21:13.659Z","avatar_url":"https://github.com/tyrchen.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Quenya\n\n__Disclaimer: Quenya is under active development and is at its early stage. Please DO NOT use it in prod environment. Use with cautions.__\n\nQuenya is a framework to build high-quality REST API applications based on extended [OpenAPI spec](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md). For the Quenya extension, see [here](docs/quenya_extension.md). With the OAPI spec, Quenya can generate high-quality code for many parts of the API pipeline:\n\n- Preprocessors:\n  - [x] request validator: validate the request params\n  - [ ] auth handler: process authentication for the API endpoints\n  - [ ] access controller: process authorization for the API endpoints\n- API handlers:\n  - [x] fake API handler to generate a fake response for mocking purpose\n  - [ ] gRPC handler to act as a proxy between your client and your gRPC server (require extended OpenAPI grammar)\n- Postprocessors:\n  - [x] response validator to validate the response body and headers (for dev/testing purpose)\n\nQuenya will also generate property testing, it will use `Plug.Test` and `StreamData` to build tests. Requests (url, query, request headers and request body) will be generated and then sent to generated `Router`, then it will use the response schema to validate the result. Currently the testing only covers happy path.\n\nQuenya will also provide a set of modules, plugs, test helpers to help you build REST APIs easily.\n\n## How to use Quenya\n\n### Install CLI\n\nFirst of all, install Quenya CLI:\n\n```bash\n$ mix archive.install hex quenya_installer\nResolving Hex dependencies...\nDependency resolution completed:\nNew:\n  quenya_installer 0.3.0\n* Getting quenya_installer (Hex package)\n\n20:22:15.605 [info]  erl_tar: removed leading '/' from member names\n\nAll dependencies are up to date\nCompiling 5 files (.ex)\nGenerated quenya_installer app\nGenerated archive \"quenya_installer-0.3.0.ez\" with MIX_ENV=prod\nAre you sure you want to install \"quenya_installer-0.3.0.ez\"? [Yn]\n* creating /Users/tchen/.mix/archives/quenya_installer-0.3.0\n```\n\n### Generate APP from an existing OAPI spec\n\nOnce you finished installing quenya CLI, you can build a API app with quenya:\n\n```bash\n$ cd /tmp\n$ curl https://raw.githubusercontent.com/tyrchen/quenya/master/parser/test/fixture/petstore.yml \u003e petstore.yml\n$ mix quenya.new petstore.yml petstore\n* creating petstore/config/config.exs\n* creating petstore/config/dev.exs\n* creating petstore/config/prod.exs\n* creating petstore/config/staging.exs\n* creating petstore/config/test.exs\n* creating petstore/lib/petstore/application.ex\n* creating petstore/lib/petstore.ex\n* creating petstore/mix.exs\n* creating petstore/README.md\n* creating petstore/.formatter.exs\n* creating petstore/.gitignore\n* creating petstore/test/test_helper.exs\n\nFetch and install dependencies? [Yn]\n* running mix deps.get\n* running mix deps.compile\n\nWe are almost there! The following steps are missing:\n\n    $ cd petstore\n\nYou can run your app inside IEx (Interactive Elixir) as:\n\n    $ iex -S mix\n\n\n```\n\nThis will create a new elixir app, copy your spec file (or spec folder) to `priv/spec/main.yml`, and generate API code based on the spec.\n\n### Running the app\n\nNow you can run the app:\n\n```bash\n$ cd petstore/\n$ mix compile.quenya # this command will generate/regenerate code on /gen and /test/gen folders\n$ iex -S mix\nErlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [hipe] [dtrace]\n\nCompiling 44 files (.ex)\nGenerated petstore app\nInteractive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help)\n```\n\nJust run a few commands without writing even a single line of code, you have an API app ready to use. Try open `http://localhost:4000/swagger`. You will see an API playground with standard Swagger UI:\n\n![](docs/images/swagger.jpg)\n\nIt's great but nothing special. Now, try to invoke one of the APIs, say `GET /pet/findByStatus`:\n\n![](docs/images/swagger_call.jpg)\n\nAmazing! Don't believe what you saw? Try with this command:\n\n```bash\ncurl -X POST \"http://localhost:4000/pet\" -H  \"accept: application/json\" -H  \"Content-Type: application/json\" -d \"{\\\"name\\\":\\\"doggie\\\",\\\"photoUrls\\\":[\\\"bad url\\\"]}\" -i\nHTTP/1.1 400 Bad Request\ncache-control: max-age=0, private, must-revalidate\ncontent-length: 33\ndate: Mon, 30 Nov 2020 04:45:37 GMT\nserver: Cowboy\n\nExpected to be a valid image_uri.\n```\n\nAccording to [petstore.yml](test/fixture/petstore.yml), request body must be a Pet type, and `name` / `photoUrls` are required. `photoUrls` shall be an array of string, with format as `image_url` (an extended format by quenya). Quenya will validate requests by its schema so here we need a valid url. Let's correct this:\n\n```bash\n$ curl -X POST \"http://localhost:4000/pet\" -H  \"accept: application/json\" -H  \"Content-Type: application/json\" -d \"{\\\"name\\\":\\\"doggie\\\",\\\"photoUrls\\\":[\\\"https://source.unsplash.com/random\\\"]}\" -i\nHTTP/1.1 200 OK\ncache-control: max-age=0, private, must-revalidate\ncontent-length: 376\ncontent-type: application/json; charset=utf-8\ndate: Mon, 30 Nov 2020 04:51:03 GMT\nserver: Cowboy\n\n{\"category\":{\"id\":683,\"name\":\"Dtlir6vgkz6UeAwK5q4._9--A.--._V_mjp.K--3T.0-e_.7-_qfRmfu\"},\"id\":928,\"name\":\"758Yhl_jx_Rt_fi5fz_JtE_k__JY2J__Tt9Y1\",\"photoUrls\":[\"https://source.unsplash.com/random/400x400\",\"https://source.unsplash.com/random/400x400\"],\"status\":\"sold\",\"tags\":[{\"id\":480,\"name\":\"iusto\"},{\"id\":64,\"name\":\"error\"},{\"id\":658,\"name\":\"modi\"},{\"id\":313,\"name\":\"nihil\"}]}\n```\n\n### Running the tests\n\nQuenya generates property tests for all your API endpoints based on OAPI spec, so before coding your own API handler into the repo, you'd like to be more test-driven, try `mix test` now:\n\n```bash\n$ mix test\nCompiling 42 files (.ex)\nGenerated petstore app\n....................\n\nFinished in 2.7 seconds\n20 properties, 2 failures\n```\n\nNote these tests covers all success cases. In future, we will try to cover all failed cases in Quenya.\n\n### How much code Quenya generated for you?\n\nIf you have `tokei` installed, you can have a basic idea on how much code Quenya generated for you:\n\n```bash\n$ tokei gen test\n-------------------------------------------------------------------------------\n Language            Files        Lines         Code     Comments       Blanks\n-------------------------------------------------------------------------------\n Elixir                 83         8152         7060            0         1092\n-------------------------------------------------------------------------------\n Total                  83         8152         7060            0         1092\n-------------------------------------------------------------------------------\n```\n\nThat's 8k LoC for the petstore spec. The more APIs you defined, the more Quenya will do for you. Once we have most of the parts of Quenya built, this number will be much bigger.\n\n### What's under the hood?\n\nNow you have a basic feeling on what's going on. By default, Quenya will generate an API router based on API spec, with a convenient swagger UI. For each route defined in the spec, Quenya will generate a Plug for it. And a Plug is a pipeline which will execute in this order:\n\n- preprocessors: any Plug to be executed before the actual route handler. Here, RequestValidator Plug will help to validate request params against the schema.\n- handlers: handlers for the route. This is what you shall put your actual API logic, but for mocking purpose, Quenya generates a fake handler which meets the response schema. In future, Quenya will support gRPC handler which will be very useful if what you need is a grpc proxy (think [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway)).\n- postprocessors: any Plug to be executed before sending the response. Quenya can generate a ResponseValidator if you need it. It's good for dev/staging purpose. By default it won't generate it.\n\nQuenya consists of 3 parts:\n\n1. quenya_installer: help with Quenya project generation (the CLI you just used).\n2. quenya_builder: a code generator to generate API implementation based on extended OpenAPI v3 spec. Every time you run `mix compile`, Quenya will rebuild the spec to code (need improvement here).\n3. quenya: a library consist of utility functions, tests and a playground to play with API or API stub.\n\n## What's the generated code?\n\nIf you look at the `gen` folder in the newly generated app, you'll find all your routes and routers are organized by `operationId`:\n\n```bash\n$ tree -L 1\n.\n├── Petstore.Gen.ApiRouter.ex\n├── Petstore.Gen.Router.ex\n├── addPet\n├── createUser\n├── createUsersWithArrayInput\n├── createUsersWithListInput\n├── deleteOrder\n├── deletePet\n├── deleteUser\n├── findPetsByStatus\n├── findPetsByTags\n├── getInventory\n├── getOrderById\n├── getPetById\n├── getUserByName\n├── loginUser\n├── logoutUser\n├── placeOrder\n├── updatePet\n├── updatePetWithForm\n├── updateUser\n└── uploadFile\n\n20 directories, 2 files\n```\n\nThe main router will serve swagger and forward the path (extracted from the spec) to the API router:\n\n```elixir\ndefmodule Petstore.Gen.Router do\n  @moduledoc false\n  use Plug.Router\n  use Plug.ErrorHandler\n  require Logger\n  alias Quenya.Plug.SwaggerPlug\n  plug Plug.Logger, log: :info\n  plug Plug.Static, at: \"/public\", from: {:quenya, \"priv/swagger\"}\n\n  plug :match\n  plug Plug.Parsers, parsers: [:json], pass: [\"application/json\"], json_decoder: Jason\n  plug :dispatch\n\n  def handle_errors(conn, %{kind: _kind, reason: %{message: msg}, stack: _stack}) do\n    Plug.Conn.send_resp(conn, conn.status, msg)\n  end\n\n  def handle_errors(conn, %{kind: kind, reason: reason, stack: stack}) do\n    Logger.warn(\n      \"Internal error:\\n kind: #{inspect(kind)}\\n reason: #{inspect(reason)}\\n stack: #{\n        inspect(stack)\n      }\"\n    )\n\n    Plug.Conn.send_resp(conn, conn.status, \"Internal server error\")\n  end\n\n  get(\"/swagger/main.json\", to: SwaggerPlug, init_opts: [app: :petstore])\n  get(\"/swagger\", to: SwaggerPlug, init_opts: [spec: \"/swagger/main.json\"])\n  forward \"/\", to: Petstore.Gen.ApiRouter, init_opts: []\nend\n```\n\nThe API router contains code for all routes, for example:\n\n```elixir\nput(\"/user/:username\",\n    to: RoutePlug,\n    init_opts: [\n      preprocessors: [Petstore.Gen.UpdateUser.RequestValidator],\n      postprocessors: [],\n      handlers: [Petstore.Gen.UpdateUser.FakeHandler]\n    ]\n  )\n```\n\nWhen a `PUT /user/:username` request kicks in, it will be handled by `Quenya.Plug.RoutePlug`, and it will run `preprocessors`, `handlers` and `postprocessors` in the right order.\n\n## Why Quenya?\n\nI've given a topic [Building next-gen APIs](docs/building-next-gen-apis.pdf) in 10/2020. The original idea is: why don't I build a code generator to generate API code that we don't need to write repeatedly? I always hold this tenet that everything could be generated should be generated.\n\nBuilding a high-quality HTTP API app is non-trivial. Good APIs have these traits:\n\nFor API users:\n\n- Easy to learn and intuitive to use (the app provides full-fledged and good quality docs / playground)\n- Hard to misuse (API is type-safety and provides proper error responses)\n- Powerful enough to drive business requirements (flexible, performant)\n- Easy to evolve as the products grow\n- Opinionated (don't make me think)\n\nFor developers:\n\n- Easy to read and maintain existing code\n- Easy to write new APIs / extend existing APIs\n- Easy to generate code based on API spec (client SDKs, test cases, and even server implementation)\n\nAPI implementation is just a small part of the API lifecycle, we need API design, mocking, testing, simulating, documentation, deployment, etc.\n\n![](docs/images/api.png)\n\nQuenya tries to help you start with the API spec, iterate it without writing the code, while at the same time various teams can play with the mocking server based on the spec to nail down what is actually needed. We believe this is the best approach to improve productivity.\n\n## Why not GraphQL or other solutions?\n\nSee the above slides and you'll see why.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftyrchen%2Fquenya","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftyrchen%2Fquenya","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftyrchen%2Fquenya/lists"}