Skip to content

Commit

Permalink
prices: UpfrontFare implemented
Browse files Browse the repository at this point in the history
Fixes #24.

Allows retrieval of UpfrontFare that can give fareID
to enable requestin an Uber. Also implemented helpers
for retrieving OAuth2.0 transports and integrating them
into the client.

```go
func main() {
	client, err := uber.NewClientFromOAuth2File("./testdata/.uber/credentials.json")
	if err != nil {
		log.Fatal(err)
	}

	upfrontFare, err := client.UpfrontFare(&uber.EstimateRequest{
		StartLatitude:  37.7752315,
		EndLatitude:    37.7752415,
		StartLongitude: -122.418075,
		EndLongitude:   -122.518075,
		SeatCount:      2,
	})
	if err != nil {
		log.Fatal(err)
	}

        if upfrontFare.SurgeInEffect() {
                fmt.Printf("Surge is in effect!\n")
                fmt.Printf("Please visit this URL to confirm %q then"+
                           "request again", upfrontFare.Estimate.SurgeConfirmationURL)
                return
        }

        fmt.Printf("FareID: %s\n", upfrontFare.Fare.ID)
	fmt.Printf("Fare: %#v\n", upfrontFare.Fare)
	fmt.Printf("FareEstimate: %#v\n", upfrontFare.Estimate)
	fmt.Printf("Trip: %#v\n", upfrontFare.Trip)
}
```
  • Loading branch information
odeke-em committed May 13, 2017
1 parent bc79b21 commit e28016a
Show file tree
Hide file tree
Showing 8 changed files with 398 additions and 22 deletions.
28 changes: 28 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,31 @@ func Example_client_OpenMap() {
log.Fatal(err)
}
}

func Example_client_UpfrontFare() {
client, err := uber.NewClientFromOAuth2File("./testdata/.uber/credentials.json")
if err != nil {
log.Fatal(err)
}

upfrontFare, err := client.UpfrontFare(&uber.EstimateRequest{
StartLatitude: 37.7752315,
EndLatitude: 37.7752415,
StartLongitude: -122.418075,
EndLongitude: -122.518075,
SeatCount: 2,
})
if err != nil {
log.Fatal(err)
}

if upfrontFare.SurgeInEffect() {
fmt.Printf("Surge is in effect!\n")
fmt.Printf("Please visit this URL to confirm %q then"+
"request again", upfrontFare.Estimate.SurgeConfirmationURL)
return
}

fmt.Printf("Fare: %#v\n", upfrontFare.Fare)
fmt.Printf("Trip: %#v\n", upfrontFare.Trip)
}
26 changes: 26 additions & 0 deletions oauth2/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ package oauth2

import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net/http"
"net/url"
"os"
"reflect"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -52,6 +55,29 @@ func TransportWithBase(token *oauth2.Token, base http.RoundTripper) *oauth2.Tran
return tr
}

var (
errNoOAuth2TokenDeserialized = errors.New("unable to deserialize an OAuth2.0 token")

blankOAuth2Token = oauth2.Token{}
)

func TransportFromFile(path string) (*oauth2.Transport, error) {
blob, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}

token := new(oauth2.Token)
if err := json.Unmarshal(blob, token); err != nil {
return nil, err
}
if reflect.DeepEqual(blankOAuth2Token, *token) {
return nil, errNoOAuth2TokenDeserialized
}

return Transport(token), nil
}

