From ea60113fa1f75da6d2aa8a00010bbbcc15448fee Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Fri, 3 Jan 2025 10:35:34 -0800 Subject: [PATCH] Time check is added. (#162) # Describe Request Time check is added. # Change Type New checker. --- DOC.md | 73 ++++++++++++++++++++++++++++++++++++++++- README.md | 1 + locales/DOC.md | 1 + locales/en_us.go | 1 + maker.go | 1 + time.go | 67 ++++++++++++++++++++++++++++++++++++++ time_test.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 time.go create mode 100644 time_test.go diff --git a/DOC.md b/DOC.md index 129dd97..24db858 100644 --- a/DOC.md +++ b/DOC.md @@ -41,6 +41,7 @@ Package v2 Checker is a Go library for validating user input through checker rul - [func IsMAC\(value string\) \(string, error\)](<#IsMAC>) - [func IsMasterCardCreditCard\(number string\) \(string, error\)](<#IsMasterCardCreditCard>) - [func IsRegexp\(expression, value string\) \(string, error\)](<#IsRegexp>) +- [func IsTime\(params, value string\) \(string, error\)](<#IsTime>) - [func IsURL\(value string\) \(string, error\)](<#IsURL>) - [func IsUnionPayCreditCard\(number string\) \(string, error\)](<#IsUnionPayCreditCard>) - [func IsVisaCreditCard\(number string\) \(string, error\)](<#IsVisaCreditCard>) @@ -268,6 +269,15 @@ var ( ) ``` + + +```go +var ( + // ErrTime indicates that the value is not a valid based on the given time format. + ErrTime = NewCheckError("NOT_TIME") +) +``` + ## func [Check]() @@ -1083,6 +1093,67 @@ func main() {

+ +## func [IsTime]() + +```go +func IsTime(params, value string) (string, error) +``` + +IsTime checks if the given value is a valid time based on the given layout. + +
Example +

+ + + +```go +package main + +import ( + v2 "github.com/cinar/checker/v2" +) + +func main() { + value := "2024-12-31" + + _, err := v2.IsTime("DateOnly", value) + if err != nil { + panic(err) + } +} +``` + +

+
+ +
Example (Custom) +

+ + + +```go +package main + +import ( + v2 "github.com/cinar/checker/v2" +) + +func main() { + rfc3339Layout := "2006-01-02T15:04:05Z07:00" + + value := "2024-12-31T10:20:00Z07:00" + + _, err := v2.IsTime(rfc3339Layout, value) + if err != nil { + panic(err) + } +} +``` + +

+
+ ## func [IsURL]() @@ -1211,7 +1282,7 @@ func RegisterLocale(locale string, messages map[string]string) RegisterLocale registers the localized error messages for the given locale. -## func [RegisterMaker]() +## func [RegisterMaker]() ```go func RegisterMaker(name string, maker MakeCheckFunc) diff --git a/README.md b/README.md index 1389044..fd03e2d 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,7 @@ type Person struct { - [`min-len`](DOC.md#func-minlen): Ensures the length of the given value (string, slice, or map) is at least n. - [`required`](DOC.md#func-required) Ensures the value is provided. - [`regexp`](DOC.md#func-makeregexpchecker) Ensured the string matches the pattern. +- [`time`](DOC.md#func-istime) Ensured the string matches the provided time layout. - [`url`](DOC.md#IsURL): Ensures the string is a valid URL. # Normalizers Provided diff --git a/locales/DOC.md b/locales/DOC.md index f34002c..0d7131f 100644 --- a/locales/DOC.md +++ b/locales/DOC.md @@ -51,6 +51,7 @@ var EnUSMessages = map[string]string{ "NOT_MAC": "Not a valid MAC address.", "NOT_MAX_LEN": "Value cannot be greater than {{ .max }}.", "NOT_MIN_LEN": "Value cannot be less than {{ .min }}.", + "NOT_TIME": "Not a valid time.", "REQUIRED": "Required value is missing.", "NOT_URL": "Not a valid URL.", } diff --git a/locales/en_us.go b/locales/en_us.go index 01cd1eb..c519a2c 100644 --- a/locales/en_us.go +++ b/locales/en_us.go @@ -25,6 +25,7 @@ var EnUSMessages = map[string]string{ "NOT_MAC": "Not a valid MAC address.", "NOT_MAX_LEN": "Value cannot be greater than {{ .max }}.", "NOT_MIN_LEN": "Value cannot be less than {{ .min }}.", + "NOT_TIME": "Not a valid time.", "REQUIRED": "Required value is missing.", "NOT_URL": "Not a valid URL.", } diff --git a/maker.go b/maker.go index ed1b529..5d0e1bc 100644 --- a/maker.go +++ b/maker.go @@ -39,6 +39,7 @@ var makers = map[string]MakeCheckFunc{ nameMinLen: makeMinLen, nameRegexp: makeRegexp, nameRequired: makeRequired, + nameTime: makeTime, nameTitle: makeTitle, nameTrimLeft: makeTrimLeft, nameTrimRight: makeTrimRight, diff --git a/time.go b/time.go new file mode 100644 index 0000000..747e611 --- /dev/null +++ b/time.go @@ -0,0 +1,67 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "time" +) + +const ( + // time is the name of the time format check. + nameTime = "time" +) + +var ( + // ErrTime indicates that the value is not a valid based on the given time format. + ErrTime = NewCheckError("NOT_TIME") +) + +// timeLayouts is a map of time layouts that can be used in the time check. +var timeLayouts = map[string]string{ + "Layout": time.Layout, + "ANSIC": time.ANSIC, + "UnixDate": time.UnixDate, + "RubyDate": time.RubyDate, + "RFC822": time.RFC822, + "RFC822Z": time.RFC822Z, + "RFC850": time.RFC850, + "RFC1123": time.RFC1123, + "RFC1123Z": time.RFC1123Z, + "RFC3339": time.RFC3339, + "RFC3339Nano": time.RFC3339Nano, + "Kitchen": time.Kitchen, + "Stamp": time.Stamp, + "StampMilli": time.StampMilli, + "StampMicro": time.StampMicro, + "StampNano": time.StampNano, + "DateTime": time.DateTime, + "DateOnly": time.DateOnly, + "TimeOnly": time.TimeOnly, +} + +// IsTime checks if the given value is a valid time based on the given layout. +func IsTime(params, value string) (string, error) { + layout, ok := timeLayouts[params] + if !ok { + layout = params + } + + _, err := time.Parse(layout, value) + if err != nil { + return value, ErrTime + } + + return value, nil +} + +// makeTime makes a time format check based on the given time layout. +func makeTime(params string) CheckFunc[reflect.Value] { + return func(value reflect.Value) (reflect.Value, error) { + _, err := IsTime(params, value.String()) + return value, err + } +} diff --git a/time_test.go b/time_test.go new file mode 100644 index 0000000..f6f462a --- /dev/null +++ b/time_test.go @@ -0,0 +1,84 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "errors" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsTime() { + value := "2024-12-31" + + _, err := v2.IsTime("DateOnly", value) + if err != nil { + panic(err) + } +} + +func ExampleIsTime_custom() { + rfc3339Layout := "2006-01-02T15:04:05Z07:00" + + value := "2024-12-31T10:20:00Z07:00" + + _, err := v2.IsTime(rfc3339Layout, value) + if err != nil { + panic(err) + } +} + +func TestIsTimeSuccess(t *testing.T) { + value := "2024-12-31" + + result, err := v2.IsTime("DateOnly", value) + if result != value { + t.Fatalf("result (%s) is not the original value (%s)", result, value) + } + + if err != nil { + t.Fatal(err) + } +} + +func TestIsTimeError(t *testing.T) { + value := "2024-12-31" + + result, err := v2.IsTime("2006-02-01", value) + if result != value { + t.Fatalf("result (%s) is not the original value (%s)", result, value) + } + + if !errors.Is(err, v2.ErrTime) { + t.Fatalf("expected error %s actual %s", v2.ErrTime, err) + } + + message := "Not a valid time." + + if err.Error() != message { + t.Fatalf("expected %s actual %s", message, err.Error()) + } +} + +func TestStructTimeError(t *testing.T) { + type Person struct { + Birthday string `checkers:"time:DateOnly"` + } + + person := &Person{ + Birthday: "2024-31-12", + } + + errs, ok := v2.CheckStruct(person) + if ok { + t.Fatalf("expected errors") + } + + if !errors.Is(errs["Birthday"], v2.ErrTime) { + t.Fatalf("expected time error") + } +}