Skip to content

Instantly share code, notes, and snippets.

@drewblas
Forked from gamache/validating.md
Last active March 30, 2016 14:30
Show Gist options
  • Select an option

  • Save drewblas/b8f81c9fdba1824ec2939c5847871051 to your computer and use it in GitHub Desktop.

Select an option

Save drewblas/b8f81c9fdba1824ec2939c5847871051 to your computer and use it in GitHub Desktop.

Revisions

  1. drewblas revised this gist Mar 30, 2016. 1 changed file with 14 additions and 0 deletions.
    14 changes: 14 additions & 0 deletions validating.md
    Original file line number Diff line number Diff line change
    @@ -82,6 +82,20 @@ defmodule JsonSchema do
    {:ok, schema}
    end

    ### FROM DREW:
    ### This is a more minor quible, but I thought I'd throw it in to try and help:
    #
    # Calling validate should not be done from inside the GenServer
    # Doing so means that there is essentially a single-line queue where ALL
    # validate calls are passing through this single synchronous mailbox.
    # So if you've got 1,000 phoenix connections/processes all trying to validate
    # input, they will wait one by one to validate.
    #
    # There's no reason for this, as the only thing the GenServer should be responsible
    # for is holding the state (the resolved schema). Validating doesn't CHANGE the state.
    # So a normal module function can fetch the resolved schema from the GenServer
    # and use it in the validate call.
    #
    def handle_call({:validate, object, type}, _from, schema) do
    errors = get_validation_errors(object, type, schema)
    |> transform_errors
  2. drewblas revised this gist Mar 30, 2016. 1 changed file with 5 additions and 0 deletions.
    5 changes: 5 additions & 0 deletions validating.md
    Original file line number Diff line number Diff line change
    @@ -102,6 +102,11 @@ defmodule JsonSchema do
    ## validate throws a BadMapError on certain kinds of invalid
    ## input; absorb it (TODO fix ExJsonSchema upstream)
    try do
    ### FROM DREW:
    ### This call is a big problem:
    # type_schema is a %{} (Plain map). It's *NOT* a completely resolved schema (it's no longer a %ExJsonSchema.Schema.Root{}
    # This means that it will match on https://github.com/jonasschmidt/ex_json_schema/blob/238f565a62acaf6e304c66c3cca4d80d2ef43d22/lib/ex_json_schema/validator.ex#L23-L25
    # and RE-CALL .resolve every time. Exactly what you were trying to avoid
    ExJsonSchema.Validator.validate(schema, type_schema, string_keyed_object)
    rescue
    _ -> [{"Failed validation", []}]
  3. @gamache gamache revised this gist Jan 20, 2016. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions validating.md
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    ## `schema.json`
    ## `priv/schema.json`

    ```json
    {
    @@ -76,7 +76,7 @@ defmodule JsonSchema do
    use GenServer

    def init(_) do
    schema = File.read!("./schema.json")
    schema = File.read!(Application.app_dir(:myapp) <> "/priv/schema.json")
    |> Poison.decode!
    |> ExJsonSchema.Schema.resolve
    {:ok, schema}
  4. @gamache gamache created this gist Jan 20, 2016.
    129 changes: 129 additions & 0 deletions validating.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,129 @@
    ## `schema.json`

    ```json
    {
    "$schema": "http://json-schema.org/draft-04/schema#",
    "title": "Events API Schema",

    "definitions": {

    "event_collection": {
    "type": "object",
    "required": ["events"],
    "properties": {
    "events": {
    "type": "array",
    "items": {
    "$ref": "#/definitions/event"
    }
    }
    }
    },

    "event": {
    "type": "object",
    "required": ["name", "timestamp"],
    "properties": {
    "name": {
    "type": "string"
    },
    "timestamp": {
    "type": ["string", "integer"],
    "format": "date-time"
    },
    "attributes": {
    "type": "object"
    }
    }
    }
    }
    }
    ```

    ## `json_schema.ex`
    ```elixir
    defmodule JsonSchema do
    @moduledoc ~S"""
    A service which validates objects according to types defined
    in `schema.json`.
    """

    @doc ~S"""
    Validates an object by type. Returns a list of {msg, [columns]} tuples
    describing any validation errors, or [] if validation succeeded.
    """
    def validate(server \\ :json_schema, object, type) do
    GenServer.call(server, {:validate, object, type})
    end

    @doc ~S"""
    Returns true if the object is valid according to the specified type,
    false otherwise.
    """
    def valid?(server \\ :json_schema, object, type) do
    [] == validate(server, object, type)
    end

    @doc ~S"""
    Converts the output of `validate/3` into a JSON-compatible structure,
    a list of error messages.
    """
    def errors_to_json(errors) do
    errors |> Enum.map(fn ({msg, _cols}) -> msg end)
    end


    use GenServer

    def init(_) do
    schema = File.read!("./schema.json")
    |> Poison.decode!
    |> ExJsonSchema.Schema.resolve
    {:ok, schema}
    end

    def handle_call({:validate, object, type}, _from, schema) do
    errors = get_validation_errors(object, type, schema)
    |> transform_errors
    {:reply, errors, schema}
    end

    defp get_validation_errors(object, type, schema) do
    type_string = type |> to_string
    type_schema = schema.schema["definitions"][type_string]

    not_a_struct = case object do
    %{__struct__: _} -> Map.from_struct(object)
    _ -> object
    end

    string_keyed_object = ensure_key_strings(not_a_struct)

    ## validate throws a BadMapError on certain kinds of invalid
    ## input; absorb it (TODO fix ExJsonSchema upstream)
    try do
    ExJsonSchema.Validator.validate(schema, type_schema, string_keyed_object)
    rescue
    _ -> [{"Failed validation", []}]
    end
    end

    @doc ~S"""
    Makes sure that all the keys in the map are strings and not atoms.
    Works on nested data structures.
    """
    defp ensure_key_strings(x) do
    cond do
    is_map x ->
    Enum.reduce x, %{}, fn({k,v}, acc) ->
    Map.put acc, to_string(k), ensure_key_strings(v)
    end
    is_list x ->
    Enum.map(x, fn (v) -> ensure_key_strings(v) end)
    true ->
    x
    end
    end

    end
    ```