balancer.NewCheckEngineWithBalancer   C
last analyzed

Complexity

Conditions 10

Size

Total Lines 69
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 49
nop 7
dl 0
loc 69
rs 5.869
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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:

Complexity

Complex classes like balancer.NewCheckEngineWithBalancer 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 balancer
2
3
import (
4
	"context"
5
	"fmt"
6
	"log/slog"
7
	"time"
8
9
	"google.golang.org/grpc"
10
	"google.golang.org/grpc/credentials"
11
	"google.golang.org/grpc/credentials/insecure"
12
13
	"github.com/Permify/permify/internal/config"
14
	"github.com/Permify/permify/internal/engines"
15
	"github.com/Permify/permify/internal/invoke"
16
	"github.com/Permify/permify/internal/storage"
17
	"github.com/Permify/permify/pkg/balancer"
18
	base "github.com/Permify/permify/pkg/pb/base/v1"
19
)
20
21
// Balancer is a wrapper around the balancer hash implementation that
22
type Balancer struct {
23
	schemaReader storage.SchemaReader
24
	checker      invoke.Check
25
	client       base.PermissionClient
26
}
27
28
// NewCheckEngineWithBalancer creates a new check engine with a load balancer.
29
// It takes a Check interface, SchemaReader, distributed config, gRPC config, and authn config as input.
30
// It returns a Check interface and an error if any.
31
func NewCheckEngineWithBalancer(
32
	ctx context.Context,
33
	checker invoke.Check,
34
	schemaReader storage.SchemaReader,
35
	no string,
36
	dst *config.Distributed,
37
	srv *config.GRPC,
38
	authn *config.Authn,
39
) (invoke.Check, error) {
40
	var (
41
		creds    credentials.TransportCredentials
42
		options  []grpc.DialOption
43
		isSecure bool
44
		err      error
45
	)
46
47
	// Set up TLS credentials if paths are provided
48
	if srv.TLSConfig.Enabled && srv.TLSConfig.CertPath != "" {
49
		isSecure = true
50
		creds, err = credentials.NewClientTLSFromFile(srv.TLSConfig.CertPath, no)
51
		if err != nil {
52
			return nil, fmt.Errorf("could not load TLS certificate: %w", err)
0 ignored issues
show
introduced by
unrecognized printf verb 'w'
Loading history...
53
		}
54
	} else {
55
		creds = insecure.NewCredentials()
56
	}
57
58
	bc := &balancer.Config{
59
		PartitionCount:    dst.PartitionCount,
60
		ReplicationFactor: dst.ReplicationFactor,
61
		Load:              dst.Load,
62
		PickerWidth:       dst.PickerWidth,
63
	}
64
65
	bcjson, err := bc.ServiceConfigJSON()
66
	if err != nil {
67
		return nil, err
68
	}
69
70
	// Append common options
71
	options = append(
72
		options,
73
		grpc.WithDefaultServiceConfig(bcjson),
74
		grpc.WithTransportCredentials(creds),
75
	)
76
77
	// Handle authentication if enabled
78
	if authn != nil && authn.Enabled {
79
		token, err := setupAuthn(ctx, authn)
80
		if err != nil {
81
			return nil, err
82
		}
83
		if isSecure {
84
			options = append(options, grpc.WithPerRPCCredentials(secureTokenCredentials{"authorization": "Bearer " + token}))
85
		} else {
86
			options = append(options, grpc.WithPerRPCCredentials(nonSecureTokenCredentials{"authorization": "Bearer " + token}))
87
		}
88
	}
89
90
	conn, err := grpc.NewClient(dst.Address, options...)
91
	if err != nil {
92
		return nil, err
93
	}
94
95
	return &Balancer{
96
		schemaReader: schemaReader,
97
		checker:      checker,
98
		client:       base.NewPermissionClient(conn),
99
	}, nil
100
}
101
102
// Check performs a permission check using the schema reader to obtain
103
// entity definitions, then distributes the request based on a generated key.
104
func (c *Balancer) Check(ctx context.Context, request *base.PermissionCheckRequest) (*base.PermissionCheckResponse, error) {
105
	// Fetch the EntityDefinition for the given tenant, entity type, and schema version.
106
	en, _, err := c.schemaReader.ReadEntityDefinition(ctx, request.GetTenantId(), request.GetEntity().GetType(), request.GetMetadata().GetSchemaVersion())
107
	if err != nil {
108
		slog.ErrorContext(ctx, err.Error())
109
		// If an error occurs while reading the entity definition, deny permission and return the error.
110
		return &base.PermissionCheckResponse{
111
			Can: base.CheckResult_CHECK_RESULT_DENIED,
112
			Metadata: &base.PermissionCheckResponseMetadata{
113
				CheckCount: 0,
114
			},
115
		}, err
116
	}
117
118
	isRelational := engines.IsRelational(en, request.GetPermission())
119
120
	// Add a timeout of 2 seconds to the context and also set the generated key as a value.
121
	withTimeout, cancel := context.WithTimeout(context.WithValue(ctx, balancer.Key, []byte(engines.GenerateKey(request, isRelational))), 4*time.Second)
122
	defer cancel()
123
124
	// Logging the intention to forward the request to the underlying client.
125
	slog.InfoContext(ctx, "Forwarding request with key to the underlying client")
126
127
	// Perform the actual permission check by making a call to the underlying client.
128
	response, err := c.client.Check(withTimeout, request)
129
	if err != nil {
130
		// Log the error and return it.
131
		slog.ErrorContext(ctx, err.Error())
132
		return &base.PermissionCheckResponse{
133
			Can: base.CheckResult_CHECK_RESULT_DENIED,
134
			Metadata: &base.PermissionCheckResponseMetadata{
135
				CheckCount: 0,
136
			},
137
		}, err
138
	}
139
140
	// Return the response received from the client.
141
	return response, nil
142
}
143