From 17abe5294aa2a46563efedb1b836aa91aadec432 Mon Sep 17 00:00:00 2001 From: John Beisley Date: Tue, 8 Oct 2013 23:00:49 +0100 Subject: [PATCH] Initial work on marshalling/unmarshalling SOAP types. So far added: * fixed.14.4 * char * date --- soap/types.go | 60 +++++++++++++++++++++++++ soap/types_test.go | 107 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 soap/types.go create mode 100644 soap/types_test.go diff --git a/soap/types.go b/soap/types.go new file mode 100644 index 0000000..46889f3 --- /dev/null +++ b/soap/types.go @@ -0,0 +1,60 @@ +package soap + +import ( + "errors" + "fmt" + "strconv" + "time" + "unicode/utf8" +) + +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 +} + +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 +} + +func MarshalChar(v rune) (string, error) { + if v == 0 { + return "", errors.New("soap char: rune 0 is not allowed") + } + return string(v), nil +} + +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 MarshalDate(v time.Time) (string, error) { + return v.Format("2006-01-02"), nil +} + +var dateFmts = []string{"2006-01-02", "20060102"} + +func UnmarshalDate(s string) (time.Time, error) { + for _, f := range dateFmts { + if t, err := time.Parse(f, s); err == nil { + return t, nil + } + } + return time.Time{}, fmt.Errorf("soap date: value %q is not in a recognized date format", s) +} diff --git a/soap/types_test.go b/soap/types_test.go new file mode 100644 index 0000000..db30cba --- /dev/null +++ b/soap/types_test.go @@ -0,0 +1,107 @@ +package soap + +import ( + "math" + "testing" + "time" +) + +type convTest interface { + Marshal() (string, error) + Unmarshal(string) (interface{}, error) + Equal(result interface{}) bool +} + +type testCase struct { + value convTest + str string + wantMarshalErr bool + wantUnmarshalErr bool + noMarshal bool + noUnMarshal bool +} + +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 Test(t *testing.T) { + tests := []testCase{ + // Fixed14_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, time.UTC)}}, + {str: "20131008", value: DateTest{time.Date(2013, 10, 8, 0, 0, 0, 0, time.UTC)}, noMarshal: true}, + {str: "2013-10-08T10:30:50", value: DateTest{}, wantUnmarshalErr: true, noMarshal: true}, + {str: "", value: DateTest{}, wantMarshalErr: true, wantUnmarshalErr: true, noMarshal: true}, + {str: "-1", value: DateTest{}, wantUnmarshalErr: true, noMarshal: true}, + } + + for _, test := range tests { + if test.noMarshal { + } else if resultStr, err := test.value.Marshal(); err != nil && !test.wantMarshalErr { + t.Errorf("For %s, want %q, got error: %v", test.value, test.str, err) + } else if err == nil && test.wantMarshalErr { + t.Errorf("For %s, want error, got %q", test.value, resultStr) + } else if err == nil && resultStr != test.str { + t.Errorf("For %s, want %q, got %q", test.value, test.str, resultStr) + } + + if test.noUnMarshal { + } else if resultValue, err := test.value.Unmarshal(test.str); err != nil && !test.wantUnmarshalErr { + t.Errorf("For %q, want %v, got error: %v", test.str, test.value, err) + } else if err == nil && test.wantUnmarshalErr { + t.Errorf("For %q, want error, got %v", test.str, resultValue) + } else if err == nil && !test.value.Equal(resultValue) { + t.Errorf("For %q, want %v, got %v", test.str, test.value, resultValue) + } + } +}