cloud/storage/dropbox: return errors.NotExist when block is not found

By introducing a custom DropboxAPIError type for handling API specific
errors in case of a 409 status code. The implementation is very basic
and looks for a "not_found" string inside the error message.

The reason is that the structure of the error JSON is dependent on the
API endpoints and the Upspin storage backends only return a 'errors.NotExist',
in case a block is not found, otherwise always with a 'errors.IO'.

Fix upspin/upspin#516

Change-Id: Iff4736fe0f8a406614a426926c1b8fe44c2c977d
Reviewed-on: https://upspin-review.googlesource.com/15382
Reviewed-by: Andrew Gerrand <adg@golang.org>
diff --git a/cloud/storage/dropbox/dropbox.go b/cloud/storage/dropbox/dropbox.go
index 3abb0ec..84385da 100644
--- a/cloud/storage/dropbox/dropbox.go
+++ b/cloud/storage/dropbox/dropbox.go
@@ -12,6 +12,7 @@
 	"io"
 	"io/ioutil"
 	"net/http"
+	"strings"
 
 	"upspin.io/cloud/storage"
 	"upspin.io/errors"
@@ -69,9 +70,12 @@
 
 	data, err := d.doRequest(req)
 	if err != nil {
+		if derr, ok := err.(DropboxAPIError); ok && derr.StatusCode() == 404 {
+			return nil, errors.E(op, errors.NotExist, derr)
+		}
+
 		return nil, errors.E(op, errors.IO, err)
 	}
-
 	return data, nil
 }
 
@@ -161,14 +165,40 @@
 	}
 	defer resp.Body.Close()
 
-	if resp.StatusCode != 200 {
-		return nil, errors.Errorf(resp.Status)
-	}
-
 	body, err := ioutil.ReadAll(resp.Body)
 	if err != nil {
 		return nil, err
 	}
 
+	if resp.StatusCode == http.StatusConflict {
+		var dbxErr DropboxAPIError
+		err := json.Unmarshal(body, &dbxErr)
+		if err != nil {
+			return nil, err
+		}
+
+		return nil, dbxErr
+	}
+
+	if resp.StatusCode != 200 {
+		return nil, errors.Errorf(resp.Status)
+	}
+
 	return body, nil
 }
+
+type DropboxAPIError struct {
+	ErrorSummary string `json:"error_summary"`
+}
+
+func (e DropboxAPIError) StatusCode() int {
+	if strings.Contains(e.ErrorSummary, "not_found") {
+		return 404
+	}
+
+	return 0
+}
+
+func (e DropboxAPIError) Error() string {
+	return e.ErrorSummary
+}