@@ -53,140 +53,78 @@ func SetPromsafeTag(tag string) {
5353 promsafeTag = tag
5454}
5555
56- // labelProviderMarker is a marker interface for enforcing type-safety.
57- // With its help we can force our label-related functions to only accept SingleLabelProvider or StructLabelProvider.
58- type labelProviderMarker interface {
59- marker ()
60- }
61-
62- // SingleLabelProvider is a type used for declaring a single label.
63- // When used as labelProviderMarker it provides just a label name.
64- // It's meant to be used with single-label metrics only!
65- // Use StructLabelProvider for multi-label metrics.
66- type SingleLabelProvider string
67-
68- var _ labelProviderMarker = SingleLabelProvider ("" )
69-
70- func (s SingleLabelProvider ) marker () {
71- panic ("marker interface method should never be called" )
56+ // labelsProviderMarker is a marker interface for enforcing type-safety of StructLabelProvider.
57+ type labelsProviderMarker interface {
58+ labelsProviderMarker ()
7259}
7360
7461// StructLabelProvider should be embedded in any struct that serves as a label provider.
7562type StructLabelProvider struct {}
7663
77- var _ labelProviderMarker = (* StructLabelProvider )(nil )
78-
79- func (s StructLabelProvider ) marker () {
80- panic ("marker interface method should never be called" )
81- }
82-
83- // handler is a helper struct that helps us to handle type-safe labels
84- // It holds a label name in case if it's the only label (when SingleLabelProvider is used).
85- type handler [T labelProviderMarker ] struct {
86- theOnlyLabelName string
87- }
88-
89- func newHandler [T labelProviderMarker ](labelProvider T ) handler [T ] {
90- var h handler [T ]
91- if s , ok := any (labelProvider ).(SingleLabelProvider ); ok {
92- h .theOnlyLabelName = string (s )
93- }
94- return h
95- }
96-
97- // extractLabelsWithValues extracts labels names+values from a given labelProviderMarker (SingleLabelProvider or StructLabelProvider)
98- func (h handler [T ]) extractLabels (labelProvider T ) []string {
99- if any (labelProvider ) == nil {
100- return nil
101- }
102- if s , ok := any (labelProvider ).(SingleLabelProvider ); ok {
103- return []string {string (s )}
104- }
64+ var _ labelsProviderMarker = (* StructLabelProvider )(nil )
10565
106- // Here, then, it can be only a struct, that is a parent of StructLabelProvider
107- labels := extractLabelFromStruct (labelProvider )
108- labelNames := make ([]string , 0 , len (labels ))
109- for k := range labels {
110- labelNames = append (labelNames , k )
111- }
112- return labelNames
66+ func (s StructLabelProvider ) labelsProviderMarker () {
67+ panic ("labelsProviderMarker interface method should never be called" )
11368}
11469
115- // extractLabelsWithValues extracts labels names+values from a given labelProviderMarker (SingleLabelProvider or StructLabelProvider)
116- func (h handler [T ]) extractLabelsWithValues (labelProvider T ) prometheus.Labels {
117- if any (labelProvider ) == nil {
118- return nil
119- }
120-
121- // TODO: let's handle defaults as well, why not?
70+ // newEmptyLabels creates a new empty labels instance of type T
71+ // It's a bit tricky as we want to support both structs and pointers to structs
72+ // e.g. &MyLabels{StructLabelProvider} or MyLabels{StructLabelProvider}
73+ func newEmptyLabels [T labelsProviderMarker ]() T {
74+ var emptyLabels T
12275
123- if s , ok := any (labelProvider ).(SingleLabelProvider ); ok {
124- return prometheus.Labels {h .theOnlyLabelName : string (s )}
76+ // Let's Support both Structs or Pointer to Structs given as T
77+ val := reflect .ValueOf (& emptyLabels ).Elem ()
78+ if val .Kind () == reflect .Ptr {
79+ val .Set (reflect .New (val .Type ().Elem ()))
12580 }
12681
127- // Here, then, it can be only a struct, that is a parent of StructLabelProvider
128- return extractLabelFromStruct (labelProvider )
129- }
130-
131- // extractLabelValues extracts label string values from a given labelProviderMarker (SingleLabelProvider or StructLabelProvider)
132- func (h handler [T ]) extractLabelValues (labelProvider T ) []string {
133- m := h .extractLabelsWithValues (labelProvider )
134-
135- labelValues := make ([]string , 0 , len (m ))
136- for _ , v := range m {
137- labelValues = append (labelValues , v )
138- }
139- return labelValues
82+ return emptyLabels
14083}
14184
14285// NewCounterVecT creates a new CounterVecT with type-safe labels.
143- func NewCounterVecT [T labelProviderMarker ](opts prometheus.CounterOpts , labels T ) * CounterVecT [T ] {
144- h := newHandler ( labels )
86+ func NewCounterVecT [T labelsProviderMarker ](opts prometheus.CounterOpts ) * CounterVecT [T ] {
87+ emptyLabels := newEmptyLabels [ T ]( )
14588
14689 var inner * prometheus.CounterVec
147-
14890 if factory != nil {
149- inner = factory .NewCounterVec (opts , h . extractLabels ( labels ))
91+ inner = factory .NewCounterVec (opts , extractLabelNames ( emptyLabels ))
15092 } else {
151- inner = prometheus .NewCounterVec (opts , h . extractLabels ( labels ))
93+ inner = prometheus .NewCounterVec (opts , extractLabelNames ( emptyLabels ))
15294 }
15395
154- return & CounterVecT [T ]{
155- handler : h ,
156- inner : inner ,
157- }
96+ return & CounterVecT [T ]{inner : inner }
15897}
15998
160- // CounterVecT is a wrapper around prometheus.CounterVecT that allows type-safe labels.
161- type CounterVecT [T labelProviderMarker ] struct {
162- handler [T ]
99+ // CounterVecT is a wrapper around prometheus.CounterVec that allows type-safe labels.
100+ type CounterVecT [T labelsProviderMarker ] struct {
163101 inner * prometheus.CounterVec
164102}
165103
166104// GetMetricWithLabelValues behaves like prometheus.CounterVec.GetMetricWithLabelValues but with type-safe labels.
167105func (c * CounterVecT [T ]) GetMetricWithLabelValues (labels T ) (prometheus.Counter , error ) {
168- return c .inner .GetMetricWithLabelValues (c . handler . extractLabelValues (labels )... )
106+ return c .inner .GetMetricWithLabelValues (extractLabelValues (labels )... )
169107}
170108
171109// GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
172110func (c * CounterVecT [T ]) GetMetricWith (labels T ) (prometheus.Counter , error ) {
173- return c .inner .GetMetricWith (c . handler . extractLabelsWithValues (labels ))
111+ return c .inner .GetMetricWith (extractLabelsWithValues (labels ))
174112}
175113
176114// WithLabelValues behaves like prometheus.CounterVec.WithLabelValues but with type-safe labels.
177115func (c * CounterVecT [T ]) WithLabelValues (labels T ) prometheus.Counter {
178- return c .inner .WithLabelValues (c . handler . extractLabelValues (labels )... )
116+ return c .inner .WithLabelValues (extractLabelValues (labels )... )
179117}
180118
181119// With behaves like prometheus.CounterVec.With but with type-safe labels.
182120func (c * CounterVecT [T ]) With (labels T ) prometheus.Counter {
183- return c .inner .With (c . handler . extractLabelsWithValues (labels ))
121+ return c .inner .With (extractLabelsWithValues (labels ))
184122}
185123
186124// CurryWith behaves like prometheus.CounterVec.CurryWith but with type-safe labels.
187125// It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
188126func (c * CounterVecT [T ]) CurryWith (labels T ) (* CounterVecT [T ], error ) {
189- curriedInner , err := c .inner .CurryWith (c . handler . extractLabelsWithValues (labels ))
127+ curriedInner , err := c .inner .CurryWith (extractLabelsWithValues (labels ))
190128 if err != nil {
191129 return nil , err
192130 }
@@ -197,7 +135,7 @@ func (c *CounterVecT[T]) CurryWith(labels T) (*CounterVecT[T], error) {
197135// MustCurryWith behaves like prometheus.CounterVec.MustCurryWith but with type-safe labels.
198136// It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
199137func (c * CounterVecT [T ]) MustCurryWith (labels T ) * CounterVecT [T ] {
200- c .inner = c .inner .MustCurryWith (c . handler . extractLabelsWithValues (labels ))
138+ c .inner = c .inner .MustCurryWith (extractLabelsWithValues (labels ))
201139 return c
202140}
203141
@@ -221,24 +159,111 @@ func NewCounterFuncT(opts prometheus.CounterOpts, function func() float64) prome
221159 return prometheus .NewCounterFunc (opts , function )
222160}
223161
162+ //
163+ // Shorthand for Metrics with a single label
164+ //
165+
166+ // singleLabelProviderMarker is a marker interface for enforcing type-safety of SingleLabelProvider.
167+ type singleLabelProviderMarker interface {
168+ singleLabelProviderMarker ()
169+ }
170+
171+ // SingleLabelProvider is a type used for declaring a single label only.
172+ // When declaring a metric it's values used as a label name
173+ // When calling With() it's values used as a label value
174+ type SingleLabelProvider string
175+
176+ var _ singleLabelProviderMarker = SingleLabelProvider ("" )
177+
178+ func (s SingleLabelProvider ) singleLabelProviderMarker () {
179+ panic ("singleLabelProviderMarker interface method should never be called" )
180+ }
181+
182+ // NewCounterVecT1 creates a new CounterVecT with the only single label
183+ func NewCounterVecT1 (opts prometheus.CounterOpts , singleLabelProvider singleLabelProviderMarker ) * CounterVecT1 {
184+
185+ // labelName is the string itself
186+ // and singleLabelProviderMarker here can ONLY be SingleLabelProvider
187+ labelName := string (singleLabelProvider .(SingleLabelProvider ))
188+
189+ var inner * prometheus.CounterVec
190+ if factory != nil {
191+ inner = factory .NewCounterVec (opts , []string {labelName })
192+ } else {
193+ inner = prometheus .NewCounterVec (opts , []string {labelName })
194+ }
195+
196+ return & CounterVecT1 {inner : inner , labelName : labelName }
197+ }
198+
199+ // CounterVecT1 is a wrapper around prometheus.CounterVec that allows a single type-safe label.
200+ type CounterVecT1 struct {
201+ labelName string
202+ inner * prometheus.CounterVec
203+ }
204+
205+ // GetMetricWithLabelValues behaves like prometheus.CounterVec.GetMetricWithLabelValues but with type-safe labels.
206+ func (c * CounterVecT1 ) GetMetricWithLabelValues (labelValue string ) (prometheus.Counter , error ) {
207+ return c .inner .GetMetricWithLabelValues (labelValue )
208+ }
209+
210+ // GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
211+ func (c * CounterVecT1 ) GetMetricWith (labelValue string ) (prometheus.Counter , error ) {
212+ return c .inner .GetMetricWith (prometheus.Labels {c .labelName : labelValue })
213+ }
214+
215+ // WithLabelValues behaves like prometheus.CounterVec.WithLabelValues but with type-safe labels.
216+ func (c * CounterVecT1 ) WithLabelValues (labelValue string ) prometheus.Counter {
217+ return c .inner .WithLabelValues (labelValue )
218+ }
219+
220+ // With behaves like prometheus.CounterVec.With but with type-safe labels.
221+ func (c * CounterVecT1 ) With (labelValue string ) prometheus.Counter {
222+ return c .inner .With (prometheus.Labels {c .labelName : labelValue })
223+ }
224+
225+ // CurryWith behaves like prometheus.CounterVec.CurryWith but with type-safe labels.
226+ // It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
227+ func (c * CounterVecT1 ) CurryWith (labelValue string ) (* CounterVecT1 , error ) {
228+ curriedInner , err := c .inner .CurryWith (prometheus.Labels {c .labelName : labelValue })
229+ if err != nil {
230+ return nil , err
231+ }
232+ c .inner = curriedInner
233+ return c , nil
234+ }
235+
236+ // MustCurryWith behaves like prometheus.CounterVec.MustCurryWith but with type-safe labels.
237+ // It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
238+ func (c * CounterVecT1 ) MustCurryWith (labelValue string ) * CounterVecT1 {
239+ c .inner = c .inner .MustCurryWith (prometheus.Labels {c .labelName : labelValue })
240+ return c
241+ }
242+
243+ // Unsafe returns the underlying prometheus.CounterVec
244+ // it's used to call any other method of prometheus.CounterVec that doesn't require type-safe labels
245+ func (c * CounterVecT1 ) Unsafe () * prometheus.CounterVec {
246+ return c .inner
247+ }
248+
224249//
225250// Promauto compatibility
226251//
227252
228253// Factory is a promauto-like factory that allows type-safe labels.
229254// We have to duplicate promauto.Factory logic here, because promauto.Factory's registry is private.
230- type Factory [T labelProviderMarker ] struct {
255+ type Factory [T labelsProviderMarker ] struct {
231256 r prometheus.Registerer
232257}
233258
234259// WithAuto is a helper function that allows to use promauto.With with promsafe.With
235- func WithAuto (r prometheus.Registerer ) Factory [labelProviderMarker ] {
236- return Factory [labelProviderMarker ]{r : r }
260+ func WithAuto [ T labelsProviderMarker ] (r prometheus.Registerer ) Factory [T ] {
261+ return Factory [T ]{r : r }
237262}
238263
239264// NewCounterVecT works like promauto.NewCounterVec but with type-safe labels
240- func (f Factory [T ]) NewCounterVecT (opts prometheus.CounterOpts , labels T ) * CounterVecT [T ] {
241- c := NewCounterVecT (opts , labels )
265+ func (f Factory [T ]) NewCounterVecT (opts prometheus.CounterOpts ) * CounterVecT [T ] {
266+ c := NewCounterVecT [ T ] (opts )
242267 if f .r != nil {
243268 f .r .MustRegister (c .inner )
244269 }
@@ -257,10 +282,50 @@ func (f Factory[T]) NewCounterFuncT(opts prometheus.CounterOpts, function func()
257282 return promauto .With (f .r ).NewCounterFunc (opts , function )
258283}
259284
285+ // TODO: we can't use Factory with NewCounterT1. If we need, then we need a new type-less Factory
286+
260287//
261288// Helpers
262289//
263290
291+ // extractLabelsWithValues extracts labels names+values from a given labelsProviderMarker (parent instance of a StructLabelProvider)
292+ func extractLabelsWithValues (labelProvider labelsProviderMarker ) prometheus.Labels {
293+ if any (labelProvider ) == nil {
294+ return nil
295+ }
296+
297+ // TODO: let's handle defaults as well, why not?
298+
299+ // Here, then, it can be only a struct, that is a parent of StructLabelProvider
300+ return extractLabelFromStruct (labelProvider )
301+ }
302+
303+ // extractLabelValues extracts label string values from a given labelsProviderMarker (parent instance of aStructLabelProvider)
304+ func extractLabelValues (labelProvider labelsProviderMarker ) []string {
305+ m := extractLabelsWithValues (labelProvider )
306+
307+ labelValues := make ([]string , 0 , len (m ))
308+ for _ , v := range m {
309+ labelValues = append (labelValues , v )
310+ }
311+ return labelValues
312+ }
313+
314+ // extractLabelNames extracts labels names from a given labelsProviderMarker (parent instance of aStructLabelProvider)
315+ func extractLabelNames (labelProvider labelsProviderMarker ) []string {
316+ if any (labelProvider ) == nil {
317+ return nil
318+ }
319+
320+ // Here, then, it can be only a struct, that is a parent of StructLabelProvider
321+ labels := extractLabelFromStruct (labelProvider )
322+ labelNames := make ([]string , 0 , len (labels ))
323+ for k := range labels {
324+ labelNames = append (labelNames , k )
325+ }
326+ return labelNames
327+ }
328+
264329// extractLabelFromStruct extracts labels names+values from a given StructLabelProvider
265330func extractLabelFromStruct (structWithLabels any ) prometheus.Labels {
266331 labels := prometheus.Labels {}
0 commit comments