Create v2/soap/types, refactored from v1.

This commit is contained in:
John Beisley 2021-07-04 18:14:04 +01:00 committed by Huin
parent 0926402f8c
commit 47e825446b
2 changed files with 1288 additions and 0 deletions

864
v2/soap/types/types.go Normal file
View File

@ -0,0 +1,864 @@
// Package types defines types that encode values in SOAP requests and responses.
package types
import (
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"net/url"
"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
)
type SOAPValue interface {
Marshal() (string, error)
Unmarshal(s string) error
}
type UI1 uint8
var _ SOAPValue = new(UI1)
func NewUI1(v uint8) *UI1 {
v2 := UI1(v)
return &v2
}
func (v *UI1) String() string {
return strconv.FormatUint(uint64(*v), 10)
}
func (v *UI1) Marshal() (string, error) {
return v.String(), nil
}
func (v *UI1) Unmarshal(s string) error {
v2, err := strconv.ParseUint(s, 10, 8)
*v = UI1(v2)
return err
}
type UI2 uint16
var _ SOAPValue = new(UI2)
func NewUI2(v uint16) *UI2 {
v2 := UI2(v)
return &v2
}
func (v *UI2) String() string {
return strconv.FormatUint(uint64(*v), 10)
}
func (v *UI2) Marshal() (string, error) {
return v.String(), nil
}
func (v *UI2) Unmarshal(s string) error {
v2, err := strconv.ParseUint(s, 10, 16)
*v = UI2(v2)
return err
}
type UI4 uint32
var _ SOAPValue = new(UI4)
func NewUI4(v uint32) *UI4 {
v2 := UI4(v)
return &v2
}
func (v *UI4) String() string {
return strconv.FormatUint(uint64(*v), 10)
}
func (v *UI4) Marshal() (string, error) {
return v.String(), nil
}
func (v *UI4) Unmarshal(s string) error {
v2, err := strconv.ParseUint(s, 10, 32)
*v = UI4(v2)
return err
}
type UI8 uint64
var _ SOAPValue = new(UI8)
func NewUI8(v uint64) *UI8 {
v2 := UI8(v)
return &v2
}
func (v *UI8) String() string {
return strconv.FormatUint(uint64(*v), 10)
}
func (v *UI8) Marshal() (string, error) {
return v.String(), nil
}
func (v *UI8) Unmarshal(s string) error {
v2, err := strconv.ParseUint(s, 10, 64)
*v = UI8(v2)
return err
}
type I1 int8
var _ SOAPValue = new(I1)
func NewI1(v int8) *I1 {
v2 := I1(v)
return &v2
}
func (v *I1) String() string {
return strconv.FormatInt(int64(*v), 10)
}
func (v *I1) Marshal() (string, error) {
return v.String(), nil
}
func (v *I1) Unmarshal(s string) error {
v2, err := strconv.ParseInt(s, 10, 8)
*v = I1(v2)
return err
}
type I2 int16
var _ SOAPValue = new(I2)
func NewI2(v int16) *I2 {
v2 := I2(v)
return &v2
}
func (v *I2) String() string {
return strconv.FormatInt(int64(*v), 10)
}
func (v *I2) Marshal() (string, error) {
return v.String(), nil
}
func (v *I2) Unmarshal(s string) error {
v2, err := strconv.ParseInt(s, 10, 16)
*v = I2(v2)
return err
}
type I4 int32
var _ SOAPValue = new(I4)
func NewI4(v int32) *I4 {
v2 := I4(v)
return &v2
}
func (v *I4) String() string {
return strconv.FormatInt(int64(*v), 10)
}
func (v *I4) Marshal() (string, error) {
return v.String(), nil
}
func (v *I4) Unmarshal(s string) error {
v2, err := strconv.ParseInt(s, 10, 32)
*v = I4(v2)
return err
}
type I8 int64
var _ SOAPValue = new(I8)
func NewI8(v int64) *I8 {
v2 := I8(v)
return &v2
}
func (v *I8) String() string {
return strconv.FormatInt(int64(*v), 10)
}
func (v *I8) Marshal() (string, error) {
return v.String(), nil
}
func (v *I8) Unmarshal(s string) error {
v2, err := strconv.ParseInt(s, 10, 64)
*v = I8(v2)
return err
}
type R4 float32
var _ SOAPValue = new(R4)
func NewR4(v float32) *R4 {
v2 := R4(v)
return &v2
}
func (v *R4) String() string {
return strconv.FormatFloat(float64(*v), 'G', -1, 32)
}
func (v *R4) Marshal() (string, error) {
return v.String(), nil
}
func (v *R4) Unmarshal(s string) error {
v2, err := strconv.ParseFloat(s, 32)
*v = R4(v2)
return err
}
type R8 float64
var _ SOAPValue = new(R8)
func NewR8(v float64) *R8 {
v2 := R8(v)
return &v2
}
func (v *R8) String() string {
return strconv.FormatFloat(float64(*v), 'G', -1, 64)
}
func (v *R8) Marshal() (string, error) {
return v.String(), nil
}
func (v *R8) Unmarshal(s string) error {
v2, err := strconv.ParseFloat(s, 64)
*v = R8(v2)
return err
}
// FloatFixed14_4 maps a float64 to the SOAP "fixed.14.4" type.
type FloatFixed14_4 float64
var _ SOAPValue = new(FloatFixed14_4)
func NewFloatFixed14_4(v float64) *FloatFixed14_4 {
v2 := FloatFixed14_4(v)
return &v2
}
func (v *FloatFixed14_4) String() string {
return strconv.FormatFloat(float64(*v), 'f', 4, 64)
}
func (v *FloatFixed14_4) Marshal() (string, error) {
if *v >= 1e14 || *v <= -1e14 {
return "", fmt.Errorf("soap fixed14.4: value %v out of bounds", v)
}
return v.String(), nil
}
func (v *FloatFixed14_4) Unmarshal(s string) error {
v2, err := strconv.ParseFloat(s, 64)
if err != nil {
return err
}
if v2 >= 1e14 || v2 <= -1e14 {
return fmt.Errorf("soap fixed14.4: value %q out of bounds", s)
}
*v = FloatFixed14_4(v2)
return nil
}
// Char maps rune to SOAP "char" type.
type Char rune
var _ SOAPValue = new(Char)
func NewChar(v rune) *Char {
v2 := Char(v)
return &v2
}
func (v *Char) String() string {
return string(*v)
}
func (v *Char) Marshal() (string, error) {
if *v == 0 {
return "", errors.New("soap char: rune 0 is not allowed")
}
return v.String(), nil
}
func (v *Char) Unmarshal(s string) error {
if len(s) == 0 {
return errors.New("soap char: got empty string")
}
v2, n := utf8.DecodeRune([]byte(s))
if n != len(s) {
return fmt.Errorf("soap char: value %q is not a single rune", s)
}
*v = Char(v2)
return nil
}
type String string
var _ SOAPValue = new(String)
func NewString(v string) *String {
v2 := String(v)
return &v2
}
func (v *String) Marshal() (string, error) {
return string(*v), nil
}
func (v *String) Unmarshal(s string) error {
*v = String(s)
return 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) (TimeOfDay, error) {
var parts []string
for _, re := range timeRegexps {
parts = re.FindStringSubmatch(s)
if parts != nil {
break
}
}
if parts == nil {
return TimeOfDay{}, fmt.Errorf("soap time: value %q is not in ISO8601 time format", s)
}
var err error
var hour, minute, second int8
hour = int8(parseInt(parts[1], &err))
if len(parts[2]) != 0 {
minute = int8(parseInt(parts[2], &err))
if len(parts[3]) != 0 {
second = int8(parseInt(parts[3], &err))
}
}
if err != nil {
return TimeOfDay{}, fmt.Errorf("soap time: %q: %v", s, err)
}
return TimeOfDay{hour, minute, second}, nil
}
// (+|-)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
}
// TimeOfDay is used in cases where SOAP "time" or "time.tz" is used.
// It contains non-timezone aware components.
type TimeOfDay struct {
Hour int8
Minute int8
Second int8
}
var _ SOAPValue = &TimeOfDay{}
// Sets components based on duration since midnight.
func (v *TimeOfDay) SetFromDuration(d time.Duration) error {
if d < 0 || d > 24*time.Hour {
return fmt.Errorf("out of range of SOAP time type: %v", d)
}
v.Hour = int8(d / time.Hour)
d = d % time.Hour
v.Minute = int8(d / time.Minute)
d = d % time.Minute
v.Second = int8(d / time.Second)
return nil
}
// Returns duration since midnight.
func (v *TimeOfDay) ToDuration() time.Duration {
return time.Duration(v.Hour)*time.Hour +
time.Duration(v.Minute)*time.Minute +
time.Duration(v.Second)*time.Second
}
func (v *TimeOfDay) String() string {
return fmt.Sprintf("%02d:%02d:%02d", v.Hour, v.Minute, v.Second)
}
func (v *TimeOfDay) Equal(o *TimeOfDay) bool {
return v.Hour == o.Hour && v.Minute == o.Minute && v.Second == o.Second
}
// IsValid returns true iff v is positive and <= 24 hours.
// It allows equal to 24 hours as a special case as 24:00:00 is an allowed
// value by the SOAP type.
func (v *TimeOfDay) CheckValid() error {
if (v.Hour < 0 || v.Minute < 0 || v.Second < 0) ||
(v.Hour == 24 && (v.Minute > 0 || v.Second > 0)) ||
v.Hour > 24 || v.Minute >= 60 || v.Second >= 60 {
return fmt.Errorf("soap time: value %v has components(s) out of range", v)
}
return nil
}
func (v *TimeOfDay) Marshal() (string, error) {
if err := v.CheckValid(); err != nil {
return "", err
}
return v.String(), nil
}
func (v *TimeOfDay) Unmarshal(s string) error {
var err error
*v, err = parseTimeParts(s)
if err != nil {
return err
}
return v.CheckValid()
}
// TimeOfDayTZ is used in cases where SOAP "time.tz" is used.
type TimeOfDayTZ struct {
// Components of the time of day.
TimeOfDay TimeOfDay
// 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
}
var _ SOAPValue = &TimeOfDayTZ{}
func (v *TimeOfDayTZ) String() string {
return fmt.Sprintf("%v %t %+03d:%02d:%02d", v.TimeOfDay, v.HasOffset, v.Offset/3600, (v.Offset%3600)/60, v.Offset%60)
}
func (v *TimeOfDayTZ) Equal(o *TimeOfDayTZ) bool {
return v.TimeOfDay.Equal(&o.TimeOfDay) &&
v.HasOffset == o.HasOffset && v.Offset == o.Offset
}
func (v *TimeOfDayTZ) Marshal() (string, error) {
tod, err := v.TimeOfDay.Marshal()
if err != nil {
return "", err
}
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 tod + tz, nil
}
func (v *TimeOfDayTZ) Unmarshal(s string) error {
zoneIndex := strings.IndexAny(s, "Z+-")
var timePart string
if zoneIndex == -1 {
v.HasOffset = false
v.Offset = 0
timePart = s
} else {
v.HasOffset = true
timePart = s[:zoneIndex]
var err error
v.Offset, err = parseTimezone(s[zoneIndex:])
if err != nil {
return err
}
}
if err := v.TimeOfDay.Unmarshal(timePart); err != nil {
return err
}
return nil
}
// DateLocal maps time.Time to the SOAP "date" type. Dates map to midnight in
// the local time zone. The time of day components are ignored when
// marshalling.
type DateLocal time.Time
var _ SOAPValue = &DateLocal{}
func NewDateLocal(v time.Time) *DateLocal {
v2 := DateLocal(v)
return &v2
}
func (v DateLocal) String() string {
return v.ToTime().String()
}
func (v DateLocal) ToTime() time.Time {
return time.Time(v)
}
func (v *DateLocal) Marshal() (string, error) {
return time.Time(*v).In(localLoc).Format("2006-01-02"), nil
}
func (v *DateLocal) Unmarshal(s string) error {
year, month, day, err := parseDateParts(s)
if err != nil {
return err
}
*v = DateLocal(time.Date(year, time.Month(month), day, 0, 0, 0, 0, localLoc))
return nil
}
// MarshalDateTime maps time.Time to SOAP "dateTime" type, with the local timezone.
type DateTimeLocal time.Time
var _ SOAPValue = &DateTimeLocal{}
func NewDateTimeLocal(v time.Time) *DateTimeLocal {
v2 := DateTimeLocal(v)
return &v2
}
func (v DateTimeLocal) String() string {
return v.ToTime().String()
}
func (v DateTimeLocal) ToTime() time.Time {
return time.Time(v)
}
func (v *DateTimeLocal) Marshal() (string, error) {
return v.ToTime().In(localLoc).Format("2006-01-02T15:04:05"), nil
}
func (v *DateTimeLocal) Unmarshal(s string) error {
dateStr, timeStr, zoneStr, err := splitCompleteDateTimeZone(s)
if err != nil {
return err
}
if len(zoneStr) != 0 {
return fmt.Errorf("soap datetime: unexpected timezone in %q", s)
}
year, month, day, err := parseDateParts(dateStr)
if err != nil {
return err
}
var tod TimeOfDay
if len(timeStr) != 0 {
tod, err = parseTimeParts(timeStr)
if err != nil {
return err
}
}
*v = DateTimeLocal(time.Date(year, time.Month(month), day,
int(tod.Hour), int(tod.Minute), int(tod.Second), 0,
localLoc))
return nil
}
// DateTimeLocal maps time.Time to SOAP "dateTime.tz" type, using the local
// timezone when one is unspecified.
type DateTimeTZLocal time.Time
var _ SOAPValue = &DateTimeTZLocal{}
func NewDateTimeTZLocal(v time.Time) *DateTimeTZLocal {
v2 := DateTimeTZLocal(v)
return &v2
}
func (v DateTimeTZLocal) String() string {
return v.ToTime().String()
}
func (v DateTimeTZLocal) ToTime() time.Time {
return time.Time(v)
}
func (v *DateTimeTZLocal) Marshal() (string, error) {
return time.Time(*v).Format("2006-01-02T15:04:05-07:00"), nil
}
func (v *DateTimeTZLocal) Unmarshal(s string) error {
dateStr, timeStr, zoneStr, err := splitCompleteDateTimeZone(s)
if err != nil {
return err
}
year, month, day, err := parseDateParts(dateStr)
if err != nil {
return err
}
var tod TimeOfDay
var location *time.Location = localLoc
if len(timeStr) != 0 {
tod, err = parseTimeParts(timeStr)
if err != nil {
return err
}
if len(zoneStr) != 0 {
var offset int
offset, err = parseTimezone(zoneStr)
if err != nil {
return err
}
if offset == 0 {
location = time.UTC
} else {
location = time.FixedZone("", offset)
}
}
}
*v = DateTimeTZLocal(time.Date(year, time.Month(month), day,
int(tod.Hour), int(tod.Minute), int(tod.Second), 0,
location))
return nil
}
type Boolean bool
var _ SOAPValue = new(Boolean)
func NewBoolean(v bool) *Boolean {
v2 := Boolean(v)
return &v2
}
func (v *Boolean) String() string {
if *v {
return "true"
}
return "false"
}
func (v *Boolean) Marshal() (string, error) {
if *v {
return "1", nil
}
return "0", nil
}
func (v *Boolean) Unmarshal(s string) error {
switch s {
case "0", "false", "no":
*v = false
case "1", "true", "yes":
*v = true
default:
return fmt.Errorf("soap boolean: %q is not a valid boolean value", s)
}
return nil
}
// BinBase64 maps []byte to SOAP "bin.base64" type.
type BinBase64 []byte
var _ SOAPValue = new(BinBase64)
func NewBinBase64(v []byte) *BinBase64 {
v2 := BinBase64(v)
return &v2
}
func (v *BinBase64) String() string {
return base64.StdEncoding.EncodeToString(*v)
}
func (v *BinBase64) Marshal() (string, error) {
return v.String(), nil
}
func (v *BinBase64) Unmarshal(s string) error {
v2, err := base64.StdEncoding.DecodeString(s)
*v = v2
return err
}
// BinHex maps []byte to SOAP "bin.hex" type.
type BinHex []byte
var _ SOAPValue = new(BinHex)
func NewBinHex(v []byte) *BinHex {
v2 := BinHex(v)
return &v2
}
func (v *BinHex) String() string {
return hex.EncodeToString(*v)
}
func (v *BinHex) Marshal() (string, error) {
return v.String(), nil
}
func (v *BinHex) Unmarshal(s string) error {
v2, err := hex.DecodeString(s)
*v = v2
return err
}
// URI maps *url.URL to SOAP "uri" type.
type URI url.URL
var _ SOAPValue = new(URI)
func (v *URI) String() string {
return v.ToURL().String()
}
func (v *URI) ToURL() *url.URL {
return (*url.URL)(v)
}
func (v *URI) Marshal() (string, error) {
return (*url.URL)(v).String(), nil
}
func (v *URI) Unmarshal(s string) error {
v2, err := url.Parse(s)
if err != nil {
return err
}
*v = URI(*v2)
return nil
}

