Skip to content

Commit

Permalink
add json functions for config parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
lovromazgon committed Feb 29, 2024
1 parent 1221866 commit e5b020b
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 4 deletions.
49 changes: 45 additions & 4 deletions config/parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,25 @@

package config

import (
"fmt"
"strconv"
"strings"
)

// Parameters is a map of all configuration parameters.
type Parameters map[string]Parameter

// Parameter defines a single configuration parameter.
type Parameter struct {
// Default is the default value of the parameter, if any.
Default string
Default string `json:"default"`
// Description holds a description of the field and how to configure it.
Description string
Description string `json:"description"`
// Type defines the parameter data type.
Type ParameterType
Type ParameterType `json:"type"`
// Validations list of validations to check for the parameter.
Validations []Validation
Validations []Validation `json:"validations"`
}

type ParameterType int
Expand All @@ -41,3 +47,38 @@ const (
ParameterTypeFile // file
ParameterTypeDuration // duration
)

func (pt ParameterType) MarshalText() ([]byte, error) {
return []byte(pt.String()), nil
}

func (pt *ParameterType) UnmarshalText(b []byte) error {
if len(b) == 0 {
return nil // empty string, do nothing
}

switch string(b) {
case ParameterTypeString.String():
*pt = ParameterTypeString
case ParameterTypeInt.String():
*pt = ParameterTypeInt
case ParameterTypeFloat.String():
*pt = ParameterTypeFloat
case ParameterTypeBool.String():
*pt = ParameterTypeBool
case ParameterTypeFile.String():
*pt = ParameterTypeFile
case ParameterTypeDuration.String():
*pt = ParameterTypeDuration
default:
// it may not be a known parameter type, but we also allow ParameterType(int)
valIntRaw := strings.TrimSuffix(strings.TrimPrefix(string(b), "ParameterType("), ")")
valInt, err := strconv.Atoi(valIntRaw)
if err != nil {
return fmt.Errorf("parameter type %q: %w", b, ErrInvalidParameterType)
}
*pt = ParameterType(valInt)
}

return nil
}
156 changes: 156 additions & 0 deletions config/parameter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Copyright © 2024 Meroxa, 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 config

import (
"regexp"
"testing"

"github.com/goccy/go-json"
"github.com/matryer/is"
)

func TestParameters_MarshalJSON(t *testing.T) {
is := is.New(t)
parameters := Parameters{
"empty": {},
"string": {
Default: "test-string",
Description: "test-description",
Type: ParameterTypeString,
Validations: []Validation{},
},
"int": {
Default: "test-string",
Description: "test-description",
Type: ParameterTypeInt,
Validations: []Validation{},
},
"float": {
Default: "test-float",
Description: "test-description",
Type: ParameterTypeFloat,
Validations: []Validation{},
},
"bool": {
Default: "test-bool",
Description: "test-description",
Type: ParameterTypeBool,
Validations: []Validation{},
},
"file": {
Default: "test-file",
Description: "test-description",
Type: ParameterTypeFile,
Validations: []Validation{},
},
"duration": {
Default: "test-duration",
Description: "test-description",
Type: ParameterTypeDuration,
Validations: []Validation{},
},
"validations": {
Validations: []Validation{
ValidationRequired{},
ValidationGreaterThan{V: 1.2},
ValidationLessThan{V: 3.4},
ValidationInclusion{List: []string{"1", "2", "3"}},
ValidationExclusion{List: []string{"4", "5", "6"}},
ValidationRegex{Regex: regexp.MustCompile("test-regex")},
},
},
}

want := `{
"bool": {
"default": "test-bool",
"description": "test-description",
"type": "bool",
"validations": []
},
"duration": {
"default": "test-duration",
"description": "test-description",
"type": "duration",
"validations": []
},
"empty": {
"default": "",
"description": "",
"type": "ParameterType(0)",
"validations": null
},
"file": {
"default": "test-file",
"description": "test-description",
"type": "file",
"validations": []
},
"float": {
"default": "test-float",
"description": "test-description",
"type": "float",
"validations": []
},
"int": {
"default": "test-string",
"description": "test-description",
"type": "int",
"validations": []
},
"string": {
"default": "test-string",
"description": "test-description",
"type": "string",
"validations": []
},
"validations": {
"default": "",
"description": "",
"type": "ParameterType(0)",
"validations": [
{
"type": "required",
"value": ""
},
{
"type": "greater-than",
"value": "1.2"
},
{
"type": "less-than",
"value": "3.4"
},
{
"type": "inclusion",
"value": "1,2,3"
},
{
"type": "exclusion",
"value": "4,5,6"
},
{
"type": "regex",
"value": "test-regex"
}
]
}
}`

got, err := json.MarshalIndent(parameters, "", " ")
is.NoErr(err)
is.Equal(want, string(got))
}
51 changes: 51 additions & 0 deletions config/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"slices"
"strconv"
"strings"

