Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Callbacks #716

Merged
merged 43 commits into from
Nov 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
de73c0e
feat: runtime expressions resolver
karol-maciaszek Oct 18, 2019
90df29c
Merge branch 'master' into feat/webhooks
XVincentX Oct 19, 2019
1c95e55
chore: bits of callback implementation
karol-maciaszek Oct 24, 2019
fadcd2d
Merge commit '08aeb8219150c8ed25dc22d6b75035d239222749' into feat/web…
karol-maciaszek Oct 25, 2019
24c64eb
chore: refactored forwarder
karol-maciaszek Oct 25, 2019
842fa30
chore: callback improvements
karol-maciaszek Oct 25, 2019
5b986d9
fix: linting
karol-maciaszek Oct 25, 2019
4456b8b
test: part of tests
karol-maciaszek Oct 28, 2019
aa5641d
test: mocker tests, various fixes
karol-maciaszek Oct 29, 2019
f719250
chore: improvements in runtime expressions
karol-maciaszek Oct 29, 2019
667f64d
fix: further runtimeExpressions improvements
karol-maciaszek Oct 29, 2019
fca776f
chore: more changes in runtimeExpressions
karol-maciaszek Oct 29, 2019
bb9a203
Merge branch 'master' into feat/webhooks
karol-maciaszek Oct 29, 2019
aeb8383
chore: renamed util/response to utils/parseResponse
karol-maciaszek Oct 31, 2019
7e08a94
fix: improved typing
karol-maciaszek Oct 31, 2019
8b86b96
fix: auto-generation of content-type for callback request
karol-maciaszek Nov 4, 2019
cf0a87e
fix: fixed generation of content-type header
karol-maciaszek Nov 4, 2019
3756d2f
feat: added callback example
karol-maciaszek Nov 4, 2019
63b64ea
Merge branch 'master' into feat/webhooks
XVincentX Nov 4, 2019
0071ba3
Merge branch 'master' into feat/webhooks
XVincentX Nov 4, 2019
7f3f0bd
docs: improved callbacks documentation
karol-maciaszek Nov 5, 2019
cbec5f3
docs: further docs improvements
karol-maciaszek Nov 5, 2019
61ca306
fix: test description fixes
karol-maciaszek Nov 5, 2019
9429b8a
fix: added comment to runtime expressions
karol-maciaszek Nov 5, 2019
eaed708
chore: code style
karol-maciaszek Nov 5, 2019
8e0933b
fix: improvements on runtimeExpressions
karol-maciaszek Nov 5, 2019
e7f1cf4
fix: removed left-over
karol-maciaszek Nov 5, 2019
51c1ab3
fix: adjusted callbacks to recent validator refactor
karol-maciaszek Nov 5, 2019
9c3956c
fix: typing fix
karol-maciaszek Nov 5, 2019
76d92bb
fix: type fixes
karol-maciaszek Nov 5, 2019
28975cf
fix: improved test types
karol-maciaszek Nov 5, 2019
725f188
fix: more specific types
karol-maciaszek Nov 5, 2019
631560e
feat: introduced assertResolvesLeft and assertResolvesRight helper fu…
karol-maciaszek Nov 5, 2019
94f1396
chore: better den for fp assertion helpers
karol-maciaszek Nov 5, 2019
16dd64c
fix: improved fp usage
karol-maciaszek Nov 5, 2019
1acfa79
Merge branch 'master' into feat/webhooks
XVincentX Nov 5, 2019
511605d
Improve WebHooks calling chain (#772)
XVincentX Nov 6, 2019
7e18e19
docs: added callbacks limitations section
karol-maciaszek Nov 6, 2019
708ea09
fix: param generator input update
karol-maciaszek Nov 6, 2019
a5c7555
docs: docs improvements
karol-maciaszek Nov 6, 2019
b1de84e
docs: add comment
XVincentX Nov 6, 2019
a22024a
fix: more verbose error
karol-maciaszek Nov 6, 2019
80a92db
Merge branch 'feat/webhooks' of github.com:stoplightio/prism into fea…
karol-maciaszek Nov 6, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions docs/guides/callbacks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Mocking Callbacks with Prism

## What are callbacks?
karol-maciaszek marked this conversation as resolved.
Show resolved Hide resolved

Callback in OpenApi 3 defines an outgoing, asynchronous request that your service will make to some other service. Typical real-life example is a code repository. You can subscribe to certain events on a repo (like commit or tag) and your api will start receiving notifications for those events. Another example is one-time notifications. You can subscribe to a `invoice paid` event and a callback will be invoked when such payment is processed.

##### Sources:

- [Callback Docs](https://swagger.io/docs/specification/callbacks/)
- [Callback Object Specification](https://spec.openapis.org/oas/v3.0.2#callbackObject)

## The Example

This example shows how Prism mocks callbacks. There are two services defined: `payment-service` and `client-service`. `payment-service` exposes a subscribe-to-invoice-events method. Client service defines a notification reception endpoint. Our goal is to programmatically subscribe to events about certain invoice.

### Environment setup

Start `payment-service` exposing `/subscribe` callback.

```bash
prism mock -p 4010 examples/callbacks/payment-service.oas3.yaml
```

Start `client-service` exposing `/notify` operation used for receiving callback requests.

```bash
prism mock -p 4011 examples/callbacks/client-service.oas3.yaml
```

### Run!

Subscribe to callback

```bash
curl -v -H'Content-type: application/json' -d'{ "url": "http://localhost:4011/notify", "token": "ssecurre" }' http://127.0.0.1:4010/invoices/123/subscribe
```

Now, the console for `payment-service` should contain:

```
[HTTP SERVER] post /invoices/123/subscribe ℹ info Request received
[NEGOTIATOR] ℹ info Request contains an accept header: */*
[VALIDATOR] ✔ success The request passed the validation rules. Looking for the best response
[NEGOTIATOR] ✔ success Found a compatible content for */*
[NEGOTIATOR] ✔ success Responding with the requested status code 202
[CALLBACK] ℹ info actions: Making request to http://localhost:4011/notify?token=ssecurre...
[CALLBACK] ℹ info actions: Request finished
```

The console of `client-service`:

```
[HTTP SERVER] post /notify ℹ info Request received
[NEGOTIATOR] ℹ info Request contains an accept header: */*
[VALIDATOR] ✔ success The request passed the validation rules. Looking for the best response
[NEGOTIATOR] ✔ success Found a compatible content for */*
[NEGOTIATOR] ✔ success Responding with the requested status code 200
```

After subscribing via `/subscribe`, Prism successfully invoked `/notify` callback with mocked payload.
karol-maciaszek marked this conversation as resolved.
Show resolved Hide resolved

## Limitations

- no support for `servers` and `security` definitions inside callback operation
- no support for `$url` and `$request.path.*` runtime expressions
2 changes: 1 addition & 1 deletion docs/guides/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ prism mock https://raw.githack.com/OAI/OpenAPI-Specification/master/examples/v3.
● note DELETE http://127.0.0.1:4010/pets/10
```

The generated paths will have their parameters (query or path) bolded and in color.
The generated paths will have their parameters (query or path) bolded and in color.

Then in another tab, you can hit the HTTP server with your favorite HTTP client.

Expand Down
4 changes: 4 additions & 0 deletions docs/guides/mock-responses.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,7 @@ When we call `curl http://127.0.0.1:4010/pets/123?__dynamic=true`, the operation
The more descriptive your description is, the better job Prism can do at creating a mock response.

_**Tip:** If your team needs help creating better quality API description documents, take a look at [Spectral](https://stoplight.io/spectral/). You could enforce the use of `example` properties, or similar._

### Mocking OpenAPI Callbacks

Follow this guide to learn [how to mock callbacks](./callbacks.md).
27 changes: 27 additions & 0 deletions examples/callbacks/client-service.oas3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
openapi: 3.0.0
paths:
/notify:
post:
summary: 'Receive notification about certain invoice'
security:
- Token: []
requestBody:
required: true
content:
application/json:
schema:
$ref: 'payment-service.oas3.yaml#/components/schemas/notification'
responses:
'200':
description: 'Notification successfully processed'
content:
text/plain:
examples:
ok:
value: 'ok'
components:
securitySchemes:
Token:
type: apiKey
name: token
in: query
74 changes: 74 additions & 0 deletions examples/callbacks/payment-service.oas3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
openapi: 3.0.0
paths:
/invoices/{id}/subscribe:
parameters:
- name: id
in: path
required: true
description: The id of the invoice about which you want to receive notifications
schema:
$ref: '#/components/schemas/invoiceId'
post:
summary: Subscription to events about given invoice
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
url:
type: string
format: uri
token:
type: string
required:
- url
- token
responses:
'202':
description: 'Successfully subscribed to events about given invoice'
content:
text/plain:
examples:
ok:
value: 'ok'

callbacks:
actions:
'{$request.body#/url}?token={$request.body#/token}':
post:
summary: 'Receive notification about certain invoice'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/notification'
responses:
200:
description: 'Notification successfully processed'
content:
text/plain:
examples:
ok:
value: 'ok'
components:
schemas:
invoiceId:
type: integer
format: int64
notification:
type: object
properties:
invoiceId:
$ref: "#/components/schemas/invoiceId"
action:
type: string
enum:
- paid
- expired
- failure
required:
- invoiceId
- action
1 change: 0 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
},
"bugs": "https://github.com/stoplightio/prism/issues",
"dependencies": {
"@stoplight/http-spec": "^2.2.3",
"@stoplight/prism-core": "^3.1.1",
"@stoplight/prism-http-server": "^3.1.1",
"chokidar": "^3.2.1",
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/const/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export const LOG_COLOR_MAP = {
'HTTP SERVER': { index: 0, color: chalk.bgYellowBright },
NEGOTIATOR: { index: 1, color: chalk.bgCyanBright },
VALIDATOR: { index: 1, color: chalk.bgGreenBright },
CALLBACK: { index: 1, color: chalk.bgBlue },
};
25 changes: 10 additions & 15 deletions packages/cli/src/util/__tests__/colorizer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ import {
PRE_PARAM_VALUE_TAG,
POST_PARAM_VALUE_TAG,
attachTagsToParamsValues,
transformPathParamsValues
transformPathParamsValues,
} from '../colorizer';

describe('colorizer', () => {

describe('transformPathParamsValues()', () => {
it('colorizes tagged values of query params', () => {
const path = `/no_auth/pets/findByStatus?status=${PRE_PARAM_VALUE_TAG}sold,pending${POST_PARAM_VALUE_TAG}`;

expect(transformPathParamsValues(path, chalk.bold.blue)).toBe(`/no_auth/pets/findByStatus?status=${chalk.bold.blue('sold,pending')}`);
expect(transformPathParamsValues(path, chalk.bold.blue)).toBe(
`/no_auth/pets/findByStatus?status=${chalk.bold.blue('sold,pending')}`
);
});

it('colorizes tagged values of path params', () => {
Expand All @@ -23,30 +24,25 @@ describe('colorizer', () => {
});

describe('attachTagsToParamsValues()', () => {

describe('adding tags', () => {
it('tags multiple values', () => {
const values = {
status: [
'available',
'pending',
'sold'
]
status: ['available', 'pending', 'sold'],
};

expect(attachTagsToParamsValues(values)).toStrictEqual({
status: [
`${PRE_PARAM_VALUE_TAG}available${POST_PARAM_VALUE_TAG}`,
`${PRE_PARAM_VALUE_TAG}pending${POST_PARAM_VALUE_TAG}`,
`${PRE_PARAM_VALUE_TAG}sold${POST_PARAM_VALUE_TAG}`
]
`${PRE_PARAM_VALUE_TAG}sold${POST_PARAM_VALUE_TAG}`,
],
});
});

describe('tagging single values', () => {
it('tags string values', () => {
const valuesOfParams = {
name: 'dignissimos'
name: 'dignissimos',
};

expect(attachTagsToParamsValues(valuesOfParams)).toStrictEqual({
Expand All @@ -56,11 +52,11 @@ describe('colorizer', () => {

it('tags numeric values', () => {
const valuesOfParams = {
petId: 170
petId: 170,
};

expect(attachTagsToParamsValues(valuesOfParams)).toStrictEqual({
petId: `${PRE_PARAM_VALUE_TAG}170${POST_PARAM_VALUE_TAG}`
petId: `${PRE_PARAM_VALUE_TAG}170${POST_PARAM_VALUE_TAG}`,
});
});
});
Expand All @@ -71,6 +67,5 @@ describe('colorizer', () => {

expect(attachTagsToParamsValues(values)).toStrictEqual({});
});

});
});
2 changes: 1 addition & 1 deletion packages/cli/src/util/__tests__/paths.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpParamStyles } from '@stoplight/types';
import { createExamplePath } from '../paths';
import { assertRight, assertLeft } from '@stoplight/prism-core/src/utils/__tests__/utils';
import { assertRight, assertLeft } from '@stoplight/prism-core/src/__tests__/utils';

describe('createExamplePath()', () => {
describe('path parameters', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as Option from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';
import * as Either from 'fp-ts/lib/Either';
import * as TaskEither from 'fp-ts/lib/TaskEither';

export function assertNone<A>(e: Option.Option<A>) {
pipe(
Expand All @@ -9,34 +10,42 @@ export function assertNone<A>(e: Option.Option<A>) {
() => ({}),
a => {
throw new Error('None expected, got a Some: ' + a);
},
),
}
)
);
}

export function assertSome<A>(e: Option.Option<A>, onSome: (a: A) => void = () => { }) {
export function assertSome<A>(e: Option.Option<A>, onSome: (a: A) => void = () => {}) {
pipe(
e,
Option.fold(() => {
throw new Error('Some expected, got a None');
}, onSome),
}, onSome)
);
}

export function assertRight<L, A>(e: Either.Either<L, A>, onRight: (a: A) => void = () => { }) {
export function assertRight<L, A>(e: Either.Either<L, A>, onRight: (a: A) => void = () => {}) {
pipe(
e,
Either.fold(l => {
throw new Error('Right expected, got a Left: ' + l);
}, onRight),
}, onRight)
);
}

export function assertLeft<L, A>(e: Either.Either<L, A>, onLeft: (a: L) => void = () => { }) {
export function assertLeft<L, A>(e: Either.Either<L, A>, onLeft: (a: L) => void = () => {}) {
pipe(
e,
Either.fold(onLeft, a => {
throw new Error('Left expected, got a Right: ' + a);
}),
})
);
}

export async function assertResolvesRight<L, A>(e: TaskEither.TaskEither<L, A>, onRight: (a: A) => void = () => {}) {
assertRight(await e(), onRight);
}

export async function assertResolvesLeft<L, A>(e: TaskEither.TaskEither<L, A>, onLeft: (a: L) => void = () => {}) {
assertLeft(await e(), onLeft);
}
karol-maciaszek marked this conversation as resolved.
Show resolved Hide resolved
Loading