Skip to content

Commit

Permalink
Merge pull request #2 from bluzky/feature/improve-api
Browse files Browse the repository at this point in the history
unify api for simple use
  • Loading branch information
bluzky committed Feb 28, 2024
2 parents 7ecb10c + c9924d4 commit 12bf549
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 756 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ defschema IndexParams do
end

def index(conn, params) do
with {:ok, better_params} <- IndexParams.cast(params) do
with {:ok, better_params} <- Skema.cast(params, IndexParams) do
# do anything with your params
else
{:error, errors} -> # return params error
Expand Down
149 changes: 130 additions & 19 deletions lib/schema.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,131 @@
defmodule Skema.Schema do
@moduledoc """
Borrow from https://github.com/ejpcmac/typed_struct/blob/main/lib/typed_struct.ex
Schema specifications:
Internal presentation of a schema is just a map with field names as keys and field options as values.
**Example**
```elixir
%{
name: [type: :string, format: ~r/\d{4}/],
age: [type: :integer, number: [min: 15, max: 50]].
skill: [type: {:array, :string}, length: [min: 1, max: 10]]
}
```
## I. Field type
**Built-in types**
A type could be any of built-in supported types:
- `boolean`
- `string` | `binary`
- `integer`
- `float`
- `number` (integer or float)
- `date`
- `time`
- `datetime` | `utc_datetime`: date time with time zone
- `naive_datetime`: date time without time zone
- `map`
- `keyword`
- `{array, type}` array of built-in type, all item must be the same type
**Other types**
Custom type may be supported depends on module.
**Nested types**
Nested types could be a another **schema** or list of **schema**
```elixir
%{
user: [type: %{
name: [type: :string]
}]
}
```
Or list of schema
```elixir
%{
users: [type: {:array, %{
name: [type: :string]
}} ]
}
```
## II. Field casting and default value
These specifications is used for casting data with `Skema.Params.cast`
### 1. Default value
Is used when the given field is missing or nil.
- Default could be a value
```elixir
%{
status: [type: :string, default: "active"]
}
```
- Or a `function/0`, this function will be invoke each time data is `casted`
```elixir
%{
published_at: [type: :datetime, default: &DateTime.utc_now/0]
}
```
### 2. Custom cast function
You can provide a function to cast field value instead of using default casting function by using
`cast_func: <function/1>`
```elixir
%{
published_at: [type: :datetime, cast_func: &DateTime.from_iso8601/1]
}
```
## III. Field validation
**These validation are supported by [valdi](https://hex.pm/packages/valdi)**
**Custom validation function**
You can provide a function to validate the value.
Define validation: `func: <function>`
Function must be follow this signature
```elixir
@spec func(value::any()) :: :ok | {:error, message::String.t()}
```
## Define schema with `defschema` macro
`defschema` helps you define schema clearly and easy to use.
```elixir
defmodule MyStruct do
use Skema.Schema
defschema do
field :field_one, :string
field :field_two, :integer, required: true
field :field_four, :atom, default: :hey
field :update_time, :naive_datetime, default: &NaiveDateTime.utc_now/0
end
end
```
"""
@accumulating_attrs [
:ts_fields,
Expand Down Expand Up @@ -30,7 +155,6 @@ defmodule Skema.Schema do
defschema do
field :field_one, :string
field :field_two, :integer, required: true
field :field_three, :boolean, required: true
field :field_four, :atom, default: :hey
field :update_time, :naive_datetime, default: &NaiveDateTime.utc_now/0
Expand Down Expand Up @@ -95,29 +219,16 @@ defmodule Skema.Schema do

def cast(params) when is_map(params) do
case Skema.cast(params, @ts_fields) do
%{valid?: true, valid_data: data} -> {:ok, new(data)}
error -> {:error, error}
{:ok, data} -> {:ok, new(data)}
error -> error
end
end

def validate(params) do
case Skema.validate(params, @ts_fields) do
%{valid?: true} -> :ok
error -> {:error, error}
end
end

def cast_and_validate(params) do
params
|> Skema.cast(@ts_fields)
|> Skema.validate()
|> case do
%{valid?: true, valid_data: data} -> {:ok, new(data)}
error -> {:error, error}
end
Skema.validate(params, @ts_fields)
end

def __schema__(:fields) do
def __fields__ do
@ts_fields
end
end
Expand Down Expand Up @@ -161,7 +272,7 @@ defmodule Skema.Schema do
## Options
* `default` - sets the default value for the field
* `enforce` - if set to true, enforces the field and makes its type
* `required` - if set to true, enforces the field and makes its type
non-nullable
"""
defmacro field(name, type, opts \\ []) do
Expand Down
Loading

0 comments on commit 12bf549

Please sign in to comment.