Skip to content

Commit

Permalink
⚙️ #40: Added config validation
Browse files Browse the repository at this point in the history
  • Loading branch information
roma-glushko committed Jan 21, 2024
1 parent c4a75b7 commit a49714f
Show file tree
Hide file tree
Showing 13 changed files with 97 additions and 36 deletions.
6 changes: 1 addition & 5 deletions config.dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ telemetry:
level: debug # debug, info, warn, error, fatal
encoding: console

#api:
# http:
# ...

routers:
language:
- id: myrouter
Expand All @@ -18,4 +14,4 @@ routers:
azureopenai:
api_key: ""
model: ""
base_url: ""
base_url: ""
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.21.5

require (
github.com/cloudwego/hertz v0.7.3
github.com/go-playground/validator/v10 v10.17.0
github.com/hertz-contrib/logger/zap v1.1.0
github.com/hertz-contrib/swagger v0.1.0
github.com/spf13/cobra v1.8.0
Expand All @@ -28,14 +29,18 @@ require (
github.com/cloudwego/netpoll v0.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.20.2 // indirect
github.com/go-openapi/jsonreference v0.20.4 // indirect
github.com/go-openapi/spec v0.20.13 // indirect
github.com/go-openapi/swag v0.22.7 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/nyaruka/phonenumbers v1.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand All @@ -45,6 +50,7 @@ require (
github.com/tidwall/pretty v1.2.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
golang.org/x/arch v0.6.0 // indirect
golang.org/x/crypto v0.16.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
Expand Down
15 changes: 15 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU=
Expand All @@ -43,6 +45,14 @@ github.com/go-openapi/spec v0.20.13 h1:XJDIN+dLH6vqXgafnl5SUIMnzaChQ6QTo0/UPMbkI
github.com/go-openapi/spec v0.20.13/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw=
github.com/go-openapi/swag v0.22.7 h1:JWrc1uc/P9cSomxfnsFSVWoE1FW6bNbrVPmpQYpCcR8=
github.com/go-openapi/swag v0.22.7/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
Expand Down Expand Up @@ -71,6 +81,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U=
Expand Down Expand Up @@ -99,6 +111,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
Expand Down Expand Up @@ -129,6 +142,8 @@ golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc=
golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ func main() {
cli := cmd.NewCLI()

if err := cli.Execute(); err != nil {
log.Fatalf("glide run finished with error: %v", err)
log.Fatalf("Glide has finished with error: %v", err)
}
}
2 changes: 1 addition & 1 deletion pkg/api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "glide/pkg/api/http"

// Config defines configuration for all API types we support (e.g. HTTP, gRPC)
type Config struct {
HTTP *http.ServerConfig `yaml:"http"`
HTTP *http.ServerConfig `yaml:"http" validate:"required"`
}

func DefaultConfig() *Config {
Expand Down
3 changes: 2 additions & 1 deletion pkg/cmd/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ func NewCLI() *cobra.Command {

return gateway.Run(cmd.Context())
},
// SilenceUsage: true,
SilenceUsage: true,
SilenceErrors: true,
}

cli.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file")
Expand Down
4 changes: 2 additions & 2 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (

// Config is a general top-level Glide configuration
type Config struct {
Telemetry *telemetry.Config `yaml:"telemetry"`
API *api.Config `yaml:"api"`
Telemetry *telemetry.Config `yaml:"telemetry" validate:"required"`
API *api.Config `yaml:"api" validate:"required"`
Routers routers.Config `yaml:"routers" validate:"required"`
}

Expand Down
52 changes: 47 additions & 5 deletions pkg/config/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,39 @@ import (
"fmt"
"os"
"path/filepath"
"reflect"
"strings"

"github.com/go-playground/validator/v10"

"gopkg.in/yaml.v3"
)

// Provider reads, collects, validates and process config files
type Provider struct {
expander *Expander
Config *Config
expander *Expander
Config *Config
validator *validator.Validate
}

// NewProvider creates a instance of Config Provider
func NewProvider() *Provider {
configValidator := validator.New(validator.WithRequiredStructEnabled())

configValidator.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("yaml"), ",", 2)[0]

if name == "-" {
return ""
}

return name
})

return &Provider{
expander: &Expander{},
Config: nil,
expander: &Expander{},
Config: nil,
validator: configValidator,
}
}

Expand All @@ -38,7 +56,31 @@ func (p *Provider) Load(configPath string) (*Provider, error) {
return p, fmt.Errorf("unable to parse config file %v: %w", configPath, err)
}

// TODO: validate config values
err = p.validator.Struct(cfg)

if err != nil {
// this check is only needed when your code could produce
// an invalid value for validation such as interface with nil
// value most including myself do not usually have code like this.
if _, ok := err.(*validator.InvalidValidationError); ok {
return p, err
}

errors := make([]string, 0, len(err.(validator.ValidationErrors)))

for _, err := range err.(validator.ValidationErrors) {
namespace := strings.TrimLeft(err.Namespace(), "Config.")

errors = append(errors, fmt.Sprintf("- ❌ %v field is %v, %v provided", namespace, err.Tag(), err.Value()))
}

// from here you can create your own error messages in whatever language you wish
return p, fmt.Errorf(
"failed to validate config file %v:\n%v\nPlease make sure the config file is properly formatted",
configPath,
strings.Join(errors, "\n"),
)
}

p.Config = cfg

Expand Down
9 changes: 6 additions & 3 deletions pkg/providers/azureopenai/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ type Client struct {

// NewClient creates a new Azure OpenAI client for the OpenAI API.
func NewClient(providerConfig *Config, clientConfig *clients.ClientConfig, tel *telemetry.Telemetry) (*Client, error) {
chatURL := fmt.Sprintf("%s/openai/deployments/%s/chat/completions?api-version=%s", providerConfig.BaseURL, providerConfig.Model, providerConfig.APIVersion)

fmt.Println("chatURL", chatURL)
chatURL := fmt.Sprintf(
"%s/openai/deployments/%s/chat/completions?api-version=%s",
providerConfig.BaseURL,
providerConfig.Model,
providerConfig.APIVersion,
)

c := &Client{
baseURL: providerConfig.BaseURL,
Expand Down
16 changes: 7 additions & 9 deletions pkg/providers/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,18 @@ import (
var ErrProviderNotFound = errors.New("provider not found")

type LangModelConfig struct {
ID string `yaml:"id" json:"id" validate:"required"` // Model instance ID (unique in scope of the router)
Enabled bool `yaml:"enabled" json:"enabled"` // Is the model enabled?
ID string `yaml:"id" json:"id" validate:"required"` // Model instance ID (unique in scope of the router)
Enabled bool `yaml:"enabled" json:"enabled" validate:"required"` // Is the model enabled?
ErrorBudget *health.ErrorBudget `yaml:"error_budget" json:"error_budget" swaggertype:"primitive,string"`
Latency *latency.Config `yaml:"latency" json:"latency"`
Weight int `yaml:"weight" json:"weight"`
Client *clients.ClientConfig `yaml:"client" json:"client"`
OpenAI *openai.Config `yaml:"openai" json:"openai"`
AzureOpenAI *azureopenai.Config `yaml:"azureopenai" json:"azureopenai"`
Cohere *cohere.Config `yaml:"cohere" json:"cohere"`
OctoML *octoml.Config `yaml:"octoml" json:"octoml"`
Anthropic *anthropic.Config `yaml:"anthropic" json:"anthropic"`
// Add other providers like
// Cohere *cohere.Config
// Anthropic *anthropic.Config
OpenAI *openai.Config `yaml:"openai" json:"openai"`
AzureOpenAI *azureopenai.Config `yaml:"azureopenai" json:"azureopenai"`
Cohere *cohere.Config `yaml:"cohere" json:"cohere"`
OctoML *octoml.Config `yaml:"octoml" json:"octoml"`
Anthropic *anthropic.Config `yaml:"anthropic" json:"anthropic"`
}

func DefaultLangModelConfig() *LangModelConfig {
Expand Down
12 changes: 6 additions & 6 deletions pkg/routers/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

type Config struct {
LanguageRouters []LangRouterConfig `yaml:"language"` // the list of language routers
LanguageRouters []LangRouterConfig `yaml:"language" validate:"required"` // the list of language routers
}

func (c *Config) BuildLangRouters(tel *telemetry.Telemetry) ([]*LangRouter, error) {
Expand Down Expand Up @@ -48,11 +48,11 @@ func (c *Config) BuildLangRouters(tel *telemetry.Telemetry) ([]*LangRouter, erro
// TODO: Had to keep RoutingStrategy because of https://github.com/swaggo/swag/issues/1738
// LangRouterConfig
type LangRouterConfig struct {
ID string `yaml:"id" json:"routers" validate:"required"` // Unique router ID
Enabled bool `yaml:"enabled" json:"enabled"` // Is router enabled?
Retry *retry.ExpRetryConfig `yaml:"retry" json:"retry"` // retry when no healthy model is available to router
RoutingStrategy routing.Strategy `yaml:"strategy" json:"strategy" swaggertype:"primitive,string"` // strategy on picking the next model to serve the request
Models []providers.LangModelConfig `yaml:"models" json:"models" validate:"required"` // the list of models that could handle requests
ID string `yaml:"id" json:"routers" validate:"required"` // Unique router ID
Enabled bool `yaml:"enabled" json:"enabled" validate:"required"` // Is router enabled?
Retry *retry.ExpRetryConfig `yaml:"retry" json:"retry" validate:"required"` // retry when no healthy model is available to router
RoutingStrategy routing.Strategy `yaml:"strategy" json:"strategy" swaggertype:"primitive,string" validate:"required"` // strategy on picking the next model to serve the request
Models []providers.LangModelConfig `yaml:"models" json:"models" validate:"required"` // the list of models that could handle requests
}

// BuildModels creates LanguageModel slice out of the given config
Expand Down
4 changes: 2 additions & 2 deletions pkg/telemetry/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import (

type LogConfig struct {
// Level is the minimum enabled logging level.
Level zapcore.Level `yaml:"level"`
Level zapcore.Level `yaml:"level" validate:"required"`

// Encoding sets the logger's encoding. Valid values are "json", "console"
Encoding string `yaml:"encoding"`
Encoding string `yaml:"encoding" validate:"required"`

// DisableCaller stops annotating logs with the calling function's file name and line number.
// By default, all logs are annotated.
Expand Down
2 changes: 1 addition & 1 deletion pkg/telemetry/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package telemetry
import "go.uber.org/zap"

type Config struct {
LogConfig *LogConfig `yaml:"logging"`
LogConfig *LogConfig `yaml:"logging" validate:"required"`
// TODO: add OTEL config
}

Expand Down

0 comments on commit a49714f

Please sign in to comment.