Skip to content

Commit

Permalink
refactor: register plugins via init function
Browse files Browse the repository at this point in the history
  • Loading branch information
sundowndev committed Jul 23, 2022
1 parent 48ee683 commit 4888dc2
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 101 deletions.
9 changes: 4 additions & 5 deletions cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,16 @@ func runScan() {
exitWithError(err)
}

remoteLibrary := remote.NewLibrary()
remote.InitScanners(remoteLibrary)

for _, pluginPath := range pluginPaths {
s, err := remote.OpenPlugin(pluginPath)
err := remote.OpenPlugin(pluginPath)
if err != nil {
exitWithError(err)
}
remoteLibrary.AddScanner(s)
}

remoteLibrary := remote.NewLibrary()
remote.InitScanners(remoteLibrary)

result, errs := remoteLibrary.Scan(num)

err = output.GetOutput(output.Console, os.Stdout).Write(result, errs)
Expand Down
20 changes: 8 additions & 12 deletions examples/plugin/customscanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,16 @@ type customScannerResponse struct {
Hidden string `json:"-" console:"-"`
}

// NewScanner creates a new instance of this scanner.
// The name of the function MUST be NewScanner.
func NewScanner() remote.Scanner {
return &customScanner{}
}

// Name returns the unique identifier this
// scanner should be associated to.
// Please keep in mind this value could be used for
// automation and so must remain simple.
// Name returns the unique name this scanner.
func (s *customScanner) Name() string {
return "customscanner"
}

// ShouldRun returns a boolean indicating whether
// this scanner should be used or not.
// This can be useful to check for authentication and
// avoid running the scanner when it just can't work.
// This can be useful to check for authentication or
// country code support for example, and avoid running
// the scanner when it just can't work.
func (s *customScanner) ShouldRun(n number.Number) bool {
return true
}
Expand All @@ -45,3 +37,7 @@ func (s *customScanner) Scan(n number.Number) (interface{}, error) {
}
return data, nil
}

func init() {
remote.RegisterPlugin(&customScanner{})
}
51 changes: 51 additions & 0 deletions examples/plugin/customscanner_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package main

import (
"github.com/stretchr/testify/assert"
"github.com/sundowndev/phoneinfoga/v2/lib/number"
"github.com/sundowndev/phoneinfoga/v2/lib/remote"
"testing"
)

func TestCustomScanner(t *testing.T) {
testcases := []struct {
name string
number *number.Number
expected customScannerResponse
wantError string
}{
{
name: "test successful scan",
number: func() *number.Number {
n, _ := number.NewNumber("15556661212")
return n
}(),
expected: customScannerResponse{
Valid: true,
Info: "This number is known for scams!",
Hidden: "This will not appear in the output",
},
},
}

for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
scanner := &customScanner{}

remoteLib := remote.NewLibrary()
remoteLib.AddScanner(scanner)

if !scanner.ShouldRun(*tt.number) {
t.Fatal("ShouldRun() should be truthy")
}

got, err := scanner.Scan(*tt.number)
if tt.wantError != "" {
assert.EqualError(t, err, tt.wantError)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tt.expected, got)
})
}
}
2 changes: 2 additions & 0 deletions lib/remote/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ func InitScanners(remote *Library) {
remote.AddScanner(NewNumverifyScanner(numverifySupplier))
remote.AddScanner(NewGoogleSearchScanner())
remote.AddScanner(NewOVHScanner(ovhSupplier))

remote.LoadPlugins()
}
23 changes: 19 additions & 4 deletions lib/remote/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import (
"sync"
)

var mu sync.Locker = &sync.RWMutex{}
var plugins []Scanner

type Library struct {
m *sync.RWMutex
scanners []Scanner
Expand All @@ -21,17 +24,23 @@ func NewLibrary() *Library {
}
}

func (r *Library) LoadPlugins() {
for _, s := range plugins {
r.AddScanner(s)
}
}

func (r *Library) AddScanner(s Scanner) {
r.scanners = append(r.scanners, s)
}

