Skip to content

Commit

Permalink
feat: populate params jwt defaults (#373)
Browse files Browse the repository at this point in the history
* feat: add JWT Default as parameter in constructSchema()

* feat: add function to check if jwtDefaults exist in schema

* feat: add tests for user defined jwt defaults for parameters

* refactor: run eslint

* refactor: fix tests

* refactor: update test to reflect constructSchema changes

* refactor: rename `jwtDefaults` to `globalDefaults` and extract out of Operation class

* refactor: add try/catch for when jsonpointer throws an exception

* refactor: fix merge conflicts

* refactor: add `globalDefault` param into Operation.getParametersAsJsonSchema docblock

* refactor: update test to remove `jwtDefault` from operation

Co-authored-by: Jon Ursenbach <erunion@users.noreply.github.com>
  • Loading branch information
darrenyong and erunion authored Feb 17, 2021
1 parent bf650cf commit 7a4acf7
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 17 deletions.
30 changes: 30 additions & 0 deletions __tests__/tooling/operation/get-parameters-as-json-schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1304,6 +1304,36 @@ describe('defaults', () => {
expect(oasInstance.operation('/', 'get').getParametersAsJsonSchema()).toMatchSnapshot();
});

it('should not add jwtDefaults if there are no matches', async () => {
const schema = new Oas(petstore);
await schema.dereference();
const operation = schema.operation('/pet', 'post');
const jwtDefaults = {
fakeParameter: {
id: 4,
name: 'Testing',
},
};

const jsonSchema = await operation.getParametersAsJsonSchema(jwtDefaults);
expect(jsonSchema[0].schema.properties.category.default).toBeUndefined();
});

it('should use user defined jwtDefaults', async () => {
const schema = new Oas(petstore);
await schema.dereference();
const operation = schema.operation('/pet', 'post');
const jwtDefaults = {
category: {
id: 4,
name: 'Testing',
},
};

const jsonSchema = operation.getParametersAsJsonSchema(jwtDefaults);
expect(jsonSchema[0].schema.properties.category.default).toStrictEqual(jwtDefaults.category);
});

it.todo('with usages of `oneOf` cases');

it.todo('with usages of `allOf` cases');
Expand Down
1 change: 0 additions & 1 deletion tooling/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ class Oas {

operation(path, method) {
const operation = getPathOperation(this, { swagger: { path }, api: { method } });

// If `getPathOperation` wasn't able to find the operation in the API definition, we should still set an empty
// schema on the operation in the `Operation` class because if we don't trying to use any of the accessors on that
// class are going to fail as `schema` will be `undefined`.
Expand Down
6 changes: 4 additions & 2 deletions tooling/operation.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,13 @@ class Operation {

/**
* Convert the operation into an array of JSON Schema for each available type of parameter available on the operation.
* `globalDefaults` contains an object of user defined parameter defaults used in constructSchema
*
* @param {Object} globalDefaults
* @return {array}
*/
getParametersAsJsonSchema() {
return getParametersAsJsonSchema(this.path, this.schema, this.oas);
getParametersAsJsonSchema(globalDefaults) {
return getParametersAsJsonSchema(this.path, this.schema, this.oas, globalDefaults);
}

/**
Expand Down
55 changes: 41 additions & 14 deletions tooling/operation/get-parameters-as-json-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,9 @@ function searchForExampleByPointer(pointer, examples = []) {
* @param {Object} data
* @param {Object[]} prevSchemas
* @param {string} currentLocation
* @param {Object} globalDefaults
*/
function constructSchema(data, prevSchemas = [], currentLocation = '') {
function constructSchema(data, prevSchemas = [], currentLocation = '', globalDefaults) {
const schema = { ...data };

// If this schema contains a `$ref`, it's circular and we shouldn't try to resolve it. Just return and move along.
Expand Down Expand Up @@ -275,7 +276,7 @@ function constructSchema(data, prevSchemas = [], currentLocation = '') {
// `items` contains a `$ref`, so since it's circular we should do a no-op here and ignore it.
} else {
// Run through the arrays contents and clean them up.
schema.items = constructSchema(schema.items, prevSchemas, `${currentLocation}/0`);
schema.items = constructSchema(schema.items, prevSchemas, `${currentLocation}/0`, globalDefaults);
}
} else if ('properties' in schema || 'additionalProperties' in schema) {
// This is a fix to handle cases where someone may have typod `items` as `properties` on an array. Since
Expand All @@ -294,7 +295,8 @@ function constructSchema(data, prevSchemas = [], currentLocation = '') {
schema.properties[prop] = constructSchema(
schema.properties[prop],
prevSchemas,
`${currentLocation}/${encodePointer(prop)}`
`${currentLocation}/${encodePointer(prop)}`,
globalDefaults
);

return true;
Expand All @@ -312,7 +314,12 @@ function constructSchema(data, prevSchemas = [], currentLocation = '') {
) {
schema.additionalProperties = {};
} else {
schema.additionalProperties = constructSchema(data.additionalProperties, prevSchemas, currentLocation);
schema.additionalProperties = constructSchema(
data.additionalProperties,
prevSchemas,
currentLocation,
globalDefaults
);
}
}
}
Expand All @@ -323,11 +330,24 @@ function constructSchema(data, prevSchemas = [], currentLocation = '') {
['allOf', 'anyOf', 'oneOf'].forEach(polyType => {
if (polyType in schema && Array.isArray(schema[polyType])) {
schema[polyType].forEach((item, idx) => {
schema[polyType][idx] = constructSchema(item, prevSchemas, `${currentLocation}/${idx}`);
schema[polyType][idx] = constructSchema(item, prevSchemas, `${currentLocation}/${idx}`, globalDefaults);
});
}
});

// Users can pass in parameter defaults via JWT User Data: https://docs.readme.com/docs/passing-data-to-jwt
// We're checking to see if the defaults being passed in exist on endpoints via jsonpointer
if (globalDefaults && Object.keys(globalDefaults).length > 0 && currentLocation) {
try {
const userJwtDefault = jsonpointer.get(globalDefaults, currentLocation);
if (userJwtDefault) {
schema.default = userJwtDefault;
}
} catch (err) {
// If jsonpointer returns an error, we won't show any defaults for that path.
}
}

// Only add a default value if we actually have one.
if ('default' in schema && typeof schema.default !== 'undefined') {
if (('allowEmptyValue' in schema && schema.allowEmptyValue && schema.default === '') || schema.default !== '') {
Expand All @@ -347,7 +367,7 @@ function constructSchema(data, prevSchemas = [], currentLocation = '') {
return schema;
}

function getRequestBody(operation, oas) {
function getRequestBody(operation, oas, globalDefaults) {
const schema = getSchema(operation, oas);
if (!schema || !schema.schema) return null;

Expand All @@ -366,7 +386,7 @@ function getRequestBody(operation, oas) {
examples.push({ examples: requestBody.examples });
}

const cleanedSchema = constructSchema(requestBody.schema, examples);
const cleanedSchema = constructSchema(requestBody.schema, examples, '', globalDefaults);
if (oas.components) {
const components = {};
Object.keys(oas.components).forEach(componentType => {
Expand All @@ -376,7 +396,12 @@ function getRequestBody(operation, oas) {
}

Object.keys(oas.components[componentType]).forEach(schemaName => {
components[componentType][schemaName] = constructSchema(oas.components[componentType][schemaName]);
components[componentType][schemaName] = constructSchema(
oas.components[componentType][schemaName],
[],
'',
globalDefaults
);
});
}
});
Expand Down Expand Up @@ -404,7 +429,7 @@ function getCommonParams(path, oas) {
return [];
}

function getParameters(path, operation, oas) {
function getParameters(path, operation, oas, globalDefaults) {
let operationParams = operation.parameters || [];
const commonParams = getCommonParams(path, oas);

Expand Down Expand Up @@ -436,7 +461,7 @@ function getParameters(path, operation, oas) {
let schema = {};
if ('schema' in current) {
schema = {
...(current.schema ? constructSchema(current.schema) : {}),
...(current.schema ? constructSchema(current.schema, [], '', globalDefaults) : {}),
};
} else if ('content' in current && typeof current.content === 'object') {
const contentKeys = Object.keys(current.content);
Expand All @@ -457,7 +482,9 @@ function getParameters(path, operation, oas) {

if (typeof current.content[contentType] === 'object' && 'schema' in current.content[contentType]) {
schema = {
...(current.content[contentType].schema ? constructSchema(current.content[contentType].schema) : {}),
...(current.content[contentType].schema
? constructSchema(current.content[contentType].schema, [], '', globalDefaults)
: {}),
};
}
}
Expand Down Expand Up @@ -499,14 +526,14 @@ function getParameters(path, operation, oas) {
});
}

module.exports = (path, operation, oas) => {
module.exports = (path, operation, oas, globalDefaults = {}) => {
const hasRequestBody = !!operation.requestBody;
const hasParameters = !!(operation.parameters && operation.parameters.length !== 0);
if (!hasParameters && !hasRequestBody && getCommonParams(path, oas).length === 0) return null;

const typeKeys = Object.keys(types);
return [getRequestBody(operation, oas)]
.concat(...getParameters(path, operation, oas))
return [getRequestBody(operation, oas, globalDefaults)]
.concat(...getParameters(path, operation, oas, globalDefaults))
.filter(Boolean)
.sort((a, b) => {
return typeKeys.indexOf(a.type) - typeKeys.indexOf(b.type);
Expand Down

0 comments on commit 7a4acf7

Please sign in to comment.