@@ -528,20 +528,18 @@ func fastQuorum(responses *gorums.Responses[*Response]) (*Response, error) {
528528## Custom Return Types
529529
530530Gorums supports custom aggregation functions that return types different from the proto response type.
531- This is useful when you need to aggregate multiple responses into a summary or statistics object.
531+ This is useful when you need to aggregate multiple responses into a summary, statistics object, or any other custom type .
532532
533- ### Example: Collecting Multiple Responses
533+ ### Recommended Pattern: Functions Taking ` * Responses[Resp] `
534534
535- Consider a ` StopBenchmark` RPC that returns ` *MemoryStat` from each node, but you want to return ` *MemoryStatList` containing all stats:
535+ The recommended approach is to define functions that accept ` *Responses[Resp]` directly.
536+ This gives you full access to all iterator methods (` IgnoreErrors ()` , ` Filter ()` , ` CollectN ()` , ` CollectAll ()` ) and the ability to return any type.
536537
537538` ` ` go
538- // Proto definitions:
539- // rpc StopBenchmark(StopRequest) returns (MemoryStat) { option (gorums.quorumcall) = true; }
540- // message MemoryStat { uint64 allocs = 1; uint64 memory = 2; }
541- // message MemoryStatList { repeated MemoryStat memory_stats = 1; }
542-
543- // Custom aggregation function with different return type
544- func StopBenchmarkQF (replies map [uint32 ]*MemoryStat) (*MemoryStatList, error ) {
539+ // Custom aggregation function that returns a different type
540+ // Input: *Responses[*MemoryStat], Output: *MemoryStatList
541+ func CollectStats (resp *gorums.Responses [*MemoryStat]) (*MemoryStatList, error ) {
542+ replies := resp.IgnoreErrors ().CollectAll ()
545543 if len (replies) == 0 {
546544 return nil , gorums.ErrIncomplete
547545 }
@@ -550,36 +548,121 @@ func StopBenchmarkQF(replies map[uint32]*MemoryStat) (*MemoryStatList, error) {
550548 }, nil
551549}
552550
553- // Usage: Two-step pattern
551+ // Usage: Call the function directly, passing the Responses object
554552cfgCtx := config.Context (ctx)
555- responses := StopBenchmark (cfgCtx, &StopRequest{})
556- replies := responses.Seq ().IgnoreErrors ().CollectAll () // map[uint32]*MemoryStat
557- memStats , err := StopBenchmarkQF (replies) // Returns *MemoryStatList
553+ memStats , err := CollectStats (StopBenchmark (cfgCtx, &StopRequest{}))
554+ ` ` `
555+
556+ ### Example: Same Type Aggregation
557+
558+ When the return type matches the response type, you can still use this pattern for custom quorum logic:
559+
560+ ` ` ` go
561+ // Custom majority quorum with validation
562+ func ValidatedMajority (resp *gorums.Responses [*State]) (*State, error ) {
563+ replies := resp.IgnoreErrors ().CollectN (resp.Size ()/2 + 1 )
564+ if len (replies) < resp.Size ()/2 +1 {
565+ return nil , gorums.ErrIncomplete
566+ }
567+ // Return the first valid reply
568+ for _ , r := range replies {
569+ if isValid (r) {
570+ return r, nil
571+ }
572+ }
573+ return nil , gorums.ErrIncomplete
574+ }
575+
576+ // Usage
577+ cfgCtx := config.Context (ctx)
578+ state , err := ValidatedMajority (ReadQC (cfgCtx, &ReadRequest{}))
579+ ` ` `
580+
581+ ### Example: Custom Return Type (Slice)
582+
583+ ` ` ` go
584+ // Collect all string values from responses
585+ func CollectAllValues (resp *gorums.Responses [*StringValue]) ([]string , error ) {
586+ replies := resp.IgnoreErrors ().CollectAll ()
587+ if len (replies) == 0 {
588+ return nil , gorums.ErrIncomplete
589+ }
590+ result := make ([]string , 0 , len (replies))
591+ for _ , v := range replies {
592+ result = append (result, v.GetValue ())
593+ }
594+ return result, nil
595+ }
596+
597+ // Usage: Returns []string instead of *StringValue
598+ values , err := CollectAllValues (GetValues (cfgCtx, &Request{}))
558599` ` `
559600
560601### Example: Computing Aggregate Statistics
561602
562603` ` ` go
563- // Aggregate results from multiple nodes
564- func AggregateResults (replies map [uint32 ]*Result) (*Result, error ) {
604+ // Aggregate results from multiple nodes into a summary
605+ func AggregateResults (resp *gorums.Responses [*Result]) (*Result, error ) {
606+ replies := resp.IgnoreErrors ().CollectAll ()
565607 if len (replies) == 0 {
566608 return nil , gorums.ErrIncomplete
567609 }
568610
569- resp := &Result{}
611+ summary := &Result{}
570612 for _ , reply := range replies {
571- resp .TotalOps += reply.TotalOps
572- resp .TotalTime += reply.TotalTime
573- resp .Throughput += reply.Throughput
613+ summary .TotalOps += reply.TotalOps
614+ summary .TotalTime += reply.TotalTime
615+ summary .Throughput += reply.Throughput
574616 }
575617
576618 // Calculate averages
577- numNodes := len (replies)
578- resp.TotalOps /= uint64 (numNodes)
579- resp.TotalTime /= int64 (numNodes)
580- resp.Throughput /= float64 (numNodes)
619+ n := uint64 (len (replies))
620+ summary.TotalOps /= n
621+ summary.TotalTime /= int64 (n)
622+ summary.Throughput /= float64 (n)
623+
624+ return summary, nil
625+ }
626+ ` ` `
627+
628+ ### Example: Returning a Primitive Type
629+
630+ ` ` ` go
631+ // Count responses from specific nodes
632+ func CountFromPrimaryNodes (resp *gorums.Responses [*Response]) (int , error ) {
633+ count := 0
634+ for r := range resp.IgnoreErrors ().Filter (func (nr gorums.NodeResponse [*Response]) bool {
635+ return isPrimaryNode (nr.NodeID )
636+ }) {
637+ count++
638+ }
639+ if count == 0 {
640+ return 0 , gorums.ErrIncomplete
641+ }
642+ return count, nil
643+ }
644+ ` ` `
581645
582- return resp, nil
646+ ### Example: Explicit Error Handling
647+
648+ When you need to handle errors from individual nodes explicitly:
649+
650+ ` ` ` go
651+ // Require all nodes to succeed
652+ func RequireAllSuccess (resp *gorums.Responses [*Response]) (*Response, error ) {
653+ var first *Response
654+ for r := range resp.Seq () {
655+ if r.Err != nil {
656+ return nil , r.Err // Fail fast on any error
657+ }
658+ if first == nil {
659+ first = r.Value
660+ }
661+ }
662+ if first == nil {
663+ return nil , gorums.ErrIncomplete
664+ }
665+ return first, nil
583666}
584667` ` `
585668
0 commit comments