diff --git a/go.mod b/go.mod index e1e357c2..2db4b15c 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/prometheus/client_golang v1.20.4 github.com/rs/cors v1.11.1 github.com/sapcc/go-api-declarations v1.12.6 - github.com/sapcc/go-bits v0.0.0-20240923114016-cdf67f5b7651 + github.com/sapcc/go-bits v0.0.0-20240925150026-37945f71a03e go.uber.org/automaxprocs v1.6.0 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 59212da1..17b231c1 100644 --- a/go.sum +++ b/go.sum @@ -152,8 +152,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/sapcc/go-api-declarations v1.12.6 h1:04sQmS3ipzPc+ENSCWW9A4FgCN7Qa5JIbNUNh7ziQJA= github.com/sapcc/go-api-declarations v1.12.6/go.mod h1:83R3hTANhuRXt/pXDby37IJetw8l7DG41s33Tp9NXxI= -github.com/sapcc/go-bits v0.0.0-20240923114016-cdf67f5b7651 h1:FobXJ2jpJdyybu0NsqPE2Rxn8FVW7ynmIQCMgfF1lck= -github.com/sapcc/go-bits v0.0.0-20240923114016-cdf67f5b7651/go.mod h1:80FgmJ7WhHF837BcfKnTiic5IxIt0XopzIZwtNJfBSs= +github.com/sapcc/go-bits v0.0.0-20240925150026-37945f71a03e h1:HJRF3mL9TnBtF0x4oNb3+F9BC9H2Ez7lxKqaVqVBqgk= +github.com/sapcc/go-bits v0.0.0-20240925150026-37945f71a03e/go.mod h1:mDSCc/u8EOqT79GNcSug5G25/Tt3PfWItGZCtdLvDQo= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= diff --git a/vendor/github.com/sapcc/go-bits/regexpext/configset.go b/vendor/github.com/sapcc/go-bits/regexpext/configset.go new file mode 100644 index 00000000..f3ba976d --- /dev/null +++ b/vendor/github.com/sapcc/go-bits/regexpext/configset.go @@ -0,0 +1,111 @@ +/******************************************************************************* +* +* Copyright 2024 SAP SE +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You should have received a copy of the License along with this +* program. If not, 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 regexpext + +// ConfigSet works similar to map[K]V in that it picks values of type V for +// keys of type K, but the keys in the data structure are actually regexes that +// can apply to an entire set of K instead of just one specific value of K. +// +// This type is intended to use in configuration, as a substitute for map[K]V +// that satisfies the DRY rule (Don't Repeat Yourself), or when the full set of +// relevant keys is not known at config editing time. +type ConfigSet[K ~string, V any] []struct { + Key BoundedRegexp `json:"key" yaml:"key"` + Value V `json:"value" yaml:"value"` +} + +// The basis for both Pick and PickAndFill. This uses MatchString to leverage +// the specific optimizations in type BoundedRegexp for this function. +func (cs ConfigSet[K, V]) pick(key K) (BoundedRegexp, V, bool) { + for _, entry := range cs { + if entry.Key.MatchString(string(key)) { + return entry.Key, entry.Value, true + } + } + var zero V + return "", zero, false +} + +// Pick returns the first value entry whose key regex matches the supplied key, or +// the given default value if none of the entries in the ConfigSet matches the key. +func (cs ConfigSet[K, V]) Pick(key K, defaultValue V) V { + _, value, ok := cs.pick(key) + if ok { + return value + } else { + return defaultValue + } +} + +// PickAndFill is like Pick, but if the regex in the matching entry contains +// parenthesized subexpressions (also known as capture groups), the fill +// callback is used to expand references to the captured texts in the value. +// +// Usage in code looks like this: +// +// type ObjectType string +// type MetricSource struct { +// PrometheusQuery string +// } +// var cs ConfigSet[ObjectType, MetricSource] +// // omitted: fill `cs` by parsing configuration +// +// objectName := "foo_widget" +// metricSource := cs.PickAndFill(objectName, MetricSource{}, func(ms *MetricSource, expand func(string) string) { +// ms.PrometheusQuery = expand(ms.PrometheusQuery) +// }) +// +// With this, configuration can be condensed like in the example below: +// +// originalConfig := ConfigSet[ObjectType, MetricSource]{ +// { Key: "foo_widget", Value: MetricSource{PrometheusQuery: "count(foo_widgets)"} }, +// { Key: "bar_widget", Value: MetricSource{PrometheusQuery: "count(bar_widgets)"} }, +// { Key: "qux_widget", Value: MetricSource{PrometheusQuery: "count(qux_widgets)"} }, +// } +// condensedConfig := ConfigSet[ObjectType, MetricSource]{ +// { Key: "(foo|bar|qux)_widget", Value: MetricSource{PrometheusQuery: "count(${1}_widgets)"} }, +// } +// +// Expansion follows the same rules as for regexp.ExpandString() from the standard library. +func (cs ConfigSet[K, V]) PickAndFill(key K, defaultValue V, fill func(value *V, expand func(string) string)) V { + keyRx, value, ok := cs.pick(key) + if !ok { + return defaultValue + } + + rx, err := keyRx.Regexp() + if err != nil { + // defense in depth: this should not happen because the regex should have been validated at UnmarshalYAML time + return defaultValue + } + match := rx.FindStringSubmatchIndex(string(key)) + if match == nil { + // defense in depth: this should not happen because this is only called after the key has already matched + return defaultValue + } + + // match[0] always exists and refers to the full match; if there are capture groups, they are in match[1:] + if len(match) > 1 { + fill(&value, func(in string) string { + return string(rx.ExpandString(nil, in, string(key), match)) + }) + } + return value +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3d8f68ad..f4237e65 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -112,7 +112,7 @@ github.com/rs/cors/internal ## explicit; go 1.21 github.com/sapcc/go-api-declarations/bininfo github.com/sapcc/go-api-declarations/deployevent -# github.com/sapcc/go-bits v0.0.0-20240923114016-cdf67f5b7651 +# github.com/sapcc/go-bits v0.0.0-20240925150026-37945f71a03e ## explicit; go 1.23 github.com/sapcc/go-bits/assert github.com/sapcc/go-bits/easypg