Skip to content
1,646 changes: 907 additions & 739 deletions sdflint/sdf-validation.jso.json

Large diffs are not rendered by default.

52 changes: 33 additions & 19 deletions sdflint/sdflint.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ if (require.main === module) { /* run as stand-alone? */
let options = {};
let schemaFile;

process.argv.slice(3).forEach( (option) => {
process.argv.slice(3).forEach((option) => {
let name = option.substring(0, option.indexOf("="));
let value = option.substring(option.indexOf("=") + 1);
options[name] = value;
Expand All @@ -48,15 +48,20 @@ if (require.main === module) { /* run as stand-alone? */
try {
sdfFile = fs.readFileSync(inFile,
{ encoding: 'utf-8' });
} catch (err) {
console.error("Can't read input SDF file: " + err.message);
process.exit(1);
}
try {
schema = JSON.parse(fs.readFileSync(schemaFile,
{ encoding: 'utf-8' }));
} catch (err) {
res.errorCount++;
res.errors.parse = err.message;
}
} catch (err) {
console.error("Can't read schema file: " + err.message);
process.exit(1);
}

sdfLint(sdfFile, schema, res, options);
console.dir(res, {depth: null});
console.dir(res, { depth: null });

process.exit(res.errorCount);
}
Expand All @@ -68,7 +73,7 @@ if (require.main === module) { /* run as stand-alone? */
schema The filename of the JSON schema to use
license The license string to accept as valid
`
);
);
}
}

Expand All @@ -77,7 +82,7 @@ function fileNameCheck(fileName, res) {
let fileNameRe = new RegExp(FILENAME_RE);
let baseFileName = path.parse(fileName).base;

if (! baseFileName.match(fileNameRe)) {
if (!baseFileName.match(fileNameRe)) {
res.errorCount++;
res.errors.fileName = "File name " + baseFileName +
" does not match " + FILENAME_RE;
Expand All @@ -87,6 +92,11 @@ function fileNameCheck(fileName, res) {

function validCharsCheck(sdfFile, res) {
let sdfStr = JSON.stringify(sdfFile);
if (!sdfStr) {
res.errorCount++;
res.errors.file = "Invalid SDF file";
return;
};
let invalidLoc = sdfStr.search(new RegExp(VALID_CHARS_RE));
if (invalidLoc != -1) {
res.errorCount++;
Expand All @@ -97,13 +107,13 @@ function validCharsCheck(sdfFile, res) {


function licenseCheck(sdf, license, res) {
if (! sdf.info || ! sdf.info.license) {
if (!sdf.info || !sdf.info.license) {
res.errorCount++;
res.errors.license = "No license defined in model"
} else if (sdf.info.license != license) {
res.errorCount++;
res.errors.license = "Model has license '" + sdf.info.license +
"' expected '" + license + "'";
"' expected '" + license + "'";
}
}

Expand All @@ -112,25 +122,29 @@ function sdfLint(sdfFile, schema, res, options) {
let ajv = new Ajv(AJV_OPTIONS);
let sdf;

if (! res) {
if (!res) {
res = {
errorCount: 0,
errors: {}
};
}
if (! options) {
if (!options) {
options = {};
}

try {
sdf = JSON.parse(sdfFile);
} catch (err) {
res.errorCount++;
res.errors.parse = err.message;
if (options.isJSON) {
sdf = sdfFile; /* already parsed as JSON */
} else {
try {
sdf = JSON.parse(sdfFile);
} catch (err) {
res.errorCount++;
res.errors.parse = err.message;
}
validCharsCheck(sdfFile, res);
}
validCharsCheck(sdfFile, res);

if (! schema) {
if (!schema) {
schema = JSON.parse(fs.readFileSync(
DEF_SCHEMA_FILE, { encoding: 'utf-8' }));
}
Expand Down
15 changes: 15 additions & 0 deletions sdflint/tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Test cases for SDF schema and linter

This folder contains a simple test program and test cases for testing if the SDF linter program and JSO schema catch various errors in SDF documents.

The program `test-schema.js` takes at minimum two parameters: a (valid) SDF document used as basis for testing and a schema file used to validate the document.

For example: `node test-schema.js sdfobject-test.sdf.json ../sdf-validation.jso.json`

The `sdfobject-test.sdf.json` is an SDF document that exercises many of the features of SDF Object definition and is a good start for the SDF document used with testing.

For schema file the default validation schema of the linter can be used with `../sdf-validation.jso.json`.

In this mode the program changes one by one each quality in the SDF document first to a (semi random) integer value and then to a string value. If the linter does not detect this as an error, information about this is printed to console. For example `Missed change: info/title -> "FOOBAR"` indicates a change in the info block's title quality (that is in fact a change that is OK to not flag as error since title can be any string).

If further parameters are given to the program, those are assumed to be JSON merge-patch documents that alter the base SDF document. After applying each merge-patch, validation is run and if no error is detected, information about this is printed (if error is detected, nothing is printed unless verbose mode is used). Set of example merge-patch documents are available in the `schema-errors` folder. This mode also needs to have the "merge-patch" [command line program](https://rubygems.org/gems/merge-patch/versions/0.1.0) installed ("gem install merge-patch").
12 changes: 12 additions & 0 deletions sdflint/tests/schema-errors/misspell-type.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"sdfObject": {
"TestObject": {
"sdfProperty": {
"IntegerTestProperty": {
"type": null,
"typemisspelled" : "integer"
}
}
}
}
}
11 changes: 11 additions & 0 deletions sdflint/tests/schema-errors/numeric-description.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"sdfObject": {
"TestObject": {
"sdfProperty": {
"IntegerTestProperty": {
"description": 42
}
}
}
}
}
123 changes: 123 additions & 0 deletions sdflint/tests/sdfobject-test.sdf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
{
"info": {
"title": "Test object",
"copyright": "Example Test Corp 2023",
"license": "BSD-3-Clause",
"version": "2023-11-04",
"features": [],
"$comment": "Test comment",
"modified": "2023-11-05"
},
"namespace": {
"foo": "https://foo.example.com/models",
"bar": "https://foo.example.com/bar"
},
"defaultNamespace": "foo",
"sdfObject": {
"TestObject": {
"sdfProperty": {
"IntegerTestProperty": {
"description": "Integer temperature read-only",
"type": "integer",
"minimum": -3,
"maximum": 50,
"unit": "Cel",
"writable": false
},
"StringTestProperty": {
"label": "String Test Property",
"type": "string",
"readable": true
},
"BooleanTestProperty": {
"type": "boolean",
"nullable": true
},
"ArrayTestProperty": {
"type": "array",
"maxItems": 10,
"minItems": 2,
"items" : {
"type": "integer"
}
},
"FormatTestProperty": {
"type": "string",
"format": "uuid"
},
"EnumTestProperty": {
"type": "string",
"enum": ["foo", "bar", "baz"]
},
"ChoiceTestProperty": {
"type": "string",
"sdfChoice": {
"foo" : {
"description": "Foo value",
"const": 1
},
"bar": {
"description": "Bar value",
"const": 2
}
}
},
"DefaultValueTestProperty": {
"default": "foo",
"type": "string"
},
"ExclusiveLimitsTestProperty": {
"type": "integer",
"exclusiveMaximum": 50,
"exclusiveMinimum": -3
},
"RequiredTestProperty": {
"type": "boolean",
"sdfRequired": [
true
]
},
"RefTestProperty": {
"sdfRef": "#/sdfObject/TestObject/sdfData/TestData",
"description": "sdfRef test property"
}
},
"sdfRequired": [
"TestAction",
"#/sdfObject/TestObject/sdfProperty/IntegerTestProperty"
],
"sdfAction": {
"TestAction": {
"description": "Action for testing",
"sdfInputData": {
"type": "number"
},
"sdfOutputData": {
"type": "string"
}
},
"TestActionObjectOutput": {
"description": "Test action with Object output data",
"sdfOutputData": {
"type": "object",
"properties": {
"a": {
"type": "integer"
},
"b": {
"type": "string"
}
}
}
}
},
"sdfData": {
"TestData": {
"type": "string",
"sdfType": "byte-string",
"contentFormat": "60"
}
}
}
}
}
Loading