Skip to content

Commit abdd27a

Browse files
bdebinskaStefan Fochler
andauthored
PD-3376: add JsonSchema definitions (#5)
* add schemas * update schemas * use jsv convention * use title for shorthand format version * version bump to 0.2.0 * update schemas and add tests Co-authored-by: Stefan Fochler <s.fochler@box-id.com> * add changelog --------- Co-authored-by: Stefan Fochler <s.fochler@box-id.com>
1 parent dd479f8 commit abdd27a

File tree

6 files changed

+467
-3
lines changed

6 files changed

+467
-3
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
Unreleased changes will be displayed here upon implementation.
11+
12+
## [0.2.0] - 2025-08-13
13+
14+
### Added
15+
16+
- [JSV](https://hexdocs.pm/jsv/JSV.html)-compatible JsonSchema definitions for `format` and `defaults` as
17+
`ValueFormatter.Schema.Format` and `ValueFormatter.Schema.DefaultFormats`, respectively.
18+
19+
[unreleased]: https://github.com/box-id/value_formatters/compare/0.2.0...HEAD
20+
[0.2.0]: https://github.com/box-id/value_formatters/releases/tag/0.2.0

lib/schemas.ex

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
defmodule ValueFormatters.Schemas do
2+
def number_options do
3+
%{
4+
precision: %{
5+
type: :number,
6+
description: "Number of decimal places"
7+
},
8+
unit: %{
9+
type: :string,
10+
description: "If set, the formatter appends ' ' + unit to the display value"
11+
}
12+
}
13+
end
14+
15+
def date_options do
16+
%{
17+
date_display: %{
18+
type: :string,
19+
description: """
20+
How the formatter should display the date portion:
21+
22+
- `full`: Wednesday, November 29, 2023
23+
24+
- `long`: November 29, 2023
25+
26+
- `medium`: Nov 29, 2023
27+
28+
- `short`: 11/29/23
29+
30+
- `none`: Don't display date
31+
""",
32+
enum: ["full", "long", "medium", "short", "none"],
33+
default: "medium"
34+
},
35+
time_display: %{
36+
type: :string,
37+
description: """
38+
How the formatter should display the time portion:
39+
40+
- `full`: 3:44:28 PM GMT
41+
42+
- `long`: 3:44:28 PM UTC
43+
44+
- `medium`: 3:44:28 PM
45+
46+
- `short`: 3:44 PM
47+
48+
- `none`: Don't display time
49+
""",
50+
enum: ["full", "long", "medium", "short", "none"],
51+
default: "medium"
52+
}
53+
}
54+
end
55+
56+
def date_unix_options do
57+
%{
58+
milliseconds: %{
59+
type: :boolean,
60+
default: false,
61+
description:
62+
"Whether the formatter should output the values milliseconds (instead of seconds)."
63+
}
64+
}
65+
end
66+
67+
def coordinates_options do
68+
%{
69+
radius_display: %{
70+
type: :boolean,
71+
default: true,
72+
description:
73+
"Whether the formatter should include the radius/accuracy information (if present)."
74+
}
75+
}
76+
end
77+
end
78+
79+
defmodule ValueFormatters.Schemas.Format do
80+
import ValueFormatters.Schemas
81+
82+
def json_schema() do
83+
%{
84+
description: "Formats for value formatting",
85+
oneOf: [
86+
%{
87+
const: nil,
88+
description: "Skip formatting and return raw value."
89+
},
90+
%{
91+
type: :string,
92+
title: "shorthand",
93+
description: "A shorthand representation of the format",
94+
enum: [
95+
"number",
96+
"string",
97+
"date",
98+
"date_relative",
99+
"date_iso",
100+
"date_unix",
101+
"coordinates"
102+
]
103+
},
104+
%{
105+
type: :object,
106+
title: "string",
107+
properties: %{
108+
format: %{
109+
const: "string",
110+
description:
111+
"Use to explicitly disable any kind of formatting that would otherwise take place, but still return a string."
112+
}
113+
},
114+
required: [:format],
115+
additionalProperties: false
116+
},
117+
%{
118+
type: :object,
119+
title: "number",
120+
properties:
121+
Map.merge(
122+
%{
123+
format: %{
124+
const: "number",
125+
description:
126+
"Use to display numeric values and format them according to the user's locale."
127+
}
128+
},
129+
number_options()
130+
),
131+
required: [:format],
132+
additionalProperties: false
133+
},
134+
%{
135+
type: :object,
136+
title: "date",
137+
properties:
138+
Map.merge(
139+
%{
140+
format: %{
141+
const: "date",
142+
description:
143+
"Use to display date-time values and format them according to the user's locale."
144+
}
145+
},
146+
date_options()
147+
),
148+
required: [:format],
149+
additionalProperties: false
150+
},
151+
%{
152+
type: :object,
153+
title: "date_relative",
154+
properties: %{
155+
format: %{
156+
const: "date_relative",
157+
description: """
158+
Use format: "date_relative" to display a relative date string (e.g. “2 days ago”) by comparing the given value against the current date & time. Only the largest sensible unit is displayed, e.g. the formatter will only display “days” even when other components such as hours, minutes etc. aren't equal to zero.
159+
160+
The implementation can choose to update the displayed value in appropriate intervals. Also, it can choose to display the absolute date on user interaction, e.g. in a tooltip.
161+
162+
This format currently doesn't support any options.
163+
"""
164+
}
165+
},
166+
required: [:format],
167+
additionalProperties: false
168+
},
169+
%{
170+
type: :object,
171+
title: "date_iso",
172+
properties: %{
173+
format: %{
174+
const: "date_iso",
175+
description: "Use to display date-time values in ISO 8601 extended format."
176+
}
177+
},
178+
required: [:format],
179+
additionalProperties: false
180+
},
181+
%{
182+
type: :object,
183+
title: "date_unix",
184+
properties:
185+
Map.merge(
186+
%{
187+
format: %{
188+
const: "date_unix",
189+
description: "Use to display date-time values in seconds since unix epoch."
190+
}
191+
},
192+
date_unix_options()
193+
),
194+
required: [:format],
195+
additionalProperties: false
196+
},
197+
%{
198+
type: :object,
199+
title: "coordinates",
200+
properties:
201+
Map.merge(
202+
%{
203+
format: %{
204+
const: "coordinates",
205+
description: "Use to display latitude & longitude information."
206+
}
207+
},
208+
coordinates_options()
209+
),
210+
required: [:format],
211+
additionalProperties: false
212+
}
213+
]
214+
}
215+
end
216+
end
217+
218+
defmodule ValueFormatters.Schemas.DefaultFormats do
219+
import ValueFormatters.Schemas
220+
221+
def json_schema() do
222+
%{
223+
type: :object,
224+
description: "Default formats for value formatting",
225+
properties: %{
226+
number: %{
227+
type: [:object, :null],
228+
description:
229+
"Use to display numeric values and format them according to the user's locale.",
230+
properties: number_options(),
231+
additionalProperties: false
232+
},
233+
string: %{
234+
type: [:object, :null],
235+
description:
236+
"Use to explicitly disable any kind of formatting that would otherwise take place.",
237+
properties: %{},
238+
additionalProperties: false
239+
},
240+
date: %{
241+
type: [:object, :null],
242+
description:
243+
"Use to to display date-time values and format them according to the user's locale.",
244+
properties: date_options(),
245+
additionalProperties: false
246+
},
247+
date_relative: %{
248+
type: [:object, :null],
249+
description: """
250+
Use format: "date_relative" to display a relative date string (e.g. “2 days ago”) by comparing the given value against the current date & time. Only the largest sensible unit is displayed, e.g. the formatter will only display “days” even when other components such as hours, minutes etc. aren't equal to zero.
251+
252+
The implementation can choose to update the displayed value in appropriate intervals. Also, it can choose to display the absolute date on user interaction, e.g. in a tooltip.
253+
254+
This format currently doesn't support any options.
255+
""",
256+
properties: %{},
257+
additionalProperties: false
258+
},
259+
date_iso: %{
260+
type: [:object, :null],
261+
description: "Use to display date-time values in ISO 8601 extended format.",
262+
properties: %{},
263+
additionalProperties: false
264+
},
265+
date_unix: %{
266+
type: [:object, :null],
267+
description: "Use to display date-time values in seconds since unix epoch.",
268+
properties: date_unix_options(),
269+
additionalProperties: false
270+
},
271+
coordinates: %{
272+
type: [:object, :null],
273+
description: "Use to display latitude & longitude information.",
274+
properties: coordinates_options(),
275+
additionalProperties: false
276+
}
277+
},
278+
additionalProperties: false
279+
}
280+
end
281+
end

lib/value_formatters.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ defmodule ValueFormatters do
195195
value <> separator <> render_function.(unit)
196196
end
197197

198-
defp format_string(value, _string_definition), do: {:ok, value}
198+
defp format_string(value, _string_definition), do: {:ok, to_string(value)}
199199

200200
defp format_date(value, date_definition, opts) do
201201
with {:ok, value} <- pre_process_date_value(value, date_definition, opts) do

mix.exs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ defmodule ValueFormatters.MixProject do
44
def project do
55
[
66
app: :value_formatters,
7-
version: "0.1.3",
7+
version: "0.2.0",
88
elixir: "~> 1.15",
99
elixirc_paths: elixirc_paths(Mix.env()),
1010
start_permanent: Mix.env() == :prod,
@@ -32,7 +32,8 @@ defmodule ValueFormatters.MixProject do
3232
{:mox, "~> 1.0", only: [:dev, :test]},
3333
{:timex, "~> 3.7", only: :test},
3434
{:ok, "~> 2.3.0"},
35-
{:mix_test_watch, "~> 1.3", only: [:dev, :test]}
35+
{:mix_test_watch, "~> 1.3", only: [:dev, :test]},
36+
{:jsv, "~> 0.10", optional: true}
3637
]
3738
end
3839

mix.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
"hackney": {:hex, :hackney, "1.24.1", "f5205a125bba6ed4587f9db3cc7c729d11316fa8f215d3e57ed1c067a9703fa9", [:rebar3], [{:certifi, "~> 2.15.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.4", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "f4a7392a0b53d8bbc3eb855bdcc919cd677358e65b2afd3840b5b3690c4c8a39"},
2020
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
2121
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
22+
"jsv": {:hex, :jsv, "0.10.1", "a55790331196b92a17034f56cef48438dc407e6dca8cc570eff779f83f8680ea", [:mix], [{:abnf_parsec, "~> 2.0", [hex: :abnf_parsec, repo: "hexpm", optional: true]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:poison, "~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "2b51d3e90d7cdc839516c0c3ba0da21814f4666dbb94bcdf103def8766e78d74"},
2223
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
2324
"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"},
2425
"makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
2526
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
2627
"mimerl": {:hex, :mimerl, "1.4.0", "3882a5ca67fbbe7117ba8947f27643557adec38fa2307490c4c4207624cb213b", [:rebar3], [], "hexpm", "13af15f9f68c65884ecca3a3891d50a7b57d82152792f3e19d88650aa126b144"},
2728
"mix_test_watch": {:hex, :mix_test_watch, "1.3.0", "2ffc9f72b0d1f4ecf0ce97b044e0e3c607c3b4dc21d6228365e8bc7c2856dc77", [:mix], [{:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "f9e5edca976857ffac78632e635750d158df14ee2d6185a15013844af7570ffe"},
2829
"mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"},
30+
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
2931
"nimble_ownership": {:hex, :nimble_ownership, "1.0.1", "f69fae0cdd451b1614364013544e66e4f5d25f36a2056a9698b793305c5aa3a6", [:mix], [], "hexpm", "3825e461025464f519f3f3e4a1f9b68c47dc151369611629ad08b636b73bb22d"},
3032
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
3133
"ok": {:hex, :ok, "2.3.0", "0a3d513ec9038504dc5359d44e14fc14ef59179e625563a1a144199cdc3a6d30", [:mix], [], "hexpm", "f0347b3f8f115bf347c704184b33cf084f2943771273f2b98a3707a5fa43c4d5"},

0 commit comments

Comments
 (0)