diff --git a/integration/pkg/api/const.go b/integration/pkg/api/const.go index 865404c24ba..a4d0744a6e2 100644 --- a/integration/pkg/api/const.go +++ b/integration/pkg/api/const.go @@ -4,9 +4,13 @@ package api // Semicolon defines name for ";". // QuestionMark defines name for "?". // Slash defines name for "/". +// Ampersand defines name for "&". +// EqualTo defines name for "=". const ( Comma = "," Semicolon = ";" QuestionMark = "?" Slash = "/" + Ampersand = "&" + EqualTo = "=" ) diff --git a/integration/pkg/api/osrm/coordinate/index.go b/integration/pkg/api/osrm/coordinate/index.go new file mode 100644 index 00000000000..6e75ce5c7d7 --- /dev/null +++ b/integration/pkg/api/osrm/coordinate/index.go @@ -0,0 +1,47 @@ +package coordinate + +import ( + "fmt" + "strconv" + "strings" + + "github.com/Telenav/osrm-backend/integration/pkg/api" + "github.com/golang/glog" +) + +// Index indicates which NO. of Coordinates. +type Index uint + +// Indexes represents a list of Index. +type Indexes []Index + +func (i *Indexes) String() string { + var s string + for _, v := range *i { + if len(s) > 0 { + s += api.Semicolon + } + s += strconv.FormatUint(uint64(v), 10) + } + return s +} + +// PraseIndexes parses string to indexes of coordinates. +func PraseIndexes(s string) (Indexes, error) { + indexes := []Index{} + + splits := strings.Split(s, api.Semicolon) + for _, split := range splits { + if len(split) == 0 { + continue + } + n, err := strconv.ParseUint(split, 10, 32) + if err != nil { + fullErr := fmt.Errorf("invalid indexes value: %s, err %v", s, err) + glog.Warning(fullErr) + return nil, fullErr + } + indexes = append(indexes, Index(n)) + } + return indexes, nil +} diff --git a/integration/pkg/api/osrm/coordinate/index_test.go b/integration/pkg/api/osrm/coordinate/index_test.go new file mode 100644 index 00000000000..b49cfb1ecdc --- /dev/null +++ b/integration/pkg/api/osrm/coordinate/index_test.go @@ -0,0 +1,56 @@ +package coordinate + +import ( + "reflect" + "testing" +) + +func TestPraseIndexes(t *testing.T) { + + cases := []struct { + s string + Indexes + expectFail bool + }{ + {"0;5;7", Indexes{0, 5, 7}, false}, + {"0;5;7;", Indexes{0, 5, 7}, false}, + {"0", Indexes{0}, false}, + {"5;1;4;2;3;6", Indexes{5, 1, 4, 2, 3, 6}, false}, + {"", Indexes{}, false}, + {"-1", nil, true}, + {"a", nil, true}, + } + + for _, c := range cases { + indexes, err := PraseIndexes(c.s) + if err != nil && c.expectFail { + continue //right + } else if (err != nil && !c.expectFail) || (err == nil && c.expectFail) { + t.Errorf("parse %s expect fail %t, but got err %v", c.s, c.expectFail, err) + continue + } + + if !reflect.DeepEqual(indexes, c.Indexes) { + t.Errorf("parse %s, expect %v, but got %v", c.s, c.Indexes, indexes) + } + } +} + +func TestIndexesString(t *testing.T) { + cases := []struct { + expect string + Indexes + }{ + {"0;5;7", Indexes{0, 5, 7}}, + {"0", Indexes{0}}, + {"5;1;4;2;3;6", Indexes{5, 1, 4, 2, 3, 6}}, + {"", Indexes{}}, + } + + for _, c := range cases { + s := c.Indexes.String() + if s != c.expect { + t.Errorf("%v String(), expect %s, but got %s", c.Indexes, c.expect, s) + } + } +} diff --git a/integration/pkg/api/osrm/genericoptions/elements.go b/integration/pkg/api/osrm/genericoptions/elements.go new file mode 100644 index 00000000000..806f1bb3195 --- /dev/null +++ b/integration/pkg/api/osrm/genericoptions/elements.go @@ -0,0 +1,37 @@ +package genericoptions + +import ( + "strings" + + "github.com/Telenav/osrm-backend/integration/pkg/api" +) + +// Elements represents values split by `;`, i.e. bearings, radiuses, hints, approaches +type Elements []string + +// ParseElemenets parses OSRM option elements. +func ParseElemenets(s string) (Elements, error) { + + s = strings.TrimSuffix(s, api.Semicolon) // remove the last `;` if exist + if len(s) == 0 { + return Elements{}, nil + } + + elements := Elements{} + splits := strings.Split(s, api.Semicolon) + for _, split := range splits { + elements = append(elements, split) + } + return elements, nil +} + +func (e *Elements) String() string { + var s string + for _, element := range *e { + if len(s) > 0 { + s += api.Semicolon + } + s += element + } + return s +} diff --git a/integration/pkg/api/osrm/genericoptions/elements_test.go b/integration/pkg/api/osrm/genericoptions/elements_test.go new file mode 100644 index 00000000000..f857eacd3b3 --- /dev/null +++ b/integration/pkg/api/osrm/genericoptions/elements_test.go @@ -0,0 +1,53 @@ +package genericoptions + +import ( + "reflect" + "testing" +) + +func TestPraseElements(t *testing.T) { + + cases := []struct { + s string + Elements + expectFail bool + }{ + {"0;5;7", Elements{"0", "5", "7"}, false}, + {"0;5;7;", Elements{"0", "5", "7"}, false}, + {"0;;7", Elements{"0", "", "7"}, false}, + {"", Elements{}, false}, + } + + for _, c := range cases { + elements, err := ParseElemenets(c.s) + if err != nil && c.expectFail { + continue //right + } else if (err != nil && !c.expectFail) || (err == nil && c.expectFail) { + t.Errorf("parse %s expect fail %t, but got err %v", c.s, c.expectFail, err) + continue + } + + if !reflect.DeepEqual(elements, c.Elements) { + t.Errorf("parse %s, expect %v, but got %v", c.s, c.Elements, elements) + } + } +} + +func TestElementsString(t *testing.T) { + cases := []struct { + expect string + Elements + }{ + {"0;5;7", Elements{"0", "5", "7"}}, + {"0;;7", Elements{"0", "", "7"}}, + {"", Elements{""}}, + {"", Elements{}}, + } + + for _, c := range cases { + s := c.Elements.String() + if s != c.expect { + t.Errorf("%v String(), expect %s, but got %s", c.Elements, c.expect, s) + } + } +} diff --git a/integration/pkg/api/osrm/genericoptions/exclude.go b/integration/pkg/api/osrm/genericoptions/exclude.go new file mode 100644 index 00000000000..efec858158f --- /dev/null +++ b/integration/pkg/api/osrm/genericoptions/exclude.go @@ -0,0 +1,42 @@ +package genericoptions + +import ( + "strings" + + "github.com/Telenav/osrm-backend/integration/pkg/api" +) + +// Classes represents OSRM exclude classes. +// https://github.com/Telenav/osrm-backend/blob/master-telenav/docs/http.md#general-options +type Classes []string + +// ParseClasses parses OSRM option elements. +func ParseClasses(s string) (Classes, error) { + + s = strings.TrimSuffix(s, api.Comma) // remove the last `,` if exist + + classes := Classes{} + splits := strings.Split(s, api.Comma) + for _, split := range splits { + if len(split) == 0 { + continue + } + classes = append(classes, split) + } + return classes, nil +} + +func (c *Classes) String() string { + var s string + for _, class := range *c { + if len(class) == 0 { + continue + } + + if len(s) > 0 { + s += api.Comma + } + s += class + } + return s +} diff --git a/integration/pkg/api/osrm/genericoptions/exclude_test.go b/integration/pkg/api/osrm/genericoptions/exclude_test.go new file mode 100644 index 00000000000..84eccd830cd --- /dev/null +++ b/integration/pkg/api/osrm/genericoptions/exclude_test.go @@ -0,0 +1,53 @@ +package genericoptions + +import ( + "reflect" + "testing" +) + +func TestPraseClasses(t *testing.T) { + + cases := []struct { + s string + Classes + expectFail bool + }{ + {"0,5,7", Classes{"0", "5", "7"}, false}, + {"0,5,7,", Classes{"0", "5", "7"}, false}, + {"0,,7", Classes{"0", "7"}, false}, + {"", Classes{}, false}, + } + + for _, c := range cases { + classes, err := ParseClasses(c.s) + if err != nil && c.expectFail { + continue //right + } else if (err != nil && !c.expectFail) || (err == nil && c.expectFail) { + t.Errorf("parse %s expect fail %t, but got err %v", c.s, c.expectFail, err) + continue + } + + if !reflect.DeepEqual(classes, c.Classes) { + t.Errorf("parse %s, expect %v, but got %v", c.s, c.Classes, classes) + } + } +} + +func TestClassesString(t *testing.T) { + cases := []struct { + expect string + Classes + }{ + {"0,5,7", Classes{"0", "5", "7"}}, + {"0,7", Classes{"0", "", "7"}}, + {"", Classes{""}}, + {"", Classes{}}, + } + + for _, c := range cases { + s := c.Classes.String() + if s != c.expect { + t.Errorf("%v String(), expect %s, but got %s", c.Classes, c.expect, s) + } + } +} diff --git a/integration/pkg/api/osrm/genericoptions/generic_hints.go b/integration/pkg/api/osrm/genericoptions/generic_hints.go new file mode 100644 index 00000000000..2a0d8346211 --- /dev/null +++ b/integration/pkg/api/osrm/genericoptions/generic_hints.go @@ -0,0 +1,18 @@ +package genericoptions + +import ( + "strconv" + + "github.com/golang/glog" +) + +// ParseGenerateHints parses generic generate_hints option. +func ParseGenerateHints(s string) (bool, error) { + b, err := strconv.ParseBool(s) + if err != nil { + glog.Warning(err) + return false, err + } + + return b, nil +} diff --git a/integration/pkg/api/osrm/genericoptions/generic_hints_test.go b/integration/pkg/api/osrm/genericoptions/generic_hints_test.go new file mode 100644 index 00000000000..b8006f307a2 --- /dev/null +++ b/integration/pkg/api/osrm/genericoptions/generic_hints_test.go @@ -0,0 +1,31 @@ +package genericoptions + +import "testing" + +func TestParseGenerateHints(t *testing.T) { + cases := []struct { + s string + expect bool + expectFail bool + }{ + {"true", true, false}, + {"false", false, false}, + {"", false, true}, + {"true1", false, true}, + {"-1", false, true}, + } + + for _, c := range cases { + b, err := ParseGenerateHints(c.s) + if err != nil && c.expectFail { + continue //right + } else if (err != nil && !c.expectFail) || (err == nil && c.expectFail) { + t.Errorf("parse %s expect fail %t, but got err %v", c.s, c.expectFail, err) + continue + } + + if b != c.expect { + t.Errorf("parse %s, expect %t, but got %t", c.s, c.expect, b) + } + } +} diff --git a/integration/pkg/api/osrm/genericoptions/values.go b/integration/pkg/api/osrm/genericoptions/values.go index a75e40c2c5a..ee27d35ff79 100644 --- a/integration/pkg/api/osrm/genericoptions/values.go +++ b/integration/pkg/api/osrm/genericoptions/values.go @@ -1 +1,6 @@ package genericoptions + +// GenerateHints values +const ( + GenerateHintsDefaultValue = true // default +) diff --git a/integration/pkg/api/osrm/route/options/options_test.go b/integration/pkg/api/osrm/route/options/options_test.go new file mode 100644 index 00000000000..4643ca12918 --- /dev/null +++ b/integration/pkg/api/osrm/route/options/options_test.go @@ -0,0 +1,187 @@ +package options + +import ( + "reflect" + "testing" +) + +func TestParseAlternatives(t *testing.T) { + + cases := []struct { + s string + expect string + expectNum int + expectFail bool + }{ + {"true", "true", 2, false}, + {"false", "false", 1, false}, + {"0", "0", 0, false}, // same as false + {"1", "1", 1, false}, + {"5", "5", 5, false}, + {"100", "100", 100, false}, + {"111111111", "111111111", 111111111, false}, + {"-1", "", 1, true}, + } + + for _, c := range cases { + alternatives, num, err := ParseAlternatives(c.s) + if err != nil && c.expectFail { + continue //right + } else if (err != nil && !c.expectFail) || (err == nil && c.expectFail) { + t.Errorf("parse %s expect fail %t, but got err %v", c.s, c.expectFail, err) + continue + } + + if alternatives != c.expect || num != c.expectNum { + t.Errorf("parse %s, expect %s %d, but got %s %d", c.s, c.expect, c.expectNum, alternatives, num) + } + } +} + +func TestParseSteps(t *testing.T) { + cases := []struct { + s string + expect bool + expectFail bool + }{ + {"true", true, false}, + {"false", false, false}, + {"", false, true}, + {"true1", false, true}, + {"-1", false, true}, + } + + for _, c := range cases { + b, err := ParseSteps(c.s) + if err != nil && c.expectFail { + continue //right + } else if (err != nil && !c.expectFail) || (err == nil && c.expectFail) { + t.Errorf("parse %s expect fail %t, but got err %v", c.s, c.expectFail, err) + continue + } + + if b != c.expect { + t.Errorf("parse %s, expect %t, but got %t", c.s, c.expect, b) + } + } +} + +func TestParseAnnotations(t *testing.T) { + + cases := []struct { + s string + expect string + expectFail bool + }{ + {"true", ValueTrue, false}, + {"false", ValueFalse, false}, + {"nodes", "nodes", false}, + {"nodes,distance", "nodes,distance", false}, + {"nodes,distance,duration,datasources,weight,speed", "nodes,distance,duration,datasources,weight,speed", false}, + {"nodes,", "", true}, + {"nodes,distances", "", true}, + {"", "", true}, + } + + for _, c := range cases { + annotations, err := ParseAnnotations(c.s) + if err != nil && c.expectFail { + continue //right + } else if (err != nil && !c.expectFail) || (err == nil && c.expectFail) { + t.Errorf("parse %s expect fail %t, but got err %v", c.s, c.expectFail, err) + continue + } + + if !reflect.DeepEqual(annotations, c.expect) { + t.Errorf("parse %s, expect %v, but got %v", c.s, c.expect, annotations) + } + } + +} + +func TestParseGeometries(t *testing.T) { + + cases := []struct { + s string + expect string + expectFail bool + }{ + {"polyline", GeometriesValuePolyline, false}, + {"polyline6", GeometriesValuePolyline6, false}, + {"geojson", GeometriesValueGeojson, false}, + {"polyli", "", true}, + {"", "", true}, + } + + for _, c := range cases { + geometries, err := ParseGeometries(c.s) + if err != nil && c.expectFail { + continue //right + } else if (err != nil && !c.expectFail) || (err == nil && c.expectFail) { + t.Errorf("parse %s expect fail %t, but got err %v", c.s, c.expectFail, err) + continue + } + + if !reflect.DeepEqual(geometries, c.expect) { + t.Errorf("parse %s, expect %v, but got %v", c.s, c.expect, geometries) + } + } +} + +func TestParseOverview(t *testing.T) { + + cases := []struct { + s string + expect string + expectFail bool + }{ + {"simplified", OverviewValueSimplified, false}, + {"full", OverviewValueFull, false}, + {"false", OverviewValueFalse, false}, + {"simp", "", true}, + {"", "", true}, + } + + for _, c := range cases { + overview, err := ParseOverview(c.s) + if err != nil && c.expectFail { + continue //right + } else if (err != nil && !c.expectFail) || (err == nil && c.expectFail) { + t.Errorf("parse %s expect fail %t, but got err %v", c.s, c.expectFail, err) + continue + } + + if !reflect.DeepEqual(overview, c.expect) { + t.Errorf("parse %s, expect %v, but got %v", c.s, c.expect, overview) + } + } +} + +func TestParseContinueStraight(t *testing.T) { + + cases := []struct { + s string + expect string + expectFail bool + }{ + {"default", ContinueStraightValueDefault, false}, + {"false", ContinueStraightValueFalse, false}, + {"true", ContinueStraightValueTrue, false}, + {"1", "", true}, + {"", "", true}, + } + + for _, c := range cases { + continueStraight, err := ParseContinueStraight(c.s) + if err != nil && c.expectFail { + continue //right + } else if (err != nil && !c.expectFail) || (err == nil && c.expectFail) { + t.Errorf("parse %s expect fail %t, but got err %v", c.s, c.expectFail, err) + continue + } + + if !reflect.DeepEqual(continueStraight, c.expect) { + t.Errorf("parse %s, expect %v, but got %v", c.s, c.expect, continueStraight) + } + } +} diff --git a/integration/pkg/api/osrm/route/options/parse.go b/integration/pkg/api/osrm/route/options/parse.go new file mode 100644 index 00000000000..438a84b259c --- /dev/null +++ b/integration/pkg/api/osrm/route/options/parse.go @@ -0,0 +1,114 @@ +package options + +import ( + "fmt" + "strconv" + "strings" + + "github.com/Telenav/osrm-backend/integration/pkg/api" + "github.com/golang/glog" +) + +// ParseAlternatives parses route service Alternatives option. +func ParseAlternatives(s string) (string, int, error) { + + if n, err := strconv.ParseUint(s, 10, 32); err == nil { + return s, int(n), nil + } + if b, err := strconv.ParseBool(s); err == nil { + if b { + return s, 2, nil // true : 2 + } + return s, 1, nil // false : 1 + } + + err := fmt.Errorf("invalid alternatives value: %s", s) + glog.Warning(err) + return "", 1, err // use value 1 if fail +} + +// ParseSteps parses route service Steps option. +func ParseSteps(s string) (bool, error) { + b, err := strconv.ParseBool(s) + if err != nil { + glog.Warning(err) + return false, err + } + + return b, nil +} + +// ParseAnnotations parses route service Annotations option. +func ParseAnnotations(s string) (string, error) { + + validAnnotationsValues := map[string]struct{}{ + AnnotationsValueTrue: struct{}{}, + AnnotationsValueFalse: struct{}{}, + AnnotationsValueNodes: struct{}{}, + AnnotationsValueDistance: struct{}{}, + AnnotationsValueDuration: struct{}{}, + AnnotationsValueDataSources: struct{}{}, + AnnotationsValueWeight: struct{}{}, + AnnotationsValueSpeed: struct{}{}, + } + + splits := strings.Split(s, api.Comma) + for _, split := range splits { + if _, found := validAnnotationsValues[split]; !found { + + err := fmt.Errorf("invalid annotations value: %s", s) + glog.Warning(err) + return "", err + } + } + + return s, nil +} + +// ParseGeometries parses route service Geometries option. +func ParseGeometries(s string) (string, error) { + validGeometriesValues := map[string]struct{}{ + GeometriesValuePolyline: struct{}{}, + GeometriesValuePolyline6: struct{}{}, + GeometriesValueGeojson: struct{}{}, + } + + if _, found := validGeometriesValues[s]; !found { + err := fmt.Errorf("invalid geometries value: %s", s) + glog.Warning(err) + return "", err + } + return s, nil +} + +// ParseOverview parses route service Overview option. +func ParseOverview(s string) (string, error) { + validOverviewValues := map[string]struct{}{ + OverviewValueSimplified: struct{}{}, + OverviewValueFull: struct{}{}, + OverviewValueFalse: struct{}{}, + } + + if _, found := validOverviewValues[s]; !found { + err := fmt.Errorf("invalid overview value: %s", s) + glog.Warning(err) + return "", err + } + return s, nil +} + +// ParseContinueStraight parses route service ContinueStraight option. +func ParseContinueStraight(s string) (string, error) { + validContinueStraightValues := map[string]struct{}{ + ContinueStraightValueDefault: struct{}{}, + ContinueStraightValueFalse: struct{}{}, + ContinueStraightValueTrue: struct{}{}, + } + + if _, found := validContinueStraightValues[s]; !found { + err := fmt.Errorf("invalid continue_straight value: %s", s) + glog.Warning(err) + return "", err + } + return s, nil +} diff --git a/integration/pkg/api/osrm/route/options/values.go b/integration/pkg/api/osrm/route/options/values.go index b4e37b5a94c..83ba0178bdb 100644 --- a/integration/pkg/api/osrm/route/options/values.go +++ b/integration/pkg/api/osrm/route/options/values.go @@ -32,3 +32,30 @@ const ( AnnotationsDefaultValue = AnnotationsValueFalse // default ) + +// Geometries values +const ( + GeometriesValuePolyline = "polyline" + GeometriesValuePolyline6 = "polyline6" + GeometriesValueGeojson = "geojson" + + GeometriesDefaultValue = GeometriesValuePolyline +) + +// Overview values +const ( + OverviewValueSimplified = "simplified" + OverviewValueFull = "full" + OverviewValueFalse = "false" + + OverviewDefaultValue = OverviewValueSimplified +) + +// ContinueStraight values +const ( + ContinueStraightValueDefault = "default" + ContinueStraightValueTrue = ValueTrue + ContinueStraightValueFalse = ValueFalse + + ContinueStraightDefaultValue = ContinueStraightValueDefault +) diff --git a/integration/pkg/api/osrm/route/request.go b/integration/pkg/api/osrm/route/request.go index a7e3fede016..18fdfa7e292 100644 --- a/integration/pkg/api/osrm/route/request.go +++ b/integration/pkg/api/osrm/route/request.go @@ -8,6 +8,8 @@ import ( "strconv" "strings" + "github.com/Telenav/osrm-backend/integration/pkg/api/osrm/genericoptions" + "github.com/golang/glog" "github.com/Telenav/osrm-backend/integration/pkg/api" @@ -17,30 +19,56 @@ import ( // Request represent OSRM api v1 route request parameters. type Request struct { + + // Path Service string Version string Profile string Coordinates coordinate.Coordinates - // Route service query parameters - Alternatives string - Steps bool - Annotations string - - //TODO: other parameters + // generic parameters + Bearings genericoptions.Elements + Radiuses genericoptions.Elements + GenerateHints bool + Hints genericoptions.Elements + Approaches genericoptions.Elements + Exclude genericoptions.Classes + // Route service query parameters + Alternatives string + Steps bool + Annotations string + Geometries string + Overview string + ContinueStraight string + Waypoints coordinate.Indexes } // NewRequest create an empty route Request. func NewRequest() *Request { return &Request{ - Service: "route", - Version: "v1", - Profile: "driving", - Coordinates: coordinate.Coordinates{}, - Alternatives: options.AlternativesDefaultValue, - Steps: options.StepsDefaultValue, - Annotations: options.AnnotationsDefaultValue, + // Path + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{}, + + // generic options + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + + // route options + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, + Waypoints: coordinate.Indexes{}, } } @@ -56,17 +84,20 @@ func ParseRequestURI(requestURI string) (*Request, error) { } // ParseRequestURL parse Request URL to Request. -func ParseRequestURL(url *url.URL) (*Request, error) { - if url == nil { +func ParseRequestURL(u *url.URL) (*Request, error) { + if u == nil { return nil, fmt.Errorf("empty URL input") } req := NewRequest() - if err := req.parsePath(url.Path); err != nil { + if err := req.parsePath(u.Path); err != nil { return nil, err } - req.parseQuery(url.Query()) + + //NOTE: url.Query() will also use ";" as seprator, which is not expected. So we implements our own version instead. + //req.parseQuery(u.Query()) + req.parseQuery(api.ParseQueryDiscardError(u.RawQuery)) return req, nil } @@ -76,6 +107,27 @@ func (r *Request) QueryValues() (v url.Values) { v = make(url.Values) + // generic options + if len(r.Bearings) > 0 { + v.Add(genericoptions.KeyBearings, r.Bearings.String()) + } + if len(r.Radiuses) > 0 { + v.Add(genericoptions.KeyRadiuses, r.Radiuses.String()) + } + if r.GenerateHints != genericoptions.GenerateHintsDefaultValue { + v.Add(genericoptions.KeyGenerateHints, strconv.FormatBool(r.GenerateHints)) + } + if len(r.Hints) > 0 { + v.Add(genericoptions.KeyHints, r.Hints.String()) + } + if len(r.Approaches) > 0 { + v.Add(genericoptions.KeyApproaches, r.Approaches.String()) + } + if len(r.Exclude) > 0 { + v.Add(genericoptions.KeyExclude, r.Exclude.String()) + } + + // route options if r.Alternatives != options.AlternativesDefaultValue { v.Add(options.KeyAlternatives, r.Alternatives) } @@ -85,13 +137,31 @@ func (r *Request) QueryValues() (v url.Values) { if r.Annotations != options.AnnotationsDefaultValue { v.Add(options.KeyAnnotations, r.Annotations) } + if r.Geometries != options.GeometriesDefaultValue { + v.Add(options.KeyGeometries, r.Geometries) + } + if r.Overview != options.OverviewDefaultValue { + v.Add(options.KeyOverview, r.Overview) + } + if r.ContinueStraight != options.ContinueStraightDefaultValue { + v.Add(options.KeyContinueStraight, r.ContinueStraight) + } + if len(r.Waypoints) > 0 { + v.Add(options.KeyWaypoints, r.Waypoints.String()) + } return } -// QueryString convert RouteRequest to "URL encoded" form ("bar=baz&foo=quux") . +// QueryString convert RouteRequest to "URL encoded" form ("bar=baz&foo=quux"), but NOT escape. func (r *Request) QueryString() string { - return r.QueryValues().Encode() + rawQuery := r.QueryValues().Encode() + query, err := url.QueryUnescape(rawQuery) + if err != nil { + glog.Warning(err) + return rawQuery // use rawQuery if unescape fail + } + return query } // RequestURI convert RouteRequest to RequestURI (e.g. "/path?foo=bar"). @@ -114,7 +184,7 @@ func (r *Request) RequestURI() string { // AlternativesNumber returns alternatives as number value. func (r *Request) AlternativesNumber() int { - _, n, _ := parseAlternatives(r.Alternatives) + _, n, _ := options.ParseAlternatives(r.Alternatives) return n } @@ -146,67 +216,71 @@ func (r *Request) parsePath(path string) error { func (r *Request) parseQuery(values url.Values) { + if v := values.Get(genericoptions.KeyBearings); len(v) > 0 { + if bearings, err := genericoptions.ParseElemenets(v); err == nil { + r.Bearings = bearings + } + } + if v := values.Get(genericoptions.KeyRadiuses); len(v) > 0 { + if radiuses, err := genericoptions.ParseElemenets(v); err == nil { + r.Radiuses = radiuses + } + } + if v := values.Get(genericoptions.KeyGenerateHints); len(v) > 0 { + if generateHints, err := genericoptions.ParseGenerateHints(v); err == nil { + r.GenerateHints = generateHints + } + } + if v := values.Get(genericoptions.KeyHints); len(v) > 0 { + if hints, err := genericoptions.ParseElemenets(v); err == nil { + r.Hints = hints + } + } + if v := values.Get(genericoptions.KeyApproaches); len(v) > 0 { + if approaches, err := genericoptions.ParseElemenets(v); err == nil { + r.Approaches = approaches + } + } + if v := values.Get(genericoptions.KeyExclude); len(v) > 0 { + if classes, err := genericoptions.ParseClasses(v); err == nil { + r.Exclude = classes + } + } + if v := values.Get(options.KeyAlternatives); len(v) > 0 { - if alternatives, _, err := parseAlternatives(v); err == nil { + if alternatives, _, err := options.ParseAlternatives(v); err == nil { r.Alternatives = alternatives } } - if v := values.Get(options.KeySteps); len(v) > 0 { - if b, err := strconv.ParseBool(v); err == nil { + if b, err := options.ParseSteps(v); err == nil { r.Steps = b - } else { - glog.Warning(err) } } - if v := values.Get(options.KeyAnnotations); len(v) > 0 { - if annotations, err := parseAnnotations(v); err == nil { + if annotations, err := options.ParseAnnotations(v); err == nil { r.Annotations = annotations } } - -} - -func parseAlternatives(s string) (string, int, error) { - - if n, err := strconv.ParseUint(s, 10, 32); err == nil { - return s, int(n), nil + if v := values.Get(options.KeyGeometries); len(v) > 0 { + if geometries, err := options.ParseGeometries(v); err == nil { + r.Geometries = geometries + } } - if b, err := strconv.ParseBool(s); err == nil { - if b { - return s, 2, nil // true : 2 + if v := values.Get(options.KeyOverview); len(v) > 0 { + if overview, err := options.ParseOverview(v); err == nil { + r.Overview = overview } - return s, 1, nil // false : 1 } - - err := fmt.Errorf("invalid alternatives value: %s", s) - glog.Warning(err) - return "", 1, err // use value 1 if fail -} - -func parseAnnotations(s string) (string, error) { - - validAnnotationsValues := map[string]struct{}{ - options.AnnotationsValueTrue: struct{}{}, - options.AnnotationsValueFalse: struct{}{}, - options.AnnotationsValueNodes: struct{}{}, - options.AnnotationsValueDistance: struct{}{}, - options.AnnotationsValueDuration: struct{}{}, - options.AnnotationsValueDataSources: struct{}{}, - options.AnnotationsValueWeight: struct{}{}, - options.AnnotationsValueSpeed: struct{}{}, + if v := values.Get(options.KeyContinueStraight); len(v) > 0 { + if continueStraight, err := options.ParseContinueStraight(v); err == nil { + r.ContinueStraight = continueStraight + } } - - splits := strings.Split(s, api.Comma) - for _, split := range splits { - if _, found := validAnnotationsValues[split]; !found { - - err := fmt.Errorf("invalid annotations value: %s", s) - glog.Warning(err) - return "", err + if v := values.Get(options.KeyWaypoints); len(v) > 0 { + if indexes, err := coordinate.PraseIndexes(v); err == nil { + r.Waypoints = indexes } } - return s, nil } diff --git a/integration/pkg/api/osrm/route/request_test.go b/integration/pkg/api/osrm/route/request_test.go index 420994a1061..e35daaa650d 100644 --- a/integration/pkg/api/osrm/route/request_test.go +++ b/integration/pkg/api/osrm/route/request_test.go @@ -4,6 +4,8 @@ import ( "reflect" "testing" + "github.com/Telenav/osrm-backend/integration/pkg/api/osrm/genericoptions" + "github.com/Telenav/osrm-backend/integration/pkg/api/osrm/coordinate" "github.com/Telenav/osrm-backend/integration/pkg/api/osrm/route/options" ) @@ -15,51 +17,305 @@ func TestRouteRequestURI(t *testing.T) { }{ { Request{ - Service: "route", - Version: "v1", - Profile: "driving", - Coordinates: coordinate.Coordinates{coordinate.Coordinate{37.364336, -122.006349}, coordinate.Coordinate{37.313767, -121.875654}}, - Alternatives: options.AlternativesDefaultValue, - Steps: options.StepsDefaultValue, - Annotations: options.AnnotationsDefaultValue, + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, }, "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767", }, { Request{ - Service: "route", - Version: "v1", - Profile: "driving", - Coordinates: coordinate.Coordinates{coordinate.Coordinate{37.364336, -122.006349}, coordinate.Coordinate{37.313767, -121.875654}}, - Alternatives: options.AlternativesValueTrue, - Steps: options.StepsDefaultValue, - Annotations: options.AnnotationsDefaultValue, + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: options.AlternativesValueTrue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, }, "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?alternatives=true", }, { Request{ - Service: "route", - Version: "v1", - Profile: "driving", - Coordinates: coordinate.Coordinates{coordinate.Coordinate{37.364336, -122.006349}, coordinate.Coordinate{37.313767, -121.875654}}, - Alternatives: "100", - Steps: true, - Annotations: options.AnnotationsDefaultValue, + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesValuePolyline6, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, + }, + "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?geometries=polyline6", + }, + { + Request{ + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewValueFull, + ContinueStraight: options.ContinueStraightDefaultValue, + }, + "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?overview=full", + }, + { + Request{ + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightValueFalse, + }, + "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?continue_straight=false", + }, + { + Request{ + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, + Waypoints: coordinate.Indexes{0, 3, 5}, + }, + "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?waypoints=0;3;5", + }, + { + Request{ + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{"0,0", "120,180", "360,150"}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, + Waypoints: coordinate.Indexes{}, + }, + "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?bearings=0,0;120,180;360,150", + }, + { + Request{ + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{"1.2", "0.6", "1000.1", "unlimited"}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, + Waypoints: coordinate.Indexes{}, + }, + "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?radiuses=1.2;0.6;1000.1;unlimited", + }, + { + Request{ + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: false, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, + Waypoints: coordinate.Indexes{}, + }, + "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?generate_hints=false", + }, + { + Request{ + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{"LZ42gzaeNoMwAAAABwAAAAkAAAB3AAAAIccvQp1vtUDjiPpAQ9TUQjAAAAAHAAAACQAAAHcAAAC1VgAArlS6-DEjOgKzVLr4cCI6AgEAjwXzVESk", "NoM5g0CDOYMAAAAAKgAAAAAAAAApAAAAAAAAAJ_1F0IAAAAAkhEUQgAAAAAqAAAAAAAAACkAAAC1VgAA-FK8-GZeOQI6U7z451w5AgAA7wLzVESk"}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, + Waypoints: coordinate.Indexes{}, + }, + "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?hints=LZ42gzaeNoMwAAAABwAAAAkAAAB3AAAAIccvQp1vtUDjiPpAQ9TUQjAAAAAHAAAACQAAAHcAAAC1VgAArlS6-DEjOgKzVLr4cCI6AgEAjwXzVESk;NoM5g0CDOYMAAAAAKgAAAAAAAAApAAAAAAAAAJ_1F0IAAAAAkhEUQgAAAAAqAAAAAAAAACkAAAC1VgAA-FK8-GZeOQI6U7z451w5AgAA7wLzVESk", + }, + { + Request{ + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{"curb", "curb", "unrestricted"}, + Exclude: genericoptions.Classes{}, + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, + Waypoints: coordinate.Indexes{}, + }, + "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?approaches=curb;curb;unrestricted", + }, + { + Request{ + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{"toll", "motoway", "ferry"}, + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, + Waypoints: coordinate.Indexes{}, + }, + "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?exclude=toll,motoway,ferry", + }, + { + Request{ + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: "100", + Steps: true, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, }, "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?alternatives=100&steps=true", }, { Request{ - Service: "route", - Version: "v1", - Profile: "driving", - Coordinates: coordinate.Coordinates{coordinate.Coordinate{37.364336, -122.006349}, coordinate.Coordinate{37.313767, -121.875654}}, - Alternatives: "5", - Steps: true, - Annotations: options.AnnotationsValueTrue, + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: "5", + Steps: true, + Annotations: options.AnnotationsValueTrue, + Geometries: options.GeometriesValueGeojson, + Overview: options.OverviewValueFalse, + ContinueStraight: options.ContinueStraightValueTrue, + Waypoints: coordinate.Indexes{2, 3}, }, - "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?alternatives=5&annotations=true&steps=true", + "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?alternatives=5&annotations=true&continue_straight=true&geometries=geojson&overview=false&steps=true&waypoints=2;3", }, } @@ -80,80 +336,140 @@ func TestParseRouteRequest(t *testing.T) { expectFail bool }{ { - "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?&alternatives=5&annotations=true&steps=true", + "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?&alternatives=5&annotations=true&steps=true&geometries=polyline&overview=false&continue_straight=true&waypoints=2;4;6&bearings=0,0;120,180;360,150&radiuses=1.2;0.6;1000.1;unlimited&generate_hints=false&hints=LZ42gzaeNoMwAAAABwAAAAkAAAB3AAAAIccvQp1vtUDjiPpAQ9TUQjAAAAAHAAAACQAAAHcAAAC1VgAArlS6-DEjOgKzVLr4cCI6AgEAjwXzVESk;NoM5g0CDOYMAAAAAKgAAAAAAAAApAAAAAAAAAJ_1F0IAAAAAkhEUQgAAAAAqAAAAAAAAACkAAAC1VgAA-FK8-GZeOQI6U7z451w5AgAA7wLzVESk&approaches=curb;curb;unrestricted&exclude=toll,motoway,ferry", &Request{ - Service: "route", - Version: "v1", - Profile: "driving", - Coordinates: coordinate.Coordinates{coordinate.Coordinate{37.364336, -122.006349}, coordinate.Coordinate{37.313767, -121.875654}}, - Alternatives: "5", - Steps: true, - Annotations: options.AnnotationsValueTrue, + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{"0,0", "120,180", "360,150"}, + Radiuses: genericoptions.Elements{"1.2", "0.6", "1000.1", "unlimited"}, + GenerateHints: false, + Hints: genericoptions.Elements{"LZ42gzaeNoMwAAAABwAAAAkAAAB3AAAAIccvQp1vtUDjiPpAQ9TUQjAAAAAHAAAACQAAAHcAAAC1VgAArlS6-DEjOgKzVLr4cCI6AgEAjwXzVESk", "NoM5g0CDOYMAAAAAKgAAAAAAAAApAAAAAAAAAJ_1F0IAAAAAkhEUQgAAAAAqAAAAAAAAACkAAAC1VgAA-FK8-GZeOQI6U7z451w5AgAA7wLzVESk"}, + Approaches: genericoptions.Elements{"curb", "curb", "unrestricted"}, + Exclude: genericoptions.Classes{"toll", "motoway", "ferry"}, + Alternatives: "5", + Steps: true, + Annotations: options.AnnotationsValueTrue, + Geometries: options.GeometriesValuePolyline, + Overview: options.OverviewValueFalse, + ContinueStraight: options.ContinueStraightValueTrue, + Waypoints: coordinate.Indexes{2, 4, 6}, }, false, }, { - "http://localhost:8080/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?alternatives=5&annotations=true&steps=true", + "http://localhost:8080/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?alternatives=5&annotations=true&steps=true&geometries=polyline&overview=false&continue_straight=true&waypoints=2;4;6&bearings=0,0;120,180;360,150&radiuses=1.2;0.6;1000.1;unlimited&generate_hints=false&hints=LZ42gzaeNoMwAAAABwAAAAkAAAB3AAAAIccvQp1vtUDjiPpAQ9TUQjAAAAAHAAAACQAAAHcAAAC1VgAArlS6-DEjOgKzVLr4cCI6AgEAjwXzVESk;NoM5g0CDOYMAAAAAKgAAAAAAAAApAAAAAAAAAJ_1F0IAAAAAkhEUQgAAAAAqAAAAAAAAACkAAAC1VgAA-FK8-GZeOQI6U7z451w5AgAA7wLzVESk&approaches=curb;curb;unrestricted&exclude=toll,motoway,ferry", &Request{ - Service: "route", - Version: "v1", - Profile: "driving", - Coordinates: coordinate.Coordinates{coordinate.Coordinate{37.364336, -122.006349}, coordinate.Coordinate{37.313767, -121.875654}}, - Alternatives: "5", - Steps: true, - Annotations: options.AnnotationsValueTrue, + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{"0,0", "120,180", "360,150"}, + Radiuses: genericoptions.Elements{"1.2", "0.6", "1000.1", "unlimited"}, + GenerateHints: false, + Hints: genericoptions.Elements{"LZ42gzaeNoMwAAAABwAAAAkAAAB3AAAAIccvQp1vtUDjiPpAQ9TUQjAAAAAHAAAACQAAAHcAAAC1VgAArlS6-DEjOgKzVLr4cCI6AgEAjwXzVESk", "NoM5g0CDOYMAAAAAKgAAAAAAAAApAAAAAAAAAJ_1F0IAAAAAkhEUQgAAAAAqAAAAAAAAACkAAAC1VgAA-FK8-GZeOQI6U7z451w5AgAA7wLzVESk"}, + Approaches: genericoptions.Elements{"curb", "curb", "unrestricted"}, + Exclude: genericoptions.Classes{"toll", "motoway", "ferry"}, + Alternatives: "5", + Steps: true, + Annotations: options.AnnotationsValueTrue, + Geometries: options.GeometriesValuePolyline, + Overview: options.OverviewValueFalse, + ContinueStraight: options.ContinueStraightValueTrue, + Waypoints: coordinate.Indexes{2, 4, 6}, }, false, }, { "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767", &Request{ - Service: "route", - Version: "v1", - Profile: "driving", - Coordinates: coordinate.Coordinates{coordinate.Coordinate{37.364336, -122.006349}, coordinate.Coordinate{37.313767, -121.875654}}, - Alternatives: options.AlternativesDefaultValue, - Steps: options.StepsDefaultValue, - Annotations: options.AnnotationsDefaultValue, + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, + Waypoints: coordinate.Indexes{}, }, false, }, { "http://localhost:8080/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767", &Request{ - Service: "route", - Version: "v1", - Profile: "driving", - Coordinates: coordinate.Coordinates{coordinate.Coordinate{37.364336, -122.006349}, coordinate.Coordinate{37.313767, -121.875654}}, - Alternatives: options.AlternativesDefaultValue, - Steps: options.StepsDefaultValue, - Annotations: options.AnnotationsDefaultValue, + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, + Waypoints: coordinate.Indexes{}, }, false, }, { "route/v1/driving/-122.006349,37.364336;-121.875654,37.313767", &Request{ - Service: "route", - Version: "v1", - Profile: "driving", - Coordinates: coordinate.Coordinates{coordinate.Coordinate{37.364336, -122.006349}, coordinate.Coordinate{37.313767, -121.875654}}, - Alternatives: options.AlternativesDefaultValue, - Steps: options.StepsDefaultValue, - Annotations: options.AnnotationsDefaultValue, + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, + Waypoints: coordinate.Indexes{}, }, false, }, { "/route/v1/driving/-122.006349,37.364336;-121.875654,37.313767?alternatives=-1&annotations=tru,&steps=alse,", &Request{ - Service: "route", - Version: "v1", - Profile: "driving", - Coordinates: coordinate.Coordinates{coordinate.Coordinate{37.364336, -122.006349}, coordinate.Coordinate{37.313767, -121.875654}}, - Alternatives: options.AlternativesDefaultValue, - Steps: options.StepsDefaultValue, - Annotations: options.AnnotationsDefaultValue, + Service: "route", + Version: "v1", + Profile: "driving", + Coordinates: coordinate.Coordinates{coordinate.Coordinate{Lat: 37.364336, Lon: -122.006349}, coordinate.Coordinate{Lat: 37.313767, Lon: -121.875654}}, + Bearings: genericoptions.Elements{}, + Radiuses: genericoptions.Elements{}, + GenerateHints: genericoptions.GenerateHintsDefaultValue, + Hints: genericoptions.Elements{}, + Approaches: genericoptions.Elements{}, + Exclude: genericoptions.Classes{}, + Alternatives: options.AlternativesDefaultValue, + Steps: options.StepsDefaultValue, + Annotations: options.AnnotationsDefaultValue, + Geometries: options.GeometriesDefaultValue, + Overview: options.OverviewDefaultValue, + ContinueStraight: options.ContinueStraightDefaultValue, + Waypoints: coordinate.Indexes{}, }, false, }, @@ -177,69 +493,3 @@ func TestParseRouteRequest(t *testing.T) { } } - -func TestParseAlternatives(t *testing.T) { - - cases := []struct { - s string - expect string - expectNum int - expectFail bool - }{ - {"true", "true", 2, false}, - {"false", "false", 1, false}, - {"0", "0", 0, false}, // same as false - {"1", "1", 1, false}, - {"5", "5", 5, false}, - {"100", "100", 100, false}, - {"111111111", "111111111", 111111111, false}, - {"-1", "", 1, true}, - } - - for _, c := range cases { - alternatives, num, err := parseAlternatives(c.s) - if err != nil && c.expectFail { - continue //right - } else if (err != nil && !c.expectFail) || (err == nil && c.expectFail) { - t.Errorf("parse %s expect fail %t, but got err %v", c.s, c.expectFail, err) - continue - } - - if alternatives != c.expect || num != c.expectNum { - t.Errorf("parse %s, expect %s %d, but got %s %d", c.s, c.expect, c.expectNum, alternatives, num) - } - } -} - -func TestParseAnnotations(t *testing.T) { - - cases := []struct { - s string - expect string - expectFail bool - }{ - {"true", options.ValueTrue, false}, - {"false", options.ValueFalse, false}, - {"nodes", "nodes", false}, - {"nodes,distance", "nodes,distance", false}, - {"nodes,distance,duration,datasources,weight,speed", "nodes,distance,duration,datasources,weight,speed", false}, - {"nodes,", "", true}, - {"nodes,distances", "", true}, - {"", "", true}, - } - - for _, c := range cases { - annotations, err := parseAnnotations(c.s) - if err != nil && c.expectFail { - continue //right - } else if (err != nil && !c.expectFail) || (err == nil && c.expectFail) { - t.Errorf("parse %s expect fail %t, but got err %v", c.s, c.expectFail, err) - continue - } - - if !reflect.DeepEqual(annotations, c.expect) { - t.Errorf("parse %s, expect %v, but got %v", c.s, c.expect, annotations) - } - } - -} diff --git a/integration/pkg/api/url_parse_query.go b/integration/pkg/api/url_parse_query.go new file mode 100644 index 00000000000..cf25fb1c29f --- /dev/null +++ b/integration/pkg/api/url_parse_query.go @@ -0,0 +1,39 @@ +package api + +import ( + "fmt" + "net/url" + "strings" +) + +// ParseQuery is similar with url.ParseQuery(https://golang.org/pkg/net/url/#ParseQuery). +// The only difference is that url.ParseQuery uses both `&` and `;` as seperator, +// in contrast ParseQuery only uses `&`. +func ParseQuery(rawQuery string) (url.Values, error) { + values := url.Values{} + + queryStr, err := url.QueryUnescape(rawQuery) + if err != nil { + return values, err + } + + for _, s := range strings.Split(queryStr, Ampersand) { + keyValues := strings.Split(s, EqualTo) + if len(keyValues) != 2 { + if err == nil { // return err describes the first decoding error encountered, if any. + err = fmt.Errorf("invalid query key-value %s", s) + } + continue + } + values[keyValues[0]] = []string{keyValues[1]} + } + return values, err +} + +// ParseQueryDiscardError is similar with url.Query(https://golang.org/pkg/net/url/#URL.Query), +// which parses RawQuery and returns the corresponding values. +// It silently discards malformed value pairs. To check errors use ParseQuery. +func ParseQueryDiscardError(rawQuery string) url.Values { + v, _ := ParseQuery(rawQuery) + return v +} diff --git a/integration/pkg/api/url_parse_query_test.go b/integration/pkg/api/url_parse_query_test.go new file mode 100644 index 00000000000..80d7aa292fe --- /dev/null +++ b/integration/pkg/api/url_parse_query_test.go @@ -0,0 +1,24 @@ +package api + +import ( + "net/url" + "reflect" + "testing" +) + +func TestParseQuery(t *testing.T) { + cases := []struct { + s string + url.Values + }{ + {"waypoint=1", map[string][]string{"waypoint": []string{"1"}}}, + {"waypoint=1;2;3", map[string][]string{"waypoint": []string{"1;2;3"}}}, + } + + for _, c := range cases { + v, _ := ParseQuery(c.s) + if !reflect.DeepEqual(v, c.Values) { + t.Errorf("parse %s, expect %v but got %v", c.s, c.Values, v) + } + } +}