GithubHelp home page GithubHelp logo

exegesis's Introduction

Exegesis OpenAPI Engine

NPM version Build Status Coverage Status Greenkeeper badge semantic-release

exegesis

n. An explanation or critical interpretation of a text, especially an API definition document.

-- No dictionary ever

This library implements a framework-agnostic server side implementation of OpenAPI 3.x.

Description

Exegesis is a library for implementing server-side OpenAPI 3.x The library has been written in such a way that hopefully it will also be used to implement future versions of OpenAPI, or possibly even other API description standards altogether.

You probably don't want to be using this library directly. Have a look at:

Features

Tutorial

Check out the tutorial here.

API

compileApi(openApiDoc, options[, done])

This function takes an API document and a set of options, and returns a connect-style middleware function which will execute the API.

openApiDoc is either a path to your openapi.yaml or openapi.json file, or it can be a JSON object with the contents of your OpenAPI document. This should have the x-exegesis-controller extension defined on any paths you want to be able to access.

options is described in detail here. At a minimum, you'll probably want to provide options.controllers, a path to where your controller modules can be found. If you have any security requirements defined, you'll also want to pass in some authenticators. To enable response validation, you'll want to provide a validation callback function via onResponseValidationError(). Exegesis's functionality can also be extended using plugins, which run on every request. Plugins let you add functionality like role base authorization, or CORS.

compileRunner(openApiDoc, options[, done])

This function is similar to compileApi; it takes an API document and a set of options, and returns a "runner". The runner is a function runner(req, res), which takes in a standard node HTTP request and response. It will not modify the response, however. Instead it returns (either via callback or Promise) and HttpResult object. This is a {headers, status, body} object, where body is a readable stream, read to be piped to the response.

writeHttpResult(httpResult, res[, done])

A convenience function for writing an HttpResult from a runner out to the response.

Example

import * as path from 'path';
import * as http from 'http';
import * as exegesis from 'exegesis';

// See https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md
const options = {
  controllers: path.resolve(__dirname, './src/controllers'),
};

// `compileApi()` can either be used with a callback, or if none is provided,
// will return a Promise.
exegesis.compileApi(
  path.resolve(__dirname, './openapi/openapi.yaml'),
  options,
  (err, middleware) => {
    if (err) {
      console.error('Error creating middleware', err.stack);
      process.exit(1);
    }

    const server = http.createServer((req, res) =>
      middleware(req, res, (err) => {
        if (err) {
          res.writeHead(err.status || 500);
          res.end(`Internal error: ${err.message}`);
        } else {
          res.writeHead(404);
          res.end();
        }
      })
    );

    server.listen(3000);
  }
);

Internal Workings

Internally, when you "compile" an API, Exegesis produces an ApiInterface object. This is an object that, given a method, url, and headers, returns a resolvedOperation - essentially a collection of functions that will parse and validate the body and parameters, has the controller that executes the functionality, etc... The only current implementation for an ApiInterface is the oas3/OpenApi class. Essentially this class's job is to take in an OpenAPI 3.x.x document, and turn it an ApiInterface that Exegesis can use. In theory, however, we could parse some other API document format, produce an ApiInterface, and Exegsis would still be able to run it.

exegesis's People

Contributors

bars0um avatar borsucok avatar chuve avatar confuser avatar dependabot-preview[bot] avatar dependabot[bot] avatar erykwarren avatar fantasy-is-absent avatar greenkeeper[bot] avatar greenkeeperio-bot avatar hiestaa avatar jose-oliveira avatar jwalton avatar niallmccullagh avatar oguzbilgener avatar pvince avatar rjray avatar ryan-gordon avatar systemdisc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

exegesis's Issues

PathResolver can sometimes resolve to the wrong path

I have two different 'paths' in my application:
POST /api/v0/{grouping}/items/generate
GET /api/v0/{grouping}/items/{id}

The two paths appear in my openapi.yaml file in that order. While doing testing I noticed that when I called POST /api/v0/{grouping}/items/generate no controller was getting called.

I stepped through the exegesisRunner until I hit the PathResolver.resolvePath function and noticed that when I ran POST /api/v0/{grouping}/items/generate, resolvePath was returning GET /api/v0/{grouping}/items/{id} with 'grouping' set as a parameter, and with 'id' set as 'generate'.

When I stepped through resolvePath I noticed that the for loop @

