Skip to content

Commit

Permalink
feat: dummypay payments generation
Browse files Browse the repository at this point in the history
Signed-off-by: Lawrence Zawila <113581282+darkmatterpool@users.noreply.github.com>
  • Loading branch information
darkmatterpool committed Sep 25, 2022
1 parent 8b71be7 commit 6a0f52f
Show file tree
Hide file tree
Showing 11 changed files with 375 additions and 52 deletions.
41 changes: 40 additions & 1 deletion pkg/bridge/connectors/dummypay/config.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,52 @@
package dummypay

import (
"fmt"
"syscall"
"time"
)

// Config is the configuration for the dummy payment connector.
type Config struct {
Directory string
// Directory is the directory where the files are stored.
Directory string `json:"directory" yaml:"directory" bson:"directory"`

// FilePollingPeriod is the period between file polling.
FilePollingPeriod time.Duration `json:"filePollingPeriod" yaml:"filePollingPeriod" bson:"filePollingPeriod"`

// FileGenerationPeriod is the period between file generation.
FileGenerationPeriod time.Duration `json:"fileGenerationPeriod" yaml:"fileGenerationPeriod" bson:"fileGenerationPeriod"`
}

// String returns a string representation of the configuration.
func (cfg Config) String() string {
return fmt.Sprintf("directory: %s, filePollingPeriod: %s, fileGenerationPeriod: %s",
cfg.Directory, cfg.FilePollingPeriod, cfg.FileGenerationPeriod)
}

// Validate validates the configuration.
func (cfg Config) Validate() error {
// require directory path to be present
if cfg.Directory == "" {
return ErrMissingDirectory
}

// check if RW access is available for the given directory
if err := syscall.Access(cfg.Directory, syscall.O_RDWR); err != nil {
return fmt.Errorf("directory is not accessible: %w", err)
}

// check if file polling period is set properly
if cfg.FilePollingPeriod <= 0 {
return fmt.Errorf("filePollingPeriod must be greater than 0: %w",
ErrFilePollingPeriodInvalid)
}

// check if file generation period is set properly
if cfg.FileGenerationPeriod <= 0 {
return fmt.Errorf("fileGenerationPeriod must be greater than 0: %w",
ErrFileGenerationPeriodInvalid)
}

return nil
}
59 changes: 59 additions & 0 deletions pkg/bridge/connectors/dummypay/connector.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,62 @@
package dummypay

import (
"context"
"fmt"

"github.com/numary/go-libs/sharedlogging"
"github.com/numary/payments/pkg/bridge/task"
)

// connectorName is the name of the connector.
const connectorName = "dummypay"

// Connector is the connector for the dummy payment connector.
type Connector struct {
logger sharedlogging.Logger
cfg Config
}

// Install executes post-installation steps to read and generate files.
// It is called after the connector is installed.
func (c *Connector) Install(ctx task.ConnectorContext[TaskDescriptor]) error {
if err := ctx.Scheduler().Schedule(newTaskReadFiles(), true); err != nil {
return fmt.Errorf("failed to schedule task to read files: %w", err)
}

if err := ctx.Scheduler().Schedule(newTaskGenerateFiles(), true); err != nil {
return fmt.Errorf("failed to schedule task to generate files: %w", err)
}

return nil
}

// Uninstall executes pre-uninstallation steps to remove the generated files.
// It is called before the connector is uninstalled.
func (c *Connector) Uninstall(ctx context.Context) error {
c.logger.Infof("Removing generated files from '%s'...", c.cfg.Directory)

err := removeFiles(c.cfg)
if err != nil {
return fmt.Errorf("failed to remove generated files: %w", err)
}

return nil
}

// Resolve resolves a task execution request based on the task descriptor.
func (c *Connector) Resolve(descriptor TaskDescriptor) task.Task {
c.logger.Infof("Executing '%s' task...", descriptor.Key)

return handleResolve(c.cfg, descriptor)
}

// NewConnector creates a new dummy payment connector.
func NewConnector(logger sharedlogging.Logger, cfg Config) *Connector {
return &Connector{
logger: logger.WithFields(map[string]any{
"component": "connector",
}),
cfg: cfg,
}
}
11 changes: 10 additions & 1 deletion pkg/bridge/connectors/dummypay/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ package dummypay
import "github.com/pkg/errors"

var (
// ErrMissingDirectory is returned when the directory is missing.
ErrMissingDirectory = errors.New("missing directory to watch")
ErrMissingTask = errors.New("task is not implemented")

// ErrFilePollingPeriodInvalid is returned when the file polling period is invalid.
ErrFilePollingPeriodInvalid = errors.New("file polling period is invalid")

// ErrFileGenerationPeriodInvalid is returned when the file generation period is invalid.
ErrFileGenerationPeriodInvalid = errors.New("file generation period is invalid")

// ErrMissingTask is returned when the task is missing.
ErrMissingTask = errors.New("task is not implemented")
)
68 changes: 42 additions & 26 deletions pkg/bridge/connectors/dummypay/loader.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,51 @@
package dummypay

