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

refactor: move createFromString and resource from Prism Http to CLI #1009

Merged
merged 30 commits into from
May 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c4bf34e
refactor: move createFromString and resource to CLI
XVincentX Feb 27, 2020
eb48cc7
fix: remove import
XVincentX Feb 27, 2020
b709659
docs: clarify the movement
XVincentX Feb 27, 2020
6985bd5
test: fix
XVincentX Feb 27, 2020
3f1d04b
chore: move package to cli
XVincentX Feb 27, 2020
6d3ec45
docs: update
XVincentX Feb 28, 2020
2cfd601
fix: correct the deps
XVincentX Feb 28, 2020
098256c
Apply suggestions from code review
XVincentX Feb 28, 2020
71e926d
refactor: rename function name
XVincentX Feb 28, 2020
70d5e7a
refacotr: rename file
XVincentX Feb 28, 2020
805af10
refactor: renaming completion
XVincentX Feb 28, 2020
bf3a6cd
Merge branch 'master' into feat/adios-fromSpec
XVincentX Mar 13, 2020
d8c3608
canary
XVincentX Mar 13, 2020
ced29e3
call it canary
XVincentX Mar 13, 2020
dcb3f78
test: rename
XVincentX Mar 13, 2020
0e31097
Merge branch 'master' into feat/adios-fromSpec
XVincentX May 7, 2020
f8510c0
move http spec away
XVincentX May 7, 2020
6f488e4
remove refs
XVincentX May 7, 2020
1866fa7
import only us locale
XVincentX May 7, 2020
071b9c5
type is import revisit
XVincentX May 7, 2020
44e93c0
prefer types
XVincentX May 7, 2020
08d82f4
change stuff
XVincentX May 7, 2020
168cb80
add back deps
XVincentX May 7, 2020
90d60b2
call beta again
XVincentX May 7, 2020
4bc6e85
move test file
XVincentX May 7, 2020
133696c
fix import
XVincentX May 7, 2020
f68f30c
rename typeis usage
XVincentX May 8, 2020
e9629f2
reword docs
XVincentX May 8, 2020
f597c6c
default export
XVincentX May 8, 2020
5a9870b
docs: changelog
XVincentX May 8, 2020
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

# Unreleased

## Changed

- **BREAKING**: The `getHttpOperationsFromSpec` and `getHttpOperationsFromResource` have been moved from the HTTP Package to the CLI package. If you're using Prism programmatically, this might require some code changes on your side [#1009](https://github.com/stoplightio/prism/pull/1009)
- **BREAKING**: The `createClientFromOperations` is now exported as `export function` instead of exporting an object. If you're using Prism programmatically, this might require some code changes on your side [#1009](https://github.com/stoplightio/prism/pull/1009)

# 3.3.4 (2020-05-04)

## Fixed
Expand Down
50 changes: 22 additions & 28 deletions docs/guides/http-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,33 @@

Prism includes a fully-featured HTTP Client that you can use to seamlessly perform requests to both a real server and a mocked document. The client is modeled after Axios so it may feel familiar.

### Create from a filename or http resource
### Create From Manual HTTP Operations

```ts
const client = await createClientFromResource('examples/petstore.oas2.yaml', {
mock: true,
validateRequest: true,
validateResponse: true,
});
const createClientFromOperations = require('@stoplight/prism-http/dist/client');

const client = createClientFromOperations(
[
{
method: 'get',
path: '/hello',
id: 'n1',
responses: [{ code: '200' }],
},
],
{ mock: true, validateRequest: true, validateResponse: true }
);
```

### Create from OpenAPI string
To create the required operations array you can use two utility functions defined in the `@stoplight/prism-cli` package:

### Create from file or HTTP resource

```ts
const { getHttpOperationsFromSpec, getHttpOperationsFromResource } = require('@stoplight/prism-cli/dist/operations');

const operations = await getHttpOperationsFromResource('examples/petstore.oas2.yaml');

const descriptionDoc = `
openapi: 3.0.2
paths:
Expand All @@ -25,27 +39,7 @@ paths:
description: hello
`;

const client = await createClientFromString(descriptionDoc, {
mock: true,
validateRequest: true,
validateResponse: true,
});
```

