Skip to content

Commit 01717eb

Browse files
committed
CI, README, package, docs
1 parent f260cd7 commit 01717eb

4 files changed

Lines changed: 163 additions & 12 deletions

File tree

.github/workflows/ci.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-22.04
12+
env:
13+
MIX_ENV: test
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
include:
18+
- pair:
19+
elixir: "1.15"
20+
otp: "25"
21+
- pair:
22+
elixir: "1.19"
23+
otp: "28"
24+
lint: lint
25+
steps:
26+
- uses: actions/checkout@v5
27+
28+
- uses: erlef/setup-beam@v1
29+
with:
30+
otp-version: ${{matrix.pair.otp}}
31+
elixir-version: ${{matrix.pair.elixir}}
32+
33+
- uses: actions/cache@v4
34+
with:
35+
path: |
36+
deps
37+
_build
38+
key: ${{ runner.os }}-mix-${{matrix.pair.elixir}}-${{matrix.pair.otp}}-${{ hashFiles('**/mix.lock') }}
39+
restore-keys: |
40+
${{ runner.os }}-mix-${{matrix.pair.elixir}}-${{matrix.pair.otp}}
41+
42+
- run: mix deps.get
43+
44+
- run: mix format --check-formatted
45+
if: ${{ matrix.lint }}
46+
47+
- run: mix deps.unlock --check-unused
48+
if: ${{ matrix.lint }}
49+
50+
- run: mix deps.compile
51+
52+
- run: mix compile --warnings-as-errors
53+
if: ${{ matrix.lint }}
54+
55+
- run: mix test

README.md

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,82 @@
11
# Schemecto
22

3-
**TODO: Add description**
3+
Schemecto provides schemaless Ecto changesets with support for nested schemaless changesets and JSON Schema generation.
4+
5+
## Usage
6+
7+
Define types and validation functions for your data structures:
8+
9+
```elixir
10+
defmodule Example do
11+
def changeset(params) do
12+
types = %{
13+
name: :string,
14+
email: :string,
15+
address: Schemecto.one(
16+
%{street: :string, city: :string, zip: :string},
17+
with: &Example.validate_address/2
18+
)
19+
}
20+
21+
Schemecto.new(types)
22+
|> Ecto.Changeset.cast(params, [:name, :email, :address])
23+
|> Ecto.Changeset.validate_required([:name, :email])
24+
|> Ecto.Changeset.validate_format(:email, ~r/@/)
25+
end
26+
27+
def validate_address(changeset, params) do
28+
changeset
29+
|> Ecto.Changeset.cast(params, [:street, :city, :zip])
30+
|> Ecto.Changeset.validate_required([:street, :city])
31+
|> Ecto.Changeset.validate_length(:zip, is: 5)
32+
end
33+
end
34+
```
35+
36+
Since Schemecto is built on Ecto changesets, you can use all standard Ecto validation functions. The types map can be constructed dynamically at runtime, making it ideal for scenarios where the schema must be composed at runtime.
37+
38+
## JSON Schema Generation
39+
40+
Convert your changesets to JSON Schema to generate API documentation, validate client-side forms, or integrate with other tools:
41+
42+
```elixir
43+
Schemecto.to_json_schema(changeset)
44+
```
45+
46+
The generated schema includes required fields, format patterns, length constraints, numeric bounds, and enum fields from your changeset. For instance, the changeset above will emit:
47+
48+
```json
49+
%{
50+
"properties" => %{
51+
"address" => %{
52+
"properties" => %{
53+
"city" => %{"type" => "string"},
54+
"street" => %{"type" => "string"},
55+
"zip" => %{"maxLength" => 5, "minLength" => 5, "type" => "string"}
56+
},
57+
"required" => ["street", "city"],
58+
"type" => "object"
59+
},
60+
"email" => %{"pattern" => "@", "type" => "string"},
61+
"name" => %{"type" => "string"}
62+
},
63+
"required" => ["name", "email"],
64+
"type" => "object"
65+
}
66+
```
467

568
## Installation
669

7-
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
8-
by adding `schemecto` to your list of dependencies in `mix.exs`:
70+
Add `schemecto` to your dependencies in `mix.exs`:
971

1072
```elixir
1173
def deps do
1274
[
13-
{:schemecto, "~> 0.1.0"}
75+
{:schemecto, github: "josevalim/schemecto"}
1476
]
1577
end
1678
```
1779

18-
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
19-
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
20-
be found at <https://hexdocs.pm/schemecto>.
21-
2280
## License
2381

2482
Copyright 2026 José Valim

mix.exs

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
defmodule Schemecto.MixProject do
22
use Mix.Project
33

4+
@version "0.1.0"
5+
@repo_url "https://github.com/josevalim/schemecto"
46
def project do
57
[
68
app: :schemecto,
7-
version: "0.1.0",
9+
version: @version,
810
elixir: "~> 1.15",
9-
start_permanent: Mix.env() == :prod,
1011
elixirc_paths: if(Mix.env() == :test, do: ["lib", "test/support"], else: ["lib"]),
11-
deps: deps()
12+
deps: deps(),
13+
14+
# Hex
15+
package: package(),
16+
description: "Schemaless Ecto changesets with support for nesting and JSON Schemas",
17+
18+
# Docs
19+
name: "Schemecto",
20+
docs: &docs/0
1221
]
1322
end
1423

@@ -17,6 +26,29 @@ defmodule Schemecto.MixProject do
1726
end
1827

1928
defp deps do
20-
[{:ecto, "~> 3.12"}]
29+
[
30+
{:ecto, "~> 3.12"},
31+
{:ex_doc, "~> 0.34", only: :docs}
32+
]
33+
end
34+
35+
defp package do
36+
[
37+
licenses: ["Apache-2.0"],
38+
links: %{
39+
"GitHub" => @repo_url,
40+
"Changelog" => "#{@repo_url}/blob/main/CHANGELOG.md"
41+
}
42+
]
43+
end
44+
45+
defp docs do
46+
[
47+
# The main page in the docs
48+
main: "readme",
49+
extras: ["README.md"]
50+
source_ref: "v#{@version}",
51+
source_url: @repo_url
52+
]
2153
end
2254
end

mix.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
%{
22
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
3+
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
34
"ecto": {:hex, :ecto, "3.13.5", "9d4a69700183f33bf97208294768e561f5c7f1ecf417e0fa1006e4a91713a834", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "df9efebf70cf94142739ba357499661ef5dbb559ef902b68ea1f3c1fabce36de"},
5+
"ex_doc": {:hex, :ex_doc, "0.39.3", "519c6bc7e84a2918b737aec7ef48b96aa4698342927d080437f61395d361dcee", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "0590955cf7ad3b625780ee1c1ea627c28a78948c6c0a9b0322bd976a079996e1"},
6+
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
7+
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
8+
"makeup_erlang": {:hex, :makeup_erlang, "1.0.3", "4252d5d4098da7415c390e847c814bad3764c94a814a0b4245176215615e1035", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "953297c02582a33411ac6208f2c6e55f0e870df7f80da724ed613f10e6706afd"},
9+
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
410
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
511
}

0 commit comments

Comments
 (0)