File size: 2,052 Bytes
8da2481 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | defmodule Plausible.Stats.JSONSchema do
@moduledoc """
Module for validating query parameters against JSON schema.
Note that `internal` queries expose some metrics, filter types and other features not
available on the public API.
"""
use Plausible
alias Plausible.Stats.JSONSchema.Utils
alias Plausible.Stats.QueryError
@json_schema_filepath "priv/json-schemas/query-api-schema.json"
@external_resource @json_schema_filepath
@raw_schema Application.app_dir(:plausible, @json_schema_filepath)
|> File.read!()
|> Jason.decode!()
|> Utils.traverse(fn
%{"$comment" => "only :ee"} = value ->
if(ee?(), do: Map.delete(value, "$comment"), else: :remove)
value ->
value
end)
def raw_schema(), do: @raw_schema
@query_schema ExJsonSchema.Schema.resolve(@raw_schema)
def validate(params) do
case ExJsonSchema.Validator.validate(@query_schema, params) do
:ok ->
:ok
{:error, errors} ->
{:error,
%QueryError{code: :failed_schema_validation, message: format_errors(errors, params)}}
end
end
defp format_errors(errors, params) do
errors
|> Enum.map_join("\n", fn {error, path} ->
value = JSONPointer.get!(params, path)
"#{path}: #{reword(path, error, value)}"
end)
end
@no_matches "Expected exactly one of the schemata to match, but none of them did."
defp reword("#/dimensions/" <> _, @no_matches, value), do: "Invalid dimension #{i(value)}"
defp reword("#/metrics/" <> _, @no_matches, value), do: "Invalid metric #{i(value)}"
defp reword("#/filters/" <> _, @no_matches, value), do: "Invalid filter #{i(value)}"
defp reword("#/date_range", @no_matches, value), do: "Invalid date range #{i(value)}"
defp reword("#/order_by/" <> _, @no_matches, value) do
"Invalid value in order_by #{i(value)}"
end
defp reword(_path, error, _value), do: error
defp i(value), do: inspect(value, charlists: :as_lists)
end
|