Skip to content

Custom format validation #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
15 changes: 8 additions & 7 deletions src/Form.elm
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Documentation for the original TypeScript library can be found here: <https://js
-}

import Form.Error as Error
import Form.State
import Form.State exposing (Settings)
import Form.Validation exposing (validate)
import Form.Widget
import Form.Widget.Generate
Expand Down Expand Up @@ -52,13 +52,14 @@ causes the resulting form to differ from json-forms.io specification. These diff
should be documented.

-}
init : UI.DefOptions -> String -> Schema -> Maybe UiSchema -> Form
init options id schema uiSchema =
init : Settings -> UI.DefOptions -> String -> Schema -> Maybe UiSchema -> Form
init settings options id schema uiSchema =
{ schema = schema
, uiSchema = Maybe.withDefaultLazy (\() -> generateUiSchema schema) uiSchema
, uiSchemaIsGenerated = uiSchema == Nothing
, state = Form.State.initState id (defaultValue schema) (validate schema)
, state = Form.State.initState id (defaultValue schema) (validate settings schema)
, defaultOptions = options
, settings = settings
}


Expand Down Expand Up @@ -121,7 +122,7 @@ setSchema schema form =

else
form.uiSchema
, state = Form.State.initState form.state.formId (defaultValue schema) (validate schema)
, state = Form.State.initState form.state.formId (defaultValue schema) (validate form.settings schema)
}


Expand All @@ -145,7 +146,7 @@ update msg form =
{ form
| state =
Form.State.updateState
(validate form.schema)
(validate form.settings form.schema)
msg
form.state
}
Expand All @@ -170,7 +171,7 @@ The value is present only if form validation passes with no errors.
-}
getSubmitValue : Form -> Maybe Value
getSubmitValue form =
validate form.schema form.state.value
validate form.settings form.schema form.state.value
|> Result.toMaybe


Expand Down
1 change: 1 addition & 0 deletions src/Form/Error.elm
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type ErrorValue
= Empty
| InvalidString
| InvalidFormat TextFormat
| InvalidCustomFormat String
| InvalidInt
| InvalidFloat
| InvalidBool
Expand Down
12 changes: 12 additions & 0 deletions src/Form/State.elm
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module Form.State exposing
( Form
, FormState
, Msg(..)
, Settings
, ValidateWidgets(..)
, getErrorAt
, initState
Expand All @@ -27,6 +28,7 @@ type alias Form =
, uiSchemaIsGenerated : Bool
, state : FormState
, defaultOptions : UI.DefOptions
, settings : Settings
}


Expand All @@ -40,6 +42,16 @@ type alias FormState =
}


{-| Settings for forms initialization:

- `customFormats` where keys are the accepted custom formats (e.g., personal-number-se\_bank\_id) and values are validation functions for the formats.

-}
type alias Settings =
{ customFormats : Dict String (String -> Result String String)
}


