| Conditions | 12 |
| Total Lines | 124 |
| Code Lines | 73 |
| Lines | 0 |
| Ratio | 0 % |
| Changes | 0 | ||
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like servers.*PermissionServer.BulkCheck often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | package servers |
||
| 53 | func (r *PermissionServer) BulkCheck(ctx context.Context, request *v1.PermissionBulkCheckRequest) (*v1.PermissionBulkCheckResponse, error) { |
||
| 54 | // emptyResp is a default, empty response that we will return in case of an error or when the context is cancelled. |
||
| 55 | emptyResp := &v1.PermissionBulkCheckResponse{ |
||
| 56 | Results: make([]*v1.PermissionCheckResponse, 0), |
||
| 57 | } |
||
| 58 | |||
| 59 | ctx, span := internal.Tracer.Start(ctx, "permissions.bulk-check") |
||
| 60 | defer span.End() |
||
| 61 | |||
| 62 | // Validate tenant_id |
||
| 63 | if request.GetTenantId() == "" { |
||
| 64 | err := status.Error(GetStatus(nil), "tenant_id is required") |
||
| 65 | span.RecordError(err) |
||
| 66 | span.SetStatus(otelCodes.Error, err.Error()) |
||
| 67 | return nil, err |
||
| 68 | } |
||
| 69 | |||
| 70 | checkItems := request.GetItems() |
||
| 71 | |||
| 72 | // Validate number of requests |
||
| 73 | if len(checkItems) == 0 { |
||
| 74 | err := status.Error(GetStatus(nil), "at least one item is required") |
||
| 75 | span.RecordError(err) |
||
| 76 | span.SetStatus(otelCodes.Error, err.Error()) |
||
| 77 | return nil, err |
||
| 78 | } |
||
| 79 | |||
| 80 | if len(checkItems) > 100 { |
||
| 81 | err := status.Error(GetStatus(nil), "maximum 100 items allowed") |
||
| 82 | span.RecordError(err) |
||
| 83 | span.SetStatus(otelCodes.Error, err.Error()) |
||
| 84 | return nil, err |
||
| 85 | } |
||
| 86 | |||
| 87 | // Create a buffered channel for BulkPermissionCheckResponses. |
||
| 88 | // The buffer size is equal to the number of references in the entity. |
||
| 89 | type ResultChannel struct {int; *v1.PermissionCheckResponse} |
||
| 90 | resultChannel := make(chan ResultChannel, len(checkItems)) |
||
| 91 | |||
| 92 | // The WaitGroup and Mutex are used for synchronization. |
||
| 93 | var wg sync.WaitGroup |
||
| 94 | var mutex sync.Mutex |
||
| 95 | |||
| 96 | // Process each check request |
||
| 97 | for i, checkRequestItem := range checkItems { |
||
| 98 | wg.Add(1) |
||
| 99 | |||
| 100 | go func(index int, checkRequestItem *v1.PermissionBulkCheckRequestItem) { |
||
| 101 | defer wg.Done() |
||
| 102 | |||
| 103 | // Validate individual request |
||
| 104 | v := checkRequestItem.Validate() |
||
| 105 | if v != nil { |
||
| 106 | // Return error response for this check |
||
| 107 | resultChannel <- ResultChannel{ |
||
| 108 | index, |
||
| 109 | &v1.PermissionCheckResponse{ |
||
| 110 | Can: v1.CheckResult_CHECK_RESULT_DENIED, |
||
| 111 | Metadata: &v1.PermissionCheckResponseMetadata{ |
||
| 112 | CheckCount: 0, |
||
| 113 | }, |
||
| 114 | }, |
||
| 115 | } |
||
| 116 | return |
||
| 117 | } |
||
| 118 | |||
| 119 | // Perform the check using existing Check function |
||
| 120 | checkRequest := &v1.PermissionCheckRequest{ |
||
| 121 | TenantId: request.GetTenantId(), |
||
| 122 | Subject: checkRequestItem.GetSubject(), |
||
| 123 | Entity: checkRequestItem.GetEntity(), |
||
| 124 | Permission: checkRequestItem.GetPermission(), |
||
| 125 | Metadata: request.GetMetadata(), |
||
| 126 | Context: request.GetContext(), |
||
| 127 | Arguments: request.GetArguments(), |
||
| 128 | } |
||
| 129 | response, err := r.invoker.Check(ctx, checkRequest) |
||
| 130 | if err != nil { |
||
| 131 | // Log error but don't fail the entire bulk operation |
||
| 132 | slog.ErrorContext(ctx, "check failed in bulk operation", "error", err.Error(), "index", index) |
||
| 133 | resultChannel <- ResultChannel{ |
||
| 134 | index, |
||
| 135 | &v1.PermissionCheckResponse{ |
||
| 136 | Can: v1.CheckResult_CHECK_RESULT_DENIED, |
||
| 137 | Metadata: &v1.PermissionCheckResponseMetadata{ |
||
| 138 | CheckCount: 0, |
||
| 139 | }, |
||
| 140 | }, |
||
| 141 | } |
||
| 142 | return |
||
| 143 | } |
||
| 144 | |||
| 145 | resultChannel <- ResultChannel{index, response} |
||
| 146 | }(i, checkRequestItem) |
||
| 147 | } |
||
| 148 | |||
| 149 | // Once the function returns, we wait for all goroutines to finish, then close the resultChannel. |
||
| 150 | defer func() { |
||
| 151 | wg.Wait() |
||
| 152 | close(resultChannel) |
||
| 153 | }() |
||
| 154 | |||
| 155 | // We read the responses from the resultChannel. |
||
| 156 | // We expect as many responses as there are references in the entity. |
||
| 157 | results := make([]*v1.PermissionCheckResponse, len(request.GetItems())) |
||
| 158 | for range checkItems { |
||
| 159 | select { |
||
| 160 | // If we receive a response from the resultChannel, we check for errors. |
||
| 161 | case response := <-resultChannel: |
||
| 162 | // If there's no error, we add the result to our response's Results map. |
||
| 163 | // We use a mutex to safely update the map since multiple goroutines may be writing to it concurrently. |
||
| 164 | mutex.Lock() |
||
| 165 | results[response.int] = response.PermissionCheckResponse |
||
| 166 | mutex.Unlock() |
||
| 167 | |||
| 168 | // If the context is done (i.e., canceled or deadline exceeded), we return an empty response and an error. |
||
| 169 | case <-ctx.Done(): |
||
| 170 | return emptyResp, errors.New(v1.ErrorCode_ERROR_CODE_CANCELLED.String()) |
||
| 171 | } |
||
| 172 | } |
||
| 173 | |||
| 174 | return &v1.PermissionBulkCheckResponse{ |
||
| 175 | Results: results, |
||
| 176 | }, nil |
||
| 177 | } |
||
| 283 |