Skip to content

Commit

Permalink
feat: Add support for Route Import
Browse files Browse the repository at this point in the history
This implementation of RouteImport() only supports the same subset of parameters as Routes()
  • Loading branch information
parmus committed Aug 2, 2024
1 parent 6a62a76 commit 0469f16
Show file tree
Hide file tree
Showing 3 changed files with 306 additions and 0 deletions.
19 changes: 19 additions & 0 deletions routingv8/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,3 +505,22 @@ func (t *SpanAttribute) String() string {
return invalid
}
}

type RouteImportRequest struct {
TransportMode TransportMode
// Which attributes to return in the response.
// If not specified defaults to SummaryReturnAttribute.
Return []ReturnAttribute
// The time of departure.
// If not specified the current time is used.
// To not take time into account use DepartureTimeAny.
DepartureTime string
// Spans define which content attributes that are included in the response spans
Spans []SpanAttribute
// An array of GPS coordinates
Trace []GeoWaypoint
}

type RouteImportRequestBody struct {
Trace []GeoWaypoint `json:"trace"`
}
62 changes: 62 additions & 0 deletions routingv8/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package routingv8

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
Expand Down Expand Up @@ -77,6 +78,67 @@ func (s *RoutingService) Routes(
return &resp, nil
}

func (s *RoutingService) RouteImport(
ctx context.Context,
req *RouteImportRequest,
) (_ *RoutesResponse, err error) {
tm := req.TransportMode.String()
if tm == invalid || tm == unspecified {
return nil, fmt.Errorf("invalid transportmode")
}

if len(req.Trace) < 2 {
return nil, fmt.Errorf("trace parameter must contain at least 2 waypoints")
}

u, err := s.URL.Parse("import")
if err != nil {
return nil, err
}

values := make(url.Values)
returns := make([]string, 0, len(req.Return))
if len(req.Return) > 0 {
for _, attribute := range req.Return {
returns = append(returns, string(attribute))
}
} else {
returns = []string{string(SummaryReturnAttribute)}
}
values.Add("return", strings.Join(returns, ","))
if req.DepartureTime != "" {
values.Add("departureTime", req.DepartureTime)
}
values.Add("transportMode", tm)
if len(req.Spans) > 0 {
if !returnContains(req.Return, PolylineReturnAttribute) {
return nil, errors.New("spans parameter also requires that the polyline option is set in the return parameter")
}
spanStrings := make([]string, 0, len(req.Spans))
for _, span := range req.Spans {
spanStrings = append(spanStrings, string(span))
}
values.Add("spans", strings.Join(spanStrings, ","))
}

bytes, err := json.Marshal(&RouteImportRequestBody{
Trace: req.Trace,
})
if err != nil {
return nil, err
}

r, err := s.Client.NewRequest(ctx, u, http.MethodPost, values.Encode(), bytes)
if err != nil {
return nil, fmt.Errorf("unable to create get request: %v", err)
}
var resp RoutesResponse
if err := s.Client.Do(r, &resp); err != nil {
return nil, err
}
return &resp, nil
}

