Skip to content

Commit

Permalink
Merge pull request #75 from muonsoft/next
Browse files Browse the repository at this point in the history
new constraints
  • Loading branch information
strider2038 authored Jul 17, 2022
2 parents 9f7a128 + 0165cb3 commit 05cfc84
Show file tree
Hide file tree
Showing 20 changed files with 1,834 additions and 342 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/testdata
/var
6 changes: 6 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
run:
skip-dirs:
- var
linters:
enable:
- asciicheck
Expand Down Expand Up @@ -119,6 +122,9 @@ issues:
- path: validate/web.go
linters:
- lll
- path: path.go
linters:
- gocritic
- path: arguments.go
linters:
- unparam
Expand Down
4 changes: 4 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import (
)

var (
ErrInvalidDate = NewError("invalid date", message.InvalidDate)
ErrInvalidDateTime = NewError("invalid datetime", message.InvalidDateTime)
ErrInvalidEAN13 = NewError("invalid EAN-13", message.InvalidEAN13)
ErrInvalidEAN8 = NewError("invalid EAN-8", message.InvalidEAN8)
ErrInvalidEmail = NewError("invalid email", message.InvalidEmail)
ErrInvalidHostname = NewError("invalid hostname", message.InvalidHostname)
ErrInvalidIP = NewError("invalid IP address", message.InvalidIP)
ErrInvalidJSON = NewError("invalid JSON", message.InvalidJSON)
ErrInvalidTime = NewError("invalid time", message.InvalidTime)
ErrInvalidUPCA = NewError("invalid UPC-A", message.InvalidUPCA)
ErrInvalidUPCE = NewError("invalid UPC-E", message.InvalidUPCE)
ErrInvalidURL = NewError("invalid URL", message.InvalidURL)
Expand All @@ -39,6 +42,7 @@ var (
ErrNotUnique = NewError("is not unique", message.NotUnique)
ErrNotValid = NewError("is not valid", message.NotValid)
ErrProhibitedIP = NewError("is prohibited IP", message.ProhibitedIP)
ErrProhibitedURL = NewError("is prohibited URL", message.ProhibitedURL)
ErrTooEarly = NewError("is too early", message.TooEarly)
ErrTooEarlyOrEqual = NewError("is too early or equal", message.TooEarlyOrEqual)
ErrTooFewElements = NewError("too few elements", message.TooFewElements)
Expand Down
18 changes: 14 additions & 4 deletions is/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package is_test

import (
"fmt"
"regexp"

"github.com/muonsoft/validation/is"
"github.com/muonsoft/validation/validate"
Expand Down Expand Up @@ -152,15 +153,24 @@ func ExampleHTML5Email() {
}

func ExampleURL() {
fmt.Println(is.URL("https://example.com")) // valid absolute URL
fmt.Println(is.URL("ftp://example.com", "http", "https", "ftp")) // valid URL with custom schema
fmt.Println(is.URL("example.com")) // invalid URL
fmt.Println(is.URL("//example.com", "")) // valid relative URL
fmt.Println(is.URL("https://example.com")) // valid absolute URL
fmt.Println(is.URL("ftp://example.com", validate.RestrictURLSchemas("http", "https", "ftp"))) // valid URL with custom schema
fmt.Println(is.URL("example.com")) // invalid URL
fmt.Println(is.URL("//example.com", validate.RestrictURLSchemas(""))) // valid relative URL
fmt.Println(is.URL("http://example.com", validate.RestrictURLHosts("sample.com"))) // not matching host
fmt.Println( // matching by regexp
is.URL(
"http://sub.example.com",
validate.RestrictURLHostByPattern(regexp.MustCompile(`^.*\.example\.com$`)),
),
)
// Output:
// true
// true
// false
// true
// false
// true
}

func ExampleIP() {
Expand Down
17 changes: 11 additions & 6 deletions is/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@ func HTML5Email(value string) bool {
return html5EmailRegex.MatchString(value)
}

// URL is used to check that value is a valid URL string. By default (if no schemas are passed),
// the function checks only for the http:// and https:// schemas. Use the schemas argument
// to configure the list of expected schemas. If an empty string is passed as a schema, then
// URL value may be treated as relative (without schema, e.g. "//example.com").
func URL(value string, schemas ...string) bool {
return validate.URL(value, schemas...) == nil
// URL is used to validate that value is a valid URL string. You can use a list of restrictions
// to additionally check for a restricted set of URLs. By default, if no restrictions are passed,
// the function checks for the http:// and https:// schemas.
//
// Use the validate.RestrictURLSchemas option to configure the list of expected schemas. If an empty
// string is passed as a schema, then URL value may be treated as relative (without schema, e.g. "//example.com").
//
// Use the validate.RestrictURLHosts or validate.RestrictURLHostByPattern option to configure
// the list of allowed hosts.
func URL(value string, restrictions ...validate.URLRestriction) bool {
return validate.URL(value, restrictions...) == nil
}

// IP checks that a value is a valid IP address (IPv4 or IPv6). You can use a list
Expand Down
103 changes: 103 additions & 0 deletions it/date_time.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package it

import (
"context"
"time"

"github.com/muonsoft/validation"
)

// DateTimeConstraint checks that the string value is a valid date and time value specified by a specific layout.
// The layout can be redefined using the WithLayout method.
type DateTimeConstraint struct {
isIgnored bool
groups []string
err error
layout string
messageTemplate string
messageParameters validation.TemplateParameterList
}

// IsDateTime checks that the string value is a valid date and time. By default, it uses time.RFC3339 layout.
// The layout can be redefined using the WithLayout method.
func IsDateTime() DateTimeConstraint {
return DateTimeConstraint{
layout: time.RFC3339,
err: validation.ErrInvalidDateTime,
messageTemplate: validation.ErrInvalidDateTime.Message(),
}
}

// IsDate checks that the string value is a valid date. It uses "2006-01-02" layout.
// The layout can be redefined using the WithLayout method.
func IsDate() DateTimeConstraint {
return DateTimeConstraint{
layout: "2006-01-02",
err: validation.ErrInvalidDate,
messageTemplate: validation.ErrInvalidDate.Message(),
}
}

// IsTime checks that the string value is a valid time. It uses "15:04:05" layout.
// The layout can be redefined using the WithLayout method.
func IsTime() DateTimeConstraint {
return DateTimeConstraint{
layout: "15:04:05",
err: validation.ErrInvalidTime,
messageTemplate: validation.ErrInvalidTime.Message(),
}
}

// WithLayout specifies the layout to be used for datetime parsing.
func (c DateTimeConstraint) WithLayout(layout string) DateTimeConstraint {
c.layout = layout
return c
}

// WithError overrides default error for produced violation.
func (c DateTimeConstraint) WithError(err error) DateTimeConstraint {
c.err = err
return c
}

// WithMessage sets the violation message template. You can set custom template parameters
// for injecting its values into the final message. Also, you can use default parameters:
//
// {{ layout }} - date time layout used for parsing;
// {{ value }} - the current (invalid) value.
func (c DateTimeConstraint) WithMessage(template string, parameters ...validation.TemplateParameter) DateTimeConstraint {
c.messageTemplate = template
c.messageParameters = parameters
return c
}

// When enables conditional validation of this constraint. If the expression evaluates to false,
// then the constraint will be ignored.
func (c DateTimeConstraint) When(condition bool) DateTimeConstraint {
c.isIgnored = !condition
return c
}

// WhenGroups enables conditional validation of the constraint by using the validation groups.
func (c DateTimeConstraint) WhenGroups(groups ...string) DateTimeConstraint {
c.groups = groups
return c
}

func (c DateTimeConstraint) ValidateString(ctx context.Context, validator *validation.Validator, value *string) error {
if c.isIgnored || validator.IsIgnoredForGroups(c.groups...) || value == nil || *value == "" {
return nil
}
if _, err := time.Parse(c.layout, *value); err == nil {
return nil
}

return validator.BuildViolation(ctx, c.err, c.messageTemplate).
WithParameters(
c.messageParameters.Prepend(
validation.TemplateParameter{Key: "{{ layout }}", Value: c.layout},
validation.TemplateParameter{Key: "{{ value }}", Value: *value},
)...,
).
WithParameter("{{ value }}", *value).Create()
}
149 changes: 149 additions & 0 deletions it/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net"
"net/url"
"regexp"
"time"

Expand Down Expand Up @@ -419,6 +420,90 @@ func ExampleHasUniqueValues() {
// violation: "This collection should contain only unique elements."
}

func ExampleIsDateTime() {
fmt.Println(
"#1 invalid:",
validator.Validate(
context.Background(),
validation.String(`invalid`, it.IsDateTime()),
),
)
fmt.Println(
"#2 valid RFC3339:",
validator.Validate(
context.Background(),
validation.String(`2022-07-12T12:34:56+00:00`, it.IsDateTime()),
),
)
fmt.Println(
"#3 custom layout:",
validator.Validate(
context.Background(),
validation.String(`2022-07-12 12:34:56`, it.IsDateTime().WithLayout("2006-01-02 15:04:05")),
),
)
// Output:
// #1 invalid: violation: "This value is not a valid datetime."
// #2 valid RFC3339: <nil>
// #3 custom layout: <nil>
}

func ExampleIsTime() {
fmt.Println(
"#1 invalid:",
validator.Validate(
context.Background(),
validation.String(`invalid`, it.IsTime()),
),
)
fmt.Println(
"#2 valid:",
validator.Validate(
context.Background(),
validation.String(`12:34:56`, it.IsTime()),
),
)
fmt.Println(
"#3 custom layout:",
validator.Validate(
context.Background(),
validation.String(`12:34:56+00:00`, it.IsTime().WithLayout("15:04:05Z07:00")),
),
)
// Output:
// #1 invalid: violation: "This value is not a valid time."
// #2 valid: <nil>
// #3 custom layout: <nil>
}

func ExampleIsDate() {
fmt.Println(
"#1 invalid:",
validator.Validate(
context.Background(),
validation.String(`invalid`, it.IsDate()),
),
)
fmt.Println(
"#2 valid:",
validator.Validate(
context.Background(),
validation.String(`2022-07-12`, it.IsDate()),
),
)
fmt.Println(
"#3 custom layout:",
validator.Validate(
context.Background(),
validation.String(`12 Jul 22 12:34`, it.IsDate().WithLayout("02 Jan 06 15:04")),
),
)
// Output:
// #1 invalid: violation: "This value is not a valid date."
// #2 valid: <nil>
// #3 custom layout: <nil>
}

func ExampleHasMinCount() {
v := []int{1, 2}
err := validator.ValidateCountable(context.Background(), len(v), it.HasMinCount(3))
Expand Down Expand Up @@ -659,6 +744,70 @@ func ExampleURLConstraint_WithSchemas() {
// <nil>
}

func ExampleURLConstraint_WithHosts() {
fmt.Println(
"valid:",
validator.Validate(
context.Background(),
validation.String("https://example.com", it.IsURL().WithHosts("example.com")),
),
)
fmt.Println(
"invalid:",
validator.Validate(
context.Background(),
validation.String("https://sample.com", it.IsURL().WithHosts("example.com")),
),
)
// Output:
// valid: <nil>
// invalid: violation: "This URL is prohibited to use."
}

func ExampleURLConstraint_WithHostMatches() {
fmt.Println(
"valid:",
validator.Validate(
context.Background(),
validation.String("https://sub.example.com", it.IsURL().WithHostMatches(regexp.MustCompile(`^.*\.example\.com`))),
),
)
fmt.Println(
"invalid:",
validator.Validate(
context.Background(),
validation.String("https://sub.sample.com", it.IsURL().WithHostMatches(regexp.MustCompile(`^.*\.example\.com`))),
),
)
// Output:
// valid: <nil>
// invalid: violation: "This URL is prohibited to use."
}

func ExampleURLConstraint_WithRestriction() {
fmt.Println(
"valid:",
validator.Validate(
context.Background(),
validation.String("https://example.com", it.IsURL().WithRestriction(func(u *url.URL) bool {
return u.Host == "example.com"
})),
),
)
fmt.Println(
"invalid:",
validator.Validate(
context.Background(),
validation.String("https://sample.com", it.IsURL().WithRestriction(func(u *url.URL) bool {
return u.Host == "example.com"
})),
),
)
// Output:
// valid: <nil>
// invalid: violation: "This URL is prohibited to use."
}

func ExampleIsIP_validIP() {
v := "123.123.123.123"
err := validator.Validate(context.Background(), validation.String(v, it.IsIP()))
Expand Down
Loading

0 comments on commit 05cfc84

Please sign in to comment.