Skip to content

Commit

Permalink
builtins.validateJsonSchema
Browse files Browse the repository at this point in the history
- Add https://github.com/pboettch/json-schema-validator
  as a dependency

  `json-schema-validator` is a library that uses `nlohmann` internally,
  which is being used in other components of Nix already

  `json-schema-validator` is MIT licensed,
  complies with the latest draft of JSON schema
  and is the second one with most stars on GitHub
- Add a new builtin for validating data with a JSON schema.

  This builtin receives two arguments: `data` and `schema`,
  both in Nix format
- Demo:
  ```nix
  let
    schema = {
      title = "A person";
      properties = {
        age = {
          description = "Age of the person";
          type = "number";
          minimum = 1;
          maximum = 200;
        };
        name = {
          description = "Complete Name for the person";
          first.type = "string";
          last.type = "string";
          required = [ "first" "last" ];
          type = "object";
        };
      };
      required = [ "name" "age" ];
      type = "object";
    };
  in
  builtins.map (builtins.validateJsonSchema schema) [
    { }
    { age = 24; name.first = "Jane"; }
    { age = 24; name.first = "Jane"; name.last = "Doe"; }
  ]
  ```

  ```bash
  $ nix-instantiate --eval --strict test.nix
  [
    { success = false;
      value = "At '/', required property 'name' not found in object"; }
    { success = false;
      value = "At '/name', required property 'last' not found in object"; }
    { success = true;
      value = { age = 24; name = { first = "Jane"; last = "Doe"; }; }; }
  ]
  ```

- Pending work:
  - Add docs
  - Add tests

Mentions NixOS#5392
  • Loading branch information
kamadorueda committed Nov 3, 2021
1 parent 886ad00 commit 1dbbcee
Show file tree
Hide file tree
Showing 8 changed files with 2,692 additions and 0 deletions.
74 changes: 74 additions & 0 deletions src/libexpr/primops/validateJsonSchema.cc
@@ -0,0 +1,74 @@
#include "eval-inline.hh"
#include "primops.hh"
#include "value-to-json.hh"

#include <iostream>
#include <sstream>

#include <nlohmann/json-patch.cpp>
#include <nlohmann/json-schema.hpp>
#include <nlohmann/json-uri.cpp>
#include <nlohmann/json-validator.cpp>
#include <nlohmann/json.hpp>

class custom_error_handler : public error_handler
{
void error(const json::json_pointer &ptr, const json &instance, const std::string &message) override
{
std::string pos = ptr.to_string();

if (pos == "")
pos = "/";

throw std::invalid_argument("At '" + pos + "', " + message);
}
};

namespace nix
{

static void prim_validateJsonSchema(EvalState &state, const Pos &pos, Value **args, Value &v)
{
state.forceValue(*args[0], pos);
state.forceValue(*args[1], pos);

PathSet context;
std::ostringstream dataStr;
std::ostringstream schemaStr;
printValueAsJSON(state, true, *args[0], schemaStr, context);
printValueAsJSON(state, true, *args[1], dataStr, context);

nlohmann::json dataJson = nlohmann::json::parse(dataStr.str());
nlohmann::json schemaJson = nlohmann::json::parse(schemaStr.str());

nlohmann::json_schema::json_validator validator;
custom_error_handler validator_error_handler;

state.mkAttrs(v, 2);
try
{
validator.set_root_schema(schemaJson);
validator.validate(dataJson, validator_error_handler);
v.attrs->push_back(Attr(state.sValue, args[1]));
mkBool(*state.allocAttr(v, state.symbols.create("success")), true);
}
catch (const std::exception &e)
{
Value *error = state.allocValue();
mkString(*error, e.what());
v.attrs->push_back(Attr(state.sValue, error));
mkBool(*state.allocAttr(v, state.symbols.create("success")), false);
}
v.attrs->sort();
};

static RegisterPrimOp r_validateJsonSchema({
.name = "validateJsonSchema",
.args = {"schema", "data"},
.doc = R"(
Validate data with the provided JSON schema.
)",
.fun = prim_validateJsonSchema,
});

} // namespace nix
121 changes: 121 additions & 0 deletions src/nlohmann/json-patch.cpp
@@ -0,0 +1,121 @@
/*
* JSON schema validator for JSON for modern C++
*
* Copyright (c) 2016-2019 Patrick Boettcher <p@yai.se>.
*
* SPDX-License-Identifier: MIT
*
*/
#include "json-patch.hpp"

