-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Loading status checks…
add array
Showing
15 changed files
with
361 additions
and
89 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package owl | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"reflect" | ||
"strconv" | ||
) | ||
|
||
type ArraySchema struct { | ||
schema *AnySchema | ||
of Schema | ||
} | ||
|
||
func Array(schema Schema) *ArraySchema { | ||
self := &ArraySchema{Any(), schema} | ||
self.Rule("type", self.Type(), func(value reflect.Value) (any, error) { | ||
if !value.IsValid() { | ||
return nil, nil | ||
} | ||
|
||
if value.Kind() != reflect.Array && value.Kind() != reflect.Slice { | ||
return value.Interface(), errors.New("must be an array/slice") | ||
} | ||
|
||
return value.Interface(), nil | ||
}) | ||
|
||
self.Rule("items", schema, nil) | ||
return self | ||
} | ||
|
||
func (self ArraySchema) Type() string { | ||
return fmt.Sprintf("array[%s]", self.of.Type()) | ||
} | ||
|
||
func (self *ArraySchema) Rule(key string, value any, rule RuleFn) *ArraySchema { | ||
self.schema.Rule(key, value, rule) | ||
return self | ||
} | ||
|
||
func (self *ArraySchema) Message(message string) *ArraySchema { | ||
self.schema.Message(message) | ||
return self | ||
} | ||
|
||
func (self *ArraySchema) Required() *ArraySchema { | ||
self.schema.Required() | ||
return self | ||
} | ||
|
||
func (self *ArraySchema) Min(min int) *ArraySchema { | ||
return self.Rule("min", min, func(value reflect.Value) (any, error) { | ||
if !value.IsValid() { | ||
return nil, nil | ||
} | ||
|
||
if value.Len() < min { | ||
return value.Interface(), fmt.Errorf("must have length of at least %d", min) | ||
} | ||
|
||
return value.Interface(), nil | ||
}) | ||
} | ||
|
||
func (self *ArraySchema) Max(max int) *ArraySchema { | ||
return self.Rule("max", max, func(value reflect.Value) (any, error) { | ||
if !value.IsValid() { | ||
return nil, nil | ||
} | ||
|
||
if value.Len() > max { | ||
return value.Interface(), fmt.Errorf("must have length of at most %d", max) | ||
} | ||
|
||
return value.Interface(), nil | ||
}) | ||
} | ||
|
||
func (self ArraySchema) MarshalJSON() ([]byte, error) { | ||
return json.Marshal(self.schema) | ||
} | ||
|
||
func (self ArraySchema) Validate(value any) error { | ||
return self.validate("", reflect.Indirect(reflect.ValueOf(value))) | ||
} | ||
|
||
func (self ArraySchema) validate(key string, value reflect.Value) error { | ||
if err := self.schema.validate(key, value); err != nil { | ||
return err | ||
} | ||
|
||
if !value.IsValid() { | ||
return nil | ||
} | ||
|
||
if value.Kind() == reflect.Interface { | ||
value = value.Elem() | ||
} | ||
|
||
err := NewEmptyError("items", key) | ||
|
||
for i := 0; i < value.Len(); i++ { | ||
item := reflect.Indirect(value.Index(i)) | ||
|
||
if item.Kind() == reflect.Interface { | ||
item = item.Elem() | ||
} | ||
|
||
if e := self.of.validate(strconv.Itoa(i), item); e != nil { | ||
err = err.Add(e) | ||
} | ||
} | ||
|
||
if len(err.Errors) > 0 { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
package owl_test | ||
|
||
import ( | ||
"encoding/json" | ||
"testing" | ||
|
||
"github.com/aacebo/owl" | ||
) | ||
|
||
func Test_Array(t *testing.T) { | ||
t.Run("required", func(t *testing.T) { | ||
t.Run("should succeed", func(t *testing.T) { | ||
err := owl.Array(owl.String()).Required().Validate([]any{}) | ||
|
||
if err != nil { | ||
t.Fatal(err.Error()) | ||
} | ||
}) | ||
|
||
t.Run("should fail", func(t *testing.T) { | ||
err := owl.Array(owl.String()).Required().Validate(nil) | ||
|
||
if err == nil { | ||
t.Fatal() | ||
} | ||
}) | ||
}) | ||
|
||
t.Run("items", func(t *testing.T) { | ||
t.Run("should succeed", func(t *testing.T) { | ||
err := owl.Array(owl.String().Required()).Required().Validate([]string{"test"}) | ||
|
||
if err != nil { | ||
t.Fatal(err.Error()) | ||
} | ||
}) | ||
|
||
t.Run("should succeed when item not required", func(t *testing.T) { | ||
err := owl.Array(owl.String()).Required().Validate([]any{nil}) | ||
|
||
if err != nil { | ||
t.Fatal(err.Error()) | ||
} | ||
}) | ||
|
||
t.Run("should fail when not array", func(t *testing.T) { | ||
err := owl.Array(owl.Bool()).Validate("test") | ||
|
||
if err == nil { | ||
t.FailNow() | ||
} | ||
}) | ||
|
||
t.Run("should fail when item type invalid", func(t *testing.T) { | ||
err := owl.Array(owl.Bool()).Required().Validate([]string{"test"}) | ||
|
||
if err == nil { | ||
t.FailNow() | ||
} | ||
}) | ||
|
||
t.Run("should fail when item required", func(t *testing.T) { | ||
err := owl.Array(owl.String().Required()).Required().Validate([]any{nil}) | ||
|
||
if err == nil { | ||
t.FailNow() | ||
} | ||
}) | ||
}) | ||
|
||
t.Run("message", func(t *testing.T) { | ||
t.Run("should have custom error message", func(t *testing.T) { | ||
err := owl.Array(owl.String()).Required().Message("a test message").Validate(nil) | ||
|
||
if err == nil { | ||
t.FailNow() | ||
} | ||
|
||
if err.Error() != `{"errors":[{"rule":"required","message":"a test message"}]}` { | ||
t.Errorf( | ||
"expected `%s`, received `%s`", | ||
`{"errors":[{"rule":"required","message":"required"}]}`, | ||
err.Error(), | ||
) | ||
} | ||
}) | ||
}) | ||
|
||
t.Run("min", func(t *testing.T) { | ||
t.Run("should succeed when nil", func(t *testing.T) { | ||
err := owl.Array(owl.String()).Min(5).Validate(nil) | ||
|
||
if err != nil { | ||
t.Fatal(err.Error()) | ||
} | ||
}) | ||
|
||
t.Run("should succeed when gt min", func(t *testing.T) { | ||
err := owl.Array(owl.String()).Min(5).Validate([]string{ | ||
"a", "b", "c", "d", "e", | ||
}) | ||
|
||
if err != nil { | ||
t.Fatal(err.Error()) | ||
} | ||
}) | ||
|
||
t.Run("should fail when lt min", func(t *testing.T) { | ||
err := owl.Array(owl.String()).Min(5).Validate([]string{ | ||
"a", "b", "c", "d", | ||
}) | ||
|
||
if err == nil { | ||
t.Fatal() | ||
} | ||
}) | ||
}) | ||
|
||
t.Run("max", func(t *testing.T) { | ||
t.Run("should succeed when nil", func(t *testing.T) { | ||
err := owl.Array(owl.String()).Max(5).Validate(nil) | ||
|
||
if err != nil { | ||
t.Fatal(err.Error()) | ||
} | ||
}) | ||
|
||
t.Run("should succeed when lt max", func(t *testing.T) { | ||
err := owl.Array(owl.String()).Max(5).Validate([]string{ | ||
"a", "b", "c", "d", "e", | ||
}) | ||
|
||
if err != nil { | ||
t.Fatal(err.Error()) | ||
} | ||
}) | ||
|
||
t.Run("should fail when gt max", func(t *testing.T) { | ||
err := owl.Array(owl.String()).Max(5).Validate([]string{ | ||
"a", "b", "c", "d", "e", "f", | ||
}) | ||
|
||
if err == nil { | ||
t.Fatal() | ||
} | ||
}) | ||
}) | ||
|
||
t.Run("json", func(t *testing.T) { | ||
t.Run("serialize", func(t *testing.T) { | ||
schema := owl.Array(owl.String()).Required() | ||
b, err := json.Marshal(schema) | ||
|
||
if err != nil { | ||
t.Error(err) | ||
} | ||
|
||
if string(b) != `{"items":{"type":"string"},"required":true,"type":"array[string]"}` { | ||
t.Errorf( | ||
"expected `%s`, received `%s`", | ||
`{"items":{"type":"string"},"required":true,"type":"array[string]"}`, | ||
string(b), | ||
) | ||
} | ||
}) | ||
}) | ||
} | ||
|
||
func ExampleArray() { | ||
schema := owl.Array(owl.String().Required()) | ||
|
||
if err := schema.Validate([]string{"test"}); err != nil { // nil | ||
panic(err) | ||
} | ||
|
||
if err := schema.Validate([]int{1}); err != nil { // error | ||
panic(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters