mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-12 04:54:45 +00:00
fix(providers): validate metadataPath has a leading slash
fetchModelMetadata (provider_openai.go) builds its request URL by plain string concatenation, a.baseURL+a.metadataPath. A metadataPath missing its leading slash would silently glue onto baseURL's own path with no separator (e.g. "http://host:1234api/v0/models"), a malformed URL that only ever surfaced as a confusing runtime fetch failure. buildAdapters now rejects a non-empty metadataPath without a leading "/" at construction, naming the provider, the same fail-fast treatment every other malformed config value already gets. Empty (the default) is always valid. Folded review minor.
This commit is contained in:
@@ -708,6 +708,24 @@ func TestBuildAdapters(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("bad: metadataPath without a leading slash is a constructor error (review fix)", func(t *testing.T) {
|
||||||
|
cfg := &Config{Providers: map[string]*ProviderConfig{
|
||||||
|
"lmstudio": {Type: "openai", MetadataPath: "api/v0/models"},
|
||||||
|
}}
|
||||||
|
if _, err := buildAdapters(cfg); err == nil {
|
||||||
|
t.Error("want an error for a metadataPath missing its leading slash")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("good: empty metadataPath is valid (feature disabled)", func(t *testing.T) {
|
||||||
|
cfg := &Config{Providers: map[string]*ProviderConfig{
|
||||||
|
"p1": {Type: "openai", APIKey: "sk-test"},
|
||||||
|
}}
|
||||||
|
if _, err := buildAdapters(cfg); err != nil {
|
||||||
|
t.Errorf("buildAdapters: %v, want no error for an empty metadataPath", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("good: explicit base URL trailing slash trimmed", func(t *testing.T) {
|
t.Run("good: explicit base URL trailing slash trimmed", func(t *testing.T) {
|
||||||
cfg := &Config{Providers: map[string]*ProviderConfig{
|
cfg := &Config{Providers: map[string]*ProviderConfig{
|
||||||
"p1": {Type: "openai", BaseURL: "https://custom.example.com/", APIKey: "sk-test"},
|
"p1": {Type: "openai", BaseURL: "https://custom.example.com/", APIKey: "sk-test"},
|
||||||
|
|||||||
@@ -367,6 +367,25 @@ func validateConfigName(kind, name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateMetadataPath rejects a non-empty ProviderConfig.MetadataPath
|
||||||
|
// (feature v0.23) that does not start with "/" (review fix, folded
|
||||||
|
// minor) — fetchModelMetadata (provider_openai.go) builds its request
|
||||||
|
// URL by plain string concatenation, a.baseURL+a.metadataPath; a value
|
||||||
|
// missing the leading slash would silently glue onto baseURL's own path
|
||||||
|
// with no separator (e.g. "http://host:1234api/v0/models"), a malformed
|
||||||
|
// URL that only surfaces as a confusing runtime fetch failure instead of
|
||||||
|
// a clear, named construction error. Empty (the default, metadata
|
||||||
|
// capture disabled) is always valid.
|
||||||
|
func validateMetadataPath(providerName, metadataPath string) error {
|
||||||
|
if metadataPath == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(metadataPath, "/") {
|
||||||
|
return fmt.Errorf("llmgateway: provider %q: metadataPath %q must start with \"/\"", providerName, metadataPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// buildAdapters resolves cfg.Providers into a map of providerAdapter keyed
|
// buildAdapters resolves cfg.Providers into a map of providerAdapter keyed
|
||||||
// by provider name. Every provider name is validated (validateConfigName)
|
// by provider name. Every provider name is validated (validateConfigName)
|
||||||
// and every ProviderConfig value must be non-nil before anything else runs,
|
// and every ProviderConfig value must be non-nil before anything else runs,
|
||||||
@@ -413,6 +432,10 @@ func buildAdapters(cfg *Config) (map[string]providerAdapter, error) {
|
|||||||
base = defaultBaseURLByType[pc.Type]
|
base = defaultBaseURLByType[pc.Type]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := validateMetadataPath(name, pc.MetadataPath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
switch pc.Type {
|
switch pc.Type {
|
||||||
case providerTypeOpenAI:
|
case providerTypeOpenAI:
|
||||||
a := newOpenAIAdapter(name, base, apiKey)
|
a := newOpenAIAdapter(name, base, apiKey)
|
||||||
|
|||||||
Reference in New Issue
Block a user