#include <nlohmann/json-schema.hpp>

namespace
{

// originally from http://jsonpatch.com/, http://json.schemastore.org/json-patch
// with fixes
const nlohmann::json patch_schema = R"patch({
"title": "JSON schema for JSONPatch files",
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "array",
"items": {
"oneOf": [
{
"additionalProperties": false,
"required": [ "value", "op", "path"],
"properties": {
"path" : { "$ref": "#/definitions/path" },
"op": {
"description": "The operation to perform.",
"type": "string",
"enum": [ "add", "replace", "test" ]
},
"value": {
"description": "The value to add, replace or test."
}
}
},
{
"additionalProperties": false,
"required": [ "op", "path"],
"properties": {
"path" : { "$ref": "#/definitions/path" },
"op": {
"description": "The operation to perform.",
"type": "string",
"enum": [ "remove" ]
}
}
},
{
"additionalProperties": false,
"required": [ "from", "op", "path" ],
"properties": {
"path" : { "$ref": "#/definitions/path" },
"op": {
"description": "The operation to perform.",
"type": "string",
"enum": [ "move", "copy" ]
},
"from": {
"$ref": "#/definitions/path",
"description": "A JSON Pointer path pointing to the location to move/copy from."
}
}
}
]
},
"definitions": {
"path": {
"description": "A JSON Pointer path.",
"type": "string"
}
}
})patch"_json;
} // namespace

namespace nlohmann
{

json_patch::json_patch(json &&patch) : j_(std::move(patch))
{
validateJsonPatch(j_);
}

json_patch::json_patch(const json &patch) : j_(std::move(patch))
{
validateJsonPatch(j_);
}

json_patch &json_patch::add(const json::json_pointer &ptr, json value)
{
j_.push_back(json{{"op", "add"}, {"path", ptr}, {"value", std::move(value)}});
return *this;
}

json_patch &json_patch::replace(const json::json_pointer &ptr, json value)
{
j_.push_back(json{{"op", "replace"}, {"path", ptr}, {"value", std::move(value)}});
return *this;
}

json_patch &json_patch::remove(const json::json_pointer &ptr)
{
j_.push_back(json{{"op", "remove"}, {"path", ptr}});
return *this;
}

void json_patch::validateJsonPatch(json const &patch)
{
// static put here to have it created at the first usage of validateJsonPatch
static nlohmann::json_schema::json_validator patch_validator(patch_schema);

patch_validator.validate(patch);

for (auto const &op : patch)
json::json_pointer(op["path"].get<std::string>());
}

} // namespace nlohmann
53 changes: 53 additions & 0 deletions src/nlohmann/json-patch.hpp
@@ -0,0 +1,53 @@
/*
* JSON schema validator for JSON for modern C++
*
* Copyright (c) 2016-2019 Patrick Boettcher <p@yai.se>.
*
* SPDX-License-Identifier: MIT
*
*/
#pragma once

#include <nlohmann/json.hpp>
#include <string>

namespace nlohmann
{
class JsonPatchFormatException : public std::exception
{
public:
explicit JsonPatchFormatException(std::string msg) : ex_{std::move(msg)}
{
}

inline const char *what() const noexcept override final
{
return ex_.c_str();
}

private:
std::string ex_;
};

class json_patch
{
public:
json_patch() = default;
json_patch(json &&patch);
json_patch(const json &patch);

json_patch &add(const json::json_pointer &, json value);
json_patch &replace(const json::json_pointer &, json value);
json_patch &remove(const json::json_pointer &);

operator json() const
{
return j_;
}

private:
json j_;

static void validateJsonPatch(json const &patch);
};
} // namespace nlohmann

0 comments on commit 1dbbcee

Please sign in to comment.