424
v2/soap/types/types_test.go Normal file
View File

@ -0,0 +1,424 @@
package types
import (
"bytes"
"fmt"
"testing"
"time"
)
type isEqual func(got, want SOAPValue) bool
type typeTestCase struct {
makeValue func() SOAPValue
isEqual isEqual
marshalTests []marshalCase
marshalErrs []SOAPValue
unmarshalTests []unmarshalCase
unmarshalErrs []string
}
type marshalCase struct {
input SOAPValue
want string
}
type unmarshalCase struct {
input string
want SOAPValue
}
func Test(t *testing.T) {
// Fake out the local time for the implementation.
localLoc = time.FixedZone("Fake/Local", 6*3600)
defer func() {
localLoc = time.Local
}()
badNumbers := []string{"", " ", "abc"}
typeTestCases := []typeTestCase{
{
makeValue: func() SOAPValue { return new(UI1) },
isEqual: func(got, want SOAPValue) bool { return *got.(*UI1) == *want.(*UI1) },
marshalTests: []marshalCase{
{NewUI1(0), "0"},
{NewUI1(1), "1"},
{NewUI1(255), "255"},
},
unmarshalErrs: append([]string{"-1", "256"}, badNumbers...),
},
{
makeValue: func() SOAPValue { return new(UI2) },
isEqual: func(got, want SOAPValue) bool { return *got.(*UI2) == *want.(*UI2) },
marshalTests: []marshalCase{
{NewUI2(0), "0"},
{NewUI2(1), "1"},
{NewUI2(65535), "65535"},
},
unmarshalErrs: append([]string{"-1", "65536"}, badNumbers...),
},
{
makeValue: func() SOAPValue { return new(UI4) },
isEqual: func(got, want SOAPValue) bool { return *got.(*UI4) == *want.(*UI4) },
marshalTests: []marshalCase{
{NewUI4(0), "0"},
{NewUI4(1), "1"},
{NewUI4(4294967295), "4294967295"},
},
unmarshalErrs: append([]string{"-1", "4294967296"}, badNumbers...),
},
{
makeValue: func() SOAPValue { return new(UI8) },
isEqual: func(got, want SOAPValue) bool { return *got.(*UI8) == *want.(*UI8) },
marshalTests: []marshalCase{
{NewUI8(0), "0"},
{NewUI8(1), "1"},
{NewUI8(18446744073709551615), "18446744073709551615"},
},
unmarshalErrs: append([]string{"-1", "18446744073709551616"}, badNumbers...),
},
{
makeValue: func() SOAPValue { return new(I1) },
isEqual: func(got, want SOAPValue) bool { return *got.(*I1) == *want.(*I1) },
marshalTests: []marshalCase{
{NewI1(0), "0"},
{NewI1(1), "1"},
{NewI1(-1), "-1"},
{NewI1(127), "127"},
{NewI1(-128), "-128"},
},
unmarshalErrs: append([]string{"-129", "128"}, badNumbers...),
},
{
makeValue: func() SOAPValue { return new(I2) },
isEqual: func(got, want SOAPValue) bool { return *got.(*I2) == *want.(*I2) },
marshalTests: []marshalCase{
{NewI2(0), "0"},
{NewI2(1), "1"},
{NewI2(-1), "-1"},
{NewI2(32767), "32767"},
{NewI2(-32768), "-32768"},
},
unmarshalErrs: append([]string{"-32769", "32768"}, badNumbers...),
},
{
makeValue: func() SOAPValue { return new(I4) },
isEqual: func(got, want SOAPValue) bool { return *got.(*I4) == *want.(*I4) },
marshalTests: []marshalCase{
{NewI4(0), "0"},
{NewI4(1), "1"},
{NewI4(-1), "-1"},
{NewI4(2147483647), "2147483647"},
{NewI4(-2147483648), "-2147483648"},
},
unmarshalErrs: append([]string{"-2147483649", "2147483648"}, badNumbers...),
},
{
makeValue: func() SOAPValue { return new(I8) },
isEqual: func(got, want SOAPValue) bool { return *got.(*I8) == *want.(*I8) },
marshalTests: []marshalCase{
{NewI8(0), "0"},
{NewI8(1), "1"},
{NewI8(-1), "-1"},
{NewI8(9223372036854775807), "9223372036854775807"},
{NewI8(-9223372036854775808), "-9223372036854775808"},
},
unmarshalErrs: append([]string{"-9223372036854775809", "9223372036854775808"}, badNumbers...),
},
{
makeValue: func() SOAPValue { return new(FloatFixed14_4) },
isEqual: func(got, want SOAPValue) bool { return *got.(*FloatFixed14_4) == *want.(*FloatFixed14_4) },
marshalTests: []marshalCase{
{NewFloatFixed14_4(0), "0.0000"},
{NewFloatFixed14_4(1), "1.0000"},
{NewFloatFixed14_4(1.2346), "1.2346"},
{NewFloatFixed14_4(-1), "-1.0000"},
{NewFloatFixed14_4(-1.2346), "-1.2346"},
{NewFloatFixed14_4(1e13), "10000000000000.0000"},
{NewFloatFixed14_4(-1e13), "-10000000000000.0000"},
},
marshalErrs: []SOAPValue{
NewFloatFixed14_4(1e14),
NewFloatFixed14_4(-1e14),
},
},
{
makeValue: func() SOAPValue { return new(Char) },
isEqual: func(got, want SOAPValue) bool { return *got.(*Char) == *want.(*Char) },
marshalTests: []marshalCase{
{NewChar('a'), "a"},
{NewChar('z'), "z"},
{NewChar('\u1234'), "\u1234"},
},
marshalErrs: []SOAPValue{NewChar(0)},
unmarshalErrs: []string{"aa", ""},
},
{
makeValue: func() SOAPValue { return new(DateLocal) },
isEqual: func(got, want SOAPValue) bool {
return got.(*DateLocal).ToTime().Equal(want.(*DateLocal).ToTime())
},
marshalTests: []marshalCase{
{NewDateLocal(time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)), "2013-10-08"},
},
unmarshalTests: []unmarshalCase{
{"20131008", NewDateLocal(time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc))},
},
unmarshalErrs: []string{"", "-1"},
},
{
makeValue: func() SOAPValue { return new(TimeOfDay) },
isEqual: func(got, want SOAPValue) bool {
return got.(*TimeOfDay).Equal(want.(*TimeOfDay))
},
marshalTests: []marshalCase{
{&TimeOfDay{}, "00:00:00"},
// ISO8601 special case
{&TimeOfDay{Hour: 24}, "24:00:00"},
},
unmarshalTests: []unmarshalCase{
{"000000", &TimeOfDay{}},
},
unmarshalErrs: []string{
// Misformatted values:
"foo 01:02:03", "foo\n01:02:03", "01:02:03 foo", "01:02:03\nfoo", "01:02:03Z",
"01:02:03+01", "01:02:03+01:23", "01:02:03+0123", "01:02:03-01", "01:02:03-01:23",
"01:02:03-0123",
// Values out of range:
"24:01:00",
"24:00:01",
"25:00:00",
"00:60:00",
"00:00:60",
// Unexpected timezone component:
"01:02:03Z",
"01:02:03+01",
"01:02:03+01:23",
"01:02:03+0123",
"01:02:03-01",
"01:02:03-01:23",
"01:02:03-0123",
},
},
{
makeValue: func() SOAPValue { return new(TimeOfDayTZ) },
isEqual: func(got, want SOAPValue) bool {
return got.(*TimeOfDayTZ).Equal(want.(*TimeOfDayTZ))
},
marshalTests: []marshalCase{
{&TimeOfDayTZ{}, "00:00:00"},
// ISO8601 special case
{&TimeOfDayTZ{TimeOfDay{24, 0, 0}, false, 0}, "24:00:00"},
{&TimeOfDayTZ{TimeOfDay{1, 2, 3}, true, 0}, "01:02:03Z"},
{&TimeOfDayTZ{TimeOfDay{1, 2, 3}, true, 3600 + 23*60}, "01:02:03+01:23"},
{&TimeOfDayTZ{TimeOfDay{1, 2, 3}, true, -(3600 + 23*60)}, "01:02:03-01:23"},
},
unmarshalTests: []unmarshalCase{
{"000000", &TimeOfDayTZ{}},
{"01Z", &TimeOfDayTZ{TimeOfDay{1, 0, 0}, true, 0}},
{"01+01", &TimeOfDayTZ{TimeOfDay{1, 0, 0}, true, 3600}},
{"01:02:03+01", &TimeOfDayTZ{TimeOfDay{1, 2, 3}, true, 3600}},
{"01:02:03+0123", &TimeOfDayTZ{TimeOfDay{1, 2, 3}, true, 3600 + 23*60}},
{"01:02:03-01", &TimeOfDayTZ{TimeOfDay{1, 2, 3}, true, -3600}},
{"01:02:03-0123", &TimeOfDayTZ{TimeOfDay{1, 2, 3}, true, -(3600 + 23*60)}},
},
unmarshalErrs: []string{
// Misformatted values:
"foo 01:02:03", "foo\n01:02:03", "01:02:03 foo", "01:02:03\nfoo",
// Values out of range:
"24:01:00",
"24:00:01",
"25:00:00",
"00:60:00",
"00:00:60",
},
},
{
makeValue: func() SOAPValue { return new(DateLocal) },
isEqual: func(got, want SOAPValue) bool {
return got.(*DateLocal).ToTime().Equal(want.(*DateLocal).ToTime())
},
marshalTests: []marshalCase{
{NewDateLocal(time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)), "2013-10-08"},
},
unmarshalTests: []unmarshalCase{
{"20131008", NewDateLocal(time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc))},
},
unmarshalErrs: []string{
// Unexpected time component.
"2013-10-08T10:30:50",
// Unexpected timezone component.
"2013-10-08+01",
},
},
{
makeValue: func() SOAPValue { return new(DateTimeLocal) },
isEqual: func(got, want SOAPValue) bool {
return got.(*DateTimeLocal).ToTime().Equal(want.(*DateTimeLocal).ToTime())
},
marshalTests: []marshalCase{
{NewDateTimeLocal(time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)), "2013-10-08T00:00:00"},
{NewDateTimeLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, localLoc)), "2013-10-08T10:30:50"},
},
unmarshalTests: []unmarshalCase{
{"20131008", NewDateTimeLocal(time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc))},
},
unmarshalErrs: []string{
// Unexpected timezone component.
"2013-10-08T10:30:50+01",
},
},
{
makeValue: func() SOAPValue { return new(DateTimeTZLocal) },
isEqual: func(got, want SOAPValue) bool {
return got.(*DateTimeTZLocal).ToTime().Equal(want.(*DateTimeTZLocal).ToTime())
},
marshalTests: []marshalCase{
{NewDateTimeTZLocal(time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc)), "2013-10-08T00:00:00+06:00"},
{NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, localLoc)), "2013-10-08T10:30:50+06:00"},
{NewDateTimeTZLocal(time.Date(2013, 10, 8, 0, 0, 0, 0, time.UTC)), "2013-10-08T00:00:00+00:00"},
{NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.UTC)), "2013-10-08T10:30:50+00:00"},
{NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:23", 3600+23*60))), "2013-10-08T10:30:50+01:23"},
{NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:23", -(3600+23*60)))), "2013-10-08T10:30:50-01:23"},
},
unmarshalTests: []unmarshalCase{
{"20131008", NewDateTimeTZLocal(time.Date(2013, 10, 8, 0, 0, 0, 0, localLoc))},
{"2013-10-08T10:30:50", NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, localLoc))},
{"2013-10-08T10:30:50Z", NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.UTC))},
{"2013-10-08T10:30:50+01", NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:00", 3600)))},
{"2013-10-08T10:30:50+0123", NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("+01:23", 3600+23*60)))},
{"2013-10-08T10:30:50-01", NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:00", -3600)))},
{"2013-10-08T10:30:50-0123", NewDateTimeTZLocal(time.Date(2013, 10, 8, 10, 30, 50, 0, time.FixedZone("-01:23", -(3600+23*60))))},
},
},
{
makeValue: func() SOAPValue { return new(Boolean) },
isEqual: func(got, want SOAPValue) bool {
return *got.(*Boolean) == *want.(*Boolean)
},
marshalTests: []marshalCase{
{NewBoolean(true), "1"},
{NewBoolean(false), "0"},
},
unmarshalTests: []unmarshalCase{
{"true", NewBoolean(true)},
{"false", NewBoolean(false)},
{"yes", NewBoolean(true)},
{"no", NewBoolean(false)},
},
unmarshalErrs: []string{"", "2", "-1"},
},
{
makeValue: func() SOAPValue { return new(BinBase64) },
isEqual: func(got, want SOAPValue) bool {
return bytes.Equal(*got.(*BinBase64), *want.(*BinBase64))
},
marshalTests: []marshalCase{
{&BinBase64{}, ""},
{NewBinBase64([]byte("a")), "YQ=="},
{NewBinBase64([]byte("Longer String.")), "TG9uZ2VyIFN0cmluZy4="},
{NewBinBase64([]byte("Longer Aligned.")), "TG9uZ2VyIEFsaWduZWQu"},
},
},
{
makeValue: func() SOAPValue { return new(BinHex) },
isEqual: func(got, want SOAPValue) bool {
return bytes.Equal(*got.(*BinHex), *want.(*BinHex))
},
marshalTests: []marshalCase{
{&BinHex{}, ""},
{NewBinHex([]byte("a")), "61"},
{NewBinHex([]byte("Longer String.")), "4c6f6e67657220537472696e672e"},
},
unmarshalTests: []unmarshalCase{
{"4C6F6E67657220537472696E672E", NewBinHex([]byte("Longer String."))},
},
},
{
makeValue: func() SOAPValue { return new(URI) },
isEqual: func(got, want SOAPValue) bool {
return got.(*URI).ToURL().String() == want.(*URI).ToURL().String()
},
marshalTests: []marshalCase{
{&URI{Scheme: "http", Host: "example.com", Path: "/path"}, "http://example.com/path"},
},
},
}
for _, tt := range typeTestCases {
tt := tt
// Convert marshalTests into additional round trip equivalent unmarshalTests
for _, mt := range tt.marshalTests {
tt.unmarshalTests = append(tt.unmarshalTests, unmarshalCase{
input: mt.want,
want: mt.input,
})
}
t.Run(fmt.Sprintf("%T", tt.makeValue()), func(t *testing.T) {
for i, mt := range tt.marshalTests {
mt := mt
t.Run(fmt.Sprintf("marshalTest#%d_%v", i, mt.input), func(t *testing.T) {
got, err := mt.input.Marshal()
if err != nil {
t.Errorf("got unexpected error: %v", err)
}
if got != mt.want {
t.Errorf("got %q, want: %q", got, mt.want)
}
})
}
for i, input := range tt.marshalErrs {
input := input
t.Run(fmt.Sprintf("marshalErr#%d_%v", i, input), func(t *testing.T) {
got, err := input.Marshal()
if err == nil {
t.Errorf("got %q, want error", got)
}
})
}
for i, ut := range tt.unmarshalTests {
ut := ut
t.Run(fmt.Sprintf("unmarshalTest#%d_%q", i, ut.input), func(t *testing.T) {
got := tt.makeValue()
if err := got.Unmarshal(ut.input); err != nil {
t.Errorf("got error, want success")
}
if !tt.isEqual(got, ut.want) {
t.Errorf("got %v, want %v", got, ut.want)
}
})
}
for i, input := range tt.unmarshalErrs {
input := input
t.Run(fmt.Sprintf("unmarshalErrs#%d_%q", i, input), func(t *testing.T) {
got := tt.makeValue()
if err := got.Unmarshal(input); err == nil {
t.Errorf("got %v, want error", got)
}
})
}
})
}
}