@@ -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- }
64+ var _ labelsProviderMarker = (* StructLabelProvider )(nil )
8265
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
66+ func (s StructLabelProvider ) labelsProviderMarker () {
67+ panic ("labelsProviderMarker interface method should never be called" )
8768}
8869
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- }
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
9675
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 )}
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 ()))
10480 }
10581
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
113- }
114-
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?
122-
123- if s , ok := any (labelProvider ).(SingleLabelProvider ); ok {
124- return prometheus.Labels {h .theOnlyLabelName : string (s )}
125- }
126-
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,110 @@ 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+ // labelName is the string itself
185+ // and singleLabelProviderMarker here can ONLY be SingleLabelProvider
186+ labelName := string (singleLabelProvider .(SingleLabelProvider ))
187+
188+ var inner * prometheus.CounterVec
189+ if factory != nil {
190+ inner = factory .NewCounterVec (opts , []string {labelName })
191+ } else {
192+ inner = prometheus .NewCounterVec (opts , []string {labelName })
193+ }
194+
195+ return & CounterVecT1 {inner : inner , labelName : labelName }
196+ }
197+
198+ // CounterVecT1 is a wrapper around prometheus.CounterVec that allows a single type-safe label.
199+ type CounterVecT1 struct {
200+ labelName string
201+ inner * prometheus.CounterVec
202+ }
203+
204+ // GetMetricWithLabelValues behaves like prometheus.CounterVec.GetMetricWithLabelValues but with type-safe labels.
205+ func (c * CounterVecT1 ) GetMetricWithLabelValues (labelValue string ) (prometheus.Counter , error ) {
206+ return c .inner .GetMetricWithLabelValues (labelValue )
207+ }
208+
209+ // GetMetricWith behaves like prometheus.CounterVec.GetMetricWith but with type-safe labels.
210+ func (c * CounterVecT1 ) GetMetricWith (labelValue string ) (prometheus.Counter , error ) {
211+ return c .inner .GetMetricWith (prometheus.Labels {c .labelName : labelValue })
212+ }
213+
214+ // WithLabelValues behaves like prometheus.CounterVec.WithLabelValues but with type-safe labels.
215+ func (c * CounterVecT1 ) WithLabelValues (labelValue string ) prometheus.Counter {
216+ return c .inner .WithLabelValues (labelValue )
217+ }
218+
219+ // With behaves like prometheus.CounterVec.With but with type-safe labels.
220+ func (c * CounterVecT1 ) With (labelValue string ) prometheus.Counter {
221+ return c .inner .With (prometheus.Labels {c .labelName : labelValue })
222+ }
223+
224+ // CurryWith behaves like prometheus.CounterVec.CurryWith but with type-safe labels.
225+ // It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
226+ func (c * CounterVecT1 ) CurryWith (labelValue string ) (* CounterVecT1 , error ) {
227+ curriedInner , err := c .inner .CurryWith (prometheus.Labels {c .labelName : labelValue })
228+ if err != nil {
229+ return nil , err
230+ }
231+ c .inner = curriedInner
232+ return c , nil
233+ }
234+
235+ // MustCurryWith behaves like prometheus.CounterVec.MustCurryWith but with type-safe labels.
236+ // It still returns a CounterVecT, but it's inner prometheus.CounterVec is curried.
237+ func (c * CounterVecT1 ) MustCurryWith (labelValue string ) * CounterVecT1 {
238+ c .inner = c .inner .MustCurryWith (prometheus.Labels {c .labelName : labelValue })
239+ return c
240+ }
241+
242+ // Unsafe returns the underlying prometheus.CounterVec
243+ // it's used to call any other method of prometheus.CounterVec that doesn't require type-safe labels
244+ func (c * CounterVecT1 ) Unsafe () * prometheus.CounterVec {
245+ return c .inner
246+ }
247+
224248//
225249// Promauto compatibility
226250//
227251
228252// Factory is a promauto-like factory that allows type-safe labels.
229253// We have to duplicate promauto.Factory logic here, because promauto.Factory's registry is private.
230- type Factory [T labelProviderMarker ] struct {
254+ type Factory [T labelsProviderMarker ] struct {
231255 r prometheus.Registerer
232256}
233257
234258// 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 }
259+ func WithAuto [ T labelsProviderMarker ] (r prometheus.Registerer ) Factory [T ] {
260+ return Factory [T ]{r : r }
237261}
238262
239263// 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 )
264+ func (f Factory [T ]) NewCounterVecT (opts prometheus.CounterOpts ) * CounterVecT [T ] {
265+ c := NewCounterVecT [ T ] (opts )
242266 if f .r != nil {
243267 f .r .MustRegister (c .inner )
244268 }
@@ -257,10 +281,50 @@ func (f Factory[T]) NewCounterFuncT(opts prometheus.CounterOpts, function func()
257281 return promauto .With (f .r ).NewCounterFunc (opts , function )
258282}
259283
284+ // TODO: we can't use Factory with NewCounterT1. If we need, then we need a new type-less Factory
285+
260286//
261287// Helpers
262288//
263289
290+ // extractLabelsWithValues extracts labels names+values from a given labelsProviderMarker (parent instance of a StructLabelProvider)
291+ func extractLabelsWithValues (labelProvider labelsProviderMarker ) prometheus.Labels {
292+ if any (labelProvider ) == nil {
293+ return nil
294+ }
295+
296+ // TODO: let's handle defaults as well, why not?
297+
298+ // Here, then, it can be only a struct, that is a parent of StructLabelProvider
299+ return extractLabelFromStruct (labelProvider )
300+ }
301+
302+ // extractLabelValues extracts label string values from a given labelsProviderMarker (parent instance of aStructLabelProvider)
303+ func extractLabelValues (labelProvider labelsProviderMarker ) []string {
304+ m := extractLabelsWithValues (labelProvider )
305+
306+ labelValues := make ([]string , 0 , len (m ))
307+ for _ , v := range m {
308+ labelValues = append (labelValues , v )
309+ }
310+ return labelValues
311+ }
312+
313+ // extractLabelNames extracts labels names from a given labelsProviderMarker (parent instance of aStructLabelProvider)
314+ func extractLabelNames (labelProvider labelsProviderMarker ) []string {
315+ if any (labelProvider ) == nil {
316+ return nil
317+ }
318+
319+ // Here, then, it can be only a struct, that is a parent of StructLabelProvider
320+ labels := extractLabelFromStruct (labelProvider )
321+ labelNames := make ([]string , 0 , len (labels ))
322+ for k := range labels {
323+ labelNames = append (labelNames , k )
324+ }
325+ return labelNames
326+ }
327+
264328// extractLabelFromStruct extracts labels names+values from a given StructLabelProvider
265329func extractLabelFromStruct (structWithLabels any ) prometheus.Labels {
266330 labels := prometheus.Labels {}
0 commit comments