-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
441249a
commit 8c4ee93
Showing
4 changed files
with
279 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package starr_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"golift.io/starr" | ||
) | ||
|
||
//nolint:testifylint // we want to test each one and not fail on an error. | ||
func TestNone(t *testing.T) { | ||
t.Parallel() | ||
assert.ErrorIs(t, starr.None(starr.ErrNilClient), starr.ErrNilClient) | ||
assert.ErrorIs(t, starr.None("string", starr.ErrNilClient), starr.ErrNilClient) | ||
assert.ErrorIs(t, starr.None(uint(1), starr.ErrNilClient), starr.ErrNilClient) | ||
assert.ErrorIs(t, starr.None("string", uint(1), starr.ErrNilClient), starr.ErrNilClient) | ||
assert.ErrorIs(t, starr.None(1.0, "string", starr.ErrNilClient), starr.ErrNilClient) | ||
assert.NoError(t, starr.None(1.0, "string")) | ||
assert.NoError(t, starr.None("string")) | ||
assert.NoError(t, starr.None(1.0)) | ||
assert.NoError(t, starr.None()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// Package orbit provides functions to modify data structures among the various starr libraries. | ||
// These functions cannot live in the starr library without causing an import cycle. | ||
// These are wrappers around the starr library and other sub modules. | ||
package orbit | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"reflect" | ||
|
||
"golift.io/starr/lidarr" | ||
"golift.io/starr/prowlarr" | ||
"golift.io/starr/radarr" | ||
"golift.io/starr/readarr" | ||
"golift.io/starr/sonarr" | ||
) | ||
|
||
var ErrNotPtr = errors.New("must provide a pointer to a non-nil value") | ||
|
||
// Copy is an easy way to copy one data structure to another. | ||
func Copy(src, dst any) error { | ||
if src == nil || reflect.TypeOf(src).Kind() != reflect.Ptr { | ||
return fmt.Errorf("copy source: %w", ErrNotPtr) | ||
} else if dst == nil || reflect.TypeOf(dst).Kind() != reflect.Ptr { | ||
return fmt.Errorf("copy destination: %w", ErrNotPtr) | ||
} | ||
|
||
var buf bytes.Buffer | ||
if err := json.NewEncoder(&buf).Encode(src); err != nil { | ||
return fmt.Errorf("encoding: %w", err) | ||
} | ||
|
||
if err := json.NewDecoder(&buf).Decode(dst); err != nil { | ||
return fmt.Errorf("decoding: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// IndexerInput represents all possible Indexer inputs. | ||
type IndexerInput interface { | ||
lidarr.IndexerInput | prowlarr.IndexerInput | radarr.IndexerInput | | ||
readarr.IndexerInput | sonarr.IndexerInput | ||
} | ||
|
||
// IndexerOutput represents all possible Indexer outputs. | ||
type IndexerOutput interface { | ||
lidarr.IndexerOutput | prowlarr.IndexerOutput | radarr.IndexerOutput | | ||
readarr.IndexerOutput | sonarr.IndexerOutput | ||
} | ||
|
||
// CopyIndexers copies a slice of indexers from one type to another, so you may copy them among instances. | ||
// The destination must be a pointer to a slice, so it can be updated in place. | ||
// The destination slice may be empty but the pointer to it must not be nil. | ||
func CopyIndexers[S IndexerInput | IndexerOutput, D IndexerInput](src []*S, dst *[]*D, keepTags bool) ([]*D, error) { | ||
if dst == nil { | ||
return nil, ErrNotPtr | ||
} | ||
|
||
var err error | ||
|
||
for idx, indexer := range src { | ||
if len(*dst)-1 >= idx { // The destination slice location exists, so update it in place. | ||
_, err = CopyIndexer(indexer, (*dst)[idx], keepTags) | ||
} else { // The destination slice is shorter than the source, so append to it. | ||
newIndexer := new(D) | ||
newIndexer, err = CopyIndexer(indexer, newIndexer, keepTags) | ||
*dst = append(*dst, newIndexer) // This happens before checking the error. | ||
} | ||
|
||
if err != nil { | ||
break | ||
} | ||
} | ||
|
||
return *dst, err | ||
} | ||
|
||
// CopyIndexer copies an indexer from one type to another, so you may copy them among instances. | ||
func CopyIndexer[S IndexerInput | IndexerOutput, D IndexerInput](src *S, dst *D, keepTags bool) (*D, error) { | ||
if err := Copy(src, dst); err != nil { | ||
return dst, err | ||
} | ||
|
||
element := reflect.ValueOf(dst).Elem() | ||
zeroField(element.FieldByName("ID"), true) | ||
zeroField(element.FieldByName("Tags"), !keepTags) | ||
|
||
return dst, nil | ||
} | ||
|
||
func zeroField(field reflect.Value, really bool) { | ||
if really && field.CanSet() { | ||
field.SetZero() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package orbit_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"golift.io/starr" | ||
"golift.io/starr/orbit" | ||
"golift.io/starr/prowlarr" | ||
"golift.io/starr/sonarr" | ||
) | ||
|
||
func copyData(t *testing.T) (*prowlarr.IndexerOutput, *sonarr.IndexerInput) { | ||
t.Helper() | ||
|
||
return &prowlarr.IndexerOutput{ | ||
ID: 2, | ||
Priority: 3, | ||
Name: "yes", | ||
Protocol: "usenet", | ||
Implementation: "core", | ||
ConfigContract: "hancock", | ||
Tags: []int{1, 2, 5}, | ||
Fields: []*starr.FieldOutput{ | ||
{Name: "One", Value: "one"}, | ||
{Name: "Two", Value: 2.0}, | ||
{Name: "Three", Value: uint(3)}, | ||
{Name: "Five", Value: 5}, | ||
}, | ||
}, | ||
&sonarr.IndexerInput{ | ||
// These are not part of the used input, so set them before copying. | ||
EnableAutomaticSearch: true, | ||
EnableInteractiveSearch: true, | ||
EnableRss: true, | ||
DownloadClientID: 15, | ||
} | ||
} | ||
|
||
func TestCopyIndexers(t *testing.T) { | ||
t.Parallel() | ||
src1, dst1 := copyData(t) | ||
src2, dst2 := copyData(t) | ||
src3, dst3 := copyData(t) | ||
src4, _ := copyData(t) | ||
src5, _ := copyData(t) | ||
// We test for these. | ||
src1.Priority = 1 | ||
src2.Priority = 2 | ||
src3.Priority = 3 | ||
src4.Priority = 4 | ||
src5.Priority = 5 | ||
// Make two lists. | ||
srcs := append([]*prowlarr.IndexerOutput{}, src1, src2, src3, src4, src5) | ||
dsts := append([]*sonarr.IndexerInput{}, dst1, dst2, dst3) // Short by 2. | ||
// Copy the lists. | ||
dsts2, err := orbit.CopyIndexers(srcs, &dsts, true) | ||
require.NoError(t, err) | ||
// Make sure both outputs have a length matching the input. | ||
assert.Len(t, dsts, len(srcs)) | ||
assert.Len(t, dsts2, len(srcs)) | ||
// Test that values got copied. | ||
for idx, src := range srcs { | ||
assert.Zero(t, dsts[idx].ID) | ||
assert.Equal(t, src.Priority, dsts[idx].Priority) | ||
assert.Equal(t, src.Tags, dsts[idx].Tags) | ||
} | ||
} | ||
|
||
// TestCopyIndexersNilDest test a nil destination pointer and slice. | ||
func TestCopyIndexersNilDest(t *testing.T) { | ||
t.Parallel() | ||
src1, _ := copyData(t) | ||
src2, _ := copyData(t) | ||
// Make two lists. | ||
srcs := append([]*prowlarr.IndexerOutput{}, src1, src2) | ||
dsts := new([]*sonarr.IndexerInput) // Super empty. | ||
*dsts = nil // Nil the slice. | ||
// Copy the lists. | ||
dsts2, err := orbit.CopyIndexers(srcs, dsts, false) | ||
require.NoError(t, err) | ||
// Make sure both outputs have a length matching the input. | ||
assert.Len(t, *dsts, len(srcs)) | ||
assert.Len(t, dsts2, len(srcs)) | ||
// Test that tags got removed. | ||
for idx, src := range srcs { | ||
assert.Zero(t, (*dsts)[idx].ID) | ||
assert.Equal(t, src.Priority, (*dsts)[idx].Priority) | ||
assert.NotEqual(t, src.Tags, (*dsts)[idx].Tags) | ||
} | ||
|
||
// Make an error. | ||
dsts = nil // This is a no-no. | ||
require.ErrorIs(t, starr.None(orbit.CopyIndexers(srcs, dsts, false)), orbit.ErrNotPtr) | ||
} | ||
|
||
func TestCopyIndexer(t *testing.T) { | ||
t.Parallel() | ||
|
||
src, dst := copyData(t) | ||
// Verify everything copies over. | ||
require.NoError(t, starr.None(orbit.CopyIndexer(src, dst, true))) | ||
assert.Equal(t, src.Fields[0].Value, dst.Fields[0].Value) | ||
assert.Equal(t, src.Fields[1].Value, dst.Fields[1].Value) | ||
assert.EqualValues(t, src.Fields[2].Value, dst.Fields[2].Value) | ||
assert.EqualValues(t, src.Fields[3].Value, dst.Fields[3].Value) | ||
assert.Equal(t, src.Fields[0].Name, dst.Fields[0].Name) | ||
assert.Equal(t, src.Fields[1].Name, dst.Fields[1].Name) | ||
assert.Equal(t, src.Fields[2].Name, dst.Fields[2].Name) | ||
assert.Equal(t, src.Fields[3].Name, dst.Fields[3].Name) | ||
assert.Zero(t, dst.ID) | ||
assert.Equal(t, src.Priority, dst.Priority) | ||
assert.Equal(t, src.Name, dst.Name) | ||
assert.Equal(t, src.Protocol, dst.Protocol) | ||
assert.Equal(t, src.Implementation, dst.Implementation) | ||
assert.Equal(t, src.ConfigContract, dst.ConfigContract) | ||
assert.Equal(t, src.Tags[0], dst.Tags[0]) | ||
assert.Equal(t, src.Tags[1], dst.Tags[1]) | ||
assert.Equal(t, src.Tags[2], dst.Tags[2]) | ||
// Check passed in values. | ||
assert.Equal(t, int64(15), dst.DownloadClientID) | ||
assert.True(t, dst.EnableAutomaticSearch) | ||
assert.True(t, dst.EnableInteractiveSearch) | ||
assert.True(t, dst.EnableRss) | ||
// Make sure tags get depleted. | ||
starr.Must(orbit.CopyIndexer(src, dst, false)) | ||
assert.Zero(t, dst.Tags) | ||
} | ||
|
||
func TestCopy(t *testing.T) { | ||
t.Parallel() | ||
|
||
broken := struct{}{} | ||
good := &prowlarr.IndexerOutput{} | ||
|
||
require.ErrorIs(t, orbit.Copy(broken, good), orbit.ErrNotPtr) | ||
require.ErrorIs(t, orbit.Copy(good, broken), orbit.ErrNotPtr) | ||
} |