diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index f054162..c5dd59c 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -15,11 +15,11 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - elixir: [1.11] - otp: [23.3] + elixir: [1.13] + otp: [25.3] steps: - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: setup @@ -28,7 +28,7 @@ jobs: elixir-version: ${{ matrix.elixir }} otp-version: ${{ matrix.otp }} - name: Retrieve Cached Dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 id: mix-cache with: path: | @@ -51,10 +51,10 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - elixir: [1.11] - otp: [23.3] + elixir: [1.13] + otp: [25.3] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup elixir @@ -63,7 +63,7 @@ jobs: elixir-version: ${{ matrix.elixir }} otp-version: ${{ matrix.otp }} - name: Retrieve Cached Dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 id: mix-cache with: path: | @@ -80,10 +80,10 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - elixir: [1.11] - otp: [23.3] + elixir: [1.13] + otp: [25.3] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: erlef/setup-beam@v1 @@ -91,7 +91,7 @@ jobs: elixir-version: ${{ matrix.elixir }} otp-version: ${{ matrix.otp }} - name: Retrieve Cached Dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 id: mix-cache with: path: | diff --git a/config/config.exs b/config/config.exs deleted file mode 100644 index af80b06..0000000 --- a/config/config.exs +++ /dev/null @@ -1,30 +0,0 @@ -# This file is responsible for configuring your application -# and its dependencies with the aid of the Mix.Config module. -use Mix.Config - -# This configuration is loaded before any dependency and is restricted -# to this project. If another project depends on this project, this -# file won't be loaded nor affect the parent project. For this reason, -# if you want to provide default values for your application for -# 3rd-party users, it should be done in your "mix.exs" file. - -# You can configure your application as: -# -# config :norm, key: :value -# -# and access this configuration in your application as: -# -# Application.get_env(:norm, :key) -# -# You can also configure a 3rd-party app: -# -# config :logger, level: :info -# - -# It is also possible to import configuration files, relative to this -# directory. For example, you can emulate configuration per environment -# by uncommenting the line below and defining dev.exs, test.exs and such. -# Configuration from the imported file will override the ones defined -# here (which is why it is important to import them last). -# -# import_config "#{Mix.env()}.exs" diff --git a/lib/norm/conformer.ex b/lib/norm/conformer.ex index f67d4b9..af67cb2 100644 --- a/lib/norm/conformer.ex +++ b/lib/norm/conformer.ex @@ -30,9 +30,7 @@ defmodule Norm.Conformer do end defp build_path(keys) do - keys - |> Enum.map(&format_val/1) - |> Enum.join("/") + Enum.map_join(keys, "/", &format_val/1) end defp format_val(nil), do: "nil" diff --git a/lib/norm/core/selection.ex b/lib/norm/core/selection.ex index 39217df..9d07b60 100644 --- a/lib/norm/core/selection.ex +++ b/lib/norm/core/selection.ex @@ -49,7 +49,7 @@ defmodule Norm.Core.Selection do defp validate_selectors!([]), do: true defp validate_selectors!([{_key, inner} | rest]), do: validate_selectors!(inner) and validate_selectors!(rest) defp validate_selectors!([_key | rest]), do: validate_selectors!(rest) - defp validate_selectors!(other), do: raise ArgumentError, "select expects a list of keys but received: #{inspect other}" + defp validate_selectors!(other), do: raise(ArgumentError, "select expects a list of keys but received: #{inspect other}") defp assert_spec!(%Schema{}=schema, key) do case Schema.key_present?(schema, key) do diff --git a/lib/norm/errors.ex b/lib/norm/errors.ex index a29ee52..617af02 100644 --- a/lib/norm/errors.ex +++ b/lib/norm/errors.ex @@ -2,10 +2,7 @@ defmodule Norm.MismatchError do defexception [:message] def exception(errors) do - msg = - errors - |> Enum.map(&Norm.Conformer.error_to_msg/1) - |> Enum.join("\n") + msg = Enum.map_join(errors, "\n", &Norm.Conformer.error_to_msg/1) %__MODULE__{message: "Could not conform input:\n" <> msg} end @@ -67,8 +64,7 @@ defmodule Norm.SpecError do specs = specs |> Enum.map(& f.(&1, i)) - |> Enum.map(&pad(&1, (i + 1) * 2)) - |> Enum.join("\n") + |> Enum.map_join("\n", &pad(&1, (i + 1) * 2)) "%{\n" <> specs <> "\n" <> pad("}", i * 2) end @@ -79,8 +75,7 @@ defmodule Norm.SpecError do formatted = specs |> Enum.map(&format(&1, i)) - |> Enum.map(&pad(&1, (i + 1) * 2)) - |> Enum.join("\n") + |> Enum.map_join("\n", &pad(&1, (i + 1) * 2)) if length(specs) > 0 do "alt([\n#{formatted}\n" <> pad("])", i * 2) @@ -92,8 +87,7 @@ defmodule Norm.SpecError do formatted = specs |> Enum.map(&format(&1, i)) - |> Enum.map(&pad(&1, (i + 1) * 2)) - |> Enum.join("\n") + |> Enum.map_join("\n", &pad(&1, (i + 1) * 2)) if length(specs) > 0 do "one_of([\n#{formatted}\n" <> pad("])", i * 2) diff --git a/mix.exs b/mix.exs index f9d98ec..220f276 100644 --- a/mix.exs +++ b/mix.exs @@ -2,19 +2,20 @@ defmodule Norm.MixProject do use Mix.Project @version "0.13.0" + @source_url "https://github.com/elixir-toniq/norm" def project do [ app: :norm, version: @version, - elixir: "~> 1.7", + elixir: "~> 1.11", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, deps: deps(), description: description(), package: package(), name: "Norm", - source_url: "https://github.com/keathley/norm", + source_url: @source_url, docs: docs() ] end @@ -31,7 +32,7 @@ defmodule Norm.MixProject do defp deps do [ {:credo, "~> 1.4", only: [:dev, :test], runtime: false}, - {:stream_data, "~> 0.5", optional: true}, + {:stream_data, "~> 0.6 or ~> 1.0", optional: true}, {:ex_doc, "~> 0.19", only: [:dev, :test]} ] end @@ -49,14 +50,14 @@ defmodule Norm.MixProject do [ name: "norm", licenses: ["MIT"], - links: %{"GitHub" => "https://github.com/keathley/norm"} + links: %{"GitHub" => @source_url} ] end def docs do [ source_ref: "v#{@version}", - source_url: "https://github.com/keathley/norm", + source_url: @source_url, main: "Norm" ] end diff --git a/mix.lock b/mix.lock index 3b56535..05e18a8 100644 --- a/mix.lock +++ b/mix.lock @@ -1,14 +1,14 @@ %{ - "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, - "credo": {:hex, :credo, "1.5.6", "e04cc0fdc236fefbb578e0c04bd01a471081616e741d386909e527ac146016c6", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4b52a3e558bd64e30de62a648518a5ea2b6e3e5d2b164ef5296244753fc7eb17"}, + "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, + "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, "earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, - "ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"}, - "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, - "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, - "stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, + "ex_doc": {:hex, :ex_doc, "0.34.0", "ab95e0775db3df71d30cf8d78728dd9261c355c81382bcd4cefdc74610bef13e", [:mix], [{:earmark_parser, "~> 1.4.39", [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", "60734fb4c1353f270c3286df4a0d51e65a2c1d9fba66af3940847cc65a8066d7"}, + "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [: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", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "stream_data": {:hex, :stream_data, "1.1.2", "05499eaec0443349ff877aaabc6e194e82bda6799b9ce6aaa1aadac15a9fdb4d", [:mix], [], "hexpm", "129558d2c77cbc1eb2f4747acbbea79e181a5da51108457000020a906813a1a9"}, } diff --git a/test/norm/contract_test.exs b/test/norm/contract_test.exs index 4004a07..e5cc721 100644 --- a/test/norm/contract_test.exs +++ b/test/norm/contract_test.exs @@ -39,7 +39,10 @@ defmodule Norm.ContractTest do end test "bad contract" do - assert_raise ArgumentError, ~r/got: `@contract\(foo\(n\)\)`/, fn -> + expected = if version().minor >= 13, + do: ~r/got: `@contract foo\(n\)`/, else: ~r/got: `@contract\(foo\(n\)\)`/ + + assert_raise ArgumentError, expected, fn -> defmodule BadContract do use Norm @@ -105,4 +108,6 @@ defmodule Norm.ContractTest do assert inspect(contract) == "%Norm.Contract{args: [a: #Norm.Spec, arg2: #Norm.Spec], result: #Norm.Spec}" end + + defp version, do: Version.parse!(System.version()) end diff --git a/test/norm/core/schema_test.exs b/test/norm/core/schema_test.exs index e70b8c6..17c88eb 100644 --- a/test/norm/core/schema_test.exs +++ b/test/norm/core/schema_test.exs @@ -153,11 +153,11 @@ defmodule Norm.Core.SchemaTest do assert input == conform!(input, User.s()) assert {:error, errors} = conform(%User{name: :foo, age: "31", email: 42}, User.s()) - assert errors == [ + assert MapSet.equal?(MapSet.new(errors), MapSet.new([ %{spec: "is_integer()", input: "31", path: [:age]}, %{spec: "is_binary()", input: 42, path: [:email]}, %{spec: "is_binary()", input: :foo, path: [:name]} - ] + ])) end test "only checks the keys that have specs" do diff --git a/test/norm/core/spec_test.exs b/test/norm/core/spec_test.exs index c70b553..4d19e5a 100644 --- a/test/norm/core/spec_test.exs +++ b/test/norm/core/spec_test.exs @@ -15,7 +15,9 @@ defmodule Norm.Core.SpecTest do assert {:error, errors} = conform(nil, hex) assert errors == [%{spec: "is_binary()", input: nil, path: []}] assert {:error, errors} = conform("bad", hex) - assert errors == [%{spec: "&(String.starts_with?(&1, \"#\"))", input: "bad", path: []}] + + spec = if version().minor >= 13, do: "&String.starts_with?(&1, \"#\")", else: "&(String.starts_with?(&1, \"#\"))" + assert errors == [%{spec: spec, input: "bad", path: []}] end test "'and' and 'or' can be chained" do @@ -144,4 +146,6 @@ defmodule Norm.Core.SpecTest do "#Norm.Spec" end end + + defp version, do: Version.parse!(System.version()) end