Skip to content

Commit

Permalink
fix: add translate callback support (#211)
Browse files Browse the repository at this point in the history
* fix: add translate callback test

* fix: add callback context to generate id

* fix: updates based on pr feedback
  • Loading branch information
brendarearden authored Feb 10, 2023
1 parent bf82030 commit 382dcff
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 3 deletions.
31 changes: 31 additions & 0 deletions src/__tests__/generators.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { idGenerators } from '../generators';

describe('idGenerators', () => {
it('httpOperation ids should be unique', () => {
const operation1 = { parentId: '12345', method: 'post', path: '/test/path/{id}' };
const operation2 = { parentId: '12345', method: 'post', path: '/test/path/id' };
const id1 = idGenerators.httpOperation(operation1);
const id2 = idGenerators.httpOperation(operation2);
expect(id1).toEqual('http_operation-12345-post-/test/path/{}');
expect(id2).toEqual('http_operation-12345-post-/test/path/id');
expect(id1).not.toEqual(id2);
});
it('httpOperation ids should be data resilient', () => {
const operation1 = { parentId: '12345', method: 'post', path: '/test/path/{id}' };
const operation2 = { parentId: '12345', method: 'post', path: '/test/path/{name}' };
const id1 = idGenerators.httpOperation(operation1);
const id2 = idGenerators.httpOperation(operation2);
expect(id1).toEqual('http_operation-12345-post-/test/path/{}');
expect(id2).toEqual('http_operation-12345-post-/test/path/{}');
expect(id1).toEqual(id2);
});
it('httpCallbackOperation ids should be unique', () => {
const operation1 = { parentId: '12345', method: 'post', path: '{$request.body#/returnedPetAdoptedUrl}' };
const operation2 = { parentId: '12345', method: 'post', path: '{$request.body#/newPetAvailableUrl}' };
const id1 = idGenerators.httpCallbackOperation(operation1);
const id2 = idGenerators.httpCallbackOperation(operation2);
expect(id1).toEqual('http_callback-12345-post-{$request.body#/returnedPetAdoptedUrl}');
expect(id2).toEqual('http_callback-12345-post-{$request.body#/newPetAvailableUrl}');
expect(id1).not.toEqual(id2);
});
});
4 changes: 4 additions & 0 deletions src/generators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export const idGenerators = {
return join(['http_operation', props.parentId, props.method, sanitizePath(props.path)]);
},

httpCallbackOperation: (props: Context & { method: string; path: string }) => {
return join(['http_callback', props.parentId, props.method, props.path]);
},

httpPathParam: (props: Context & { keyOrName: string }) => {
return join(['http_path_param', props.parentId, props.keyOrName]);
},
Expand Down
11 changes: 10 additions & 1 deletion src/oas/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,16 @@ export const transformOasOperation: TranslateFunction<

const serviceId = (this.ids.service = String(this.document['x-stoplight']?.id));
this.ids.path = this.generateId.httpPath({ parentId: serviceId, path });
const operationId = (this.ids.operation = this.generateId.httpOperation({ parentId: serviceId, method, path }));
let operationId: string;
if (this.context === 'callback') {
operationId = this.ids.operation = this.generateId.httpCallbackOperation({
parentId: serviceId,
method,
path,
});
} else {
operationId = this.ids.operation = this.generateId.httpOperation({ parentId: serviceId, method, path });
}

this.context = 'operation';

Expand Down
252 changes: 252 additions & 0 deletions src/oas3/transformers/__tests__/callbacks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import { DeepPartial } from '@stoplight/types';
import { OpenAPIObject } from 'openapi3-ts';

import { createContext } from '../../../oas/context';
import { translateToCallbacks as _translateToCallbacks } from '../callbacks';

const translateToCallbacks = (document: DeepPartial<OpenAPIObject>, callbacks: unknown) =>
_translateToCallbacks.call(createContext(document), callbacks);

describe('translateToCallbacks', () => {
it('given multiple callback entries, it should translate', () => {
const callbackEntries = {
newPetWebhook: {
'{$request.body#/newPetAvailableUrl}': {
post: {
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
properties: {
message: {
type: 'string',
example: 'A new pet has arrived',
},
},
required: ['message'],
},
},
},
},
responses: {
'200': {
description: 'Your server returns this code if it accepts the callback',
},
},
},
},
'{$request.body#/returnedPetAvailableUrl}': {
post: {
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
properties: {
message: {
type: 'string',
example: 'A pet has been returned',
},
},
required: ['message'],
},
},
},
},
responses: {
'200': {
description: 'Your server returns this code if it accepts the callback',
},
},
},
},
},
petAdopted: {
'{$request.body#/adoptedUrl}': {
post: {
requestBody: {
required: true,
content: {
'application/json': {
schema: {
type: 'object',
properties: {
message: {
type: 'string',
example: 'A pet has been adopted',
},
},
required: ['message'],
},
},
},
},
responses: {
'200': {
description: 'Your server returns this code if it accepts the callback',
},
},
},
},
},
};
expect(translateToCallbacks({}, callbackEntries)).toStrictEqual([
{
callbackName: 'newPetWebhook',
extensions: {},
id: '3245690b6a7fc',
method: 'post',
path: '{$request.body#/newPetAvailableUrl}',
request: {
body: {
contents: [
{
encodings: [],
examples: [],
id: expect.any(String),
mediaType: 'application/json',
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
properties: {
message: {
examples: ['A new pet has arrived'],
type: 'string',
},
},
required: ['message'],
type: 'object',
'x-stoplight': {
id: expect.any(String),
},
},
},
],
id: expect.any(String),
required: true,
},
cookie: [],
headers: [],
path: [],
query: [],
},
responses: [
{
code: '200',
contents: [],
description: 'Your server returns this code if it accepts the callback',
headers: [],
id: expect.any(String),
},
],
security: [],
servers: [],
tags: [],
},
{
callbackName: 'newPetWebhook',
extensions: {},
id: '07041d5723f4a',
method: 'post',
path: '{$request.body#/returnedPetAvailableUrl}',
request: {
body: {
contents: [
{
encodings: [],
examples: [],
id: expect.any(String),
mediaType: 'application/json',
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
properties: {
message: {
examples: ['A pet has been returned'],
type: 'string',
},
},
required: ['message'],
type: 'object',
'x-stoplight': {
id: expect.any(String),
},
},
},
],
id: expect.any(String),
required: true,
},
cookie: [],
headers: [],
path: [],
query: [],
},
responses: [
{
code: '200',
contents: [],
description: 'Your server returns this code if it accepts the callback',
headers: [],
id: expect.any(String),
},
],
security: [],
servers: [],
tags: [],
},
{
callbackName: 'petAdopted',
extensions: {},
id: '2333951a518f9',
method: 'post',
path: '{$request.body#/adoptedUrl}',
request: {
body: {
contents: [
{
encodings: [],
examples: [],
id: expect.any(String),
mediaType: 'application/json',
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
properties: {
message: {
examples: ['A pet has been adopted'],
type: 'string',
},
},
required: ['message'],
type: 'object',
'x-stoplight': {
id: expect.any(String),
},
},
},
],
id: expect.any(String),
required: true,
},
cookie: [],
headers: [],
path: [],
query: [],
},
responses: [
{
code: '200',
contents: [],
description: 'Your server returns this code if it accepts the callback',
headers: [],
id: expect.any(String),
},
],
security: [],
servers: [],
tags: [],
},
]);
});
});
6 changes: 6 additions & 0 deletions src/oas3/transformers/callbacks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { IHttpCallbackOperation } from '@stoplight/types';
import type { OpenAPIObject } from 'openapi3-ts';

