jonasschmidt / ex_json_schema

An Elixir JSON Schema validator
MIT License
366 stars 98 forks source link

Internationalization Support #8

Closed ericbmerritt closed 5 years ago

ericbmerritt commented 8 years ago

We have a need to present error messages from ex_json_schema in various languages. Its work I am willing to do, and you should see a PR from me eventually. However, I thought I would get it on your radar to see if you had any comments.

x4lldux commented 8 years ago

Instead of internationalization, I would opt for separating validations and human-readable reporting into two different steps, like so:

iex> vowels_schema = %{
  "$schema" => "http://json-schema.org/draft-04/schema",
  "properties" => %{
      "vowels" => %{
        "items" => %{
          "enum" => [ "a", "e", "o", "u", "i" ],
          "minItemsh" => 2, 
          "type" => "string"
        },
        "type" => "array"
      }
    },
  "required" => ["vowels"],
  "type" => "object"
}

iex> vs = ExJsonSchema.Schema.resolve vowels
...

iex> {:error, errs} = ExJsonSchema.Validator.validate vs, %{"vowels" => [3]}
{:error, [{:not_allowed_in_enum, 3, "#/vowels/0"},
  {:type_mismatch, :string, :integer, "#/vowels/0"},
  {:minimum_items_needed, 2, 1, "#/vowels"},
  {:required_not_present, "boom", "#"}]}

iex> ExJsonSchema.Formatter.format errs 
 [{"Value 3 is not allowed in enum.", "#/vowels/0"},
  {"Type mismatch. Expected String but got Integer.", "#/vowels/0"},
  {"Expected a minimum of 2 items but got 1.", "#/vowels"},
  {"Required property boom was not present.", "#"}]

That way validation can be processed by our logic and formatter could do internalization.

x4lldux commented 8 years ago

POC looks like this X4lldux/ex_json_schema@bff986de90d5954cc6daef6babfc60221029497e and works like this:

iex(1)> v=%{"$schema" => "http://json-schema.org/draft-04/schema",
...(1)>   "additionalProperties" => false,
...(1)>   "properties" => %{"vowels" => %{"additionalItems" => false,
...(1)>       "items" => %{"enum" => ["a", "e", "o", "u", "i"], "type" => "string"},
...(1)>       "minItems" => 2, "type" => "array"}}, "required" => ["vowels", "boom"],
...(1)>   "type" => "object"}
%{"$schema" => "http://json-schema.org/draft-04/schema",
  "additionalProperties" => false,
  "properties" => %{"vowels" => %{"additionalItems" => false,
      "items" => %{"enum" => ["a", "e", "o", "u", "i"], "type" => "string"},
      "minItems" => 2, "type" => "array"}}, "required" => ["vowels", "boom"],
  "type" => "object"}
iex(2)> {:error, errs} = v |> ExJsonSchema.Schema.resolve |> ExJsonSchema.Validator.validate(%{"asd"=> 1, "vowels" => [1]})   
{:error,
 [{#ExJsonSchemaValidatorError<{NotAllowedInEnum, "1"}>, "#/vowels/0"},
  {#ExJsonSchemaValidatorError<{TypeMismatch, "String", "Integer"}>,
   "#/vowels/0"},
  {#ExJsonSchemaValidatorError<{TooFewItems, 2, 1}>, "#/vowels"},
  {#ExJsonSchemaValidatorError<{AdditionalPropertiesNotAllowed, "asd"}>,
   "#/asd"}, {#ExJsonSchemaValidatorError<{RequiredNotPresent, "boom"}>, "#"}]}
iex(3)> errs |> ExJsonSchema.Formatter.format
[{"Value 1 is not allowed in enum.", "#/vowels/0"},
 {"Type mismatch. Expected String but got Integer.", "#/vowels/0"},
 {"Expected a minimum of 2 items but got 1.", "#/vowels"},
 {"Schema does not allow additional properties: \"asd\".", "#/asd"},
 {"Required property boom was not present.", "#"}]

It introduces new dependency disc_union, but covering 30 cases of possible errors can be prone to typos and/or omitting some something - this gives compile-time warnings about it.

The benefits are cleaner separation, tests can be done against predefined identifiers and not text messages and grouping all possible errors in one place.

@ericbmerritt extracting messages from ExJsonSchema.Formatter to PO files, shouldn't be a problem now. @jonasschmidt what's your take on this?

jonasschmidt commented 8 years ago

Sorry about the late response, I was on vacation until today.

@ericbmerritt I agree with @X4lldux that localisation should be kept separate from the validator itself. It's more of an UI concern and not part of the actual validation. Would be nice to have it as a separate library though, based on a solution like what @X4lldux proposes as an integration point.

@X4lldux I like that approach. I will look into your solution this week.

x4lldux commented 8 years ago

I think that default "stringification" modules should be included - current messages are clear and will definitely satisfy most use cases. This could be done either by a dedicated function (like that ExJsonSchema.Formatter.format/1) or by implementing a Inspect protocol for ExJsonSchema.Validator.Error - but this is a sketchy solution., so better don't do it ;)

@jonasschmidt I'm gonna apologize a head of time, I start my vacation this week so I also will be late to response.

ericbmerritt commented 8 years ago

@jonasschmidt I agree that it should be kept separate. However, the library should support it in some way (returning error identifiers rather than strings or some such). I like the approach described here. It would solve my use case.

jonasschmidt commented 8 years ago

Great, I will have a solution ready as soon as possible.

jonasschmidt commented 5 years ago

This can (finally) be solved with a custom error formatter in the current (0.6.x) version. Examples of how to do that are available in the README.