func returnContains(requested []ReturnAttribute, needle ReturnAttribute) bool {
for _, attr := range requested {
if attr == needle {
Expand Down
225 changes: 225 additions & 0 deletions routingv8/routes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,23 @@ import (

type RoutesMock struct {
requestRawQuery string
requestRawBody string
responseStatus int
responseBody routingv8.RoutesResponse
error *routingv8.HereErrorResponse
}

func (c *RoutesMock) Do(req *http.Request) (*http.Response, error) {
c.requestRawQuery = req.URL.RawQuery
if req.Body != nil {
body, err := io.ReadAll(req.Body)
if err != nil {
panic(err)
}
c.requestRawBody = string(body)
} else {
c.requestRawBody = ""
}
headers := http.Header{}
headers.Add("Content-Type", "application/json")
b, err := json.Marshal(c.responseBody)
Expand Down Expand Up @@ -248,3 +258,218 @@ func TestRoutingervice_Routes_Error(t *testing.T) {
assert.DeepEqual(t, responseError.Response, &exp)
assert.Check(t, responseError.HTTPBody != "")
}

func TestRoutingervice_RouteImport(t *testing.T) {
t.Parallel()
ctx := context.Background()

// Einride Gothenburg.
origin := routingv8.GeoWaypoint{
Elevation: 1,
Lat: 57.707752,
Long: 11.949767,
}
// Einride Stockholm.
destination := routingv8.GeoWaypoint{
Elevation: 2,
Lat: 59.337492,
Long: 18.063672,
}

exp := routingv8.RoutesResponse{
Routes: []routingv8.Route{
{
ID: "route-1",
Sections: []routingv8.Section{
{
ID: "section-1",
Type: "veicle",
Departure: routingv8.VehicleDeparture{
Place: routingv8.Place{
Type: "place",
Location: origin,
OriginalLocation: origin,
},
},
Arrival: routingv8.VehicleDeparture{
Place: routingv8.Place{
Type: "place",
Location: destination,
OriginalLocation: destination,
},
},
Summary: routingv8.Summary{
Duration: 243,
Length: 1206,
BaseDuration: 136,
},
},
},
},
},
}
httpClient := RoutesMock{responseBody: exp, responseStatus: 200}
routingClient := routingv8.NewClient(&httpClient)

got, err := routingClient.Routing.RouteImport(ctx, &routingv8.RouteImportRequest{
Trace: []routingv8.GeoWaypoint{
origin,
destination,
},
TransportMode: routingv8.TransportModeCar,
})
assert.NilError(t, err)
assert.DeepEqual(t, &exp, got)
}

func TestRoutingervice_RouteImport_QueryParams(t *testing.T) {
t.Parallel()
ctx := context.Background()

// Einride Gothenburg.
origin := routingv8.GeoWaypoint{
Lat: 57.707752,
Long: 11.949767,
}
// Einride Stockholm.
destination := routingv8.GeoWaypoint{
Lat: 59.337492,
Long: 18.063672,
}
traceBody := `{"trace":[{"lat":57.707752,"lng":11.949767},{"lat":59.337492,"lng":18.063672}]}`

for _, tt := range []struct {
name string
request *routingv8.RouteImportRequest
expectedURLParams string
expectedBody string
errStr string
}{
{
name: "minimal",
request: &routingv8.RouteImportRequest{
Trace: []routingv8.GeoWaypoint{
origin,
destination,
},
TransportMode: routingv8.TransportModeCar,
},
expectedURLParams: "return=summary&transportMode=car",
expectedBody: traceBody,
},
{
name: "multiple return attributes",
request: &routingv8.RouteImportRequest{
Trace: []routingv8.GeoWaypoint{
origin,
destination,
},
TransportMode: routingv8.TransportModeCar,
Return: []routingv8.ReturnAttribute{
routingv8.SummaryReturnAttribute,
routingv8.PolylineReturnAttribute,
},
},
expectedURLParams: "return=summary%2Cpolyline&transportMode=car",
expectedBody: traceBody,
},
{
name: "with spans",
request: &routingv8.RouteImportRequest{
Trace: []routingv8.GeoWaypoint{
origin,
destination,
},
TransportMode: routingv8.TransportModeCar,
Return: []routingv8.ReturnAttribute{
routingv8.SummaryReturnAttribute,
routingv8.PolylineReturnAttribute,
},
Spans: []routingv8.SpanAttribute{
routingv8.SpanAttributeNames,
routingv8.SpanAttributeMaxSpeed,
},
},
expectedURLParams: "return=summary%2Cpolyline&spans=names%2CmaxSpeed&transportMode=car",
expectedBody: traceBody,
},
{
name: "trace with too few point",
request: &routingv8.RouteImportRequest{
Trace: []routingv8.GeoWaypoint{
origin,
},
TransportMode: routingv8.TransportModeCar,
},
errStr: "trace parameter must contain at least 2 waypoints",
},
{
name: "with spans without wanted polyline returned",
request: &routingv8.RouteImportRequest{
Trace: []routingv8.GeoWaypoint{
origin,
destination,
},
TransportMode: routingv8.TransportModeCar,
Return: []routingv8.ReturnAttribute{
routingv8.SummaryReturnAttribute,
},
Spans: []routingv8.SpanAttribute{
routingv8.SpanAttributeNames,
routingv8.SpanAttributeMaxSpeed,
},
},
errStr: "spans parameter also requires that the polyline option is set in the return parameter",
},
} {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
client := RoutesMock{}
routingClient := routingv8.NewClient(&client)

_, err := routingClient.Routing.RouteImport(ctx, tt.request)
if tt.errStr != "" {
assert.ErrorContains(t, err, tt.errStr)
}
assert.Equal(t, client.requestRawQuery, tt.expectedURLParams)
assert.Equal(t, client.requestRawBody, tt.expectedBody)
})
}
}

func TestRoutingervice_RouteImport_Error(t *testing.T) {
t.Parallel()
ctx := context.Background()

// Einride Gothenburg.
origin := routingv8.GeoWaypoint{
Lat: 57.707752,
Long: 11.949767,
}
// Einride Stockholm.
destination := routingv8.GeoWaypoint{
Lat: 59.337492,
Long: 18.063672,
}

exp := routingv8.HereErrorResponse{
Title: "Mocked Error",
Status: 400,
}

httpClient := RoutesMock{responseStatus: 400, error: &exp}
routingClient := routingv8.NewClient(&httpClient)

_, err := routingClient.Routing.RouteImport(ctx, &routingv8.RouteImportRequest{
Trace: []routingv8.GeoWaypoint{
origin,
destination,
},
TransportMode: routingv8.TransportModeTruck,
})
var responseError *routingv8.ResponseError
assert.Check(t, errors.As(err, &responseError))
assert.DeepEqual(t, responseError.Response, &exp)
assert.Check(t, responseError.HTTPBody != "")
}

0 comments on commit 0469f16

Please sign in to comment.