Skip to content

Commit fb95c7b

Browse files
author
ehennum
committed
validate *.api against JSON schema during code generation #558
1 parent dabf32f commit fb95c7b

File tree

5 files changed

+197
-7
lines changed

5 files changed

+197
-7
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright (c) 2020 MarkLogic Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
'use strict';
17+
18+
const Ajv = require('ajv');
19+
20+
const endpointDeclarationSchema =
21+
{"$schema": "http://json-schema.org/draft-07/schema#",
22+
"title": "MarkLogic Endpoint Function Declaration",
23+
"$comment": "SIMPLIFIED TO THE STABLE DECLARATIONS USED FOR CODE GENERATION",
24+
"type": "object",
25+
"definitions": {
26+
"desc": {
27+
"type":"string", "description":"Documentation about the property"
28+
},
29+
"datatype": {
30+
"type":"string", "description":"The type of the value",
31+
"enum":[
32+
"boolean", "date", "dateTime", "dayTimeDuration", "decimal", "double", "float",
33+
"int", "long", "string", "time", "unsignedInt", "unsignedLong",
34+
"array", "object",
35+
"binaryDocument", "jsonDocument", "textDocument", "xmlDocument",
36+
"session"
37+
]
38+
},
39+
"nullable": {
40+
"type":"boolean", "description":"Whether a null value is allowed",
41+
"default":false
42+
},
43+
"multiple": {
44+
"type":"boolean", "description":"Whether multiple values are allowed",
45+
"default":false
46+
}
47+
// "$comment": "SIMPLIFIED BY DELETING doubleMeter, doubleLiteral, ulMeter, AND unsignedLongLiteral"
48+
},
49+
"propertyNames": {
50+
// "$comment": "MODIFIED TO ALLOW FOR PROPERTIES DELETED DURING SIMPLIFICATION OR ADDED IN LATER RELEASES",
51+
"pattern": "^\\$?[A-Za-z_][\\w.-]*$"
52+
},
53+
"properties": {
54+
"functionName": {
55+
"type":"string", "description":"The name of a database function provided by a service declared by service.json"
56+
},
57+
"endpoint": {
58+
"type":"string", "description":"The full path name of a standalone bulk IO endpoint"
59+
},
60+
"desc": {"$ref":"#/definitions/desc"},
61+
"params": {
62+
"type":"array", "description":"The parameters of the function",
63+
"items": {
64+
"type":"object",
65+
"required": ["name", "datatype"],
66+
"propertyNames": {
67+
"pattern": "^(\\$[A-Za-z_][\\w.-]*|name|desc|datatype|nullable|multiple)$"
68+
},
69+
"properties": {
70+
"name": {
71+
"type":"string", "description":"The name of the function parameter"
72+
},
73+
"desc": {"$ref":"#/definitions/desc"},
74+
"datatype": {"$ref":"#/definitions/datatype"},
75+
"nullable": {"$ref":"#/definitions/nullable"},
76+
"multiple": {"$ref":"#/definitions/multiple"}
77+
}
78+
}
79+
},
80+
"return": {
81+
"type":"object", "description":"The return value of the function",
82+
"required": ["datatype"],
83+
"propertyNames": {
84+
"pattern": "^(\\$[A-Za-z_][\\w.-]*|desc|datatype|nullable|multiple)$"
85+
},
86+
"properties": {
87+
"desc": {"$ref":"#/definitions/desc"},
88+
"datatype": {"$ref":"#/definitions/datatype"},
89+
"nullable": {"$ref":"#/definitions/nullable"},
90+
"multiple": {"$ref":"#/definitions/multiple"}
91+
}
92+
}
93+
// "$comment": "SIMPLIFIED BY DELETING errorDetail AND monitoring"
94+
}
95+
};
96+
97+
const ajv = new Ajv({
98+
allErrors: true,
99+
validateSchema: false // true
100+
});
101+
102+
const validateWithSchema = ajv.compile(endpointDeclarationSchema);
103+
104+
function validate(endpointDeclaration) {
105+
const isValid = validateWithSchema(endpointDeclaration);
106+
const result = {isValid: isValid};
107+
if (!isValid) {
108+
result.errors = validateWithSchema.errors;
109+
}
110+
return result;
111+
}
112+
113+
module.exports = {
114+
validate: validate
115+
};