import { createContext } from '../../oas/context';
import { entries } from '../../utils';
import { transformOas3Operation } from '../operation';
import type { Oas3TranslateFunction } from '../types';
Expand All @@ -19,11 +20,16 @@ export const translateToCallbacks: Oas3TranslateFunction<[callbacks: unknown], I
paths: { [path]: { [method]: op } },
};

const ctx = createContext(document);
ctx.context = 'callback';
Object.assign(ctx.ids, this.ids);
ctx.ids.operation = this.generateId.httpCallbackOperation({ parentId: this.ids.service, method, path });
results.push({
...transformOas3Operation({
document,
method,
path,
ctx,
}),
callbackName,
});
Expand Down
4 changes: 2 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ export type HttpOperationTransformer<T> = (opts: T) => IHttpOperation;

export type ArrayCallbackParameters<T> = [T, number, T[]];

export type AvailableContext = 'service' | 'path' | 'operation';
export type AvailableContext = 'service' | 'path' | 'operation' | 'callback';

export type References = Record<string, { resolved: boolean; value: string }>;

export type TransformerContext<T extends Fragment = Fragment> = {
document: T;
context: AvailableContext;
parentId: string;
readonly ids: Record<AvailableContext, string>;
readonly ids: Omit<Record<AvailableContext, string>, 'callback'>;
readonly references: References;
generateId: ((template: string) => string) & {
[key in keyof typeof idGenerators]: (
Expand Down

0 comments on commit 382dcff

Please sign in to comment.