for(const dynamicPath of this._dynamicPaths) {
has no 'early out'.

As in, it first finds the 'generate' endpoint, then keeps processing the path and the last path it processes that matches is the GET .../items/{id}, so it returns that.

As a temporary hack, I have changed my local code such that the for loop stops once it matches any path, but then it leaves the end user at the mercy of the order they define their endpoints in their specification file.

I am not sure if there is a way you can change the code to build up a list of 'matched' paths, and then prefer paths that have more 'fixed' values over paths that have more variables?

PATCH method not supported

oas3/Path.js does not list PATCH among HTTP-methods:

const HTTP_METHODS = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE'];

This results in all paths with patch is ignored.

Add cors middleware to exegesis middleware?

How do I add the cors middleware to express?

I tried with app.use(cors(exegesisMiddleware));

But it does not work.
I have searched for an exegesis-plugin-cors plugin also, but only found: exegesis-plugin-context
Can this be used for cors?

An in-range update of lint-staged is breaking the build 🚨

The devDependency lint-staged was updated from 8.1.6 to 8.1.7.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

lint-staged is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build failed (Details).
  • coverage/coveralls: First build on greenkeeper/lint-staged-8.1.7 at 84.495% (Details).

Release Notes for v8.1.7

8.1.7 (2019-05-15)

Bug Fixes

  • Resolve security vulnerability in dependencies (#615) (315890a), closes #600
Commits

The new version differs by 2 commits.

  • 315890a fix: Resolve security vulnerability in dependencies (#615)
  • cbf0e0e docs: Correct section about filtering files (#612)

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Error: Operation /paths/~1secure/get references security scheme "sessionKey" but no authenticator was provided.

Version Info

  • node: 10.15.1
  • exegesis-express: 1.0.0
  • exegesis: 1.3.0
  • express: 4.16.3

Problem

My openAPI.yaml is:

openapi: "3.0.1"
info:
  version: 1.0.0
  title: Exegesis Integration Test
  license:
    name: MIT
paths:
  /greet:
    get:
      summary: List all pets
      x-exegesis-controller: greetController
      operationId: greetGet
      parameters:
        - name: name
          in: query
          description: Name of user to greet
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Greet message.
          content:
            application/json:
              schema:
                type: object
                required:
                  - greeting
                properties:
                  greeting: {type: string}
        default:
          description: unexpected error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /secure:
    get:
      summary: A secure operation
      x-exegesis-controller: secureController
      operationId: secureGet
      security:
        - sessionKey: []
      responses:
        '200':
          description: Greet message.
          content:
            application/json:
              schema: {type: 'object'}
components:
  securitySchemes:
    sessionKey:
      type: 'apiKey'
      name: 'session'
      in: 'header'
  schemas:
    Error:
      required:
        - message
      properties:
        message: {type: string}

copy from 'https://github.com/exegesis-js/exegesis-express/blob/master/test/integrationSample/openapi.yaml'.

Main is index.js:

import express from 'express';
import path from 'path';
import http from 'http';
import * as exegesisExpress from 'exegesis-express';

async function createServer() {
    // See https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md
    const options = {
        controllers: path.resolve(__dirname, './controllers')
    };

    const exegesisMiddleware = await exegesisExpress.middleware(
        path.resolve(__dirname, './openApi.yaml'),
        options
    );

    const app = express();

    // If you have any body parsers, this should go before them.
    app.use(exegesisMiddleware);

    app.use((req, res) => {
        res.status(404).json({message: `Not found`});
    });

    app.use((err, req, res, next) => {
        res.status(500).json({message: `Internal error: ${err.message}`});
    });

    const server = http.createServer(app);
    server.listen(3000);
}

createServer();

when I run babel-node index.js, then I get:

➜  exegesis babel-node index.js
(node:6771) UnhandledPromiseRejectionWarning: Error: Operation /paths/~1secure/get references security scheme "sessionKey" but no authenticator was provided.
    at new Operation (/Users/yaojifeng/WebstormProjects/exegesis/node_modules/exegesis/src/oas3/Operation.ts:133:27)
    at Path._operations.HTTP_METHODS.map.filter.reduce (/Users/yaojifeng/WebstormProjects/exegesis/node_modules/exegesis/src/oas3/Path.ts:36:38)
    at Array.reduce (<anonymous>)
    at new Path (/Users/yaojifeng/WebstormProjects/exegesis/node_modules/exegesis/src/oas3/Path.ts:34:14)
    at new Paths (/Users/yaojifeng/WebstormProjects/exegesis/node_modules/exegesis/src/oas3/Paths/index.ts:17:32)
    at new OpenApi (/Users/yaojifeng/WebstormProjects/exegesis/node_modules/exegesis/src/oas3/OpenApi.ts:50:23)
    at Object.<anonymous> (/Users/yaojifeng/WebstormProjects/exegesis/node_modules/exegesis/src/oas3/index.ts:16:12)
    at Generator.next (<anonymous>)
    at /Users/yaojifeng/WebstormProjects/exegesis/node_modules/exegesis/lib/oas3/index.js:7:71
    at new Promise (<anonymous>)
(node:6771) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:6771) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Support object oriented controllers

I'd like to create the controllers as classes and not in the functional way. I'd like to propose controllers to also support classes. This could be implemented as such:

  • Is the module export a class? (Don't currently know, if that can be reflected or if something like an .isclass=true export has to be implemented)
  • If so, instantiate the class
  • On the controller invokation, call a method with the controller name on the instance

Header parameters are not being handled as case-insensitive

Headers field names are case-insensitive as per RFC7230, but they're being treated as case-sensitive in requests.

The root of the problem seems to be that in oas3/parameterParsers/index.ts, many parameters go through the toStructuredParser function, that does const value = rawParamValues[location.name]. rawParamValues has the header keys already in lower-case, while location.name is still in Train-Case.

I've got a PR to fix this in the works, though I'm not sure if the way I solved this was the best approach possible. I copied the one used for query parameters -- to call a different function in oas3/Operation.ts in parseParameters for them and, do a little preprocessing -- but it feels a little redundant to do this for each location (i.e. in: query / in: header), when we already have custom parsers that vary by style / format.

update promise-breaker dep. break build

"promise-breaker": "^4.1.11"

is broken, bc. it install version 4.1.13 when you reinstall deps.
with this version i get error in build process

> tsc

src/index.ts:185:25 - error TS2345: Argument of type 'Callback<MiddlewareFunction> | undefined' is not assignable to parameter of type 'Callback<(ctx: Context, next: Callback<void>) => Promise<void>> | null | undefined'.
  Type 'Callback<MiddlewareFunction>' is not assignable to type 'Callback<(ctx: Context, next: Callback<void>) => Promise<void>> | null | undefined'.
    Type 'Callback<MiddlewareFunction>' is not assignable to type 'Callback<(ctx: Context, next: Callback<void>) => Promise<void>>'.
      Types of parameters 'value' and 'result' are incompatible.
        Types of parameters 'next' and 'next' are incompatible.
          Type 'Callback<void> | undefined' is not assignable to type 'Callback<void>'.
            Type 'undefined' is not assignable to type 'Callback<void>'.

185   return pb.addCallback(done, async () => {
          

also package-lock.json should`nt be gitted.

setHeader not chainable

Is there a reason that setHeader in ExegesisResponseImpl.ts in a controller does not return this and therefore is not chainable? The example in Exegesis Controllers.md uses setHeader in a chainable fashion if I am not mistaken.
Is there a difference besides the if(this.ended && !this._afterController) check and chainability compared to to the method header?

An in-range update of @types/node is breaking the build 🚨

The devDependency @types/node was updated from 10.12.15 to 10.12.16.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

@types/node is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).
  • coverage/coveralls: First build on greenkeeper/@types/node-10.12.16 at 80.804% (Details).

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Custom error format proposal

Hi Jason,

It would be great for developers using the library to be able to provide custom error formats.

For example, I would like to provide data so that a front end could localize an error message in the browser user's locale or customise the message entirely.

I noticed that exegesis uses Ajv which provides a lot of data points. See ajv#error-objects

My thoughts are to:

  1. Add more of these data points to IValidationError and set appropriately
  2. Modify src/core/exegesisRunner.ts#handleError to maintain current format for backward compatibility.
  3. Developers using the library could set autoHandleHttpErrors to false, catch and format the errors as they wish.

I would appreciate your feedback. I don't mind giving this a stab if you think it is a good idea.

Cheers,

Niall

The automated release is failing 🚨

🚨 The automated release from the master branch failed. 🚨

I recommend you give this issue a high priority, so other packages depending on you could benefit from your bug fixes and new features.

You can find below the list of errors reported by semantic-release. Each one of them has to be resolved in order to automatically publish your package. I’m sure you can resolve this 💪.

Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.

Once all the errors are resolved, semantic-release will release your package the next time you push a commit to the master branch. You can also manually restart the failed CI job that runs semantic-release.

If you are not sure how to resolve this, here is some links that can help you:

If those don’t help, or if this issue is reporting something you think isn’t right, you can always ask the humans behind semantic-release.


The push permission to the Git repository is required.

semantic-release cannot push the version tag to the branch master on remote Git repository.

Please refer to the authentication configuration documentation to configure the Git credentials on your CI environment.


Good luck with your project ✨

Your semantic-release bot 📦🚀

onHeaders listner not invoking when a response is about to write headers

We are recording start and end time when the request is served, so we start recording time in postRouting and try to end recording just when a response is about to write headers. For this i am using onHeaders library (https://github.com/jshttp/on-headers) where we can override onHeaders listener, but it looks like it never invokes this listener. I suspect when it reaches to postController the header is already sent. please suggest

Typescript build failed

When building with tsc following error occures:

node_modules/exegesis/lib/types/bodyParser.d.ts:2:8 - error TS1192: Module '"http"' has no default export.

2 import http from 'http';
         ~~~~


error An unexpected error occurred: "Command failed.

How to access express res object

Using exegesis-express, I am not sure how to access the express res object (for example, to call res.cookie)... The class ExegesisResponseImpl does not offer all those functions. Is the user expected to use origRes for those?

Idea: could the exegesis-express offer a res that is a proxy or a mixin to both the express res object and ExegesisReponse... I could dig into a more concrete suggestion

nullable is not recognized / respected in schema

Hey there,

I was trying to convert a former Swagger 2 application to OpenAPI 3, switching to Exegesis. I began by following the Exegesis Tutorial which worked. Then I replaced the OpenAPI yaml and adjusted some names. Now my requests are still accepted and reply as expected - except if one of the fields is null. This was the original reason why I wanted to switch to OpenAPI 3 - it has the "nullable" attribute. Sadly, this seems to be ignored by exegesis.

So, here is my OpenAPI doc (it describes something similar to the payload sent by Bitbucket with webhooks):

openapi: 3.0.1
info:
  title: My API
  version: 1.0.0
paths:
  '/push':
    post:
      summary: Greets the user
      operationId: processMessageForPush
      x-exegesis-controller: incoming
      parameters:
        - name: GIT_BRANCH
          in: query
          schema:
            type: string
          style: form
          explode: false
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Payload"
      responses:
        '200':
          description: YEAH
components:
  schemas:
    Payload:
      description: >-
        Message object. Containing set of control parameters and a payload
        KeyValuePair array.
      type: object
      properties:
        actor:
          $ref: "#/components/schemas/User"
        repository:
          type: object
        push:
          $ref: "#/components/schemas/PushInfo"
    PushInfo:
      type: object
      properties:
        changes:
          type: array
          items:
            $ref: "#/components/schemas/ChangeInfo"
    ChangeInfo:
      type: object
      properties:
        new:
          $ref: "#/components/schemas/StateInfo"
        old:
          $ref: "#/components/schemas/StateInfo"
        links:
          $ref: "#/components/schemas/Links"
        created:
          type: boolean
        forced:
          type: boolean
        closed:
          type: boolean
        commits:
          type: array
          items:
            $ref: "#/components/schemas/CommitInfo"
        truncated:
          type: boolean
    CommitInfo:
      type: object
      properties:
        hash:
          type: string
        type:
          type: string
        message:
          type: string
        author:
          $ref: "#/components/schemas/User"
        links:
          $ref: "#/components/schemas/Links"
    StateInfo:
      type: object
      nullable: true
      properties:
        type:
          type: string
        name:
          type: string
        target:
          $ref: "#/components/schemas/TargetInfo"
        links:
          $ref: "#/components/schemas/Links"
    TargetInfo:
      type: object
      properties:
        type:
          type: string
        hash:
          type: string
        author:
          $ref: "#/components/schemas/User"
        message:
          type: string
        date:
          type: string
        parents:
          type: array
          items:
            $ref: "#/components/schemas/Parent"
        links:
          $ref: "#/components/schemas/Links"
    Parent:
      type: object
      properties:
        type:
          type: string
        hash:
          type: string
        links:
          $ref: "#/components/schemas/Links"
    User:
      type: object
    Links:
      type: object
      properties:
        html:
          $ref: "#/components/schemas/href"
        diff:
          $ref: "#/components/schemas/href"
        commits:
          $ref: "#/components/schemas/href"
        self:
          $ref: "#/components/schemas/href"
    href:
      type: object
      properties:
        href:
          type: string

The request I send is a POST request to the URL http://localhost:3000/push?GIT_BRANCH=develop with the body as follows:

{
  "push": {
    "changes": [
      {
        "forced": false,
        "old": null,
        "links": {
          "commits": {
            "href": "asdf"
          },
          "html": {
            "href": "asdf"
          }
        },
        "truncated": true,
        "commits": [
          {
            "hash": "123412341234",
            "links": {
              "self": {
                "href": "asdf"
              },
              "comments": {
                "href": "asdf"
              },
              "patch": {
                "href": "asdf"
              },
              "html": {
                "href": "asdf"
              },
              "diff": {
                "href": "asdf"
              },
              "approve": {
                "href": "asdf"
              },
              "statuses": {
                "href": "asdf"
              }
            },
            "author": {
              "raw": "One Author",
              "type": "author",
              "user": {
                "username": "test",
                "display_name": "One Author",
                "account_id": "asdf",
                "links": {
                  "self": {
                    "href": "asdf"
                  },
                  "html": {
                    "href": "asdf"
                  },
                  "avatar": {
                    "href": "asdf"
                  }
                },
                "type": "user",
                "nickname": "test",
                "uuid": "asdf"
              }
            },
            "summary": {
              "raw": "sdfhsdhfsdf\n",
              "markup": "markdown",
              "html": "<p>sdfhsdhfsdf</p>",
              "type": "rendered"
            },
            "parents": [
              {
                "type": "commit",
                "hash": "123412341234",
                "links": {
                  "self": {
                    "href": "asdf"
                  },
                  "html": {
                    "href": "asdf"
                  }
                }
              }
            ],
            "date": "2018-10-30T16:19:16+00:00",
            "message": "sdfhsdhfsdf\n",
            "type": "commit"
          },
          {
            "hash": "123412341234",
            "links": {
              "self": {
                "href": "asdf"
              },
              "comments": {
                "href": "asdf"
              },
              "patch": {
                "href": "asdf"
              },
              "html": {
                "href": "asdf"
              },
              "diff": {
                "href": "asdf"
              },
              "approve": {
                "href": "asdf"
              },
              "statuses": {
                "href": "asdf"
              }
            },
            "author": {
              "raw": "One Author",
              "type": "author",
              "user": {
                "username": "test",
                "display_name": "One Author",
                "account_id": "asdf",
                "links": {
                  "self": {
                    "href": "asdf"
                  },
                  "html": {
                    "href": "asdf"
                  },
                  "avatar": {
                    "href": "asdf"
                  }
                },
                "type": "user",
                "nickname": "test",
                "uuid": "asdf"
              }
            },
            "summary": {
              "raw": "because I need real commits to test my triggers, I will fix all the spelling mistakes\n",
              "markup": "markdown",
              "html": "<p>because I need real commits to test my triggers, I will fix all the spelling mistakes</p>",
              "type": "rendered"
            },
            "parents": [
              {
                "type": "commit",
                "hash": "123412341234",
                "links": {
                  "self": {
                    "href": "asdf"
                  },
                  "html": {
                    "href": "asdf"
                  }
                }
              }
            ],
            "date": "2018-10-30T15:46:20+00:00",
            "message": "because I need real commits to test my triggers, I will fix all the spelling mistakes\n",
            "type": "commit"
          },
          {
            "hash": "123412341234",
            "links": {
              "self": {
                "href": "asdf"
              },
              "comments": {
                "href": "asdf"
              },
              "patch": {
                "href": "asdf"
              },
              "html": {
                "href": "asdf"
              },
              "diff": {
                "href": "asdf"
              },
              "approve": {
                "href": "asdf"
              },
              "statuses": {
                "href": "asdf"
              }
            },
            "author": {
              "raw": "asdf",
              "type": "author",
              "user": {
                "username": "asdf",
                "display_name": "asdf",
                "account_id": "asdf",
                "links": {
                  "self": {
                    "href": "asdf"
                  },
                  "html": {
                    "href": "asdf"
                  },
                  "avatar": {
                    "href": "asdf"
                  }
                },
                "type": "user",
                "nickname": "asdf",
                "uuid": "asdf"
              }
            },
            "summary": {
              "raw": "Updated Version to 1.0\n",
              "markup": "markdown",
              "html": "<p>Updated Version to 1.0</p>",
              "type": "rendered"
            },
            "parents": [
              {
                "type": "commit",
                "hash": "123412341234",
                "links": {
                  "self": {
                    "href": "asdf"
                  },
                  "html": {
                    "href": "asdf"
                  }
                }
              }
            ],
            "date": "2018-09-07T12:38:19+00:00",
            "message": "Updated Version to 1.0\n",
            "type": "commit"
          },
          {
            "hash": "123412341234",
            "links": {
              "self": {
                "href": "asdf"
              },
              "comments": {
                "href": "asdf"
              },
              "patch": {
                "href": "asdf"
              },
              "html": {
                "href": "asdf"
              },
              "diff": {
                "href": "asdf"
              },
              "approve": {
                "href": "asdf"
              },
              "statuses": {
                "href": "asdf"
              }
            },
            "author": {
              "raw": "asdf",
              "type": "author",
              "user": {
                "username": "asdf",
                "display_name": "asdf",
                "account_id": "asdf",
                "links": {
                  "self": {
                    "href": "asdf"
                  },
                  "html": {
                    "href": "asdf"
                  },
                  "avatar": {
                    "href": "asdf"
                  }
                },
                "type": "user",
                "nickname": "asdf",
                "uuid": "asdf"
              }
            },
            "summary": {
              "raw": "Updated Version to 1.0.2-testprod\n",
              "markup": "markdown",
              "html": "<p>Updated Version to 1.0.2-testprod</p>",
              "type": "rendered"
            },
            "parents": [
              {
                "type": "commit",
                "hash": "123412341234",
                "links": {
                  "self": {
                    "href": "asdf"
                  },
                  "html": {
                    "href": "asdf"
                  }
                }
              }
            ],
            "date": "2018-07-19T09:24:19+00:00",
            "message": "Updated Version to 1.0.2-testprod\n",
            "type": "commit"
          },
          {
            "hash": "123412341234",
            "links": {
              "self": {
                "href": "asdf"
              },
              "comments": {
                "href": "asdf"
              },
              "patch": {
                "href": "asdf"
              },
              "html": {
                "href": "asdf"
              },
              "diff": {
                "href": "asdf"
              },
              "approve": {
                "href": "asdf"
              },
              "statuses": {
                "href": "asdf"
              }
            },
            "author": {
              "raw": "One Author",
              "type": "author",
              "user": {
                "username": "test",
                "display_name": "One Author",
                "account_id": "asdf",
                "links": {
                  "self": {
                    "href": "asdf"
                  },
                  "html": {
                    "href": "asdf"
                  },
                  "avatar": {
                    "href": "asdf"
                  }
                },
                "type": "user",
                "nickname": "test",
                "uuid": "asdf"
              }
            },
            "summary": {
              "raw": "asdf",
              "markup": "markdown",
              "html": "asdf",
              "type": "rendered"
            },
            "parents": [
              {
                "type": "commit",
                "hash": "123412341234",
                "links": {
                  "self": {
                    "href": "asdf"
                  },
                  "html": {
                    "href": "asdf"
                  }
                }
              }
            ],
            "date": "2018-07-19T08:29:02+00:00",
            "message": "asdf",
            "type": "commit"
          }
        ],
        "created": true,
        "closed": false,
        "new": {
          "target": {
            "hash": "123412341234",
            "links": {
              "self": {
                "href": "asdf"
              },
              "html": {
                "href": "asdf"
              }
            },
            "author": {
              "raw": "One Author",
              "type": "author",
              "user": {
                "username": "test",
                "display_name": "One Author",
                "account_id": "asdf",
                "links": {
                  "self": {
                    "href": "asdf"
                  },
                  "html": {
                    "href": "asdf"
                  },
                  "avatar": {
                    "href": "asdf"
                  }
                },
                "type": "user",
                "nickname": "test",
                "uuid": "asdf"
              }
            },
            "summary": {
              "raw": "sdfhsdhfsdf\n",
              "markup": "markdown",
              "html": "<p>sdfhsdhfsdf</p>",
              "type": "rendered"
            },
            "parents": [
              {
                "type": "commit",
                "hash": "123412341234",
                "links": {
                  "self": {
                    "href": "asdf"
                  },
                  "html": {
                    "href": "asdf"
                  }
                }
              }
            ],
            "date": "2018-10-30T16:19:16+00:00",
            "message": "sdfhsdhfsdf\n",
            "type": "commit"
          },
          "links": {
            "commits": {
              "href": "asdf"
            },
            "self": {
              "href": "asdf"
            },
            "html": {
              "href": "asdf"
            }
          },
          "default_merge_strategy": "merge_commit",
          "merge_strategies": [
            "merge_commit",
            "squash",
            "fast_forward"
          ],
          "type": "branch",
          "name": "spelling"
        }
      }
    ]
  },
  "repository": {
    "scm": "git",
    "website": "",
    "name": "asdf",
    "links": {
      "self": {
        "href": "asdf"
      },
      "html": {
        "href": "asdf"
      },
      "avatar": {
        "href": "asdf"
      }
    },
    "project": {
      "links": {
        "self": {
          "href": "asdf"
        },
        "html": {
          "href": "asdf"
        },
        "avatar": {
          "href": "asdf"
        }
      },
      "type": "project",
      "uuid": "asdf",
      "key": "asdf",
      "name": "asdf"
    },
    "full_name": "asdf/asdf",
    "owner": {
      "username": "asdf",
      "type": "team",
      "display_name": "asdf",
      "uuid": "asdf",
      "links": {
        "self": {
          "href": "asdf"
        },
        "html": {
          "href": "asdf"
        },
        "avatar": {
          "href": "asdf"
        }
      }
    },
    "type": "repository",
    "is_private": true,
    "uuid": "asdf"
  },
  "actor": {
    "username": "test",
    "display_name": "One Author",
    "account_id": "asdf",
    "links": {
      "self": {
        "href": "asdf"
      },
      "html": {
        "href": "asdf"
      },
      "avatar": {
        "href": "asdf"
      }
    },
    "type": "user",
    "nickname": "test",
    "uuid": "asdf"
  }
}

As you can see, the value for push.changes[0].old is null, which is why I made the according schema nullable. Still, I receive the following answer:

{
    "message": "Validation errors",
    "errors": [
        {
            "message": "should be object",
            "location": {
                "in": "request",
                "name": "body",
                "docPath": "/paths/~1push/post/requestBody/content/application~1json/schema",
                "path": "/push/changes/0/old"
            }
        }
    ]
}

How is that possible? Apart from some names, I did not change the options or the content of index.js as explained in the tutorial.

(Field contents in the payload have been partly anonymized.)

What is the best way to return custom validation error messages

If the validation is part of OAS spec than it gives a nice Error object e.g.,

{
   "message" : "Validation errors",
   "errors" : [
      {
         "location" : {
            "path" : [],
            "name" : "message",
            "docPath" : [
               "paths",
               "/hello",
               "get",
               "parameters",
               "0"
            ],
            "in" : "query"
         },
         "type" : "error",
         "message" : "Missing required query parameter \"message\""
      }
   ]
}

What is the best way to do additional validations (more complex ones) in controllers and return the error in similar format, obviously I can build it manually but ideally if the framework has a standard way to send such errors that would help in being consistent.

An in-range update of @types/lodash is breaking the build 🚨

The devDependency @types/lodash was updated from 4.14.126 to 4.14.127.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

@types/lodash is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

When is approx. RTM 1.0 date?

Really like this project, since currently there are no other alternatives to swagger-tools and swagger-node for OpenAPI 3.0, this seems like a great project.

I want to use it for one of my projects, but before I go on that journey wanted to understand your plans for 1.0 release.

Any plans to include UI docs with Exegesis

I have seen other OpenAPI/Swagger frameworks which provide a live documented version of the API usually located at the '/ui' path. I believe this is achieved using swagger-ui.

Would this be possible to include functionality for this with Exegesis as an optional feature? I would be happy to help via PR's
Story:
As a Developer, I want to have the option for generating a UI docs page so that when needed, developers won't have to code it themselves.

Array type query parameters are not being parsed correctly.

I have a path (Please excuse any formatting errors, I am typing this up in the text box):

  '/items':
    get:
      x-exegesis-controller: get_Items
      description: Gets items
      operationId: getItems
      parameters:
      - name: fields
        in: query
        schema:
          type: array
          items:
            type: string
            enum:
              - field1
              - field2
              - field3
              - field4
      responses:
        '200':
          description: Success

I am expecting to be able to run the following query: GET /api/v0/items?field1,field2

However when I run that I get the following error:

{
    "message": "Validation errors",
    "errors": [
        {
            "message": "should be equal to one of the allowed values",
            "location": {
                "in": "query",
                "name": "fields",
                "docPath": "/paths/items/get/parameters/1/schema",
                "path": "/0"
            }
        }
    ]
}

I did some digging, and I think this issue is tied to this line:

return [decodeURIComponent(value)];

I was able to hack around the problem by changing that line from return [decodeURIComponent(value)]; to return decodeURIComponent(value).split(',');

An in-range update of @types/lodash is breaking the build 🚨

The devDependency @types/lodash was updated from 4.14.124 to 4.14.125.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

@types/lodash is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Feature Request: Generate a '405 - Method Not allowed' error

I am migrating a project from swagger-connect to exegesis-express.

If an endpoint is recognized, but the method is not swagger-connect will generate a 405 error message:

{
    "message": "Path [/items] defined in Swagger, but POST operation is not.",
    "statusCode": 405,
    "status": 405,
    "allowedMethods": [
        "GET"
    ],
    "code": "Method Not Allowed"
}

If I make the same request against my Exegesis server it is falling through to my 404 error handling.

Action required: Greenkeeper could not be activated 🚨

🚨 You need to enable Continuous Integration on all branches of this repository. 🚨

To enable Greenkeeper, you need to make sure that a commit status is reported on all branches. This is required by Greenkeeper because it uses your CI build statuses to figure out when to notify you about breaking changes.

Since we didn’t receive a CI status on the greenkeeper/initial branch, it’s possible that you don’t have CI set up yet. We recommend using Travis CI, but Greenkeeper will work with every other CI service as well.

If you have already set up a CI for this repository, you might need to check how it’s configured. Make sure it is set to run on all new branches. If you don’t want it to run on absolutely every branch, you can whitelist branches starting with greenkeeper/.

Once you have installed and configured CI on this repository correctly, you’ll need to re-trigger Greenkeeper’s initial pull request. To do this, please delete the greenkeeper/initial branch in this repository, and then remove and re-add this repository to the Greenkeeper App’s white list on Github. You'll find this list on your repo or organization’s settings page, under Installed GitHub Apps.

Looking way to read custom extensions in OAS spec

@jwalton I am trying to figure out a way to get the custom extensions from yaml file for the current path.

I was able to get it using,

context.api.pathItemObject.get["x-rate-limit"]

Is this the recommended way of getting these custom extensions?

Confusing message when authenticator result type is `invalid`

Version Info:

  • node: 10.8.0
  • exegesis-express: 1.0.5
  • exegesis-express: 1.0.0
  • express: 4.16.3

Problem

I built a simple API to gain an understanding of your library. There is a single endpoint that is secured with the following scheme, which uses the sample authenticator that is detailed in OAS3 Security.md

  securitySchemes:
    sessionKey:
      type: apiKey
      in: header
      name: session

When the session header is missing from the request the expected message is returned with a 401 status.

curl -i -X GET 'http://localhost:3000/v1/greet?name=ted'
HTTP/1.1 401 Unauthorized
content-type: application/json
Date: Fri, 31 Aug 2018 09:47:16 GMT
Connection: keep-alive
Transfer-Encoding: chunked

{"message":"Must authenticate using one of the following schemes: sessionKey."}

However, if the session header is specified but it is invalid, the same message is returned.

curl -i -X GET 'http://localhost:3000/v1/greet?name=ted' -H 'session: secret1'
HTTP/1.1 401 Unauthorized
content-type: application/json
Date: Fri, 31 Aug 2018 09:48:42 GMT
Connection: keep-alive
Transfer-Encoding: chunked

{"message":"Must authenticate using one of the following schemes: sessionKey."}

My initial conclusion was that the library was not detecting the session header, so I went digging into the code. I got down to the authenticate method on in lib/oas3/Operation.js and realised that it does detect the header, but noticed that it doesn't return the result if the security type is invalid.

I would've expected that the message and status code that I defined and returned in the authenticator to be set in the response.

For multiple authenticators, I would expect the behaviour to the same as a successful authentication. That is, it would return the result of the first invalid authenticator encountered.

Add a note to the documentation on testing controllers

As an exegesis user
I want a way to test my controllers
so that I can also test certain parts of the context (does the controller set the correct status code for example)

given an exegesis controller

when my controller modifies the status code

then I should be able to asset this change with access to the context object

given an exegesis controller

when my controller returns a result to the user
then I should be able review the returned payload to ensure it conforms to specs

I could try to implement this but wondering where to start.

Ajv 'coerceTypes' option should probably not be set for request bodies

I realize the following structure is not great, but it matches a legacy API that we can't change without potentially breaking customer's programs. If I was implementing this today, we would do this differently.

I have a body property that can either be a positive integer (minValue: 1) or it can be a string value representing the name of something. The integer and the string both uniquely identify something.

POST .../object/{objectID}
{
   tagIdentifier: 15
}

OR

POST .../object/{objectID}
{
   tagIdentifier: "Name of label"
} 

I wrote up the definition for tagIdentifier as:

Object:
  properties:
    tagIdentifier:
      oneOf:
        - type: string
        - type: number
          format: int64
          minimum: 1

Howerver, the following was passing schema validation:

POST .../object/{objectID}
{
  tagIdentifier: -1
}

Digging into the issue I traced it back to the validator which was coercing the 'tagIdentifier' value to always be a string value, so when it went through validation it was checking this as "-1" instead of as the actual -1 value.

Digging through the code it appears the issue is tied to the initialization of Ajv.

const ajv = new Ajv({

This is hardly beautiful, but I was able to test out a fix by adding the following lines right before the ajv initialization:

    let coerceTypesOveride = allowTypeCoercion ? 'array' : false;
    if (allowTypeCoercion && parameterLocation.in === 'request') {
        // We do not want to allow type coercion on request bodies in any form.
        coerceTypesOveride = false;
    }

Changing the type coercion to NOT coerce arrays fixed a separate issue I was having as well, where an array of enum values was accepting a non-array. It was a lesser issue that I was going to ignore, but this resolves that as well.

Currently I am in crunch trying to get our swagger-node to exegesis-express conversion done, but once that is finished I will see if I can send a pull request to resolve this if you agree with the changes. Maybe there could be a configuration option to choose when to allow type coercion? I think query and path are pretty much required to do type coercion, but it should be an option for request bodies.

Consider passing compiledOptions to plugin preCompile

Currently the raw options are passed to preCompile(). However, it may be (more?) useful to have the compiledOptions. Specifically I'd like to create a plugin that can reference the controllers. But this currently fails if the controllers are passed as a path in the original options. The compiledOptions have already resolved the controllers string into an object but that is not accessible in preCompile().

An in-range update of openapi3-ts is breaking the build 🚨

The dependency openapi3-ts was updated from 1.0.3 to 1.1.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

openapi3-ts is a direct dependency of this project, and it is very likely causing it to break. If other packages depend on yours, this update is probably also breaking those in turn.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).

Commits

The new version differs by 4 commits.

  • e0f2a55 version 1.1.0
  • 57f828f upgrade libs
  • a372e24 Merge pull request #35 from mauriedo/fix-references-in-components-types
  • 2204058 fix references in components

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

How to send a custom response on validation error?

I'm trying to add a custom onResponseValidationError callback that sets the status to 400 and returns JSON containing validation errors.

Here is what I have so far:

const options = {
    controllers: path.resolve(__dirname, './controllers'),
    allowMissingControllers: false,
    validateDefaultResponses: true,
    onResponseValidationError: (result) => {
      console.log("There is a validation error.");
      return result.context.res.status(400).json(result.errors);
    }
  };

However I keep getting this as the response:

{
  "message": "Trying to set status after response has been ended."
}

This is my controller for the reference:

exports.getGreeting = (context) => {
  const name = context.params.query.name;
  context.res
      .status(200)
      .setBody({invalidField: `Hello ${name}`});
}

I'm puzzled how to intercept regular controller flow on validation error and send a different response.

An in-range update of semantic-release is breaking the build 🚨

The devDependency semantic-release was updated from 15.9.15 to 15.9.16.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

semantic-release is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build could not complete due to an error (Details).
  • coverage/coveralls: First build on greenkeeper/semantic-release-15.9.16 at 80.549% (Details).

Release Notes for v15.9.16

15.9.16 (2018-09-19)

Bug Fixes

  • package: update env-ci to version 3.0.0 (b9ae7d2)
Commits

The new version differs by 2 commits.

  • b9ae7d2 fix(package): update env-ci to version 3.0.0
  • c27e18e chore(package): update nock to version 10.0.0

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Automatic mock responses

Is-it possible to have an option or a plugin to automatically returns the answers provided in the swagger specification file inside a controller?

That will avoid us to create the same thing from the Swagger specification file and inside the controller.

Thanks a lot for your help.

Better support for application/x-www-form-urlencoded bodies.

Exegesis has support for x-www-form-urlencoded bodies, but if you give it a complicated schema, it will probably have a hard time finding your properties, or may get the types wrong.

You can avoid this by using a schema where all the properties are at the top level. But what really needs to happen here is, we need to replace this with something which recursively traverses all the allOf, oneOf, and anyOf keys to extract a schema for each property.

Can't pipe to context response

I use request module to stream a image from another source to context response as below

exports.getResourceByName = async context => {
  request('https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png').pipe(context.res);
};

and get this error

TypeError: dest.on is not a function
    at Request.Stream.pipe (internal/streams/legacy.js:28:8)
    at Request.pipe (D:\br24\projects\cps\node_modules\request\request.js:1488:34)
    ...

it works with res from express

app.get('/google', (req, res) => {
  request('https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png').pipe(res);
});

How to do testing of an Exegesis API

I have been building out an API which utilises Exegesis to map an OpenAPI doc to routes. I am wondering how this can be tested, given the setup of Exegesis is a little different than a normal Express API. It would be great to have some resource to reference on how testing can be done.

As a Exegesis API developer
I want a streamlined way to test my routes
So that automated testing can be done on the routes; This would allow for CI/CD testing also.

Acceptance Criteria:

Scenario 1:
Given a number of controllers in the controllers folder
And each of these routes mapped to a OpenAPI path
When it comes to testing
There should be a boilerplate that can be followed or some documentation

I can help with this but would need some guidance on where to start.

Feature request: Generating mocks responses

I implemented a controller that send mocks response using json-schema-faker v0.4.7.
myController.js

const jsf = require('json-schema-faker');
jsf.option({ optionalsProbabilty: 0.5 });
const jsonSchema = require("exegesis/lib/utils/jsonSchema");
exports.getPartenaireById = function getPartenaireById(context) {
    const path = `${context.api.operationPtr}/responses/200/content/application~1json/schema`;
    let schema = jsonSchema.extractSchema(context.api.openApiDoc, path);
    const mock = jsf(schema);

    return mock;
}

Perhaps you have a good solution to make it generic and integrate it in the core or in a plugin?

Thanks

An in-range update of supertest-fetch is breaking the build 🚨

The devDependency supertest-fetch was updated from 1.2.0 to 1.2.1.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

supertest-fetch is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build failed (Details).

Release Notes for v1.2.1

1.2.1 (2018-09-17)

Bug Fixes

  • Fix to handle case when passed in server is bound to a UNIX domain socket. (dddfd29), closes #15
Commits

The new version differs by 1 commits.

  • dddfd29 fix: Fix to handle case when passed in server is bound to a UNIX domain socket.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Authentication errors don't throw errors, instead modify the status and response body directly.

In Operation.js line 315, notice how the code sets a message in the response body directly. This makes it hard for applications to format the error response, for instance, if we need to always return RFC 7807 Problem Details for HTTP APIs.

Since the rest of Exegesis already has an HttpError type defined and even an option parameter that controls whether Exegesis should handle the conversion of these errors into HTTP error responses, shouldn't the authentication process also use this code flow?

It would avoid creating extra code in our applications specifically for 401 responses.

Response body validation does not work

exegesis-express does not validate the body of a JSON response even if response validation is enabled with the onResponseValidationError option.

Steps to reproduce

  • Install node v10.12.0 (current LTS) or newer.

  • Extract the minimal example: exegesis-example.zip

  • Run npm i && node .

  • Send a GET request against http://localhost:8080/

Expected result

The server responds with status code 500 and message Response validation failed

Actual result

The server responds with status code 200 and message { "message": {} }.

Further information

Stepping through Response.validateResponse reveals, that response validation is skipped silently (in itself arguably not correct if a response specification exists) if the body is a string, which it always seems to be, even if the controller returns an object or calls res.json.

License?

Does this project have a license it is being released under?

option for skipping content-type validation for GET request

Some client library automatically adds a "content-type: application/json" header to all requests if such header is absent, which causes the api server to return "Invalid content-type: application/json" on GET requests. Since it doesn't make much sense to have a content-type header in get requests, is it possible to add an option to not validate content type for GET?

Thanks

How should we return relevant status codes.

In the 2 samples using Exegesis. One endpoint returns a string with a 200 status code.

What would be the recommended way to return a desired status code.

Taking an example of searching a database for a value that isn't there, one might return a 404 or 400 status code to the user depending on whether they are following a REST spec.
How can we the dev, catch any potential errors and then send a 404/400/XXX status code.

I found one method which would be to take an instance of ExegesisContext as a parameter to the controller being used and calling context.res.setStatus(404) before returning any values but would this be the preferred way of doing things?

An in-range update of @types/node is breaking the build 🚨

The devDependency @types/node was updated from 12.0.1 to 12.0.2.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

@types/node is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build failed (Details).
  • coverage/coveralls: First build on greenkeeper/@types/node-12.0.2 at 84.31% (Details).

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.