From 1ca6eb1cc063b64b19456e09e07f69d2835fcbb1 Mon Sep 17 00:00:00 2001
From: Aaron <aaron@bettercoder.net>
Date: Sat, 21 Feb 2015 21:03:03 -0800
Subject: [PATCH] Change the way callbacks work AGAIN.

---
 callbacks.go      | 59 ++++++++++++++++++++++++++++++++-------
 callbacks_test.go | 71 +++++++++++++++++++++++++++++++++++------------
 2 files changed, 103 insertions(+), 27 deletions(-)

diff --git a/callbacks.go b/callbacks.go
index 79861a4..9e97dd1 100644
--- a/callbacks.go
+++ b/callbacks.go
@@ -9,7 +9,7 @@ import (
 // Event is used for callback registration.
 type Event int
 
-// These are the events that are available for use.
+// Event values
 const (
 	EventRegister Event = iota
 	EventAuth
@@ -19,11 +19,49 @@ const (
 	EventGet
 )
 
+const eventNames = "EventRegisterEventAuthEventAuthFailEventRecoverStartEventRecoverEndEventGet"
+
+var eventIndexes = [...]uint8{0, 13, 22, 35, 52, 67, 75}
+
+func (i Event) String() string {
+	if i < 0 || i+1 >= Event(len(eventIndexes)) {
+		return fmt.Sprintf("Event(%d)", i)
+	}
+	return eventNames[eventIndexes[i]:eventIndexes[i+1]]
+}
+
+// Interrupt is used to signal to callback mechanisms
+// that the current process should not continue.
+type Interrupt int
+
+// Interrupt values
+const (
+	// InterruptNone means there was no interrupt present and the process should continue.
+	InterruptNone Interrupt = iota
+	// InterruptAccountLocked occurs if a user's account has been locked
+	// by the lock module.
+	InterruptAccountLocked
+	// InterruptAccountNotConfirmed occurs if a user's account is not confirmed
+	// and therefore cannot be used yet.
+	InterruptAccountNotConfirmed
+)
+
+const interruptNames = "InterruptNoneInterruptAccountLockedInterruptAccountNotConfirmed"
+
+var interruptIndexes = [...]uint8{0, 13, 35, 63}
+
+func (i Interrupt) String() string {
+	if i < 0 || i+1 >= Interrupt(len(interruptIndexes)) {
+		return fmt.Sprintf("Interrupt(%d)", i)
+	}
+	return interruptNames[interruptIndexes[i]:interruptIndexes[i+1]]
+}
+
 // Before callbacks can interrupt the flow by returning a bool. This is used to stop
 // the callback chain and the original handler from continuing execution.
 // The execution should also stopped if there is an error (and therefore if error is set
 // the bool is automatically considered set).
-type Before func(*Context) (bool, error)
+type Before func(*Context) (Interrupt, error)
 
 // After is a request callback that happens after the event.
 type After func(*Context) error
@@ -59,22 +97,23 @@ func (c *Callbacks) After(e Event, f After) {
 // FireBefore event to all the callbacks with a context. The error
 // should be passed up despite being logged once here already so it
 // can write an error out to the HTTP Client. If err is nil then
-// check the value of interrupted. If interrupted is false
-// then the handler can continue it's task otherwise it must stop.
-func (c *Callbacks) FireBefore(e Event, ctx *Context) (interrupted bool, err error) {
+// check the value of interrupted. If error is nil then the interrupt
+// value should be checked. If it is not InterruptNone then there is a reason
+// the current process should stop it's course of action.
+func (c *Callbacks) FireBefore(e Event, ctx *Context) (interrupt Interrupt, err error) {
 	callbacks := c.before[e]
 	for _, fn := range callbacks {
-		interrupted, err = fn(ctx)
+		interrupt, err = fn(ctx)
 		if err != nil {
 			fmt.Fprintf(Cfg.LogWriter, "Callback error (%s): %v\n", runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name(), err)
-			return false, err
+			return InterruptNone, err
 		}
-		if interrupted {
-			return true, nil
+		if interrupt != InterruptNone {
+			return interrupt, nil
 		}
 	}
 
-	return false, nil
+	return InterruptNone, nil
 }
 
 // FireAfter event to all the callbacks with a context. The error can safely be
diff --git a/callbacks_test.go b/callbacks_test.go
index 960c9b6..32ca01e 100644
--- a/callbacks_test.go
+++ b/callbacks_test.go
@@ -12,9 +12,9 @@ func TestCallbacks(t *testing.T) {
 	beforeCalled := false
 	c := NewCallbacks()
 
-	c.Before(EventRegister, func(ctx *Context) (bool, error) {
+	c.Before(EventRegister, func(ctx *Context) (Interrupt, error) {
 		beforeCalled = true
-		return false, nil
+		return InterruptNone, nil
 	})
 	c.After(EventRegister, func(ctx *Context) error {
 		afterCalled = true
@@ -25,11 +25,11 @@ func TestCallbacks(t *testing.T) {
 		t.Error("Neither should be called.")
 	}
 
-	stopped, err := c.FireBefore(EventRegister, NewContext())
+	interrupt, err := c.FireBefore(EventRegister, NewContext())
 	if err != nil {
 		t.Error("Unexpected error:", err)
 	}
-	if stopped {
+	if interrupt != InterruptNone {
 		t.Error("It should not have been stopped.")
 	}
 
@@ -51,21 +51,21 @@ func TestCallbacksInterrupt(t *testing.T) {
 	before2 := false
 	c := NewCallbacks()
 
-	c.Before(EventRegister, func(ctx *Context) (bool, error) {
+	c.Before(EventRegister, func(ctx *Context) (Interrupt, error) {
 		before1 = true
-		return true, nil
+		return InterruptAccountLocked, nil
 	})
-	c.Before(EventRegister, func(ctx *Context) (bool, error) {
+	c.Before(EventRegister, func(ctx *Context) (Interrupt, error) {
 		before2 = true
-		return false, nil
+		return InterruptNone, nil
 	})
 
-	stopped, err := c.FireBefore(EventRegister, NewContext())
+	interrupt, err := c.FireBefore(EventRegister, NewContext())
 	if err != nil {
 		t.Error(err)
 	}
-	if !stopped {
-		t.Error("It was not stopped.")
+	if interrupt != InterruptAccountLocked {
+		t.Error("The interrupt signal was not account locked:", interrupt)
 	}
 
 	if !before1 {
@@ -87,20 +87,20 @@ func TestCallbacksBeforeErrors(t *testing.T) {
 
 	errValue := errors.New("Problem occured")
 
-	c.Before(EventRegister, func(ctx *Context) (bool, error) {
+	c.Before(EventRegister, func(ctx *Context) (Interrupt, error) {
 		before1 = true
-		return false, errValue
+		return InterruptNone, errValue
 	})
-	c.Before(EventRegister, func(ctx *Context) (bool, error) {
+	c.Before(EventRegister, func(ctx *Context) (Interrupt, error) {
 		before2 = true
-		return false, nil
+		return InterruptNone, nil
 	})
 
-	stopped, err := c.FireBefore(EventRegister, NewContext())
+	interrupt, err := c.FireBefore(EventRegister, NewContext())
 	if err != errValue {
 		t.Error("Expected an error to come back.")
 	}
-	if stopped {
+	if interrupt != InterruptNone {
 		t.Error("It should not have been stopped.")
 	}
 
@@ -152,3 +152,40 @@ func TestCallbacksAfterErrors(t *testing.T) {
 		t.Error("Error string wrong:", estr)
 	}
 }
+
+func TestEventString(t *testing.T) {
+	tests := []struct {
+		ev  Event
+		str string
+	}{
+		{EventRegister, "EventRegister"},
+		{EventAuth, "EventAuth"},
+		{EventAuthFail, "EventAuthFail"},
+		{EventRecoverStart, "EventRecoverStart"},
+		{EventRecoverEnd, "EventRecoverEnd"},
+		{EventGet, "EventGet"},
+	}
+
+	for i, test := range tests {
+		if got := test.ev.String(); got != test.str {
+			t.Errorf("%d) Wrong string for Event(%d) expected: %v got: %s", i, test.ev, test.str, got)
+		}
+	}
+}
+
+func TestInterruptString(t *testing.T) {
+	tests := []struct {
+		in  Interrupt
+		str string
+	}{
+		{InterruptNone, "InterruptNone"},
+		{InterruptAccountLocked, "InterruptAccountLocked"},
+		{InterruptAccountNotConfirmed, "InterruptAccountNotConfirmed"},
+	}
+
+	for i, test := range tests {
+		if got := test.in.String(); got != test.str {
+			t.Errorf("%d) Wrong string for Event(%d) expected: %v got: %s", i, test.in, test.str, got)
+		}
+	}
+}