From 64ec22f7095f2a4d3efbda69ef1d9e210c1becd9 Mon Sep 17 00:00:00 2001 From: Jason Hall Date: Tue, 21 Oct 2014 15:10:41 -0400 Subject: [PATCH] remove Client struct, use context values for API key, Work client ID and private key, and http Client --- context.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++ directions.go | 4 +-- distance.go | 4 +-- elevation.go | 16 ++++++------ geocode.go | 8 +++--- maps.go | 64 ++++++++++-------------------------------------- maps_test.go | 49 +++++++++++++++++++------------------ static.go | 4 +-- streetview.go | 4 +-- timezone.go | 4 +-- 10 files changed, 127 insertions(+), 97 deletions(-) create mode 100644 context.go diff --git a/context.go b/context.go new file mode 100644 index 0000000..06f9e92 --- /dev/null +++ b/context.go @@ -0,0 +1,67 @@ +package maps + +import ( + "net/http" + + "code.google.com/p/go.net/context" +) + +type contextKey int + +// NewContext returns a new context that uses the provided http.Client and API key. +func NewContext(key string, c *http.Client) context.Context { + return WithContext(context.Background(), key, c) +} + +// WithContext returns a new context in a similar way NewContext does, but initiates the new context with the specified parent. +func WithContext(parent context.Context, key string, c *http.Client) context.Context { + vals := map[string]interface{}{} + vals["key"] = key + vals["httpClient"] = c + return context.WithValue(parent, contextKey(0), vals) +} + +// NewWorkContext returns a new context that uses the provided http.Client and Google Maps API for Work client ID and private key. +func NewWorkContext(clientID, privateKey string, c *http.Client) context.Context { + return WithWorkCredentials(context.Background(), clientID, privateKey, c) +} + +// WithWorkCredentials returns a new context in a similar way NewWorkContext does, but initiates the new context with the specified parent. +func WithWorkCredentials(parent context.Context, clientID, privateKey string, c *http.Client) context.Context { + vals := map[string]interface{}{} + vals["workClientID"] = clientID + vals["workPrivKey"] = privateKey + vals["httpClient"] = c + return context.WithValue(parent, contextKey(0), vals) +} + +func key(ctx context.Context) string { + k, found := ctx.Value(contextKey(0)).(map[string]interface{})["key"] + if found { + return k.(string) + } + return "" +} + +func workCreds(ctx context.Context) (string, string) { + var clientID string + cid, found := ctx.Value(contextKey(0)).(map[string]interface{})["workClientID"] + if found { + clientID = cid.(string) + } + + var privateKey string + pkey, found := ctx.Value(contextKey(0)).(map[string]interface{})["workPrivKey"] + if found { + privateKey = pkey.(string) + } + return clientID, privateKey +} + +func httpClient(ctx context.Context) *http.Client { + cl, found := ctx.Value(contextKey(0)).(map[string]interface{})["httpClient"] + if found && cl != nil { + return cl.(*http.Client) + } + return &http.Client{Transport: &backoff{Transport: http.DefaultTransport}} +} diff --git a/directions.go b/directions.go index 6c63854..5a9e4da 100644 --- a/directions.go +++ b/directions.go @@ -37,9 +37,9 @@ const ( // Directions requests routes between orig and dest Locations. // // See https://developers.google.com/maps/documentation/directions/ -func (c Client) Directions(ctx context.Context, orig, dest Location, opts *DirectionsOpts) ([]Route, error) { +func Directions(ctx context.Context, orig, dest Location, opts *DirectionsOpts) ([]Route, error) { var d directionsResponse - if err := c.doDecode(ctx, baseURL+directions(orig, dest, opts), &d); err != nil { + if err := doDecode(ctx, baseURL+directions(orig, dest, opts), &d); err != nil { return nil, err } if d.Status != StatusOK { diff --git a/distance.go b/distance.go index ba6d1f8..63b0692 100644 --- a/distance.go +++ b/distance.go @@ -11,9 +11,9 @@ import ( // DistanceMatrix requests travel distance and time for a matrix of origins and destinations. // // See https://developers.google.com/maps/documentation/distancematrix/ -func (c Client) DistanceMatrix(ctx context.Context, orig, dest []Location, opts *DistanceMatrixOpts) (*DistanceMatrixResult, error) { +func DistanceMatrix(ctx context.Context, orig, dest []Location, opts *DistanceMatrixOpts) (*DistanceMatrixResult, error) { var d distanceResponse - if err := c.doDecode(ctx, baseURL+distancematrix(orig, dest, opts), &d); err != nil { + if err := doDecode(ctx, baseURL+distancematrix(orig, dest, opts), &d); err != nil { return nil, err } if d.Status != StatusOK { diff --git a/elevation.go b/elevation.go index cbf5557..dfc9be8 100644 --- a/elevation.go +++ b/elevation.go @@ -10,9 +10,9 @@ import ( // Elevation requests elevation data for a series of locations. // // See https://developers.google.com/maps/documentation/elevation/ -func (c Client) Elevation(ctx context.Context, ll []LatLng) ([]ElevationResult, error) { +func Elevation(ctx context.Context, ll []LatLng) ([]ElevationResult, error) { var r elevationResponse - if err := c.doDecode(ctx, baseURL+elevation(ll), &r); err != nil { + if err := doDecode(ctx, baseURL+elevation(ll), &r); err != nil { return nil, err } if r.Status != StatusOK { @@ -24,9 +24,9 @@ func (c Client) Elevation(ctx context.Context, ll []LatLng) ([]ElevationResult, // ElevationPolyline requests elevation data for a series of locations as specified as an encoded polyline. // // See https://developers.google.com/maps/documentation/elevation/#Locations -func (c Client) ElevationPolyline(ctx context.Context, p string) ([]ElevationResult, error) { +func ElevationPolyline(ctx context.Context, p string) ([]ElevationResult, error) { var r elevationResponse - if err := c.doDecode(ctx, baseURL+elevationpoly(p), &r); err != nil { + if err := doDecode(ctx, baseURL+elevationpoly(p), &r); err != nil { return nil, err } if r.Status != StatusOK { @@ -36,9 +36,9 @@ func (c Client) ElevationPolyline(ctx context.Context, p string) ([]ElevationRes } // ElevationPath requests elevation data for a number of samples along a path described as a series of locations. -func (c Client) ElevationPath(ctx context.Context, ll []LatLng, samples int) ([]ElevationResult, error) { +func ElevationPath(ctx context.Context, ll []LatLng, samples int) ([]ElevationResult, error) { var r elevationResponse - if err := c.doDecode(ctx, baseURL+elevationpath(ll, samples), &r); err != nil { + if err := doDecode(ctx, baseURL+elevationpath(ll, samples), &r); err != nil { return nil, err } if r.Status != StatusOK { @@ -48,9 +48,9 @@ func (c Client) ElevationPath(ctx context.Context, ll []LatLng, samples int) ([] } // ElevationPathPoly requests elevation data for a number of samples along a path described as a series of locations specified as an encoded polyline. -func (c Client) ElevationPathPoly(ctx context.Context, p string, samples int) ([]ElevationResult, error) { +func ElevationPathPoly(ctx context.Context, p string, samples int) ([]ElevationResult, error) { var r elevationResponse - if err := c.doDecode(ctx, baseURL+elevationpathpoly(p, samples), &r); err != nil { + if err := doDecode(ctx, baseURL+elevationpathpoly(p, samples), &r); err != nil { return nil, err } if r.Status != StatusOK { diff --git a/geocode.go b/geocode.go index 9b6725f..7a3bd73 100644 --- a/geocode.go +++ b/geocode.go @@ -33,9 +33,9 @@ const ( ) // Geocode requests conversion of an address to a latitude/longitude pair. -func (c Client) Geocode(ctx context.Context, opts *GeocodeOpts) ([]GeocodeResult, error) { +func Geocode(ctx context.Context, opts *GeocodeOpts) ([]GeocodeResult, error) { var r geocodeResponse - if err := c.doDecode(ctx, baseURL+geocode(opts), &r); err != nil { + if err := doDecode(ctx, baseURL+geocode(opts), &r); err != nil { return nil, err } if r.Status != StatusOK { @@ -194,9 +194,9 @@ type GeocodeResult struct { // ReverseGeocode requests conversion of a location to its nearest address. // // See https://developers.google.com/maps/documentation/geocoding/#ReverseGeocoding -func (c Client) ReverseGeocode(ctx context.Context, ll LatLng, opts *ReverseGeocodeOpts) ([]GeocodeResult, error) { +func ReverseGeocode(ctx context.Context, ll LatLng, opts *ReverseGeocodeOpts) ([]GeocodeResult, error) { var r geocodeResponse - if err := c.doDecode(ctx, baseURL+reversegeocode(ll, opts), &r); err != nil { + if err := doDecode(ctx, baseURL+reversegeocode(ll, opts), &r); err != nil { return nil, err } if r.Status != StatusOK { diff --git a/maps.go b/maps.go index 4afe1fa..77d8072 100644 --- a/maps.go +++ b/maps.go @@ -35,61 +35,23 @@ const ( StatusOverQueryLimit = "OVER_QUERY_LIMIT" ) -// Client provides methods to make requests of various Maps APIs. -type Client struct { - // The underlying http.RoundTripper to use. If this is not specified, http.DefaultTransport will be used. - Transport http.RoundTripper - - // Key is the API key to use to authorize requests. - Key string - - // ClientID is the Google Maps API for Work client ID to use. - // - // See https://developers.google.com/maps/documentation/business/webservices/auth - ClientID string - - // PrivateKey is the base64-encoded private key to use for Google Maps API for Work requests. - // - // See https://developers.google.com/maps/documentation/business/webservices/auth - PrivateKey string -} - -// New returns a Client using the specified API key and RoundTripper. -func New(key string, rt http.RoundTripper) Client { - return Client{Key: key, Transport: rt} -} - -// NewForWork returns a Client using the specified Google Maps API for Work client ID and signature, and RoundTripper. -// -// See https://developers.google.com/maps/documentation/business/ -func NewForWork(clientID, privateKey string, rt http.RoundTripper) Client { - return Client{ClientID: clientID, PrivateKey: privateKey, Transport: rt} -} - -func (c Client) do(ctx context.Context, url string) (*http.Response, error) { - // TODO: Use ctx here. - - t := c.Transport - if t == nil { - t = http.DefaultTransport - } - cl := &http.Client{Transport: &backoff{ - Transport: t, - }} +func do(ctx context.Context, url string) (*http.Response, error) { + cl := httpClient(ctx) req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } q := req.URL.Query() - if c.Key != "" { - q.Set("key", c.Key) + if k := key(ctx); k != "" { + q.Set("key", k) } - if c.ClientID != "" { - q.Set("client", c.ClientID) + clientID, privKey := workCreds(ctx) + if clientID != "" { + q.Set("client", clientID) } enc := q.Encode() - if c.PrivateKey != "" { - sig, err := c.genSig(req.URL.Path, enc) + if privKey != "" { + sig, err := genSig(privKey, req.URL.Path, enc) if err != nil { return nil, err } @@ -100,9 +62,9 @@ func (c Client) do(ctx context.Context, url string) (*http.Response, error) { } // See https://developers.google.com/maps/documentation/business/webservices/auth -func (c Client) genSig(path, query string) (string, error) { +func genSig(privKey, path, query string) (string, error) { toSign := path + "?" + query - decodedKey, err := base64.URLEncoding.DecodeString(c.PrivateKey) + decodedKey, err := base64.URLEncoding.DecodeString(privKey) if err != nil { return "", err } @@ -113,8 +75,8 @@ func (c Client) genSig(path, query string) (string, error) { return base64.URLEncoding.EncodeToString(d.Sum(nil)), nil } -func (c Client) doDecode(ctx context.Context, url string, r interface{}) error { - resp, err := c.do(ctx, url) +func doDecode(ctx context.Context, url string, r interface{}) error { + resp, err := do(ctx, url) if err != nil { return err } diff --git a/maps_test.go b/maps_test.go index 03c3459..7e6a21c 100644 --- a/maps_test.go +++ b/maps_test.go @@ -2,16 +2,17 @@ package maps import ( "image/color" + "net/http" "testing" "time" - - "code.google.com/p/go.net/context" ) -var ctx = context.Background() +var ( + ctx = NewContext("key", &http.Client{}) + wctx = NewWorkContext("clientID", "privKey", &http.Client{}) +) func TestDirections(t *testing.T) { - c := New("", nil) orig, dest := Address("111 8th Ave, NYC"), Address("170 E 92nd St, NYC") opts := &DirectionsOpts{ Mode: ModeTransit, @@ -19,7 +20,7 @@ func TestDirections(t *testing.T) { Alternatives: true, } t.Logf("%s", baseURL+directions(orig, dest, opts)) - r, err := c.Directions(ctx, orig, dest, opts) + r, err := Directions(ctx, orig, dest, opts) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -27,7 +28,6 @@ func TestDirections(t *testing.T) { } func TestStaticMap(t *testing.T) { - c := New("", nil) s := Size{512, 512} opts := &StaticMapOpts{ Center: LatLng{-5, -5}, @@ -77,13 +77,12 @@ func TestStaticMap(t *testing.T) { }, } t.Logf("%s", baseURL+staticmap(s, opts)) - if _, err := c.StaticMap(ctx, s, opts); err != nil { + if _, err := StaticMap(ctx, s, opts); err != nil { t.Errorf("unexpected error: %v", err) } } func TestStreetView(t *testing.T) { - c := New("", nil) s := Size{600, 300} opts := &StreetViewOpts{ Location: &LatLng{46.414382, 10.013988}, @@ -91,17 +90,16 @@ func TestStreetView(t *testing.T) { Pitch: -0.76, } t.Logf("%s", baseURL+streetview(s, opts)) - if _, err := c.StreetView(ctx, s, opts); err != nil { + if _, err := StreetView(ctx, s, opts); err != nil { t.Errorf("unexpected error: %v", err) } } func TestTimeZone(t *testing.T) { - c := New("", nil) ll := LatLng{40.7142700, -74.0059700} tm := time.Now() t.Logf("%s", baseURL+timezone(ll, tm, nil)) - r, err := c.TimeZone(ctx, ll, tm, nil) + r, err := TimeZone(ctx, ll, tm, nil) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -109,10 +107,9 @@ func TestTimeZone(t *testing.T) { } func TestElevation(t *testing.T) { - c := New("", nil) ll := []LatLng{{39.7391536, -104.9847034}} t.Logf("%s", baseURL+elevation(ll)) - r, err := c.Elevation(ctx, ll) + r, err := Elevation(ctx, ll) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -120,7 +117,7 @@ func TestElevation(t *testing.T) { p := "gfo}EtohhU" t.Logf("%s", baseURL+elevationpoly(p)) - r, err = c.ElevationPolyline(ctx, p) + r, err = ElevationPolyline(ctx, p) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -129,7 +126,7 @@ func TestElevation(t *testing.T) { samples := 3 ll = []LatLng{{36.578581, -118.291994}, {36.23998, -116.83171}} t.Logf("%s", baseURL+elevationpath(ll, samples)) - r, err = c.ElevationPath(ctx, ll, samples) + r, err = ElevationPath(ctx, ll, samples) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -137,7 +134,7 @@ func TestElevation(t *testing.T) { p = "gfo}EtohhUxD@bAxJmGF" t.Logf("%s", baseURL+elevationpathpoly(p, samples)) - r, err = c.ElevationPathPoly(ctx, p, samples) + r, err = ElevationPathPoly(ctx, p, samples) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -145,12 +142,11 @@ func TestElevation(t *testing.T) { } func TestGeocode(t *testing.T) { - c := New("", nil) opts := &GeocodeOpts{ Address: Address("1600 Amphitheatre Parkway, Mountain View, CA"), } t.Logf("%s", baseURL+geocode(opts)) - r, err := c.Geocode(ctx, opts) + r, err := Geocode(ctx, opts) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -158,7 +154,7 @@ func TestGeocode(t *testing.T) { ll := LatLng{40.714224, -73.961452} t.Logf("%s", baseURL+reversegeocode(ll, nil)) - r, err = c.ReverseGeocode(ctx, ll, nil) + r, err = ReverseGeocode(ctx, ll, nil) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -166,7 +162,6 @@ func TestGeocode(t *testing.T) { } func TestDistanceMatrix(t *testing.T) { - c := New("", nil) orig := []Location{Address("Vancouver, BC"), Address("Seattle")} dst := []Location{Address("San Francisco"), Address("Victoria, BC")} opts := &DistanceMatrixOpts{ @@ -174,7 +169,7 @@ func TestDistanceMatrix(t *testing.T) { Language: "fr-FR", } t.Logf("%s", baseURL+distancematrix(orig, dst, opts)) - r, err := c.DistanceMatrix(ctx, orig, dst, opts) + r, err := DistanceMatrix(ctx, orig, dst, opts) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -184,13 +179,19 @@ func TestDistanceMatrix(t *testing.T) { // Based on https://developers.google.com/maps/documentation/business/webservices/auth#signature_examples func TestSignature(t *testing.T) { - c := NewForWork("", "vNIXE0xscrmjlyV-12Nj_BvUPaw=", nil) - sig, err := c.genSig("/maps/api/geocode/json", "address=New+York&client=clientID") + clientID := "clientID" + privateKey := "vNIXE0xscrmjlyV-12Nj_BvUPaw=" + sig, err := genSig(privateKey, "/maps/api/geocode/json", "address=New+York&client="+clientID) if err != nil { t.Errorf("unexpected error: %v", err) } exp := "chaRF2hTJKOScPr-RQCEhZbSzIE=" if sig != exp { - t.Errorf("wrong signature, got %q, want %q", sig, exp) + t.Errorf("unexpected signature, got %q, want %q", sig, exp) + } + + ctx := NewWorkContext(clientID, privateKey, nil) + if gcid, gkey := workCreds(ctx); gcid != clientID || gkey != privateKey { + t.Errorf("unepxected credentials from context, got %q and %q, want %q and %q", gcid, gkey, clientID, privateKey) } } diff --git a/static.go b/static.go index df32942..f811f5a 100644 --- a/static.go +++ b/static.go @@ -48,8 +48,8 @@ const ( ) // StaticMap requests a static map image of a requested size. -func (c Client) StaticMap(ctx context.Context, s Size, opts *StaticMapOpts) (io.ReadCloser, error) { - resp, err := c.do(ctx, baseURL+staticmap(s, opts)) +func StaticMap(ctx context.Context, s Size, opts *StaticMapOpts) (io.ReadCloser, error) { + resp, err := do(ctx, baseURL+staticmap(s, opts)) if err != nil { return nil, err } diff --git a/streetview.go b/streetview.go index 4ad5007..5599e79 100644 --- a/streetview.go +++ b/streetview.go @@ -10,8 +10,8 @@ import ( ) // StreetView requests a static StreetView image of the requested size. -func (c Client) StreetView(ctx context.Context, s Size, opts *StreetViewOpts) (io.ReadCloser, error) { - resp, err := c.do(ctx, baseURL+streetview(s, opts)) +func StreetView(ctx context.Context, s Size, opts *StreetViewOpts) (io.ReadCloser, error) { + resp, err := do(ctx, baseURL+streetview(s, opts)) if err != nil { return nil, err } diff --git a/timezone.go b/timezone.go index 4a6e1d2..c8d15f0 100644 --- a/timezone.go +++ b/timezone.go @@ -11,9 +11,9 @@ import ( // TimeZone requests time zone information about a location. // // See https://developers.google.com/maps/documentation/timezone/ -func (c Client) TimeZone(ctx context.Context, ll LatLng, t time.Time, opts *TimeZoneOpts) (*TimeZoneResult, error) { +func TimeZone(ctx context.Context, ll LatLng, t time.Time, opts *TimeZoneOpts) (*TimeZoneResult, error) { var r timeZoneResponse - if err := c.doDecode(ctx, baseURL+timezone(ll, t, opts), &r); err != nil { + if err := doDecode(ctx, baseURL+timezone(ll, t, opts), &r); err != nil { return nil, err } if r.Status != StatusOK {