Skip to content

Commit

Permalink
Merge pull request #50 from bufbuild/rodaine/builder-refactor
Browse files Browse the repository at this point in the history
Refactor Registry To Match Bike Shedding
  • Loading branch information
rodaine authored Apr 12, 2023
2 parents d7ef460 + ad7da1e commit 105acdd
Show file tree
Hide file tree
Showing 46 changed files with 1,822 additions and 2,295 deletions.
30 changes: 10 additions & 20 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,23 @@ clean: ## Delete intermediate build artifacts

.PHONY: test
test: build ## Run unit tests
$(GO) test -vet=off -race -cover ./...
$(GO) test -race -cover ./go/...

.PHONY: build
build: generate ## Build all packages
$(GO) build ./...

.PHONY: install
install: ## Install all binaries
$(GO) install ./...
$(GO) build ./go/...

.PHONY: lint
lint: $(BIN)/golangci-lint ## Lint Go and protobuf
$(GO) vet ./...
$(BIN)/golangci-lint run
cd ./go && ../$(BIN)/golangci-lint run

.PHONY: lintfix
lintfix: $(BIN)/golangci-lint ## Automatically fix some lint errors
$(BIN)/golangci-lint run --fix
cd ./go && ../$(BIN)/golangci-lint run --fix

.PHONY: generate
generate: $(BIN)/buf $(BIN)/protoc-gen-go $(BIN)/license-header ## Regenerate code and licenses
rm -rf gen
generate: $(BIN)/buf $(BIN)/license-header ## Regenerate code and licenses
rm -rf go/gen
PATH=$(abspath $(BIN)) buf generate
@# We want to operate on a list of modified and new files, excluding
@# deleted and ignored files. git-ls-files can't do this alone. comm -23 takes
Expand All @@ -67,7 +62,7 @@ generate: $(BIN)/buf $(BIN)/protoc-gen-go $(BIN)/license-header ## Regenerate co

.PHONY: upgrade
upgrade: ## Upgrade dependencies
$(GO) get -u -t ./... && go mod tidy -v
cd ./go && $(GO) get -u -t ./... && go mod tidy -v

.PHONY: checkgenerate
checkgenerate:
Expand All @@ -76,18 +71,18 @@ checkgenerate:

.PHONY: benchmarks
benchmarks: ## Run benchmarks tests
$(GO) test -bench=. -count=10 > ./.tmp/benchmarks.txt
$(GO) test -bench=. -count=10 ./go/... > ./.tmp/benchmarks.txt

.PHONY: profile
profile: memprofile cpuprofile ## Generate memory and cpu profile

.PHONY: memprofile
memprofile:
$(GO) test -bench=. -count=10 -memprofile .tmp/memprofile.out
$(GO) test -bench=. -count=10 -memprofile .tmp/memprofile.out ./go

.PHONY: cpuprofile
cpuprofile:
$(GO) test -bench=. -count=10 -cpuprofile .tmp/cpuprofile.out
$(GO) test -bench=. -count=10 -cpuprofile .tmp/cpuprofile.out ./go

.PHONY: showmemprofile ## Visualize memory profile
showmemprofile: memprofile
Expand All @@ -101,11 +96,6 @@ $(BIN)/buf: Makefile
@mkdir -p $(@D)
GOBIN=$(abspath $(@D)) $(GO) install github.com/bufbuild/buf/cmd/[email protected]

$(BIN)/protoc-gen-go: Makefile
@mkdir -p $(@D)
@# The version of protoc-gen-go is determined by the version in go.mod
GOBIN=$(abspath $(@D)) $(GO) install google.golang.org/protobuf/cmd/protoc-gen-go

$(BIN)/license-header: Makefile
@mkdir -p $(@D)
GOBIN=$(abspath $(@D)) $(GO) install \
Expand Down
14 changes: 0 additions & 14 deletions benchmarks.sh

This file was deleted.

4 changes: 2 additions & 2 deletions buf.gen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ version: v1
managed:
enabled: true
go_package_prefix:
default: github.com/bufbuild/protovalidate/gen
default: github.com/bufbuild/protovalidate/go/gen
plugins:
- plugin: buf.build/protocolbuffers/go
out: gen
out: go/gen
opt: paths=source_relative
22 changes: 0 additions & 22 deletions go.mod

This file was deleted.

3 changes: 3 additions & 0 deletions go.work
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
use (
"go"
)
108 changes: 108 additions & 0 deletions go/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright 2023 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package protovalidate

import (
"fmt"
"strings"

"github.com/bufbuild/protovalidate/go/gen/buf/validate"
)

func mergeErrors(dst, src error, failFast bool) (err error, ok bool) {
if src == nil {
return dst, true
}

srcValErrs, ok := src.(*ValidationError)
if !ok {
return src, false
}

if dst == nil {
return src, !(failFast && srcValErrs.hasViolations())
}

dstValErrs, ok := dst.(*ValidationError)
if !ok {
// what should we do here?
return dst, false
}

dstValErrs.Violations = append(dstValErrs.Violations, srcValErrs.Violations...)
return dst, !(failFast && dstValErrs.hasViolations())
}

