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
|
|
|
|