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"
"io"
"net/http"
"strings"
"github.com/huin/goupnp/v2alpha/soap"
"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{}
// 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 {
httpClient HttpClient
endpointURL string
maxErrorResponseBytes int
}
// 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()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("SOAP request got HTTP %s (%d)",
resp.Status, resp.StatusCode)
return &SOAPError{
description: fmt.Sprintf("SOAP request got HTTP %s (%d)",
resp.Status, resp.StatusCode),
}
}
return ParseResponseAction(resp, actionOut)
@ -110,7 +148,10 @@ func SetRequestAction(
buf := &bytes.Buffer{}
err := envelope.Write(buf, actionIn)
if err != nil {
return fmt.Errorf("encoding envelope: %w", err)
return &SOAPError{
description: "encoding envelope",
cause: err,
}
}
req.Body = io.NopCloser(buf)
@ -131,31 +172,39 @@ func ParseResponseAction(
actionOut *envelope.Action,
) error {
if resp.Body == nil {
return errors.New("missing response body")
return &SOAPError{description: "missing HTTP response body"}
}
buf := &bytes.Buffer{}
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 _, ok := err.(*envelope.Fault); ok {
if errors.Is(err, envelope.ErrFault) {
// Parsed cleanly, got SOAP fault.
return err
return &SOAPError{
description: "SOAP fault",
cause: err,
}
}
// Parsing problem, provide some information for context.
dispLen := buf.Len()
truncMessage := ""
if dispLen > 1024 {
dispLen = 1024
truncMessage = fmt.Sprintf("first %d bytes: ", dispLen)
truncMessage = fmt.Sprintf("first %d bytes (total %d bytes): ", dispLen, buf.Len())
}
return &SOAPError{
description: fmt.Sprintf(
"parsing SOAP response from HTTP body (%s%q)",
truncMessage, buf.Bytes()[:dispLen],
),
cause: err,
}
return fmt.Errorf(
"parsing response body (%s%q): %w",
truncMessage, buf.Bytes()[:dispLen],
err,
)
}
return nil