// A ValidationError is returned if one or more constraint violations were
// detected.
type ValidationError validate.Violations

func (err *ValidationError) Error() string {
sb := &strings.Builder{}
sb.WriteString("[protovalidate] validation error:\n")
for _, violation := range err.Violations {
sb.WriteString(" - ")
if violation.FieldPath != "" {
sb.WriteString(violation.FieldPath)
sb.WriteString(": ")
}
_, _ = fmt.Fprintf(sb, "%s [%s]\n",
violation.Message,
violation.ConstraintId)
}
return sb.String()
}

func (err *ValidationError) ToProto() *validate.Violations { return (*validate.Violations)(err) }

func (err *ValidationError) prefixPaths(prefix string, sep string) {
for _, violation := range err.Violations {
if violation.FieldPath == "" {
violation.FieldPath = prefix
} else {
violation.FieldPath = prefix + sep + violation.FieldPath
}
}
}

func (err *ValidationError) hasViolations() bool {
return err != nil && len(err.Violations) > 0
}

// A RuntimeError is returned if a valid CEL expression evaluation is terminated.
// The two built-in reasons are 'no_matching_overload' when a CEL function has
// no overload for the types of the arguments or 'no_such_field' when a map or
// message does not contain the desired field.
type RuntimeError struct {
cause error
}

func (err RuntimeError) Error() string {
return fmt.Sprintf("[protovalidate] runtime error: %v", err.cause)
}

func (err RuntimeError) Unwrap() error { return err.cause }

// A CompilationError is returned if a CEL expression cannot be compiled and
// type-checked.
type CompilationError struct {
cause error
}

func (err CompilationError) Error() string {
return fmt.Sprintf("[protovalidate] compilation error: %v", err.cause)
}

func (err CompilationError) Unwrap() error { return err.cause }
125 changes: 125 additions & 0 deletions go/evaluator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2023 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package protovalidate

import (
validatev2 "github.com/bufbuild/protovalidate/go/gen/buf/validate"
"google.golang.org/protobuf/reflect/protoreflect"
)

type evaluator interface {
evaluate(msg protoreflect.Message, failFast bool) error
}

type messageEvaluator struct {
err error
constraints []evaluator
}

func (m *messageEvaluator) evaluate(msg protoreflect.Message, failFast bool) error {
if err := m.err; err != nil {
return err
}
var (
err error
ok bool
)
for _, constraint := range m.constraints {
evalErr := constraint.evaluate(msg, failFast)
if err, ok = mergeErrors(err, evalErr, failFast); !ok {
break
}
}
return err
}

type messageExpressionEvaluator struct {
exprs []compiledExpression
}

func (m messageExpressionEvaluator) evaluate(msg protoreflect.Message, failFast bool) error {
binding := namedBinding{name: "this", val: msg.Interface()}
return evalExprs(m.exprs, binding, failFast)
}

type fieldExpressionEvaluator struct {
field protoreflect.FieldDescriptor
exprs []compiledExpression
}

func (f fieldExpressionEvaluator) evaluate(msg protoreflect.Message, failFast bool) error {
binding := namedBinding{name: "this", val: msg.Get(f.field)}
err := evalExprs(f.exprs, binding, failFast)
if valErr, ok := err.(*ValidationError); ok {
valErr.prefixPaths(string(f.field.Name()), ".")
}
return err
}

type messageFieldEvaluator struct {
field protoreflect.FieldDescriptor
embeddedMessageEvaluator
}

func (m messageFieldEvaluator) evaluate(msg protoreflect.Message, failFast bool) error {
fldMsg := msg.Get(m.field).Message()
err := m.embeddedMessageEvaluator.evaluate(fldMsg, failFast)
if valErr, ok := err.(*ValidationError); ok {
valErr.prefixPaths(string(m.field.FullName()), ".")
}
return err
}

type embeddedMessageEvaluator struct {
required bool
skipped bool

msgEval *messageEvaluator
exprs []compiledExpression
}

func (e embeddedMessageEvaluator) evaluate(msg protoreflect.Message, failFast bool) error {
if e.required && !msg.IsValid() {
return &ValidationError{Violations: []*validatev2.Violation{{
ConstraintId: "required",
Message: "value is required",
}}}
}

var (
err error
ok bool
)
if !e.skipped && msg.IsValid() {
evalErr := e.msgEval.evaluate(msg, failFast)
err, ok = mergeErrors(err, evalErr, failFast)
if !ok {
return err
}
}

binding := namedBinding{name: "this", val: msg.Interface()}
evalErr := evalExprs(e.exprs, binding, failFast)
err, _ = mergeErrors(err, evalErr, failFast)
return err
}

var (
_ evaluator = (*messageEvaluator)(nil)
_ evaluator = messageExpressionEvaluator{}
_ evaluator = fieldExpressionEvaluator{}
_ evaluator = messageFieldEvaluator{}
_ evaluator = embeddedMessageEvaluator{}
)
Loading

0 comments on commit 105acdd

Please sign in to comment.