Procházet zdrojové kódy

refactor: enable a result that better aligns w/ the way errors are reported by a DSL that I use at work

Sam Jaffe před 3 týdny
rodič
revize
7edc91c9dc
2 změnil soubory, kde provedl 80 přidání a 21 odebrání
  1. 1 1
      include/jvalidate/validation_visitor.h
  2. 79 20
      src/validate.cxx

+ 1 - 1
include/jvalidate/validation_visitor.h

@@ -782,7 +782,7 @@ private:
     if (status == Status::Accept and visited_) {
       VISITED(K).insert(key);
     }
-    if (status == Status::Reject and result_) {
+    if ((status == Status::Reject or tracking_ == StoreResults::ForAnything) and result_) {
       result_->merge(std::move(result));
     }
     return status;

+ 79 - 20
src/validate.cxx

@@ -3,7 +3,9 @@
 #include <filesystem>
 #include <fstream>
 #include <iostream>
+#include <sstream>
 #include <stdexcept>
+#include <string_view>
 
 #include <curl/curl.h>
 
@@ -37,17 +39,6 @@ size_t transfer_to_buffer(char * data, size_t size, size_t nmemb, void * userdat
   return actual_size;
 }
 
-Json::Value load_stream(std::filesystem::path const & file) {
-  std::ifstream in(file);
-  Json::Value json;
-  Json::CharReaderBuilder builder;
-  std::string error;
-  if (!Json::parseFromStream(builder, in, &json, &error)) {
-    throw std::invalid_argument(error);
-  }
-  return json;
-}
-
 bool curl_get(jvalidate::URI const & uri, Json::Value & out) {
   if (uri.scheme().starts_with("http")) {
     std::stringstream ss;
@@ -72,23 +63,91 @@ bool curl_get(jvalidate::URI const & uri, Json::Value & out) {
   }
 }
 
+struct ProgramArgs {
+  ProgramArgs(std::string_view program, std::vector<std::string_view> args) {
+    size_t nargs = 0;
+    for (size_t i = 0; i < args.size(); ++i) {
+      if (args[i] == "--verbose" || args[i] == "-v") {
+        verbose = true;
+      } else if (args[i] == "--explain") {
+        format_as_explaination = true;
+      } else {
+        switch (nargs++) {
+        case 0:
+          schema = args[i];
+          break;
+        case 1:
+          object = args[i];
+          break;
+        }
+      }
+    }
+
+    if (nargs != 2) {
+      throw std::invalid_argument("usage: " + std::string(program) +
+                                  " [--verbose|-v] [--explain] schema object");
+    }
+  }
+
+  bool verbose = false;
+  bool format_as_explaination = false;
+  std::string_view schema;
+  std::string_view object;
+};
+
 int main(int argc, char const * const * argv) {
-  if (argc <= 2) {
-    std::cerr << "usage: " << argv[0] << " schema object\n";
+  ProgramArgs const args{argv[0], {argv + 1, argv + argc}};
+
+  Json::Value jschema;
+  Json::Value jobject;
+  if (!load_file(args.schema, jschema) || !load_file(args.object, jobject)) {
     return EXIT_FAILURE;
   }
 
-  Json::Value jschema = load_stream(argv[1]);
-  Json::Value jobject = load_stream(argv[2]);
-
   using enum jvalidate::schema::Version;
   jvalidate::Schema schema(jschema, Draft2020_12, &curl_get);
 
   jvalidate::ValidationResult result;
-  if (!jvalidate::Validator(schema, {.only_return_results_with_error = true})
-           .validate(jobject, &result)) {
-    std::cout << result;
+  bool compact_error = !args.verbose && !args.format_as_explaination;
+  if (jvalidate::Validator(schema, {.only_return_results_with_error = compact_error})
+          .validate(jobject, &result)) {
+    return EXIT_SUCCESS;
+  }
+
+  if (!args.format_as_explaination) {
+    std::cout << result << "\n";
+    return EXIT_FAILURE;
+  }
+
+  std::stringstream ss;
+  ss << result;
+  Json::Value json;
+  load_stream(ss, json);
+
+  std::map<std::string, Json::Value> because;
+  for (Json::Value const & elem : json["details"]) {
+    std::string const & path = elem["evaluationPath"].asString();
+    if (size_t pos = path.find("/if"); pos != std::string::npos && elem.isMember("errors")) {
+      because.emplace(path.substr(0, pos), elem);
+    }
+  }
+
+  Json::Value out;
+  for (Json::Value & elem : json["details"]) {
+    if (!elem.isMember("errors") || elem["valid"].asBool()) {
+      continue;
+    }
+
+    for (std::string const & path = elem["evaluationPath"].asString();
+         auto const & [key, reason] : because) {
+      if (path.starts_with(key)) {
+        elem["because"] = reason;
+      }
+    }
+
+    out.append(std::move(elem));
   }
+  std::cout << out << std::endl;
 
-  return EXIT_SUCCESS;
+  return EXIT_FAILURE;
 }