"github.com/goccy/go-json"
)

type Validation interface {
Expand All @@ -42,6 +44,41 @@ const (
ValidationTypeRegex // regex
)

func (vt ValidationType) MarshalText() ([]byte, error) {
return []byte(vt.String()), nil
}

func (vt *ValidationType) UnmarshalText(b []byte) error {
if len(b) == 0 {
return nil // empty string, do nothing
}

switch string(b) {
case ValidationTypeRequired.String():
*vt = ValidationTypeRequired
case ValidationTypeGreaterThan.String():
*vt = ValidationTypeGreaterThan
case ValidationTypeLessThan.String():
*vt = ValidationTypeLessThan
case ValidationTypeInclusion.String():
*vt = ValidationTypeInclusion
case ValidationTypeExclusion.String():
*vt = ValidationTypeExclusion
case ValidationTypeRegex.String():
*vt = ValidationTypeRegex
default:
// it may not be a known validation type, but we also allow ValidationType(int)
valIntRaw := strings.TrimSuffix(strings.TrimPrefix(string(b), "ValidationType("), ")")
valInt, err := strconv.Atoi(valIntRaw)
if err != nil {
return fmt.Errorf("validation type %q: %w", b, ErrInvalidValidationType)
}
*vt = ValidationType(valInt)
}

return nil
}

type ValidationRequired struct{}

func (v ValidationRequired) Type() ValidationType { return ValidationTypeRequired }
Expand All @@ -52,6 +89,7 @@ func (v ValidationRequired) Validate(value string) error {
}
return nil
}
func (v ValidationRequired) MarshalJSON() ([]byte, error) { return jsonMarshalValidation(v) }

type ValidationGreaterThan struct {
V float64
Expand All @@ -70,6 +108,7 @@ func (v ValidationGreaterThan) Validate(value string) error {
}
return nil
}
func (v ValidationGreaterThan) MarshalJSON() ([]byte, error) { return jsonMarshalValidation(v) }

type ValidationLessThan struct {
V float64
Expand All @@ -88,6 +127,7 @@ func (v ValidationLessThan) Validate(value string) error {
}
return nil
}
func (v ValidationLessThan) MarshalJSON() ([]byte, error) { return jsonMarshalValidation(v) }

type ValidationInclusion struct {
List []string
Expand All @@ -101,6 +141,7 @@ func (v ValidationInclusion) Validate(value string) error {
}
return nil
}
func (v ValidationInclusion) MarshalJSON() ([]byte, error) { return jsonMarshalValidation(v) }

type ValidationExclusion struct {
List []string
Expand All @@ -114,6 +155,7 @@ func (v ValidationExclusion) Validate(value string) error {
}
return nil
}
func (v ValidationExclusion) MarshalJSON() ([]byte, error) { return jsonMarshalValidation(v) }

type ValidationRegex struct {
Regex *regexp.Regexp
Expand All @@ -127,3 +169,12 @@ func (v ValidationRegex) Validate(value string) error {
}
return nil
}
func (v ValidationRegex) MarshalJSON() ([]byte, error) { return jsonMarshalValidation(v) }

func jsonMarshalValidation(v Validation) ([]byte, error) {
//nolint:wrapcheck // no need to wrap this error, this will be called by the JSON lib itself
return json.Marshal(map[string]any{
"type": v.Type(),
"value": v.Value(),
})
}

0 comments on commit e5b020b

Please sign in to comment.