### Create From Manual Http Operations

```ts
const client = createClientFromOperations(
[
{
method: 'get',
path: '/hello',
id: 'n1',
responses: [{ code: '200' }],
},
],
{ mock: true, validateRequest: true, validateResponse: true }
);
const operations = await getHttpOperationsFromSpec(descriptionDoc);
```

---
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"posttest": "yarn lint",
"test": "jest",
"release": "lerna version",
"release:beta": "lerna publish --preid beta --no-push --no-git-tag-version --dist-tag beta",
"prerelease:beta": "yarn build",
"release:beta": "lerna publish --preid beta --force-publish=* --no-push --no-git-tag-version --dist-tag beta",
"prebuild.binary": "yarn build",
"build.binary": "npx pkg --output ./cli-binaries/prism-cli ./packages/cli/",
"test.harness": "jest -c ./jest.harness.config.js"
Expand Down
6 changes: 6 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
},
"bugs": "https://github.com/stoplightio/prism/issues",
"dependencies": {
"@stoplight/http-spec": "^2.12.0",
"@stoplight/json": "^3.1.2",
"@stoplight/json-ref-readers": "^1.1.1",
"@stoplight/json-ref-resolver": "^3.0.1",
"@stoplight/yaml": "^3.0.2",
"node-fetch": "^2.6.0",
"@stoplight/prism-core": "^3.3.4",
"@stoplight/prism-http": "^3.3.4",
"@stoplight/prism-http-server": "^3.3.4",
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/__tests__/commands.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as prismHttp from '@stoplight/prism-http';
import * as operationUtils from '../../operations';
import * as yargs from 'yargs';
import { createMultiProcessPrism, createSingleProcessPrism } from '../../util/createServer';
import mockCommand from '../mock';
Expand All @@ -11,7 +11,7 @@ jest.mock('../../util/createServer', () => ({
createSingleProcessPrism: jest.fn().mockResolvedValue([]),
}));

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

describe.each<{ 0: string; 1: string; 2: unknown }>([
['mock', '', { dynamic: false }],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ export async function getHttpOperationsFromResource(file: string): Promise<IHttp
? fetch(file).then(d => d.text())
: fs.promises.readFile(file, { encoding: 'utf8' }));

return getHttpOperations(fileContent, isRemote ? file : resolve(file));
return getHttpOperationsFromSpec(fileContent, isRemote ? file : resolve(file));
}

export default async function getHttpOperations(specContent: string, baseUri?: string): Promise<IHttpOperation[]> {
export async function getHttpOperationsFromSpec(specContent: string, baseUri?: string): Promise<IHttpOperation[]> {
const parsedContent = parse(specContent);
const { result: resolvedContent, errors } = await httpAndFileResolver.resolve(parsedContent, { baseUri });

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import getHttpOperations from '../getHttpOperations';
import { getHttpOperationsFromSpec } from '../../operations';

describe('getHttpOperations()', () => {
describe('getHttpOperationsFromSpec()', () => {
describe('ref resolving fails', () => {
it('fails with exception', () => {
return expect(
getHttpOperations(
getHttpOperationsFromSpec(
JSON.stringify({
openapi: '3.0.0',
paths: { $ref: 'abc://' },
Expand All @@ -17,7 +17,7 @@ describe('getHttpOperations()', () => {

it('deduplicates similar errors', () => {
return expect(
getHttpOperations(
getHttpOperationsFromSpec(
JSON.stringify({
openapi: '3.0.0',
paths: { $ref: 'abc://' },
Expand All @@ -33,18 +33,18 @@ describe('getHttpOperations()', () => {
describe('ref resolving succeeds', () => {
describe('OpenAPI 2 document is provided', () => {
it('detects it properly', () => {
return expect(getHttpOperations(JSON.stringify({ swagger: '2.0' }))).resolves.toBeTruthy();
return expect(getHttpOperationsFromSpec(JSON.stringify({ swagger: '2.0' }))).resolves.toBeTruthy();
});
});

describe('OpenAPI 3 document is provided', () => {
it('detects it properly', () => {
return expect(getHttpOperations(JSON.stringify({ openapi: '3.0.0' }))).resolves.toBeTruthy();
return expect(getHttpOperationsFromSpec(JSON.stringify({ openapi: '3.0.0' }))).resolves.toBeTruthy();
});

it('returns correct HttpOperation', () => {
return expect(
getHttpOperations(
getHttpOperationsFromSpec(
JSON.stringify({
openapi: '3.0.0',
paths: {
Expand All @@ -71,13 +71,15 @@ describe('getHttpOperations()', () => {

describe('Postman Collection document is provided', () => {
it('detects it properly', () => {
return expect(getHttpOperations(JSON.stringify({ info: { name: 'Test' }, item: [] }))).resolves.toBeTruthy();
return expect(
getHttpOperationsFromSpec(JSON.stringify({ info: { name: 'Test' }, item: [] }))
).resolves.toBeTruthy();
});
});

describe('unknown document is provided', () => {
it('throws error', () => {
return expect(getHttpOperations(JSON.stringify({}))).rejects.toThrow(/^Unsupported document format$/);
return expect(getHttpOperationsFromSpec(JSON.stringify({}))).rejects.toThrow(/^Unsupported document format$/);
});
});
});
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/util/createServer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createLogger } from '@stoplight/prism-core';
import { IHttpConfig, IHttpProxyConfig, getHttpOperationsFromResource } from '@stoplight/prism-http';
import { IHttpConfig, IHttpProxyConfig } from '@stoplight/prism-http';
import { createServer as createHttpServer } from '@stoplight/prism-http-server';
import * as chalk from 'chalk';
import * as cluster from 'cluster';
Expand All @@ -13,6 +13,7 @@ import { LOG_COLOR_MAP } from '../const/options';
import { createExamplePath } from './paths';
import { attachTagsToParamsValues, transformPathParamsValues } from './colorizer';
import { CreatePrism } from './runner';
import { getHttpOperationsFromResource } from '../operations';

signale.config({ displayTimestamp: true });

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/util/runner.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { getHttpOperationsFromResource } from '@stoplight/prism-http';
import { IPrismHttpServer } from '@stoplight/prism-http-server/src/types';
import * as chokidar from 'chokidar';
import * as os from 'os';
import { CreateMockServerOptions } from './createServer';
import { getHttpOperationsFromResource } from '../operations';

export type CreatePrism = (options: CreateMockServerOptions) => Promise<IPrismHttpServer | void>;

Expand Down
3 changes: 2 additions & 1 deletion packages/http-server/src/__tests__/server.oas.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createLogger } from '@stoplight/prism-core';
import { getHttpOperationsFromResource, IHttpConfig } from '@stoplight/prism-http';
import { getHttpOperationsFromResource } from '@stoplight/prism-cli/src/operations';
import { IHttpConfig } from '@stoplight/prism-http';
import { resolve } from 'path';
import { merge } from 'lodash';
import fetch, { RequestInit } from 'node-fetch';
Expand Down
8 changes: 4 additions & 4 deletions packages/http-server/src/serialize.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { j2xParser } from 'fast-xml-parser';
import typeIs = require('type-is');
import { is as typeIs } from 'type-is';

const xmlSerializer = new j2xParser({});

const serializers = [
{
test: (value: string) => !!typeIs.is(value, ['application/json', 'application/*+json']),
test: (value: string) => !!typeIs(value, ['application/json', 'application/*+json']),
serializer: JSON.stringify,
},
{
test: (value: string) => !!typeIs.is(value, ['application/xml', 'application/*+xml']),
test: (value: string) => !!typeIs(value, ['application/xml', 'application/*+xml']),
serializer: (data: unknown) => (typeof data === 'string' ? data : xmlSerializer.parse({ xml: data })),
},
{
test: (value: string) => !!typeIs.is(value, ['text/*']),
test: (value: string) => !!typeIs(value, ['text/*']),
serializer: (data: unknown) => {
if (['string', 'undefined'].includes(typeof data)) {
return data;
Expand Down
5 changes: 1 addition & 4 deletions packages/http/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@
"node": ">=8"
},
"dependencies": {
"@stoplight/http-spec": "2.11.0",
"@stoplight/json": "^3.1.2",
"@stoplight/json-ref-readers": "^1.1.1",
"@stoplight/json-ref-resolver": "^3.0.1",
"@stoplight/json": "^3.7.1",
"@stoplight/prism-core": "^3.3.4",
"@stoplight/types": "^11.6.0",
"@stoplight/yaml": "^3.0.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/http/src/__tests__/http-prism-instance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Scope as NockScope } from 'nock';
import * as nock from 'nock';
import { basename, resolve } from 'path';
import { createInstance, IHttpProxyConfig, IHttpRequest, IHttpResponse, ProblemJsonError } from '../';
import { getHttpOperationsFromResource } from '../getHttpOperations';
import { getHttpOperationsFromResource } from '@stoplight/prism-cli/src/operations';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is /src/ in the bundle? Should it?

In the example (README.md) you are importing it from /dist/ which looks more appropriate.

This applies to ~10 other places as well.

import { UNPROCESSABLE_ENTITY } from '../mocker/errors';
import { NO_PATH_MATCHED_ERROR, NO_SERVER_MATCHED_ERROR } from '../router/errors';
import { assertResolvesRight, assertResolvesLeft } from '@stoplight/prism-core/src/__tests__/utils';
Expand Down
18 changes: 2 additions & 16 deletions packages/http/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { IPrismOutput } from '@stoplight/prism-core';
import { IHttpOperation } from '@stoplight/types';
import { defaults, partial } from 'lodash';
import { defaults } from 'lodash';
import { parse as parseQueryString } from 'querystring';
import { parse as parseUrl } from 'url';
import { createInstance } from '.';
import getHttpOperations, { getHttpOperationsFromResource } from './getHttpOperations';
import { IHttpConfig, IHttpRequest, IHttpResponse, IHttpUrl } from './types';
import { fold } from 'fp-ts/lib/TaskEither';
import * as Task from 'fp-ts/lib/Task';
Expand All @@ -18,18 +17,7 @@ interface IClientConfig extends IHttpConfig {
baseUrl?: string;
}

function createClientFrom(
getResource: (v: string) => Promise<IHttpOperation[]>,
document: string,
defaultConfig: IHttpConfig
): Promise<PrismHttp> {
return getResource(document).then(resources => createClientFromOperations(resources, defaultConfig));
}

const createClientFromResource = partial(createClientFrom, getHttpOperationsFromResource);
const createClientFromString = partial(createClientFrom, getHttpOperations);

function createClientFromOperations(resources: IHttpOperation[], defaultConfig: IClientConfig): PrismHttp {
export function createClientFromOperations(resources: IHttpOperation[], defaultConfig: IClientConfig): PrismHttp {
const obj = createInstance(defaultConfig, { logger });

type headersFromRequest = Required<Pick<IHttpRequest, 'headers'>>;
Expand Down Expand Up @@ -178,5 +166,3 @@ export type PrismHttp = {
patch: IRequestFunctionWithMethodWithBody;
trace: IRequestFunctionWithMethod;
};

export { createClientFromResource, createClientFromString, createClientFromOperations };
1 change: 0 additions & 1 deletion packages/http/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import mock from './mocker';
import route from './router';
import { validateInput, validateOutput, validateSecurity } from './validator';
export * from './types';
export * from './getHttpOperations';
export * from './mocker/errors';
export * from './router/errors';
export * from './mocker/serializer/style';
Expand Down
2 changes: 1 addition & 1 deletion packages/http/src/mocker/generator/JSONSchema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as faker from 'faker';
import * as faker from 'faker/locale/en_US';
XVincentX marked this conversation as resolved.
Show resolved Hide resolved
import { cloneDeep } from 'lodash';
import { JSONSchema } from '../../types';

Expand Down
4 changes: 2 additions & 2 deletions packages/http/src/mocker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { map } from 'fp-ts/lib/Array';
import { Do } from 'fp-ts-contrib/lib/Do';
import { isNumber, isString, keyBy, mapValues, groupBy, get } from 'lodash';
import { Logger } from 'pino';
import * as typeIs from 'type-is';
import { is } from 'type-is';
XVincentX marked this conversation as resolved.
Show resolved Hide resolved
import {
ContentExample,
IHttpOperationConfig,
Expand Down Expand Up @@ -112,7 +112,7 @@ function parseBodyIfUrlEncoded(request: IHttpRequest, resource: IHttpOperation)
const mediaType = caseless(request.headers || {}).get('content-type');
if (!mediaType) return request;

if (!typeIs.is(mediaType, ['application/x-www-form-urlencoded'])) return request;
if (!is(mediaType, ['application/x-www-form-urlencoded'])) return request;

const specs = pipe(
O.fromNullable(resource.request),
Expand Down
2 changes: 1 addition & 1 deletion packages/http/src/utils/parseResponse.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Response } from 'node-fetch';
import type { Response } from 'node-fetch';
import { is as typeIs } from 'type-is';
import * as E from 'fp-ts/lib/Either';
import * as TE from 'fp-ts/lib/TaskEither';
Expand Down
4 changes: 2 additions & 2 deletions packages/http/src/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as caseless from 'caseless';
import { findFirst, isNonEmpty } from 'fp-ts/lib/Array';
import * as O from 'fp-ts/lib/Option';
import * as E from 'fp-ts/lib/Either';
import * as typeIs from 'type-is';
import { is as typeIs } from 'type-is';
import { pipe } from 'fp-ts/lib/pipeable';
import { inRange } from 'lodash';
import { validateSecurity } from './validators/security';
Expand Down Expand Up @@ -95,7 +95,7 @@ const findResponseByStatus = (responses: IHttpOperationResponse[], statusCode: n
const validateMediaType = (contents: NonEmptyArray<IMediaTypeContent>, mediaType: string) =>
pipe(
contents,
findFirst(c => !!typeIs.is(mediaType, [c.mediaType])),
findFirst(c => !!typeIs(mediaType, [c.mediaType])),
E.fromOption<IPrismDiagnostic>(() => ({
message: `The received media type "${mediaType || ''}" does not match the one${
contents.length > 1 ? 's' : ''
Expand Down
6 changes: 3 additions & 3 deletions packages/http/src/validator/validators/body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { JSONSchema } from '../../types';
import { body } from '../deserializers';
import { IHttpValidator } from './types';
import { validateAgainstSchema } from './utils';
import { is } from 'type-is';
import { is as typeIs } from 'type-is';
import * as NonEmptyArray from 'fp-ts/lib/NonEmptyArray';

export function deserializeFormBody(
Expand Down Expand Up @@ -58,7 +58,7 @@ export function decodeUriEntities(target: Dictionary<string>) {
export function findContentByMediaTypeOrFirst(specs: IMediaTypeContent[], mediaType: string) {
return pipe(
specs,
Array.findFirst(spec => !!is(mediaType, [spec.mediaType])),
Array.findFirst(spec => !!typeIs(mediaType, [spec.mediaType])),
O.alt(() => Array.head(specs)),
O.map(content => ({ mediaType, content }))
);
Expand Down Expand Up @@ -105,7 +105,7 @@ export class HttpBodyValidator implements IHttpValidator<any, IMediaTypeContent>
({ content, mediaType: mt, schema }) =>
pipe(
mt,
O.fromPredicate(mediaType => !!is(mediaType, ['application/x-www-form-urlencoded'])),
O.fromPredicate(mediaType => !!typeIs(mediaType, ['application/x-www-form-urlencoded'])),
O.fold(
() =>
pipe(
Expand Down
Loading