fury-adapter-oas3-parser
Advanced tools
Comparing version 0.4.0 to 0.4.1
# Fury OAS3 Parser Changelog | ||
## 0.4.1 (28-01-19) | ||
### Enhancements | ||
- Path and query parameters are not supported in 'Operation Object' | ||
### Bug Fixes | ||
- Unsupported properties in 'Parameter Object' will now emit an unsupported | ||
warning, previously using unsupported properties was emitting a warning that | ||
the properties were invalid. | ||
- The parser will no longer error out for unsupported parameter 'in' values, | ||
instead an unsupported warning will be emitted. | ||
- Parameter names containing unreserved URI Template characters (`-`, `.`, `_`, | ||
and `~`) are now supported. | ||
- Referencing (`$ref`) a parameter that couldn't be parsed (due to the | ||
parameter failing validation) will no longer cause a cryptic error that the | ||
referenced object was not defined. | ||
## 0.4.0 (25-01-19) | ||
@@ -4,0 +26,0 @@ |
const R = require('ramda'); | ||
const { isMember, isObject } = require('../predicates'); | ||
@@ -44,26 +43,2 @@ function createAnnotation(annotationClass, namespace, message, element) { | ||
function validateObjectContainsRequiredKeys(namespace, path, requiredKeys, object) { | ||
if (!isObject(object)) { | ||
return new namespace.elements.ParseResult([object]); | ||
} | ||
// FIXME Can be simplified once https://github.com/refractproject/minim/issues/201 is completed | ||
const hasMember = (key) => { | ||
const findKey = R.allPass([isMember, member => member.key.toValue() === key]); | ||
const matchingMembers = object.content.filter(findKey); | ||
return matchingMembers.length > 0; | ||
}; | ||
const missingKeys = R.reject(hasMember, requiredKeys); | ||
const errorFromKey = key => createError(namespace, `'${path}' is missing required property '${key}'`, object); | ||
if (missingKeys.length > 0) { | ||
return new namespace.elements.ParseResult( | ||
R.map(errorFromKey, missingKeys) | ||
); | ||
} | ||
return new namespace.elements.ParseResult([object]); | ||
} | ||
module.exports = { | ||
@@ -79,3 +54,2 @@ createError, | ||
createMemberValueNotBooleanError: R.curry(createMemberValueNotBooleanError), | ||
validateObjectContainsRequiredKeys: R.curry(validateObjectContainsRequiredKeys), | ||
}; |
@@ -19,3 +19,50 @@ const R = require('ramda'); | ||
const valueIsObject = R.compose(isObject, getValue); | ||
/** | ||
* Is the given parse result empty (i.e, contains no elements other than annotations) | ||
* | ||
* @param parseResult {ParseResult} | ||
* @returns boolean | ||
*/ | ||
const isParseResultEmpty = parseResult => R.reject(isAnnotation, parseResult).isEmpty; | ||
/** | ||
* Parse a component member | ||
* | ||
* This function takes another parser that is capable of parsing the specific | ||
* component type (schema parser, parameter parser etc) and parses a | ||
* component using the given parser. It will return a parse result of member. | ||
* | ||
* In the cases that the given member cannot be parsed, it will result in | ||
* returning a member element to represent the key without a value. | ||
* | ||
* @param context | ||
* @param parser {function} | ||
* @param member {Member} - Member element to represent the component, | ||
* member key contains the reusable component name, value represents | ||
* the reusable component | ||
* | ||
* @returns ParseResult | ||
*/ | ||
const parseComponentMember = R.curry((context, parser, member) => { | ||
// Create a Member Element with `member.key` as the key | ||
const Member = R.constructN(2, context.namespace.elements.Member)(member.key); | ||
const parseResult = pipeParseResult(context.namespace, | ||
parser(context), | ||
Member)(member.value); | ||
if (isParseResultEmpty(parseResult)) { | ||
// parse result does not contain a member, that's because parsing a | ||
// component has failed. We want to store the member without value in | ||
// this case so that we can correctly know if a component with the name | ||
// existed during dereferencing. | ||
parseResult.unshift(Member(undefined)); | ||
} | ||
return parseResult; | ||
}); | ||
/** | ||
* Parse Components Object | ||
@@ -32,32 +79,35 @@ * | ||
const validateIsObject = key => R.unless(isObject, | ||
createWarning(namespace, `'${name}' '${key}' is not an object`)); | ||
const createMemberValueNotObjectWarning = member => createWarning(namespace, | ||
`'${name}' '${member.key.toValue()}' is not an object`, member.value); | ||
const validateIsObject = R.unless(valueIsObject, createMemberValueNotObjectWarning); | ||
const parseSchemaMember = member => pipeParseResult(namespace, | ||
R.compose(parseSchemaObject(context), getValue), | ||
(dataStructure) => { | ||
// eslint-disable-next-line no-param-reassign | ||
dataStructure.content.id = member.key.clone(); | ||
return dataStructure; | ||
})(member); | ||
/** | ||
* Parses a member representing a component object (such as an object | ||
* representing the parameter components) | ||
* | ||
* @param parser {function} | ||
* @param member {Member} | ||
* | ||
* @returns ParseResult | ||
*/ | ||
const parseComponentObjectMember = (parser) => { | ||
const parseMember = parseComponentMember(context, parser); | ||
const parseSchemasObject = pipeParseResult(namespace, | ||
validateIsObject('schemas'), | ||
parseObject(context, name, parseSchemaMember)); | ||
const parseParametersObjectMember = (member) => { | ||
// Create a Member Element with `member.key` as the key | ||
const Member = R.constructN(2, namespace.elements.Member)(member.key); | ||
const parseResult = parseParameterObject(context, member.value); | ||
// Wrap non-annotation elements in member element | ||
return R.map(R.unless(isAnnotation, Member), parseResult); | ||
return pipeParseResult(context.namespace, | ||
validateIsObject, | ||
R.compose(parseObject(context, name, parseMember), getValue)); | ||
}; | ||
const parseParametersObject = pipeParseResult(namespace, | ||
validateIsObject('parameters'), | ||
parseObject(context, name, parseParametersObjectMember)); | ||
// eslint-disable-next-line no-param-reassign | ||
const setDataStructureId = (dataStructure, key) => { dataStructure.content.id = key.clone(); }; | ||
const parseSchemas = pipeParseResult(namespace, | ||
parseComponentObjectMember(parseSchemaObject), | ||
(object) => { | ||
object.forEach(setDataStructureId); | ||
return object; | ||
}); | ||
const parseMember = R.cond([ | ||
[hasKey('schemas'), R.compose(parseSchemasObject, getValue)], | ||
[hasKey('parameters'), R.compose(parseParametersObject, getValue)], | ||
[hasKey('schemas'), parseSchemas], | ||
[hasKey('parameters'), parseComponentObjectMember(parseParameterObject)], | ||
[isUnsupportedKey, createUnsupportedMemberWarning(namespace, name)], | ||
@@ -72,3 +122,3 @@ | ||
return parseObject(context, name, parseMember, element); | ||
return parseObject(context, name, parseMember)(element); | ||
} | ||
@@ -75,0 +125,0 @@ |
@@ -6,3 +6,2 @@ const R = require('ramda'); | ||
createInvalidMemberWarning, | ||
validateObjectContainsRequiredKeys, | ||
} = require('../annotations'); | ||
@@ -52,4 +51,3 @@ const { | ||
R.unless(isObject, createError(namespace, `'${name}' is not an object`)), | ||
validateObjectContainsRequiredKeys(namespace, name, requiredKeys), | ||
parseObject(context, name, parseMember), | ||
parseObject(context, name, parseMember, requiredKeys), | ||
(info) => { | ||
@@ -56,0 +54,0 @@ const api = new namespace.elements.Category(); |
@@ -0,1 +1,3 @@ | ||
/* eslint-disable no-underscore-dangle */ | ||
const R = require('ramda'); | ||
@@ -9,3 +11,2 @@ | ||
createInvalidMemberWarning, | ||
validateObjectContainsRequiredKeys, | ||
} = require('../annotations'); | ||
@@ -31,2 +32,41 @@ const pipeParseResult = require('../../pipeParseResult'); | ||
function filterSourceMaps(result) { | ||
if (isAnnotation(result)) { | ||
return result; | ||
} | ||
if (result) { | ||
if (!result.element) { | ||
return result; | ||
} | ||
if (result._attributes) { | ||
result.attributes.remove('sourceMap'); | ||
result.attributes.forEach((value, key, member) => { | ||
filterSourceMaps(member); | ||
}); | ||
} | ||
if (result._meta) { | ||
result.meta.forEach((value, key, member) => { | ||
filterSourceMaps(member); | ||
}); | ||
} | ||
if (result.content) { | ||
if (Array.isArray(result.content)) { | ||
result.content.forEach((value) => { | ||
filterSourceMaps(value); | ||
}); | ||
} | ||
if (result.content.key) { | ||
filterSourceMaps(result.content.key); | ||
if (result.content.value) { | ||
filterSourceMaps(result.content.value); | ||
} | ||
} | ||
if (result.content.element) { | ||
filterSourceMaps(result.content); | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
function parseOASObject(context, object) { | ||
@@ -68,4 +108,3 @@ const { namespace } = context; | ||
const parseOASObject = pipeParseResult(namespace, | ||
validateObjectContainsRequiredKeys(namespace, name, requiredKeys), | ||
parseObject(context, name, parseMember), | ||
parseObject(context, name, parseMember, requiredKeys), | ||
(object) => { | ||
@@ -94,5 +133,6 @@ const api = object.get('info'); | ||
return parseOASObject(object); | ||
return context.options.generateSourceMap ? parseOASObject(object) : filterSourceMaps(parseOASObject(object)); | ||
} | ||
module.exports = R.curry(parseOASObject); |
@@ -6,3 +6,2 @@ const R = require('ramda'); | ||
createInvalidMemberWarning, | ||
validateObjectContainsRequiredKeys, | ||
createIdentifierNotUniqueWarning, | ||
@@ -15,2 +14,3 @@ } = require('../annotations'); | ||
const parseResponsesObject = require('./parseResponsesObject'); | ||
const parseParameterObjects = require('./parseParameterObjects'); | ||
@@ -20,4 +20,3 @@ const name = 'Operation Object'; | ||
const unsupportedKeys = [ | ||
'tags', 'externalDocs', 'parameters', 'requestBody', | ||
'callbacks', 'deprecated', 'security', | ||
'tags', 'externalDocs', 'requestBody', 'callbacks', 'deprecated', 'security', | ||
]; | ||
@@ -39,2 +38,25 @@ const isUnsupportedKey = R.anyPass(R.map(hasKey, unsupportedKeys)); | ||
function hrefVariablesFromParameters(namespace, parameters) { | ||
const path = parameters.get('path') | ||
? parameters.get('path') | ||
: new namespace.elements.HrefVariables(); | ||
const query = parameters.get('query') | ||
? parameters.get('query') | ||
: new namespace.elements.HrefVariables(); | ||
if (!path.isEmpty || !query.isEmpty) { | ||
return path.concat(query); | ||
} | ||
return undefined; | ||
} | ||
function hrefFromParameters(path, queryParameters) { | ||
const href = path.clone(); | ||
const queryString = queryParameters.keys().join(','); | ||
href.content += `{?${queryString}}`; | ||
return href; | ||
} | ||
/** | ||
@@ -44,2 +66,3 @@ * Parse Operation Object | ||
* @param namespace {Namespace} | ||
* @param path {StringElement} | ||
* @param element {Element} | ||
@@ -50,3 +73,3 @@ * @returns ParseResult<Transition> | ||
*/ | ||
function parseOperationObject(context, member) { | ||
function parseOperationObject(context, path, member) { | ||
const { namespace } = context; | ||
@@ -69,2 +92,3 @@ | ||
[hasKey('responses'), R.compose(parseResponsesObject(context), getValue)], | ||
[hasKey('parameters'), R.compose(parseParameterObjects(context, name), getValue)], | ||
@@ -81,4 +105,3 @@ [isUnsupportedKey, createUnsupportedMemberWarning(namespace, name)], | ||
const parseOperation = pipeParseResult(namespace, | ||
validateObjectContainsRequiredKeys(namespace, name, requiredKeys), | ||
parseObject(context, name, parseMember), | ||
parseObject(context, name, parseMember, requiredKeys), | ||
(operation) => { | ||
@@ -98,2 +121,12 @@ const transition = new namespace.elements.Transition(); | ||
const parameters = operation.get('parameters'); | ||
if (parameters) { | ||
const queryParameters = parameters.get('query'); | ||
if (queryParameters) { | ||
transition.href = hrefFromParameters(path, queryParameters); | ||
} | ||
transition.hrefVariables = hrefVariablesFromParameters(namespace, parameters); | ||
} | ||
const transactions = createTransactions(namespace, member, operation); | ||
@@ -100,0 +133,0 @@ transition.content = transition.content.concat(transactions); |
const R = require('ramda'); | ||
const { isExtension, hasKey } = require('../../predicates'); | ||
const { isExtension, hasKey, getValue } = require('../../predicates'); | ||
const { | ||
createError, | ||
createWarning, | ||
createUnsupportedMemberWarning, | ||
createInvalidMemberWarning, | ||
validateObjectContainsRequiredKeys, | ||
} = require('../annotations'); | ||
@@ -17,4 +17,4 @@ const pipeParseResult = require('../../pipeParseResult'); | ||
const unsupportedKeys = [ | ||
// FIXME Only contains "fixed" fields from spec | ||
'deprecated', 'allowEmptyValue', | ||
'deprecated', 'allowEmptyValue', 'style', 'explode', 'allowReserved', | ||
'schema', 'example', 'examples', 'content', | ||
]; | ||
@@ -31,5 +31,5 @@ const isUnsupportedKey = R.anyPass(R.map(hasKey, unsupportedKeys)); | ||
const unreservedCharacterRegex = /^[A-z0-9\\.\\_\\~\\-]+$/; | ||
function nameContainsReservedCharacter(member) { | ||
return !/^[A-z0-9]+$/.test(member.value.toValue()); | ||
return !unreservedCharacterRegex.test(member.value.toValue()); | ||
} | ||
@@ -49,9 +49,12 @@ | ||
const validateIn = R.unless(isValidInValue, createError(namespace, | ||
"'Parameter Object' 'in' must be either 'query, 'header', 'path' or 'cookie'")); | ||
const createInvalidInError = R.compose( | ||
createError(namespace, `'${name}' 'in' must be either 'query, 'header', 'path' or 'cookie'`), | ||
getValue | ||
); | ||
const validateIn = R.unless(isValidInValue, createInvalidInError); | ||
// FIXME: The following should not be an error | ||
const isSupportedIn = R.anyPass([hasValue('path'), hasValue('query')]); | ||
const ensureSupportedIn = R.unless(isSupportedIn, createError(namespace, | ||
"Only 'in' values of 'path' and 'query' are supported at the moment")); | ||
const createUnsupportedInWarning = member => createWarning(namespace, | ||
`'${name}' 'in' ${member.value.toValue()} is unsupported`, member.value); | ||
const ensureSupportedIn = R.unless(isSupportedIn, createUnsupportedInWarning); | ||
@@ -63,4 +66,6 @@ const parseIn = pipeParseResult(namespace, | ||
const createUnsupportedNameError = createError(namespace, | ||
`'${name}' 'name' contains unsupported characters. Only alphanumeric characters are currently supported`); | ||
const createUnsupportedNameError = R.compose( | ||
createError(namespace, `'${name}' 'name' contains unsupported characters. Only alphanumeric characters are currently supported`), | ||
getValue | ||
); | ||
const validateName = R.when(nameContainsReservedCharacter, createUnsupportedNameError); | ||
@@ -87,4 +92,3 @@ const parseName = pipeParseResult(namespace, | ||
const parseParameter = pipeParseResult(namespace, | ||
validateObjectContainsRequiredKeys(namespace, name, requiredKeys), | ||
parseObject(context, name, parseMember), | ||
parseObject(context, name, parseMember, requiredKeys), | ||
(parameter) => { | ||
@@ -91,0 +95,0 @@ const member = new namespace.elements.Member(parameter.get('name')); |
@@ -38,8 +38,2 @@ const R = require('ramda'); | ||
function createErrorForMissingPathParameter(namespace, path, variable) { | ||
// FIXME: This shouldn't be an error | ||
const message = `Path '${path.toValue()}' contains variable '${variable}' which is not declared in the parameters section of the '${name}'`; | ||
return createError(namespace, message, path); | ||
} | ||
function createErrorForMissingPathVariable(namespace, path, variable) { | ||
@@ -50,32 +44,2 @@ return createError(namespace, `Path '${path.toValue()}' is missing path variable '${variable}'. Add '{${variable}}' to the path`, path); | ||
/** | ||
* Validates that there is a href variable for each path variable in the given path | ||
* @param namespace | ||
* @param path {StringElement} | ||
* @param pathItem {ObjectElement} | ||
* @retuns ParseResult<ObjectElement> | ||
*/ | ||
function validatePathForMissingHrefVariables(namespace, path, pathItem) { | ||
const pathVariables = extractPathVariables(path.toValue()); | ||
const parameters = pathItem.get('parameters') | ||
? pathItem.get('parameters') | ||
: new namespace.elements.Object(); | ||
const hrefVariables = parameters.get('path') | ||
? parameters.get('path') | ||
: new namespace.elements.HrefVariables(); | ||
const missingParameters = hrefVariables | ||
? pathVariables.filter(name => !hrefVariables.getMember(name)) | ||
: pathVariables; | ||
if (missingParameters.length > 0) { | ||
const toError = R.curry(createErrorForMissingPathParameter)(namespace, path); | ||
return new namespace.elements.ParseResult(missingParameters.map(toError)); | ||
} | ||
return new namespace.elements.ParseResult([pathItem]); | ||
} | ||
/** | ||
* Validates that each href variable is found within the given path | ||
@@ -163,3 +127,3 @@ * @param namespace | ||
[hasKey('parameters'), R.curry(parseParameters)(context, member.key)], | ||
[isHttpMethodKey, parseOperationObject(context)], | ||
[isHttpMethodKey, parseOperationObject(context, member.key)], | ||
@@ -180,3 +144,2 @@ // FIXME Parse $ref | ||
parseObject(context, name, parseMember), | ||
R.curry(validatePathForMissingHrefVariables)(namespace, member.key), | ||
(pathItem) => { | ||
@@ -183,0 +146,0 @@ const resource = new namespace.elements.Resource(); |
@@ -6,3 +6,2 @@ const R = require('ramda'); | ||
createInvalidMemberWarning, | ||
validateObjectContainsRequiredKeys, | ||
} = require('../annotations'); | ||
@@ -23,3 +22,5 @@ const { isExtension, hasKey } = require('../../predicates'); | ||
* @param element {Element} | ||
* @returns ParseResult | ||
* @returns ParseResult - A parse result containing error if the referenced | ||
* object was not found, the referenced element, or if the referenced | ||
* element was not successfully parsed, an empty parse result. | ||
* | ||
@@ -56,3 +57,3 @@ * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#referenceObject | ||
const element = component.get(referenceParts[3]); | ||
const element = component.getMember(referenceParts[3]); | ||
if (!element) { | ||
@@ -62,3 +63,7 @@ return createError(namespace, `'${ref.toValue()}' is not defined`, ref); | ||
return element; | ||
if (element.value) { | ||
return element.value; | ||
} | ||
return new namespace.elements.ParseResult([]); | ||
}; | ||
@@ -73,4 +78,3 @@ | ||
const parseReference = pipeParseResult(namespace, | ||
validateObjectContainsRequiredKeys(namespace, name, requiredKeys), | ||
parseObject(context, name, parseMember), | ||
parseObject(context, name, parseMember, requiredKeys), | ||
object => object.get('$ref'), | ||
@@ -77,0 +81,0 @@ parseRef); |
@@ -10,3 +10,2 @@ const R = require('ramda'); | ||
createInvalidMemberWarning, | ||
validateObjectContainsRequiredKeys, | ||
} = require('../annotations'); | ||
@@ -80,4 +79,3 @@ const parseObject = require('../parseObject'); | ||
const parseResponse = pipeParseResult(namespace, | ||
validateObjectContainsRequiredKeys(namespace, name, requiredKeys), | ||
parseObject(context, name, parseMember), | ||
parseObject(context, name, parseMember, requiredKeys), | ||
(responseObject) => { | ||
@@ -84,0 +82,0 @@ // Try to fecth responses from the media type parsing |
@@ -33,5 +33,5 @@ const R = require('ramda'); | ||
return new namespace.elements.ParseResult([]); | ||
return new namespace.elements.ParseResult([openapi]); | ||
} | ||
module.exports = R.curry(parseOpenAPI); |
@@ -5,3 +5,4 @@ const R = require('ramda'); | ||
} = require('../predicates'); | ||
const { createWarning } = require('./annotations'); | ||
const { createError, createWarning } = require('./annotations'); | ||
const pipeParseResult = require('../pipeParseResult'); | ||
@@ -32,2 +33,32 @@ /* | ||
// FIXME Can be simplified once https://github.com/refractproject/minim/issues/201 is completed | ||
const hasMember = R.curry((object, key) => { | ||
const findKey = R.allPass([isMember, member => member.key.toValue() === key]); | ||
const matchingMembers = object.content.filter(findKey); | ||
return matchingMembers.length > 0; | ||
}); | ||
const validateObjectContainsRequiredKeys = R.curry((namespace, path, requiredKeys, object) => { | ||
const missingKeys = R.reject(hasMember(object), requiredKeys); | ||
const errorFromKey = key => createError(namespace, `'${path}' is missing required property '${key}'`, object); | ||
if (missingKeys.length > 0) { | ||
return new namespace.elements.ParseResult( | ||
R.map(errorFromKey, missingKeys) | ||
); | ||
} | ||
return new namespace.elements.ParseResult([object]); | ||
}); | ||
const validateObjectContainsRequiredKeysNoError = R.curry((namespace, requiredKeys, object) => { | ||
const missingKeys = R.reject(hasMember(object), requiredKeys); | ||
if (missingKeys.length > 0) { | ||
return new namespace.elements.ParseResult(); | ||
} | ||
return new namespace.elements.ParseResult([object]); | ||
}); | ||
/** | ||
@@ -64,3 +95,5 @@ * A callback for transforming a member element | ||
* @param namespace | ||
* @param name {string} - The human readable name of the element. Used for annotation messages. | ||
* @param transform {transformMember} - The callback to transform a member | ||
* @param requiredKeys {string[]} - The callback to transform a member | ||
* @param object {ObjectElement} - The object containing members to transform | ||
@@ -70,11 +103,5 @@ * | ||
*/ | ||
function parseObject(context, name, parseMember, object) { | ||
function parseObject(context, name, parseMember, requiredKeys) { | ||
const { namespace } = context; | ||
if (!isObject(object)) { | ||
return new namespace.elements.ParseResult([ | ||
createWarning(namespace, `'${name}' is not an object`, object), | ||
]); | ||
} | ||
// Create a member from a key and value | ||
@@ -119,6 +146,10 @@ const createMember = R.constructN(2, namespace.elements.Member); | ||
return validateMembers(object); | ||
return pipeParseResult(namespace, | ||
R.unless(isObject, createWarning(namespace, `'${name}' is not an object`)), | ||
validateObjectContainsRequiredKeys(namespace, name, requiredKeys || []), | ||
validateMembers, | ||
validateObjectContainsRequiredKeysNoError(namespace, requiredKeys || [])); | ||
} | ||
module.exports = R.curry(parseObject); | ||
module.exports = parseObject; |
{ | ||
"name": "fury-adapter-oas3-parser", | ||
"version": "0.4.0", | ||
"version": "0.4.1", | ||
"description": "Open API Specification 3 API Elements Parser", | ||
@@ -5,0 +5,0 @@ "author": "Apiary.io <support@apiary.io>", |
@@ -70,3 +70,3 @@ # OpenAPI Support | ||
| operationId | ✓ | | ||
| parameters | [✕](https://github.com/apiaryio/api-elements.js/issues/65) | | ||
| parameters | [~](https://github.com/apiaryio/api-elements.js/issues/65) | | ||
| requestBody | ✕ | | ||
@@ -73,0 +73,0 @@ | responses | [~](#responses-object) | |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
1741
0
81588