Repository: bpicode/fritzctl
Pull request #89 (2017-10-03 09:19:17)

remove DecodeError


Patch:
diff --git a/httpread/httpread.go b/httpread/httpread.go
index d0716a3..e3e0c13 100644
--- a/httpread/httpread.go
+++ b/httpread/httpread.go
@@ -18,53 +18,48 @@ var (
 	httpStatusBuzzwords = map[string]int{"500 Internal Server Error": 500}
 )
 
-// HTTPStatusCodeError represents an 4xx client or a 5xx server error.
-type HTTPStatusCodeError struct {
-	error
+type stringDecoder struct {
+	reader io.Reader
 }
 
-func statusCodeError(code int, phrase string) *HTTPStatusCodeError {
-	return &HTTPStatusCodeError{error: fmt.Errorf("HTTP status code error (%d): remote replied with '%s'", code, phrase)}
+func (s *stringDecoder) Decode(v interface{}) error {
+	bytesRead, err := ioutil.ReadAll(s.reader)
+	if err != nil {
+		return err
+	}
+	sp, ok := v.(*string)
+	if !ok {
+		return errors.New("cannot decode into string, call with string pointer")
+	}
+	*sp = string(bytesRead)
+	return nil
 }
 
 // ReadFullyString reads a http response into a string.
 // The response is checked for its status code and the http.Response.Body is closed.
 func ReadFullyString(f func() (*http.Response, error)) (string, error) {
-	response, err := f()
+	body := ""
+	err := readDecode(f, func(r io.Reader) decoder {
+		return &stringDecoder{reader: r}
+	}, &body)
 	if err != nil {
 		return "", err
 	}
-	defer response.Body.Close()
-	bytesRead, err := ioutil.ReadAll(response.Body)
-	body := string(bytesRead)
-	logger.Debug("DATA:", body)
-	statusCode, statusPhrase := guessStatusCode(response.StatusCode, response.Status, body)
-	if statusCode >= 400 {
-		return body, statusCodeError(statusCode, statusPhrase)
+	sc, sp := guessStatusCode(body)
+	if sc >= 400 {
+		return "", fmt.Errorf("HTTP status code error (%d, guessed): remote replied with '%s'", sc, sp)
 	}
-	return body, err
+	return body, nil
 }
 
-func guessStatusCode(claimedCode int, claimedPhrase, body string) (int, string) {
-	if claimedCode >= 400 {
-		return claimedCode, claimedPhrase // This is already bad enough.
-	}
+func guessStatusCode(body string) (int, string) {
 	// There are web servers that send the wrong status code, but provide some hint in the text/html.
 	for k, v := range httpStatusBuzzwords {
 		if strings.Contains(strings.ToLower(body), strings.ToLower(k)) {
 			return v, k
 		}
 	}
-	return claimedCode, claimedPhrase
-}
-
-// DecodeError represents an error related to unmarshalling.
-type DecodeError struct {
-	error
-}
-
-func decodeError(err error) *DecodeError {
-	return &DecodeError{error: errors.Wrap(err, "unable to parse remote response")}
+	return 0, ""
 }
 
 type decoder interface {
@@ -96,7 +91,7 @@ func readDecode(f func() (*http.Response, error), df decoderFactory, v interface
 	}
 	defer response.Body.Close()
 	if response.StatusCode >= 400 {
-		return statusCodeError(response.StatusCode, response.Status)
+		return fmt.Errorf("HTTP status code error (%d): remote replied with '%s'", response.StatusCode, response.Status)
 	}
 	return decode(response.Body, df, v)
 }
@@ -107,7 +102,7 @@ func decode(r io.Reader, df decoderFactory, v interface{}) error {
 	defer func() { logger.Debug("DATA:", buf) }()
 	err := df(tee).Decode(v)
 	if err != nil {
-		return decodeError(err)
+		return errors.Wrapf(err, "unable to decode remote response")
 	}
 	return nil
 }


Test patch:
diff --git a/httpread/httpread_test.go b/httpread/httpread_test.go
index 314919b..92bc0c7 100644
--- a/httpread/httpread_test.go
+++ b/httpread/httpread_test.go
@@ -1,6 +1,7 @@
 package httpread
 
 import (
+	"errors"
 	"io"
 	"net/http"
 	"strings"
@@ -17,6 +18,13 @@ func (dummyCloser) Close() error {
 	return nil
 }
 
+type errorReader struct {
+}
+
+func (errorReader) Read(p []byte) (n int, err error) {
+	return 0, errors.New("error")
+}
+
 // TestReadFullyErrorAtRequest reads from an error-prone source and asserts that the error is propagated.
 func TestReadFullyErrorAtRequest(t *testing.T) {
 	clientPtr := &http.Client{}
@@ -94,7 +102,7 @@ func TestReadFullyXMLSuccess(t *testing.T) {
 	assert.NoError(t, err)
 }
 
-// TestReadFullyJSON tests decoding into JSOn.
+// TestReadFullyJSON tests decoding into JSON.
 func TestReadFullyJSON(t *testing.T) {
 	resp := &http.Response{StatusCode: 200, Status: "OK", Body: dummyCloser{Reader: strings.NewReader(`{"a":"b"}`)}}
 	var payload struct {
@@ -106,3 +114,10 @@ func TestReadFullyJSON(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, payload.A, "b")
 }
+
+// TestStringDecoder tests decoding into strings.
+func TestStringDecoder(t *testing.T) {
+	assert.Error(t, (&stringDecoder{reader: errorReader{}}).Decode(new(string)))
+	assert.Error(t, (&stringDecoder{reader: strings.NewReader("somevalue")}).Decode(&struct{}{}))
+	assert.NoError(t, (&stringDecoder{reader: strings.NewReader("somevalue")}).Decode(new(string)))
+}