func (r *Library) AddResult(k string, v interface{}) {
func (r *Library) addResult(k string, v interface{}) {
r.m.Lock()
defer r.m.Unlock()
r.results[k] = v
}

func (r *Library) AddError(k string, err error) {
func (r *Library) addError(k string, err error) {
r.m.Lock()
defer r.m.Unlock()
r.errors[k] = err
Expand All @@ -51,11 +60,11 @@ func (r *Library) Scan(n *number.Number) (map[string]interface{}, map[string]err
defer wg.Done()
data, err := s.Scan(*n)
if err != nil {
r.AddError(s.Name(), err)
r.addError(s.Name(), err)
return
}
if data != nil {
r.AddResult(s.Name(), data)
r.addResult(s.Name(), data)
}
}(s)
}
Expand All @@ -64,3 +73,9 @@ func (r *Library) Scan(n *number.Number) (map[string]interface{}, map[string]err

return r.results, r.errors
}

func RegisterPlugin(s Scanner) {
mu.Lock()
defer mu.Unlock()
plugins = append(plugins, s)
}
23 changes: 5 additions & 18 deletions lib/remote/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,15 @@ type Scanner interface {
ShouldRun(number.Number) bool
}

func parseEntryFunc(p Plugin) (Scanner, error) {
symbol, err := p.Lookup("NewScanner")
if err != nil {
return nil, fmt.Errorf("exported function NewScanner not found")
}

fn, ok := symbol.(func() Scanner)
if !ok {
return nil, fmt.Errorf("exported function NewScanner does not follow the remote.Scanner interface")
}
return fn(), nil
}

func OpenPlugin(path string) (Scanner, error) {
func OpenPlugin(path string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return nil, fmt.Errorf("given path %s does not exist", path)
return fmt.Errorf("given path %s does not exist", path)
}

p, err := plugin.Open(path)
_, err := plugin.Open(path)
if err != nil {
return nil, fmt.Errorf("given plugin %s is not valid", path)
return fmt.Errorf("given plugin %s is not valid", path)
}

return parseEntryFunc(p)
return nil
}
63 changes: 1 addition & 62 deletions lib/remote/scanner_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package remote

import (
"errors"
"github.com/stretchr/testify/assert"
"github.com/sundowndev/phoneinfoga/v2/mocks"
"testing"
)

Expand All @@ -27,67 +25,8 @@ func Test_ValidatePlugin_Errors(t *testing.T) {

for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
_, err := OpenPlugin(tt.path)
err := OpenPlugin(tt.path)
assert.EqualError(t, err, tt.wantErr)
})
}
}

func Test_parseEntryFunc(t *testing.T) {
testcases := []struct {
name string
mocks func(*mocks.Plugin)
wantErr string
expected Scanner
}{
{
name: "test with invalid plugin",
mocks: func(p *mocks.Plugin) {
p.On("Lookup", "NewScanner").Return(nil, errors.New("dummy error"))
},
wantErr: "exported function NewScanner not found",
},
{
name: "test with invalid exported function",
mocks: func(p *mocks.Plugin) {
fn := func() interface{} {
return &mocks.Scanner{}
}

p.On("Lookup", "NewScanner").Return(fn, nil)
},
wantErr: "exported function NewScanner does not follow the remote.Scanner interface",
},
{
name: "test with valid plugin",
mocks: func(p *mocks.Plugin) {
fn := func() Scanner {
return &mocks.Scanner{}
}

p.On("Lookup", "NewScanner").Return(fn, nil)
},
wantErr: "",
expected: &mocks.Scanner{},
},
}

for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
fakePlugin := &mocks.Plugin{}

tt.mocks(fakePlugin)

s, err := parseEntryFunc(fakePlugin)
if err != nil {
if tt.wantErr == "" {
t.Fatal(err)
}
return
}
assert.Equal(t, tt.expected, s)

fakePlugin.AssertExpectations(t)
})
}
}

0 comments on commit 4888dc2

Please sign in to comment.