changed README.md
 
@@ -115,6 +115,52 @@ This validation can be skipped for `nil` or blank values by including
115
115
See the documentation on `Vex.Validators.Length` for details on
116
116
available options.
117
117
118
+ ### Number
119
+
120
+ Ensure a value is a number greater than a given number:
121
+
122
+ ```elixir
123
+ Vex.valid? value, number: [greater_than: 2]
124
+ ```
125
+
126
+ Ensure a value is a number less than or equal to a given number:
127
+
128
+ ```elixir
129
+ Vex.valid? value, number: [less_than_or_equal_to: 10]
130
+ ```
131
+
132
+ Ensure a value is a number is within a given range:
133
+
134
+ ```elixir
135
+ Vex.valid? value, number: [greater_than_or_equal_to: 0, less_than: 10]
136
+ ```
137
+
138
+ This validation can be skipped for `nil` or blank values by including
139
+ `allow_nil: true` or `allow_blank: true` respectively in the options.
140
+
141
+ See the documentation on `Vex.Validators.Number` for details
142
+ on available options.
143
+
144
+ ### UUID
145
+
146
+ Ensure a value is a valid UUID string:
147
+
148
+ ```elixir
149
+ Vex.valid? value, uuid: true
150
+ ```
151
+
152
+ Ensure a value is a valid UUID string in a given format:
153
+
154
+ ```elixir
155
+ Vex.valid? value, uuid: [format: :hex]
156
+ ```
157
+
158
+ This validation can be skipped for `nil` or blank values by including
159
+ `allow_nil: true` or `allow_blank: true` respectively in the options.
160
+
161
+ See the documentation on `Vex.Validators.Uuid` for details
162
+ on available options.
163
+
118
164
### Acceptance
119
165
120
166
Ensure an attribute is set to a positive (or custom) value. For use
changed hex_metadata.config
 
@@ -16,11 +16,12 @@
16
16
<<"lib/vex/validators/by.ex">>,<<"lib/vex/validators/confirmation.ex">>,
17
17
<<"lib/vex/validators/exclusion.ex">>,<<"lib/vex/validators/format.ex">>,
18
18
<<"lib/vex/validators/inclusion.ex">>,<<"lib/vex/validators/length.ex">>,
19
- <<"lib/vex/validators/presence.ex">>,<<"mix.exs">>,<<"README.md">>,
19
+ <<"lib/vex/validators/number.ex">>,<<"lib/vex/validators/presence.ex">>,
20
+ <<"lib/vex/validators/uuid.ex">>,<<"mix.exs">>,<<"README.md">>,
20
21
<<"LICENSE">>]}.
21
22
{<<"licenses">>,[<<"MIT License">>]}.
22
23
{<<"links">>,[{<<"github">>,<<"https://github.com/CargoSense/vex">>}]}.
23
24
{<<"maintainers">>,[<<"Bruce Williams">>,<<"Ben Wilson">>,<<"John Hyland">>]}.
24
25
{<<"name">>,<<"vex">>}.
25
26
{<<"requirements">>,[]}.
26
- {<<"version">>,<<"0.7.0">>}.
27
+ {<<"version">>,<<"0.8.0">>}.
added lib/vex/validators/number.ex
 
