Skip to content
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

go: enable multi-error validation (#47) #455

Merged
merged 8 commits into from
Apr 1, 2021
Merged
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
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,22 @@ Executing `protoc` with PGV and the target language's default plugin will create
```go
p := new(Person)

err := p.Validate() // err: Id must be greater than 999
err := p.Validate(false) // err: Id must be greater than 999
p.Id = 1000

err = p.Validate() // err: Email must be a valid email address
err = p.Validate(false) // err: Email must be a valid email address
p.Email = "example@lyft.com"

err = p.Validate() // err: Name must match pattern '^[^\d\s]+( [^\d\s]+)*$'
err = p.Validate(false) // err: Name must match pattern '^[^\d\s]+( [^\d\s]+)*$'
p.Name = "Protocol Buffer"

err = p.Validate() // err: Home is required
err = p.Validate(false) // err: Home is required
p.Home = &Location{37.7, 999}

err = p.Validate() // err: Home.Lng must be within [-180, 180]
err = p.Validate(false) // err: Home.Lng must be within [-180, 180]
p.Home.Lng = -122.4

err = p.Validate() // err: nil
err = p.Validate(false) // err: nil
```

## Usage
Expand Down Expand Up @@ -101,7 +101,7 @@ protoc \
example.proto
```

All messages generated include the new `Validate() error` method. PGV requires no additional runtime dependencies from the existing generated code.
All messages generated include the new `Validate(all bool) error` method (the `all` argument specifies whether to check as many fields as possible, or just fail early on the first invalid one). PGV requires no additional runtime dependencies from the existing generated code.

**Note**: by default **example.pb.validate.go** is nested in a directory structure that matches your `option go_package` name. You can change this using the protoc parameter `paths=source_relative:.`. Then `--validate_out` will output the file where it is expected. See Google's protobuf documenation or [packages and input paths](https://github.com/golang/protobuf#packages-and-input-paths) or [parameters](https://github.com/golang/protobuf#parameters) for more information.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,17 @@ public static void main(String[] args) {

writeResult(Harness.TestResult.newBuilder().setValid(true).build());
} catch (UnimplementedException ex) {
writeResult(Harness.TestResult.newBuilder().setValid(false).setAllowFailure(true).setReason(ex.getMessage()).build());
writeResult(Harness.TestResult.newBuilder().setValid(false).setAllowFailure(true).addReasons(ex.getMessage()).build());
} catch (ValidationException ex) {
writeResult(Harness.TestResult.newBuilder().setValid(false).setReason(ex.getMessage()).build());
writeResult(Harness.TestResult.newBuilder().setValid(false).addReasons(ex.getMessage()).build());
} catch (NullPointerException ex) {
if (message.getDescriptorForType().getOptions().getExtension(Validate.ignored)) {
writeResult(Harness.TestResult.newBuilder().setValid(false).setAllowFailure(true).setReason("validation not generated due to ignore option").build());
writeResult(Harness.TestResult.newBuilder().setValid(false).setAllowFailure(true).addReasons("validation not generated due to ignore option").build());
} else {
writeResult(Harness.TestResult.newBuilder().setValid(false).setError(true).setReason(Throwables.getStackTraceAsString(ex)).build());
writeResult(Harness.TestResult.newBuilder().setValid(false).setError(true).addReasons(Throwables.getStackTraceAsString(ex)).build());
}
} catch (Throwable ex) {
writeResult(Harness.TestResult.newBuilder().setValid(false).setError(true).setReason(Throwables.getStackTraceAsString(ex)).build());
writeResult(Harness.TestResult.newBuilder().setValid(false).setError(true).addReasons(Throwables.getStackTraceAsString(ex)).build());
}

System.out.flush();
Expand Down
10 changes: 7 additions & 3 deletions templates/go/duration.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ const durationTpl = `{{ $f := .Field }}{{ $r := .Rules }}
{{ if or $r.In $r.NotIn $r.Lt $r.Lte $r.Gt $r.Gte $r.Const }}
if d := {{ accessor . }}; d != nil {
dur, err := ptypes.Duration(d)
if err != nil { return {{ errCause . "err" "value is not a valid duration" }} }

{{ template "durationcmp" . }}
if err != nil {
err = {{ errCause . "err" "value is not a valid duration" }}
if !all { return err }
errors = append(errors, err)
} else {
{{ template "durationcmp" . }}
}
}
{{ end }}
`
8 changes: 5 additions & 3 deletions templates/go/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ const messageTpl = `
{{ if .MessageRules.GetSkip }}
// skipping validation for {{ $f.Name }}
{{ else }}
if v, ok := interface{}({{ accessor . }}).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return {{ errCause . "err" "embedded message failed validation" }}
if v, ok := interface{}({{ accessor . }}).(interface{ Validate(bool) error }); ok {
if err := v.Validate(all); err != nil {
err = {{ errCause . "err" "embedded message failed validation" }}
if !all { return err }
errors = append(errors, err)
}
}
{{ end }}
Expand Down
4 changes: 3 additions & 1 deletion templates/go/required.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package golang
const requiredTpl = `
{{ if .Rules.GetRequired }}
if {{ accessor . }} == nil {
return {{ err . "value is required" }}
err := {{ err . "value is required" }}
if !all { return err }
errors = append(errors, err)
}
{{ end }}
`
10 changes: 7 additions & 3 deletions templates/go/timestamp.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ const timestampTpl = `{{ $f := .Field }}{{ $r := .Rules }}
{{ if or $r.Lt $r.Lte $r.Gt $r.Gte $r.LtNow $r.GtNow $r.Within $r.Const }}
if t := {{ accessor . }}; t != nil {
ts, err := ptypes.Timestamp(t)
if err != nil { return {{ errCause . "err" "value is not a valid timestamp" }} }

{{ template "timestampcmp" . }}
if err != nil {
err = {{ errCause . "err" "value is not a valid timestamp" }}
if !all { return err }
errors = append(errors, err)
} else {
{{ template "timestampcmp" . }}
}
}
{{ end }}
`
8 changes: 6 additions & 2 deletions templates/goshared/any.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ const anyTpl = `{{ $f := .Field }}{{ $r := .Rules }}
if a := {{ accessor . }}; a != nil {
{{ if $r.In }}
if _, ok := {{ lookup $f "InLookup" }}[a.GetTypeUrl()]; !ok {
return {{ err . "type URL must be in list " $r.In }}
err := {{ err . "type URL must be in list " $r.In }}
if !all { return err }
errors = append(errors, err)
}
{{ else if $r.NotIn }}
if _, ok := {{ lookup $f "NotInLookup" }}[a.GetTypeUrl()]; ok {
return {{ err . "type URL must not be in list " $r.NotIn }}
err := {{ err . "type URL must not be in list " $r.NotIn }}
if !all { return err }
errors = append(errors, err)
}
{{ end }}
}
Expand Down
60 changes: 45 additions & 15 deletions templates/goshared/bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,80 +10,110 @@ const bytesTpl = `
{{ if or $r.Len (and $r.MinLen $r.MaxLen (eq $r.GetMinLen $r.GetMaxLen)) }}
{{ if $r.Len }}
if len({{ accessor . }}) != {{ $r.GetLen }} {
return {{ err . "value length must be " $r.GetLen " bytes" }}
err := {{ err . "value length must be " $r.GetLen " bytes" }}
if !all { return err }
errors = append(errors, err)
}
{{ else }}
if len({{ accessor . }}) != {{ $r.GetMinLen }} {
return {{ err . "value length must be " $r.GetMinLen " bytes" }}
err := {{ err . "value length must be " $r.GetMinLen " bytes" }}
if !all { return err }
errors = append(errors, err)
}
{{ end }}
{{ else if $r.MinLen }}
{{ if $r.MaxLen }}
if l := len({{ accessor . }}); l < {{ $r.GetMinLen }} || l > {{ $r.GetMaxLen }} {
return {{ err . "value length must be between " $r.GetMinLen " and " $r.GetMaxLen " bytes, inclusive" }}
err := {{ err . "value length must be between " $r.GetMinLen " and " $r.GetMaxLen " bytes, inclusive" }}
if !all { return err }
errors = append(errors, err)
}
{{ else }}
if len({{ accessor . }}) < {{ $r.GetMinLen }} {
return {{ err . "value length must be at least " $r.GetMinLen " bytes" }}
err := {{ err . "value length must be at least " $r.GetMinLen " bytes" }}
if !all { return err }
errors = append(errors, err)
}
{{ end }}
{{ else if $r.MaxLen }}
if len({{ accessor . }}) > {{ $r.GetMaxLen }} {
return {{ err . "value length must be at most " $r.GetMaxLen " bytes" }}
err := {{ err . "value length must be at most " $r.GetMaxLen " bytes" }}
if !all { return err }
errors = append(errors, err)
}
{{ end }}

{{ if $r.Prefix }}
if !bytes.HasPrefix({{ accessor . }}, {{ lit $r.GetPrefix }}) {
return {{ err . "value does not have prefix " (byteStr $r.GetPrefix) }}
err := {{ err . "value does not have prefix " (byteStr $r.GetPrefix) }}
if !all { return err }
errors = append(errors, err)
}
{{ end }}

{{ if $r.Suffix }}
if !bytes.HasSuffix({{ accessor . }}, {{ lit $r.GetSuffix }}) {
return {{ err . "value does not have suffix " (byteStr $r.GetSuffix) }}
err := {{ err . "value does not have suffix " (byteStr $r.GetSuffix) }}
if !all { return err }
errors = append(errors, err)
}
{{ end }}

{{ if $r.Contains }}
if !bytes.Contains({{ accessor . }}, {{ lit $r.GetContains }}) {
return {{ err . "value does not contain " (byteStr $r.GetContains) }}
err := {{ err . "value does not contain " (byteStr $r.GetContains) }}
if !all { return err }
errors = append(errors, err)
}
{{ end }}

{{ if $r.In }}
if _, ok := {{ lookup $f "InLookup" }}[string({{ accessor . }})]; !ok {
return {{ err . "value must be in list " $r.In }}
err := {{ err . "value must be in list " $r.In }}
if !all { return err }
errors = append(errors, err)
}
{{ else if $r.NotIn }}
if _, ok := {{ lookup $f "NotInLookup" }}[string({{ accessor . }})]; ok {
return {{ err . "value must not be in list " $r.NotIn }}
err := {{ err . "value must not be in list " $r.NotIn }}
if !all { return err }
errors = append(errors, err)
}
{{ end }}

{{ if $r.Const }}
if !bytes.Equal({{ accessor . }}, {{ lit $r.Const }}) {
return {{ err . "value must equal " $r.Const }}
err := {{ err . "value must equal " $r.Const }}
if !all { return err }
errors = append(errors, err)
}
{{ end }}

{{ if $r.GetIp }}
if ip := net.IP({{ accessor . }}); ip.To16() == nil {
return {{ err . "value must be a valid IP address" }}
err := {{ err . "value must be a valid IP address" }}
if !all { return err }
errors = append(errors, err)
}
{{ else if $r.GetIpv4 }}
if ip := net.IP({{ accessor . }}); ip.To4() == nil {
return {{ err . "value must be a valid IPv4 address" }}
err := {{ err . "value must be a valid IPv4 address" }}
if !all { return err }
errors = append(errors, err)
}
{{ else if $r.GetIpv6 }}
if ip := net.IP({{ accessor . }}); ip.To16() == nil || ip.To4() != nil {
return {{ err . "value must be a valid IPv6 address" }}
err := {{ err . "value must be a valid IPv6 address" }}
if !all { return err }
errors = append(errors, err)
}
{{ end }}

{{ if $r.Pattern }}
if !{{ lookup $f "Pattern" }}.Match({{ accessor . }}) {
return {{ err . "value does not match regex pattern " (lit $r.GetPattern) }}
err := {{ err . "value does not match regex pattern " (lit $r.GetPattern) }}
if !all { return err }
errors = append(errors, err)
}
{{ end }}

Expand Down
4 changes: 3 additions & 1 deletion templates/goshared/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package goshared
const constTpl = `{{ $r := .Rules }}
{{ if $r.Const }}
if {{ accessor . }} != {{ lit $r.GetConst }} {
return {{ err . "value must equal " $r.GetConst }}
err := {{ err . "value must equal " $r.GetConst }}
if !all { return err }
errors = append(errors, err)
}
{{ end }}
`
Loading