Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

please make Response.Unmarshal to accept io.Reader #80

Open
isaacwein opened this issue Apr 14, 2022 · 6 comments
Open

please make Response.Unmarshal to accept io.Reader #80

isaacwein opened this issue Apr 14, 2022 · 6 comments

Comments

@isaacwein
Copy link

isaacwein commented Apr 14, 2022

Please make Response.Unmarshal to accept io.Reader
it is better for performance if the xml.decoder gets a large http repose, it can read it directly from the socket rather then converting the whole response body with io.ReadAll(resp.body) to memory and then passing it to the xml.decoder

this is how I need to do it now

body := []byte(`xml-rpc code`)

err = xmlrpc.Response(body).Unmarshal(&resData)

this is how it will be used after

body := strings.NewReader(`xml-rpc-code`)

err = xmlrpc.Response(body).Unmarshal(&resData)
@icholy
Copy link
Collaborator

icholy commented Sep 19, 2022

The tricky part here would be fault detection

xmlrpc/response.go

Lines 26 to 32 in 3377102

if !faultRx.Match(r) {
return nil
}
var fault FaultError
if err := unmarshal(r, &fault); err != nil {
return err
}

We could potentially use a bufio.Reader to peek at the first N bytes of the stream and use that to figure out if there was a fault.

@isaacwein
Copy link
Author

isaacwein commented Sep 19, 2022 via email

@icholy
Copy link
Collaborator

icholy commented Sep 19, 2022

Bufio.reader will defeat the whole purpose of leaving it as a stream

Why do you say that? There's no way to avoid using one when using the encoding/xml package.
See: https://cs.opensource.google/go/go/+/refs/tags/go1.19.1:src/encoding/xml/xml.go;l=375;drc=63d05642d48ec81637481518df962f2b3be435a3

@isaacwein
Copy link
Author

isaacwein commented Sep 19, 2022 via email

@isaacwein
Copy link
Author

you make a struct liket this

type respData struct {
	Suc any        `xmlrpc:"suc"` // here will go the success data that that need to pe decoded on
	Err *XmlRpcErr `xmlrpc:"err"` // here will go the data if the response body is an error
}

now you make on it a custom UnmarshalXML method and check if the first element after <methodResponse> is <params> or <fault> that will let you know if you need to call the success decoder or the error decoder

I made a partial example go playground

package main

import (
	"encoding/xml"
	"fmt"
	"io"
	"strings"
)

// XML-RPC response success body example
var sucResp = `
<?xml version='1.0'?>
<methodResponse>
    <params>
        <param>
            <value>
                <struct>
                    <member>
                        <name>name</name>
                        <value>
                            <string>jack</string>
                        </value>
                    </member>
                </struct>
            </value>
        </param>
    </params>
</methodResponse>
`
// XML-RPC response error body example
var errorResp = `
<?xml version='1.0'?>
<methodResponse>
    <fault>
        <value>
            <struct>
                <member>
                    <name>faultCode</name>
                    <value>
                        <int>400</int>
                    </value>
                </member>
                <member>
                    <name>faultString</name>
                    <value>
                        <string>Account not found</string>
                    </value>
                </member>
            </struct>
        </value>
    </fault>
</methodResponse>
`

func main() {
	type SUC struct {
		Name string `xmlrpc:"name"`
	}
	
	fmt.Println("---- parsing on success body ----")
	err := XmlRpcDecoder(strings.NewReader(sucResp), &SUC{})
	if err != nil {
		fmt.Println("err", err)
	}
	
	fmt.Println("---- parsing on error body ----")
	err = XmlRpcDecoder(strings.NewReader(errorResp), &SUC{})
	if err != nil {
		fmt.Println("err", err)
	}
}

// XmlRpcErr XML-RPC response error
type XmlRpcErr struct {
	FaultCode   int    `xmlrpc:"faultCode"`
	FaultString string `xmlrpc:"faultString"`
}

func (e *XmlRpcErr) Error() string {
	return fmt.Sprintf("xmlrpc error: %d - %s", e.FaultCode, e.FaultString)
}

// respData XML-RPC response data that holds both success and error data
type respData struct {
	Suc any        `xmlrpc:"suc"`
	Err *XmlRpcErr `xmlrpc:"err"`
}
// UnmarshalXML checking if the xml-rpc body is a success or error
func (r *respData) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	// decode inner elements

	for {
		t, err := d.Token()
		if err != nil {
			return fmt.Errorf("getting first element error: %w",err)
		}
		//var i any
		switch tt := t.(type) {
		case xml.StartElement:
			switch tt.Name.Local {
			case "params":
				fmt.Printf("you decoding success data on r.Suc %T\n", r.Suc)
				return d.DecodeElement(r.Suc, &tt)
			case "fault":
				fmt.Printf("you decoding error data on r.Err %T\n", r.Err)
				return d.DecodeElement(r.Err, &tt)
			}

		case xml.EndElement:
			if tt == start.End() {
				return nil
			}
		}

	}
}

// XmlRpcDecoder XML-RPC response decoder
func XmlRpcDecoder(body io.Reader, sucData any) error {

	resp := &respData{Suc: sucData}

	err := xml.NewDecoder(body).Decode(resp)
	if err != nil {
		return err
	}
	if resp.Err != nil {
		return resp.Err
	}
	return nil
}

@isaacwein
Copy link
Author

isaacwein commented Sep 22, 2022

Bufio.reader will defeat the whole purpose of leaving it as a stream

Why do you say that? There's no way to avoid using one when using the encoding/xml package. See: https://cs.opensource.google/go/go/+/refs/tags/go1.19.1:src/encoding/xml/xml.go;l=375;drc=63d05642d48ec81637481518df962f2b3be435a3

sorry I am taking back my statement, I thought you wanted to read the whole body to memory and then decode it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants