Skip to content

Commit

Permalink
delivery: implemented Client.ListDeliveries
Browse files Browse the repository at this point in the history
Updates #32

* Implemented Client.ListDeliveries.
* Introduced scope oauth2.ScopeDelivery.

NB:
* ListDeliveries hits the /v1 API instead of
/v1.2

Sample usage:
```go
func main() {
	client, err := uber.NewClientFromOAuth2File(os.ExpandEnv("$HOME/.uber/credentials.json"))
	if err != nil {
		log.Fatal(err)
	}

	delivRes, err := client.ListDeliveries(&uber.DeliveryListRequest{
		Status:      uber.StatusCompleted,
		StartOffset: 20,
	})
	if err != nil {
		log.Fatal(err)
	}

	itemCount := uint64(0)
	for page := range delivRes.Pages {
		if page.Err != nil {
			fmt.Printf("Page #%d err: %v", page.PageNumber, page.Err)
		}
		for i, delivery := range page.Deliveries {
			fmt.Printf("\t(%d): %#v\n", i, delivery)
			itemCount += 1
		}
		if itemCount >= 10 {
			delivRes.Cancel()
		}
	}
}
```
  • Loading branch information
odeke-em committed Jul 5, 2017
1 parent 294214b commit 18b827c
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 13 deletions.
2 changes: 1 addition & 1 deletion cmd/uber/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func authorize() {
scopes := []string{
oauth2.ScopeProfile, oauth2.ScopeRequest,
oauth2.ScopeHistory, oauth2.ScopePlaces,
oauth2.ScopeRequestReceipt,
oauth2.ScopeRequestReceipt, oauth2.ScopeDelivery,
}

token, err := oauth2.AuthorizeByEnvApp(scopes...)
Expand Down
35 changes: 29 additions & 6 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,6 @@ func Example_client_EstimatePrice() {
cancelPaging()
}
}
// Output:
// WW
}

func Example_client_EstimateTime() {
Expand Down Expand Up @@ -184,8 +182,6 @@ func Example_client_EstimateTime() {
cancelPaging()
}
}
// Output:
// WW
}