@@ -0,0 +1,205 @@
1
+ defmodule Vex.Validators.Number do
2
+ @moduledoc """
3
+ Ensure a value is a number.
4
+
5
+ ## Options
6
+
7
+ At least one of the following must be provided:
8
+
9
+ * `:is`: The value is a number (integer or float) or not.
10
+ * `:equal_to`: The value is a number equal to this number.
11
+ * `:greater_than` : The value is a number greater than this number.
12
+ * `:greater_than_or_equal_to`: The value is a number greater than or equal to this number.
13
+ * `:less_than` : The value is a number less than this number.
14
+ * `:less_than_or_equal_to`: The value is a number less than or equal to this number.
15
+
16
+ Optional:
17
+
18
+ * `:message`: A custom error message. May be in EEx format and use the fields described
19
+ in [Custom Error Messages](#module-custom-error-messages).
20
+ * `:allow_nil`: A boolean whether to skip this validation for `nil` values.
21
+ * `:allow_blank`: A boolean whether to skip this validaton for blank values.
22
+
23
+ The `:is` option can be provided in place of the keyword list if no other options are set.
24
+ When multiple options are than the validator will do an `and` logic between them.
25
+
26
+ ## Examples
27
+
28
+ Examples when using the `:is` option:
29
+
30
+ iex> Vex.Validators.Number.validate("not_a_number", is: true)
31
+ {:error, "must be a number"}
32
+ iex> Vex.Validators.Number.validate(3.14, is: true)
33
+ :ok
34
+
35
+ iex> Vex.Validators.Number.validate("not_a_number", is: false)
36
+ :ok
37
+ iex> Vex.Validators.Number.validate(3.14, is: false)
38
+ {:error, "must not be a number"}
39
+
40
+ Examples when using the boolean value in options for the `:is` option:
41
+
42
+ iex> Vex.Validators.Number.validate("not_a_number", true)
43
+ {:error, "must be a number"}
44
+ iex> Vex.Validators.Number.validate(3.14, true)
45
+ :ok
46
+
47
+ iex> Vex.Validators.Number.validate("not_a_number", false)
48
+ :ok
49
+ iex> Vex.Validators.Number.validate(3.14, false)
50
+ {:error, "must not be a number"}
51
+
52
+ Examples when using the `:equal_to` option:
53
+
54
+ iex> Vex.Validators.Number.validate(3.14, equal_to: 1.41)
55
+ {:error, "must be a number equal to 1.41"}
56
+ iex> Vex.Validators.Number.validate(3.14, equal_to: 3.14)
57
+ :ok
58
+ iex> Vex.Validators.Number.validate(3.14, equal_to: 6.28)
59
+ {:error, "must be a number equal to 6.28"}
60
+
61
+ Examples when using the `:greater_than` option:
62
+
63
+ iex> Vex.Validators.Number.validate(3.14, greater_than: 1.41)
64
+ :ok
65
+ iex> Vex.Validators.Number.validate(3.14, greater_than: 3.14)
66
+ {:error, "must be a number greater than 3.14"}
67
+ iex> Vex.Validators.Number.validate(3.14, greater_than: 6.28)
68
+ {:error, "must be a number greater than 6.28"}
69
+
70
+ Examples when using the `:greater_than_or_equal_to` option:
71
+
72
+ iex> Vex.Validators.Number.validate(3.14, greater_than_or_equal_to: 1.41)
73
+ :ok
74
+ iex> Vex.Validators.Number.validate(3.14, greater_than_or_equal_to: 3.14)
75
+ :ok
76
+ iex> Vex.Validators.Number.validate(3.14, greater_than_or_equal_to: 6.28)
77
+ {:error, "must be a number greater than or equal to 6.28"}
78
+
79
+ Examples when using the `:less_than` option:
80
+
81
+ iex> Vex.Validators.Number.validate(3.14, less_than: 1.41)
82
+ {:error, "must be a number less than 1.41"}
83
+ iex> Vex.Validators.Number.validate(3.14, less_than: 3.14)
84
+ {:error, "must be a number less than 3.14"}
85
+ iex> Vex.Validators.Number.validate(3.14, less_than: 6.28)
86
+ :ok
87
+
88
+ Examples when using the `:less_than_or_equal_to` option:
89
+
90
+ iex> Vex.Validators.Number.validate(3.14, less_than_or_equal_to: 1.41)
91
+ {:error, "must be a number less than or equal to 1.41"}
92
+ iex> Vex.Validators.Number.validate(3.14, less_than_or_equal_to: 3.14)
93
+ :ok
94
+ iex> Vex.Validators.Number.validate(3.14, less_than_or_equal_to: 6.28)
95
+ :ok
96
+
97
+ Examples when using the combinations of the above options:
98
+
99
+ iex> Vex.Validators.Number.validate("not_a_number", is: true, greater_than: 0, less_than_or_equal_to: 3.14)
100
+ {:error, "must be a number"}
101
+ iex> Vex.Validators.Number.validate(0, is: true, greater_than: 0, less_than_or_equal_to: 3.14)
102
+ {:error, "must be a number greater than 0"}
103
+ iex> Vex.Validators.Number.validate(1.41, is: true, greater_than: 0, less_than_or_equal_to: 3.14)
104
+ :ok
105
+ iex> Vex.Validators.Number.validate(3.14, is: true, greater_than: 0, less_than_or_equal_to: 3.14)
106
+ :ok
107
+ iex> Vex.Validators.Number.validate(6.28, is: true, greater_than: 0, less_than_or_equal_to: 3.14)
108
+ {:error, "must be a number less than or equal to 3.14"}
109
+
110
+ ## Custom Error Messages
111
+
112
+ Custom error messages (in EEx format), provided as :message, can use the following values:
113
+
114
+ iex> Vex.Validators.Number.__validator__(:message_fields)
115
+ [
116
+ value: "Bad value",
117
+ is: "Is number",
118
+ equal_to: "Equal to number",
119
+ greater_than: "Greater than number",
120
+ greater_than_or_equal_to: "Greater than or equal to number",
121
+ less_than: "Less than number",
122
+ less_than_or_equal_to: "Less than or equal to number"
123
+ ]
124
+
125
+ An example:
126
+
127
+ iex> Vex.Validators.Number.validate(3.14, less_than: 1.41,
128
+ ...> message: "<%= inspect value %> should be less than <%= less_than %>")
129
+ {:error, "3.14 should be less than 1.41"}
130
+ """
131
+
132
+ use Vex.Validator
133
+
134
+ @option_keys [
135
+ :is,
136
+ :equal_to,
137
+ :greater_than,
138
+ :greater_than_or_equal_to,
139
+ :less_than,
140
+ :less_than_or_equal_to
141
+ ]
142
+
143
+ @message_fields [
144
+ value: "Bad value",
145
+ is: "Is number",
146
+ equal_to: "Equal to number",
147
+ greater_than: "Greater than number",
148
+ greater_than_or_equal_to: "Greater than or equal to number",
149
+ less_than: "Less than number",
150
+ less_than_or_equal_to: "Less than or equal to number"
151
+ ]
152
+ def validate(value, options) when is_boolean(options) do
153
+ validate(value, is: options)
154
+ end
155
+
156
+ def validate(value, options) when is_list(options) do
157
+ unless_skipping value, options do
158
+ Enum.reduce_while(options, :ok, fn
159
+ {k, o}, _ when k in @option_keys ->
160
+ case do_validate(value, k, o) do
161
+ :ok ->
162
+ {:cont, :ok}
163
+
164
+ {:error, default_message} ->
165
+ fields =
166
+ options
167
+ |> Keyword.take(@option_keys)
168
+ |> Keyword.put(:value, value)
169
+ |> Keyword.put(:less_than, options[:less_than])
170
+
171
+ error = {:error, message(options, default_message, fields)}
172
+
173
+ {:halt, error}
174
+ end
175
+
176
+ _, _ ->
177
+ {:cont, :ok}
178
+ end)
179
+ end
180
+ end
181
+
182
+ defp do_validate(_, _, nil), do: :ok
183
+ defp do_validate(v, :is, o) when is_number(v) === o, do: :ok
184
+ defp do_validate(_, :is, true), do: {:error, "must be a number"}
185
+ defp do_validate(_, :is, false), do: {:error, "must not be a number"}
186
+
187
+ defp do_validate(_, k, o) when not is_number(o),
188
+ do: raise("Invalid value #{inspect(o)} for option #{k}")
189
+
190
+ defp do_validate(v, :equal_to, o) when is_number(v) and v == o, do: :ok
191
+ defp do_validate(_, :equal_to, o), do: {:error, "must be a number equal to #{o}"}
192
+ defp do_validate(v, :greater_than, o) when is_number(v) and v > o, do: :ok
193
+ defp do_validate(_, :greater_than, o), do: {:error, "must be a number greater than #{o}"}
194
+ defp do_validate(v, :greater_than_or_equal_to, o) when is_number(v) and v >= o, do: :ok
195
+
196
+ defp do_validate(_, :greater_than_or_equal_to, o),
197
+ do: {:error, "must be a number greater than or equal to #{o}"}
198
+
199
+ defp do_validate(v, :less_than, o) when is_number(v) and v < o, do: :ok
200
+ defp do_validate(_, :less_than, o), do: {:error, "must be a number less than #{o}"}
201
+ defp do_validate(v, :less_than_or_equal_to, o) when is_number(v) and v <= o, do: :ok
202
+
203
+ defp do_validate(_, :less_than_or_equal_to, o),
204
+ do: {:error, "must be a number less than or equal to #{o}"}
205
+ end
added lib/vex/validators/uuid.ex
 
