Issues (35)

pkg/attribute/attribute.go (3 issues)

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