{-| Controls which widgets should show validation errors.

All - all widgets should show validation errors. Used after a form submission attempt.
Expand Down
64 changes: 36 additions & 28 deletions src/Form/Validation.elm
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
module Form.Validation exposing (validate)

import Form.Error as Error exposing (ErrorValue(..))
import Dict
import Form.Error as Error exposing (ErrorValue(..), TextFormat(..))
import Form.Normalization exposing (normalizeValue)
import Form.Regex
import Form.State exposing (Settings)
import Json.Decode as Decode exposing (Value)
import Json.Encode as Encode
import Json.Schema.Definitions
Expand All @@ -19,17 +21,17 @@ import Set
import Validation exposing (Validation, error)


validate : Schema -> Value -> Validation Value
validate schema rawValue =
validate : Settings -> Schema -> Value -> Validation Value
validate settings schema rawValue =
let
value =
normalizeValue rawValue
in
Validation.voidRight value <| validateSchema schema value
Validation.voidRight value <| validateSchema settings schema value


validateSchema : Schema -> Value -> Validation Value
validateSchema schema rawValue =
validateSchema : Settings -> Schema -> Value -> Validation Value
validateSchema settings schema rawValue =
let
value =
normalizeValue rawValue
Expand All @@ -44,29 +46,29 @@ validateSchema schema rawValue =
Validation.fail (error <| Unimplemented "Boolean schemas are not implemented.")

ObjectSchema objectSchema ->
validateSubSchema objectSchema value
validateSubSchema settings objectSchema value


validateSubSchema : SubSchema -> Value -> Validation Value
validateSubSchema schema =
validateSubSchema : Settings -> SubSchema -> Value -> Validation Value
validateSubSchema settings schema =
let
typeValidations : Value -> Validation Value
typeValidations =
case schema.type_ of
SingleType type_ ->
validateSingleType schema type_
validateSingleType settings schema type_

AnyType ->
Validation.succeed

NullableType type_ ->
Validation.oneOf
[ \v -> Result.map (always Encode.null) <| validateNull v
, validateSingleType schema type_
, validateSingleType settings schema type_
]

UnionType types ->
Validation.oneOf <| List.map (\type_ -> validateSingleType schema type_) types
Validation.oneOf <| List.map (\type_ -> validateSingleType settings schema type_) types
in
Validation.validateAll
[ Validation.whenJust schema.const validateConst
Expand All @@ -75,8 +77,8 @@ validateSubSchema schema =
]


validateSingleType : SubSchema -> SingleType -> Value -> Validation Value
validateSingleType schema type_ value =
validateSingleType : Settings -> SubSchema -> SingleType -> Value -> Validation Value
validateSingleType settings schema type_ value =
case type_ of
ObjectType ->
let
Expand All @@ -99,7 +101,7 @@ validateSingleType schema type_ value =
Ok Encode.null

( Just val, _ ) ->
validateSchema propSchema val
validateSchema settings propSchema val
in
Validation.validateAll (List.map (\( key, propSchema ) _ -> validateKey key propSchema) propList) value

Expand All @@ -113,7 +115,7 @@ validateSingleType schema type_ value =
Result.map Encode.bool <| validateBool value

StringType ->
Result.map Encode.string <| validateString schema value
Result.map Encode.string <| validateString settings schema value

NullType ->
Result.map (always Encode.null) <| validateNull value
Expand All @@ -122,8 +124,8 @@ validateSingleType schema type_ value =
Err <| error (Error.Unimplemented "array")


validateString : SubSchema -> Value -> Validation String
validateString schema v =
validateString : Settings -> SubSchema -> Value -> Validation String
validateString settings schema v =
case Decode.decodeValue Decode.string v of
Err _ ->
Err <| error Error.InvalidString
Expand All @@ -133,31 +135,37 @@ validateString schema v =
[ Validation.whenJust schema.minLength validateMinLength
, Validation.whenJust schema.maxLength validateMaxLength
, Validation.whenJust schema.pattern validatePattern -- TODO: check specs if this is correct
, Validation.whenJust schema.format validateFormat -- TODO: check specs if this is correct
, Validation.whenJust schema.format (validateFormat settings) -- TODO: check specs if this is correct
]
s


validateFormat : String -> String -> Validation String
validateFormat format v =
validateFormat : Settings -> String -> String -> Validation String
validateFormat settings format value =
case format of
"date-time" ->
validateRegex Form.Regex.dateTime Error.DateTime v
validateRegex Form.Regex.dateTime Error.DateTime value

"date" ->
validateRegex Form.Regex.date Error.Date v
validateRegex Form.Regex.date Error.Date value

"time" ->
validateRegex Form.Regex.time Error.Time v
validateRegex Form.Regex.time Error.Time value

"email" ->
validateRegex Form.Regex.email Error.Email v
validateRegex Form.Regex.email Error.Email value

"phone" ->
validateRegex Form.Regex.phone Error.Phone v
validateRegex Form.Regex.phone Error.Phone value

_ ->
Validation.succeed v
customFormat ->
let
customValidation =
Dict.get customFormat settings.customFormats
|> Maybe.map (\validation -> validation value)
|> Maybe.withDefault (Result.Ok value)
in
Result.mapError (\err -> error (Error.InvalidCustomFormat err)) customValidation


validatePattern : String -> String -> Validation String
Expand Down
4 changes: 2 additions & 2 deletions src/Form/Widget/Generate.elm
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ goWidget form uiState =
let
ruleEffect : Maybe Rule.AppliedEffect
ruleEffect =
Rule.computeRule form.state.value (UI.getRule uiState.uiSchema)
Rule.computeRule form.settings form.state.value (UI.getRule uiState.uiSchema)

newUiState =
{ uiState | disabled = ruleEffect == Just Rule.Disabled }
Expand Down Expand Up @@ -116,7 +116,7 @@ categorizationWidget form uiState categorization =
Maybe.withDefault 0 <| Dict.get uiState.uiPath form.state.categoryFocus

categoryButton ix cat =
if Rule.computeRule form.state.value cat.rule == Just Rule.Hidden then
if Rule.computeRule form.settings form.state.value cat.rule == Just Rule.Hidden then
Nothing

else
Expand Down
3 changes: 3 additions & 0 deletions src/Form/Widget/View.elm
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,9 @@ errorString error =
InvalidFormat _ ->
"not the correct format"

InvalidCustomFormat _ ->
"not the correct format"

InvalidInt ->
"not a valid integer"

Expand Down
7 changes: 4 additions & 3 deletions src/UiSchema/Rule.elm
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module UiSchema.Rule exposing (AppliedEffect(..), computeRule)

import Form.State exposing (Settings)
import Form.Validation exposing (validate)
import Json.Decode exposing (Value)
import Json.Pointer as Pointer
Expand All @@ -12,16 +13,16 @@ type AppliedEffect
| Disabled


computeRule : Value -> Maybe UI.Rule -> Maybe AppliedEffect
computeRule formValue mRule =
computeRule : Settings -> Value -> Maybe UI.Rule -> Maybe AppliedEffect
computeRule settings formValue mRule =
let
condition rule =
case Pointer.pointedValue rule.condition.scope formValue of
Nothing ->
False

Just v ->
Validation.isOk <| validate rule.condition.schema v
Validation.isOk <| validate settings rule.condition.schema v

go rule =
case ( rule.effect, condition rule ) of
Expand Down