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 next, err := a.store.Mutate(func(current profile.State) (profile.State, error) { var mutationErr error switch intent.Kind { case CreateProfile: current, notice, mutationErr = create(current, intent.Profile) case ActivateProfile: current, notice, mutationErr = activate(current, intent.Name) case UpdateProfile: current, notice, mutationErr = update(current, intent.Name, intent.Profile) case DeleteProfile: current, notice, mutationErr = deleteProfile(current, intent.Name, intent.Confirm) default: mutationErr = errors.New("unsupported Profile action") } return current, mutationErr }) if err == nil { a.state = next } return Result{Snapshot: a.Snapshot(), Notice: notice, Err: err} } func create(state profile.State, input profile.Profile) (profile.State, string, error) { p, err := profile.ValidateAndNormalize(input) if err != nil { return state, "", err } if index(state, p.Name) >= 0 { return state, "", fmt.Errorf("a Profile named %q already exists", p.Name) } next := cloneState(state) next.Profiles = append(next.Profiles, p) return next, fmt.Sprintf("Created Profile %q", p.Name), nil } func activate(state profile.State, name string) (profile.State, string, error) { if index(state, name) < 0 { return state, "", fmt.Errorf("Profile %q does not exist", name) } next := cloneState(state) next.ActiveProfile = name return next, fmt.Sprintf("Activated Profile %q", name), nil } func update(state profile.State, originalName string, input profile.Profile) (profile.State, string, error) { profileIndex := index(state, originalName) if profileIndex < 0 { return state, "", fmt.Errorf("Profile %q does not exist", originalName) } p, err := profile.ValidateAndNormalize(input) if err != nil { return state, "", err } if other := index(state, p.Name); other >= 0 && other != profileIndex { return state, "", fmt.Errorf("a Profile named %q already exists", p.Name) } next := cloneState(state) next.Profiles[profileIndex] = p if next.ActiveProfile == originalName { next.ActiveProfile = p.Name } return next, fmt.Sprintf("Updated Profile %q", p.Name), nil } func deleteProfile(state profile.State, name string, confirm bool) (profile.State, string, error) { profileIndex := index(state, name) if profileIndex < 0 { return state, "", fmt.Errorf("Profile %q does not exist", name) } if len(state.Profiles) == 1 && !confirm { return state, "", errors.New("deleting the final Profile requires confirmation") } next := cloneState(state) next.Profiles = append(next.Profiles[:profileIndex], next.Profiles[profileIndex+1:]...) if next.ActiveProfile == name { next.ActiveProfile = "" } return next, fmt.Sprintf("Deleted Profile %q", name), nil } func index(state profile.State, name string) int { for i := range state.Profiles { if 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 }