summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErovia <Erovia@users.noreply.github.com>2022-02-28 20:02:39 +0000
committerGitHub <noreply@github.com>2022-02-28 20:02:39 +0000
commitfbfd5312b995a32af690c183cad0dc988f695e89 (patch)
treebf241a475ec51b79d6427ac422f197b6d3720661
parent779c7debcfff1a4a3ad578a0c12bdd50cba11039 (diff)
CLI: Validate JSON keymap input (#16261)
* Fix schema validator It should use the passed schema. * Add required attributes to keymap schema * Rework subcommands to validate the JSON keymaps The 'compile', 'flash' and 'json2c' subcommands were reworked to add JSON keymap validation so error is reported for non-JSON and non-compliant-JSON inputs. * Fix required fields in keymap schema * Add tests * Fix compiling keymaps directly from keymap directory * Schema should not require version for now.
-rw-r--r--data/schemas/keymap.jsonschema7
-rwxr-xr-xlib/python/qmk/cli/json2c.py13
-rw-r--r--lib/python/qmk/commands.py19
-rw-r--r--lib/python/qmk/json_schema.py8
-rw-r--r--lib/python/qmk/path.py6
-rw-r--r--lib/python/qmk/tests/test_cli_commands.py12
6 files changed, 47 insertions, 18 deletions
diff --git a/data/schemas/keymap.jsonschema b/data/schemas/keymap.jsonschema
index faa250a942..3803301a66 100644
--- a/data/schemas/keymap.jsonschema
+++ b/data/schemas/keymap.jsonschema
@@ -53,5 +53,10 @@
"type": "string",
"description": "asdf"
}
- }
+ },
+ "required": [
+ "keyboard",
+ "layout",
+ "layers"
+ ]
}
diff --git a/lib/python/qmk/cli/json2c.py b/lib/python/qmk/cli/json2c.py
index ae8248e6b7..2873a9bfd3 100755
--- a/lib/python/qmk/cli/json2c.py
+++ b/lib/python/qmk/cli/json2c.py
@@ -1,12 +1,11 @@
"""Generate a keymap.c from a configurator export.
"""
-import json
-
from argcomplete.completers import FilesCompleter
from milc import cli
import qmk.keymap
import qmk.path
+from qmk.commands import parse_configurator_json
@cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to')
@@ -19,14 +18,8 @@ def json2c(cli):
This command uses the `qmk.keymap` module to generate a keymap.c from a configurator export. The generated keymap is written to stdout, or to a file if -o is provided.
"""
- try:
- # Parse the configurator from json file (or stdin)
- user_keymap = json.load(cli.args.filename)
-
- except json.decoder.JSONDecodeError as ex:
- cli.log.error('The JSON input does not appear to be valid.')
- cli.log.error(ex)
- return False
+ # Parse the configurator from json file (or stdin)
+ user_keymap = parse_configurator_json(cli.args.filename)
# Environment processing
if cli.args.output and cli.args.output.name == '-':
diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py
index 275cd72e5c..e38f17156a 100644
--- a/lib/python/qmk/commands.py
+++ b/lib/python/qmk/commands.py
@@ -1,6 +1,5 @@
"""Helper functions for commands.
"""
-import json
import os
import sys
import shutil
@@ -9,10 +8,11 @@ from subprocess import DEVNULL
from time import strftime
from milc import cli
+import jsonschema
import qmk.keymap
from qmk.constants import QMK_FIRMWARE, KEYBOARD_OUTPUT_PREFIX
-from qmk.json_schema import json_load
+from qmk.json_schema import json_load, validate
time_fmt = '%Y-%m-%d-%H:%M:%S'
@@ -185,6 +185,10 @@ def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_va
A command to run to compile and flash the C file.
"""
+ # In case the user passes a keymap.json from a keymap directory directly to the CLI.
+ # e.g.: qmk compile - < keyboards/clueboard/california/keymaps/default/keymap.json
+ user_keymap["keymap"] = user_keymap.get("keymap", "default_json")
+
# Write the keymap.c file
keyboard_filesafe = user_keymap['keyboard'].replace('/', '_')
target = f'{keyboard_filesafe}_{user_keymap["keymap"]}'
@@ -248,8 +252,15 @@ def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_va
def parse_configurator_json(configurator_file):
"""Open and parse a configurator json export
"""
- # FIXME(skullydazed/anyone): Add validation here
- user_keymap = json.load(configurator_file)
+ user_keymap = json_load(configurator_file)
+ # Validate against the jsonschema
+ try:
+ validate(user_keymap, 'qmk.keymap.v1')
+
+ except jsonschema.ValidationError as e:
+ cli.log.error(f'Invalid JSON keymap: {configurator_file} : {e.message}')
+ exit(1)
+
orig_keyboard = user_keymap['keyboard']
aliases = json_load(Path('data/mappings/keyboard_aliases.json'))
diff --git a/lib/python/qmk/json_schema.py b/lib/python/qmk/json_schema.py
index ffc7c6bcd1..2b48782fbb 100644
--- a/lib/python/qmk/json_schema.py
+++ b/lib/python/qmk/json_schema.py
@@ -16,7 +16,11 @@ def json_load(json_file):
Note: file must be a Path object.
"""
try:
- return hjson.load(json_file.open(encoding='utf-8'))
+ # Get the IO Stream for Path objects
+ # Not necessary if the data is provided via stdin
+ if isinstance(json_file, Path):
+ json_file = json_file.open(encoding='utf-8')
+ return hjson.load(json_file)
except (json.decoder.JSONDecodeError, hjson.HjsonDecodeError) as e:
cli.log.error('Invalid JSON encountered attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e)
@@ -62,7 +66,7 @@ def create_validator(schema):
"""Creates a validator for the given schema id.
"""
schema_store = compile_schema_store()
- resolver = jsonschema.RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store)
+ resolver = jsonschema.RefResolver.from_schema(schema_store[schema], store=schema_store)
return jsonschema.Draft7Validator(schema_store[schema], resolver=resolver).validate
diff --git a/lib/python/qmk/path.py b/lib/python/qmk/path.py
index dfb8371f84..9b94abbc12 100644
--- a/lib/python/qmk/path.py
+++ b/lib/python/qmk/path.py
@@ -70,9 +70,13 @@ def normpath(path):
class FileType(argparse.FileType):
+ def __init__(self, encoding='UTF-8'):
+ # Use UTF8 by default for stdin
+ return super().__init__(encoding=encoding)
+
def __call__(self, string):
"""normalize and check exists
otherwise magic strings like '-' for stdin resolve to bad paths
"""
norm = normpath(string)
- return super().__call__(norm if norm.exists() else string)
+ return norm if norm.exists() else super().__call__(string)
diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py
index 54b143c64f..55e69175e6 100644
--- a/lib/python/qmk/tests/test_cli_commands.py
+++ b/lib/python/qmk/tests/test_cli_commands.py
@@ -156,6 +156,18 @@ def test_json2c_stdin():
assert result.stdout == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT_ortho_1x1(KC_A)};\n\n'
+def test_json2c_wrong_json():
+ result = check_subcommand('json2c', 'keyboards/handwired/pytest/info.json')
+ check_returncode(result, [1])
+ assert 'Invalid JSON keymap' in result.stdout
+
+
+def test_json2c_no_json():
+ result = check_subcommand('json2c', 'keyboards/handwired/pytest/pytest.h')
+ check_returncode(result, [1])
+ assert 'Invalid JSON encountered' in result.stdout
+
+
def test_info():
result = check_subcommand('info', '-kb', 'handwired/pytest/basic')
check_returncode(result)