func Example_client_RetrieveMyProfile() {
Expand Down Expand Up @@ -456,8 +452,6 @@ func Example_client_ListProducts() {
for i, product := range products {
fmt.Printf("#%d: ID: %q Product: %#v\n", i, product.ID, product)
}
// Output:
// WW
}

func Example_client_ProductByID() {
Expand All @@ -473,3 +467,32 @@ func Example_client_ProductByID() {

fmt.Printf("The Product information: %#v\n", product)
}

func Example_client_ListDeliveries() {
client, err := uber.NewClientFromOAuth2File(os.ExpandEnv("$HOME/.uber/credentials.json"))
if err != nil {
log.Fatal(err)
}

delivRes, err := client.ListDeliveries(&uber.DeliveryListRequest{
Status: uber.StatusCompleted,
StartOffset: 20,
})
if err != nil {
log.Fatal(err)
}

itemCount := uint64(0)
for page := range delivRes.Pages {
if page.Err != nil {
fmt.Printf("Page #%d err: %v", page.PageNumber, page.Err)
}
for i, delivery := range page.Deliveries {
fmt.Printf("\t(%d): %#v\n", i, delivery)
itemCount += 1
}
if itemCount >= 10 {
delivRes.Cancel()
}
}
}
4 changes: 4 additions & 0 deletions oauth2/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ const (
// including pickup, destination and real-time
// location for all of your future rides.
ScopeAllTrips = "all_trips"

// ScopeDelivery is a privileged scope that gives
// access to the authenticated user's deliveries.
ScopeDelivery = "delivery"
)

func AuthorizeByEnvApp(scopes ...string) (*oauth2.Token, error) {
Expand Down
16 changes: 16 additions & 0 deletions v1/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@ func (c *Client) baseURL() string {
}
}

// Some endpoints require us to hit /v1 instead of /v1.2 as in Client.baseURL.
// These endpoints include:
// + ListDeliveries --> /v1/deliveries at least as of "Tue 4 Jul 2017 23:17:14 MDT"
func (c *Client) legacyV1BaseURL() string {
// Setting the baseURLs in here to ensure that no-one mistakenly
// directly invokes baseURL or sandboxBaseURL.
c.RLock()
defer c.RUnlock()

if c.sandboxed {
return "https://sandbox-api.uber.com/v1"
} else { // Invoking the production endpoint
return "https://api.uber.com/v1"
}
}

func NewClient(tokens ...string) (*Client, error) {
if token := otils.FirstNonEmptyString(tokens...); token != "" {
return &Client{token: token}, nil
Expand Down
154 changes: 151 additions & 3 deletions v1/deliveries.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"

"github.com/orijtech/otils"
)
Expand Down Expand Up @@ -98,7 +100,7 @@ type Phone struct {

type CurrencyCode string

type DeliveryResponse struct {
type Delivery struct {
ID string `json:"delivery_id"`
Fee float32 `json:"fee"`
QuoteID string `json:"quote_id"`
Expand Down Expand Up @@ -196,7 +198,7 @@ func (e *Endpoint) Validate() error {
return nil
}

func (c *Client) RequestDelivery(req *DeliveryRequest) (*DeliveryResponse, error) {
func (c *Client) RequestDelivery(req *DeliveryRequest) (*Delivery, error) {
if err := req.Validate(); err != nil {
return nil, err
}
Expand All @@ -215,7 +217,7 @@ func (c *Client) RequestDelivery(req *DeliveryRequest) (*DeliveryResponse, error
if err != nil {
return nil, err
}
dRes := new(DeliveryResponse)
dRes := new(Delivery)
if err := json.Unmarshal(blob, dRes); err != nil {
return nil, err
}
Expand All @@ -240,3 +242,149 @@ func (c *Client) CancelDelivery(deliveryID string) error {
_, _, err = c.doHTTPReq(httpReq)
return err
}

type DeliveryListRequest struct {
Status Status `json:"status,omitempty"`
LimitPerPage int64 `json:"limit"`
MaxPageNumber int64 `json:"max_page,omitempty"`
StartOffset int64 `json:"offset"`

ThrottleDurationMs int64 `json:"throttle_duration_ms"`
}

type DeliveryThread struct {
Pages chan *DeliveryPage `json:"-"`
Cancel func()
}

type DeliveryPage struct {
Err error `json:"error"`
PageNumber int64 `json:"page_number,omitempty"`
Deliveries []*Delivery `json:"deliveries,omitempty"`
}

type recvDelivery struct {
Count int64 `json:"count"`
NextPageQuery string `json:"next_page"`
PreviousPageQuery string `json:"previous_page"`
Deliveries []*Delivery `json:"deliveries"`
}

type deliveryPager struct {
Offset int64 `json:"offset"`
Limit int64 `json:"limit"`
Status Status `json:"status"`
}

const (
NoThrottle = -1

defaultThrottleDurationMs = 150 * time.Millisecond
)

// ListDeliveries requires authorization with OAuth2.0 with
// the delivery scope set.
func (c *Client) ListDeliveries(dReq *DeliveryListRequest) (*DeliveryThread, error) {
if dReq == nil {
dReq = &DeliveryListRequest{Status: StatusReceiptReady}
}

baseURL := c.legacyV1BaseURL()
fullURL := fmt.Sprintf("%s/deliveries", baseURL)
qv, err := otils.ToURLValues(&deliveryPager{
Limit: dReq.LimitPerPage,
Status: dReq.Status,
Offset: dReq.StartOffset,
})
if err != nil {
return nil, err
}

if len(qv) > 0 {
fullURL = fmt.Sprintf("%s/deliveries?%s", baseURL, qv.Encode())
}

parsedURL, err := url.Parse(fullURL)
if err != nil {
return nil, err
}
parsedBaseURL, err := url.Parse(baseURL)
if err != nil {
return nil, err
}

var errsList []string
if want, got := parsedBaseURL.Scheme, parsedURL.Scheme; got != want {
errsList = append(errsList, fmt.Sprintf("gotScheme=%q wantBaseScheme=%q", got, want))
}
if want, got := parsedBaseURL.Host, parsedURL.Host; got != want {
errsList = append(errsList, fmt.Sprintf("gotHost=%q wantBaseHost=%q", got, want))
}
if len(errsList) > 0 {
return nil, errors.New(strings.Join(errsList, "\n"))
}

maxPage := dReq.MaxPageNumber
pageExceeded := func(pageNumber int64) bool {
return maxPage > 0 && pageNumber >= maxPage
}

fullDeliveriesBaseURL := fmt.Sprintf("%s/deliveries", baseURL)
resChan := make(chan *DeliveryPage)
cancelChan, cancelFn := makeCancelParadigm()

go func() {
defer close(resChan)

pageNumber := int64(0)
throttleDurationMs := defaultThrottleDurationMs
if dReq.ThrottleDurationMs == NoThrottle {
throttleDurationMs = 0
} else {
throttleDurationMs = time.Duration(dReq.ThrottleDurationMs) * time.Millisecond
}

for {
page := &DeliveryPage{PageNumber: pageNumber}

req, err := http.NewRequest("GET", fullURL, nil)
if err != nil {
page.Err = err
resChan <- page
return
}

slurp, _, err := c.doReq(req)
if err != nil {
page.Err = err
resChan <- page
return
}

recv := new(recvDelivery)
if err := json.Unmarshal(slurp, recv); err != nil {
page.Err = err
resChan <- page
return
}

page.Deliveries = recv.Deliveries
resChan <- page
pageNumber += 1
pageToken := recv.NextPageQuery
if pageExceeded(pageNumber) || pageToken == "" || len(recv.Deliveries) == 0 {
return
}

fullURL = fmt.Sprintf("%s?%s", fullDeliveriesBaseURL, pageToken)

select {
case <-cancelChan:
return
case <-time.After(throttleDurationMs):
}
}
}()

return &DeliveryThread{Cancel: cancelFn, Pages: resChan}, nil
}
Loading

0 comments on commit 18b827c

Please sign in to comment.