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  }