type tokenSourcer struct {
sync.RWMutex
token *oauth2.Token
Expand Down
8 changes: 8 additions & 0 deletions v1/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,11 @@ func NewClientFromOAuth2Token(token *oauth2.Token) (*Client, error) {
oauth2Transport := uberOAuth2.Transport(token)
return &Client{rt: oauth2Transport}, nil
}

func NewClientFromOAuth2File(tokenFilepath string) (*Client, error) {
oauth2Transport, err := uberOAuth2.TransportFromFile(tokenFilepath)
if err != nil {
return nil, err
}
return &Client{rt: oauth2Transport}, nil
}
5 changes: 5 additions & 0 deletions v1/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ type Trip struct {

ProductID string `json:"product_id,omitempty"`
RequestID string `json:"request_id,omitempty"`

Unit string `json:"distance_unit,omitempty"`

DurationEstimate otils.NullableFloat64 `json:"duration_estimate,omitempty"`
DistanceEstimate otils.NullableFloat64 `json:"distance_estimate,omitempty"`
}

type Place struct {
Expand Down
120 changes: 118 additions & 2 deletions v1/prices.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package uber

import (
"bytes"
"encoding/json"
"errors"
"fmt"
Expand All @@ -29,8 +30,17 @@ type EstimateRequest struct {
StartLongitude float64 `json:"start_longitude"`
EndLongitude float64 `json:"end_longitude"`
EndLatitude float64 `json:"end_latitude"`
SeatCount int `json:"seat_count"`
ProductID string `json:"product_id"`

SeatCount int `json:"seat_count"`

// ProductID is the UniqueID of the product
// being requested. If unspecified, it will
// default to the cheapest product for the
// given location.
ProductID string `json:"product_id"`

StartPlace PlaceName `json:"start_place_id"`
EndPlace PlaceName `json:"end_place_id"`

Pager
}
Expand Down Expand Up @@ -182,3 +192,109 @@ func (c *Client) EstimatePrice(ereq *EstimateRequest) (pagesChan chan *PriceEsti

return estimatesPageChan, cancelFn, nil
}

type FareEstimate struct {
SurgeConfirmationURL string `json:"surge_confirmation_href,omitempty"`
SurgeConfirmationID string `json:"surge_confirmation_id"`

// Breakdown provides details on how a fare came to be.
Breakdown []*FareBreakdown `json:"fare_breakdown,omitempty"`

SurgeMultiplier otils.NullableFloat64 `json:"surge_multiplier"`

CurrencyCode otils.NullableString `json:"currency_code"`
DisplayAmount otils.NullableString `json:"display"`
}

type FareBreakdown struct {
Low otils.NullableFloat64 `json:"low_amount"`
High otils.NullableFloat64 `json:"high_amount"`
DisplayAmount otils.NullableString `json:"display_amount"`
DisplayName otils.NullableString `json:"display_name"`
}

type Fare struct {
Value otils.NullableFloat64 `json:"value,omitempty"`
ExpiresAt int64 `json:"expires_at,omitempty"`
CurrencyCode otils.NullableString `json:"currency_code"`
DisplayAmount otils.NullableString `json:"display"`
ID otils.NullableString `json:"fare_id"`
}

type UpfrontFare struct {
Trip *Trip `json:"trip,omitempty"`
Fare *Fare `json:"fare,omitempty"`

// PickupEstimateMinutes is the estimated time of vehicle arrival
// in minutes. It is unset if there are no cars available.
PickupEstimateMinutes otils.NullableFloat64 `json:"pickup_estimate,omitempty"`

Estimate *FareEstimate `json:"estimate,omitempty"`
}

func (upf *UpfrontFare) SurgeInEffect() bool {
return upf != nil && upf.Estimate != nil && upf.Estimate.SurgeConfirmationURL != ""
}

func (upf *UpfrontFare) NoCarsAvailable() bool {
return upf == nil || upf.PickupEstimateMinutes <= 0
}

var errInvalidSeatCount = errors.New("invalid seatcount, default and maximum value is 2")

func (esReq *EstimateRequest) validateForUpfrontFare() error {
if esReq == nil {
return errNilEstimateRequest
}

// The number of seats required for uberPool.
// Default and maximum value is 2.
if esReq.SeatCount < 0 || esReq.SeatCount > 2 {
return errInvalidSeatCount
}

// UpfrontFares require:
// * StartPlace or (StartLatitude, StartLongitude)
// * EndPlace or (EndLatitude, EndLongitude)
if esReq.StartPlace != "" && esReq.EndPlace != "" {
return nil
}

// However, checks for unspecified zero floats require
// special attention, so we'll let the JSON marshaling
// serialize them.
return nil
}

var errNilFare = errors.New("failed to unmarshal the response fare")

func (c *Client) UpfrontFare(esReq *EstimateRequest) (*UpfrontFare, error) {
if err := esReq.validateForUpfrontFare(); err != nil {
return nil, err
}

blob, err := json.Marshal(esReq)
if err != nil {
return nil, err
}

fullURL := fmt.Sprintf("%s/requests/estimate", baseURL)
req, err := http.NewRequest("POST", fullURL, bytes.NewReader(blob))
if err != nil {
return nil, err
}
slurp, _, err := c.doHTTPReq(req)
if err != nil {
return nil, err
}

upfrontFare := new(UpfrontFare)
var blankUFare UpfrontFare
if err := json.Unmarshal(slurp, upfrontFare); err != nil {
return nil, err
}
if *upfrontFare == blankUFare {
return nil, errNilFare
}
return upfrontFare, nil
}
15 changes: 15 additions & 0 deletions v1/testdata/fare-estimate-no-surge.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"fare": {
"value": 5.73,
"fare_id": "d30e732b8bba22c9cdc10513ee86380087cb4a6f89e37ad21ba2a39f3a1ba960",
"expires_at": 1476953293,
"display": "$5.73",
"currency_code": "USD"
},
"trip": {
"distance_unit": "mile",
"duration_estimate": 540,
"distance_estimate": 2.39
},
"pickup_estimate": 2
}
50 changes: 50 additions & 0 deletions v1/testdata/fare-estimate-surge.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"estimate": {
"surge_confirmation_href": "https:\/\/api.uber.com\/v1\/surge-confirmations\/7d604f5e",
"high_estimate": 11,
"surge_confirmation_id": "7d604f5e",
"minimum": 5,
"low_estimate": 8,
"fare_breakdown": [
{
"low_amount": 1.25,
"high_amount": 1.25,
"display_amount": "1.25",
"display_name": "Base Fare"
},
{
"low_amount": 1.92,
"high_amount": 2.57,
"display_amount": "1.92-2.57",
"display_name": "Distance"
},
{
"low_amount": 2.50,
"high_amount": 3.50,
"display_amount": "2.50-3.50",
"display_name": "Surge x1.5"
},
{
"low_amount": 1.25,
"high_amount": 1.25,
"display_amount": "1.25",
"display_name": "Booking Fee"
},
{
"low_amount": 1.36,
"high_amount": 1.81,
"display_amount": "1.36-1.81",
"display_name": "Time"
}
],
"surge_multiplier": 1.5,
"display": "$8-11",
"currency_code": "USD"
},
"trip": {
"distance_unit": "mile",
"duration_estimate": 480,
"distance_estimate": 1.95
},
"pickup_estimate": 2
}
Loading

0 comments on commit e28016a

Please sign in to comment.