lib/proxy-generator.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const {generate} = require('astring');
2525

2626
const Vinyl = require('./optional.js').library('vinyl');
2727

28+
const endpointDeclarationValidator = require('./endpointDeclarationValidator');
29+
2830
// START SHARED WITH Modules/MarkLogic/rest-api/lib/openapi-transform.sjs IN THE SERVER
2931
function expandDataDeclaration(id, dataDeclaration) {
3032
const datatype = dataDeclaration.datatype;
@@ -612,6 +614,10 @@ function generateModuleSource(moduleName, servicedef, endpointdefs) {
612614
} else if (endpoint.declaration.functionName === void 0 || endpoint.declaration.functionName === null) {
613615
throw new Error(`endpoint declaration without functionName property`);
614616
}
617+
const validationResult = endpointDeclarationValidator.validate(endpoint.declaration);
618+
if (!(validationResult.isValid)) {
619+
throw new Error(`invalid endpoint declaration ${validationResult.errors}`);
620+
}
615621
});
616622
}
617623
const proxyAST = buildModule(moduleName, servicedef, endpointdefs);
@@ -805,5 +811,6 @@ function transformDirectoryToSource() {
805811
module.exports = {
806812
generateSource: generateModuleSource,
807813
readDeclarations: readDeclarationsFromDirectory,
808-
generate: transformDirectoryToSource
814+
generate: transformDirectoryToSource,
815+
validate: endpointDeclarationValidator.validate
809816
};

npm-shrinkwrap.json

Lines changed: 51 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"node": ">=10.15.0"
4444
},
4545
"devDependencies": {
46+
"ajv": "^6.12.4",
4647
"ast-types": "^0.13.2",
4748
"astring": "^1.4.3",
4849
"bunyan": "^1.8.12",

test-basic-proxy/lib/negative/badGenerationTest.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,26 @@ describe('negative generation', function() {
175175
done();
176176
}
177177
});
178+
it('with endpoint failing to match schema', function(done) {
179+
[
180+
'{"functionName":1}',
181+
'{"functionName":"numericParams", "params":1}',
182+
'{"functionName":"numericParamsItem", "params":[1]}',
183+
'{"functionName":"missingParamsName", "params":[{"datatype":"int"}]}',
184+
'{"functionName":"numericParamsName", "params":[{"name":1, "datatype":"int"}]}',
185+
'{"functionName":"missingParamsDataType", "params":[{"name":"p1"}]}',
186+
'{"functionName":"incorrectParamsDataType", "params":[{"name":"p1", "datatype":"notAType"}]}',
187+
'{"functionName":"stringParamsMultiple", "params":[{"name":"p1", "datatype":"int", "multiple":"true"}]}',
188+
'{"functionName":"stringParamsNullable", "params":[{"name":"p1", "datatype":"int", "nullable":"true"}]}',
189+
'{"functionName":"numericReturn", "return":1}',
190+
'{"functionName":"missingReturnDataType", "return":{"multiple":true}}',
191+
'{"functionName":"incorrectReturnDataType", "return":{"datatype":"notAType"}}',
192+
'{"functionName":"stringReturnMultiple", "return":{"datatype":"int", "multiple":"true"}}',
193+
'{"functionName":"stringReturnNullable", "return":{"datatype":"int", "nullable":"true"}}'
194+
].forEach((badApi, i) => {
195+
const validationResult = proxy.validate(JSON.parse(badApi));
196+
expect(validationResult.isValid).to.eql(false);
197+
});
198+
done();
199+
});
178200
});

0 commit comments

Comments
 (0)