Skip to content

Commit

Permalink
Merge pull request #161 from njausteve/152-key-field-to-passFieldContent
Browse files Browse the repository at this point in the history
feat: Add validation for required value field
  • Loading branch information
njausteve authored Oct 4, 2024
2 parents 115d2f4 + d57d409 commit 0dd4ec0
Show file tree
Hide file tree
Showing 3 changed files with 300 additions and 83 deletions.
38 changes: 28 additions & 10 deletions lib/structs/field_content.ex
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ defmodule ExPass.Structs.FieldContent do
* "PKNumberStylePercent"
* "PKNumberStyleScientific"
* "PKNumberStyleSpellOut"
- `value`: The value to use for the field. This can be a localizable string, ISO 8601 date, or number.
This field is required. A date or time value must include a time zone.
"""

use TypedStruct
Expand Down Expand Up @@ -126,6 +129,18 @@ defmodule ExPass.Structs.FieldContent do
"""
@type number_style() :: String.t()

@typedoc """
The value to use for the field.
Required. Can be:
- A localizable string (e.g., "Hello, World!")
- An ISO 8601 date string (e.g., "2023-04-15T14:30:00Z")
- A number (e.g., 42)
A date or time value must include a time zone.
"""
@type value() :: String.t() | number() | DateTime.t() | Date.t()

typedstruct do
field :attributed_value, attributed_value(), default: nil
field :change_message, String.t(), default: nil
Expand All @@ -137,6 +152,7 @@ defmodule ExPass.Structs.FieldContent do
field :key, String.t(), enforce: true
field :label, String.t(), default: nil
field :number_style, number_style(), default: nil
field :value, value(), enforce: true
end

@doc """
Expand All @@ -154,6 +170,7 @@ defmodule ExPass.Structs.FieldContent do
• key
• label
• number_style
• value
## Parameters
Expand All @@ -169,23 +186,23 @@ defmodule ExPass.Structs.FieldContent do
## Examples
iex> FieldContent.new(%{key: "field1", attributed_value: "Hello, World!"})
%FieldContent{key: "field1", attributed_value: "Hello, World!", change_message: nil, currency_code: nil, data_detector_types: nil, date_style: nil, ignores_time_zone: nil, is_relative: nil, label: nil, number_style: nil}
iex> FieldContent.new(%{key: "field1", value: "Hello, World!"})
%FieldContent{key: "field1", attributed_value: nil, change_message: nil, currency_code: nil, data_detector_types: nil, date_style: nil, ignores_time_zone: nil, is_relative: nil, label: nil, number_style: nil, value: "Hello, World!"}
iex> FieldContent.new(%{key: "field2", attributed_value: 42, data_detector_types: ["PKDataDetectorTypePhoneNumber"], date_style: "PKDateStyleShort", ignores_time_zone: true, is_relative: false, number_style: "PKNumberStyleDecimal"})
%FieldContent{key: "field2", attributed_value: 42, change_message: nil, currency_code: nil, data_detector_types: ["PKDataDetectorTypePhoneNumber"], date_style: "PKDateStyleShort", ignores_time_zone: true, is_relative: false, label: nil, number_style: "PKNumberStyleDecimal"}
iex> FieldContent.new(%{key: "field2", value: 42, data_detector_types: ["PKDataDetectorTypePhoneNumber"], date_style: "PKDateStyleShort", ignores_time_zone: true, is_relative: false, number_style: "PKNumberStyleDecimal"})
%FieldContent{key: "field2", attributed_value: nil, change_message: nil, currency_code: nil, data_detector_types: ["PKDataDetectorTypePhoneNumber"], date_style: "PKDateStyleShort", ignores_time_zone: true, is_relative: false, label: nil, number_style: "PKNumberStyleDecimal", value: 42}
iex> datetime = ~U[2023-04-15 14:30:00Z]
iex> field_content = FieldContent.new(%{key: "field3", attributed_value: datetime, currency_code: "USD", date_style: "PKDateStyleLong", ignores_time_zone: true, is_relative: true})
iex> %FieldContent{key: "field3", attributed_value: ^datetime, currency_code: "USD", date_style: "PKDateStyleLong", ignores_time_zone: true, is_relative: true} = field_content
iex> field_content = FieldContent.new(%{key: "field3", value: datetime, currency_code: "USD", date_style: "PKDateStyleLong", ignores_time_zone: true, is_relative: true})
iex> %FieldContent{key: "field3", value: ^datetime, currency_code: "USD", date_style: "PKDateStyleLong", ignores_time_zone: true, is_relative: true} = field_content
iex> field_content.change_message
nil
iex> FieldContent.new(%{key: "field4", attributed_value: "<a href='http://example.com'>Click here</a>", data_detector_types: ["PKDataDetectorTypeLink"], date_style: "PKDateStyleFull", is_relative: false, number_style: "PKNumberStylePercent"})
%FieldContent{key: "field4", attributed_value: "<a href='http://example.com'>Click here</a>", change_message: nil, currency_code: nil, data_detector_types: ["PKDataDetectorTypeLink"], date_style: "PKDateStyleFull", ignores_time_zone: nil, is_relative: false, label: nil, number_style: "PKNumberStylePercent"}
iex> FieldContent.new(%{key: "field4", value: "<a href='http://example.com'>Click here</a>", data_detector_types: ["PKDataDetectorTypeLink"], date_style: "PKDateStyleFull", is_relative: false, number_style: "PKNumberStylePercent"})
%FieldContent{key: "field4", attributed_value: nil, change_message: nil, currency_code: nil, data_detector_types: ["PKDataDetectorTypeLink"], date_style: "PKDateStyleFull", ignores_time_zone: nil, is_relative: false, label: nil, number_style: "PKNumberStylePercent", value: "<a href='http://example.com'>Click here</a>"}
iex> FieldContent.new(%{key: "field5", attributed_value: "No detectors", data_detector_types: [], change_message: "Updated to %@", ignores_time_zone: true, is_relative: true, label: "Field Label", number_style: "PKNumberStyleScientific"})
%FieldContent{key: "field5", attributed_value: "No detectors", change_message: "Updated to %@", currency_code: nil, data_detector_types: [], date_style: nil, ignores_time_zone: true, is_relative: true, label: "Field Label", number_style: "PKNumberStyleScientific"}
iex> FieldContent.new(%{key: "field5", value: "No detectors", data_detector_types: [], change_message: "Updated to %@", ignores_time_zone: true, is_relative: true, label: "Field Label", number_style: "PKNumberStyleScientific"})
%FieldContent{key: "field5", attributed_value: nil, change_message: "Updated to %@", currency_code: nil, data_detector_types: [], date_style: nil, ignores_time_zone: true, is_relative: true, label: "Field Label", number_style: "PKNumberStyleScientific", value: "No detectors"}
"""
@spec new(map()) :: %__MODULE__{}
def new(attrs \\ %{}) do
Expand All @@ -202,6 +219,7 @@ defmodule ExPass.Structs.FieldContent do
|> validate(:key, &Validators.validate_required_string(&1, :key))
|> validate(:label, &Validators.validate_optional_string(&1, :label))
|> validate(:number_style, &Validators.validate_number_style/1)
|> validate(:value, &Validators.validate_required_value(&1, :value))

