• Stars
    star
    139
  • Rank 262,954 (Top 6 %)
  • Language
    Elixir
  • License
    MIT License
  • Created almost 4 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Quenya is a framework to build high-quality REST API applications based on extended OpenAPI spec

Quenya

Disclaimer: Quenya is under active development and is at its early stage. Please DO NOT use it in prod environment. Use with cautions.

Quenya is a framework to build high-quality REST API applications based on extended OpenAPI spec. For the Quenya extension, see here. With the OAPI spec, Quenya can generate high-quality code for many parts of the API pipeline:

  • Preprocessors:
    • request validator: validate the request params
    • auth handler: process authentication for the API endpoints
    • access controller: process authorization for the API endpoints
  • API handlers:
    • fake API handler to generate a fake response for mocking purpose
    • gRPC handler to act as a proxy between your client and your gRPC server (require extended OpenAPI grammar)
  • Postprocessors:
    • response validator to validate the response body and headers (for dev/testing purpose)

Quenya 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.

Quenya will also provide a set of modules, plugs, test helpers to help you build REST APIs easily.

How to use Quenya

Install CLI

First of all, install Quenya CLI:

$ mix archive.install hex quenya_installer
Resolving Hex dependencies...
Dependency resolution completed:
New:
  quenya_installer 0.3.0
* Getting quenya_installer (Hex package)

20:22:15.605 [info]  erl_tar: removed leading '/' from member names

All dependencies are up to date
Compiling 5 files (.ex)
Generated quenya_installer app
Generated archive "quenya_installer-0.3.0.ez" with MIX_ENV=prod
Are you sure you want to install "quenya_installer-0.3.0.ez"? [Yn]
* creating /Users/tchen/.mix/archives/quenya_installer-0.3.0

Generate APP from an existing OAPI spec

Once you finished installing quenya CLI, you can build a API app with quenya:

$ cd /tmp
$ curl https://raw.githubusercontent.com/tyrchen/quenya/master/parser/test/fixture/petstore.yml > petstore.yml
$ mix quenya.new petstore.yml petstore
* creating petstore/config/config.exs
* creating petstore/config/dev.exs
* creating petstore/config/prod.exs
* creating petstore/config/staging.exs
* creating petstore/config/test.exs
* creating petstore/lib/petstore/application.ex
* creating petstore/lib/petstore.ex
* creating petstore/mix.exs
* creating petstore/README.md
* creating petstore/.formatter.exs
* creating petstore/.gitignore
* creating petstore/test/test_helper.exs

Fetch and install dependencies? [Yn]
* running mix deps.get
* running mix deps.compile

We are almost there! The following steps are missing:

    $ cd petstore

You can run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix

This 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.

Running the app

Now you can run the app:

$ cd petstore/
$ mix compile.quenya # this command will generate/regenerate code on /gen and /test/gen folders
$ iex -S mix
Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [hipe] [dtrace]

Compiling 44 files (.ex)
Generated petstore app
Interactive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help)

Just 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:

It's great but nothing special. Now, try to invoke one of the APIs, say GET /pet/findByStatus:

Amazing! Don't believe what you saw? Try with this command:

curl -X POST "http://localhost:4000/pet" -H  "accept: application/json" -H  "Content-Type: application/json" -d "{\"name\":\"doggie\",\"photoUrls\":[\"bad url\"]}" -i
HTTP/1.1 400 Bad Request
cache-control: max-age=0, private, must-revalidate
content-length: 33
date: Mon, 30 Nov 2020 04:45:37 GMT
server: Cowboy

Expected to be a valid image_uri.

According to 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:

$ 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
HTTP/1.1 200 OK
cache-control: max-age=0, private, must-revalidate
content-length: 376
content-type: application/json; charset=utf-8
date: Mon, 30 Nov 2020 04:51:03 GMT
server: Cowboy

{"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"}]}

Running the tests

Quenya 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:

$ mix test
Compiling 42 files (.ex)
Generated petstore app
....................

Finished in 2.7 seconds
20 properties, 2 failures

Note these tests covers all success cases. In future, we will try to cover all failed cases in Quenya.