@@ -0,0 +1,154 @@
1
+ defmodule Vex.Validators.Uuid do
2
+ @moduledoc """
3
+ Ensure a value is a valid UUID string.
4
+
5
+ ## Options
6
+
7
+ At least one of the following must be provided:
8
+
9
+ * `:format`: Required. An atom that defines the UUID format of the value:
10
+
11
+ * `:default`: The value must be a string with format `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx`.
12
+ * `:hex`: The value must be a string with the format `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`.
13
+ * `:urn`: The value must be a string with the format `urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx`.
14
+ * `:any`: The value must be a string of any of the supported formats (`:default`, `:hex` or `:urn`).
15
+ * `:not_any`: The value must not be a valid UUID string.
16
+
17
+ *Note: `x` is a hex number.*
18
+
19
+ Optional:
20
+
21
+ * `:message`: A custom error message. May be in EEx format
22
+ and use the fields described in [Custom Error Messages](#module-custom-error-messages).
23
+ * `:allow_nil`: A boolean whether to skip this validation for `nil` values.
24
+ * `:allow_blank`: A boolean whether to skip this validaton for blank values.
25
+
26
+ The value for `:format` can be provided instead of the options keyword list.
27
+ Additionally, if the options is a boolean value, then:
28
+
29
+ * `true`: Is the same as the `[format: :any]` options.
30
+ * `false`: Is the same as the `[format: :not_any]` options.
31
+
32
+ ## Examples
33
+
34
+ Examples when using the `:any` or `true` options:
35
+
36
+ iex> Vex.Validators.Uuid.validate("02aa7f48-3ccd-11e4-b63e-14109ff1a304", format: :any)
37
+ :ok
38
+ iex> Vex.Validators.Uuid.validate("02aa7f48-3ccd-11e4-b63e-14109ff1a30", format: :any)
39
+ {:error, "must be a valid UUID string"}
40
+
41
+ iex> Vex.Validators.Uuid.validate("02aa7f48-3ccd-11e4-b63e-14109ff1a304", true)
42
+ :ok
43
+ iex> Vex.Validators.Uuid.validate("02aa7f48-3ccd-11e4-b63e-14109ff1a30", true)
44
+ {:error, "must be a valid UUID string"}
45
+
46
+ Examples when using the `:not_any` or `false` options:
47
+
48
+ iex> Vex.Validators.Uuid.validate("not_a_uuid", format: :not_any)
49
+ :ok
50
+ iex> Vex.Validators.Uuid.validate("02aa7f48-3ccd-11e4-b63e-14109ff1a304", format: :not_any)
51
+ {:error, "must not be a valid UUID string"}
52
+
53
+ iex> Vex.Validators.Uuid.validate("not_a_uuid", false)
54
+ :ok
55
+ iex> Vex.Validators.Uuid.validate("02aa7f48-3ccd-11e4-b63e-14109ff1a304", false)
56
+ {:error, "must not be a valid UUID string"}
57
+
58
+ Examples when using the `:default` option:
59
+
60
+ iex> Vex.Validators.Uuid.validate("02aa7f48-3ccd-11e4-b63e-14109ff1a304", format: :default)
61
+ :ok
62
+ iex> Vex.Validators.Uuid.validate("02aa7f483ccd11e4b63e14109ff1a304", format: :default)
63
+ {:error, "must be a valid UUID string in default format"}
64
+
65
+ Examples when using the `:hex` option:
66
+
67
+ iex> Vex.Validators.Uuid.validate("02aa7f483ccd11e4b63e14109ff1a304", format: :hex)
68
+ :ok
69
+ iex> Vex.Validators.Uuid.validate("urn:uuid:02aa7f48-3ccd-11e4-b63e-14109ff1a304", format: :hex)
70
+ {:error, "must be a valid UUID string in hex format"}
71
+
72
+ Examples when using the `:urn` option:
73
+
74
+ iex> Vex.Validators.Uuid.validate("urn:uuid:02aa7f48-3ccd-11e4-b63e-14109ff1a304", format: :urn)
75
+ :ok
76
+ iex> Vex.Validators.Uuid.validate("02aa7f48-3ccd-11e4-b63e-14109ff1a304", format: :urn)
77
+ {:error, "must be a valid UUID string in urn format"}
78
+
79
+ ## Custom Error Messages
80
+
81
+ Custom error messages (in EEx format), provided as `:message`, can use the following values:
82
+
83
+ iex> Vex.Validators.Uuid.__validator__(:message_fields)
84
+ [value: "Bad value", format: "The UUID format"]
85
+
86
+ An example:
87
+
88
+ iex> Vex.Validators.Uuid.validate("not_a_uuid", format: :any,
89
+ ...> message: "<%= value %> should be <%= format %> UUID")
90
+ {:error, "not_a_uuid should be any UUID"}
91
+ """
92
+
93
+ use Vex.Validator
94
+
95
+ @uuid_formats [:default, :hex, :urn]
96
+
97
+ @formats [:any, :not_any] ++ @uuid_formats
98
+
99
+ @urn_prefix "urn:uuid:"
100
+
101
+ @message_fields [value: "Bad value", format: "The UUID format"]
102
+ def validate(value, true), do: validate(value, format: :any)
103
+ def validate(value, false), do: validate(value, format: :not_any)
104
+ def validate(value, options) when options in @formats, do: validate(value, format: options)
105
+
106
+ def validate(value, options) when is_list(options) do
107
+ unless_skipping value, options do
108
+ format = options[:format]
109
+
110
+ case do_validate(value, format) do
111
+ :ok -> :ok
112
+ {:error, reason} -> {:error, message(options, reason, value: value, format: format)}
113
+ end
114
+ end
115
+ end
116
+
117
+ defp do_validate(<<_::64, ?-, _::32, ?-, _::32, ?-, _::32, ?-, _::96>>, :default) do
118
+ :ok
119
+ end
120
+
121
+ defp do_validate(<<_::256>>, :hex) do
122
+ :ok
123
+ end
124
+
125
+ defp do_validate(<<@urn_prefix, _::64, ?-, _::32, ?-, _::32, ?-, _::32, ?-, _::96>>, :urn) do
126
+ :ok
127
+ end
128
+
129
+ defp do_validate(_, format) when format in @uuid_formats do
130
+ {:error, "must be a valid UUID string in #{format} format"}
131
+ end
132
+
133
+ defp do_validate(value, :any) do
134
+ error = {:error, "must be a valid UUID string"}
135
+
136
+ Enum.reduce_while(@uuid_formats, error, fn format, _ ->
137
+ case do_validate(value, format) do
138
+ :ok -> {:halt, :ok}
139
+ _ -> {:cont, error}
140
+ end
141
+ end)
142
+ end
143
+
144
+ defp do_validate(value, :not_any) do
145
+ case do_validate(value, :any) do
146
+ :ok -> {:error, "must not be a valid UUID string"}
147
+ _ -> :ok
148
+ end
149
+ end
150
+
151
+ defp do_validate(_, format) do
152
+ raise "Invalid value #{inspect(format)} for option :format"
153
+ end
154
+ end
changed mix.exs
 
@@ -4,7 +4,7 @@ defmodule Vex.Mixfile do
4
4
def project do
5
5
[
6
6
app: :vex,
7
- version: "0.7.0",
7
+ version: "0.8.0",
8
8
elixir: "~> 1.2",
9
9
deps: deps(),
10
10
consolidate_protocols: Mix.env() != :test,