@@ -145,30 +145,21 @@ func cacheableAggregateSize(sizer AggregateSizer) bool {
145145 return ok && ! status .aggregateSizeLimitExceeded ()
146146}
147147
148- // AggregateSizeEstimate captures the outcome of an aggregate size computation.
148+ // ApproximateAggregateSize captures the outcome of an aggregate size computation.
149149//
150150// The Size saturates at math.MaxUint32 when the accumulated element count overflows uint32.
151151// LimitExceeded reports the computation was aborted because the value was too expensive to
152152// traverse (too deep, or too many nodes visited); in that case Size is also math.MaxUint32,
153153// but the value's true size may be smaller — the two conditions are distinguishable by the flag.
154- type AggregateSizeEstimate struct {
154+ type ApproximateAggregateSize struct {
155155 Size uint32
156156 LimitExceeded bool
157157}
158158
159- // AggregateSize returns the size of the input value, if known.
160- // Otherwise, a unit size of 1 is returned.
161- //
162- // When the calculator's depth or traversal limits are exceeded, the size saturates to
163- // math.MaxUint32. Use EstimateAggregateSize to distinguish limit-exceeded results from
164- // genuine uint32 saturation.
165- func (s * SizeCalculator ) AggregateSize (val any ) uint32 {
166- return s .EstimateAggregateSize (val ).Size
167- }
168159
169- // EstimateAggregateSize returns the aggregate size of the input value along with an indication
160+ // ApproximateAggregateSize returns the aggregate size of the input value along with an indication
170161// of whether the computation was aborted due to the calculator's depth or traversal limits.
171- func (s * SizeCalculator ) EstimateAggregateSize (val any ) AggregateSizeEstimate {
162+ func (s * SizeCalculator ) ApproximateAggregateSize (val any ) ApproximateAggregateSize {
172163 ctx := sizeContextPool .Get ().(* sizeContext )
173164 ctx .calc = s
174165 est := ctx .estimateAggregateSize (val )
@@ -178,6 +169,11 @@ func (s *SizeCalculator) EstimateAggregateSize(val any) AggregateSizeEstimate {
178169 return est
179170}
180171
172+ // AggregateSize implements the AggregateSizer interface by returning the computed size.
173+ func (s * SizeCalculator ) AggregateSize (val any ) uint32 {
174+ return s .ApproximateAggregateSize (val ).Size
175+ }
176+
181177// stringSize converts a character or byte length to an element count where stringUnitLength
182178// characters count as a single element, rounding up with a minimum size of 1.
183179func (s * SizeCalculator ) stringSize (length int ) uint32 {
@@ -265,13 +261,13 @@ func (c *sizeContext) aggregateSize(val any) uint32 {
265261}
266262
267263// estimateAggregateSize evaluates the aggregate size using this context and resets state.
268- func (c * sizeContext ) estimateAggregateSize (val any ) AggregateSizeEstimate {
264+ func (c * sizeContext ) estimateAggregateSize (val any ) ApproximateAggregateSize {
269265 c .depth = 0
270266 c .traversalCount = 0
271267 c .limitExceeded = false
272268
273269 size := c .AggregateSize (val )
274- return AggregateSizeEstimate {Size : size , LimitExceeded : c .limitExceeded }
270+ return ApproximateAggregateSize {Size : size , LimitExceeded : c .limitExceeded }
275271}
276272
277273func getProtoValueAggregateSize (c * sizeContext , v protoreflect.Value ) uint32 {
@@ -385,24 +381,19 @@ func getReflectValueAggregateSize(c *sizeContext, fieldVal reflect.Value) uint32
385381 }
386382 total := safeAddUint32 (1 , safeUint32FromInt (fieldVal .Len ()))
387383 switch elemType .Kind () {
388- case reflect .String :
384+ case reflect .String , reflect . Struct , reflect . Pointer , reflect . Slice , reflect . Array , reflect . Map , reflect . Interface :
389385 total = 1
390386 for i := 0 ; i < fieldVal .Len (); i ++ {
391- total = safeAddUint32 (total , c .AggregateSize (fieldVal .Index (i ).String ()))
392- }
393- case reflect .Struct , reflect .Pointer , reflect .Slice , reflect .Array , reflect .Map , reflect .Interface :
394- total = 1
395- for i := 0 ; i < fieldVal .Len (); i ++ {
396- total = safeAddUint32 (total , c .AggregateSize (fieldVal .Index (i ).Interface ()))
387+ total = safeAddUint32 (total , c .AggregateSize (fieldVal .Index (i )))
397388 }
398389 }
399390 return total
400391 case reflect .Map :
401392 total := uint32 (1 )
402393 iter := fieldVal .MapRange ()
403394 for iter .Next () {
404- total = safeAddUint32 (total , c .AggregateSize (iter .Key (). Interface () ))
405- total = safeAddUint32 (total , c .AggregateSize (iter .Value (). Interface () ))
395+ total = safeAddUint32 (total , c .AggregateSize (iter .Key ()))
396+ total = safeAddUint32 (total , c .AggregateSize (iter .Value ()))
406397 }
407398 return total
408399 case reflect .Pointer , reflect .Interface :
@@ -431,25 +422,45 @@ func getReflectValueAggregateSize(c *sizeContext, fieldVal reflect.Value) uint32
431422 if ! fVal .IsValid () || fVal .IsZero () {
432423 continue
433424 }
434- total = safeAddUint32 (total , c .AggregateSize (fVal . Interface () ))
425+ total = safeAddUint32 (total , c .AggregateSize (fVal ))
435426 }
436427 return total
437428 default :
438429 return 1
439430 }
440431}
441432
433+ var (
434+ aggregateSizeVisitorType = reflect .TypeFor [AggregateSizeVisitor ]()
435+ sizerType = reflect .TypeFor [traits.Sizer ]()
436+ protoMessageType = reflect .TypeFor [proto.Message ]()
437+ )
438+
442439func checkCustomSizer (c * sizeContext , fieldVal reflect.Value ) (uint32 , bool ) {
443- if ! fieldVal .CanInterface () {
440+ if ! fieldVal .IsValid () || ! fieldVal . CanInterface () {
444441 return 0 , false
445442 }
446-
447- switch sizer := fieldVal .Interface ().(type ) {
448- case AggregateSizeVisitor :
449- return sizer .AggregateSize (c ), true
450- case traits.Sizer :
451- return safeUint32FromBoxedInt (sizer .Size ().(Int )), true
452- default :
453- return 0 , false
443+ t := fieldVal .Type ()
444+ if t .Implements (aggregateSizeVisitorType ) {
445+ if sizer , ok := fieldVal .Interface ().(AggregateSizeVisitor ); ok {
446+ return sizer .AggregateSize (c ), true
447+ }
448+ }
449+ if t .Implements (sizerType ) {
450+ if sizer , ok := fieldVal .Interface ().(traits.Sizer ); ok {
451+ return safeUint32FromBoxedInt (sizer .Size ().(Int )), true
452+ }
453+ }
454+ if t .Implements (protoMessageType ) {
455+ if fieldVal .Kind () == reflect .Pointer && fieldVal .IsNil () {
456+ return 0 , true
457+ }
458+ if sizer , ok := fieldVal .Interface ().(proto.Message ); ok {
459+ if sizer == nil {
460+ return 0 , true
461+ }
462+ return getProtoMessageAggregateSize (c , sizer .ProtoReflect ()), true
463+ }
454464 }
465+ return 0 , false
455466}
0 commit comments