How much code Quenya generated for you?

If you have tokei installed, you can have a basic idea on how much code Quenya generated for you:

$ tokei gen test
-------------------------------------------------------------------------------
 Language            Files        Lines         Code     Comments       Blanks
-------------------------------------------------------------------------------
 Elixir                 83         8152         7060            0         1092
-------------------------------------------------------------------------------
 Total                  83         8152         7060            0         1092
-------------------------------------------------------------------------------

That'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.

What's under the hood?

Now 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:

  • preprocessors: any Plug to be executed before the actual route handler. Here, RequestValidator Plug will help to validate request params against the schema.
  • 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).
  • 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.

Quenya consists of 3 parts:

  1. quenya_installer: help with Quenya project generation (the CLI you just used).
  2. 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).
  3. quenya: a library consist of utility functions, tests and a playground to play with API or API stub.

What's the generated code?

If you look at the gen folder in the newly generated app, you'll find all your routes and routers are organized by operationId:

$ tree -L 1
.
├── Petstore.Gen.ApiRouter.ex
├── Petstore.Gen.Router.ex
├── addPet
├── createUser
├── createUsersWithArrayInput
├── createUsersWithListInput
├── deleteOrder
├── deletePet
├── deleteUser
├── findPetsByStatus
├── findPetsByTags
├── getInventory
├── getOrderById
├── getPetById
├── getUserByName
├── loginUser
├── logoutUser
├── placeOrder
├── updatePet
├── updatePetWithForm
├── updateUser
└── uploadFile

20 directories, 2 files

The main router will serve swagger and forward the path (extracted from the spec) to the API router:

defmodule Petstore.Gen.Router do
  @moduledoc false
  use Plug.Router
  use Plug.ErrorHandler
  require Logger
  alias Quenya.Plug.SwaggerPlug
  plug Plug.Logger, log: :info
  plug Plug.Static, at: "/public", from: {:quenya, "priv/swagger"}

  plug :match
  plug Plug.Parsers, parsers: [:json], pass: ["application/json"], json_decoder: Jason
  plug :dispatch

  def handle_errors(conn, %{kind: _kind, reason: %{message: msg}, stack: _stack}) do
    Plug.Conn.send_resp(conn, conn.status, msg)
  end

  def handle_errors(conn, %{kind: kind, reason: reason, stack: stack}) do
    Logger.warn(
      "Internal error:\n kind: #{inspect(kind)}\n reason: #{inspect(reason)}\n stack: #{
        inspect(stack)
      }"
    )

    Plug.Conn.send_resp(conn, conn.status, "Internal server error")
  end

  get("/swagger/main.json", to: SwaggerPlug, init_opts: [app: :petstore])
  get("/swagger", to: SwaggerPlug, init_opts: [spec: "/swagger/main.json"])
  forward "/", to: Petstore.Gen.ApiRouter, init_opts: []
end

The API router contains code for all routes, for example:

put("/user/:username",
    to: RoutePlug,
    init_opts: [
      preprocessors: [Petstore.Gen.UpdateUser.RequestValidator],
      postprocessors: [],
      handlers: [Petstore.Gen.UpdateUser.FakeHandler]
    ]
  )

When 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.

Why Quenya?

I've given a topic Building next-gen APIs 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.

Building a high-quality HTTP API app is non-trivial. Good APIs have these traits:

