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