查询字符串运算符

时间:2016-06-27 21:29:39

标签: go query-string

我正在开发一个API,它需要运营商作为值的过滤器。例如。 &val=true&Amount>33"。我认为内部表示将是map [string] struct {Operator string,Val [] string}但它似乎不受标准库的支持。

是否有支持此类查询的外部包?它是一个常见的用例(例如搜索API),所以我很惊讶我无法在godoc上找到任何东西。

1 个答案:

答案 0 :(得分:2)

似乎用例并不像我想象的那么普遍。我实现了一个自定义"解析器"。希望这不违反任何RFC。欢迎任何补丁!

package query

import(
    "net/url"
    "strings"
)


//ParseQueryOp parses the URL-encoded query string and returns a map 
// listing the values specified for each key. ParseQueryOp always returns 
// a non-nil map containing all the valid query parameters found;
// err describes the first decoding error encountered, if any.
// If the query has operators (e.g. &amount>300) it is Add-ed in the map
// with a prefixed key ( {{prefix}}key ).
/*
 e.g. for a query &Amount>300 the operator(">") is returnd
    s := "query &Amount>300"
    q, _ := ParseQueryOp(s, "_op_")
    print(q.Get("_op_Amount")) // prints `>`
    print(q.Get("Amount")) // prints 300
*/
func ParseQueryOp(query, prefix string) (m url.Values, err error){
    m = make(url.Values)

    for query != "" {
        var opKey string
        key := query
        if i := strings.IndexAny(key, "&;"); i >= 0 {
            key, query = key[:i], key[i+1:]
        } else {
            query = ""
        }
        if key == "" {
            continue
        }
        value := ""
        var err1 error
        if i := strings.Index(key, ">="); i >= 0{
            key, value = key[:i], key[i+2:]
            opKey = prefix + key 
            opKey, err1 = url.QueryUnescape(opKey)
            if err1 != nil {
                if err == nil {
                    err = err1
                }
                continue
            }
            m[opKey] = append(m[opKey], ">=")
        }else if i = strings.Index(key, "<="); i >= 0{
            key, value = key[:i], key[i+2:]
            opKey = prefix + key 
            opKey, err1 = url.QueryUnescape(opKey)
            if err1 != nil {
                if err == nil {
                    err = err1
                }
                continue
            }
            m[opKey] = append(m[opKey], "<=")
        }else if i = strings.Index(key, "=");  i >= 0{
            key, value = key[:i], key[i+1:]
        }else if i = strings.Index(key, ">");  i >= 0{
            key, value = key[:i], key[i+1:]
            opKey = prefix + key 
            opKey, err1 = url.QueryUnescape(opKey)
            if err1 != nil {
                if err == nil {
                    err = err1
                }
                continue
            }
            m[opKey] = append(m[opKey], ">")
        }else if i = strings.Index(key, "<"); i >= 0{
            key, value = key[:i], key[i+1:]
            opKey = prefix + key 
            opKey, err1 = url.QueryUnescape(opKey)
            if err1 != nil {
                if err == nil {
                    err = err1
                }
                continue
            }
            m[opKey] = append(m[opKey], "<")
        }

        key, err1 = url.QueryUnescape(key)
        if err1 != nil {
            if err == nil {
                err = err1
            }
            continue
        }
        value, err1 = url.QueryUnescape(value)
        if err1 != nil {
            if err == nil {
                err = err1
            }
            continue
        }
        m[key] = append(m[key], value)
    }
    return m, err
}

还有一些测试

package query


import(
    "testing"
    "net/url"
)


type parseTest struct {
    query string
    out   url.Values
}


var parseTests = []parseTest{
    {
        query: "a=1&b=2",
        out:   url.Values{"a": []string{"1"}, "b": []string{"2"}},
    },
    {
        query: "a=1&a=2&a=banana",
        out:   url.Values{"a": []string{"1", "2", "banana"}},
    },
    {
        query: "ascii=%3Ckey%3A+0x90%3E",
        out:   url.Values{"ascii": []string{"<key: 0x90>"}},
    },
    {
        query: "a=1;b=2",
        out:   url.Values{"a": []string{"1"}, "b": []string{"2"}},
    },
    {
        query: "a=1&a=2;a=banana",
        out:   url.Values{"a": []string{"1", "2", "banana"}},
    },
    {
        query: "a=1&b>2",
        out:   url.Values{"a": []string{"1"}, "_op_b": []string{">"}, "b": []string{"2"}},
    },
    {
        query: "a=1&b<2",
        out:   url.Values{"a": []string{"1"}, "_op_b": []string{"<"}, "b": []string{"2"}},
    },
    {
        query: "a=1&b>=2",
        out:   url.Values{"a": []string{"1"}, "_op_b": []string{">="}, "b": []string{"2"}},
    },
    {
        query: "a=1&b<=2",
        out:   url.Values{"a": []string{"1"}, "_op_b": []string{"<="}, "b": []string{"2"}},
    },
}

func TestParseQueryOut(t *testing.T) {
    for i, test := range parseTests {
        form, err := ParseQueryOp(test.query, "_op_")
        if err != nil {
            t.Errorf("test %d: Unexpected error: %v", i, err)
            continue
        }
        if len(form) != len(test.out) {
            t.Errorf("test %d: len(form) = %d, want %d", i, len(form), len(test.out))
        }
        for k, evs := range test.out {
            vs, ok := form[k]
            if !ok {
                t.Errorf("test %d: Missing key %q", i, k)
                continue
            }
            if len(vs) != len(evs) {
                t.Errorf("test %d: len(form[%q]) = %d, want %d", i, k, len(vs), len(evs))
                continue
            }
            for j, ev := range evs {
                if v := vs[j]; v != ev {
                    t.Errorf("test %d: form[%q][%d] = %q, want %q", i, k, j, v, ev)
                }
            }
        }
    }
}