Skip to content

Commit

Permalink
feature(cli) Add functional validation option, cleanup options
Browse files Browse the repository at this point in the history
Signed-off-by: Jerome Simeon <[email protected]>
  • Loading branch information
jeromesimeon committed Mar 28, 2021
1 parent fa9bcd0 commit 7c39b8b
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 61 deletions.
29 changes: 15 additions & 14 deletions packages/concerto-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ Options:
--version Show version number [boolean]
--verbose, -v [default: false]
--help Show help [boolean]
```

### Concerto validate

The `validate` command lets you check whether a JSON sample is a valid instance of the given model.
The `validate` command lets you check whether a JSON input is a valid instance of the given model.

```
concerto validate
Expand All @@ -31,9 +32,10 @@ Options:
--version Show version number [boolean]
--verbose, -v [default: false]
--help Show help [boolean]
--sample sample JSON to validate [string] [default: "sample.json"]
--ctoSystem system model to be used [string]
--ctoFiles array of CTO files [array] [default: "."]
--input JSON to validate [string]
--model array of concerto (cto) model files [array]
--offline do not resolve external models [boolean] [default: false]
--functional new validation API [boolean] [default: false]
```

### Concerto compile
Expand All @@ -49,46 +51,46 @@ Options:
--version Show version number [boolean]
--verbose, -v [default: false]
--help Show help [boolean]
--ctoSystem system model to be used [string]
--ctoFiles array of CTO files [array] [default: "."]
--model array of concerto (cto) model files [array] [required]
--offline do not resolve external models [boolean] [default: false]
--target target of the code generation [string] [default: "JSONSchema"]
--output output directory path [string] [default: "./output/"]
```

#### Go Lang

```
concerto compile --ctoFiles modelfile.cto --target Go
concerto compile --model modelfile.cto --target Go
```

#### Plant UML

```
concerto compile --ctoFiles modelfile.cto --target PlantUML
concerto compile --model modelfile.cto --target PlantUML
```

#### Typescript

```
concerto compile --ctoFiles modelfile.cto --target Typescript
concerto compile --model modelfile.cto --target Typescript
```

#### Java

```
concerto compile --ctoFiles modelfile.cto --target Java
concerto compile --model modelfile.cto --target Java
```

#### JSONSchema

```
concerto compile --ctoFiles modelfile.cto --target JSONSchema
concerto compile --model modelfile.cto --target JSONSchema
```

#### XMLSchema

```
concerto compile --ctoFiles modelfile.cto --target XMLSchema
concerto compile --model modelfile.cto --target XMLSchema
```

