Skip to content

Commit

Permalink
feat(OpenApi3.1-Yaml): add support for Server and ServerVariable
Browse files Browse the repository at this point in the history
Refs #1
  • Loading branch information
char0n committed Oct 13, 2020
1 parent b4161b6 commit 51de6e4
Show file tree
Hide file tree
Showing 18 changed files with 276 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import stampit from 'stampit';
import {
either,
unnest,
flatten,
propOr,
pathOr,
find,
Expand Down Expand Up @@ -399,7 +400,7 @@ const Visitor = stampit({

this.sequence = {
leave(node: YamlSequence) {
node.children = unnest(node.children);
node.children = flatten(node.children);
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const ServersVisitor = stampit(ValueVisitor, SpecificationVisitor, {
array(arrayNode) {
arrayNode.items.forEach((item: JsonNode) => {
if (isServerObject({}, item)) {
console.dir('server object');
const element = this.nodeToElement(['document', 'objects', 'Server'], item);
this.element.push(element);
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,32 @@
import { isYamlKeyValuePair } from 'apidom-ast';
import { pathSatisfies, startsWith, both, curry } from 'ramda';
import { isYamlKeyValuePair, isYamlMapping, isYamlScalar } from 'apidom-ast';
import { pathSatisfies, startsWith, both, curry, filter, anyPass } from 'ramda';

// hasKey :: String -> YamlKeyValuePair -> Boolean
const hasKey = curry((keyName, node) => {
const { key } = node;

if (!isYamlScalar(key)) {
return false;
}

return key.content === keyName;
});

// hasKeys :: [String] -> [YamlKeyValuePair] -> Boolean
const hasKeys = curry((keyNames, keyValuePairs) => {
const predicates = keyNames.map((keyName: string) => hasKey(keyName));
return filter(anyPass(predicates), keyValuePairs).length === keyNames.length;
});

// isOpenApiExtension :: Options -> YamlKeyValuePair -> Boolean
// eslint-disable-next-line import/prefer-default-export
export const isOpenApiExtension = curry((options, node) =>
both(isYamlKeyValuePair, pathSatisfies(startsWith('x-'), ['key', 'content']))(node),
);

// isServerObject :: Options -> YamlMapping -> Boolean
export const isServerObject = curry((options, node) => {
if (!isYamlMapping(node)) {
return false;
}
return hasKeys(['url'], node.content);
});
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ import LicenseVisitor from './visitors/open-api-3-1/license';
import LicenseNameVisitor from './visitors/open-api-3-1/license/NameVisitor';
import LicenseIdentifierVisitor from './visitors/open-api-3-1/license/IdentifierVisitor';
import LicenseUrlVisitor from './visitors/open-api-3-1/license/UrlVisitor';
import ServersVisitor from './visitors/open-api-3-1/ServersVisitor';
import ServerVisitor from './visitors/open-api-3-1/server';
import ServerUrlVisitor from './visitors/open-api-3-1/server/UrlVisitor';
import ServerDescriptionVisitor from './visitors/open-api-3-1/server/DescriptionVisitor';
import ServerVariablesVisitor from './visitors/open-api-3-1/server/VariablesVisitor';
import ServerVariableVisitor from './visitors/open-api-3-1/server-variable';
import ServerVariableEnumVisitor from './visitors/open-api-3-1/server-variable/EnumVisitor';
import ServerVariableDefaultVisitor from './visitors/open-api-3-1/server-variable/DefaultVisitor';
import ServerVariableDescriptionVisitor from './visitors/open-api-3-1/server-variable/DescriptionVisitor';

/**
* Specification object allows us to have complete control over visitors
Expand Down Expand Up @@ -49,6 +58,7 @@ const specification = {
info: {
$ref: '#/visitors/document/objects/Info',
},
servers: ServersVisitor,
},
},
Info: {
Expand Down Expand Up @@ -83,6 +93,22 @@ const specification = {
url: LicenseUrlVisitor,
},
},
Server: {
$visitor: ServerVisitor,
fixedFields: {
url: ServerUrlVisitor,
description: ServerDescriptionVisitor,
variables: ServerVariablesVisitor,
},
},
ServerVariable: {
$visitor: ServerVariableVisitor,
fixedFields: {
enum: ServerVariableEnumVisitor,
default: ServerVariableDefaultVisitor,
description: ServerVariableDescriptionVisitor,
},
},
},
extension: SpecificationExtensionVisitor,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import stampit from 'stampit';
import { noop } from 'ramda-adjunct';
import { YamlKeyValuePair, YamlMapping } from 'apidom-ast';

import SpecificationVisitor from '../SpecificationVisitor';
import { isOpenApiExtension } from '../../predicates';
import { visit } from '..';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import stampit from 'stampit';
import { isNonEmptyString } from 'ramda-adjunct';

import PatternedFieldsYamlMappingVisitor from './PatternedFieldsYamlMappingVisitor';

const MapYamlMappingVisitor = stampit(PatternedFieldsYamlMappingVisitor, {
props: {
fieldPatternPredicate: isNonEmptyString,
},
});

export default MapYamlMappingVisitor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import stampit from 'stampit';
import { F as stubFalse } from 'ramda';
import { noop } from 'ramda-adjunct';
import { YamlKeyValuePair, YamlMapping } from 'apidom-ast';

import SpecificationVisitor from '../SpecificationVisitor';
import { isOpenApiExtension } from '../../predicates';
import { visit } from '..';

const PatternedFieldsYamlMappingVisitor = stampit(SpecificationVisitor, {
props: {
fieldPatternPredicate: stubFalse,
specPath: noop,
ignoredFields: [],
keyMap: {
// @ts-ignore
[YamlMapping.type]: ['children'],
},
canSupportSpecificationExtensions: false,
},
init({
// @ts-ignore
specPath = this.specPath,
// @ts-ignore
ignoredFields = this.ignoredFields,
// @ts-ignore
canSupportSpecificationExtensions = this.canSupportSpecificationExtensions,
} = {}) {
this.specPath = specPath;
this.ignoredFields = ignoredFields;
this.canSupportSpecificationExtensions = canSupportSpecificationExtensions;
},
methods: {
mapping(mappingNode: YamlMapping) {
this.maybeAddSourceMap(mappingNode, this.element);
},

keyValuePair(keyValuePairNode: YamlKeyValuePair) {
const { key: keyNode, value: valueNode } = keyValuePairNode;
const keyName = keyNode.content;
const { MemberElement } = this.namespace.elements.Element.prototype;

if (this.canSupportSpecificationExtensions && isOpenApiExtension({}, keyValuePairNode)) {
const visitor = this.retrieveVisitorInstance(['document', 'extension']);
visit(keyValuePairNode, visitor);
this.element.content.push(visitor.element);
} else if (!this.ignoredFields.includes(keyName) && this.fieldPatternPredicate(keyName)) {
const specPath = this.specPath(valueNode);
const visitor = this.retrieveVisitorInstance(specPath);
const keyElement = new this.namespace.elements.String(keyName);

visit(keyValuePairNode, visitor);

const memberElement = this.maybeAddSourceMap(
keyValuePairNode,
new MemberElement(this.maybeAddSourceMap(keyNode, keyElement), visitor.element),
);
memberElement.classes.push('patternedField');

this.element.content.push(memberElement);
} else if (!this.ignoredFields.includes(keyName)) {
const keyElement = new this.namespace.elements.String(keyName);
const memberElement = this.maybeAddSourceMap(
keyValuePairNode,
new MemberElement(
this.maybeAddSourceMap(keyNode, keyElement),
this.nodeToElement(['kind'], valueNode),
),
);
this.element.content.push(memberElement);
}
},
},
});

export default PatternedFieldsYamlMappingVisitor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import stampit from 'stampit';
import { YamlSequence } from 'apidom-ast';

import { BREAK } from '..';
import SpecificationVisitor from '../SpecificationVisitor';
import { isServerObject } from '../../predicates';
import { KindVisitor } from '../generics';

const ServersVisitor = stampit(KindVisitor, SpecificationVisitor, {
init() {
this.element = new this.namespace.elements.Array();
this.element.classes.push('servers');
},
methods: {
sequence(sequenceNode: YamlSequence) {
sequenceNode.content.forEach((item) => {
if (isServerObject({}, item)) {
const element = this.nodeToElement(['document', 'objects', 'Server'], item);
this.element.push(element);
} else {
const element = this.nodeToElement(['kind'], item);
this.element.push(element);
}
});

this.maybeAddSourceMap(sequenceNode, this.element);

return BREAK;
},
},
});

export default ServersVisitor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import stampit from 'stampit';

import { KindVisitor } from '../../generics';

const DefaultVisitor = stampit(KindVisitor);

export default DefaultVisitor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import stampit from 'stampit';

import { KindVisitor } from '../../generics';

const DescriptionVisitor = stampit(KindVisitor);

export default DescriptionVisitor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import stampit from 'stampit';

import { KindVisitor } from '../../generics';

const EnumVisitor = stampit(KindVisitor);

export default EnumVisitor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import stampit from 'stampit';
import { always } from 'ramda';

import FixedFieldsYamlMappingVisitor from '../../generics/FixedFieldsYamlMappingVisitor';
import { KindVisitor } from '../../generics';

const ServerVariableVisitor = stampit(KindVisitor, FixedFieldsYamlMappingVisitor, {
props: {
specPath: always(['document', 'objects', 'ServerVariable']),
canSupportSpecificationExtensions: true,
},
init() {
this.element = new this.namespace.elements.ServerVariable();
},
});

export default ServerVariableVisitor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import stampit from 'stampit';

import { KindVisitor } from '../../generics';

const DescriptionVisitor = stampit(KindVisitor);

export default DescriptionVisitor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import stampit from 'stampit';

import { KindVisitor } from '../../generics';

const UrlVisitor = stampit(KindVisitor);

export default UrlVisitor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import stampit from 'stampit';
import { always } from 'ramda';

import { KindVisitor } from '../../generics';
import MapYamlMappingVisitor from '../../generics/MapYamlMappingVisitor';

const VariablesVisitor = stampit(KindVisitor, MapYamlMappingVisitor, {
props: {
specPath: always(['document', 'objects', 'ServerVariable']),
},
init() {
this.element = new this.namespace.elements.Object();
this.element.classes.push('variables');
},
});

export default VariablesVisitor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import stampit from 'stampit';
import { always } from 'ramda';

import FixedFieldsYamlMappingVisitor from '../../generics/FixedFieldsYamlMappingVisitor';
import { KindVisitor } from '../../generics';

const ServerVisitor = stampit(KindVisitor, FixedFieldsYamlMappingVisitor, {
props: {
specPath: always(['document', 'objects', 'Server']),
},
init() {
this.element = new this.namespace.elements.Server();
},
});

export default ServerVisitor;
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,17 @@ info:
x-username: char0n
url: https://www.linkedin.com/in/vladimirgorej/
email: [email protected]
servers:
- url: http://api.example.com/v1
description: Optional server description, e.g. Main (production) server
- url: http:{port}//staging-api.example.com
description: Optional server description, e.g. Internal staging server for testing
variables:
port:
enum:
- '8443'
- '443'
default: '8443'
description: Port description
...
prop: value
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import * as apiDOM from 'apidom';
import * as adapter from '../src/adapter-node';

const spec = fs.readFileSync(path.join(__dirname, 'fixtures', 'sample-api.yaml')).toString();
// const namespace = apiDOM.createNamespace(openapi3);

describe('apidom-parser-adapter-openapi3-1-yaml', function () {
it('test', async function () {
Expand All @@ -14,6 +13,6 @@ describe('apidom-parser-adapter-openapi3-1-yaml', function () {

const parseResult = await adapter.parse(spec, { sourceMap: true });
console.log(JSON.stringify(apiDOM.toValue(parseResult), null, 2));
// console.log (JSON.stringify(apiDOM.toJSON(namespace, parseResult), null, null));
// console.log(JSON.stringify(apiDOM.toJSON(adapter.namespace, parseResult), null, null));
});
});

0 comments on commit 51de6e4

Please sign in to comment.