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

feat(services): add tag-based query/params helper #92

Merged
merged 3 commits into from
Dec 20, 2020
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
15 changes: 14 additions & 1 deletion cli/cmd/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,20 @@ func Run(cmd *cobra.Command, _ []string) {
os.Exit(1)
}

generator, err := generators.NewGenerator(generatorName)
var generator types.Generator

var generatorFlag = cmd.Flags().Lookup("generator")

if !generatorFlag.Changed {
// try to use the service default generator if one exists
generator, _ = generators.NewGenerator(serviceSchema)
}

if generator != nil {
generatorName = serviceSchema
} else {
generator, err = generators.NewGenerator(generatorName)
}

if err != nil {
fmt.Printf("Error: %s\n", err)
Expand Down
3 changes: 2 additions & 1 deletion cli/cmd/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ func Run(cmd *cobra.Command, _ []string) {
}

configMap, maxKeyLen := format.GetConfigMap(service)
for key, value := range configMap {
for key, _ := range configMap {
value := configMap[key]
pad := strings.Repeat(" ", maxKeyLen-len(key))
_, _ = fmt.Fprintf(color.Output, "%s%s: %s\n", pad, key, value)
}
Expand Down
13 changes: 0 additions & 13 deletions internal/failures/failure.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ import "fmt"
// FailureID is a number to be used to identify a specific error
type FailureID int

const (
// FailTestSetup is FailureID used to represent an error that is part of the setup for tests
FailTestSetup FailureID = -1
)

type failure struct {
message string
id FailureID
Expand Down Expand Up @@ -48,12 +43,4 @@ func Wrap(message string, id FailureID, wrappedError error, v ...interface{}) Fa
}
}

// IsTestSetupFailure checks whether the given failure is due to the test setup being broken
func IsTestSetupFailure(f Failure) (string, bool) {
if f != nil && f.ID() == FailTestSetup {
return fmt.Sprintf("test setup failed: %s", f.Error()), true
}
return "", false
}

var _ error = &failure{}
5 changes: 3 additions & 2 deletions internal/testutils/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package testutils

import (
"github.com/containrrr/shoutrrr/pkg/format"
"net/url"

Ω "github.com/onsi/gomega"
Expand All @@ -10,7 +11,7 @@ import (

// TestConfigGetInvalidQueryValue tests whether the config returns an error when an invalid query value is requested
func TestConfigGetInvalidQueryValue(config types.ServiceConfig) {
value, err := config.Get("invalid query var")
value, err := format.GetConfigQueryResolver(config).Get("invalid query var")
Ω.ExpectWithOffset(1, value).To(Ω.BeEmpty())
Ω.ExpectWithOffset(1, err).To(Ω.HaveOccurred())
}
Expand All @@ -32,6 +33,6 @@ func TestConfigGetEnumsCount(config types.ServiceConfig, expectedCount int) {

// TestConfigGetFieldsCount tests whether the config.QueryFields return the expected amount of fields
func TestConfigGetFieldsCount(config types.ServiceConfig, expectedCount int) {
fields := config.QueryFields()
fields := format.GetConfigQueryResolver(config).QueryFields()
Ω.ExpectWithOffset(1, fields).To(Ω.HaveLen(expectedCount))
}
6 changes: 3 additions & 3 deletions pkg/format/format_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import (
)

// BuildQuery converts the fields of a config object to a delimited query string
func BuildQuery(c types.ServiceConfig) string {
func BuildQuery(cqr types.ConfigQueryResolver) string {
query := ""
fields := c.QueryFields()
format := "%s=%s"
fields := cqr.QueryFields()
for index, key := range fields {
value, _ := c.Get(key)
value, _ := cqr.Get(key)
if index == 1 {
format = "&%s=%s"
}
Expand Down
119 changes: 111 additions & 8 deletions pkg/format/formatter.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package format

import (
"errors"
"fmt"
"github.com/fatih/color"
"reflect"
"strconv"
"strings"
"unsafe"

Expand All @@ -26,14 +28,22 @@ func GetConfigMap(service types.Service) (map[string]string, int) {
return formatter.formatStructMap(configType, config, 0)
}

func GetConfigFormat(service types.Service) (reflect.Type, []fieldInfo) {
func GetServiceConfigFormat(service types.Service) (reflect.Type, []FieldInfo) {
configRef := reflect.ValueOf(service).Elem().FieldByName("config")
configType := configRef.Type().Elem()

config := reflect.New(configType)

serviceConfig := config.Interface().(types.ServiceConfig)

return GetConfigFormat(serviceConfig)
}

func GetConfigFormat(serviceConfig types.ServiceConfig) (reflect.Type, []FieldInfo) {
configType := reflect.TypeOf(serviceConfig)
if configType.Kind() == reflect.Ptr {
configType = configType.Elem()
}

formatter := formatter{
EnumFormatters: serviceConfig.Enums(),
MaxDepth: 10,
Expand Down Expand Up @@ -85,7 +95,7 @@ func (fmtr *formatter) formatStructMap(structType reflect.Type, structItem inter
fmtr.Errors = append(fmtr.Errors, err)
}
} else if nextDepth < fmtr.MaxDepth {
value, valueLen = fmtr.getFieldValueString(values.Field(i), nextDepth)
value, valueLen = fmtr.getFieldValueString(values.FieldByName(field.Name), nextDepth)
}
} else {
// Since no values was supplied, let's substitute the value with the type
Expand Down Expand Up @@ -124,20 +134,22 @@ func (fmtr *formatter) formatStructMap(structType reflect.Type, structItem inter
return valueMap, maxKeyLen
}

type fieldInfo struct {
type FieldInfo struct {
Name string
Type reflect.Type
EnumFormatter types.EnumFormatter
Description string
DefaultValue string
Template string
Required bool
Title bool
Key string
}

func (fmtr *formatter) getStructFieldInfo(structType reflect.Type) []fieldInfo {
func (fmtr *formatter) getStructFieldInfo(structType reflect.Type) []FieldInfo {

numFields := structType.NumField()
fields := make([]fieldInfo, numFields)
fields := make([]FieldInfo, 0, numFields)
maxKeyLen := 0

for i := 0; i < numFields; i++ {
Expand All @@ -148,10 +160,11 @@ func (fmtr *formatter) getStructFieldInfo(structType reflect.Type) []fieldInfo {
continue
}

info := fieldInfo{
info := FieldInfo{
Name: fieldDef.Name,
Type: fieldDef.Type,
Required: true,
Title: false,
}

if tag, ok := fieldDef.Tag.Lookup("desc"); ok {
Expand All @@ -171,11 +184,19 @@ func (fmtr *formatter) getStructFieldInfo(structType reflect.Type) []fieldInfo {
info.Required = false
}

if _, ok := fieldDef.Tag.Lookup("title"); ok {
info.Title = true
}

if tag, ok := fieldDef.Tag.Lookup("key"); ok {
info.Key = tag
}

if ef, isEnum := fmtr.EnumFormatters[fieldDef.Name]; isEnum {
info.EnumFormatter = ef
}

fields[i] = info
fields = append(fields, info)
keyLen := len(fieldDef.Name)
if keyLen > maxKeyLen {
maxKeyLen = keyLen
Expand Down Expand Up @@ -220,6 +241,10 @@ func (fmtr *formatter) getFieldValueString(field reflect.Value, depth uint8) (st
items[i], itemLen = fmtr.getFieldValueString(field.Index(i), nextDepth)
totalLen += itemLen
}
if fieldLen > 1 {
// Add space for separators
totalLen += (fieldLen - 1) * 2
}
return fmt.Sprintf("[ %s ]", strings.Join(items, ", ")), totalLen
}

Expand Down Expand Up @@ -254,3 +279,81 @@ func (fmtr *formatter) getFieldValueString(field reflect.Value, depth uint8) (st
strVal := kind.String()
return fmt.Sprintf("<?%s>", strVal), len(strVal) + 5
}

func SetConfigField(config reflect.Value, field FieldInfo, inputValue string) (valid bool, err error) {
configField := config.FieldByName(field.Name)
fieldKind := field.Type.Kind()

if fieldKind == reflect.String {
configField.SetString(inputValue)
return true, nil
} else if field.EnumFormatter != nil {
value := field.EnumFormatter.Parse(inputValue)
if value == EnumInvalid {
enumNames := strings.Join(field.EnumFormatter.Names(), ", ")
return false, fmt.Errorf("not a one of %v", enumNames)
} else {
configField.SetInt(int64(value))
return true, nil
}
} else if fieldKind >= reflect.Uint && fieldKind <= reflect.Uint64 {
var value uint64
value, err = strconv.ParseUint(inputValue, 10, field.Type.Bits())
if err == nil {
configField.SetUint(value)
return true, nil
}
} else if fieldKind >= reflect.Int && fieldKind <= reflect.Int64 {
var value int64
value, err = strconv.ParseInt(inputValue, 10, field.Type.Bits())
if err == nil {
configField.SetInt(value)
return true, nil
}
} else if fieldKind == reflect.Bool {
if value, ok := ParseBool(inputValue, false); !ok {
return false, errors.New("accepted values are 1, true, yes or 0, false, no")
} else {
configField.SetBool(value)
return true, nil
}
} else if fieldKind >= reflect.Slice {
elemKind := field.Type.Elem().Kind()
if elemKind != reflect.String {
return false, errors.New("field format is not supported")
} else {
values := strings.Split(inputValue, ",")
configField.Set(reflect.ValueOf(values))
return true, nil
}
}
return false, nil

}

func GetConfigFieldString(config reflect.Value, field FieldInfo) (value string, err error) {
configField := config.FieldByName(field.Name)
fieldKind := field.Type.Kind()

if fieldKind == reflect.String {
return configField.String(), nil
} else if field.EnumFormatter != nil {
return field.EnumFormatter.Print(int(configField.Int())), nil
} else if fieldKind >= reflect.Uint && fieldKind <= reflect.Uint64 {
return strconv.FormatUint(configField.Uint(), 10), nil
} else if fieldKind >= reflect.Int && fieldKind <= reflect.Int64 {
return strconv.FormatInt(configField.Int(), 10), nil
} else if fieldKind == reflect.Bool {
return PrintBool(configField.Bool()), nil
} else if fieldKind >= reflect.Slice {
sliceLen := configField.Len()
sliceValue := configField.Slice(0, sliceLen)
if field.Type.Elem().Kind() != reflect.String {
return "", errors.New("field format is not supported")
}
slice := sliceValue.Interface().([]string)
return strings.Join(slice, ","), nil
}
return "", fmt.Errorf("field kind %x is not supported", fieldKind)

}
Loading