1
0
Fork 0
mirror of https://github.com/imjasonh/staticmaps synced 2026-07-09 03:15:09 +00:00

remove Client struct, use context values for API key, Work client ID and private key, and http Client

This commit is contained in:
Jason Hall 2014-10-21 15:10:41 -04:00
parent 9166c9bae7
commit 64ec22f709
10 changed files with 127 additions and 97 deletions

67
context.go Normal file
View file

@ -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}}
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

64
maps.go
View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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 {