For API users:

  • Easy to learn and intuitive to use (the app provides full-fledged and good quality docs / playground)
  • Hard to misuse (API is type-safety and provides proper error responses)
  • Powerful enough to drive business requirements (flexible, performant)
  • Easy to evolve as the products grow
  • Opinionated (don't make me think)

For developers:

  • Easy to read and maintain existing code
  • Easy to write new APIs / extend existing APIs
  • Easy to generate code based on API spec (client SDKs, test cases, and even server implementation)

API implementation is just a small part of the API lifecycle, we need API design, mocking, testing, simulating, documentation, deployment, etc.

Quenya 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.

Why not GraphQL or other solutions?

See the above slides and you'll see why.

More Repositories

1

geektime-rust

我的极客时间 Rust 课程的代码仓库,随课程更新
Rust
905
star
2

unchained

My personal study of blockchain related technology.
Makefile
392
star
3

rust-training

my rust training to the team.
Rust
337
star
4

racket-book

My racket study documentation
Racket
311
star
5

book_next

wechat docs organized with the new makefile
JavaScript
120
star
6

reservation

core service for resource reservation
Rust
90
star
7

chinese_translation

An elixir module to translate simplified Chinese to traditional Chinese, and vice versa, based on wikipedia data
Elixir
89
star
8

renovate

Renovate is a CLI tool to help you to work on Postgres SQL migration easily.
Rust
65
star
9

teamspark

A simple bug/idea/feature tracking system for better team collaboration
CoffeeScript
60
star
10

system_design

system design talk and other notes.
52
star
11

cellar

A password tool for user to derive a large amount of application passwords deterministically based on a passphrase. Cryptographically strong.
Rust
47
star
12

chatroom

very simple chatroom for learning goroutine and channel
Go
44
star
13

awesome-resources

books, videos, online classes and other resources which helped me.
29
star
14

node-eventasync

node.js event emitter monkey patch for supporting asynchronous listeners
JavaScript
28
star
15

podgen

Statically generate a podcast site with itunes enabled rss. See live demo:
Go
27
star
16

simple-kv

Rust
25
star
17

async-prost

Rust
24
star
18

transformer

transform mime resources
JavaScript
21
star
19

aws-lambda-thumbnail

Example code to show aws lambda event driven functionality
JavaScript
19
star
20

coderunnerd

An unsafe code runner as a web service. Just like play.golang.org but supports more languages
JavaScript
19
star
21

elixir-meet-rust

my slides and demo for beijing elixir meetup 05/16/2020
Elixir
18
star
22

conceal

conceal your secret files for individual receiver
Rust
18
star
23

goodfilm

Learn to use postgrest by following http://blog.jonharrington.org/postgrest-introduction/
PLpgSQL
17
star
24

ex_polars

Elixir support for polars, a pandas like dataframe library.
Jupyter Notebook
16
star
25

fast2s

Rust
16
star
26

llm-apps

PLpgSQL
16
star
27

rust-lambda

the example code for my rust talk
TypeScript
15
star
28

certify

Rust
15
star
29

db-schema

This crate provides a set of functions to generate SQL statements for various PostgreSQL schema objects, such as tables, views, materialized views, functions, triggers, and indexes. The generated SQL statements can be useful for schema introspection, documentation, or migration purposes.
Rust
15
star
30

curl-parser

Convert curl command to a ParsedRequest (could be further converted to reqwest::RequestBuilder)
Rust
14
star
31

awesome-es6

A curated list of delightful ES6 packages and resources.
13
star
32

tyrchen.github.com

My personal blog
HTML
12
star
33

simple-dns

A simple DNS server
Rust
12
star
34

princess

my experiment on drab, for my daughter Lindsey to explore beauty of the math.
CSS
11
star
35

deno-utils

Rust
11
star
36

axum-swagger-ui

swagger UI integration with axum
HTML
10
star
37

xunmi

Rust
10
star
38

select

Rust
9
star
39

simplehooks

simple github webhooks
JavaScript
9
star
40

prost-helper

Two crates to facilitate prost to better work with protobuf. Serde is supported and best practices are applied.
Rust
9
star
41

ex_pre_commit_hook

pre commit hook for elixir project which handles compile / credo / test / docs
Elixir
8
star
42

tonic-mock

Test utilities for easy mocking tonic streaming interface
Rust
8
star
43

sqlx-db-tester

A simple tool to test sqlx with postgres. It will automatically create a database and drop it after the test.
Rust
8
star
44

ocap-example

Example code to play with ocap service
Python
6
star
45

rust-template

Rust
6
star
46

2020

Jupyter Notebook
6
star
47

json_data_faker

Generate JSON data from JSON schema by using faking data.
Elixir
6
star
48

easy-qjs

A quickjs wrapper for easy to integrate a JS engine to Rust code
Rust
6
star
49

elixir-hitchhikers-guide

HTML
5
star
50

simple_servers

My personal play with various rust servers including tokio, rocket, rust-libp2p, snow (noise protocol), etc.
Rust
5
star
51

pingpong

benchmark scheduler performance
Elixir
5
star
52

deneb

elixir library for generating vega-lite charts
Jupyter Notebook
5
star
53

tuqiongbixian

程序人生之图穷匕见 podcast (powered by https://github.com/tyrchen/podgen)
CSS
4
star
54

utility-belt

Rust
4
star
55

tub

Tub is a code generator that helps you generate code from data to various schema.
Elixir
4
star
56

simple_bitmap

Simple bitmap for manipulate the bitmap, membership check, and quickly get MSB (most significant bit).
Elixir
4
star
57

proto-builder-trait

Builder tools for easily adding attributes for prost-build/tonic-build generated code. serde/sqlx/derive_builder are supported.
Rust
4
star
58

mobc-tonic

An easy-to-use connection pool for tonic GRPC clients. Support TLS (even client cert) just by configuration.
Rust
4
star
59

free-icons

Use free svg icons in your html projects
Rust
3
star
60

rust-tauri-template

Vue
3
star
61

tokio-tls-helper

Make TLS easy to use for tokio applications.
Rust
3
star
62

assumeRole

AWS & IAM Q&A
JavaScript
3
star
63

jobs

CSS
3
star
64

stream-operators

selected rxjs operators implemented for standard Rust Stream
Rust
3
star
65

slides

JavaScript
3
star
66

react-mobx-rxjs-show

A simple example shows how react / mobx / rxjs rolling together
JavaScript
3
star
67

appshare

Share local http application
Go
3
star
68

rust-lib-template

Rust
3
star
69

cerf

A simple yet useful code examination software
CSS
3
star
70

xftts-dart

Xun Fei TTS dart implementation
Dart
2
star
71

common_parser

General parsers for various use cases
Elixir
2
star
72

django-utility-tools

small python or shell scripts that helps with django projects
2
star
73

xftts-cli

A simple CLI for generating mp3 based on Xun Fei TTS
Dart
2
star
74

church

JavaScript
2
star
75

scratch-with-lindsey

scratch code co-authored with my daughter, Lindsey.
2
star
76

vint

The command line client for cerf exam service.
Python
2
star
77

chinese_translation_server

chinese translation server utilizing the chinse_translation module
Elixir
2
star
78

data-pager

A simple pager tool
Rust
1
star
79

pusher-beam-rust

Rust
1
star
80

autopod

a flutter app that allow you write blogs and generate podcast based on blog entry.
Dart
1
star
81

next-crash-course

JavaScript
1
star
82

sicilia

Toureet editor admin. For editor to translate data.
Python
1
star
83

goutil

Golang utility functions, for grouping commonly used utility functions.
Go
1
star
84

sails-oauth2

Integrate oauth2 support for sails
JavaScript
1
star
85

flutter_template

Dart
1
star
86

flutter_clean_arch

flutter clean arch following https://www.youtube.com/playlist?list=PLB6lc7nQ1n4iYGE_khpXRdJkJEp9WOech
Dart
1
star
87

dynamodb-tools

A simple library to make the test your code against dynamodb local easy.
Rust
1
star
88

podgen-basic

Basic template for podcast site generator.
CSS
1
star
89

aws-team-dropbox

team internal dropbox leveraging aws s3
Makefile
1
star
90

academic-kickstart

Shell
1
star
91

cleanmyass

1
star
92

australia

toureet document center.
JavaScript
1
star
93

gnats

Python
1
star
94

easter

Easter is an independent project for tracking and displaying user behavior.
JavaScript
1
star
95

fish

django gearman distributed task project
Python
1
star
96

langkawi

TukeQ social registration project
Python
1
star
97

es6-hook

automatically add webpack and es6/react dependencies
JavaScript
1
star
98

accept-header

A simple library for parsing HTTP Accept headers for content negotiation
Rust
1
star
99

kagalaska

tagging service
Python
1
star
100

pulumi-examples

JavaScript
1
star