func actualFunctionName(name string) string {
	nameLow := strings.ToLower(name)
	if actual, ok := transformFunctionWhitelist[nameLow]; ok {
		return actual
	}
	if !strings.HasPrefix(nameLow, functionPrefixST) {
		stName := functionPrefixST + nameLow
		if actual, ok := transformFunctionWhitelist[stName]; ok {
			return actual
		}
	}
	return ""
}
actualFunctionName converts an input function name
to an actual function name from the whitelist
package service
import (
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"github.com/CrunchyData/pg_featureserv/internal/api"
	"github.com/CrunchyData/pg_featureserv/internal/conf"
	"github.com/CrunchyData/pg_featureserv/internal/data"
)
func parseRequestParams(r *http.Request) (api.RequestParam, error) {
	queryValues := r.URL.Query()
	paramValues := extractSingleArgs(queryValues)
	param := api.RequestParam{
		Limit:     conf.Configuration.Paging.LimitDefault,
		Offset:    0,
		Precision: -1,
		Values:    paramValues,
	}
	limit, err := parseLimit(paramValues)
	if err != nil {
		return param, err
	}
	param.Limit = limit
	offset, err := parseInt(paramValues, api.ParamOffset, 0, -1, 0)
	if err != nil {
		return param, err
	}
	param.Offset = offset
	bbox, err := parseBbox(paramValues)
	if err != nil {
		return param, err
	}
	param.Bbox = bbox
	props, err := parseProperties(paramValues)
	if err != nil {
		return param, err
	}
	param.Properties = props
	groupBy, err := parseGroupBy(paramValues)
	if err != nil {
		return param, err
	}
	param.GroupBy = groupBy
	orderBy, err := parseOrderBy(paramValues)
	if err != nil {
		return param, err
	}
	param.SortBy = orderBy
	sortBy, err := parseSortBy(paramValues)
	if err != nil {
		return param, err
	}
	param.SortBy = sortBy
	precision, err := parseInt(paramValues, api.ParamPrecision, 0, 20, -1)
	if err != nil {
		return param, err
	}
	param.Precision = precision
	param.TransformFuns, err = parseTransform(paramValues)
	if err != nil {
		return param, err
	}
	return param, nil
}
func extractSingleArgs(queryArgs url.Values) api.NameValMap {
	vals := make(map[string]string)
	for keyRaw := range queryArgs {
		queryval := queryArgs.Get(keyRaw)
		key := strings.ToLower(keyRaw)
		vals[key] = queryval
	}
	return vals
}
func parseInt(values api.NameValMap, key string, minVal int, maxVal int, defaultVal int) (int, error) {
	valStr := values[key]
	if len(valStr) < 1 {
		return defaultVal, nil
	}
	val, err := strconv.Atoi(valStr)
	if err != nil {
		return 0, fmt.Errorf(api.ErrMsgInvalidParameterValue, key, valStr)
	}
	if val < minVal {
		val = minVal
	}
	if maxVal >= 0 && val > maxVal {
		val = maxVal
	}
	return val, nil
}
func parseLimit(values api.NameValMap) (int, error) {
	val := values[api.ParamLimit]
	if len(val) < 1 {
		return conf.Configuration.Paging.LimitDefault, nil
	}
	limit, err := strconv.Atoi(val)
	if err != nil {
		return 0, fmt.Errorf(api.ErrMsgInvalidParameterValue, api.ParamLimit, val)
	}
	if limit < 0 || limit > conf.Configuration.Paging.LimitMax {
		limit = conf.Configuration.Paging.LimitMax
	}
	return limit, nil
}
func parseBbox(values api.NameValMap) (*data.Extent, error) {
	val := values[api.ParamBbox]
	if len(val) < 1 {
		return nil, nil
	}
	nums := strings.Split(val, ",")
	var isErr = false
	if len(nums) != 4 {
		return nil, fmt.Errorf(api.ErrMsgInvalidParameterValue, api.ParamBbox, val)
	}
	minLon, err := strconv.ParseFloat(nums[0], 64)
	if err != nil {
		isErr = true
	}
	minLat, err := strconv.ParseFloat(nums[1], 64)
	if err != nil {
		isErr = true
	}
	maxLon, err := strconv.ParseFloat(nums[2], 64)
	if err != nil {
		isErr = true
	}
	maxLat, err := strconv.ParseFloat(nums[3], 64)
	if err != nil {
		isErr = true
	}
	if isErr {
		return nil, fmt.Errorf(api.ErrMsgInvalidParameterValue, api.ParamBbox, val)
	}
	var bbox = data.Extent{Minx: minLon, Miny: minLat, Maxx: maxLon, Maxy: maxLat}
	return &bbox, nil
}
func parseProperties(values api.NameValMap) ([]string, error) {
	val, ok := values[api.ParamProperties]
	if !ok {
		return nil, nil
	}
	if len(val) < 1 {
		return []string{}, nil
	}
	namesRaw := strings.Split(val, ",")
	return namesRaw, nil
}
func parseGroupBy(values api.NameValMap) ([]string, error) {
	val, ok := values[api.ParamGroupBy]
	if !ok {
		return nil, nil
	}
	if !ok || len(val) < 1 {
		return []string{}, nil
	}
	namesRaw := strings.Split(val, ",")
	return namesRaw, nil
}
func parseSortBy(values api.NameValMap) ([]data.Sorting, error) {
	var sorting []data.Sorting
	val := values[api.ParamSortBy]
	if len(val) < 1 {
		return sorting, nil
	}
	valLow := strings.ToLower(val)
	sortCols := strings.Split(valLow, ",")
	sortCol := sortCols[0]
	isDesc := false
	name := sortCol
	if strings.HasPrefix(name, "+") {
		name = name[1:]
		isDesc = false
	}
	if strings.HasPrefix(name, "-") {
		name = name[1:]
		isDesc = true
	}
	sorting = append(sorting, data.Sorting{Name: name, IsDesc: isDesc})
	return sorting, nil
}
func parseOrderBy(values api.NameValMap) ([]data.Sorting, error) {
	var orderBy []data.Sorting
	val := values[api.ParamOrderBy]
	if len(val) < 1 {
		return orderBy, nil
	}
	valLow := strings.ToLower(val)
	nameDir := strings.Split(valLow, api.OrderByDirSep)
	name := nameDir[0]
	isDesc := false
	var err error
	if len(nameDir) >= 2 {
		dirSpec := nameDir[1]
		isDesc, err = parseOrderByDir(dirSpec)
		if err != nil {
			return nil, err
		}
	}
	orderBy = append(orderBy, data.Sorting{Name: name, IsDesc: isDesc})
	return orderBy, nil
}
func parseOrderByDir(dir string) (bool, error) {
	if dir == api.OrderByDirD {
		return true, nil
	}
	if dir == api.OrderByDirA {
		return false, nil
	}
	err := fmt.Errorf(api.ErrMsgInvalidParameterValue, api.ParamOrderBy, dir)
	return false, err
}
func normalizePropNames(requestNames []string, colNames []string) []string {
	if requestNames == nil {
		return colNames
	}
	if len(requestNames) == 0 {
		return requestNames
	}
	nameSet := toNameSet(requestNames)
	var propNames []string
	for _, colName := range colNames {
		if _, ok := nameSet[colName]; ok {
			propNames = append(propNames, colName)
		}
	}
	return propNames
}
func toNameSet(strs []string) map[string]bool {
	set := make(map[string]bool)
	for _, s := range strs {
		sLow := strings.ToLower(s)
		set[sLow] = true
	}
	return set
}
const (
	transformFunSep   = "|"
	transformParamSep = ","
	functionPrefixST  = "st_"
)
var transformFunctionWhitelist map[string]string
func initTransforms(funNames []string) {
	transformFunctionWhitelist = make(map[string]string)
	for _, name := range funNames {
		nameLow := strings.ToLower(name)
		transformFunctionWhitelist[nameLow] = name
	}
}