Skip to content

Commit

Permalink
Merge pull request #1765 from lud/elixir-jsv
Browse files Browse the repository at this point in the history
Work in progress: Harness for elixir-jsv
  • Loading branch information
Julian authored Jan 14, 2025
2 parents 6cbbaa6 + 5215b54 commit d0b23ec
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ updates:
schedule:
interval: "daily"

- package-ecosystem: "mix"
directory: "/implementations/elixir-jsv"
schedule:
interval: daily

# ignore: scala-mjs-validator - Dependabot doesn't support Scala.
# ignore: scala-rc-circe-json-validator - Dependabot doesn't support Scala.

Expand Down Expand Up @@ -333,6 +338,11 @@ updates:
schedule:
interval: "daily"

- package-ecosystem: "docker"
directory: "/implementations/elixir-jsv"
schedule:
interval: daily

- package-ecosystem: "docker"
directory: "/bowtie/tests/fauxmplementations/envsonschema"
schedule:
Expand Down
4 changes: 4 additions & 0 deletions implementations/elixir-jsv/.formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
37 changes: 37 additions & 0 deletions implementations/elixir-jsv/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Bowtie specific

# Revert global gitignore
!lib

# We always want fresh deps
mix.lock


# Elixir generic .gitignore

## The directory Mix will write compiled artifacts to.
/_build/

## If you run "mix test --cover", coverage assets end up here.
/cover/

## The directory Mix downloads your dependencies sources to.
/deps/

## Where third-party dependencies like ExDoc output generated docs.
/doc/

## Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

## If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

## Also ignore archive artifacts (built via "mix archive.build").
*.ez

## Ignore package tarball (built via "mix hex.build").
bowtie_jsv-*.tar

## Temporary files, for example, from tests.
/tmp/
26 changes: 26 additions & 0 deletions implementations/elixir-jsv/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Builder image ------------------------------------------------------------- #
FROM elixir:1.18-alpine

WORKDIR /app

# Install Elixir package manager
RUN mix local.hex --force && mix local.rebar --force

# Install and compile dependencies
COPY mix.exs mix.exs
RUN mix deps.get && mix deps.compile

# Copy the actual code
COPY lib lib

# Generate a production release
ENV MIX_ENV=prod
RUN mix compile && mix release --overwrite

# Runner image -------------------------------------------------------------- #
FROM alpine

RUN apk update && apk add --no-cache openssl ncurses-libs libstdc++
COPY --from=0 /app/_build/prod/rel/bowtie_jsv /bowtie_jsv

ENTRYPOINT [ "/bowtie_jsv/bin/bowtie_jsv", "start" ]
115 changes: 115 additions & 0 deletions implementations/elixir-jsv/lib/bowtie_jsv.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
defmodule BowtieJSV do
@dialects [
"http://json-schema.org/draft-07/schema#",
"https://json-schema.org/draft/2020-12/schema"
]

def run() do
debug("starting")
loop_lines(%{})
debug("finished")
end

def debug(msg) do
IO.puts(:stderr, msg)
end

def debug(value, label) do
IO.inspect(:stderr, value, label: label)
end

defp loop_lines(state) do
case IO.read(:stdio, :line) do
:eof ->
debug("EOF received")

line ->
{:reply, resp, state} = handle_raw_command(line, state)
debug(resp, "resp")
IO.puts(JSON.encode!(resp))
loop_lines(state)
end
end

defp handle_raw_command(json, state) do
cmd = JSON.decode!(json)
handle_command(cmd, state)
end

defp handle_command(%{"cmd" => "start", "version" => 1}, state) do
{:reply, start_response(), state}
end

defp handle_command(%{"cmd" => "stop"}, _state) do
System.halt()
end

defp handle_command(%{"cmd" => "dialect", "dialect" => dialect}, state) do
{:reply, %{ok: true}, Map.put(state, :default_dialect, dialect)}
end

defp handle_command(%{"cmd" => "run", "seq" => tseq, "case" => tcase}, state) do
%{"schema" => raw_schema, "tests" => tests} = tcase
registry = Map.get(tcase, "registry")
debug(tcase, "tcase")

root = build_schema(raw_schema, registry, state)

results = Enum.map(tests, fn test -> run_test(test, root) end)

{:reply, %{seq: tseq, results: results}, state}
rescue
e -> {:reply, errored(e, __STACKTRACE__, %{seq: tseq}), state}
end

defp build_schema(raw_schema, registry, state) do
JSV.build!(raw_schema,
resolver: {BowtieJSV.Resolver, registry: registry},
default_meta: state.default_dialect
)
end

defp run_test(test, root) do
%{"instance" => data} = test

case JSV.validate(data, root) do
{:ok, _} -> %{valid: true}
{:error, verr} -> %{valid: false, output: JSV.normalize_error(verr)}
end
rescue
e -> errored(e, __STACKTRACE__)
end

defp start_response do
%{
version: 1,
implementation: %{
language: "elixir",
name: "jsv",
version: jsv_vsn(),
dialects: @dialects,
documentation: "https://hexdocs.pm/jsv/",
homepage: "https://github.com/lud/jsv",
issues: "https://github.com/lud/jsv/issues",
source: "https://github.com/lud/jsv"
}
}
end

defp jsv_vsn do
{:ok, jsv_vsn} = :application.get_key(:jsv, :vsn)
List.to_string(jsv_vsn)
end

defp errored(e, stacktrace, additional_data \\ %{}) do
errorred_payload = %{
errored: true,
context: %{
message: Exception.message(e),
traceback: Exception.format_stacktrace(stacktrace)
}
}

Map.merge(errorred_payload, additional_data)
end
end
8 changes: 8 additions & 0 deletions implementations/elixir-jsv/lib/bowtie_jsv/app.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
defmodule BowtieJSV.App do
def start(_, _) do
children = [{Task, fn -> BowtieJSV.run() end}]

opts = [strategy: :one_for_one, name: BowtieJSV.Supervisor]
Supervisor.start_link(children, opts)
end
end
26 changes: 26 additions & 0 deletions implementations/elixir-jsv/lib/bowtie_jsv/resolver.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule BowtieJSV.Resolver do
alias JSV.Resolver.Embedded
@behaviour JSV.Resolver

@impl true
def resolve(uri, opts) do
registry = make_registry(opts)

case registry do
%{^uri => schema} ->
BowtieJSV.debug(["resolving from registry: ", uri])
{:ok, schema}

_ ->
BowtieJSV.debug(["resolving from embedded: ", uri])
Embedded.resolve(uri, [])
end
end

defp make_registry(opts) do
case Keyword.get(opts, :registry) do
nil -> %{}
map -> map
end
end
end
35 changes: 35 additions & 0 deletions implementations/elixir-jsv/mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule BowtieJSV.MixProject do
use Mix.Project

def project do
[
app: :bowtie_jsv,
version: "0.1.0",
elixir: "~> 1.17",
start_permanent: Mix.env() == :prod,
deps: deps(),
escript: [main_module: BowtieJSV, name: "bowtie_jsv", include_priv_for: [:jsv]],
releases: [
bowtie_jsv: [
include_executables_for: [:unix],
include_erts: true
]
]
]
end

# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {BowtieJSV.App, []}
]
end

# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:jsv, "~> 0.3"}
]
end
end
24 changes: 24 additions & 0 deletions implementations/elixir-jsv/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/bash -ev
trap exit INT

docker build --progress plain -t localhost/jsv-bowtie .

docker run --rm -i localhost/jsv-bowtie <<EOF
{"cmd": "start", "version": 1}
{"cmd": "dialect", "dialect": "https://json-schema.org/draft/2020-12/schema" }
{"cmd": "run", "seq": 11, "case": {"description": "test case 2", "schemaxxx": {"const": 37}, "tests": [{"description": "not 37", "instance": {}}, {"description": "is 37", "instance": 37}] }}
{"cmd": "run", "seq": 11, "case": {"description": "test case 2", "schema": {"format": "fail_if_uppercase"}, "tests": [{"description": "not uppercase", "instance": "hello"}, {"description": "is uppercase", "instance": "HELLO"}] }}
{"cmd": "stop"}
EOF

docker run --rm -i localhost/jsv-bowtie <<EOF
{"cmd": "start", "version": 1}
{"cmd": "dialect", "dialect": "https://json-schema.org/draft/2020-12/schema" }
{"cmd": "run", "seq": 11, "case": {"description": "test case 1", "schema": {}, "tests": [{"description": "a test", "instance": {}}] }}
{"cmd": "run", "seq": 11, "case": {"description": "test case 2", "schema": {"const": 37}, "tests": [{"description": "not 37", "instance": {}}, {"description": "is 37", "instance": 37}] }}
{"cmd": "stop"}
EOF

bowtie smoke -i localhost/jsv-bowtie
bowtie suite -x -i localhost/jsv-bowtie draft2020-12 | bowtie summary --show failures
bowtie suite -x -i localhost/jsv-bowtie draft7 | bowtie summary --show failures

0 comments on commit d0b23ec

Please sign in to comment.