carueda/tscfg

Fully validate given config on construction

Closed this issue · 5 comments

Looking through the codegen it appears that it just calls Config.get*() for each of the config object's fields, so if any keys are missing or mistyped it will error out on the first one.

However, you can call Config.checkValid() with a prototype config and it will report errors for all missing or mistyped keys in the config with a runtime exception; we've been specifying a schema manually in our config static class (which kind of invalidates my point in #47 about not having two different sources of truth for the config schema but this way we would still only have a single spec, our default config) but it would be nice when using this library to automatically generate the prototype config and validate the config being passed to the constructor, e.g. for the example in README:

public class Cfg {
  public final Service service;
  public static class Service {
    public final boolean debug;
    public final double factor;
    public final int poolSize;
    public final String url;
  }
  
  public Cfg(Config config) {
    config.checkValid(Config.parseMap(
      Map.of(
        "service", Map.of(
          "debug", false, // the value doesn't matter, only the type
          factor, 0.0,
          poolSize, (int) 0,
          url, "",
        )
    ));

    // initialize `Cfg` if an exception was not thrown
  }
}

Sorry, I'm not quite following this (probably so much multitasking). Re "automatically generate the prototype config" perhaps the template generation capability could help (at least in part)? Re "it will error out on the first one" ... you are suggesting to use checkValid to report all possible missing/invalid entries at once? If so, I'm afraid it'd be more complicated than that... Surely I'm not really understanding your observation/suggestion.

The issue with just calling the Config.get*() methods when constructing a generated object is that the first one to have a key missing or mistyped in the config file will throw a RuntimeException; if you're initializing this in a static like we are at work then it will end up killing the process. Then you have to fix the config file, recompile because it's in src/main/resources/ and try running again, possibly just to have it die again on another key error. If you're not aware that you have the wrong config file you might end up having to repeat this several more times until you fix all the errors.

Instead, you can call Config.checkValid() and pass in a Map<String, Object> that has the same structure as the config you're expecting; the config has all the keys that are in the map and the values have the same class or are also a Map<String, Object> describing a nested object in the same manner. Config will perform a relative intersection between the map and the config structure ([map] - [config]) and throw an error listing all the mismatches at once so no more guessing and checking. (Optional keys can be omitted from the map; it won't throw an error for keys that are in the config but not in the map, only the other way around.)

Thanks for the extra clarification.

Hi @abonander I already did good progress on a complete validation of the input config (thanks again for the great suggestion), but won't be able to give this some more exhaustive testing for a while. As previously noted, this validation requires some more logic than a mere checkValid mainly due to the possibility of having optional entries (even complete subtrees in the config), so such validation has to take that into account. My focus has initially been on collecting/reporting all "missing" (required) properties, but later on this should also consider collecting all incompatible values. The code is in the 49_full_validation branch if you have some time to give it a try.

@abonander fyi just released 0.9.92 with this.