Passed
Pull Request — master (#1080)
by Tolga
02:16
created

attribute.EntityAndAttributeToString   A

Complexity

Conditions 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nop 2
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
package attribute
2
3
import (
4
	"errors"
5
	"fmt"
6
	"strconv"
7
	"strings"
8
9
	"google.golang.org/protobuf/proto"
10
	"google.golang.org/protobuf/types/known/anypb"
11
12
	base "github.com/Permify/permify/pkg/pb/base/v1"
13
)
14
15
const (
16
	ENTITY    = "%s:%s"
17
	ATTRIBUTE = "$%s"
18
	VALUE     = "%s:%s"
19
)
20
21
// Attribute function takes a string representation of an attribute and converts it back into the Attribute object.
22
func Attribute(attribute string) (*base.Attribute, error) {
23
	// Splitting the attribute string by "@" delimiter
24
	s := strings.Split(strings.TrimSpace(attribute), "|")
25
	if len(s) != 2 || s[0] == "" || s[1] == "" {
26
		// The attribute string should have exactly two parts
27
		return nil, ErrInvalidAttribute
28
	}
29
30
	// Splitting the entity part of the string by "#" delimiter
31
	e := strings.Split(s[0], "$")
32
	if len(e) != 2 || e[0] == "" || e[1] == "" {
33
		// The entity string should have exactly two parts
34
		return nil, ErrInvalidEntity
35
	}
36
37
	// Splitting the entity type and id by ":" delimiter
38
	et := strings.Split(e[0], ":")
39
	if len(et) != 2 || et[0] == "" || et[1] == "" {
40
		// The entity type and id should have exactly two parts
41
		return nil, ErrInvalidAttribute
42
	}
43
44
	// Splitting the attribute value part of the string by ":" delimiter
45
	v := strings.Split(s[1], ":")
46
	if len(v) != 2 || v[0] == "" || v[1] == "" {
47
		// The attribute value string should have exactly two parts
48
		return nil, ErrInvalidAttribute
49
	}
50
51
	// Declare a proto message to hold the attribute value
52
	var wrapped proto.Message
53
54
	// Parse the attribute value based on its type
55
	switch v[0] {
56
	case "boolean":
57
		boolVal, err := strconv.ParseBool(v[1])
58
		if err != nil {
59
			return nil, fmt.Errorf("failed to parse boolean: %v", err)
60
		}
61
		wrapped = &base.BooleanValue{Data: boolVal}
62
	case "boolean[]":
63
		var ba []bool
64
		val := strings.Split(v[1], ",")
65
		for _, value := range val {
66
			boolVal, err := strconv.ParseBool(value)
67
			if err != nil {
68
				return nil, fmt.Errorf("failed to parse boolean: %v", err)
69
			}
70
			ba = append(ba, boolVal)
71
		}
72
		wrapped = &base.BooleanArrayValue{Data: ba}
73
	case "string":
74
		wrapped = &base.StringValue{Data: v[1]}
75
	case "string[]":
76
		var sa []string
77
		val := strings.Split(v[1], ",")
78
		for _, value := range val {
79
			sa = append(sa, value)
80
		}
81
		wrapped = &base.StringArrayValue{Data: sa}
82
	case "double":
83
		doubleVal, err := strconv.ParseFloat(v[1], 64)
84
		if err != nil {
85
			return nil, fmt.Errorf("failed to parse float: %v", err)
86
		}
87
		wrapped = &base.DoubleValue{Data: doubleVal}
88
	case "double[]":
89
		var da []float64
90
		val := strings.Split(v[1], ",")
91
		for _, value := range val {
92
			doubleVal, err := strconv.ParseFloat(value, 64)
93
			if err != nil {
94
				return nil, fmt.Errorf("failed to parse float: %v", err)
95
			}
96
			da = append(da, doubleVal)
97
		}
98
		wrapped = &base.DoubleArrayValue{Data: da}
99
	case "integer":
100
		intVal, err := strconv.ParseInt(v[1], 10, 32)
101
		if err != nil {
102
			return nil, fmt.Errorf("failed to parse integer: %v", err)
103
		}
104
		wrapped = &base.IntegerValue{Data: int32(intVal)}
105
	case "integer[]":
106
107
		var ia []int32
108
		val := strings.Split(v[1], ",")
109
		for _, value := range val {
110
			intVal, err := strconv.ParseInt(value, 10, 32)
111
			if err != nil {
112
				return nil, fmt.Errorf("failed to parse integer: %v", err)
113
			}
114
			ia = append(ia, int32(intVal))
115
		}
116
		wrapped = &base.IntegerArrayValue{Data: ia}
117
	default:
118
		return nil, ErrInvalidValue
119
	}
120
121
	// Convert the wrapped attribute value into Any proto message
122
	value, err := anypb.New(wrapped)
123
	if err != nil {
124
		return nil, err
125
	}
126
127
	// Return the attribute object
128
	return &base.Attribute{
129
		Entity: &base.Entity{
130
			Type: et[0],
131
			Id:   et[1],
132
		},
133
		Attribute: e[1],
134
		Value:     value,
135
	}, nil
136
}
137
138
// ToString function takes an Attribute object and converts it into a string.
139
func ToString(attribute *base.Attribute) string {
140
	// Get the entity from the attribute
141
	entity := attribute.GetEntity()
142
143
	// Convert the entity to string
144
	strEntity := EntityToString(entity)
145
146
	// Create the string representation of the attribute
147
	result := fmt.Sprintf("%s$%s|%s:%s", strEntity, attribute.GetAttribute(), TypeUrlToString(attribute.GetValue().GetTypeUrl()), AnyToString(attribute.GetValue()))
148
149
	return result
150
}
151
152
// EntityAndAttributeToString converts an entity and attribute to a single string.
153
func EntityAndAttributeToString(entity *base.Entity, attr string) string {
154
	// Convert the entity to string format
155
	strEntity := EntityToString(entity)
156
157
	// Combine the entity string with the attribute using a dollar sign as the separator
158
	result := fmt.Sprintf("%s$%s", strEntity, attr)
159
160
	// Return the combined string
161
	return result
162
}
163
164
// EntityToString function takes an Entity object and converts it into a string.
165
func EntityToString(entity *base.Entity) string {
166
	return fmt.Sprintf(ENTITY, entity.GetType(), entity.GetId())
167
}
168
169
// EntityAndCallOrAttributeToString -
170
func EntityAndCallOrAttributeToString(entity *base.Entity, attributeOrCall string, arguments ...*base.Argument) string {
171
	return EntityToString(entity) + fmt.Sprintf(ATTRIBUTE, CallOrAttributeToString(attributeOrCall, arguments...))
172
}
173
174
// CallOrAttributeToString -
175
func CallOrAttributeToString(attributeOrCall string, arguments ...*base.Argument) string {
176
	if len(arguments) > 0 {
177
		var args []string
178
		for _, arg := range arguments {
179
			if arg.GetComputedAttribute() != nil {
180
				args = append(args, arg.GetComputedAttribute().GetName())
181
			} else {
182
				args = append(args, "request."+arg.GetContextAttribute().GetName())
183
			}
184
		}
185
		return fmt.Sprintf("%s(%s)", attributeOrCall, strings.Join(args, ","))
186
	}
187
	return attributeOrCall
188
}
189
190
func TypeUrlToString(url string) string {
191
	switch url {
192
	case "type.googleapis.com/base.v1.StringValue":
193
		return "string"
194
	case "type.googleapis.com/base.v1.BooleanValue":
195
		return "boolean"
196
	case "type.googleapis.com/base.v1.IntegerValue":
197
		return "integer"
198
	case "type.googleapis.com/base.v1.DoubleValue":
199
		return "double"
200
	case "type.googleapis.com/base.v1.StringArrayValue":
201
		return "string[]"
202
	case "type.googleapis.com/base.v1.BooleanArrayValue":
203
		return "boolean[]"
204
	case "type.googleapis.com/base.v1.IntegerArrayValue":
205
		return "integer[]"
206
	case "type.googleapis.com/base.v1.DoubleArrayValue":
207
		return "double[]"
208
	default:
209
		return ""
210
	}
211
}
212
213
// AnyToString function takes an Any proto message and converts it into a string.
214
func AnyToString(any *anypb.Any) string {
215
	var str string
216
217
	// Convert the Any proto message into string based on its TypeUrl
218
	switch any.TypeUrl {
219
	case "type.googleapis.com/base.v1.BooleanValue":
220
		boolVal := &base.BooleanValue{}
221
		if err := any.UnmarshalTo(boolVal); err != nil {
222
			return "undefined"
223
		}
224
		str = strconv.FormatBool(boolVal.Data)
225
	case "type.googleapis.com/base.v1.BooleanArrayValue":
226
		boolVal := &base.BooleanArrayValue{}
227
		if err := any.UnmarshalTo(boolVal); err != nil {
228
			return "undefined"
229
		}
230
		var strs []string
231
		for _, b := range boolVal.GetData() {
232
			strs = append(strs, strconv.FormatBool(b))
233
		}
234
		str = strings.Join(strs, ",")
235
	case "type.googleapis.com/base.v1.StringValue":
236
		stringVal := &base.StringValue{}
237
		if err := any.UnmarshalTo(stringVal); err != nil {
238
			return "undefined"
239
		}
240
		str = stringVal.Data
241
	case "type.googleapis.com/base.v1.StringArrayValue":
242
		stringVal := &base.StringArrayValue{}
243
		if err := any.UnmarshalTo(stringVal); err != nil {
244
			return "undefined"
245
		}
246
		var strs []string
247
		for _, v := range stringVal.GetData() {
248
			strs = append(strs, v)
249
		}
250
		str = strings.Join(strs, ",")
251
	case "type.googleapis.com/base.v1.DoubleValue":
252
		doubleVal := &base.DoubleValue{}
253
		if err := any.UnmarshalTo(doubleVal); err != nil {
254
			return "undefined"
255
		}
256
		str = strconv.FormatFloat(doubleVal.Data, 'f', -1, 64)
257
	case "type.googleapis.com/base.v1.DoubleArrayValue":
258
		doubleVal := &base.DoubleArrayValue{}
259
		if err := any.UnmarshalTo(doubleVal); err != nil {
260
			return "undefined"
261
		}
262
		var strs []string
263
		for _, v := range doubleVal.GetData() {
264
			strs = append(strs, strconv.FormatFloat(v, 'f', -1, 64))
265
		}
266
		str = strings.Join(strs, ",")
267
	case "type.googleapis.com/base.v1.IntegerValue":
268
		intVal := &base.IntegerValue{}
269
		if err := any.UnmarshalTo(intVal); err != nil {
270
			return "undefined"
271
		}
272
		str = strconv.Itoa(int(intVal.Data))
273
	case "type.googleapis.com/base.v1.IntegerArrayValue":
274
		intVal := &base.IntegerArrayValue{}
275
		if err := any.UnmarshalTo(intVal); err != nil {
276
			return "undefined"
277
		}
278
		var strs []string
279
		for _, v := range intVal.GetData() {
280
			strs = append(strs, strconv.Itoa(int(v)))
281
		}
282
		str = strings.Join(strs, ",")
283
	default:
284
		return "undefined"
285
	}
286
287
	return str
288
}
289
290
// TypeToString function takes an AttributeType enum and converts it into a string.
291
func TypeToString(attributeType base.AttributeType) string {
292
	switch attributeType {
293
	case base.AttributeType_ATTRIBUTE_TYPE_INTEGER:
294
		return "integer"
295
	case base.AttributeType_ATTRIBUTE_TYPE_INTEGER_ARRAY:
296
		return "integer[]"
297
	case base.AttributeType_ATTRIBUTE_TYPE_DOUBLE:
298
		return "double"
299
	case base.AttributeType_ATTRIBUTE_TYPE_DOUBLE_ARRAY:
300
		return "double[]"
301
	case base.AttributeType_ATTRIBUTE_TYPE_STRING:
302
		return "string"
303
	case base.AttributeType_ATTRIBUTE_TYPE_STRING_ARRAY:
304
		return "string[]"
305
	case base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN:
306
		return "boolean"
307
	case base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN_ARRAY:
308
		return "boolean[]"
309
	default:
310
		return "undefined"
311
	}
312
}
313
314
// ValidateValue checks the validity of the 'any' parameter which is a protobuf 'Any' type,
315
// based on the attribute type provided.
316
//
317
// 'any' is a protobuf 'Any' type which should contain a value of a specific type.
318
// 'attributeType' is an enum indicating the expected type of the value within 'any'.
319
// The function returns an error if the value within 'any' is not of the expected type, or if unmarshalling fails.
320
//
321
// The function returns nil if the value is valid (i.e., it is of the expected type and can be successfully unmarshalled).
322
func ValidateValue(any *anypb.Any, attributeType base.AttributeType) error {
323
	// Declare a variable 'target' of type proto.Message to hold the unmarshalled value.
324
	var target proto.Message
325
326
	// Depending on the expected attribute type, assign 'target' a new instance of the corresponding specific type.
327
	switch attributeType {
328
	case base.AttributeType_ATTRIBUTE_TYPE_INTEGER:
329
		target = &base.IntegerValue{}
330
	case base.AttributeType_ATTRIBUTE_TYPE_INTEGER_ARRAY:
331
		target = &base.IntegerArrayValue{}
332
	case base.AttributeType_ATTRIBUTE_TYPE_DOUBLE:
333
		target = &base.DoubleValue{}
334
	case base.AttributeType_ATTRIBUTE_TYPE_DOUBLE_ARRAY:
335
		target = &base.DoubleArrayValue{}
336
	case base.AttributeType_ATTRIBUTE_TYPE_STRING:
337
		target = &base.StringValue{}
338
	case base.AttributeType_ATTRIBUTE_TYPE_STRING_ARRAY:
339
		target = &base.StringArrayValue{}
340
	case base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN:
341
		target = &base.BooleanValue{}
342
	case base.AttributeType_ATTRIBUTE_TYPE_BOOLEAN_ARRAY:
343
		target = &base.BooleanArrayValue{}
344
	default:
345
		// If attributeType doesn't match any of the known types, return an error indicating invalid argument.
346
		return errors.New(base.ErrorCode_ERROR_CODE_INVALID_ARGUMENT.String())
347
	}
348
349
	// Attempt to unmarshal the value in 'any' into 'target'.
350
	// If this fails, return the error from UnmarshalTo.
351
	if err := any.UnmarshalTo(target); err != nil {
352
		return err
353
	}
354
355
	// If the value was successfully unmarshalled and is of the expected type, return nil to indicate success.
356
	return nil
357
}
358