Skip to content

Commit

Permalink
api: ensure all enumeration types implement UnmarshalText
Browse files Browse the repository at this point in the history
We want these types to be able to decode incoming JSON, but there were a
few enumeration types that didn't implement UnmarshalText (implicitly or
otherwise).

Signed-off-by: Jacob Howard <[email protected]>
  • Loading branch information
xenoscopic committed Oct 18, 2024
1 parent 15e9d73 commit fe518d1
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 2 deletions.
23 changes: 23 additions & 0 deletions pkg/forwarding/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,29 @@ func (s Status) MarshalText() ([]byte, error) {
return []byte(result), nil
}

// UnmarshalText implements encoding.TextUnmarshaler.UnmarshalText.
func (s *Status) UnmarshalText(textBytes []byte) error {
// Convert the bytes to a string.
text := string(textBytes)

// Convert to a forwarding status.
switch text {
case "disconnected":
*s = Status_Disconnected
case "connecting-source":
*s = Status_ConnectingSource
case "connecting-destination":
*s = Status_ConnectingDestination
case "forwarding":
*s = Status_ForwardingConnections
default:
return fmt.Errorf("unknown forwarding status: %s", text)
}

// Success.
return nil
}

// ensureValid ensures that EndpointState's invariants are respected.
func (s *EndpointState) ensureValid() error {
// A nil endpoint state is not valid.
Expand Down
40 changes: 39 additions & 1 deletion pkg/forwarding/state_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,41 @@
package forwarding

// TODO: Implement.
import (
"testing"
)

// TestStatusUnmarshal tests that unmarshaling from a string specification
// succeeeds for Status.
func TestStatusUnmarshal(t *testing.T) {
// Set up test cases.
testCases := []struct {
text string
expected Status
expectFailure bool
}{
{"", Status_Disconnected, true},
{"asdf", Status_Disconnected, true},
{"disconnected", Status_Disconnected, false},
{"connecting-source", Status_ConnectingSource, false},
{"connecting-destination", Status_ConnectingDestination, false},
{"forwarding", Status_ForwardingConnections, false},
}

// Process test cases.
for _, testCase := range testCases {
var status Status
if err := status.UnmarshalText([]byte(testCase.text)); err != nil {
if !testCase.expectFailure {
t.Errorf("unable to unmarshal text (%s): %s", testCase.text, err)
}
} else if testCase.expectFailure {
t.Error("unmarshaling succeeded unexpectedly for text:", testCase.text)
} else if status != testCase.expected {
t.Errorf(
"unmarshaled status (%s) does not match expected (%s)",
status,
testCase.expected,
)
}
}
}
28 changes: 28 additions & 0 deletions pkg/synchronization/core/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package core
import (
"bytes"
"errors"
"fmt"
"strings"

"github.com/mutagen-io/mutagen/pkg/synchronization/core/fastpath"
Expand Down Expand Up @@ -38,6 +39,33 @@ func (k EntryKind) MarshalText() ([]byte, error) {
return []byte(result), nil
}

// UnmarshalText implements encoding.TextUnmarshaler.UnmarshalText.
func (k *EntryKind) UnmarshalText(textBytes []byte) error {
// Convert the bytes to a string.
text := string(textBytes)

// Convert to a forwarding status.
switch text {
case "directory":
*k = EntryKind_Directory
case "file":
*k = EntryKind_File
case "symlink":
*k = EntryKind_SymbolicLink
case "untracked":
*k = EntryKind_Untracked
case "problematic":
*k = EntryKind_Problematic
case "phantom-directory":
*k = EntryKind_PhantomDirectory
default:
return fmt.Errorf("unknown entry kind: %s", text)
}

// Success.
return nil
}

// EnsureValid ensures that Entry's invariants are respected. If synchronizable
// is true, then unsynchronizable content will be considered invalid.
func (e *Entry) EnsureValid(synchronizable bool) error {
Expand Down
38 changes: 38 additions & 0 deletions pkg/synchronization/core/entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,44 @@ func TestEntryKindSynchronizable(t *testing.T) {
}
}

// TestEntryKindUnmarshal tests that unmarshaling from a string specification
// succeeeds for EntryKind.
func TestEntryKindUnmarshal(t *testing.T) {
// Set up test cases.
testCases := []struct {
text string
expected EntryKind
expectFailure bool
}{
{"", EntryKind_Directory, true},
{"asdf", EntryKind_Directory, true},
{"directory", EntryKind_Directory, false},
{"file", EntryKind_File, false},
{"symlink", EntryKind_SymbolicLink, false},
{"untracked", EntryKind_Untracked, false},
{"problematic", EntryKind_Problematic, false},
{"phantom-directory", EntryKind_PhantomDirectory, false},
}

// Process test cases.
for _, testCase := range testCases {
var kind EntryKind
if err := kind.UnmarshalText([]byte(testCase.text)); err != nil {
if !testCase.expectFailure {
t.Errorf("unable to unmarshal text (%s): %s", testCase.text, err)
}
} else if testCase.expectFailure {
t.Error("unmarshaling succeeded unexpectedly for text:", testCase.text)
} else if kind != testCase.expected {
t.Errorf(
"unmarshaled entry kind (%s) does not match expected (%s)",
kind,
testCase.expected,
)
}
}
}

func init() {
// Enable wildcard problem matching for tests.
entryEqualWildcardProblemMatch = true
Expand Down
43 changes: 43 additions & 0 deletions pkg/synchronization/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,49 @@ func (s Status) MarshalText() ([]byte, error) {
return []byte(result), nil
}

// UnmarshalText implements encoding.TextUnmarshaler.UnmarshalText.
func (s *Status) UnmarshalText(textBytes []byte) error {
// Convert the bytes to a string.
text := string(textBytes)

// Convert to a synchronization status.
switch text {
case "disconnected":
*s = Status_Disconnected
case "halted-on-root-emptied":
*s = Status_HaltedOnRootEmptied
case "halted-on-root-deletion":
*s = Status_HaltedOnRootDeletion
case "halted-on-root-type-change":
*s = Status_HaltedOnRootTypeChange
case "connecting-alpha":
*s = Status_ConnectingAlpha
case "connecting-beta":
*s = Status_ConnectingBeta
case "watching":
*s = Status_Watching
case "scanning":
*s = Status_Scanning
case "waiting-for-rescan":
*s = Status_WaitingForRescan
case "reconciling":
*s = Status_Reconciling
case "staging-alpha":
*s = Status_StagingAlpha
case "staging-beta":
*s = Status_StagingBeta
case "transitioning":
*s = Status_Transitioning
case "saving":
*s = Status_Saving
default:
return fmt.Errorf("unknown synchronization status: %s", text)
}

// Success.
return nil
}

// ensureValid ensures that EndpointState's invariants are respected.
func (s *EndpointState) ensureValid() error {
// A nil endpoint state is not valid.
Expand Down
50 changes: 49 additions & 1 deletion pkg/synchronization/state_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,51 @@
package synchronization

// TODO: Implement.
import (
"testing"
)

// TestStatusUnmarshal tests that unmarshaling from a string specification
// succeeeds for Status.
func TestStatusUnmarshal(t *testing.T) {
// Set up test cases.
testCases := []struct {
text string
expected Status
expectFailure bool
}{
{"", Status_Disconnected, true},
{"asdf", Status_Disconnected, true},
{"disconnected", Status_Disconnected, false},
{"halted-on-root-emptied", Status_HaltedOnRootEmptied, false},
{"halted-on-root-deletion", Status_HaltedOnRootDeletion, false},
{"halted-on-root-type-change", Status_HaltedOnRootTypeChange, false},
{"connecting-alpha", Status_ConnectingAlpha, false},
{"connecting-beta", Status_ConnectingBeta, false},
{"watching", Status_Watching, false},
{"scanning", Status_Scanning, false},
{"waiting-for-rescan", Status_WaitingForRescan, false},
{"reconciling", Status_Reconciling, false},
{"staging-alpha", Status_StagingAlpha, false},
{"staging-beta", Status_StagingBeta, false},
{"transitioning", Status_Transitioning, false},
{"saving", Status_Saving, false},
}

// Process test cases.
for _, testCase := range testCases {
var status Status
if err := status.UnmarshalText([]byte(testCase.text)); err != nil {
if !testCase.expectFailure {
t.Errorf("unable to unmarshal text (%s): %s", testCase.text, err)
}
} else if testCase.expectFailure {
t.Error("unmarshaling succeeded unexpectedly for text:", testCase.text)
} else if status != testCase.expected {
t.Errorf(
"unmarshaled status (%s) does not match expected (%s)",
status,
testCase.expected,
)
}
}
}

0 comments on commit fe518d1

Please sign in to comment.