From 3c1d0f28dd8acc37c8b2a4ec254e05be2c120fcc Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Mon, 28 Nov 2022 15:48:47 -0500 Subject: [PATCH] Adding text marshaling and unmarshaling Note, this removes the JSON specific handling as the general handling covers the JSON case. If this is used with JSON and > or < are used than a custom encoder is required. In Go json.Marshal will escape those. For example... cs, _ := NewConstraint(tc.constraint) buf := new(bytes.Buffer) enc := json.NewEncoder(buf) enc.SetEscapeHTML(false) err = enc.Encode(cs) jsonVal = buf.String() Signed-off-by: Matt Farina --- constraints.go | 27 ++++++------------ constraints_test.go | 68 ++++++++++++++++++++++++++++++++++----------- 2 files changed, 60 insertions(+), 35 deletions(-) diff --git a/constraints.go b/constraints.go index 492364b..203072e 100644 --- a/constraints.go +++ b/constraints.go @@ -2,7 +2,6 @@ package semver import ( "bytes" - "encoding/json" "errors" "fmt" "regexp" @@ -135,31 +134,21 @@ func (cs Constraints) String() string { return strings.Join(buf, " || ") } -// UnmarshalJSON implements JSON.Unmarshaler interface. -func (cs *Constraints) UnmarshalJSON(b []byte) error { - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - temp, err := NewConstraint(s) +// UnmarshalText implements the encoding.TextUnmarshaler interface. +func (cs *Constraints) UnmarshalText(text []byte) error { + temp, err := NewConstraint(string(text)) if err != nil { return err } + *cs = *temp + return nil } -// MarshalJSON implements JSON.Marshaler interface. -func (cs Constraints) MarshalJSON() ([]byte, error) { - // we need our own encoder so we don't escape '<' and '>' which json.Marshal does - buf := new(bytes.Buffer) - enc := json.NewEncoder(buf) - enc.SetEscapeHTML(false) - - if err := enc.Encode(cs.String()); err != nil { - return nil, err - } - return bytes.TrimRight(buf.Bytes(), "\n"), nil +// MarshalText implements the encoding.TextMarshaler interface. +func (cs Constraints) MarshalText() ([]byte, error) { + return []byte(cs.String()), nil } var constraintOps map[string]cfunc diff --git a/constraints_test.go b/constraints_test.go index 803877f..5efa92d 100644 --- a/constraints_test.go +++ b/constraints_test.go @@ -680,55 +680,91 @@ func TestConstraintString(t *testing.T) { } } -func TestJsonMarshalConstraints(t *testing.T) { +func TestTextMarshalConstraints(t *testing.T) { tests := []struct { - sCs string - want string + constraint string + want string }{ - {"1.1.1", "1.1.1"}, - {">=1.1.1", ">=1.1.1"}, - {"<=1.1.1", "<=1.1.1"}, + {"1.2.3", "1.2.3"}, + {">=1.2.3", ">=1.2.3"}, + {"<=1.2.3", "<=1.2.3"}, + {"1 <=1.2.3", "1 <=1.2.3"}, + {"1, <=1.2.3", "1 <=1.2.3"}, + {">1, <=1.2.3", ">1 <=1.2.3"}, + {"> 1 , <=1.2.3", ">1 <=1.2.3"}, } for _, tc := range tests { - cs, err := NewConstraint(tc.sCs) + cs, err := NewConstraint(tc.constraint) if err != nil { t.Errorf("Error creating constraints: %s", err) } + + out, err2 := cs.MarshalText() + if err2 != nil { + t.Errorf("Error constraint version: %s", err2) + } + + got := string(out) + if got != tc.want { + t.Errorf("Error marshaling constraint, unexpected marshaled content: got=%q want=%q", got, tc.want) + } + + // Test that this works for JSON as well as text. When JSON marshaling + // functions are missing it falls through to TextMarshal. + // NOTE: To not escape the < and > (which json.Marshal does) you need + // a custom encoder where html escaping is disabled. This must be done + // in the top level encoder being used to marshal the constraints. buf := new(bytes.Buffer) enc := json.NewEncoder(buf) enc.SetEscapeHTML(false) err = enc.Encode(cs) if err != nil { - t.Errorf("Error unmarshaling version: %s", err) + t.Errorf("Error unmarshaling constraint: %s", err) } - got := buf.String() + got = buf.String() + // The encoder used here adds a newline so we add that to what we want + // so they align. The newline is an artifact of the testing. want := fmt.Sprintf("%q\n", tc.want) if got != want { - t.Errorf("Error marshaling unexpected marshaled content: got=%q want=%q", got, want) + t.Errorf("Error marshaling constraint, unexpected marshaled content: got=%q want=%q", got, want) } } } -func TestJsonUnmarshalConstraints(t *testing.T) { +func TestTextUnmarshalConstraints(t *testing.T) { tests := []struct { - sCs string - want string + constraint string + want string }{ - {"1.1.1", "1.1.1"}, + {"1.2.3", "1.2.3"}, {">=1.2.3", ">=1.2.3"}, {"<=1.2.3", "<=1.2.3"}, + {">1 <=1.2.3", ">1 <=1.2.3"}, + {"> 1 <=1.2.3", ">1 <=1.2.3"}, + {">1, <=1.2.3", ">1 <=1.2.3"}, } for _, tc := range tests { cs := Constraints{} - err := json.Unmarshal([]byte(fmt.Sprintf("%q", tc.sCs)), &cs) + err := cs.UnmarshalText([]byte(tc.constraint)) if err != nil { t.Errorf("Error unmarshaling constraints: %s", err) } got := cs.String() if got != tc.want { - t.Errorf("Error unmarshaling unexpected object content: got=%q want=%q", got, tc.want) + t.Errorf("Error unmarshaling constraint, unexpected object content: got=%q want=%q", got, tc.want) + } + + // Test that this works for JSON as well as text. When JSON unmarshaling + // functions are missing it falls through to TextUnmarshal. + err = json.Unmarshal([]byte(fmt.Sprintf("%q", tc.constraint)), &cs) + if err != nil { + t.Errorf("Error unmarshaling constraints: %s", err) + } + got = cs.String() + if got != tc.want { + t.Errorf("Error unmarshaling constraint, unexpected object content: got=%q want=%q", got, tc.want) } } }