github.com/viant/toolbox@v0.34.5/data/udf/util.go (about) 1 package udf 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "fmt" 7 "github.com/viant/toolbox" 8 "github.com/viant/toolbox/data" 9 "math/rand" 10 "net/url" 11 "strings" 12 "time" 13 ) 14 15 //Length returns length of slice or string 16 func Length(source interface{}, state data.Map) (interface{}, error) { 17 18 if toolbox.IsSlice(source) { 19 return len(toolbox.AsSlice(source)), nil 20 } 21 if toolbox.IsMap(source) { 22 return len(toolbox.AsMap(source)), nil 23 } 24 25 if text, ok := source.(string); ok { 26 if strings.HasPrefix(text, "$") { 27 return nil, fmt.Errorf("unexpanded variable: %v", text) 28 } 29 return len(text), nil 30 } 31 return 0, nil 32 } 33 34 35 //Replace replaces text with old and new fragments 36 func Replace(source interface{}, state data.Map) (interface{}, error) { 37 var args []interface{} 38 if ! toolbox.IsSlice(source) { 39 return nil, fmt.Errorf("expacted %T, but had %T", args, source) 40 } 41 args = toolbox.AsSlice(source) 42 if len(args) < 3 { 43 return nil, fmt.Errorf("expected 3 arguments (text, old, new), but had: %v" , len(args)) 44 } 45 text := toolbox.AsString(args[0]) 46 old := toolbox.AsString(args[1]) 47 new := toolbox.AsString(args[2]) 48 count := strings.Count(text, old) 49 return strings.Replace(text, old, new, count), nil 50 } 51 52 53 // Join joins slice by separator 54 func Join(args interface{}, state data.Map) (interface{}, error) { 55 if !toolbox.IsSlice(args) { 56 return nil, fmt.Errorf("expected 2 arguments but had: %T", args) 57 } 58 arguments := toolbox.AsSlice(args) 59 if len(arguments) != 2 { 60 return nil, fmt.Errorf("expected 2 arguments but had: %v", len(arguments)) 61 } 62 63 if !toolbox.IsSlice(arguments[0]) { 64 return nil, fmt.Errorf("expected 1st arguments as slice but had: %T", arguments[0]) 65 } 66 var result = make([]string, 0) 67 toolbox.CopySliceElements(arguments[0], &result) 68 return strings.Join(result, toolbox.AsString(arguments[1])), nil 69 } 70 71 // Split split text to build a slice 72 func Split(args interface{}, state data.Map) (interface{}, error) { 73 if !toolbox.IsSlice(args) { 74 return nil, fmt.Errorf("expected 2 arguments but had: %T", args) 75 } 76 arguments := toolbox.AsSlice(args) 77 if len(arguments) != 2 { 78 return nil, fmt.Errorf("expected 2 arguments but had: %v", len(arguments)) 79 } 80 if !toolbox.IsString(arguments[0]) { 81 return nil, fmt.Errorf("expected 1st arguments as string but had: %T", arguments[0]) 82 } 83 result := strings.Split(toolbox.AsString(arguments[0]), toolbox.AsString(arguments[1])) 84 for i := range result { 85 result[i] = strings.TrimSpace(result[i]) 86 } 87 return result, nil 88 } 89 90 //Keys returns keys of the supplied map 91 func Keys(source interface{}, state data.Map) (interface{}, error) { 92 aMap, err := AsMap(source, state) 93 if err != nil { 94 return nil, err 95 } 96 var result = make([]interface{}, 0) 97 err = toolbox.ProcessMap(aMap, func(key, value interface{}) bool { 98 result = append(result, key) 99 return true 100 }) 101 if err != nil { 102 return nil, err 103 } 104 return result, nil 105 } 106 107 //Values returns values of the supplied map 108 func Values(source interface{}, state data.Map) (interface{}, error) { 109 aMap, err := AsMap(source, state) 110 if err != nil { 111 return nil, err 112 } 113 var result = make([]interface{}, 0) 114 err = toolbox.ProcessMap(aMap, func(key, value interface{}) bool { 115 result = append(result, value) 116 return true 117 }) 118 if err != nil { 119 return nil, err 120 } 121 return result, nil 122 } 123 124 //IndexOf returns index of the matched slice elements or -1 125 func IndexOf(source interface{}, state data.Map) (interface{}, error) { 126 if !toolbox.IsSlice(source) { 127 return nil, fmt.Errorf("expected arguments but had: %T", source) 128 } 129 args := toolbox.AsSlice(source) 130 if len(args) != 2 { 131 return nil, fmt.Errorf("expected 2 arguments but had: %v", len(args)) 132 } 133 134 if toolbox.IsString(args[0]) { 135 return strings.Index(toolbox.AsString(args[0]), toolbox.AsString(args[1])), nil 136 } 137 collection, err := AsCollection(args[0], state) 138 if err != nil { 139 return nil, err 140 } 141 for i, candidate := range toolbox.AsSlice(collection) { 142 if candidate == args[1] || toolbox.AsString(candidate) == toolbox.AsString(args[1]) { 143 return i, nil 144 } 145 } 146 return -1, nil 147 } 148 149 //Base64Decode encodes source using base64.StdEncoding 150 func Base64Encode(source interface{}, state data.Map) (interface{}, error) { 151 if source == nil { 152 return "", nil 153 } 154 switch value := source.(type) { 155 case string: 156 return base64.StdEncoding.EncodeToString([]byte(value)), nil 157 case []byte: 158 return base64.StdEncoding.EncodeToString(value), nil 159 default: 160 if toolbox.IsMap(source) || toolbox.IsSlice(source) { 161 encoded, err := json.Marshal(source) 162 fmt.Printf("%s %v\n", encoded, err) 163 if err == nil { 164 return base64.StdEncoding.EncodeToString(encoded), nil 165 } 166 } 167 return nil, fmt.Errorf("unsupported type: %T", source) 168 } 169 } 170 171 //Base64Decode decodes source using base64.StdEncoding 172 func Base64Decode(source interface{}, state data.Map) (interface{}, error) { 173 if source == nil { 174 return "", nil 175 } 176 switch value := source.(type) { 177 case string: 178 return base64.StdEncoding.DecodeString(value) 179 case []byte: 180 return base64.StdEncoding.DecodeString(string(value)) 181 default: 182 return nil, fmt.Errorf("unsupported type: %T", source) 183 } 184 } 185 186 //Base64DecodeText decodes source using base64.StdEncoding to string 187 func Base64DecodeText(source interface{}, state data.Map) (interface{}, error) { 188 decoded, err := Base64Decode(source, state) 189 if err != nil { 190 return nil, err 191 } 192 return toolbox.AsString(decoded), nil 193 } 194 195 //QueryEscape returns url escaped text 196 func QueryEscape(source interface{}, state data.Map) (interface{}, error) { 197 text := toolbox.AsString(source) 198 return url.QueryEscape(text), nil 199 } 200 201 //QueryUnescape returns url escaped text 202 func QueryUnescape(source interface{}, state data.Map) (interface{}, error) { 203 text := toolbox.AsString(source) 204 return url.QueryUnescape(text) 205 } 206 207 //TrimSpace returns trims spaces from supplied text 208 func TrimSpace(source interface{}, state data.Map) (interface{}, error) { 209 text := toolbox.AsString(source) 210 return strings.TrimSpace(text), nil 211 } 212 213 //Count returns count of matched nodes leaf value 214 func Count(xPath interface{}, state data.Map) (interface{}, error) { 215 result, err := aggregate(xPath, state, func(previous, newValue float64) float64 { 216 return previous + 1 217 }) 218 if err != nil { 219 return nil, err 220 } 221 return AsNumber(result, nil) 222 } 223 224 //Sum returns sums of matched nodes leaf value 225 func Sum(xPath interface{}, state data.Map) (interface{}, error) { 226 result, err := aggregate(xPath, state, func(previous, newValue float64) float64 { 227 return previous + newValue 228 }) 229 if err != nil { 230 return nil, err 231 } 232 return AsNumber(result, nil) 233 } 234 235 //Select returns all matched attributes from matched nodes, attributes can be alised with sourcePath:alias 236 func Select(params interface{}, state data.Map) (interface{}, error) { 237 var arguments = make([]interface{}, 0) 238 if toolbox.IsSlice(params) { 239 arguments = toolbox.AsSlice(params) 240 } else { 241 arguments = append(arguments, params) 242 } 243 xPath := toolbox.AsString(arguments[0]) 244 var result = make([]interface{}, 0) 245 attributes := make([]string, 0) 246 for i := 1; i < len(arguments); i++ { 247 attributes = append(attributes, toolbox.AsString(arguments[i])) 248 } 249 err := matchPath(xPath, state, func(matched interface{}) error { 250 if len(attributes) == 0 { 251 result = append(result, matched) 252 return nil 253 } 254 if !toolbox.IsMap(matched) { 255 return fmt.Errorf("expected map for %v, but had %T", xPath, matched) 256 } 257 matchedMap := data.Map(toolbox.AsMap(matched)) 258 var attributeValues = make(map[string]interface{}) 259 for _, attr := range attributes { 260 if strings.Contains(attr, ":") { 261 kvPair := strings.SplitN(attr, ":", 2) 262 value, has := matchedMap.GetValue(kvPair[0]) 263 if !has { 264 continue 265 } 266 attributeValues[kvPair[1]] = value 267 } else { 268 value, has := matchedMap.GetValue(attr) 269 if !has { 270 continue 271 } 272 attributeValues[attr] = value 273 } 274 } 275 result = append(result, attributeValues) 276 return nil 277 }) 278 return result, err 279 } 280 281 //AsNumber return int or float 282 func AsNumber(value interface{}, state data.Map) (interface{}, error) { 283 floatValue := toolbox.AsFloat(value) 284 if float64(int(floatValue)) == floatValue { 285 return int(floatValue), nil 286 } 287 return floatValue, nil 288 } 289 290 //Aggregate applies an aggregation function to matched path 291 func aggregate(xPath interface{}, state data.Map, agg func(previous, newValue float64) float64) (float64, error) { 292 var result = 0.0 293 if state == nil { 294 return 0.0, fmt.Errorf("state was empty") 295 } 296 err := matchPath(toolbox.AsString(xPath), state, func(value interface{}) error { 297 if value == nil { 298 return nil 299 } 300 floatValue, err := toolbox.ToFloat(value) 301 if err != nil { 302 return err 303 } 304 result = agg(result, floatValue) 305 return nil 306 }) 307 return result, err 308 } 309 310 func matchPath(xPath string, state data.Map, handler func(value interface{}) error) error { 311 fragments := strings.Split(toolbox.AsString(xPath), "/") 312 var node = state 313 var nodeValue interface{} 314 for i, part := range fragments { 315 316 isLast := i == len(fragments)-1 317 if isLast { 318 if part == "*" { 319 if toolbox.IsSlice(nodeValue) { 320 for _, item := range toolbox.AsSlice(nodeValue) { 321 if err := handler(item); err != nil { 322 return err 323 } 324 } 325 return nil 326 } else if toolbox.IsMap(nodeValue) { 327 for _, item := range toolbox.AsMap(nodeValue) { 328 if err := handler(item); err != nil { 329 return err 330 } 331 } 332 } 333 return handler(nodeValue) 334 } 335 336 if !node.Has(part) { 337 break 338 } 339 if err := handler(node.Get(part)); err != nil { 340 return err 341 } 342 continue 343 } 344 if part != "*" { 345 nodeValue = node.Get(part) 346 if nodeValue == nil { 347 break 348 } 349 if toolbox.IsMap(nodeValue) { 350 node = toolbox.AsMap(nodeValue) 351 continue 352 } 353 if toolbox.IsSlice(nodeValue) { 354 continue 355 } 356 break 357 } 358 359 if nodeValue == nil { 360 break 361 } 362 subXPath := strings.Join(fragments[i+1:], "/") 363 if toolbox.IsSlice(nodeValue) { 364 aSlice := toolbox.AsSlice(nodeValue) 365 for _, item := range aSlice { 366 if toolbox.IsMap(item) { 367 if err := matchPath(subXPath, toolbox.AsMap(item), handler); err != nil { 368 return err 369 } 370 continue 371 } 372 return fmt.Errorf("unsupported path type:%T", item) 373 } 374 } 375 if toolbox.IsMap(nodeValue) { 376 aMap := toolbox.AsMap(nodeValue) 377 for _, item := range aMap { 378 if toolbox.IsMap(item) { 379 if err := matchPath(subXPath, toolbox.AsMap(item), handler); err != nil { 380 return err 381 } 382 continue 383 } 384 return fmt.Errorf("unsupported path type:%T", item) 385 } 386 } 387 break 388 } 389 return nil 390 } 391 392 //Rand returns random 393 func Rand(params interface{}, state data.Map) (interface{}, error) { 394 source := rand.NewSource(time.Now().UnixNano()) 395 generator := rand.New(source) 396 floatValue := generator.Float64() 397 if params == nil || !toolbox.IsSlice(params) { 398 return floatValue, nil 399 } 400 parameters := toolbox.AsSlice(params) 401 if len(parameters) != 2 { 402 return floatValue, nil 403 } 404 min := toolbox.AsInt(parameters[0]) 405 max := toolbox.AsInt(parameters[1]) 406 return min + int(float64(max-min)*floatValue), nil 407 } 408 409 //Concat concatenate supplied parameters, parameters 410 func Concat(params interface{}, state data.Map) (interface{}, error) { 411 if params == nil || !toolbox.IsSlice(params) { 412 return nil, fmt.Errorf("invalid signature, expected: $Concat(arrayOrItem1, arrayOrItem2)") 413 } 414 var result = make([]interface{}, 0) 415 parameters := toolbox.AsSlice(params) 416 if len(parameters) == 0 { 417 return result, nil 418 } 419 420 if toolbox.IsString(parameters[0]) { 421 result := "" 422 for _, item := range parameters { 423 result += toolbox.AsString(item) 424 } 425 return result, nil 426 } 427 428 for _, item := range parameters { 429 if toolbox.IsSlice(item) { 430 itemSlice := toolbox.AsSlice(item) 431 result = append(result, itemSlice...) 432 continue 433 } 434 result = append(result, item) 435 } 436 return result, nil 437 } 438 439 //Merge creates a new merged map for supplied maps, (mapOrPath1, mapOrPath2, mapOrPathN) 440 func Merge(params interface{}, state data.Map) (interface{}, error) { 441 if params == nil || !toolbox.IsSlice(params) { 442 return nil, fmt.Errorf("invalid signature, expected: $Merge(map1, map2, override)") 443 } 444 var result = make(map[string]interface{}) 445 parameters := toolbox.AsSlice(params) 446 if len(parameters) == 0 { 447 return result, nil 448 } 449 var ok bool 450 for _, item := range parameters { 451 if toolbox.IsString(item) && state != nil { 452 if item, ok = state.GetValue(toolbox.AsString(item)); !ok { 453 continue 454 } 455 } 456 if !toolbox.IsMap(item) { 457 continue 458 } 459 itemMap := toolbox.AsMap(item) 460 for k, v := range itemMap { 461 result[k] = v 462 } 463 } 464 return result, nil 465 } 466 467 //AsNewLineDelimitedJSON convers a slice into new line delimited JSON 468 func AsNewLineDelimitedJSON(source interface{}, state data.Map) (interface{}, error) { 469 if source == nil || !toolbox.IsSlice(source) { 470 return nil, fmt.Errorf("invalid signature, expected: $AsNewLineDelimitedJSON([])") 471 } 472 aSlice := toolbox.AsSlice(source) 473 var result = make([]string, 0) 474 for _, item := range aSlice { 475 data, _ := json.Marshal(item) 476 result = append(result, string(data)) 477 } 478 return strings.Join(result, "\n"), nil 479 }