documenting example usage of a custom HTTP client (#231)
* example to implement custom logging and re-auth limits
* faq for advanced usage and customization
diff --git a/FAQ.md b/FAQ.md
new file mode 100644
index 0000000..88a366a
--- /dev/null
+++ b/FAQ.md
@@ -0,0 +1,148 @@
+# Tips
+
+## Implementing default logging and re-authentication attempts
+
+You can implement custom logging and/or limit re-auth attempts by creating a custom HTTP client
+like the following and setting it as the provider client's HTTP Client (via the
+`gophercloud.ProviderClient.HTTPClient` field):
+
+```go
+//...
+
+// LogRoundTripper satisfies the http.RoundTripper interface and is used to
+// customize the default Gophercloud RoundTripper to allow for logging.
+type LogRoundTripper struct {
+ rt http.RoundTripper
+ numReauthAttempts int
+}
+
+// newHTTPClient return a custom HTTP client that allows for logging relevant
+// information before and after the HTTP request.
+func newHTTPClient() http.Client {
+ return http.Client{
+ Transport: &LogRoundTripper{
+ rt: http.DefaultTransport,
+ },
+ }
+}
+
+// RoundTrip performs a round-trip HTTP request and logs relevant information about it.
+func (lrt *LogRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
+ glog.Infof("Request URL: %s\n", request.URL)
+
+ response, err := lrt.rt.RoundTrip(request)
+ if response == nil {
+ return nil, err
+ }
+
+ if response.StatusCode == http.StatusUnauthorized {
+ if lrt.numReauthAttempts == 3 {
+ return response, fmt.Errorf("Tried to re-authenticate 3 times with no success.")
+ }
+ lrt.numReauthAttempts++
+ }
+
+ glog.Debugf("Response Status: %s\n", response.Status)
+
+ return response, nil
+}
+
+endpoint := "https://127.0.0.1/auth"
+pc := openstack.NewClient(endpoint)
+pc.HTTPClient = newHTTPClient()
+
+//...
+```
+
+
+## Implementing custom objects
+
+OpenStack request/response objects may differ among variable names or types.
+
+### Custom request objects
+
+To pass custom options to a request, implement the desired `<ACTION>OptsBuilder` interface. For
+example, to pass in
+
+```go
+type MyCreateServerOpts struct {
+ Name string
+ Size int
+}
+```
+
+to `servers.Create`, simply implement the `servers.CreateOptsBuilder` interface:
+
+```go
+func (o MyCreateServeropts) ToServerCreateMap() (map[string]interface{}, error) {
+ return map[string]interface{}{
+ "name": o.Name,
+ "size": o.Size,
+ }, nil
+}
+```
+
+create an instance of your custom options object, and pass it to `servers.Create`:
+
+```go
+// ...
+myOpts := MyCreateServerOpts{
+ Name: "s1",
+ Size: "100",
+}
+server, err := servers.Create(computeClient, myOpts).Extract()
+// ...
+```
+
+### Custom response objects
+
+Some OpenStack services have extensions. Extensions that are supported in Gophercloud can be
+combined to create a custom object:
+
+```go
+// ...
+type MyVolume struct {
+ volumes.Volume
+ tenantattr.VolumeExt
+}
+
+var v struct {
+ MyVolume `json:"volume"`
+}
+
+err := volumes.Get(client, volID).ExtractInto(&v)
+// ...
+```
+
+## Overriding default `UnmarshalJSON` method
+
+For some response objects, a field may be a custom type or may be allowed to take on
+different types. In these cases, overriding the default `UnmarshalJSON` method may be
+necessary. To do this, declare the JSON `struct` field tag as "-" and create an `UnmarshalJSON`
+method on the type:
+
+```go
+// ...
+type MyVolume struct {
+ ID string `json: "id"`
+ TimeCreated time.Time `json: "-"`
+}
+
+func (r *MyVolume) UnmarshalJSON(b []byte) error {
+ type tmp MyVolume
+ var s struct {
+ tmp
+ TimeCreated gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
+ }
+ err := json.Unmarshal(b, &s)
+ if err != nil {
+ return err
+ }
+ *r = Volume(s.tmp)
+
+ r.TimeCreated = time.Time(s.CreatedAt)
+
+ return err
+}
+// ...
+```
diff --git a/README.md b/README.md
index 0e1fe06..8d8d0a5 100644
--- a/README.md
+++ b/README.md
@@ -125,6 +125,10 @@
new resource in the `server` variable (a
[`servers.Server`](http://godoc.org/github.com/gophercloud/gophercloud) struct).
+## Advanced Usage
+
+Have a look at the [FAQ](./FAQ.md) for some tips on customizing the way Gophercloud works.
+
## Backwards-Compatibility Guarantees
None. Vendor it and write tests covering the parts you use.