struct!(__MODULE__, attrs)
end
Expand Down
66 changes: 66 additions & 0 deletions lib/utils/validators.ex
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,72 @@ defmodule ExPass.Utils.Validators do

def validate_number_style(_), do: {:error, "number_style must be a string"}

@doc """
Validates a required value field.
The field must be a non-nil value. The value can be a localizable string, ISO 8601 date, or number.
## Parameters
* `value` - The value to validate. A date or time value must include a time zone.
* `field_name` - The name of the field being validated as an atom.
## Returns
* `:ok` if the value is valid (non-nil).
* `{:error, reason}` if the value is not valid, where reason is a string explaining the error.
## Examples
iex> validate_required_value(42, :value)
:ok
iex> validate_required_value(nil, :value)
{:error, "value is a required field and cannot be nil"}
iex> validate_required_value("2021-09-15T15:53:00Z", :value)
:ok
iex> validate_required_value("localizable string", :value)
:ok
iex> validate_required_value(nil, :another_field)
{:error, "value is a required field and cannot be nil"}
iex> validate_required_value("2023-04-15T14:30:00", :value)
{:error, "Date value must include a time zone"}
"""
@spec validate_required_value(String.t() | number() | DateTime.t() | Date.t() | nil, atom()) ::
:ok | {:error, String.t()}
def validate_required_value(nil, field_name),
do: {:error, "#{field_name} is a required field and cannot be nil"}

def validate_required_value(value, _field_name) when is_binary(value) do
if String.contains?(value, "T") and not String.contains?(value, "Z") do
{:error, "Date value must include a time zone"}
else
:ok
end
end

def validate_required_value(value, _field_name) when is_number(value), do: :ok

def validate_required_value(%DateTime{} = value, _field_name) do
DateTime.to_iso8601(value)

:ok
end

def validate_required_value(%Date{} = value, _field_name) do
Date.to_iso8601(value)

:ok
end

def validate_required_value(_value, field_name),
do: {:error, "#{field_name} must be a string, number, DateTime, or Date"}

defp contains_unsupported_html_tags?(string) do
# Remove all valid anchor tags
string_without_anchors = String.replace(string, ~r{<a\s[^>]*>.*?</a>|<a\s[^>]*/>}, "")
Expand Down
Loading

0 comments on commit 0dd4ec0

Please sign in to comment.