Skip to content

Instantly share code, notes, and snippets.

@gamache
Last active November 6, 2021 10:01
Show Gist options
  • Select an option

  • Save gamache/e8e24eee5bd3f190de23 to your computer and use it in GitHub Desktop.

Select an option

Save gamache/e8e24eee5bd3f190de23 to your computer and use it in GitHub Desktop.

Revisions

  1. 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}
  2. 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
    ```