aboutsummaryrefslogtreecommitdiffstats
path: root/Godeps/_workspace/src/github.com/huin/goupnp/soap
diff options
context:
space:
mode:
Diffstat (limited to 'Godeps/_workspace/src/github.com/huin/goupnp/soap')
-rw-r--r--Godeps/_workspace/src/github.com/huin/goupnp/soap/soap.go157
-rw-r--r--Godeps/_workspace/src/github.com/huin/goupnp/soap/soap_test.go85
-rw-r--r--Godeps/_workspace/src/github.com/huin/goupnp/soap/types.go508
-rw-r--r--Godeps/_workspace/src/github.com/huin/goupnp/soap/types_test.go481
4 files changed, 1231 insertions, 0 deletions
diff --git a/Godeps/_workspace/src/github.com/huin/goupnp/soap/soap.go b/Godeps/_workspace/src/github.com/huin/goupnp/soap/soap.go
new file mode 100644
index 000000000..815610734
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/huin/goupnp/soap/soap.go
@@ -0,0 +1,157 @@
+// Definition for the SOAP structure required for UPnP's SOAP usage.
+
+package soap
+
+import (
+ "bytes"
+ "encoding/xml"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "reflect"
+)
+
+const (
+ soapEncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
+ soapPrefix = xml.Header + `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>`
+ soapSuffix = `</s:Body></s:Envelope>`
+)
+
+type SOAPClient struct {
+ EndpointURL url.URL
+ HTTPClient http.Client
+}
+
+func NewSOAPClient(endpointURL url.URL) *SOAPClient {
+ return &SOAPClient{
+ EndpointURL: endpointURL,
+ }
+}
+
+// PerformSOAPAction makes a SOAP request, with the given action.
+// inAction and outAction must both be pointers to structs with string fields
+// only.
+func (client *SOAPClient) PerformAction(actionNamespace, actionName string, inAction interface{}, outAction interface{}) error {
+ requestBytes, err := encodeRequestAction(actionNamespace, actionName, inAction)
+ if err != nil {
+ return err
+ }
+
+ response, err := client.HTTPClient.Do(&http.Request{
+ Method: "POST",
+ URL: &client.EndpointURL,
+ Header: http.Header{
+ "SOAPACTION": []string{`"` + actionNamespace + "#" + actionName + `"`},
+ "CONTENT-TYPE": []string{"text/xml; charset=\"utf-8\""},
+ },
+ Body: ioutil.NopCloser(bytes.NewBuffer(requestBytes)),
+ // Set ContentLength to avoid chunked encoding - some servers might not support it.
+ ContentLength: int64(len(requestBytes)),
+ })
+ if err != nil {
+ return fmt.Errorf("goupnp: error performing SOAP HTTP request: %v", err)
+ }
+ defer response.Body.Close()
+ if response.StatusCode != 200 {
+ return fmt.Errorf("goupnp: SOAP request got HTTP %s", response.Status)
+ }
+
+ responseEnv := newSOAPEnvelope()
+ decoder := xml.NewDecoder(response.Body)
+ if err := decoder.Decode(responseEnv); err != nil {
+ return fmt.Errorf("goupnp: error decoding response body: %v", err)
+ }
+
+ if responseEnv.Body.Fault != nil {
+ return responseEnv.Body.Fault
+ }
+
+ if outAction != nil {
+ if err := xml.Unmarshal(responseEnv.Body.RawAction, outAction); err != nil {
+ return fmt.Errorf("goupnp: error unmarshalling out action: %v, %v", err, responseEnv.Body.RawAction)
+ }
+ }
+
+ return nil
+}
+
+// newSOAPAction creates a soapEnvelope with the given action and arguments.
+func newSOAPEnvelope() *soapEnvelope {
+ return &soapEnvelope{
+ EncodingStyle: soapEncodingStyle,
+ }
+}
+
+// encodeRequestAction is a hacky way to create an encoded SOAP envelope
+// containing the given action. Experiments with one router have shown that it
+// 500s for requests where the outer default xmlns is set to the SOAP
+// namespace, and then reassigning the default namespace within that to the
+// service namespace. Hand-coding the outer XML to work-around this.
+func encodeRequestAction(actionNamespace, actionName string, inAction interface{}) ([]byte, error) {
+ requestBuf := new(bytes.Buffer)
+ requestBuf.WriteString(soapPrefix)
+ requestBuf.WriteString(`<u:`)
+ xml.EscapeText(requestBuf, []byte(actionName))
+ requestBuf.WriteString(` xmlns:u="`)
+ xml.EscapeText(requestBuf, []byte(actionNamespace))
+ requestBuf.WriteString(`">`)
+ if inAction != nil {
+ if err := encodeRequestArgs(requestBuf, inAction); err != nil {
+ return nil, err
+ }
+ }
+ requestBuf.WriteString(`</u:`)
+ xml.EscapeText(requestBuf, []byte(actionName))
+ requestBuf.WriteString(`>`)
+ requestBuf.WriteString(soapSuffix)
+ return requestBuf.Bytes(), nil
+}
+
+func encodeRequestArgs(w *bytes.Buffer, inAction interface{}) error {
+ in := reflect.Indirect(reflect.ValueOf(inAction))
+ if in.Kind() != reflect.Struct {
+ return fmt.Errorf("goupnp: SOAP inAction is not a struct but of type %v", in.Type())
+ }
+ enc := xml.NewEncoder(w)
+ nFields := in.NumField()
+ inType := in.Type()
+ for i := 0; i < nFields; i++ {
+ field := inType.Field(i)
+ argName := field.Name
+ if nameOverride := field.Tag.Get("soap"); nameOverride != "" {
+ argName = nameOverride
+ }
+ value := in.Field(i)
+ if value.Kind() != reflect.String {
+ return fmt.Errorf("goupnp: SOAP arg %q is not of type string, but of type %v", argName, value.Type())
+ }
+ if err := enc.EncodeElement(value.Interface(), xml.StartElement{xml.Name{"", argName}, nil}); err != nil {
+ return fmt.Errorf("goupnp: error encoding SOAP arg %q: %v", argName, err)
+ }
+ }
+ enc.Flush()
+ return nil
+}
+
+type soapEnvelope struct {
+ XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
+ EncodingStyle string `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"`
+ Body soapBody `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
+}
+
+type soapBody struct {
+ Fault *SOAPFaultError `xml:"Fault"`
+ RawAction []byte `xml:",innerxml"`
+}
+
+// SOAPFaultError implements error, and contains SOAP fault information.
+type SOAPFaultError struct {
+ FaultCode string `xml:"faultcode"`
+ FaultString string `xml:"faultstring"`
+ Detail string `xml:"detail"`
+}
+
+func (err *SOAPFaultError) Error() string {
+ return fmt.Sprintf("SOAP fault: %s", err.FaultString)
+}
diff --git a/Godeps/_workspace/src/github.com/huin/goupnp/soap/soap_test.go b/Godeps/_workspace/src/github.com/huin/goupnp/soap/soap_test.go
new file mode 100644
index 000000000..75dbbdbf1
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/huin/goupnp/soap/soap_test.go
@@ -0,0 +1,85 @@
+package soap
+
+import (
+ "bytes"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "reflect"
+ "testing"
+)
+
+type capturingRoundTripper struct {
+ err error
+ resp *http.Response
+ capturedReq *http.Request
+}
+
+func (rt *capturingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
+ rt.capturedReq = req
+ return rt.resp, rt.err
+}
+
+func TestActionInputs(t *testing.T) {
+ url, err := url.Parse("http://example.com/soap")
+ if err != nil {
+ t.Fatal(err)
+ }
+ rt := &capturingRoundTripper{
+ err: nil,
+ resp: &http.Response{
+ StatusCode: 200,
+ Body: ioutil.NopCloser(bytes.NewBufferString(`
+ <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
+ <s:Body>
+ <u:myactionResponse xmlns:u="mynamespace">
+ <A>valueA</A>
+ <B>valueB</B>
+ </u:myactionResponse>
+ </s:Body>
+ </s:Envelope>
+ `)),
+ },
+ }
+ client := SOAPClient{
+ EndpointURL: *url,
+ HTTPClient: http.Client{
+ Transport: rt,
+ },
+ }
+
+ type In struct {
+ Foo string
+ Bar string `soap:"bar"`
+ }
+ type Out struct {
+ A string
+ B string
+ }
+ in := In{"foo", "bar"}
+ gotOut := Out{}
+ err = client.PerformAction("mynamespace", "myaction", &in, &gotOut)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ wantBody := (soapPrefix +
+ `<u:myaction xmlns:u="mynamespace">` +
+ `<Foo>foo</Foo>` +
+ `<bar>bar</bar>` +
+ `</u:myaction>` +
+ soapSuffix)
+ body, err := ioutil.ReadAll(rt.capturedReq.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ gotBody := string(body)
+ if wantBody != gotBody {
+ t.Errorf("Bad request body\nwant: %q\n got: %q", wantBody, gotBody)
+ }
+
+ wantOut := Out{"valueA", "valueB"}
+ if !reflect.DeepEqual(wantOut, gotOut) {
+ t.Errorf("Bad output\nwant: %+v\n got: %+v", wantOut, gotOut)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/huin/goupnp/soap/types.go b/Godeps/_workspace/src/github.com/huin/goupnp/soap/types.go
new file mode 100644
index 000000000..cd16510e3
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/huin/goupnp/soap/types.go
@@ -0,0 +1,508 @@
+package soap
+
+import (
+ "encoding/base64"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+ "unicode/utf8"
+)
+
+var (
+ // localLoc acts like time.Local for this package, but is faked out by the
+ // unit tests to ensure that things stay constant (especially when running
+ // this test in a place where local time is UTC which might mask bugs).
+ localLoc = time.Local
+)
+
+func MarshalUi1(v uint8) (string, error) {
+ return strconv.FormatUint(uint64(v), 10), nil
+}
+
+func UnmarshalUi1(s string) (uint8, error) {
+ v, err := strconv.ParseUint(s, 10, 8)
+ return uint8(v), err
+}
+
+func MarshalUi2(v uint16) (string, error) {
+ return strconv.FormatUint(uint64(v), 10), nil
+}
+
+func UnmarshalUi2(s string) (uint16, error) {
+ v, err := strconv.ParseUint(s, 10, 16)
+ return uint16(v), err
+}
+
+func MarshalUi4(v uint32) (string, error) {
+ return strconv.FormatUint(uint64(v), 10), nil
+}
+
+func UnmarshalUi4(s string) (uint32, error) {
+ v, err := strconv.ParseUint(s, 10, 32)
+ return uint32(v), err
+}
+
+func MarshalI1(v int8) (string, error) {
+ return strconv.FormatInt(int64(v), 10), nil
+}
+
+func UnmarshalI1(s string) (int8, error) {
+ v, err := strconv.ParseInt(s, 10, 8)
+ return int8(v), err
+}
+
+func MarshalI2(v int16) (string, error) {
+ return strconv.FormatInt(int64(v), 10), nil
+}
+
+func UnmarshalI2(s string) (int16, error) {
+ v, err := strconv.ParseInt(s, 10, 16)
+ return int16(v), err
+}
+
+func MarshalI4(v int32) (string, error) {
+ return strconv.FormatInt(int64(v), 10), nil
+}
+
+func UnmarshalI4(s string) (int32, error) {
+ v, err := strconv.ParseInt(s, 10, 32)
+ return int32(v), err
+}
+
+func MarshalInt(v int64) (string, error) {
+ return strconv.FormatInt(v, 10), nil
+}
+
+func UnmarshalInt(s string) (int64, error) {
+ return strconv.ParseInt(s, 10, 64)
+}
+
+func MarshalR4(v float32) (string, error) {
+ return strconv.FormatFloat(float64(v), 'G', -1, 32), nil
+}
+
+func UnmarshalR4(s string) (float32, error) {
+ v, err := strconv.ParseFloat(s, 32)
+ return float32(v), err
+}
+
+func MarshalR8(v float64) (string, error) {
+ return strconv.FormatFloat(v, 'G', -1, 64), nil
+}
+
+func UnmarshalR8(s string) (float64, error) {
+ v, err := strconv.ParseFloat(s, 64)
+ return float64(v), err
+}
+
+// MarshalFixed14_4 marshals float64 to SOAP "fixed.14.4" type.
+func MarshalFixed14_4(v float64) (string, error) {
+ if v >= 1e14 || v <= -1e14 {
+ return "", fmt.Errorf("soap fixed14.4: value %v out of bounds", v)
+ }
+ return strconv.FormatFloat(v, 'f', 4, 64), nil
+}
+
+// UnmarshalFixed14_4 unmarshals float64 from SOAP "fixed.14.4" type.
+func UnmarshalFixed14_4(s string) (float64, error) {
+ v, err := strconv.ParseFloat(s, 64)
+ if err != nil {
+ return 0, err
+ }
+ if v >= 1e14 || v <= -1e14 {
+ return 0, fmt.Errorf("soap fixed14.4: value %q out of bounds", s)
+ }
+ return v, nil
+}
+
+// MarshalChar marshals rune to SOAP "char" type.
+func MarshalChar(v rune) (string, error) {
+ if v == 0 {
+ return "", errors.New("soap char: rune 0 is not allowed")
+ }
+ return string(v), nil
+}
+
+// UnmarshalChar unmarshals rune from SOAP "char" type.
+func UnmarshalChar(s string) (rune, error) {
+ if len(s) == 0 {
+ return 0, errors.New("soap char: got empty string")
+ }
+ r, n := utf8.DecodeRune([]byte(s))
+ if n != len(s) {
+ return 0, fmt.Errorf("soap char: value %q is not a single rune", s)
+ }
+ return r, nil
+}
+
+func MarshalString(v string) (string, error) {
+ return v, nil
+}
+
+func UnmarshalString(v string) (string, error) {
+ return v, nil
+}
+
+func parseInt(s string, err *error) int {
+ v, parseErr := strconv.ParseInt(s, 10, 64)
+ if parseErr != nil {
+ *err = parseErr
+ }
+ return int(v)
+}
+
+var dateRegexps = []*regexp.Regexp{
+ // yyyy[-mm[-dd]]
+ regexp.MustCompile(`^(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?$`),
+ // yyyy[mm[dd]]
+ regexp.MustCompile(`^(\d{4})(?:(\d{2})(?:(\d{2}))?)?$`),
+}
+
+func parseDateParts(s string) (year, month, day int, err error) {
+ var parts []string
+ for _, re := range dateRegexps {
+ parts = re.FindStringSubmatch(s)
+ if parts != nil {
+ break
+ }
+ }
+ if parts == nil {
+ err = fmt.Errorf("soap date: value %q is not in a recognized ISO8601 date format", s)
+ return
+ }
+
+ year = parseInt(parts[1], &err)
+ month = 1
+ day = 1
+ if len(parts[2]) != 0 {
+ month = parseInt(parts[2], &err)
+ if len(parts[3]) != 0 {
+ day = parseInt(parts[3], &err)
+ }
+ }
+
+ if err != nil {
+ err = fmt.Errorf("soap date: %q: %v", s, err)
+ }
+
+ return
+}
+
+var timeRegexps = []*regexp.Regexp{
+ // hh[:mm[:ss]]
+ regexp.MustCompile(`^(\d{2})(?::(\d{2})(?::(\d{2}))?)?$`),
+ // hh[mm[ss]]
+ regexp.MustCompile(`^(\d{2})(?:(\d{2})(?:(\d{2}))?)?$`),
+}
+
+func parseTimeParts(s string) (hour, minute, second int, err error) {
+ var parts []string
+ for _, re := range timeRegexps {
+ parts = re.FindStringSubmatch(s)
+ if parts != nil {
+ break
+ }
+ }
+ if parts == nil {
+ err = fmt.Errorf("soap time: value %q is not in ISO8601 time format", s)
+ return
+ }
+
+ hour = parseInt(parts[1], &err)
+ if len(parts[2]) != 0 {
+ minute = parseInt(parts[2], &err)
+ if len(parts[3]) != 0 {
+ second = parseInt(parts[3], &err)
+ }
+ }
+
+ if err != nil {
+ err = fmt.Errorf("soap time: %q: %v", s, err)
+ }
+
+ return
+}
+
+// (+|-)hh[[:]mm]
+var timezoneRegexp = regexp.MustCompile(`^([+-])(\d{2})(?::?(\d{2}))?$`)
+
+func parseTimezone(s string) (offset int, err error) {
+ if s == "Z" {
+ return 0, nil
+ }
+ parts := timezoneRegexp.FindStringSubmatch(s)
+ if parts == nil {
+ err = fmt.Errorf("soap timezone: value %q is not in ISO8601 timezone format", s)
+ return
+ }
+
+ offset = parseInt(parts[2], &err) * 3600
+ if len(parts[3]) != 0 {
+ offset += parseInt(parts[3], &err) * 60
+ }
+ if parts[1] == "-" {
+ offset = -offset
+ }
+
+ if err != nil {
+ err = fmt.Errorf("soap timezone: %q: %v", s, err)
+ }
+
+ return
+}
+
+var completeDateTimeZoneRegexp = regexp.MustCompile(`^([^T]+)(?:T([^-+Z]+)(.+)?)?$`)
+
+// splitCompleteDateTimeZone splits date, time and timezone apart from an
+// ISO8601 string. It does not ensure that the contents of each part are
+// correct, it merely splits on certain delimiters.
+// e.g "2010-09-08T12:15:10+0700" => "2010-09-08", "12:15:10", "+0700".
+// Timezone can only be present if time is also present.
+func splitCompleteDateTimeZone(s string) (dateStr, timeStr, zoneStr string, err error) {
+ parts := completeDateTimeZoneRegexp.FindStringSubmatch(s)
+ if parts == nil {
+ err = fmt.Errorf("soap date/time/zone: value %q is not in ISO8601 datetime format", s)
+ return
+ }
+ dateStr = parts[1]
+ timeStr = parts[2]
+ zoneStr = parts[3]
+ return
+}
+
+// MarshalDate marshals time.Time to SOAP "date" type. Note that this converts
+// to local time, and discards the time-of-day components.
+func MarshalDate(v time.Time) (string, error) {
+ return v.In(localLoc).Format("2006-01-02"), nil
+}
+
+var dateFmts = []string{"2006-01-02", "20060102"}
+
+// UnmarshalDate unmarshals time.Time from SOAP "date" type. This outputs the
+// date as midnight in the local time zone.
+func UnmarshalDate(s string) (time.Time, error) {
+ year, month, day, err := parseDateParts(s)
+ if err != nil {
+ return time.Time{}, err
+ }
+ return time.Date(year, time.Month(month), day, 0, 0, 0, 0, localLoc), nil
+}
+
+// TimeOfDay is used in cases where SOAP "time" or "time.tz" is used.
+type TimeOfDay struct {
+ // Duration of time since midnight.
+ FromMidnight time.Duration
+
+ // Set to true if Offset is specified. If false, then the timezone is
+ // unspecified (and by ISO8601 - implies some "local" time).
+ HasOffset bool
+
+ // Offset is non-zero only if time.tz is used. It is otherwise ignored. If
+ // non-zero, then it is regarded as a UTC offset in seconds. Note that the
+ // sub-minutes is ignored by the marshal function.
+ Offset int
+}
+
+// MarshalTimeOfDay marshals TimeOfDay to the "time" type.
+func MarshalTimeOfDay(v TimeOfDay) (string, error) {
+ d := int64(v.FromMidnight / time.Second)
+ hour := d / 3600
+ d = d % 3600
+ minute := d / 60
+ second := d % 60
+
+ return fmt.Sprintf("%02d:%02d:%02d", hour, minute, second), nil
+}
+
+// UnmarshalTimeOfDay unmarshals TimeOfDay from the "time" type.
+func UnmarshalTimeOfDay(s string) (TimeOfDay, error) {
+ t, err := UnmarshalTimeOfDayTz(s)
+ if err != nil {
+ return TimeOfDay{}, err
+ } else if t.HasOffset {
+ return TimeOfDay{}, fmt.Errorf("soap time: value %q contains unexpected timezone")
+ }
+ return t, nil
+}
+
+// MarshalTimeOfDayTz marshals TimeOfDay to the "time.tz" type.
+func MarshalTimeOfDayTz(v TimeOfDay) (string, error) {
+ d := int64(v.FromMidnight / time.Second)
+ hour := d / 3600
+ d = d % 3600
+ minute := d / 60
+ second := d % 60
+
+ tz := ""
+ if v.HasOffset {
+ if v.Offset == 0 {
+ tz = "Z"
+ } else {
+ offsetMins := v.Offset / 60
+ sign := '+'
+ if offsetMins < 1 {
+ offsetMins = -offsetMins
+ sign = '-'
+ }
+ tz = fmt.Sprintf("%c%02d:%02d", sign, offsetMins/60, offsetMins%60)
+ }
+ }
+
+ return fmt.Sprintf("%02d:%02d:%02d%s", hour, minute, second, tz), nil
+}
+
+// UnmarshalTimeOfDayTz unmarshals TimeOfDay from the "time.tz" type.
+func UnmarshalTimeOfDayTz(s string) (tod TimeOfDay, err error) {
+ zoneIndex := strings.IndexAny(s, "Z+-")
+ var timePart string
+ var hasOffset bool
+ var offset int
+ if zoneIndex == -1 {
+ hasOffset = false
+ timePart = s
+ } else {
+ hasOffset = true
+ timePart = s[:zoneIndex]
+ if offset, err = parseTimezone(s[zoneIndex:]); err != nil {
+ return
+ }
+ }
+
+ hour, minute, second, err := parseTimeParts(timePart)
+ if err != nil {
+ return
+ }
+
+ fromMidnight := time.Duration(hour*3600+minute*60+second) * time.Second
+
+ // ISO8601 special case - values up to 24:00:00 are allowed, so using
+ // strictly greater-than for the maximum value.
+ if fromMidnight > 24*time.Hour || minute >= 60 || second >= 60 {
+ return TimeOfDay{}, fmt.Errorf("soap time.tz: value %q has value(s) out of range", s)
+ }
+
+ return TimeOfDay{
+ FromMidnight: time.Duration(hour*3600+minute*60+second) * time.Second,
+ HasOffset: hasOffset,
+ Offset: offset,
+ }, nil
+}
+
+// MarshalDateTime marshals time.Time to SOAP "dateTime" type. Note that this
+// converts to local time.
+func MarshalDateTime(v time.Time) (string, error) {
+ return v.In(localLoc).Format("2006-01-02T15:04:05"), nil
+}
+
+// UnmarshalDateTime unmarshals time.Time from the SOAP "dateTime" type. This
+// returns a value in the local timezone.
+func UnmarshalDateTime(s string) (result time.Time, err error) {
+ dateStr, timeStr, zoneStr, err := splitCompleteDateTimeZone(s)
+ if err != nil {
+ return
+ }
+
+ if len(zoneStr) != 0 {
+ err = fmt.Errorf("soap datetime: unexpected timezone in %q", s)
+ return
+ }
+
+ year, month, day, err := parseDateParts(dateStr)
+ if err != nil {
+ return
+ }
+
+ var hour, minute, second int
+ if len(timeStr) != 0 {
+ hour, minute, second, err = parseTimeParts(timeStr)
+ if err != nil {
+ return
+ }
+ }
+
+ result = time.Date(year, time.Month(month), day, hour, minute, second, 0, localLoc)
+ return
+}
+
+// MarshalDateTimeTz marshals time.Time to SOAP "dateTime.tz" type.
+func MarshalDateTimeTz(v time.Time) (string, error) {
+ return v.Format("2006-01-02T15:04:05-07:00"), nil
+}
+
+// UnmarshalDateTimeTz unmarshals time.Time from the SOAP "dateTime.tz" type.
+// This returns a value in the local timezone when the timezone is unspecified.
+func UnmarshalDateTimeTz(s string) (result time.Time, err error) {
+ dateStr, timeStr, zoneStr, err := splitCompleteDateTimeZone(s)
+ if err != nil {
+ return
+ }
+
+ year, month, day, err := parseDateParts(dateStr)
+ if err != nil {
+ return
+ }
+
+ var hour, minute, second int
+ var location *time.Location = localLoc
+ if len(timeStr) != 0 {
+ hour, minute, second, err = parseTimeParts(timeStr)
+ if err != nil {
+ return
+ }
+ if len(zoneStr) != 0 {
+ var offset int
+ offset, err = parseTimezone(zoneStr)
+ if offset == 0 {
+ location = time.UTC
+ } else {
+ location = time.FixedZone("", offset)
+ }
+ }
+ }
+
+ result = time.Date(year, time.Month(month), day, hour, minute, second, 0, location)
+ return
+}
+
+// MarshalBoolean marshals bool to SOAP "boolean" type.
+func MarshalBoolean(v bool) (string, error) {
+ if v {
+ return "1", nil
+ }
+ return "0", nil
+}
+
+// UnmarshalBoolean unmarshals bool from the SOAP "boolean" type.
+func UnmarshalBoolean(s string) (bool, error) {
+ switch s {
+ case "0", "false", "no":
+ return false, nil
+ case "1", "true", "yes":
+ return true, nil
+ }
+ return false, fmt.Errorf("soap boolean: %q is not a valid boolean value", s)
+}
+
+// MarshalBinBase64 marshals []byte to SOAP "bin.base64" type.
+func MarshalBinBase64(v []byte) (string, error) {
+ return base64.StdEncoding.EncodeToString(v), nil
+}
+
+// UnmarshalBinBase64 unmarshals []byte from the SOAP "bin.base64" type.
+func UnmarshalBinBase64(s string) ([]byte, error) {
+ return base64.StdEncoding.DecodeString(s)
+}
+
+// MarshalBinHex marshals []byte to SOAP "bin.hex" type.
+func MarshalBinHex(v []byte) (string, error) {
+ return hex.EncodeToString(v), nil
+}
+
+// UnmarshalBinHex unmarshals []byte from the SOAP "bin.hex" type.
+func UnmarshalBinHex(s string) ([]byte, error) {
+ return hex.DecodeString(s)
+}
diff --git a/Godeps/_workspace/src/github.com/huin/goupnp/soap/types_test.go b/Godeps/_workspace/src/github.com/huin/goupnp/soap/types_test.go
new file mode 100644
index 000000000..da6816190
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/huin/goupnp/soap/types_test.go
@@ -0,0 +1,481 @@
+package soap
+
+import (
+ "bytes"
+ "math"
+ "testing"
+ "time"
+)
+
+type convTest interface {
+ Marshal() (string, error)
+ Unmarshal(string) (interface{}, error)
+ Equal(result interface{}) bool
+}
+
+// duper is an interface that convTest values may optionally also implement to
+// generate another convTest for a value in an otherwise identical testCase.
+type duper interface {
+ Dupe(tag string) []convTest
+}
+
+type testCase struct {
+ value convTest
+ str string
+ wantMarshalErr bool
+ wantUnmarshalErr bool
+ noMarshal bool
+ noUnMarshal bool
+ tag string
+}
+
+type Ui1Test uint8
+
+func (v Ui1Test) Marshal() (string, error) {
+ return MarshalUi1(uint8(v))
+}
+func (v Ui1Test) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalUi1(s)
+}
+func (v Ui1Test) Equal(result interface{}) bool {
+ return uint8(v) == result.(uint8)
+}
+func (v Ui1Test) Dupe(tag string) []convTest {
+ if tag == "dupe" {
+ return []convTest{
+ Ui2Test(v),
+ Ui4Test(v),
+ }
+ }
+ return nil
+}
+
+type Ui2Test uint16
+
+func (v Ui2Test) Marshal() (string, error) {
+ return MarshalUi2(uint16(v))
+}
+func (v Ui2Test) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalUi2(s)
+}
+func (v Ui2Test) Equal(result interface{}) bool {
+ return uint16(v) == result.(uint16)
+}
+
+type Ui4Test uint32
+
+func (v Ui4Test) Marshal() (string, error) {
+ return MarshalUi4(uint32(v))
+}
+func (v Ui4Test) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalUi4(s)
+}
+func (v Ui4Test) Equal(result interface{}) bool {
+ return uint32(v) == result.(uint32)
+}
+
+type I1Test int8
+
+func (v I1Test) Marshal() (string, error) {
+ return MarshalI1(int8(v))
+}
+func (v I1Test) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalI1(s)
+}
+func (v I1Test) Equal(result interface{}) bool {
+ return int8(v) == result.(int8)
+}
+func (v I1Test) Dupe(tag string) []convTest {
+ if tag == "dupe" {
+ return []convTest{
+ I2Test(v),
+ I4Test(v),
+ }
+ }
+ return nil
+}
+
+type I2Test int16
+
+func (v I2Test) Marshal() (string, error) {
+ return MarshalI2(int16(v))
+}
+func (v I2Test) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalI2(s)
+}
+func (v I2Test) Equal(result interface{}) bool {
+ return int16(v) == result.(int16)
+}
+
+type I4Test int32
+
+func (v I4Test) Marshal() (string, error) {
+ return MarshalI4(int32(v))
+}
+func (v I4Test) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalI4(s)
+}
+func (v I4Test) Equal(result interface{}) bool {
+ return int32(v) == result.(int32)
+}
+
+type IntTest int64
+
+func (v IntTest) Marshal() (string, error) {
+ return MarshalInt(int64(v))
+}
+func (v IntTest) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalInt(s)
+}
+func (v IntTest) Equal(result interface{}) bool {
+ return int64(v) == result.(int64)
+}
+
+type Fixed14_4Test float64
+
+func (v Fixed14_4Test) Marshal() (string, error) {
+ return MarshalFixed14_4(float64(v))
+}
+func (v Fixed14_4Test) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalFixed14_4(s)
+}
+func (v Fixed14_4Test) Equal(result interface{}) bool {
+ return math.Abs(float64(v)-result.(float64)) < 0.001
+}
+
+type CharTest rune
+
+func (v CharTest) Marshal() (string, error) {
+ return MarshalChar(rune(v))
+}
+func (v CharTest) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalChar(s)
+}
+func (v CharTest) Equal(result interface{}) bool {
+ return rune(v) == result.(rune)
+}
+
+type DateTest struct{ time.Time }
+
+func (v DateTest) Marshal() (string, error) {
+ return MarshalDate(time.Time(v.Time))
+}
+func (v DateTest) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalDate(s)
+}
+func (v DateTest) Equal(result interface{}) bool {
+ return v.Time.Equal(result.(time.Time))
+}
+func (v DateTest) Dupe(tag string) []convTest {
+ if tag != "no:dateTime" {
+ return []convTest{DateTimeTest{v.Time}}
+ }
+ return nil
+}
+
+type TimeOfDayTest struct {
+ TimeOfDay
+}
+
+func (v TimeOfDayTest) Marshal() (string, error) {
+ return MarshalTimeOfDay(v.TimeOfDay)
+}
+func (v TimeOfDayTest) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalTimeOfDay(s)
+}
+func (v TimeOfDayTest) Equal(result interface{}) bool {
+ return v.TimeOfDay == result.(TimeOfDay)
+}
+func (v TimeOfDayTest) Dupe(tag string) []convTest {
+ if tag != "no:time.tz" {
+ return []convTest{TimeOfDayTzTest{v.TimeOfDay}}
+ }
+ return nil
+}
+
+type TimeOfDayTzTest struct {
+ TimeOfDay
+}
+
+func (v TimeOfDayTzTest) Marshal() (string, error) {
+ return MarshalTimeOfDayTz(v.TimeOfDay)
+}
+func (v TimeOfDayTzTest) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalTimeOfDayTz(s)
+}
+func (v TimeOfDayTzTest) Equal(result interface{}) bool {
+ return v.TimeOfDay == result.(TimeOfDay)
+}
+
+type DateTimeTest struct{ time.Time }
+
+func (v DateTimeTest) Marshal() (string, error) {
+ return MarshalDateTime(time.Time(v.Time))
+}
+func (v DateTimeTest) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalDateTime(s)
+}
+func (v DateTimeTest) Equal(result interface{}) bool {
+ return v.Time.Equal(result.(time.Time))
+}
+func (v DateTimeTest) Dupe(tag string) []convTest {
+ if tag != "no:dateTime.tz" {
+ return []convTest{DateTimeTzTest{v.Time}}
+ }
+ return nil
+}
+
+type DateTimeTzTest struct{ time.Time }
+
+func (v DateTimeTzTest) Marshal() (string, error) {
+ return MarshalDateTimeTz(time.Time(v.Time))
+}
+func (v DateTimeTzTest) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalDateTimeTz(s)
+}
+func (v DateTimeTzTest) Equal(result interface{}) bool {
+ return v.Time.Equal(result.(time.Time))
+}
+
+type BooleanTest bool
+
+func (v BooleanTest) Marshal() (string, error) {
+ return MarshalBoolean(bool(v))
+}
+func (v BooleanTest) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalBoolean(s)
+}
+func (v BooleanTest) Equal(result interface{}) bool {
+ return bool(v) == result.(bool)
+}
+
+type BinBase64Test []byte
+
+func (v BinBase64Test) Marshal() (string, error) {
+ return MarshalBinBase64([]byte(v))
+}
+func (v BinBase64Test) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalBinBase64(s)
+}
+func (v BinBase64Test) Equal(result interface{}) bool {
+ return bytes.Equal([]byte(v), result.([]byte))
+}
+
+type BinHexTest []byte
+
+func (v BinHexTest) Marshal() (string, error) {
+ return MarshalBinHex([]byte(v))
+}
+func (v BinHexTest) Unmarshal(s string) (interface{}, error) {
+ return UnmarshalBinHex(s)
+}
+func (v BinHexTest) Equal(result interface{}) bool {
+ return bytes.Equal([]byte(v), result.([]byte))
+}
+
+func Test(t *testing.T) {
+ const time010203 time.Duration = (1*3600 + 2*60 + 3) * time.Second
+ const time0102 time.Duration = (1*3600 + 2*60) * time.Second
+ const time01 time.Duration = (1 * 3600) * time.Second
+ const time235959 time.Duration = (23*3600 + 59*60 + 59) * time.Second
+
+ // Fake out the local time for the implementation.
+ localLoc = time.FixedZone("Fake/Local", 6*3600)
+ defer func() {
+ localLoc = time.Local
+ }()
+
+ tests := []testCase{
+ // ui1
+ {str: "", value: Ui1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"},
+ {str: " ", value: Ui1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"},
+ {str: "abc", value: Ui1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"},
+ {str: "-1", value: Ui1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"},
+ {str: "0", value: Ui1Test(0), tag: "dupe"},
+ {str: "1", value: Ui1Test(1), tag: "dupe"},
+ {str: "255", value: Ui1Test(255), tag: "dupe"},
+ {str: "256", value: Ui1Test(0), wantUnmarshalErr: true, noMarshal: true},
+
+ // ui2
+ {str: "65535", value: Ui2Test(65535)},
+ {str: "65536", value: Ui2Test(0), wantUnmarshalErr: true, noMarshal: true},
+
+ // ui4
+ {str: "4294967295", value: Ui4Test(4294967295)},
+ {str: "4294967296", value: Ui4Test(0), wantUnmarshalErr: true, noMarshal: true},
+
+ // i1
+ {str: "", value: I1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"},
+ {str: " ", value: I1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"},
+ {str: "abc", value: I1Test(0), wantUnmarshalErr: true, noMarshal: true, tag: "dupe"},
+ {str: "0", value: I1Test(0), tag: "dupe"},
+ {str: "-1", value: I1Test(-1), tag: "dupe"},
+ {str: "127", value: I1Test(127), tag: "dupe"},
+ {str: "-128", value: I1Test(-128), tag: "dupe"},
+ {str: "128", value: I1Test(0), wantUnmarshalErr: true, noMarshal: true},
+ {str: "-129", value: I1Test(0), wantUnmarshalErr: true, noMarshal: true},
+
+ // i2
+ {str: "32767", value: I2Test(32767)},
+ {str: "-32768", value: I2Test(-32768)},
+ {str: "32768", value: I2Test(0), wantUnmarshalErr: true, noMarshal: true},
+ {str: "-32769", value: I2Test(0), wantUnmarshalErr: true, noMarshal: true},
+
+ // i4
+ {str: "2147483647", value: I4Test(2147483647)},
+ {str: "-2147483648", value: I4Test(-2147483648)},
+ {str: "2147483648", value: I4Test(0), wantUnmarshalErr: true, noMarshal: true},
+ {str: "-2147483649", value: I4Test(0), wantUnmarshalErr: true, noMarshal: true},
+
+ // int
+ {str: "9223372036854775807", value: IntTest(9223372036854775807)},
+ {str: "-9223372036854775808", value: IntTest(-9223372036854775808)},
+ {str: "9223372036854775808", value: IntTest(0), wantUnmarshalErr: true, noMarshal: true},
+ {str: "-9223372036854775809", value: IntTest(0), wantUnmarshalErr: true, noMarshal: true},
+
+ // fixed.14.4
+ {str: "0.0000", value: Fixed14_4Test(0)},
+ {str: "1.0000", value: Fixed14_4Test(1)},
+ {str: "1.2346", value: Fixed14_4Test(1.23456)},
+ {str: "-1.0000", value: Fixed14_4Test(-1)},
+ {str: "-1.2346", value: Fixed14_4Test(-1.23456)},
+ {str: "10000000000000.0000", value: Fixed14_4Test(1e13)},
+ {str: "100000000000000.0000", value: Fixed14_4Test(1e14), wantMarshalErr: true, wantUnmarshalErr: true},
+ {str: "-10000000000000.0000", value: Fixed14_4Test(-1e13)},
+ {str: "-100000000000000.0000", value: Fixed14_4Test(-1e14), wantMarshalErr: true, wantUnmarshalErr: true},
+
+ // char
+ {str: "a", value: CharTest('a')},
+ {str: "z", value: CharTest('z')},
+ {str: "\u1234", value: CharTest(0x1234)},
+ {str: "aa", value: CharTest(0), wantMarshalErr: true, wantUnmarshalErr: true},
+ {str: "", value: CharTest(0), wantMarshalErr: true, wantUnmarshalErr: true},
+
+ // date
+ {str: "2013-10-08", value: DateTest{time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)}, tag: "no:dateTime"},
+ {str: "20131008", value: DateTest{time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)}, noMarshal: true, tag: "no:dateTime"},
+ {str: "2013-10-08T10:30:50", value: DateTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime"},
+ {str: "2013-10-08T10:30:50Z", value: DateTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime"},
+ {str: "", value: DateTest{}, wantMarshalErr: true, wantUnmarshalErr: true, noMarshal: true},
+ {str: "-1", value: DateTest{}, wantUnmarshalErr: true, noMarshal: true},
+
+ // time
+ {str: "00:00:00", value: TimeOfDayTest{TimeOfDay{FromMidnight: 0}}},
+ {str: "000000", value: TimeOfDayTest{TimeOfDay{FromMidnight: 0}}, noMarshal: true},
+ {str: "24:00:00", value: TimeOfDayTest{TimeOfDay{FromMidnight: 24 * time.Hour}}, noMarshal: true}, // ISO8601 special case
+ {str: "24:01:00", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true},
+ {str: "24:00:01", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true},
+ {str: "25:00:00", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true},
+ {str: "00:60:00", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true},
+ {str: "00:00:60", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true},
+ {str: "01:02:03", value: TimeOfDayTest{TimeOfDay{FromMidnight: time010203}}},
+ {str: "010203", value: TimeOfDayTest{TimeOfDay{FromMidnight: time010203}}, noMarshal: true},
+ {str: "23:59:59", value: TimeOfDayTest{TimeOfDay{FromMidnight: time235959}}},
+ {str: "235959", value: TimeOfDayTest{TimeOfDay{FromMidnight: time235959}}, noMarshal: true},
+ {str: "01:02", value: TimeOfDayTest{TimeOfDay{FromMidnight: time0102}}, noMarshal: true},
+ {str: "0102", value: TimeOfDayTest{TimeOfDay{FromMidnight: time0102}}, noMarshal: true},
+ {str: "01", value: TimeOfDayTest{TimeOfDay{FromMidnight: time01}}, noMarshal: true},
+ {str: "foo 01:02:03", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
+ {str: "foo\n01:02:03", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
+ {str: "01:02:03 foo", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
+ {str: "01:02:03\nfoo", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
+ {str: "01:02:03Z", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
+ {str: "01:02:03+01", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
+ {str: "01:02:03+01:23", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
+ {str: "01:02:03+0123", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
+ {str: "01:02:03-01", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
+ {str: "01:02:03-01:23", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
+ {str: "01:02:03-0123", value: TimeOfDayTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:time.tz"},
+
+ // time.tz
+ {str: "24:00:01", value: TimeOfDayTzTest{}, wantUnmarshalErr: true, noMarshal: true},
+ {str: "01Z", value: TimeOfDayTzTest{TimeOfDay{time01, true, 0}}, noMarshal: true},
+ {str: "01:02:03Z", value: TimeOfDayTzTest{TimeOfDay{time010203, true, 0}}},
+ {str: "01+01", value: TimeOfDayTzTest{TimeOfDay{time01, true, 3600}}, noMarshal: true},
+ {str: "01:02:03+01", value: TimeOfDayTzTest{TimeOfDay{time010203, true, 3600}}, noMarshal: true},
+ {str: "01:02:03+01:23", value: TimeOfDayTzTest{TimeOfDay{time010203, true, 3600 + 23*60}}},
+ {str: "01:02:03+0123", value: TimeOfDayTzTest{TimeOfDay{time010203, true, 3600 + 23*60}}, noMarshal: true},
+ {str: "01:02:03-01", value: TimeOfDayTzTest{TimeOfDay{time010203, true, -3600}}, noMarshal: true},
+ {str: "01:02:03-01:23", value: TimeOfDayTzTest{TimeOfDay{time010203, true, -(3600 + 23*60)}}},
+ {str: "01:02:03-0123", value: TimeOfDayTzTest{TimeOfDay{time010203, true, -(3600 + 23*60)}}, noMarshal: true},
+
+ // dateTime
+ {str: "2013-10-08T00:00:00", value: DateTimeTest{time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)}, tag: "no:dateTime.tz"},
+ {str: "20131008", value: DateTimeTest{time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)}, noMarshal: true},
+ {str: "2013-10-08T10:30:50", value: DateTimeTest{time.Date(2013, 10, 8, 10, 30, 50, 0, localLoc)}, tag: "no:dateTime.tz"},
+ {str: "2013-10-08T10:30:50T", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true},
+ {str: "2013-10-08T10:30:50+01", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"},
+ {str: "2013-10-08T10:30:50+01:23", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"},
+ {str: "2013-10-08T10:30:50+0123", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"},
+ {str: "2013-10-08T10:30:50-01", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"},
+ {str: "2013-10-08T10:30:50-01:23", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"},
+ {str: "2013-10-08T10:30:50-0123", value: DateTimeTest{}, wantUnmarshalErr: true, noMarshal: true, tag: "no:dateTime.tz"},
+
+ // dateTime.tz
+ {str: "2013-10-08T10:30:50", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, localLoc)}, noMarshal: true},
+ {str: "2013-10-08T10:30:50+01", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:00", 3600))}, noMarshal: true},
+ {str: "2013-10-08T10:30:50+01:23", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:23", 3600+23*60))}},
+ {str: "2013-10-08T10:30:50+0123", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:23", 3600+23*60))}, noMarshal: true},
+ {str: "2013-10-08T10:30:50-01", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:00", -3600))}, noMarshal: true},
+ {str: "2013-10-08T10:30:50-01:23", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:23", -(3600+23*60)))}},
+ {str: "2013-10-08T10:30:50-0123", value: DateTimeTzTest{time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:23", -(3600+23*60)))}, noMarshal: true},
+
+ // boolean
+ {str: "0", value: BooleanTest(false)},
+ {str: "1", value: BooleanTest(true)},
+ {str: "false", value: BooleanTest(false), noMarshal: true},
+ {str: "true", value: BooleanTest(true), noMarshal: true},
+ {str: "no", value: BooleanTest(false), noMarshal: true},
+ {str: "yes", value: BooleanTest(true), noMarshal: true},
+ {str: "", value: BooleanTest(false), noMarshal: true, wantUnmarshalErr: true},
+ {str: "other", value: BooleanTest(false), noMarshal: true, wantUnmarshalErr: true},
+ {str: "2", value: BooleanTest(false), noMarshal: true, wantUnmarshalErr: true},
+ {str: "-1", value: BooleanTest(false), noMarshal: true, wantUnmarshalErr: true},
+
+ // bin.base64
+ {str: "", value: BinBase64Test{}},
+ {str: "YQ==", value: BinBase64Test("a")},
+ {str: "TG9uZ2VyIFN0cmluZy4=", value: BinBase64Test("Longer String.")},
+ {str: "TG9uZ2VyIEFsaWduZWQu", value: BinBase64Test("Longer Aligned.")},
+
+ // bin.hex
+ {str: "", value: BinHexTest{}},
+ {str: "61", value: BinHexTest("a")},
+ {str: "4c6f6e67657220537472696e672e", value: BinHexTest("Longer String.")},
+ {str: "4C6F6E67657220537472696E672E", value: BinHexTest("Longer String."), noMarshal: true},
+ }
+
+ // Generate extra test cases from convTests that implement duper.
+ var extras []testCase
+ for i := range tests {
+ if duper, ok := tests[i].value.(duper); ok {
+ dupes := duper.Dupe(tests[i].tag)
+ for _, duped := range dupes {
+ dupedCase := testCase(tests[i])
+ dupedCase.value = duped
+ extras = append(extras, dupedCase)
+ }
+ }
+ }
+ tests = append(tests, extras...)
+
+ for _, test := range tests {
+ if test.noMarshal {
+ } else if resultStr, err := test.value.Marshal(); err != nil && !test.wantMarshalErr {
+ t.Errorf("For %T marshal %v, want %q, got error: %v", test.value, test.value, test.str, err)
+ } else if err == nil && test.wantMarshalErr {
+ t.Errorf("For %T marshal %v, want error, got %q", test.value, test.value, resultStr)
+ } else if err == nil && resultStr != test.str {
+ t.Errorf("For %T marshal %v, want %q, got %q", test.value, test.value, test.str, resultStr)
+ }
+
+ if test.noUnMarshal {
+ } else if resultValue, err := test.value.Unmarshal(test.str); err != nil && !test.wantUnmarshalErr {
+ t.Errorf("For %T unmarshal %q, want %v, got error: %v", test.value, test.str, test.value, err)
+ } else if err == nil && test.wantUnmarshalErr {
+ t.Errorf("For %T unmarshal %q, want error, got %v", test.value, test.str, resultValue)
+ } else if err == nil && !test.value.Equal(resultValue) {
+ t.Errorf("For %T unmarshal %q, want %v, got %v", test.value, test.str, test.value, resultValue)
+ }
+ }
+}