Skip to content

Commit e075258

Browse files
committed
init the project
0 parents  commit e075258

8 files changed

+316
-0
lines changed

.formatter.exs

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Used by "mix format"
2+
[
3+
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
4+
]

.gitignore

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# The directory Mix will write compiled artifacts to.
2+
/_build/
3+
4+
# If you run "mix test --cover", coverage assets end up here.
5+
/cover/
6+
7+
# The directory Mix downloads your dependencies sources to.
8+
/deps/
9+
10+
# Where third-party dependencies like ExDoc output generated docs.
11+
/doc/
12+
13+
# Ignore .fetch files in case you like to edit your project deps locally.
14+
/.fetch
15+
16+
# If the VM crashes, it generates a dump, let's ignore it too.
17+
erl_crash.dump
18+
19+
# Also ignore archive artifacts (built via "mix archive.build").
20+
*.ez
21+
22+
# Ignore package tarball (built via "mix hex.build").
23+
json_data_faker-*.tar
24+

README.md

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# JsonDataFaker
2+
3+
Generate JSON data from JSON schema by using faking data.
4+
5+
```elixir
6+
iex> object_schema = %{
7+
"properties" => %{
8+
"body" => %{
9+
"maxLength" => 140,
10+
"minLength" => 3,
11+
"type" => "string"
12+
},
13+
"created" => %{
14+
"format" => "date-time",
15+
"type" => "string"
16+
},
17+
"id" => %{
18+
"format" => "uuid",
19+
"type" => "string"
20+
},
21+
"status" => %{
22+
"enum" => [
23+
"active",
24+
"completed"
25+
],
26+
"type" => "string"
27+
},
28+
"updated" => %{
29+
"format" => "date-time",
30+
"type" => "string"
31+
}
32+
},
33+
"required" => [
34+
"body"
35+
],
36+
"type" => "object"
37+
}
38+
39+
iex> schema = %{
40+
"items" => object_schema,
41+
"type" => "array"
42+
}
43+
44+
iex> JsonDataFaker.generate(schema)
45+
[
46+
%{
47+
"body" => "Do you think I am easier to be played on than a pipe?",
48+
"created" => "2020-11-28T01:15:35.268463Z",
49+
"id" => "13543d9c-0f37-482d-84d6-52b2cb8c1b3f",
50+
"status" => "active",
51+
"updated" => "2020-11-28T01:15:35.268478Z"
52+
},
53+
%{
54+
"body" => "When sorrows come, they come not single spies, but in battalions.",
55+
"created" => "2020-11-28T01:15:35.268502Z",
56+
"id" => "c95ef972-05c9-4132-9525-09c99a15bf01",
57+
"status" => "completed",
58+
"updated" => "2020-11-28T01:15:35.268517Z"
59+
}
60+
...
61+
]
62+
```
63+
64+
## Installation
65+
66+
```elixir
67+
def deps do
68+
[
69+
{:json_data_faker, "~> 0.1.0"}
70+
]
71+
end
72+
```
73+
74+
Documentation: [https://hexdocs.pm/json_data_faker](https://hexdocs.pm/json_data_faker).

lib/json_data_faker.ex

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
defmodule JsonDataFaker do
2+
@moduledoc """
3+
Generate fake data based on json schema.
4+
"""
5+
6+
alias ExJsonSchema.Schema
7+
8+
@doc """
9+
generate fake data with given schema. It could be a raw json schema or ExJsonSchema.Schema.Root type.
10+
11+
## Examples
12+
13+
iex> schema = %{
14+
...> "properties" => %{
15+
...> "body" => %{"maxLength" => 140, "minLength" => 3, "type" => "string"},
16+
...> "title" => %{"maxLength" => 64, "minLength" => 3, "type" => "string"}
17+
...> },
18+
...> "required" => ["title"],
19+
...> "type" => "object"
20+
...>}
21+
iex> %{"title" => title, "body" => body} = JsonDataFaker.generate(schema)
22+
"""
23+
def generate(%Schema.Root{} = schema) do
24+
generate_by_type(schema.schema)
25+
end
26+
27+
def generate(schema) when is_map(schema) do
28+
generate(Schema.resolve(schema))
29+
end
30+
31+
# private functions
32+
defp generate_by_type(%{"type" => "boolean"}) do
33+
Enum.random([true, false])
34+
end
35+
36+
defp generate_by_type(%{"type" => "string"} = schema) do
37+
generate_string(schema)
38+
end
39+
40+
defp generate_by_type(%{"type" => "integer"} = schema) do
41+
min = schema["minimum"] || 10
42+
max = schema["maximum"] || 1000
43+
Enum.random(min..max)
44+
end
45+
46+
defp generate_by_type(%{"type" => "array"} = schema) do
47+
inner_schema = schema["items"]
48+
count = Enum.random(5..20)
49+
50+
Enum.map(1..count, fn _ ->
51+
generate_by_type(inner_schema)
52+
end)
53+
end
54+
55+
defp generate_by_type(%{"type" => "object"} = schema) do
56+
Enum.reduce(schema["properties"], %{}, fn {k, inner_schema}, acc ->
57+
Map.put(acc, k, generate_by_type(inner_schema))
58+
end)
59+
end
60+
61+
defp generate_string(%{"format" => "date-time"}),
62+
do: DateTime.utc_now() |> DateTime.to_iso8601()
63+
64+
defp generate_string(%{"format" => "uuid"}), do: Faker.UUID.v4()
65+
defp generate_string(%{"format" => "email"}), do: Faker.Internet.email()
66+
defp generate_string(%{"format" => "hostname"}), do: Faker.Internet.domain_name()
67+
defp generate_string(%{"format" => "ipv4"}), do: Faker.Internet.ip_v4_address()
68+
defp generate_string(%{"format" => "ipv6"}), do: Faker.Internet.ip_v6_address()
69+
defp generate_string(%{"format" => "uri"}), do: Faker.Internet.url()
70+
71+
defp generate_string(%{"enum" => choices}), do: Enum.random(choices)
72+
73+
defp generate_string(%{"pattern" => regex}),
74+
do: Randex.stream(Regex.compile!(regex)) |> Enum.take(1) |> List.first()
75+
76+
defp generate_string(schema) do
77+
min = schema["minLength"] || 0
78+
max = schema["maxLength"] || 1024
79+
s = Faker.Lorem.Shakespeare.hamlet()
80+
81+
case String.length(s) do
82+
v when v > max -> String.slice(s, 0, max - 1)
83+
v when v < min -> s <> " " <> List.to_string(Faker.Lorem.characters(min - v))
84+
_ -> s
85+
end
86+
end
87+
end

mix.exs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
defmodule JsonDataFaker.MixProject do
2+
use Mix.Project
3+
4+
def project do
5+
[
6+
app: :json_data_faker,
7+
version: "0.1.0",
8+
elixir: "~> 1.10",
9+
start_permanent: Mix.env() == :prod,
10+
deps: deps()
11+
]
12+
end
13+
14+
# Run "mix help compile.app" to learn about applications.
15+
def application do
16+
[
17+
extra_applications: [:logger]
18+
]
19+
end
20+
21+
# Run "mix help deps" to learn about dependencies.
22+
defp deps do
23+
[
24+
{:ex_json_schema, "~> 0.7"},
25+
{:randex, "~> 0.4.0"},
26+
{:faker, "~> 0.16"},
27+
{:uuid, "~> 1.1"}
28+
]
29+
end
30+
end

mix.lock

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
%{
2+
"ex_json_schema": {:hex, :ex_json_schema, "0.7.4", "09eb5b0c8184e5702bc89625a9d0c05c7a0a845d382e9f6f406a0fc1c9a8cc3f", [:mix], [], "hexpm", "45c67fa840f0d719a2b5578126dc29bcdc1f92499c0f61bcb8a3bcb5935f9684"},
3+
"faker": {:hex, :faker, "0.16.0", "1e2cf3e8d60d44a30741fb98118fcac18b2020379c7e00d18f1a005841b2f647", [:mix], [], "hexpm", "fbcb9bf1299dff3c9dd7e50f41802bbc472ffbb84e7656394c8aa913ec315141"},
4+
"randex": {:hex, :randex, "0.4.0", "f76712da3663dd7d4166e017829ec428dc67df9e11b82d923dfd6f9b4854b373", [:mix], [{:stream_data, "~> 0.4", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "f803f256542bd264acea832a69f6e5fea3df4cd3f8a9f03588e5593f04c61095"},
5+
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"},
6+
}

test/json_data_faker_test.exs

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
defmodule JsonDataFakerTest do
2+
use ExUnit.Case
3+
alias ExJsonSchema.{Validator, Schema}
4+
doctest JsonDataFaker
5+
6+
@complex_object %{
7+
"properties" => %{
8+
"body" => %{
9+
"maxLength" => 140,
10+
"minLength" => 3,
11+
"type" => "string"
12+
},
13+
"created" => %{
14+
"format" => "date-time",
15+
"type" => "string"
16+
},
17+
"id" => %{
18+
"format" => "uuid",
19+
"type" => "string"
20+
},
21+
"status" => %{
22+
"enum" => [
23+
"active",
24+
"completed"
25+
],
26+
"type" => "string"
27+
},
28+
"updated" => %{
29+
"format" => "date-time",
30+
"type" => "string"
31+
}
32+
},
33+
"required" => [
34+
"body"
35+
],
36+
"type" => "object"
37+
}
38+
39+
test "string uuid generation should work" do
40+
schema = %{"type" => "string", "format" => "uuid"}
41+
assert {:ok, _} = UUID.info(JsonDataFaker.generate(schema))
42+
end
43+
44+
Enum.each(["date-time", "email", "hostname", "ipv4", "ipv6", "uri"], fn format ->
45+
test "string #{format} generation should work" do
46+
schema = %{"type" => "string", "format" => unquote(format)}
47+
s = JsonDataFaker.generate(schema)
48+
assert Validator.valid?(Schema.resolve(schema), s)
49+
end
50+
end)
51+
52+
test "string regex generation should work" do
53+
schema = %{"type" => "string", "pattern" => "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$"}
54+
s = JsonDataFaker.generate(schema)
55+
assert Validator.valid?(Schema.resolve(schema), s)
56+
end
57+
58+
test "string enum generation should work" do
59+
schema = %{"type" => "string", "enum" => ["active", "completed"]}
60+
s = JsonDataFaker.generate(schema)
61+
assert Validator.valid?(Schema.resolve(schema), s)
62+
end
63+
64+
test "string with max / min length should work" do
65+
schema = %{"type" => "string", "minLength" => 200, "maxLength" => 201}
66+
s = JsonDataFaker.generate(schema)
67+
assert Validator.valid?(Schema.resolve(schema), s)
68+
end
69+
70+
test "integer generation should work" do
71+
schema = %{"type" => "integer", "minimum" => 5, "maximum" => 20}
72+
s = JsonDataFaker.generate(schema)
73+
assert Validator.valid?(Schema.resolve(schema), s)
74+
end
75+
76+
test "complex object generation should work" do
77+
s = JsonDataFaker.generate(@complex_object)
78+
assert Validator.valid?(Schema.resolve(@complex_object), s)
79+
end
80+
81+
test "array of object generation should work" do
82+
schema = %{
83+
"items" => @complex_object,
84+
"type" => "array"
85+
}
86+
87+
s = JsonDataFaker.generate(schema)
88+
assert Validator.valid?(Schema.resolve(schema), s)
89+
end
90+
end

test/test_helper.exs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ExUnit.start()

0 commit comments

Comments
 (0)