Skip to content

Commit

Permalink
Merge pull request #127 from youyuanwu/feature/readonly-validate
Browse files Browse the repository at this point in the history
WIP: feature/ReadOnly-Validation
  • Loading branch information
casualjim authored Sep 15, 2020
2 parents 51f5ec2 + 3e3386a commit ac0948f
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 1 deletion.
56 changes: 56 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package validate

import (
"context"
)

// validateCtxKey is the key type of context key in this pkg
type validateCtxKey string

const (
operationTypeKey validateCtxKey = "operationTypeKey"
)

type operationType string

const (
request operationType = "request"
response operationType = "response"
none operationType = "none" // not specified in ctx
)

var operationTypeEnum []operationType = []operationType{request, response, none}

// WithOperationRequest returns a new context with operationType request
// in context value
func WithOperationRequest(ctx context.Context) context.Context {
return withOperation(ctx, request)
}

// WithOperationRequest returns a new context with operationType response
// in context value
func WithOperationResponse(ctx context.Context) context.Context {
return withOperation(ctx, response)
}

func withOperation(ctx context.Context, operation operationType) context.Context {
return context.WithValue(ctx, operationTypeKey, operation)
}

// extractOperationType extracts the operation type from ctx
// if not specified or of unknown value, return none operation type
func extractOperationType(ctx context.Context) operationType {
v := ctx.Value(operationTypeKey)
if v == nil {
return none
}
res, ok := v.(operationType)
if !ok {
return none
}
// validate the value is in operation enum
if err := Enum("", "", res, operationTypeEnum); err != nil {
return none
}
return res
}
50 changes: 50 additions & 0 deletions context_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package validate

import (
"context"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestContext_ExtractOperationType(t *testing.T) {

var testCases = []struct {
Ctx context.Context
ExpectedOpType operationType
}{
{
Ctx: WithOperationRequest(context.Background()),
ExpectedOpType: request,
},
{
Ctx: WithOperationResponse(context.Background()),
ExpectedOpType: response,
},
{
Ctx: context.Background(),
ExpectedOpType: none,
},
{
Ctx: context.WithValue(context.Background(), validateCtxKey("dummy"), "dummy val"),
ExpectedOpType: none,
},
{
Ctx: context.WithValue(context.Background(), operationTypeKey, "dummy val"),
ExpectedOpType: none,
},
{
Ctx: context.WithValue(context.Background(), operationTypeKey, operationType("dummy val")),
ExpectedOpType: none,
},
}

for idx, tc := range testCases {
t.Run(fmt.Sprintf("TestCase #%d", idx), func(t *testing.T) {
op := extractOperationType(tc.Ctx)
assert.Equal(t, tc.ExpectedOpType, op)
})
}

}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.14
require (
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
github.com/go-openapi/analysis v0.19.10
github.com/go-openapi/errors v0.19.6
github.com/go-openapi/errors v0.19.7
github.com/go-openapi/jsonpointer v0.19.3
github.com/go-openapi/loads v0.19.5
github.com/go-openapi/runtime v0.19.16
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA
github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
github.com/go-openapi/errors v0.19.6 h1:xZMThgv5SQ7SMbWtKFkCf9bBdvR2iEyw9k3zGZONuys=
github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.19.7-0.20200915043824-00a6ebeed93c h1:zrygysSbD99k3JFmkC4u77CHGO+UriV9pht0HqWn9XI=
github.com/go-openapi/errors v0.19.7-0.20200915043824-00a6ebeed93c/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.19.7 h1:Lcq+o0mSwCLKACMxZhreVHigB9ebghJ/lrmeaqASbjo=
github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
Expand Down
22 changes: 22 additions & 0 deletions values.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package validate

import (
"context"
"fmt"
"reflect"
"strings"
Expand Down Expand Up @@ -135,6 +136,27 @@ func MaxLength(path, in, data string, maxLength int64) *errors.Validation {
return nil
}

// ReadOnly validates an interface for readonly
func ReadOnly(ctx context.Context, path, in string, data interface{}) *errors.Validation {

// read only is only validated when operationType is request
if op := extractOperationType(ctx); op != request {
return nil
}

// data must be of zero value of its type
val := reflect.ValueOf(data)
if val.IsValid() {
if reflect.DeepEqual(reflect.Zero(val.Type()).Interface(), val.Interface()) {
return nil
}
} else {
return nil
}

return errors.ReadOnly(path, in, data)
}

// Required validates an interface for requiredness
func Required(path, in string, data interface{}) *errors.Validation {
val := reflect.ValueOf(data)
Expand Down
57 changes: 57 additions & 0 deletions values_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package validate

import (
"context"
"math"
"testing"

Expand Down Expand Up @@ -133,6 +134,62 @@ func TestValues_ValidateMaxLength(t *testing.T) {
assert.Nil(t, err)
}

func TestValues_ReadOnly(t *testing.T) {
var err error
path := "test"
in := "body"

ReadOnlySuccess := []interface{}{
"",
0,
nil,
}

// fail only when operation type is request
ReadOnlyFail := []interface{}{
" ",
"bla-bla-bla",
2,
[]interface{}{21, []int{}, "testString"},
}

t.Run("No operation context", func(t *testing.T) {
// readonly should not have any effect
ctx := context.Background()
for _, v := range ReadOnlySuccess {
err = ReadOnly(ctx, path, in, v)
assert.Nil(t, err)
}
for _, v := range ReadOnlyFail {
err = ReadOnly(ctx, path, in, v)
assert.Nil(t, err)
}

})
t.Run("operationType request", func(t *testing.T) {
ctx := WithOperationRequest(context.Background())
for _, v := range ReadOnlySuccess {
err = ReadOnly(ctx, path, in, v)
assert.Nil(t, err)
}
for _, v := range ReadOnlyFail {
err = ReadOnly(ctx, path, in, v)
assert.Error(t, err)
}
})
t.Run("operationType response", func(t *testing.T) {
ctx := WithOperationResponse(context.Background())
for _, v := range ReadOnlySuccess {
err = ReadOnly(ctx, path, in, v)
assert.Nil(t, err)
}
for _, v := range ReadOnlyFail {
err = ReadOnly(ctx, path, in, v)
assert.Nil(t, err)
}
})
}

func TestValues_ValidateRequired(t *testing.T) {
var err error
path := "test"
Expand Down

0 comments on commit ac0948f

Please sign in to comment.