Skip to content

Commit

Permalink
Proxy command (stoplightio#669)
Browse files Browse the repository at this point in the history
* chore: add path

* refactor: output is always defined

* refactor: remove early return

* feat: restore forwarder

* feat: restore the forwarder in a better version

* test: remove tests we have no idea what we're doing

* refactor: use TaskEither.left

* refactor: use uritemplate instead of homemade code

* refactor: pull shared options in a different file

* feat: add proxy basic command

* feat: create and handle the upstream option

* feat: add logging option

* refactor: forward the whole options object

* refactor: reorganise create options

* refactor: defaults are not needed here anymore

* fix: do not need to check for resource presence

* fix: let axios handle the host

* feat: types and stuff

* refactor: log option is effectively shareable

* feat: add types

* feat: export router and mocker errors

* feat: print out responses

* test: fix

* refactor: use the promise for intermediate handler

* fix: correctly stringify the validation errors

* docs: add

* refactor: ignore server and documents

* refactor: output violation

* docs: add

* refactor: it's a document, not a spec

* Apply suggestions from code review

Co-Authored-By: Phil Sturgeon <[email protected]>

* docs: checkbox

* chore: depdendenceis are good enough

* chore: package upgrade

* style: less code

* refactor: prefer single statement returns

* refactor: changes

* refactor: log in client does not make any sense

* refactor: security directory

* chore: content negotiation directory

* chore: default responses directory

* test: add

* chore: better print

* fix: do not use resolve for remote resources

* docs: changelog

* test: rename file

* test: generalize command

* refactor: always return a Promise

* refactor: simplify TypeScript's job

* test: expand for proxy command as well

* test: remove todo

* refactor: remove default template params

* test: change text

* refactor: share object

* test: add

* Update docs/guides/cli.md

Co-Authored-By: lag-of-death <[email protected]>

* chore: move dist upstair

* Update packages/http/src/mocker/generator/__tests__/HttpParamGenerator.spec.ts

* Update docs/guides/cli.md

Co-Authored-By: lag-of-death <[email protected]>

* feat: replace with node-fetch

* chore: move prettier rc out

* docs: clarify changelog line

* chore: update sample urls

* test: fix

* style: eslint prettier fixes

* chore: add docs on the limitations

* Apply suggestions from code review

Co-Authored-By: Karol Maciaszek <[email protected]>

* test: no mock is needed

* feat: passthrough for proxy if payload is string

* fix: use all fields

* test: add port test

* test: run httpbin container

* feat: add support for

* test: add passthrough html

* test: add header validation

* docs: add limitation

* refator: move operation parsing in internal functions

* test: update

* Apply suggestions from code review

Co-Authored-By: Karol Maciaszek <[email protected]>

* fix: address comments

* refactor: convert with async/await

* refactor: sl-validation

* chore: axios is no longer required

* fix: add dependency

* docs: contributing and proxy tweaks

* Update packages/cli/src/commands/proxy.ts

Co-Authored-By: Phil Sturgeon <[email protected]>

* Update docs/guides/cli.md

Co-Authored-By: Phil Sturgeon <[email protected]>

* chore: restore

* refactor: simplify replace expression

* refactor: use standard warning header

* refactor: messages only in header error

* refactor: httpBody and reportViolations param name

* refactor: use implicit async

* Update docs/guides/cli.md

Co-Authored-By: Karol Maciaszek <[email protected]>

* Update test-harness/specs/violations/print-as-http-response.txt

Co-Authored-By: Karol Maciaszek <[email protected]>

* Update packages/cli/src/util/createServer.ts

Co-Authored-By: Karol Maciaszek <[email protected]>

* deps: do not pin http spec

* test: dry with only tested properties

* test: change text

* Apply suggestions from code review

Co-Authored-By: Phil Sturgeon <[email protected]>

* refactor: --errors

* test: update and fix

* feat: report input errors and only errors

* docs: update

* refactor: back to sl-violations

* docs: add comment

* fix: print all violations

* test: add no block for warnings test

* chore: fix error message sent to the client

* feat: add request/response origin

* docs: show a returned error

* docs: it's json

* docs: clarify

* refactor: path for us are always strings

* docs: command clarification

* test: move empty response in their own directory

* chore: remove

* Update docs/guides/cli.md

Co-Authored-By: Phil Sturgeon <[email protected]>

* refactor: violation is 500

* docs: add docs for violations error

* docs: update
  • Loading branch information
XVincentX authored Oct 24, 2019
1 parent e7966df commit 3e5ac60
Show file tree
Hide file tree
Showing 165 changed files with 877 additions and 508 deletions.
14 changes: 6 additions & 8 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ executors:
docker-node:
docker:
- image: circleci/node:12
docker-harness:
docker:
- image: circleci/node:12
- image: kennethreitz/httpbin
name: httpbin
macos:
macos:
xcode: 10.2.1
Expand Down Expand Up @@ -99,7 +104,7 @@ workflows:
tags:
only: /.*/
- harness:
executor: docker-node
executor: docker-harness
filters:
tags:
only: /.*/
Expand All @@ -120,9 +125,6 @@ workflows:
tags:
only: /^v.*/
requires:
- build
- harness
- harness-macos
- publish
- stoplight/analyze:
filters:
Expand All @@ -131,8 +133,4 @@ workflows:
tags:
only: /^v.*/
requires:
- build
- harness
- harness-macos
- publish
- upload_artifacts
9 changes: 5 additions & 4 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
**/.circleci
**/.github
**/dist
**/docs
**/examples
**/node_modules
**/__tests__
**/examples
**/test-harness
**/.tsbuildinfo
**/dist
**/.circleci
**/.github
2 changes: 1 addition & 1 deletion .github/workflows/markdown-links.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ jobs:
- uses: actions/checkout@master
with:
fetch-depth: 1
- uses: gaurav-nelson/github-action-markdown-link-check@master
- uses: gaurav-nelson/github-action-markdown-link-check@0.4.0
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Prism reloads itself every time there are changes being made to the specified document [#689](https://github.com/stoplightio/prism/pull/689)
- Path parameters are now validated against schema [#702](https://github.com/stoplightio/prism/pull/702)
- The Test Harness framework now requires the `${document}` parameter explicitly [#720](https://github.com/stoplightio/prism/pull/720)
- Prism now includes a new `proxy` command that will validate the request coming in, send the request to an upstream server and then validate the response coming back [#669](https://github.com/stoplightio/prism/pull/669)

## Fixed

- Killing sub-process only if Prism is running in multi-process mode [#645](https://github.com/stoplightio/prism/pull/645)
- UUIDs are never generated as URNs [#661](https://github.com/stoplightio/prism/pull/661)
- Relative references for remote documents are now resolved correctly [#669](https://github.com/stoplightio/prism/pull/669)
- Core types are now correctly referenced in the HTTP package, restoring the type checks when using the package separately [#701](https://github.com/stoplightio/prism/pull/701)
- By upgrading Json Schema Faker to the latest version, now the schemas with `additionalProperties:false` / `additionalProperties:true` / `additionalProperties:object` will be correctly handled when dynamic mocking is enabled [#719](https://github.com/stoplightio/prism/pull/719)
- Making a request to an operation with a `deprecated` parameter is no longer causing Prism to return a 422 response [#721](https://github.com/stoplightio/prism/pull/721)
Expand Down
38 changes: 0 additions & 38 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,44 +119,6 @@ rm -rf packages/**/dist
rm -rf packages/**/*.tsbuildinfo
```

…then try again. If it does not work, you can generally blame [Vincenzo](https://github.com/XVincentX) and write him.

## Creating an issue

We want to keep issues in this repo focused on bug reports and feature requests.

For support questions, please use the [Stoplight Community forum](https://community.stoplight.io/c/open-source). If you are unsure if you are experiencing a bug, the [forum](https://community.stoplight.io/c/open-source) is a great place to start.

Before you open an issue, try to see if anyone else has already opened an issue that might be similar to your issue or feature request. Start by commenting there to see if you are having the same issue or feature request.

We have an issue template setup:

```
### **I'm submitting a...**
- bug report
- feature request
### What is the current behavior?
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem.
### What is the expected behavior?
### What is the motivation / use case for changing the behavior?
### Please tell us about your environment:
- Version: 2.0.0-beta.X
- Framework: [ ]
- Language: [all | TypeScript X.X | ES6/7 | ES5 | Dart]
### Other information
(e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, issues outside of the repo, forum, etc.)
```

We realize there is a lot of data requested here. We ask only that you do your best to provide as much information as possible so we can better help you.

## Support

For support questions, please use the [Stoplight Community forum](https://community.stoplight.io/c/open-source). If you are unsure if you are experiencing a bug, the [forum](https://community.stoplight.io/c/open-source) is a great place to start.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ After [installation](https://stoplight.io/p/docs/gh/stoplightio/prism/docs/getti
- [x] Content Negotiation
- [x] Security Validation
- [ ] Custom Mocking
- [ ] Validation Proxy
- [x] Validation Proxy
- [ ] Recording / "Learning" mode to create spec files
- [ ] Data Persistence (allow Prism act like a sandbox)

Expand Down
76 changes: 69 additions & 7 deletions docs/guides/cli.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Prism CLI

Prism CLI for now only has one command: `mock`.
Prism CLI has two commands: `mock` and `proxy`.

## Mock server

Expand All @@ -11,8 +11,8 @@ prism mock https://raw.githack.com/OAI/OpenAPI-Specification/master/examples/v3.
✔ success Prism is listening on http://127.0.0.1:4010
● note GET http://127.0.0.1:4010/pets
● note POST http://127.0.0.1:4010/pets
● note GET http://127.0.0.1:4010/pets/{id}
● note DELETE http://127.0.0.1:4010/pets/{id}
● note GET http://127.0.0.1:4010/pets/10
● note DELETE http://127.0.0.1:4010/pets/10
```

Then in another tab, you can hit the HTTP server with your favorite HTTP client.
Expand Down Expand Up @@ -42,12 +42,13 @@ When they happen, Prism restarts its HTTP server to reflect changes to operation
There is no need to manually stop and start a Prism server after a change to a specification file.

In case of removing all of the operations in a document, Prism will not be reloaded.
In such a case, Prism will keep serving operations loaded with the previous restart.
In such a case, Prism will keep serving operations loaded with the previous restart.

## Determine Response Status
## Modifying Responses

Prism can be forced to return different HTTP responses by specifying the status code in the query
string:
#### Force Response Status

Prism can be forced to return different HTTP responses by specifying the status code in the query string:

```bash
curl -v http://127.0.0.1:4010/pets/123?__code=404
Expand All @@ -61,6 +62,67 @@ Connection: keep-alive

The body, headers, etc. for this response will be taken from the API description document.

#### Request Specific Examples

You can request a specific example from your document by using the `__example` query string parameter.

```bash
curl -v http://127.0.0.1:4010/pets/123?__example=exampleKey
```

#### Dynamic Response

You can override the `--dynamic|-d` CLI param (which decides whether the generated example is static or dynamic) through the `__dynamic` query string parameter.

```bash
curl -v http://127.0.0.1:4010/pets/123?__dynamic=false
```

## Proxy

This command creates an HTTP server that will proxy all the requests to the specified upstream server. Prism will analyze the request coming in and the response coming back from the upstream server and report the discrepancies with what's declared in the provided OpenAPI document.

**Note:** Currently the proxy command _expects_ a JSON Payload for both the request and the response. If the response's `content-type` header does not indicate a JSON-ish payload (`application/json`, `application/*+json`), then the payload will be just returned as text and send back to the original client.

```
prism proxy examples/petstore.oas2.yaml https://petstore.swagger.io/v2
[CLI] … awaiting Starting Prism…
[HTTP SERVER] ℹ info Server listening at http://127.0.0.1:4010
[CLI] ● note GET http://127.0.0.1:4010/pets
[CLI] ● note POST http://127.0.0.1:4010/pets
[CLI] ● note GET http://127.0.0.1:4010/pets/10
```

The output violations will be reported on the standard output and as a response header (`sl-violations`)

```bash
prism proxy examples/petstore.oas2.yaml https://petstore.swagger.io/v2
… …

curl -v -s http://localhost:4010/pet/10 > /dev/null

< sl-violations: [{"location":["request"],"severity":"Error","code":401,"message":"Invalid security scheme used"}]
```

You can see there's a `sl-violations` header which is a JSON object with all the violations found in the response.

The header is a handy way to see contract mismatches or incorrect usage in a way that doesn't block the client, so you can monitor all/some production traffic this way record the problems. If you want Prism to make violations considerably more clear, run the proxy command with the `--errors` flag. This will turn any request or response violation into a [RFC 7807 HTTP Problem Details Error](https://tools.ietf.org/html/rfc7807) just like validation errors on the mock server.

```bash
prism proxy examples/petstore.oas2.yaml https://petstore.swagger.io/v2 --errors
… …

curl -v -X POST http://localhost:4010/pet/

< HTTP/1.1 422 Unprocessable Entity
{"type":"https://stoplight.io/prism/errors#UNPROCESSABLE_ENTITY","title":"Invalid request body payload","status":422,"detail":"Your request/response is not valid and the --errors flag is set, so Prism is generating this error for you.","validation":[{"location":["request"],"severity":"Error","code":401,"message":"Invalid security scheme used"}]}
```

The response body contains the found output violations.

**Note:** Server definitions (OAS3) and Host + BasePath (OAS2) are ignored. You need to manually specify the upstream URL when invoking Prism.

## Running in Production

When running in development mode (which happens when the `NODE_ENV` environment variable is not set to `production`) or the `-m` flag is set to false, both the HTTP Server and the CLI (which is responsible of parsing and showing the received logs on the screen) will run within the same process.
Expand Down
6 changes: 6 additions & 0 deletions docs/guides/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ paths:
**Returned Status Code: `404`**
**Explanation:** This error occurs when the current request is asking for a specific status code that the document is not listing or it's asking for a specific example that does not exist in the current document

### VIOLATIONS

**Message: Request/Response not valid**
**Returned Status Code: `500`**
**Explanation:** This error occurs when you're run Prism with the `--errors` flag and the request or the response has at least one violation marked as an error

---

## Security errors
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,12 @@
"@types/jest": "^24.0.18",
"@types/json-schema": "^7.0.3",
"@types/lodash": "^4.14.144",
"@types/nock": "^11.1.0",
"@types/node": "^12.7.12",
"@types/node-fetch": "2.5.2",
"@types/pino": "^5.8.11",
"@types/signale": "^1.2.1",
"@types/split2": "^2.1.6",
"@types/type-is": "^1.6.3",
"@types/urijs": "^1.19.4",
"@typescript-eslint/eslint-plugin": "^2.3.3",
"@typescript-eslint/parser": "^2.3.3",
"abstract-logging": "^1.0.0",
Expand Down
1 change: 0 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"@stoplight/http-spec": "^2.2.3",
"@stoplight/prism-core": "^3.1.1",
"@stoplight/prism-http-server": "^3.1.1",
"axios": "^0.19.0",
"chokidar": "^3.2.1",
"fp-ts": "^2.0.5",
"signale": "^1.4.0",
Expand Down
73 changes: 73 additions & 0 deletions packages/cli/src/commands/__tests__/commands.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as prismHttp from '@stoplight/prism-http';
import * as yargs from 'yargs';
import { createMultiProcessPrism, createSingleProcessPrism } from '../../util/createServer';
import mockCommand from '../mock';
import proxyCommand from '../proxy';

const parser = yargs.command(mockCommand).command(proxyCommand);

jest.mock('../../util/createServer', () => ({
createMultiProcessPrism: jest.fn().mockResolvedValue([]),
createSingleProcessPrism: jest.fn().mockResolvedValue([]),
}));

jest.spyOn(prismHttp, 'getHttpOperationsFromResource').mockResolvedValue([]);

describe.each<{ 0: string; 1: string; 2: unknown }>([
['mock', '', { dynamic: false }],
['proxy', 'http://github.com', { upstream: new URL('http://github.com/') }],
])('%s command', (command, upstream) => {
beforeEach(() => {
(createSingleProcessPrism as jest.Mock).mockClear();
(createMultiProcessPrism as jest.Mock).mockClear();
});

test(`starts ${command} server`, () => {
parser.parse(`${command} /path/to ${upstream}`);

expect(createMultiProcessPrism).not.toHaveBeenCalled();
expect(createSingleProcessPrism).toHaveBeenLastCalledWith(
expect.objectContaining({
document: '/path/to',
multiprocess: false,
errors: false,
})
);
});

test(`starts ${command} server on custom port`, () => {
parser.parse(`${command} /path/to -p 666 ${upstream}`);

expect(createMultiProcessPrism).not.toHaveBeenCalled();
expect(createSingleProcessPrism).toHaveBeenLastCalledWith(expect.objectContaining({ port: 666 }));
});

test(`starts ${command} server on custom host`, () => {
parser.parse(`${command} /path/to -h 0.0.0.0 ${upstream}`);

expect(createMultiProcessPrism).not.toHaveBeenCalled();
expect(createSingleProcessPrism).toHaveBeenLastCalledWith(expect.objectContaining({ host: '0.0.0.0' }));
});

test(`starts ${command} server on custom host and port`, () => {
parser.parse(`${command} /path/to -p 666 -h 0.0.0.0 ${upstream}`);

expect(createMultiProcessPrism).not.toHaveBeenCalled();
expect(createSingleProcessPrism).toHaveBeenLastCalledWith(expect.objectContaining({ port: 666, host: '0.0.0.0' }));
});

test(`starts ${command} server with multiprocess option `, () => {
parser.parse(`${command} /path/to -m -h 0.0.0.0 ${upstream}`);

expect(createSingleProcessPrism).not.toHaveBeenCalled();
expect(createMultiProcessPrism).toHaveBeenLastCalledWith(
expect.objectContaining({ multiprocess: true, host: '0.0.0.0' })
);
});

test(`starts ${command} server with error violations option on `, () => {
parser.parse(`${command} /path/to -m -h 0.0.0.0 --errors ${upstream}`);

expect(createMultiProcessPrism).toHaveBeenLastCalledWith(expect.objectContaining({ errors: true }));
});
});
Loading

0 comments on commit 3e5ac60

Please sign in to comment.