Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
281 changes: 281 additions & 0 deletions lib/schemas.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
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
%{
description: "Formats for value formatting",
oneOf: [
%{
const: nil,
description: "Skip formatting and return raw value."
},
%{
type: :string,
title: "shorthand",
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, but still return a string."
}
},
required: [:format],
additionalProperties: false
},
%{
type: :object,
title: "number",
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:
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,
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.

This format currently doesn't support any options.
"""
}
},
required: [:format],
additionalProperties: false
},
%{
type: :object,
title: "date_iso",
properties: %{
format: %{
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:
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: "coordinates",
properties:
Map.merge(
%{
format: %{
const: "coordinates",
description: "Use to display latitude & longitude information."
}
},
coordinates_options()
),
required: [:format],
additionalProperties: false
}
]
}
end
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, :null],
description:
"Use to display numeric values and format them according to the user's locale.",
properties: number_options(),
additionalProperties: false
},
string: %{
type: [:object, :null],
description:
"Use to explicitly disable any kind of formatting that would otherwise take place.",
properties: %{},
additionalProperties: false
},
date: %{
type: [:object, :null],
description:
"Use to to display date-time values and format them according to the user's locale.",
properties: date_options(),
additionalProperties: false
},
date_relative: %{
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.

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: %{},
additionalProperties: false
},
date_iso: %{
type: [:object, :null],
description: "Use to display date-time values in ISO 8601 extended format.",
properties: %{},
additionalProperties: false
},
date_unix: %{
type: [:object, :null],
description: "Use to display date-time values in seconds since unix epoch.",
properties: date_unix_options(),
additionalProperties: false
},
coordinates: %{
type: [:object, :null],
description: "Use to display latitude & longitude information.",
properties: coordinates_options(),
additionalProperties: false
}
},
additionalProperties: false
}
end
end
2 changes: 1 addition & 1 deletion lib/value_formatters.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
"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"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"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"},
Expand Down
Loading