From 199c80fd43c5a6cce27d7803ae65e2d65ad92772 Mon Sep 17 00:00:00 2001 From: Beata Date: Tue, 12 Aug 2025 12:32:51 +0200 Subject: [PATCH 1/7] add schemas --- lib/schemas.ex | 146 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 lib/schemas.ex diff --git a/lib/schemas.ex b/lib/schemas.ex new file mode 100644 index 0000000..12dc89d --- /dev/null +++ b/lib/schemas.ex @@ -0,0 +1,146 @@ +defmodule ValueFormatters.Schemas do + if Code.ensure_loaded?(JSV) do + def format do + %{ + type: :object, + description: "Formats for value formatting", + properties: %{ + oneOf: [ + %{ + type: :string, + description: "A shorthand representation of the format", + enum: [ + "number", + "string", + "date", + "date_relative", + "date_iso", + "date_unix", + "coordinates" + ] + }, + available_formats_schema() + ] + } + } + end + + def default_formats() do + %{ + type: :object, + description: "Default formats for value formatting", + properties: available_formats_schema() + } + end + + defp available_formats_schema() do + %{ + number: %{ + type: :object, + description: + "Use to display numeric values and format them according to the user's locale.", + properties: %{ + precision: %{type: :number, description: "Number of decimal places"}, + unit: %{ + type: :string, + description: "If set, the formatter appends ' ' + unit to the display value" + } + } + }, + string: %{ + type: :object, + description: + "Use to explicitly disable any kind of formatting that would otherwise take place.", + properties: %{} + }, + date: %{ + type: :object, + description: + "Use to to display date-time values and format them according to the user's locale.", + properties: %{ + date_display: %{ + type: :string, + description: """ + How the formatter should display the date portion: + + - `full`: Wednesday, November 29, 2023 + + - `long`: November 29, 2023 + + - `medium`: Nov 29, 2023 + + - `short`: 11/29/23 + + - `none`: Don't display date + """, + enum: ["full", "long", "medium", "short", "none"], + default: "medium" + }, + time_display: %{ + type: :string, + description: """ + How the formatter should display the time portion: + + - `full`: 3:44:28 PM GMT + + - `long`: 3:44:28 PM UTC + + - `medium`: 3:44:28 PM + + - `short`: 3:44 PM + + - `none`: Don't display time + """, + enum: ["full", "long", "medium", "short", "none"], + default: "medium" + } + } + }, + date_relative: %{ + type: :object, + description: """ + 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. + + 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. + + This format currently doesn't support any options. + """, + properties: %{} + }, + date_iso: %{ + type: :object, + description: "Use to display date-time values in ISO 8601 extended format.", + properties: %{} + }, + date_unix: %{ + type: :object, + description: "Use to display date-time values in seconds since unix epoch.", + properties: %{ + milliseconds: %{ + type: :boolean, + default: false, + description: + "Whether the formatter should output the values milliseconds (instead of seconds)." + } + } + }, + coordinates: %{ + type: :object, + description: "Use to display latitude & longitude information.", + properties: %{ + radius_display: %{ + type: :boolean, + default: true, + description: + "Whether the formatter should include the radius/accuracy information (if present)." + } + } + } + } + end + else + def schema do + raise "JSV is not available. Please add it to your dependencies." + end + end +end From 50ccf3885b3dfb06378955fa754eac628efd1ddd Mon Sep 17 00:00:00 2001 From: Beata Date: Tue, 12 Aug 2025 14:27:11 +0200 Subject: [PATCH 2/7] update schemas --- lib/schemas.ex | 306 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 213 insertions(+), 93 deletions(-) diff --git a/lib/schemas.ex b/lib/schemas.ex index 12dc89d..6df154d 100644 --- a/lib/schemas.ex +++ b/lib/schemas.ex @@ -1,6 +1,6 @@ defmodule ValueFormatters.Schemas do if Code.ensure_loaded?(JSV) do - def format do + def format() do %{ type: :object, description: "Formats for value formatting", @@ -19,7 +19,131 @@ defmodule ValueFormatters.Schemas do "coordinates" ] }, - available_formats_schema() + %{ + type: :object, + properties: %{ + format: %{ + const: "string", + description: + "Use to explicitly disable any kind of formatting that would otherwise take place." + } + } + }, + %{ + type: :object, + properties: %{ + format: %{ + const: "number", + description: + "Use to display numeric values and format them according to the user's locale." + }, + precision: %{type: :number, description: "Number of decimal places"}, + unit: %{ + type: :string, + description: "If set, the formatter appends ' ' + unit to the display value" + } + } + }, + %{ + type: :object, + properties: %{ + format: %{ + const: "date", + description: + "Use to display date-time values and format them according to the user's locale." + }, + date_display: %{ + type: :string, + description: """ + How the formatter should display the date portion: + + - `full`: Wednesday, November 29, 2023 + + - `long`: November 29, 2023 + + - `medium`: Nov 29, 2023 + + - `short`: 11/29/23 + + - `none`: Don't display date + """, + enum: ["full", "long", "medium", "short", "none"], + default: "medium" + }, + time_display: %{ + type: :string, + description: """ + How the formatter should display the time portion: + + - `full`: 3:44:28 PM GMT + + - `long`: 3:44:28 PM UTC + + - `medium`: 3:44:28 PM + + - `short`: 3:44 PM + + - `none`: Don't display time + """, + enum: ["full", "long", "medium", "short", "none"], + default: "medium" + } + } + }, + %{ + type: :object, + properties: %{ + format: %{ + const: "date_relative", + description: """ + 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. + + 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. + + This format currently doesn't support any options. + """ + } + } + }, + %{ + type: :object, + properties: %{ + format: %{ + const: "date_iso", + description: "Use to display date-time values in ISO 8601 extended format." + } + } + }, + %{ + type: :object, + properties: %{ + format: %{ + const: "date_unix", + description: "Use to display date-time values in seconds since unix epoch." + }, + milliseconds: %{ + type: :boolean, + default: false, + description: + "Whether the formatter should output the values milliseconds (instead of seconds)." + } + } + }, + %{ + type: :object, + properties: %{ + format: %{ + const: "coordinate", + description: "Use to display latitude & longitude information." + }, + radius_display: %{ + type: :boolean, + default: true, + description: + "Whether the formatter should include the radius/accuracy information (if present)." + } + } + } ] } } @@ -29,110 +153,106 @@ defmodule ValueFormatters.Schemas do %{ type: :object, description: "Default formats for value formatting", - properties: available_formats_schema() - } - end - - defp available_formats_schema() do - %{ - number: %{ - type: :object, - description: - "Use to display numeric values and format them according to the user's locale.", - properties: %{ - precision: %{type: :number, description: "Number of decimal places"}, - unit: %{ - type: :string, - description: "If set, the formatter appends ' ' + unit to the display value" + properties: %{ + number: %{ + type: :object, + description: + "Use to display numeric values and format them according to the user's locale.", + properties: %{ + precision: %{type: :number, description: "Number of decimal places"}, + unit: %{ + type: :string, + description: "If set, the formatter appends ' ' + unit to the display value" + } } - } - }, - string: %{ - type: :object, - description: - "Use to explicitly disable any kind of formatting that would otherwise take place.", - properties: %{} - }, - date: %{ - type: :object, - description: - "Use to to display date-time values and format them according to the user's locale.", - properties: %{ - date_display: %{ - type: :string, - description: """ - How the formatter should display the date portion: + }, + string: %{ + type: :object, + description: + "Use to explicitly disable any kind of formatting that would otherwise take place.", + properties: %{} + }, + date: %{ + type: :object, + description: + "Use to to display date-time values and format them according to the user's locale.", + properties: %{ + date_display: %{ + type: :string, + description: """ + How the formatter should display the date portion: - - `full`: Wednesday, November 29, 2023 + - `full`: Wednesday, November 29, 2023 - - `long`: November 29, 2023 + - `long`: November 29, 2023 - - `medium`: Nov 29, 2023 + - `medium`: Nov 29, 2023 - - `short`: 11/29/23 + - `short`: 11/29/23 - - `none`: Don't display date - """, - enum: ["full", "long", "medium", "short", "none"], - default: "medium" - }, - time_display: %{ - type: :string, - description: """ - How the formatter should display the time portion: + - `none`: Don't display date + """, + enum: ["full", "long", "medium", "short", "none"], + default: "medium" + }, + time_display: %{ + type: :string, + description: """ + How the formatter should display the time portion: - - `full`: 3:44:28 PM GMT + - `full`: 3:44:28 PM GMT - - `long`: 3:44:28 PM UTC + - `long`: 3:44:28 PM UTC - - `medium`: 3:44:28 PM + - `medium`: 3:44:28 PM - - `short`: 3:44 PM + - `short`: 3:44 PM - - `none`: Don't display time - """, - enum: ["full", "long", "medium", "short", "none"], - default: "medium" + - `none`: Don't display time + """, + enum: ["full", "long", "medium", "short", "none"], + default: "medium" + } } - } - }, - date_relative: %{ - type: :object, - description: """ - 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. - - 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. - - This format currently doesn't support any options. - """, - properties: %{} - }, - date_iso: %{ - type: :object, - description: "Use to display date-time values in ISO 8601 extended format.", - properties: %{} - }, - date_unix: %{ - type: :object, - description: "Use to display date-time values in seconds since unix epoch.", - properties: %{ - milliseconds: %{ - type: :boolean, - default: false, - description: - "Whether the formatter should output the values milliseconds (instead of seconds)." + }, + date_relative: %{ + type: :object, + description: """ + 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. + + 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. + + This format currently doesn't support any options. + """, + properties: %{} + }, + date_iso: %{ + type: :object, + description: "Use to display date-time values in ISO 8601 extended format.", + properties: %{} + }, + date_unix: %{ + type: :object, + description: "Use to display date-time values in seconds since unix epoch.", + properties: %{ + milliseconds: %{ + type: :boolean, + default: false, + description: + "Whether the formatter should output the values milliseconds (instead of seconds)." + } } - } - }, - coordinates: %{ - type: :object, - description: "Use to display latitude & longitude information.", - properties: %{ - radius_display: %{ - type: :boolean, - default: true, - description: - "Whether the formatter should include the radius/accuracy information (if present)." + }, + coordinates: %{ + type: :object, + description: "Use to display latitude & longitude information.", + properties: %{ + radius_display: %{ + type: :boolean, + default: true, + description: + "Whether the formatter should include the radius/accuracy information (if present)." + } } } } From 5936ec57ee70b1d481e63f097e92f583abe55f9c Mon Sep 17 00:00:00 2001 From: Beata Date: Tue, 12 Aug 2025 15:27:50 +0200 Subject: [PATCH 3/7] use jsv convention --- lib/schemas.ex | 453 +++++++++++++++++++++++++------------------------ mix.exs | 3 +- mix.lock | 2 + 3 files changed, 231 insertions(+), 227 deletions(-) diff --git a/lib/schemas.ex b/lib/schemas.ex index 6df154d..c70939d 100644 --- a/lib/schemas.ex +++ b/lib/schemas.ex @@ -1,266 +1,267 @@ -defmodule ValueFormatters.Schemas do - if Code.ensure_loaded?(JSV) do - def format() do - %{ - type: :object, - description: "Formats for value formatting", - properties: %{ - oneOf: [ - %{ - type: :string, - description: "A shorthand representation of the format", - enum: [ - "number", - "string", - "date", - "date_relative", - "date_iso", - "date_unix", - "coordinates" - ] - }, - %{ - type: :object, - properties: %{ - format: %{ - const: "string", - description: - "Use to explicitly disable any kind of formatting that would otherwise take place." - } - } +defmodule ValueFormatters.Schemas.Format do + def json_schema() do + %{ + type: :object, + description: "Formats for value formatting", + oneOf: [ + %{ + type: :string, + description: "A shorthand representation of the format", + enum: [ + "number", + "string", + "date", + "date_relative", + "date_iso", + "date_unix", + "coordinates" + ] + }, + %{ + type: :object, + title: "string", + properties: %{ + format: %{ + const: "string", + description: + "Use to explicitly disable any kind of formatting that would otherwise take place." + } + } + }, + %{ + type: :object, + title: "number", + properties: %{ + format: %{ + const: "number", + description: + "Use to display numeric values and format them according to the user's locale." }, - %{ - type: :object, - properties: %{ - format: %{ - const: "number", - description: - "Use to display numeric values and format them according to the user's locale." - }, - precision: %{type: :number, description: "Number of decimal places"}, - unit: %{ - type: :string, - description: "If set, the formatter appends ' ' + unit to the display value" - } - } + precision: %{type: :number, description: "Number of decimal places"}, + unit: %{ + type: :string, + description: "If set, the formatter appends ' ' + unit to the display value" + } + } + }, + %{ + type: :object, + title: "date", + properties: %{ + format: %{ + const: "date", + description: + "Use to display date-time values and format them according to the user's locale." }, - %{ - type: :object, - properties: %{ - format: %{ - const: "date", - description: - "Use to display date-time values and format them according to the user's locale." - }, - date_display: %{ - type: :string, - description: """ - How the formatter should display the date portion: + date_display: %{ + type: :string, + description: """ + How the formatter should display the date portion: - - `full`: Wednesday, November 29, 2023 + - `full`: Wednesday, November 29, 2023 - - `long`: November 29, 2023 + - `long`: November 29, 2023 - - `medium`: Nov 29, 2023 + - `medium`: Nov 29, 2023 - - `short`: 11/29/23 + - `short`: 11/29/23 - - `none`: Don't display date - """, - enum: ["full", "long", "medium", "short", "none"], - default: "medium" - }, - time_display: %{ - type: :string, - description: """ - How the formatter should display the time portion: + - `none`: Don't display date + """, + enum: ["full", "long", "medium", "short", "none"], + default: "medium" + }, + time_display: %{ + type: :string, + description: """ + How the formatter should display the time portion: - - `full`: 3:44:28 PM GMT + - `full`: 3:44:28 PM GMT - - `long`: 3:44:28 PM UTC + - `long`: 3:44:28 PM UTC - - `medium`: 3:44:28 PM + - `medium`: 3:44:28 PM - - `short`: 3:44 PM + - `short`: 3:44 PM - - `none`: Don't display time - """, - enum: ["full", "long", "medium", "short", "none"], - default: "medium" - } - } - }, - %{ - type: :object, - properties: %{ - format: %{ - const: "date_relative", - description: """ - 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. + - `none`: Don't display time + """, + enum: ["full", "long", "medium", "short", "none"], + default: "medium" + } + } + }, + %{ + type: :object, + title: "date_relative", + properties: %{ + format: %{ + const: "date_relative", + description: """ + 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. - 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. + 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. - This format currently doesn't support any options. - """ - } - } - }, - %{ - type: :object, - properties: %{ - format: %{ - const: "date_iso", - description: "Use to display date-time values in ISO 8601 extended format." - } - } + This format currently doesn't support any options. + """ + } + } + }, + %{ + type: :object, + title: "date_iso", + properties: %{ + format: %{ + const: "date_iso", + description: "Use to display date-time values in ISO 8601 extended format." + } + } + }, + %{ + type: :object, + title: "date_unix", + properties: %{ + format: %{ + const: "date_unix", + description: "Use to display date-time values in seconds since unix epoch." }, - %{ - type: :object, - properties: %{ - format: %{ - const: "date_unix", - description: "Use to display date-time values in seconds since unix epoch." - }, - milliseconds: %{ - type: :boolean, - default: false, - description: - "Whether the formatter should output the values milliseconds (instead of seconds)." - } - } + milliseconds: %{ + type: :boolean, + default: false, + description: + "Whether the formatter should output the values milliseconds (instead of seconds)." + } + } + }, + %{ + type: :object, + title: "coordinate", + properties: %{ + format: %{ + const: "coordinate", + description: "Use to display latitude & longitude information." }, - %{ - type: :object, - properties: %{ - format: %{ - const: "coordinate", - description: "Use to display latitude & longitude information." - }, - radius_display: %{ - type: :boolean, - default: true, - description: - "Whether the formatter should include the radius/accuracy information (if present)." - } - } + radius_display: %{ + type: :boolean, + default: true, + description: + "Whether the formatter should include the radius/accuracy information (if present)." } - ] + } } - } - end + ] + } + end +end - def default_formats() do - %{ - type: :object, - description: "Default formats for value formatting", - properties: %{ - number: %{ - type: :object, - description: - "Use to display numeric values and format them according to the user's locale.", - properties: %{ - precision: %{type: :number, description: "Number of decimal places"}, - unit: %{ - type: :string, - description: "If set, the formatter appends ' ' + unit to the display value" - } +defmodule ValueFormatters.Schemas.DefaultFormats do + def json_schema() do + %{ + type: :object, + description: "Default formats for value formatting", + properties: %{ + number: %{ + type: :object, + description: + "Use to display numeric values and format them according to the user's locale.", + properties: %{ + precision: %{type: :number, description: "Number of decimal places"}, + unit: %{ + type: :string, + description: "If set, the formatter appends ' ' + unit to the display value" } - }, - string: %{ - type: :object, - description: - "Use to explicitly disable any kind of formatting that would otherwise take place.", - properties: %{} - }, - date: %{ - type: :object, - description: - "Use to to display date-time values and format them according to the user's locale.", - properties: %{ - date_display: %{ - type: :string, - description: """ - How the formatter should display the date portion: + } + }, + string: %{ + type: :object, + description: + "Use to explicitly disable any kind of formatting that would otherwise take place.", + properties: %{} + }, + date: %{ + type: :object, + description: + "Use to to display date-time values and format them according to the user's locale.", + properties: %{ + date_display: %{ + type: :string, + description: """ + How the formatter should display the date portion: - - `full`: Wednesday, November 29, 2023 + - `full`: Wednesday, November 29, 2023 - - `long`: November 29, 2023 + - `long`: November 29, 2023 - - `medium`: Nov 29, 2023 + - `medium`: Nov 29, 2023 - - `short`: 11/29/23 + - `short`: 11/29/23 - - `none`: Don't display date - """, - enum: ["full", "long", "medium", "short", "none"], - default: "medium" - }, - time_display: %{ - type: :string, - description: """ - How the formatter should display the time portion: + - `none`: Don't display date + """, + enum: ["full", "long", "medium", "short", "none"], + default: "medium" + }, + time_display: %{ + type: :string, + description: """ + How the formatter should display the time portion: - - `full`: 3:44:28 PM GMT + - `full`: 3:44:28 PM GMT - - `long`: 3:44:28 PM UTC + - `long`: 3:44:28 PM UTC - - `medium`: 3:44:28 PM + - `medium`: 3:44:28 PM - - `short`: 3:44 PM + - `short`: 3:44 PM - - `none`: Don't display time - """, - enum: ["full", "long", "medium", "short", "none"], - default: "medium" - } + - `none`: Don't display time + """, + enum: ["full", "long", "medium", "short", "none"], + default: "medium" } - }, - date_relative: %{ - type: :object, - description: """ - 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. + } + }, + date_relative: %{ + type: :object, + description: """ + 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. - 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. + 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. - This format currently doesn't support any options. - """, - properties: %{} - }, - date_iso: %{ - type: :object, - description: "Use to display date-time values in ISO 8601 extended format.", - properties: %{} - }, - date_unix: %{ - type: :object, - description: "Use to display date-time values in seconds since unix epoch.", - properties: %{ - milliseconds: %{ - type: :boolean, - default: false, - description: - "Whether the formatter should output the values milliseconds (instead of seconds)." - } + This format currently doesn't support any options. + """, + properties: %{} + }, + date_iso: %{ + type: :object, + description: "Use to display date-time values in ISO 8601 extended format.", + properties: %{} + }, + date_unix: %{ + type: :object, + description: "Use to display date-time values in seconds since unix epoch.", + properties: %{ + milliseconds: %{ + type: :boolean, + default: false, + description: + "Whether the formatter should output the values milliseconds (instead of seconds)." } - }, - coordinates: %{ - type: :object, - description: "Use to display latitude & longitude information.", - properties: %{ - radius_display: %{ - type: :boolean, - default: true, - description: - "Whether the formatter should include the radius/accuracy information (if present)." - } + } + }, + coordinates: %{ + type: :object, + description: "Use to display latitude & longitude information.", + properties: %{ + radius_display: %{ + type: :boolean, + default: true, + description: + "Whether the formatter should include the radius/accuracy information (if present)." } } } } - end - else - def schema do - raise "JSV is not available. Please add it to your dependencies." - end + } end end diff --git a/mix.exs b/mix.exs index 8828d82..6b6c02b 100644 --- a/mix.exs +++ b/mix.exs @@ -32,7 +32,8 @@ defmodule ValueFormatters.MixProject do {:mox, "~> 1.0", only: [:dev, :test]}, {:timex, "~> 3.7", only: :test}, {:ok, "~> 2.3.0"}, - {:mix_test_watch, "~> 1.3", only: [:dev, :test]} + {:mix_test_watch, "~> 1.3", only: [:dev, :test]}, + {:jsv, "~> 0.10", optional: true} ] end diff --git a/mix.lock b/mix.lock index fb176b9..3b19b74 100644 --- a/mix.lock +++ b/mix.lock @@ -19,6 +19,7 @@ "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"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "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"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "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"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, @@ -26,6 +27,7 @@ "mimerl": {:hex, :mimerl, "1.4.0", "3882a5ca67fbbe7117ba8947f27643557adec38fa2307490c4c4207624cb213b", [:rebar3], [], "hexpm", "13af15f9f68c65884ecca3a3891d50a7b57d82152792f3e19d88650aa126b144"}, "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"}, "mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_ownership": {:hex, :nimble_ownership, "1.0.1", "f69fae0cdd451b1614364013544e66e4f5d25f36a2056a9698b793305c5aa3a6", [:mix], [], "hexpm", "3825e461025464f519f3f3e4a1f9b68c47dc151369611629ad08b636b73bb22d"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "ok": {:hex, :ok, "2.3.0", "0a3d513ec9038504dc5359d44e14fc14ef59179e625563a1a144199cdc3a6d30", [:mix], [], "hexpm", "f0347b3f8f115bf347c704184b33cf084f2943771273f2b98a3707a5fa43c4d5"}, From a6680f2046a04c14c7d0edf422fbde2b0a355910 Mon Sep 17 00:00:00 2001 From: Beata Date: Tue, 12 Aug 2025 15:29:18 +0200 Subject: [PATCH 4/7] use title for shorthand format version --- lib/schemas.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/schemas.ex b/lib/schemas.ex index c70939d..df8dc48 100644 --- a/lib/schemas.ex +++ b/lib/schemas.ex @@ -6,6 +6,7 @@ defmodule ValueFormatters.Schemas.Format do oneOf: [ %{ type: :string, + title: "shorthand", description: "A shorthand representation of the format", enum: [ "number", From 8fe0ce7c6e043f96c2f58db0b50377a7960200f5 Mon Sep 17 00:00:00 2001 From: Beata Date: Wed, 13 Aug 2025 10:17:45 +0200 Subject: [PATCH 5/7] version bump to 0.2.0 --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 6b6c02b..e12d6ef 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule ValueFormatters.MixProject do def project do [ app: :value_formatters, - version: "0.1.3", + version: "0.2.0", elixir: "~> 1.15", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, From 1e026434ff48972456c17fb4e997aeef419543af Mon Sep 17 00:00:00 2001 From: Beata Date: Wed, 13 Aug 2025 11:06:13 +0200 Subject: [PATCH 6/7] update schemas and add tests Co-authored-by: Stefan Fochler --- lib/schemas.ex | 329 +++++++++++++++++++++------------------- lib/value_formatters.ex | 2 +- test/schemas_test.exs | 160 +++++++++++++++++++ 3 files changed, 332 insertions(+), 159 deletions(-) create mode 100644 test/schemas_test.exs diff --git a/lib/schemas.ex b/lib/schemas.ex index df8dc48..b1e5183 100644 --- a/lib/schemas.ex +++ b/lib/schemas.ex @@ -1,9 +1,92 @@ +defmodule ValueFormatters.Schemas do + def number_options do + %{ + precision: %{ + type: :number, + description: "Number of decimal places" + }, + unit: %{ + type: :string, + description: "If set, the formatter appends ' ' + unit to the display value" + } + } + end + + def date_options do + %{ + date_display: %{ + type: :string, + description: """ + How the formatter should display the date portion: + + - `full`: Wednesday, November 29, 2023 + + - `long`: November 29, 2023 + + - `medium`: Nov 29, 2023 + + - `short`: 11/29/23 + + - `none`: Don't display date + """, + enum: ["full", "long", "medium", "short", "none"], + default: "medium" + }, + time_display: %{ + type: :string, + description: """ + How the formatter should display the time portion: + + - `full`: 3:44:28 PM GMT + + - `long`: 3:44:28 PM UTC + + - `medium`: 3:44:28 PM + + - `short`: 3:44 PM + + - `none`: Don't display time + """, + enum: ["full", "long", "medium", "short", "none"], + default: "medium" + } + } + end + + def date_unix_options do + %{ + milliseconds: %{ + type: :boolean, + default: false, + description: + "Whether the formatter should output the values milliseconds (instead of seconds)." + } + } + end + + def coordinates_options do + %{ + radius_display: %{ + type: :boolean, + default: true, + description: + "Whether the formatter should include the radius/accuracy information (if present)." + } + } + end +end + defmodule ValueFormatters.Schemas.Format do + import ValueFormatters.Schemas + def json_schema() do %{ - type: :object, description: "Formats for value formatting", oneOf: [ + %{ + const: nil, + description: "Skip formatting and return raw value." + }, %{ type: :string, title: "shorthand", @@ -25,72 +108,45 @@ defmodule ValueFormatters.Schemas.Format do format: %{ const: "string", description: - "Use to explicitly disable any kind of formatting that would otherwise take place." + "Use to explicitly disable any kind of formatting that would otherwise take place, but still return a string." } - } + }, + required: [:format], + additionalProperties: false }, %{ type: :object, title: "number", - properties: %{ - format: %{ - const: "number", - description: - "Use to display numeric values and format them according to the user's locale." - }, - precision: %{type: :number, description: "Number of decimal places"}, - unit: %{ - type: :string, - description: "If set, the formatter appends ' ' + unit to the display value" - } - } + properties: + Map.merge( + %{ + format: %{ + const: "number", + description: + "Use to display numeric values and format them according to the user's locale." + } + }, + number_options() + ), + required: [:format], + additionalProperties: false }, %{ type: :object, title: "date", - properties: %{ - format: %{ - const: "date", - description: - "Use to display date-time values and format them according to the user's locale." - }, - date_display: %{ - type: :string, - description: """ - How the formatter should display the date portion: - - - `full`: Wednesday, November 29, 2023 - - - `long`: November 29, 2023 - - - `medium`: Nov 29, 2023 - - - `short`: 11/29/23 - - - `none`: Don't display date - """, - enum: ["full", "long", "medium", "short", "none"], - default: "medium" - }, - time_display: %{ - type: :string, - description: """ - How the formatter should display the time portion: - - - `full`: 3:44:28 PM GMT - - - `long`: 3:44:28 PM UTC - - - `medium`: 3:44:28 PM - - - `short`: 3:44 PM - - - `none`: Don't display time - """, - enum: ["full", "long", "medium", "short", "none"], - default: "medium" - } - } + properties: + Map.merge( + %{ + format: %{ + const: "date", + description: + "Use to display date-time values and format them according to the user's locale." + } + }, + date_options() + ), + required: [:format], + additionalProperties: false }, %{ type: :object, @@ -106,7 +162,9 @@ defmodule ValueFormatters.Schemas.Format do This format currently doesn't support any options. """ } - } + }, + required: [:format], + additionalProperties: false }, %{ type: :object, @@ -116,39 +174,41 @@ defmodule ValueFormatters.Schemas.Format do const: "date_iso", description: "Use to display date-time values in ISO 8601 extended format." } - } + }, + required: [:format], + additionalProperties: false }, %{ type: :object, title: "date_unix", - properties: %{ - format: %{ - const: "date_unix", - description: "Use to display date-time values in seconds since unix epoch." - }, - milliseconds: %{ - type: :boolean, - default: false, - description: - "Whether the formatter should output the values milliseconds (instead of seconds)." - } - } + properties: + Map.merge( + %{ + format: %{ + const: "date_unix", + description: "Use to display date-time values in seconds since unix epoch." + } + }, + date_unix_options() + ), + required: [:format], + additionalProperties: false }, %{ type: :object, - title: "coordinate", - properties: %{ - format: %{ - const: "coordinate", - description: "Use to display latitude & longitude information." - }, - radius_display: %{ - type: :boolean, - default: true, - description: - "Whether the formatter should include the radius/accuracy information (if present)." - } - } + title: "coordinates", + properties: + Map.merge( + %{ + format: %{ + const: "coordinates", + description: "Use to display latitude & longitude information." + } + }, + coordinates_options() + ), + required: [:format], + additionalProperties: false } ] } @@ -156,74 +216,36 @@ defmodule ValueFormatters.Schemas.Format do end defmodule ValueFormatters.Schemas.DefaultFormats do + import ValueFormatters.Schemas + def json_schema() do %{ type: :object, description: "Default formats for value formatting", properties: %{ number: %{ - type: :object, + type: [:object, :null], description: - "Use to display numeric values and format them according to the user's locale.", - properties: %{ - precision: %{type: :number, description: "Number of decimal places"}, - unit: %{ - type: :string, - description: "If set, the formatter appends ' ' + unit to the display value" - } - } + "Use to display numeric values and format them according to the user's locale.", + properties: number_options(), + additionalProperties: false }, string: %{ - type: :object, + type: [:object, :null], description: "Use to explicitly disable any kind of formatting that would otherwise take place.", - properties: %{} + properties: %{}, + additionalProperties: false }, date: %{ - type: :object, + type: [:object, :null], description: "Use to to display date-time values and format them according to the user's locale.", - properties: %{ - date_display: %{ - type: :string, - description: """ - How the formatter should display the date portion: - - - `full`: Wednesday, November 29, 2023 - - - `long`: November 29, 2023 - - - `medium`: Nov 29, 2023 - - - `short`: 11/29/23 - - - `none`: Don't display date - """, - enum: ["full", "long", "medium", "short", "none"], - default: "medium" - }, - time_display: %{ - type: :string, - description: """ - How the formatter should display the time portion: - - - `full`: 3:44:28 PM GMT - - - `long`: 3:44:28 PM UTC - - - `medium`: 3:44:28 PM - - - `short`: 3:44 PM - - - `none`: Don't display time - """, - enum: ["full", "long", "medium", "short", "none"], - default: "medium" - } - } + properties: date_options(), + additionalProperties: false }, date_relative: %{ - type: :object, + type: [:object, :null], description: """ 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. @@ -231,38 +253,29 @@ defmodule ValueFormatters.Schemas.DefaultFormats do This format currently doesn't support any options. """, - properties: %{} + properties: %{}, + additionalProperties: false }, date_iso: %{ - type: :object, + type: [:object, :null], description: "Use to display date-time values in ISO 8601 extended format.", - properties: %{} + properties: %{}, + additionalProperties: false }, date_unix: %{ - type: :object, + type: [:object, :null], description: "Use to display date-time values in seconds since unix epoch.", - properties: %{ - milliseconds: %{ - type: :boolean, - default: false, - description: - "Whether the formatter should output the values milliseconds (instead of seconds)." - } - } + properties: date_unix_options(), + additionalProperties: false }, coordinates: %{ - type: :object, + type: [:object, :null], description: "Use to display latitude & longitude information.", - properties: %{ - radius_display: %{ - type: :boolean, - default: true, - description: - "Whether the formatter should include the radius/accuracy information (if present)." - } - } + properties: coordinates_options(), + additionalProperties: false } - } + }, + additionalProperties: false } end end diff --git a/lib/value_formatters.ex b/lib/value_formatters.ex index 35e4903..e9c0e96 100644 --- a/lib/value_formatters.ex +++ b/lib/value_formatters.ex @@ -195,7 +195,7 @@ defmodule ValueFormatters do value <> separator <> render_function.(unit) end - defp format_string(value, _string_definition), do: {:ok, value} + defp format_string(value, _string_definition), do: {:ok, to_string(value)} defp format_date(value, date_definition, opts) do with {:ok, value} <- pre_process_date_value(value, date_definition, opts) do diff --git a/test/schemas_test.exs b/test/schemas_test.exs new file mode 100644 index 0000000..fc9fccd --- /dev/null +++ b/test/schemas_test.exs @@ -0,0 +1,160 @@ +defmodule ValueFormatters.SchemasTest do + use ExUnit.Case, async: true + + alias ValueFormatters.Schemas.{ + Format, + DefaultFormats + } + + describe "Format Schema" do + test "accepts shorthands" do + assert {:ok, _} = validate_format("date") + assert {:ok, _} = validate_format("number") + assert {:ok, _} = validate_format("string") + assert {:ok, _} = validate_format("date_relative") + assert {:ok, _} = validate_format("date_unix") + assert {:ok, _} = validate_format("date_iso") + assert {:ok, _} = validate_format("coordinates") + end + + test "accepts null shorthand" do + assert {:ok, _} = validate_format(nil) + end + + test "accepts extended format" do + assert {:ok, _} = validate_format(%{"format" => "date"}) + end + + test "accepts date options" do + assert {:ok, _} = + validate_format(%{ + "format" => "date", + "time_display" => "short", + "date_display" => "long" + }) + end + + test "accepts number options" do + assert {:ok, _} = + validate_format(%{ + "format" => "number", + "precision" => 3, + "unit" => "°C" + }) + end + + test "accepts date_unix options" do + assert {:ok, _} = + validate_format(%{ + "format" => "date_unix", + "milliseconds" => true + }) + end + + test "accepts coordinates options" do + assert {:ok, _} = + validate_format(%{ + "format" => "coordinates", + "radius_display" => true + }) + end + + test "doesn't accept invalid format type" do + assert {:error, _} = + validate_default_formats(%{ + "format" => "foo" + }) + end + + test "doesn't accept invalid date options" do + assert {:error, _} = + validate_format(%{ + "format" => "date", + "time_display" => "short", + "date_display" => "long", + "foo" => "bar" + }) + end + end + + describe "DefaultFormats Schema" do + test "accepts empty object" do + assert {:ok, _} = validate_default_formats(%{}) + end + + test "accepts null value" do + assert {:ok, _} = validate_default_formats(%{"number" => nil}) + end + + test "accepts number defaults" do + assert {:ok, _} = + validate_default_formats(%{ + "number" => %{ + "precision" => 2, + "unit" => "kg" + } + }) + end + + test "accepts date_unix defaults" do + assert {:ok, _} = + validate_default_formats(%{ + "date_unix" => %{ + "milliseconds" => true + } + }) + end + + test "accepts date defaults" do + assert {:ok, _} = + validate_default_formats(%{ + "date" => %{ + "date_display" => "long", + "time_display" => "short" + } + }) + end + + test "accepts coordinate defaults" do + assert {:ok, _} = + validate_default_formats(%{ + "coordinates" => %{ + "radius_display" => false + } + }) + end + + test "doesn't accept invalid date options" do + assert {:error, _} = + validate_default_formats(%{ + "date" => %{ + "date_display" => "long", + "time_display" => "short", + "foo" => "bar" + } + }) + end + + test "doesn't accept invalid format type" do + assert {:error, _} = + validate_default_formats(%{ + "foo" => %{ + "date_display" => "long", + "time_display" => "short" + } + }) + end + end + + defp validate_format(format) do + format_schema = JSV.build!(Format) + + JSV.validate(format, format_schema) + end + + defp validate_default_formats(default_formats) do + default_formats_schema = JSV.build!(DefaultFormats) + + JSV.validate(default_formats, default_formats_schema) + end +end From 87439fb73af8bc22a6a625cad74b66f688631e3c Mon Sep 17 00:00:00 2001 From: Beata Date: Wed, 13 Aug 2025 11:09:55 +0200 Subject: [PATCH 7/7] add changelog --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..46b035b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +Unreleased changes will be displayed here upon implementation. + +## [0.2.0] - 2025-08-13 + +### Added + +- [JSV](https://hexdocs.pm/jsv/JSV.html)-compatible JsonSchema definitions for `format` and `defaults` as + `ValueFormatter.Schema.Format` and `ValueFormatter.Schema.DefaultFormats`, respectively. + +[unreleased]: https://github.com/box-id/value_formatters/compare/0.2.0...HEAD +[0.2.0]: https://github.com/box-id/value_formatters/releases/tag/0.2.0 \ No newline at end of file