Fix how envelopes are marshalled and unmarshalled.
Previously the arguments would be wrapped an additional XML element.
This commit is contained in:
parent
f8d565399b
commit
d4fdaef967
@ -32,17 +32,6 @@ func (fe *Fault) Is(target error) bool {
|
||||
return target == ErrFault
|
||||
}
|
||||
|
||||
// Various "constant" bytes used in the written envelope.
|
||||
var (
|
||||
envOpen = []byte(xml.Header + `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>`)
|
||||
env1 = []byte(`<u:`)
|
||||
env2 = []byte(` xmlns:u="`)
|
||||
env3 = []byte(`">`)
|
||||
env4 = []byte(`</u:`)
|
||||
env5 = []byte(`>`)
|
||||
envClose = []byte(`</s:Body></s:Envelope>`)
|
||||
)
|
||||
|
||||
// Action wraps a SOAP action to be read or written as part of a SOAP envelope.
|
||||
type Action struct {
|
||||
// XMLName specifies the XML element namespace (URI) and name. Together
|
||||
@ -52,18 +41,57 @@ type Action struct {
|
||||
// arguments. See https://pkg.go.dev/encoding/xml@go1.17.1#Marshal and
|
||||
// https://pkg.go.dev/encoding/xml@go1.17.1#Unmarshal for details on
|
||||
// annotating fields in the structure.
|
||||
Args any `xml:",any"`
|
||||
Args any
|
||||
}
|
||||
|
||||
// NewAction creates a SOAP action for sending with the given namespace URL,
|
||||
// NewSendAction creates a SOAP action for receiving arguments.
|
||||
func NewRecvAction(args any) *Action {
|
||||
return &Action{Args: args}
|
||||
}
|
||||
|
||||
// NewSendAction creates a SOAP action for sending with the given namespace URL,
|
||||
// action name, and arguments.
|
||||
func NewAction(nsURL, actionName string, args any) *Action {
|
||||
func NewSendAction(serviceType, actionName string, args any) *Action {
|
||||
return &Action{
|
||||
XMLName: xml.Name{Space: nsURL, Local: actionName},
|
||||
XMLName: xml.Name{Space: serviceType, Local: actionName},
|
||||
Args: args,
|
||||
}
|
||||
}
|
||||
|
||||
var _ xml.Marshaler = &Action{}
|
||||
|
||||
// MarshalXML implements `xml.Marshaller`.
|
||||
//
|
||||
// This is an implementation detail that allows packing elements inside the
|
||||
// action element from the struct in `a.Args`.
|
||||
func (a *Action) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
// Hardcodes the XML namespace. See comment in Write() for context.
|
||||
return e.EncodeElement(a.Args, xml.StartElement{
|
||||
Name: xml.Name{Space: "", Local: "u:" + a.XMLName.Local},
|
||||
Attr: []xml.Attr{{
|
||||
Name: xml.Name{Space: "", Local: "xmlns:u"},
|
||||
Value: a.XMLName.Space,
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
var _ xml.Unmarshaler = &Action{}
|
||||
|
||||
// UnmarshalXML implements `xml.Unmarshaller`.
|
||||
//
|
||||
// This is an implementation detail that allows unpacking elements inside the
|
||||
// action element into the struct in `a.Args`.
|
||||
func (a *Action) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
a.XMLName = start.Name
|
||||
return d.DecodeElement(a.Args, &start)
|
||||
}
|
||||
|
||||
// Various "constant" bytes used in the written envelope.
|
||||
var (
|
||||
envOpen = []byte(xml.Header + `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>`)
|
||||
envClose = []byte(`</s:Body></s:Envelope>`)
|
||||
)
|
||||
|
||||
// Write marshals a SOAP envelope to the writer. Errors can be from the writer
|
||||
// or XML encoding.
|
||||
func Write(w io.Writer, action *Action) error {
|
||||
@ -79,47 +107,12 @@ func Write(w io.Writer, action *Action) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(env1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = xml.EscapeText(w, []byte(action.XMLName.Local))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(env2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = xml.EscapeText(w, []byte(action.XMLName.Space))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(env3)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enc := xml.NewEncoder(w)
|
||||
err = enc.Encode(action.Args)
|
||||
err = enc.Encode(action)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = enc.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(env4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
xml.EscapeText(w, []byte(action.XMLName.Local))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(env5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(envClose)
|
||||
return err
|
||||
}
|
||||
|
@ -8,34 +8,69 @@ import (
|
||||
"io"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestWriteRead(t *testing.T) {
|
||||
type Args struct {
|
||||
Foo string `xml:"foo"`
|
||||
Bar string `xml:"bar"`
|
||||
}
|
||||
type testArgs struct {
|
||||
Foo string
|
||||
Bar string
|
||||
}
|
||||
|
||||
sendAction := NewAction("http://example.com/namespace", "MyAction", &Args{
|
||||
// TestWriteRead tests the round-trip of writing an envelope and reading it back.
|
||||
func TestWriteRead(t *testing.T) {
|
||||
argsIn := &testArgs{
|
||||
Foo: "foo-1",
|
||||
Bar: "bar-2",
|
||||
})
|
||||
}
|
||||
actionIn := NewSendAction("urn:schemas-upnp-org:service:FakeService:1", "MyAction", argsIn)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := Write(buf, sendAction)
|
||||
err := Write(buf, actionIn)
|
||||
if err != nil {
|
||||
t.Errorf("Write want success, got err=%v", err)
|
||||
t.Fatalf("Write want success, got err=%v", err)
|
||||
}
|
||||
t.Logf("Encoded envelope:\n%v", buf)
|
||||
|
||||
recvAction := &Action{Args: &Args{}}
|
||||
argsOut := &testArgs{}
|
||||
actionOut := NewRecvAction(argsOut)
|
||||
|
||||
err = Read(buf, recvAction)
|
||||
err = Read(buf, actionOut)
|
||||
if err != nil {
|
||||
t.Errorf("Read want success, got err=%v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(sendAction, recvAction) {
|
||||
t.Errorf("want recvAction=%+v, got %+v", sendAction, recvAction)
|
||||
if diff := cmp.Diff(actionIn, actionOut); diff != "" {
|
||||
t.Errorf("\nwant actionOut=%+v\ngot %+v\ndiff:\n%s", actionIn, actionOut, diff)
|
||||
}
|
||||
if diff := cmp.Diff(argsIn, argsOut); diff != "" {
|
||||
t.Errorf("\nwant argsOut=%+v\ngot %+v\ndiff:\n%s", argsIn, argsOut, diff)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRead tests read against a semi-real encoded envelope.
|
||||
func TestRead(t *testing.T) {
|
||||
env := []byte(`<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>
|
||||
<u:FakeAction xmlns:u="urn:schemas-upnp-org:service:FakeService:1">
|
||||
<Foo>foo-1</Foo>
|
||||
<Bar>bar-2</Bar>
|
||||
</u:FakeAction>
|
||||
</s:Body> </s:Envelope>`)
|
||||
argsOut := &testArgs{}
|
||||
|
||||
actionOut := NewRecvAction(argsOut)
|
||||
|
||||
if err := Read(bytes.NewBuffer(env), actionOut); err != nil {
|
||||
t.Fatalf("Read want success, got err=%v", err)
|
||||
}
|
||||
|
||||
wantArgsOut := &testArgs{
|
||||
Foo: "foo-1",
|
||||
Bar: "bar-2",
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(wantArgsOut, argsOut); diff != "" {
|
||||
t.Errorf("want argsOut=%+v, got %+v\ndiff:\n%s", wantArgsOut, argsOut, diff)
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,9 +89,7 @@ s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
</s:Envelope>
|
||||
`)
|
||||
|
||||
type args struct{}
|
||||
|
||||
err := Read(bytes.NewBuffer(env), &Action{Args: &args{}})
|
||||
err := Read(bytes.NewBuffer(env), NewRecvAction(&testArgs{}))
|
||||
if err == nil {
|
||||
t.Fatal("want err != nil, got nil")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user