import (
"fmt"
"time"

"github.com/numary/go-libs/sharedlogging"
"github.com/numary/payments/pkg/bridge/integration"
"github.com/numary/payments/pkg/bridge/task"
)

type loader struct{}

// Name returns the name of the connector.
func (l *loader) Name() string {
return connectorName
}

// AllowTasks returns the amount of tasks that are allowed to be scheduled.
func (l *loader) AllowTasks() int {
return 10
}

const (
// defaultFilePollingPeriod is the default period between file polling.
defaultFilePollingPeriod = 10 * time.Second

// defaultFileGenerationPeriod is the default period between file generation.
defaultFileGenerationPeriod = 5 * time.Second
)

// ApplyDefaults applies default values to the configuration.
func (l *loader) ApplyDefaults(cfg Config) Config {
if cfg.FileGenerationPeriod == 0 {
cfg.FileGenerationPeriod = defaultFileGenerationPeriod
}

if cfg.FilePollingPeriod == 0 {
cfg.FilePollingPeriod = defaultFilePollingPeriod
}

return cfg
}

// Load returns the connector.
func (l *loader) Load(logger sharedlogging.Logger, config Config) integration.Connector[TaskDescriptor] {
return NewConnector(logger, config)
}

// NewLoader creates a new loader.
func NewLoader() integration.Loader[Config, TaskDescriptor] {
loader := integration.
NewLoaderBuilder[Config, TaskDescriptor](connectorName).
WithLoad(func(logger sharedlogging.Logger, config Config) integration.Connector[TaskDescriptor] {
return integration.NewConnectorBuilder[TaskDescriptor]().
WithInstall(func(ctx task.ConnectorContext[TaskDescriptor]) error {
return ctx.Scheduler().Schedule(newTaskReadFiles(), true)
}).
WithResolve(func(descriptor TaskDescriptor) task.Task {
switch descriptor.Key {
case taskKeyReadFiles:
return taskReadFiles(config)
case taskKeyIngest:
return taskIngest(config, descriptor)
}

return func() error {
return fmt.Errorf("key '%s': %w", descriptor.Key, ErrMissingTask)
}
}).
Build()
}).
Build()

return loader
return &loader{}
}
10 changes: 10 additions & 0 deletions pkg/bridge/connectors/dummypay/payment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package dummypay

import payments "github.com/numary/payments/pkg"

// payment represents a payment structure used in the generated files.
type payment struct {
payments.Data
Reference string `json:"reference" bson:"reference"`
Type string `json:"type" bson:"type"`
}
32 changes: 32 additions & 0 deletions pkg/bridge/connectors/dummypay/remove_files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package dummypay

import (
"fmt"
"os"
"strings"
)

// removeFiles removes all files from the given directory.
// Only removes files that has generatedFilePrefix in the name.
func removeFiles(config Config) error {
dir, err := os.ReadDir(config.Directory)
if err != nil {
return fmt.Errorf("failed to open directory '%s': %w", config.Directory, err)
}

// iterate over all files in the directory
for _, file := range dir {
// skip files that do not match the generatedFilePrefix
if !strings.HasPrefix(file.Name(), generatedFilePrefix) {
continue
}

// remove the file
err = os.Remove(fmt.Sprintf("%s/%s", config.Directory, file.Name()))
if err != nil {
return fmt.Errorf("failed to remove file '%s': %w", file.Name(), err)
}
}

return nil
}
25 changes: 23 additions & 2 deletions pkg/bridge/connectors/dummypay/task_descriptor.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
package dummypay

import (
"fmt"

"github.com/numary/payments/pkg/bridge/task"
)

// taskKey defines a unique key of the task.
type taskKey string

// TaskDescriptor represents a task descriptor.
type TaskDescriptor struct {
Key taskKey
FileName string
}

func (td TaskDescriptor) Is(key taskKey) bool {
return td.Key == key
// handleResolve resolves a task execution request based on the task descriptor.
func handleResolve(config Config, descriptor TaskDescriptor) task.Task {
switch descriptor.Key {
case taskKeyReadFiles:
return taskReadFiles(config)
case taskKeyIngest:
return taskIngest(config, descriptor)
case taskKeyGenerateFiles:
return taskGenerateFiles(config)
}

// This should never happen.
return func() error {
return fmt.Errorf("key '%s': %w", descriptor.Key, ErrMissingTask)
}
}
Loading

0 comments on commit 6a0f52f

Please sign in to comment.