Introduce SOAPError type.

This commit is contained in:
John Beisley 2023-03-09 16:34:46 +00:00
parent 8e5cccc9ac
commit fe0b17f589

View File

@ -8,11 +8,48 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strings"
"github.com/huin/goupnp/v2alpha/soap" "github.com/huin/goupnp/v2alpha/soap"
"github.com/huin/goupnp/v2alpha/soap/envelope" "github.com/huin/goupnp/v2alpha/soap/envelope"
) )
var (
// ErrSOAP can be used with errors.Is.
ErrSOAP = errors.New("SOAP error")
)
// SOAPError describes an error from this package, potentially including a
// lower-level cause.
type SOAPError struct {
// description describes the error from the SOAP perspective.
description string
// cause may be nil.
cause error
}
func (se *SOAPError) Error() string {
b := &strings.Builder{}
b.WriteString("SOAP error")
if se.description != "" {
b.WriteString(": ")
b.WriteString(se.description)
}
if se.cause != nil {
b.WriteString(": ")
b.WriteString(se.cause.Error())
}
return b.String()
}
func (se *SOAPError) Is(target error) bool {
return target == ErrSOAP
}
func (se *SOAPError) Unwrap() error {
return se.cause
}
var _ HttpClient = &http.Client{} var _ HttpClient = &http.Client{}
// HttpClient defines the interface required of an HTTP client. It is a subset of *http.Client. // HttpClient defines the interface required of an HTTP client. It is a subset of *http.Client.
@ -40,7 +77,6 @@ type options struct {
type Client struct { type Client struct {
httpClient HttpClient httpClient HttpClient
endpointURL string endpointURL string
maxErrorResponseBytes int
} }
// New creates a new SOAP client, which will POST its requests to the // New creates a new SOAP client, which will POST its requests to the
@ -79,8 +115,10 @@ func (c *Client) Do(
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return fmt.Errorf("SOAP request got HTTP %s (%d)", return &SOAPError{
resp.Status, resp.StatusCode) description: fmt.Sprintf("SOAP request got HTTP %s (%d)",
resp.Status, resp.StatusCode),
}
} }
return ParseResponseAction(resp, actionOut) return ParseResponseAction(resp, actionOut)
@ -110,7 +148,10 @@ func SetRequestAction(
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
err := envelope.Write(buf, actionIn) err := envelope.Write(buf, actionIn)
if err != nil { if err != nil {
return fmt.Errorf("encoding envelope: %w", err) return &SOAPError{
description: "encoding envelope",
cause: err,
}
} }
req.Body = io.NopCloser(buf) req.Body = io.NopCloser(buf)
@ -131,31 +172,39 @@ func ParseResponseAction(
actionOut *envelope.Action, actionOut *envelope.Action,
) error { ) error {
if resp.Body == nil { if resp.Body == nil {
return errors.New("missing response body") return &SOAPError{description: "missing HTTP response body"}
} }
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
if _, err := io.Copy(buf, resp.Body); err != nil { if _, err := io.Copy(buf, resp.Body); err != nil {
return fmt.Errorf("reading response body: %w", err) return &SOAPError{
description: "reading HTTP response body",
cause: err,
}
} }
if err := envelope.Read(buf, actionOut); err != nil { if err := envelope.Read(buf, actionOut); err != nil {
if _, ok := err.(*envelope.Fault); ok { if errors.Is(err, envelope.ErrFault) {
// Parsed cleanly, got SOAP fault. // Parsed cleanly, got SOAP fault.
return err return &SOAPError{
description: "SOAP fault",
cause: err,
}
} }
// Parsing problem, provide some information for context. // Parsing problem, provide some information for context.
dispLen := buf.Len() dispLen := buf.Len()
truncMessage := "" truncMessage := ""
if dispLen > 1024 { if dispLen > 1024 {
dispLen = 1024 dispLen = 1024
truncMessage = fmt.Sprintf("first %d bytes: ", dispLen) truncMessage = fmt.Sprintf("first %d bytes (total %d bytes): ", dispLen, buf.Len())
} }
return fmt.Errorf( return &SOAPError{
"parsing response body (%s%q): %w", description: fmt.Sprintf(
"parsing SOAP response from HTTP body (%s%q)",
truncMessage, buf.Bytes()[:dispLen], truncMessage, buf.Bytes()[:dispLen],
err, ),
) cause: err,
}
} }
return nil return nil