|
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 value: %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 { // Parse each boolean |
|
66
|
|
|
boolVal, err := strconv.ParseBool(value) |
|
67
|
|
|
if err != nil { |
|
68
|
|
|
return nil, fmt.Errorf("failed to parse boolean array element: %w", err) |
|
|
|
|
|
|
69
|
|
|
} |
|
70
|
|
|
ba[i] = boolVal // Store parsed value |
|
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 value: %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 { // Parse each double |
|
88
|
|
|
doubleVal, err := strconv.ParseFloat(value, 64) |
|
89
|
|
|
if err != nil { |
|
90
|
|
|
return nil, fmt.Errorf("failed to parse float: %w", err) |
|
|
|
|
|
|
91
|
|
|
} |
|
92
|
|
|
da[i] = doubleVal // Store parsed value |
|
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: %w", 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 { // Parse each integer |
|
105
|
|
|
intVal, err := strconv.ParseInt(value, 10, 32) |
|
106
|
|
|
if err != nil { |
|
107
|
|
|
return nil, fmt.Errorf("failed to parse integer: %w", err) |
|
|
|
|
|
|
108
|
|
|
} |
|
109
|
|
|
ia[i] = int32(intVal) // Store parsed value |
|
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(), ",") // Join string array |
|
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
|
|
|
|