Skip to content

Commit

Permalink
feat: support circular refs (#1835)
Browse files Browse the repository at this point in the history
* feat: support circular refs

- switch to our forked json schema ref parser
- bundle http operations
- validate circular data properly
- dynamically generate data for circular structures
- tests

* chore: add pr template

* fix: test harness

* fix: opps

* chore: update stoplight json

* chore: fix building the binary for test harness

- bump stoplight json in http

* chore: remove todos
  • Loading branch information
pytlesk4 authored May 25, 2021
1 parent 36a88c0 commit d287dd7
Show file tree
Hide file tree
Showing 27 changed files with 2,748 additions and 701 deletions.
28 changes: 28 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Addresses #[ISSUE_NUMBER]

**Summary**

Replace this with something that explains what this PR is for and why it matters.

**Checklist**

- The basics
- [ ] I tested these changes manually in my local or dev environment
- Tests
- [ ] Added or updated
- [ ] N/A
- Event Tracking
- [ ] I added event tracking and followed the event tracking guidelines
- [ ] N/A
- Error Reporting
- [ ] I reported errors and followed the error reporting guidelines
- [ ] N/A

**Screenshots**

If applicable, add screenshots or gifs to help demonstrate the changes. If not applicable, remove this screenshots
section before creating the PR.

**Additional context**

Add any other context about the pull request here. Remove this section if there is no additional context.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Yarn is a package manager for your code, similar to npm. While you can use npm t
3. Git clone your fork (i.e. `git clone https://github.com/<your-username>/prism.git`) to your machine.
4. Run `yarn` to install dependencies and setup the project.
5. Because during the development we run the software directly on top of TypeScript sources, we advise you to use our script: `cd packages/cli && yarn cli mock openapi.yaml`.
6. Run `git checkout -b [name_of_your_new_branch]` to create a new branch for your work. To help build nicer changelogs, we have a convention for branch names. Please start your branch with either `feature/{branch-name}`, `chore/{branch-name}`, or `fix/{branch-name}`. For example, if I was adding a CLI, I would make my branch name: `feature/add-cli`.
6. Run `git checkout -b [name_of_your_new_branch]` to create a new branch for your work. To help build nicer changelogs, we have a convention for branch names. Please start your branch with either `feature/{branch-name}`, `chore/{branch-name}`, or `fix/{branch-name}`. For example, if I was adding a new CLI feature, I would make my branch name: `feature/add-cli-new-feature`.
7. Make changes, write code and tests, etc. The fun stuff!
8. Run `yarn test` to test your changes.
9. Commit your changes.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"test": "jest",
"release": "lerna version",
"prebuild.binary": "yarn build",
"build.binary": "npx pkg --output ./cli-binaries/prism-cli ./packages/cli/",
"build.binary": "npx pkg --output ./cli-binaries/prism-cli --options max_old_space_size=4096 ./packages/cli/",
"test.harness": "jest -c ./jest.harness.config.js"
},
"resolutions": {
Expand Down
5 changes: 3 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@
},
"bugs": "https://github.com/stoplightio/prism/issues",
"dependencies": {
"@stoplight/http-spec": "^4.1.0",
"@stoplight/http-spec": "^4.2.0",
"@stoplight/json": "^3.13.7",
"@stoplight/json-schema-ref-parser": "9.2.1",
"@stoplight/prism-core": "^4.2.4",
"@stoplight/prism-http": "^4.2.4",
"@stoplight/prism-http-server": "^4.2.4",
"@stoplight/types": "^12.2.0",
"chalk": "^4.1.1",
"chokidar": "^3.5.1",
"fp-ts": "^2.10.4",
"json-schema-ref-parser": "^9.0.7",
"lodash": "^4.17.21",
"node-fetch": "^2.6.1",
"pino": "^6.11.3",
Expand Down
23 changes: 18 additions & 5 deletions packages/cli/src/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,33 @@ import { transformOas3Operations } from '@stoplight/http-spec/oas3/operation';
import { transformOas2Operations } from '@stoplight/http-spec/oas2/operation';
import { transformPostmanCollectionOperations } from '@stoplight/http-spec/postman/operation';
import { dereference } from 'json-schema-ref-parser';
import { bundleTarget, decycle } from '@stoplight/json';
import { IHttpOperation } from '@stoplight/types';
import { get } from 'lodash';
import type { Spec } from 'swagger-schema-official';
import type { OpenAPIObject } from 'openapi3-ts';
import type { CollectionDefinition } from 'postman-collection';

export async function getHttpOperationsFromSpec(specFilePathOrObject: string | object): Promise<IHttpOperation[]> {
const result = await dereference(specFilePathOrObject, { dereference: { circular: false } });
const result = decycle(await dereference(specFilePathOrObject));

if (isOpenAPI2(result)) return transformOas2Operations(result);
if (isOpenAPI3(result)) return transformOas3Operations(result);
if (isPostmanCollection(result)) return transformPostmanCollectionOperations(result);
let operations: IHttpOperation[] = [];
if (isOpenAPI2(result)) operations = transformOas2Operations(result);
else if (isOpenAPI3(result)) operations = transformOas3Operations(result);
else if (isPostmanCollection(result)) operations = transformPostmanCollectionOperations(result);
else throw new Error('Unsupported document format');

throw new Error('Unsupported document format');
operations.forEach((op, i, ops) => {
ops[i] = bundleTarget({
document: {
...result,
__target__: op,
},
path: '#/__target__',
});
});

return operations;
}

function isOpenAPI2(document: unknown): document is Spec {
Expand Down
85 changes: 85 additions & 0 deletions packages/http-server/src/__tests__/body-params-validation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,67 @@ describe('body params validation', () => {
tags: [],
security: [],
},
{
id: '?http-operation-id?',
method: 'post',
path: '/json-body-circular-property-required',
responses: [
{
code: '200',
headers: [],
contents: [
{
mediaType: 'text/plain',
schema: {
type: 'string',
$schema: 'http://json-schema.org/draft-07/schema#',
},
examples: [],
encodings: [],
},
],
},
],
servers: [],
request: {
body: {
contents: [
{
mediaType: 'application/json',
schema: {
$ref: '#/__bundled__/schemas',
},
examples: [],
encodings: [],
},
],
},
headers: [],
query: [],
cookie: [],
path: [],
},
tags: [],
security: [],
// @ts-ignore
['__bundled__']: {
schemas: {
$schema: 'http://json-schema.org/draft-07/schema#',
type: 'object',
required: ['id'],
properties: {
id: {
type: 'integer',
minimum: -9223372036854776000,
maximum: 9223372036854776000,
},
self: {
$ref: '#/__bundled__/schemas',
},
},
},
},
},
]);
});

Expand Down Expand Up @@ -295,6 +356,30 @@ describe('body params validation', () => {
});
});

describe('operation with circular required property', () => {
describe('when property not provided', () => {
test('returns 422 & error message', async () => {
const response = await makeRequest('/json-body-circular-property-required', {
method: 'POST',
body: JSON.stringify({ id: 123, self: {} }),
headers: { 'content-type': 'application/json' },
});

expect(response.status).toBe(422);
return expect(response.json()).resolves.toMatchObject({
validation: [
{
code: 'required',
location: ['body', 'self'],
message: "must have required property 'id'",
severity: 'Error',
},
],
});
});
});
});

describe('operation with optional body', () => {
describe('when no body provided', () => {
test('returns 200', async () => {
Expand Down
Loading

0 comments on commit d287dd7

Please sign in to comment.