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 }