ajv-cli icon indicating copy to clipboard operation
ajv-cli copied to clipboard

Must you write code to get support for custom keywords/formats? [workaround available]

Open michaelgwelch opened this issue 4 years ago • 11 comments

Since this is a cli I was hoping I could just reference a keywords.txt file or formats.txt file, but it doesn't appear that's an option.

I'll try try to follow an example from the ajv-keywords repo.

Perhaps I could even tie together a text file option once I've figured this out a little bit more.

UPDATE: 2021/05/05 For those with a similar question, I've written an npm package to address this (although not with a lot of flexibility). The details are below in this comment

michaelgwelch avatar Apr 09 '21 15:04 michaelgwelch

You can pass module(s) that define custom keywords/formats. The modules should export a function that accepts Ajv instance as a parameter. The file name should start with ".", it will be resolved relative to the current folder. The package name can also be passed - it will be used in require as is.

Can you point me to the definition of this function?

I'm looking at this file as an example: https://github.com/ajv-validator/ajv-keywords/blob/master/src/index.ts

It appears to take an instance of Ajv as well as optional keyword(s) and returns the instance of Ajv. So the way to indicate an issue is by throwing an error.

Is that correct? I guess I'll give it a try

michaelgwelch avatar Apr 14 '21 15:04 michaelgwelch

Here's my first attempt:

function customKeywords(ajv, keyword) {
  console.log(`checking ${keyword}`);

  if (keyword === 'myKeyword') {
    return ajv;
  }


  throw new Error(`Unknown keyword ${keyword}`);
}

module.exports = customKeywords;

I've called this file mykeywords.js and .mykeywords.js because the docs insist it should have to start with a period.

I attempted to invoke and get an error

> ajv compile -c mykeywords myschema.schema.json
module mykeywords.js is invalid; it should export function
error: Cannot find module 'mykeywords.js'
Require stack:
- /Users/cwelchmi/.nvm/versions/node/v12.18.3/lib/node_modules/ajv-cli/dist/commands/ajv.js
- /Users/cwelchmi/.nvm/versions/node/v12.18.3/lib/node_modules/ajv-cli/dist/commands/compile.js
- /Users/cwelchmi/.nvm/versions/node/v12.18.3/lib/node_modules/ajv-cli/dist/commands/index.js
- /Users/cwelchmi/.nvm/versions/node/v12.18.3/lib/node_modules/ajv-cli/dist/index.js

I tried invoking with and without the .js extension with the same result.

So I don't even know if my code is even close to correct, because I need to determine how to actually invoke it properly

michaelgwelch avatar Apr 14 '21 15:04 michaelgwelch

The help subsystem adds this additional blurb (not exactly in the README):

-c module(s) should export a function that accepts Ajv instance as parameter (file path should start with ".", otherwise used as require package) .json extension can be omitted (but should be used in globs)

So it looks like the reference to . means current directory (not file name, as quoted above). But the last sentence mentions .json extensions which is odd because I've never heard of code using a .json extension.

michaelgwelch avatar Apr 14 '21 15:04 michaelgwelch

I'm looking at https://github.com/ajv-validator/ajv-cli/issues/86 for some additional help. Could you add a few words about you named your example and how you use -c to reference it?

michaelgwelch avatar Apr 14 '21 15:04 michaelgwelch

Ok, by stepping thru the code what appears to be meant is that you need to give a full path, and for local files this includes a ./ prefix. In other words, in my example, ./mykeywords.js

michaelgwelch avatar Apr 14 '21 16:04 michaelgwelch

Looks like what needs to be done is just use the addKeyword method.

Here's my simple example that adds one keyword:

function customKeywords(ajv, keyword) {

    ajv.addKeyword("mykeyword");

}

module.exports = customKeywords;

michaelgwelch avatar Apr 14 '21 17:04 michaelgwelch

Here's a short little module that can be reused to read keywords from a file. I plan to write a similar module for formats.

const fs = require("fs")

function customKeywords(ajv) {
  const fileContents = fs.readFileSync("keywords.txt", {encoding: "utf8"})
  const keywords = fileContents.split(/\s+/)

  keywords.forEach((keyword) => {
    if (keyword) {
      ajv.addKeyword(keyword)
    }
  })
}

module.exports = customKeywords

Now it would be nice to be able to customize the location and file name of the keywords file, but this will meet my needs for now. "Publishing" here in case others wanted help with this.

To use this module

  • Create a file called keywords.txt with your keywords separated by white space.
  • Copy this module (named customKeywords.js) and keywords.txt to a directory
  • Invoke ajv from the same directory like this
> ajv compile -s myschema.schema.json -c ./customKeywords.js

michaelgwelch avatar Apr 14 '21 18:04 michaelgwelch

I created a new npm module as a hack. https://www.npmjs.com/package/ajv-cli-custom

It's not great because it doesn't allow you to specify the name of the file. For now it's hard coded to look in the current directory for customs.json.

installation

> npm install -g ajv-cli-custom

Create customs.json file with your keywords and formats

Create this file in the location you are running ajv-cli from. For now it must be called customs.json:

{
  "keywords": ["keyword1", "keyword2", /* ... */ ],
  "formats": ["format1", "format2", /* ... */]
}

use

> ajv compile -s myschema.schema.json -c ajv-cli-custom

If this team is open to it, I wouldn't mind trying a PR to build this functionality into ajv-cli itself. Basically, introduce a new command line option to specify a json file with custom keywords and/or formats.

michaelgwelch avatar Apr 15 '21 12:04 michaelgwelch

@michaelgwelch ajv-cli-custom looks like a great start for custom formats :)

I just want to add, for people like me, who can use the formats defined in https://github.com/ajv-validator/ajv-formats#formats, that they can easily be added with the following two steps:

  1. npm i -D ajv-formats
  2. Add -c ajv-formats to your CLI command for ajx.

Example:

ajv test -s my.schema.json -d my.json --spec=draft2019 -c ajv-formats --valid --all-errors --errors=text

dotnetCarpenter avatar Aug 14 '21 15:08 dotnetCarpenter

If you're just looking to support some custom formats, there's actually fairly little code required, as per the example on user-defined formats.

function customFormats(ajv) {
    ajv.addFormat("identifier", /^a-z\$_[a-zA-Z$_0-9]*$/)
    return ajv
}

module.exports = customFormats

Then put this in a file like formats.js and pass it to the CLI with the -c option, like -c "./formats.js"

Custom validation (going beyond regex) is just a matter of passing a validation block instead.

function customFormats(ajv) {
    ajv.addFormat(
        "byte", 
        {
            type: "number",
            validate: (x) => x >= 0 && x <= 255 && x % 1 == 0,
        }
    )
    return ajv
}

module.exports = customFormats

I would suggest adding this example to the CLI project documentation, as a quick recipe to extend the tool.

MartinDevi avatar Mar 30 '22 13:03 MartinDevi

@MartinDevi Sure I discovered that and wrote a little npm package to do this for others (as noted above. (It doesn't do any validation, just supports the use of user defined names for custom keywords and formats). Would still love to see my package replaced with a command line option built-in. The issue isn't really how little code is involved. It's how to distribute it to all of your colleagues that could benefit as well. My package helps a little but even I forget how to use it after a few months go by. :smile: I've had to go back and find it on npmjs/github and reread my own documentation.

One thing my package could benefit from is a way for a user to specify the location (and name) of the file containing their custom format and keyword names. Right now it's hard-coded to the current directly (less than ideal) and the file name customs.json

michaelgwelch avatar Mar 31 '22 13:03 michaelgwelch