### Concerto Get
Expand All @@ -104,8 +106,7 @@ Options:
--version Show version number [boolean]
--verbose, -v [default: false]
--help Show help [boolean]
--ctoFiles array of local CTO files [array] [default: "."]
--ctoSystem system model to be used [string]
--model array of concerto (cto) model files [array] [required]
--output output directory path [string] [default: "./"]
```

Expand Down
44 changes: 27 additions & 17 deletions packages/concerto-cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ require('yargs')
.strict()
.usage('$0 <cmd> [args]')
.command('validate', 'validate JSON against model files', (yargs) => {
yargs.option('sample', {
describe: 'sample JSON to validate',
yargs.option('input', {
describe: 'JSON to validate',
type: 'string'
});
yargs.option('ctoFiles', {
describe: 'array of CTO files',
yargs.option('model', {
describe: 'array of concerto (cto) model files',
type: 'string',
array: true
});
Expand All @@ -39,20 +39,30 @@ require('yargs')
type: 'boolean',
default: false
});
yargs.option('functional', {
describe: 'new validation API',
type: 'boolean',
default: false
});
}, (argv) => {
if (argv.verbose) {
Logger.info(`validate sample in ${argv.sample} against the models ${argv.ctoFiles}`);
Logger.info(`validate ${argv.input} against the models ${argv.model}`);
}

try {
argv = Commands.validateValidateArgs(argv);
const options = {};
options.offline = argv.offline;
return Commands.validate(argv.sample, argv.ctoFiles, options)
options.functional = argv.functional;
return Commands.validate(argv.input, argv.model, options)
.then((result) => {
Logger.info(result);
Logger.info('Input is valid');
if (!options.functional) {
Logger.info(result);
}
})
.catch((err) => {
Logger.info('Input is invalid');
Logger.error(err.message);
});
} catch (err){
Expand All @@ -61,9 +71,9 @@ require('yargs')
}
})
.command('compile', 'generate code for a target platform', (yargs) => {
yargs.demandOption(['ctoFiles'], 'Please provide at least the CTO files');
yargs.option('ctoFiles', {
describe: 'array of CTO files',
yargs.demandOption(['model'], 'Please provide CTO models');
yargs.option('model', {
describe: 'array of concerto (cto) model files',
type: 'string',
array: true
});
Expand All @@ -84,12 +94,12 @@ require('yargs')
});
}, (argv) => {
if (argv.verbose) {
Logger.info(`generate code for target ${argv.target} from models ${argv.ctoFiles} into directory: ${argv.output}`);
Logger.info(`generate code for target ${argv.target} from models ${argv.model} into directory: ${argv.output}`);
}

const options = {};
options.offline = argv.offline;
return Commands.compile(argv.target, argv.ctoFiles, argv.output, options)
return Commands.compile(argv.target, argv.model, argv.output, options)
.then((result) => {
Logger.info(result);
})
Expand All @@ -98,9 +108,9 @@ require('yargs')
});
})
.command('get', 'save local copies of external model dependencies', (yargs) => {
yargs.demandOption(['ctoFiles'], 'Please provide at least the CTO files');
yargs.option('ctoFiles', {
describe: 'array of local CTO files',
yargs.demandOption(['model'], 'Please provide CTO models');
yargs.option('model', {
describe: 'array of concerto (cto) model files',
type: 'string',
array: true
});
Expand All @@ -111,10 +121,10 @@ require('yargs')
});
}, (argv) => {
if (argv.verbose) {
Logger.info(`saving external models from ${argv.ctoFiles} into directory: ${argv.output}`);
Logger.info(`saving external models from ${argv.model} into directory: ${argv.output}`);
}

return Commands.get(argv.ctoFiles, argv.output)
return Commands.get(argv.model, argv.output)
.then((result) => {
Logger.info(result);
})
Expand Down
22 changes: 15 additions & 7 deletions packages/concerto-cli/lib/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const Logger = require('@accordproject/concerto-core').Logger;
const ModelLoader = require('@accordproject/concerto-core').ModelLoader;
const Factory = require('@accordproject/concerto-core').Factory;
const Serializer = require('@accordproject/concerto-core').Serializer;
const Concerto = require('@accordproject/concerto-core').Concerto;
const FileWriter = require('@accordproject/concerto-tools').FileWriter;
const CodeGen = require('@accordproject/concerto-tools').CodeGen;

Expand Down Expand Up @@ -83,11 +84,11 @@ class Commands {
* @returns {object} a modfied argument object
*/
static validateValidateArgs(argv) {
argv = Commands.setDefaultFileArg(argv, 'sample', 'sample.json', ((argv, argDefaultName) => { return path.resolve('.',argDefaultName); }));
argv = Commands.setDefaultFileArg(argv, 'ctoFiles', 'model.cto', ((argv, argDefaultName) => { return [path.resolve('.',argDefaultName)]; }));
argv = Commands.setDefaultFileArg(argv, 'input', 'input.json', ((argv, argDefaultName) => { return path.resolve('.',argDefaultName); }));
argv = Commands.setDefaultFileArg(argv, 'model', 'model.cto', ((argv, argDefaultName) => { return [path.resolve('.',argDefaultName)]; }));

if(argv.verbose) {
Logger.info(`parse sample ${argv.sample} using a template ${argv.template}`);
Logger.info(`validate ${argv.input} using a model ${argv.model}`);
}

return argv;
Expand All @@ -106,11 +107,18 @@ class Commands {
const json = JSON.parse(fs.readFileSync(sample, 'utf8'));

const modelManager = await ModelLoader.loadModelManager(ctoFiles, options);
const factory = new Factory(modelManager);
const serializer = new Serializer(factory, modelManager);

const object = serializer.fromJSON(json);
return JSON.stringify(serializer.toJSON(object));
if (options.functional) {
const concerto = new Concerto(modelManager);

concerto.validate(json);
} else {
const factory = new Factory(modelManager);
const serializer = new Serializer(factory, modelManager);

const object = serializer.fromJSON(json);
return JSON.stringify(serializer.toJSON(object));
}
}

/**
Expand Down
60 changes: 37 additions & 23 deletions packages/concerto-cli/test/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,55 +28,54 @@ const Commands = require('../lib/commands');
describe('cicero-cli', () => {
const models = [path.resolve(__dirname, 'models/dom.cto'),path.resolve(__dirname, 'models/money.cto')];
const offlineModels = [path.resolve(__dirname, 'models/contract.cto'),path.resolve(__dirname, 'models/dom.cto'),path.resolve(__dirname, 'models/money.cto')];
const sample1 = path.resolve(__dirname, 'data/sample1.json');
const sample2 = path.resolve(__dirname, 'data/sample2.json');
const sampleText1 = fs.readFileSync(sample1, 'utf8');
const sampleText2 = fs.readFileSync(sample2, 'utf8');
const input1 = path.resolve(__dirname, 'data/input1.json');
const input2 = path.resolve(__dirname, 'data/input2.json');
const inputText1 = fs.readFileSync(input1, 'utf8');
const inputText2 = fs.readFileSync(input2, 'utf8');

describe('#validateValidateArgs', () => {
it('no args specified', () => {
process.chdir(path.resolve(__dirname, 'data'));
const args = Commands.validateValidateArgs({
_: ['validate'],
});
args.sample.should.match(/sample.json$/);
args.input.should.match(/input.json$/);
});

it('all args specified', () => {
process.chdir(path.resolve(__dirname, 'data'));
const args = Commands.validateValidateArgs({
_: ['validate'],
sample: 'sample1.json'
input: 'input1.json'
});
args.sample.should.match(/sample1.json$/);
args.input.should.match(/input1.json$/);
});
});

describe('#validate', () => {
describe('#validate (classic)', () => {
it('should validate against a model', async () => {
const result = await Commands.validate(sample1, models, {offline:false});
JSON.parse(result).should.deep.equal(JSON.parse(sampleText1));
const result = await Commands.validate(input1, models, {offline:false});
JSON.parse(result).should.deep.equal(JSON.parse(inputText1));
});

it('should fail to validate against a model', async () => {
try {
const result = await Commands.validate(sample2, models, {offline:false});
JSON.parse(result).should.deep.equal(JSON.parse(sampleText2));
const result = await Commands.validate(input2, models, {offline:false});
JSON.parse(result).should.deep.equal(JSON.parse(inputText2));
} catch (err) {
// XXX This fails likely because of https://github.com/accordproject/concerto/issues/245
err.message.should.equal('Instance org.accordproject.money.MonetaryAmount#null invalid enum value true for field CurrencyCode');
}
});

it('should validate against a model (offline)', async () => {
const result = await Commands.validate(sample1, offlineModels, {offline:true});
JSON.parse(result).should.deep.equal(JSON.parse(sampleText1));
const result = await Commands.validate(input1, offlineModels, {offline:true});
JSON.parse(result).should.deep.equal(JSON.parse(inputText1));
});

it('should fail to validate against a model (offline)', async () => {
try {
const result = await Commands.validate(sample2, offlineModels, {offline:true});
JSON.parse(result).should.deep.equal(JSON.parse(sampleText2));
const result = await Commands.validate(input2, offlineModels, {offline:true});
JSON.parse(result).should.deep.equal(JSON.parse(inputText2));
} catch (err) {
err.message.should.equal('Instance org.accordproject.money.MonetaryAmount#null invalid enum value true for field CurrencyCode');
}
Expand All @@ -90,20 +89,35 @@ describe('cicero-cli', () => {
});
});

it('bad sample.json', () => {
it('bad input.json', () => {
process.chdir(path.resolve(__dirname, 'data'));
(() => Commands.validateValidateArgs({
_: ['validate'],
sample: 'sample_en.json'
})).should.throw('A sample.json file is required. Try the --sample flag or create a sample.json file.');
input: 'input_en.json'
})).should.throw('A input.json file is required. Try the --input flag or create a input.json file.');
});

it('bad ctoFiles', () => {
it('bad model', () => {
process.chdir(path.resolve(__dirname, 'data'));
(() => Commands.validateValidateArgs({
_: ['validate'],
ctoFiles: ['missing.cto']
})).should.throw('A model.cto file is required. Try the --ctoFiles flag or create a model.cto file.');
model: ['missing.cto']
})).should.throw('A model.cto file is required. Try the --model flag or create a model.cto file.');
});
});

describe('#validate (functional)', () => {
it('should validate against a model', async () => {
const result = await Commands.validate(input1, models, {offline:false, functional: true});
(typeof result === 'undefined').should.equal(true);
});

it('should fail to validate against a model', async () => {
try {
await Commands.validate(input2, models, {offline:false, functional: true});
} catch (err) {
err.message.should.equal('Instance undefined invalid enum value true for field CurrencyCode');
}
});
});

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 comments on commit 7c39b8b

Please sign in to comment.