Files
proxy/internal/app/app.go
T

165 lines
3.8 KiB
Go

package app
import (
"errors"
"fmt"
"proxyctl/internal/profile"
)
type IntentKind int
const (
CreateProfile IntentKind = iota + 1
UpdateProfile
DeleteProfile
ActivateProfile
)
type Intent struct {
Kind IntentKind
Name string
Profile profile.Profile
Confirm bool
}
type Snapshot struct {
Profiles []profile.Profile
ActiveProfile string
}
type Result struct {
Snapshot Snapshot
Notice string
Err error
}
type Module interface {
Snapshot() Snapshot
Dispatch(Intent) Result
}
type Application struct {
store profile.Store
state profile.State
}
func New(store profile.Store) (*Application, error) {
state, err := store.Load()
if err != nil {
return nil, err
}
return &Application{store: store, state: state}, nil
}
func (a *Application) Snapshot() Snapshot {
profiles := append([]profile.Profile(nil), a.state.Profiles...)
return Snapshot{Profiles: profiles, ActiveProfile: a.state.ActiveProfile}
}
func (a *Application) Dispatch(intent Intent) Result {
var notice string
var err error
switch intent.Kind {
case CreateProfile:
notice, err = a.create(intent.Profile)
case ActivateProfile:
notice, err = a.activate(intent.Name)
case UpdateProfile:
notice, err = a.update(intent.Name, intent.Profile)
case DeleteProfile:
notice, err = a.delete(intent.Name, intent.Confirm)
default:
err = errors.New("unsupported Profile action")
}
return Result{Snapshot: a.Snapshot(), Notice: notice, Err: err}
}
func (a *Application) create(input profile.Profile) (string, error) {
p, err := profile.ValidateAndNormalize(input)
if err != nil {
return "", err
}
if a.index(p.Name) >= 0 {
return "", fmt.Errorf("a Profile named %q already exists", p.Name)
}
next := cloneState(a.state)
next.Profiles = append(next.Profiles, p)
if err := a.store.Save(next); err != nil {
return "", err
}
a.state = next
return fmt.Sprintf("Created Profile %q", p.Name), nil
}
func (a *Application) activate(name string) (string, error) {
if a.index(name) < 0 {
return "", fmt.Errorf("Profile %q does not exist", name)
}
next := cloneState(a.state)
next.ActiveProfile = name
if err := a.store.Save(next); err != nil {
return "", err
}
a.state = next
return fmt.Sprintf("Activated Profile %q", name), nil
}
func (a *Application) update(originalName string, input profile.Profile) (string, error) {
index := a.index(originalName)
if index < 0 {
return "", fmt.Errorf("Profile %q does not exist", originalName)
}
p, err := profile.ValidateAndNormalize(input)
if err != nil {
return "", err
}
if other := a.index(p.Name); other >= 0 && other != index {
return "", fmt.Errorf("a Profile named %q already exists", p.Name)
}
next := cloneState(a.state)
next.Profiles[index] = p
if next.ActiveProfile == originalName {
next.ActiveProfile = p.Name
}
if err := a.store.Save(next); err != nil {
return "", err
}
a.state = next
return fmt.Sprintf("Updated Profile %q", p.Name), nil
}
func (a *Application) delete(name string, confirm bool) (string, error) {
index := a.index(name)
if index < 0 {
return "", fmt.Errorf("Profile %q does not exist", name)
}
if len(a.state.Profiles) == 1 && !confirm {
return "", errors.New("deleting the final Profile requires confirmation")
}
next := cloneState(a.state)
next.Profiles = append(next.Profiles[:index], next.Profiles[index+1:]...)
if next.ActiveProfile == name {
next.ActiveProfile = ""
}
if err := a.store.Save(next); err != nil {
return "", err
}
a.state = next
return fmt.Sprintf("Deleted Profile %q", name), nil
}
func (a *Application) index(name string) int {
for i := range a.state.Profiles {
if a.state.Profiles[i].Name == name {
return i
}
}
return -1
}
func cloneState(state profile.State) profile.State {
state.Profiles = append([]profile.Profile(nil), state.Profiles...)
return state
}