<commit_before>package makex

import (
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"

	"code.google.com/p/rog-go/parallel"
)

// TargetsNeedingBuild returns an ordered list of target sets
func (c *Config) NewMaker(mf *Makefile, goals ...string) *Maker {
	m := &Maker{
		mf:     mf,
		goals:  goals,
		Config: c,
	}
	m.buildDAG()
	return m
}

type Maker struct {
	mf     *Makefile
	goals  []string
	dag    map[string][]string
	topo   [][]string
	cycles map[string][]string

	// RuleOutput specifies the writers to receive the stdout and stderr output
	// from executing a rule's recipes. After executing a rule, out and err are
	// closed. If RuleOutput is nil, os.Stdout and
	// os.Stderr are used, respectively (but not closed after use).
	RuleOutput func(Rule) (out io.WriteCloser, err io.WriteCloser, logger *log.Logger)

	// Channels to monitor progress. If non-nil, these channels are called at
	// various stages of building targets. Ended is always called *after*
	// Succeeded or Failed.
	Started, Ended, Succeeded chan<- Rule
	Failed                    chan<- RuleBuildError

	*Config
}

func (m *Maker) buildDAG() {
	// topological sort taken from
	// http://rosettacode.org/wiki/Topological_sort#Go.

	if m.dag == nil || m.cycles == nil {
		m.dag = make(map[string][]string)
		m.cycles = make(map[string][]string)
	}

	seen := make(map[string]struct{})
	queue := append([]string{}, m.goals...)
	for {
		if len(queue) == 0 {
			break
		}
		origLen := len(queue)
		for _, target := range queue {
			if _, seen := seen[target]; seen {
				continue
			}
			seen[target] = struct{}{}

			rule := m.mf.Rule(target)
			if rule == nil {
				continue
			}
			prereqs := uniqAndSort(rule.Prereqs())
			m.dag[target] = append([]string{}, prereqs...)
			for _, dep := range prereqs {
				// make a node for the prereq target even if it isn't defined
				queue = append(queue, dep)
				m.dag[dep] = m.dag[dep]
			}
		}
		queue = queue[origLen:]
	}

	// topological sort on the DAG
	for len(m.dag) > 0 {

		// collect targets with no dependencies
		var zero []string
		for target, prereqs := range m.dag {
			if len(prereqs) == 0 {
				zero = append(zero, target)
				delete(m.dag, target)
			}
		}

		// cycle detection
		if len(zero) == 0 {
			// collect un-orderable dependencies
			cycle := make(map[string]bool)
			for _, prereqs := range m.dag {
				for _, dep := range prereqs {
					cycle[dep] = true
				}
			}

			// mark targets with un-orderable dependencies
			for target, prereqs := range m.dag {
				if cycle[target] {
					m.cycles[target] = prereqs
				}
			}
			return
		}

		// output a set that can be processed concurrently
		m.topo = append(m.topo, zero)

		// remove edges (dependencies) from dg
		for _, remove := range zero {
			for target, prereqs := range m.dag {
				for i, dep := range prereqs {
					if dep == remove {
						copy(prereqs[i:], prereqs[i+1:])
						m.dag[target] = prereqs[:len(prereqs)-1]
						break
					}
				}
			}
		}
	}
}

func (m *Maker) TargetSets() [][]string {
	return m.topo
}

func (m *Maker) TargetSetsNeedingBuild() ([][]string, error) {
	for _, goal := range m.goals {
		if rule := m.mf.Rule(goal); rule == nil {
			return nil, errNoRuleToMakeTarget(goal)
		}
		if deps, isCycle := m.cycles[goal]; isCycle {
			return nil, errCircularDependency(goal, deps)
		}
	}

	targetSets := make([][]string, 0)
	for _, targetSet := range m.topo {
		var targetsNeedingBuild []string
		for _, target := range targetSet {
			exists, err := m.pathExists(target)
			if err != nil {
				return nil, err
			}
			if !exists {
				rule := m.mf.Rule(target)
				if rule == nil {
					return nil, errNoRuleToMakeTarget(target)
				}
				targetsNeedingBuild = append(targetsNeedingBuild, target)
			}
		}
		if len(targetsNeedingBuild) > 0 {
			targetSets = append(targetSets, targetsNeedingBuild)
		}
	}
	return targetSets, nil
}

// DryRun prints information about what targets *would* be built if Run() was
// called.
func (m *Maker) DryRun(w io.Writer) error {
	targetSets, err := m.TargetSetsNeedingBuild()
	if err != nil {
		return err
	}
	if len(targetSets) == 0 {
		fmt.Fprintln(w, "No target sets need building.")
	}
	for i, targetSet := range targetSets {
		if i != 0 {
			fmt.Fprintln(w)
		}
		fmt.Fprintf(w, "========= TARGET SET %d (%d targets)\n", i, len(targetSet))
		for _, target := range targetSet {
			fmt.Fprintln(w, " - ", target)
		}
	}
	return nil
}

// ruleOutput determines the io.Writers to receive the stderr and stdout output
// of a rule's recipe commands.
func (m *Maker) ruleOutput(r Rule) (stdout io.WriteCloser, stderr io.WriteCloser, logger *log.Logger) {
	if m.RuleOutput != nil {
		return m.RuleOutput(r)
	}
	return nopCloser{os.Stdout}, nopCloser{os.Stderr}, log.New(os.Stderr, fmt.Sprintf("%s: ", r.Target()), 0)
}

func (m *Maker) Run() error {
	targetSets, err := m.TargetSetsNeedingBuild()
	if err != nil {
		return err
	}

	for _, targetSet := range targetSets {
		par := parallel.NewRun(m.ParallelJobs)
		for _, target := range targetSet {
			rule := m.mf.Rule(target)
			par.Do(func() error {
				stdout, stderr, log := m.ruleOutput(rule)
				if m.Verbose {
					log.Printf("building...")
				}
				if m.Started != nil {
					m.Started <- rule
				}
				defer stdout.Close()
				defer stderr.Close()
				defer func() {
					if m.Ended != nil {
						m.Ended <- rule
					}
				}()

				for _, recipe := range rule.Recipes() {
					recipe = ExpandAutoVars(rule, recipe)
					if m.Verbose {
						log.Printf("running command: %s", recipe)
					}
					cmd := exec.Command("sh", "-c", recipe)
					cmd.Stdout, cmd.Stderr = stdout, stderr

					err := cmd.Run()
					if err != nil {
						// remove files if failed
						if exists, _ := m.pathExists(rule.Target()); exists {
							err2 := m.fs().Remove(rule.Target())
							if err2 != nil {
								log.Printf("failed to remove target after error: %s", err)
							}
						}

						log.Printf(`command failed: %s (%s)`, recipe, err)
						err2 := RuleBuildError{rule, fmt.Errorf("command failed: %s (%s)", recipe, err)}
						if m.Failed != nil {
							m.Failed <- err2
						}
						return err2
					}
				}

				if m.Succeeded != nil {
					m.Succeeded <- rule
				}
				if m.Verbose {
					log.Printf("build completed")
				}

				return nil
			})
		}
		err := par.Wait()
		if err != nil {
			return Errors(err.(parallel.Errors))
		}
	}

	return nil
}

type RuleBuildError struct {
	Rule Rule
	Err  error
}

func (e RuleBuildError) Error() string { return e.Err.Error() }

func errNoRuleToMakeTarget(target string) error {
	return fmt.Errorf("no rule to make target %q", target)
}

func errCircularDependency(target string, deps []string) error {
	return fmt.Errorf("circular dependency for target %q: %v", target, deps)
}

type nopCloser struct {
	io.Writer
}

func (nc nopCloser) Close() error { return nil }
<commit_msg>remove needless verbose log messages<commit_after>package makex

import (
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"

	"code.google.com/p/rog-go/parallel"
)

// TargetsNeedingBuild returns an ordered list of target sets
func (c *Config) NewMaker(mf *Makefile, goals ...string) *Maker {
	m := &Maker{
		mf:     mf,
		goals:  goals,
		Config: c,
	}
	m.buildDAG()
	return m
}

type Maker struct {
	mf     *Makefile
	goals  []string
	dag    map[string][]string
	topo   [][]string
	cycles map[string][]string

	// RuleOutput specifies the writers to receive the stdout and stderr output
	// from executing a rule's recipes. After executing a rule, out and err are
	// closed. If RuleOutput is nil, os.Stdout and
	// os.Stderr are used, respectively (but not closed after use).
	RuleOutput func(Rule) (out io.WriteCloser, err io.WriteCloser, logger *log.Logger)

	// Channels to monitor progress. If non-nil, these channels are called at
	// various stages of building targets. Ended is always called *after*
	// Succeeded or Failed.
	Started, Ended, Succeeded chan<- Rule
	Failed                    chan<- RuleBuildError

	*Config
}

func (m *Maker) buildDAG() {
	// topological sort taken from
	// http://rosettacode.org/wiki/Topological_sort#Go.

	if m.dag == nil || m.cycles == nil {
		m.dag = make(map[string][]string)
		m.cycles = make(map[string][]string)
	}

	seen := make(map[string]struct{})
	queue := append([]string{}, m.goals...)
	for {
		if len(queue) == 0 {
			break
		}
		origLen := len(queue)
		for _, target := range queue {
			if _, seen := seen[target]; seen {
				continue
			}
			seen[target] = struct{}{}

			rule := m.mf.Rule(target)
			if rule == nil {
				continue
			}
			prereqs := uniqAndSort(rule.Prereqs())
			m.dag[target] = append([]string{}, prereqs...)
			for _, dep := range prereqs {
				// make a node for the prereq target even if it isn't defined
				queue = append(queue, dep)
				m.dag[dep] = m.dag[dep]
			}
		}
		queue = queue[origLen:]
	}

	// topological sort on the DAG
	for len(m.dag) > 0 {

		// collect targets with no dependencies
		var zero []string
		for target, prereqs := range m.dag {
			if len(prereqs) == 0 {
				zero = append(zero, target)
				delete(m.dag, target)
			}
		}

		// cycle detection
		if len(zero) == 0 {
			// collect un-orderable dependencies
			cycle := make(map[string]bool)
			for _, prereqs := range m.dag {
				for _, dep := range prereqs {
					cycle[dep] = true
				}
			}

			// mark targets with un-orderable dependencies
			for target, prereqs := range m.dag {
				if cycle[target] {
					m.cycles[target] = prereqs
				}
			}
			return
		}

		// output a set that can be processed concurrently
		m.topo = append(m.topo, zero)

		// remove edges (dependencies) from dg
		for _, remove := range zero {
			for target, prereqs := range m.dag {
				for i, dep := range prereqs {
					if dep == remove {
						copy(prereqs[i:], prereqs[i+1:])
						m.dag[target] = prereqs[:len(prereqs)-1]
						break
					}
				}
			}
		}
	}
}

func (m *Maker) TargetSets() [][]string {
	return m.topo
}

func (m *Maker) TargetSetsNeedingBuild() ([][]string, error) {
	for _, goal := range m.goals {
		if rule := m.mf.Rule(goal); rule == nil {
			return nil, errNoRuleToMakeTarget(goal)
		}
		if deps, isCycle := m.cycles[goal]; isCycle {
			return nil, errCircularDependency(goal, deps)
		}
	}

	targetSets := make([][]string, 0)
	for _, targetSet := range m.topo {
		var targetsNeedingBuild []string
		for _, target := range targetSet {
			exists, err := m.pathExists(target)
			if err != nil {
				return nil, err
			}
			if !exists {
				rule := m.mf.Rule(target)
				if rule == nil {
					return nil, errNoRuleToMakeTarget(target)
				}
				targetsNeedingBuild = append(targetsNeedingBuild, target)
			}
		}
		if len(targetsNeedingBuild) > 0 {
			targetSets = append(targetSets, targetsNeedingBuild)
		}
	}
	return targetSets, nil
}

// DryRun prints information about what targets *would* be built if Run() was
// called.
func (m *Maker) DryRun(w io.Writer) error {
	targetSets, err := m.TargetSetsNeedingBuild()
	if err != nil {
		return err
	}
	if len(targetSets) == 0 {
		fmt.Fprintln(w, "No target sets need building.")
	}
	for i, targetSet := range targetSets {
		if i != 0 {
			fmt.Fprintln(w)
		}
		fmt.Fprintf(w, "========= TARGET SET %d (%d targets)\n", i, len(targetSet))
		for _, target := range targetSet {
			fmt.Fprintln(w, " - ", target)
		}
	}
	return nil
}

// ruleOutput determines the io.Writers to receive the stderr and stdout output
// of a rule's recipe commands.
func (m *Maker) ruleOutput(r Rule) (stdout io.WriteCloser, stderr io.WriteCloser, logger *log.Logger) {
	if m.RuleOutput != nil {
		return m.RuleOutput(r)
	}
	return nopCloser{os.Stdout}, nopCloser{os.Stderr}, log.New(os.Stderr, fmt.Sprintf("%s: ", r.Target()), 0)
}

func (m *Maker) Run() error {
	targetSets, err := m.TargetSetsNeedingBuild()
	if err != nil {
		return err
	}

	for _, targetSet := range targetSets {
		par := parallel.NewRun(m.ParallelJobs)
		for _, target := range targetSet {
			rule := m.mf.Rule(target)
			par.Do(func() error {
				stdout, stderr, log := m.ruleOutput(rule)
				if m.Started != nil {
					m.Started <- rule
				}
				defer stdout.Close()
				defer stderr.Close()
				defer func() {
					if m.Ended != nil {
						m.Ended <- rule
					}
				}()

				for _, recipe := range rule.Recipes() {
					recipe = ExpandAutoVars(rule, recipe)
					if m.Verbose {
						log.Printf("running command: %s", recipe)
					}
					cmd := exec.Command("sh", "-c", recipe)
					cmd.Stdout, cmd.Stderr = stdout, stderr

					err := cmd.Run()
					if err != nil {
						// remove files if failed
						if exists, _ := m.pathExists(rule.Target()); exists {
							err2 := m.fs().Remove(rule.Target())
							if err2 != nil {
								log.Printf("failed to remove target after error: %s", err)
							}
						}

						log.Printf(`command failed: %s (%s)`, recipe, err)
						err2 := RuleBuildError{rule, fmt.Errorf("command failed: %s (%s)", recipe, err)}
						if m.Failed != nil {
							m.Failed <- err2
						}
						return err2
					}
				}

				if m.Succeeded != nil {
					m.Succeeded <- rule
				}

				return nil
			})
		}
		err := par.Wait()
		if err != nil {
			return Errors(err.(parallel.Errors))
		}
	}

	return nil
}

type RuleBuildError struct {
	Rule Rule
	Err  error
}

func (e RuleBuildError) Error() string { return e.Err.Error() }

func errNoRuleToMakeTarget(target string) error {
	return fmt.Errorf("no rule to make target %q", target)
}

func errCircularDependency(target string, deps []string) error {
	return fmt.Errorf("circular dependency for target %q: %v", target, deps)
}

type nopCloser struct {
	io.Writer
}

func (nc nopCloser) Close() error { return nil }
<|endoftext|>


<commit_before>package maze

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/gorilla/schema"
	tk "github.com/quintans/toolkit"
	"github.com/quintans/toolkit/log"
	"github.com/quintans/toolkit/web"
)

var decoder = schema.NewDecoder()

func init() {
	decoder.SetAliasTag("json")
}

var logger log.ILogger

func SetLogger(lgr log.ILogger) {
	logger = lgr
}

// NewMaze creates maze with context factory. If nil, it uses a default context factory
func NewMaze(contextFactory func(w http.ResponseWriter, r *http.Request, filters []*Filter) IContext) *Maze {
	this := new(Maze)
	this.filters = make([]*Filter, 0)
	this.contextFactory = contextFactory
	return this
}

type Maze struct {
	filters        []*Filter
	contextFactory func(w http.ResponseWriter, r *http.Request, filters []*Filter) IContext
	lastRule       string
}

func (this *Maze) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if len(this.filters) > 0 {
		var ctx IContext
		if this.contextFactory == nil {
			// default
			ctx = NewContext(w, r, this.filters)
		} else {
			ctx = this.contextFactory(w, r, this.filters)
		}
		err := ctx.Proceed()
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
}

func (this *Maze) GET(rule string, filters ...Handler) {
	this.PushMethod([]string{"GET"}, rule, filters...)
}

func (this *Maze) POST(rule string, filters ...Handler) {
	this.PushMethod([]string{"POST"}, rule, filters...)
}

func (this *Maze) PUT(rule string, filters ...Handler) {
	this.PushMethod([]string{"PUT"}, rule, filters...)
}

func (this *Maze) DELETE(rule string, filters ...Handler) {
	this.PushMethod([]string{"DELETE"}, rule, filters...)
}

func (this *Maze) Push(rule string, filters ...Handler) {
	this.PushMethod(nil, rule, filters...)
}

// PushMethod adds the filters to the end of the last filters.
// If the rule does NOT start with '/' the applied rule will be
// the concatenation of the last rule that started with '/' and ended with a '*'
// with this one (the '*' is omitted).
// ex: /greet/* + sayHi/:Id = /greet/sayHi/:Id
func (this *Maze) PushMethod(methods []string, rule string, handlers ...Handler) {
	if strings.HasPrefix(rule, "/") {
		if strings.HasSuffix(rule, "*") {
			this.lastRule = rule[:len(rule)-1]
			logger.Debugf("Last main rule set as %s", this.lastRule)
		} else {
			// resets lastRule
			this.lastRule = ""
		}
	} else if !strings.HasPrefix(rule, "*") {
		if this.lastRule == "" {
			rule = "/" + rule
		} else {
			rule = this.lastRule + rule
		}
	}

	if len(handlers) > 0 {
		f := ConvertHandlers(handlers...)
		// rule is only set for the first filter
		if rule != "" {
			logger.Debugf("registering rule %s", rule)
			f[0].rule = rule
			if i := strings.Index(rule, ":"); i != -1 {
				f[0].template = strings.Split(rule, "/")
			}
		}
		f[0].allowedMethods = methods

		this.filters = append(this.filters, f...)
	}
}

func (this *Maze) Add(filters ...*Filter) {
	this.filters = append(this.filters, filters...)
}

// Static serves static content.
// rule defines the rule and dir the relative path
func (this *Maze) Static(rule string, dir string) {
	// delivering static content and preventing malicious access
	var fs = web.OnlyFilesFS{http.Dir(dir)}
	var fileServer = http.FileServer(fs)
	this.GET(rule, func(ctx IContext) error {
		fileServer.ServeHTTP(ctx.GetResponse(), ctx.GetRequest())
		return nil
	})
}

func (this *Maze) ListenAndServe(addr string) error {
	mux := http.NewServeMux()
	mux.Handle("/", this)

	return http.ListenAndServe(":8888", mux)
}

type IContext interface {
	Proceed() error
	GetResponse() http.ResponseWriter
	SetResponse(http.ResponseWriter)
	GetRequest() *http.Request
	GetAttribute(interface{}) interface{}
	SetAttribute(interface{}, interface{})
	CurrentFilter() *Filter

	// Payload put the json string in the request body into the struct passed as an interface{}
	Payload(interface{}) error
	// PathVars put the path parameters in a url into the struct passed as an interface{}
	PathVars(interface{}) error
	// QueryVars put the parameters in the query part of a url into the struct passed as an interface{}
	QueryVars(interface{}) error
	// Vars
	Vars(interface{}) error
	// Values gets a path parameter converter
	PathValues() Values
	// Values gets a parameter converter (path + query)
	Values() Values
	// Load calls Vars and Payload
	Load(value interface{}) error
	// TEXT converts to string the interface{} value and sends it into the response
	TEXT(interface{}) error
	// JSON marshals the interface{} value into a json string and sends it into the response
	JSON(interface{}) error
}

var _ IContext = &Context{}

type Context struct {
	Response   http.ResponseWriter
	Request    *http.Request
	Attributes map[interface{}]interface{} // attributes only valid in this request
	filters    []*Filter
	filterPos  int
	values     Values
	pathValues Values
}

func NewContext(w http.ResponseWriter, r *http.Request, filters []*Filter) *Context {
	var this = new(Context)
	this.Response = w
	this.Request = r
	this.filterPos = -1
	this.filters = filters
	this.Attributes = make(map[interface{}]interface{})
	return this
}

func (this *Context) nextFilter() *Filter {
	this.filterPos++
	if this.filterPos < len(this.filters) {
		return this.filters[this.filterPos]
	}
	// don't let ir go higher than the max
	this.filterPos = len(this.filters)

	return nil
}

// Proceed proceeds to the next valid rule
// This method should be reimplemented in specialized Context,
// extending this one
func (this *Context) Proceed() error {
	return this.Next(this)
}

func (this *Context) Next(c IContext) error {
	var next = this.nextFilter()
	if next != nil {
		if next.rule == "" {
			logger.Debugf("executing filter without rule")
			return next.handler(this)
		} else {
			// go to the next valid filter.
			// I don't use recursivity for this, because it can be very deep
			for i := this.filterPos; i < len(this.filters); i++ {
				var n = this.filters[i]
				if n.rule != "" && n.IsValid(c.GetRequest()) {
					this.filterPos = i
					logger.Debugf("executing filter %s", n.rule)
					return n.handler(this)
				}
			}
		}
	}

	return nil
}

func (this *Context) GetResponse() http.ResponseWriter {
	return this.Response
}

func (this *Context) SetResponse(w http.ResponseWriter) {
	this.Response = w
}

func (this *Context) GetRequest() *http.Request {
	return this.Request
}

func (this *Context) GetAttribute(key interface{}) interface{} {
	return this.Attributes[key]
}

func (this *Context) SetAttribute(key interface{}, value interface{}) {
	this.Attributes[key] = value
}

func (this *Context) CurrentFilter() *Filter {
	if this.filterPos < len(this.filters) {
		return this.filters[this.filterPos]
	}
	return nil
}

func (this *Context) Payload(value interface{}) error {
	if this.Request.Body != nil {
		payload, err := ioutil.ReadAll(this.Request.Body)
		if err != nil {
			return err
		}

		return json.Unmarshal(payload, value)
	}

	return nil
}

// PathVars put the path parameters in a url into the struct passed as an interface{}
func (this *Context) PathVars(value interface{}) error {
	var values = this.PathValues()
	if len(values) > 0 {
		return decoder.Decode(value, values)
	}

	return nil
}

// QueryVars put the parameters in the query part of a url into the struct passed as an interface{}
func (this *Context) QueryVars(value interface{}) error {
	var values = this.GetRequest().URL.Query()
	if len(values) > 0 {
		return decoder.Decode(value, values)
	}

	return nil
}

func (this *Context) Vars(value interface{}) error {
	var values = this.Values()
	if len(values) > 0 {
		return decoder.Decode(value, values)
	}

	return nil
}

func (this *Context) Load(value interface{}) error {
	if err := this.Vars(value); err != nil {
		return err
	}

	if err := this.Payload(value); err != nil {
		return err
	}

	return nil
}

func (this *Context) Values() Values {
	if this.values != nil {
		return this.values
	}

	this.values = make(Values)

	// path parameters
	for k, v := range this.PathValues() {
		this.values[k] = v
	}

	// query parameters
	var query = this.GetRequest().URL.Query()
	if query != nil {
		for k, v := range query {
			this.values[k] = v
		}
	}

	return this.values
}

func (this *Context) PathValues() Values {
	if this.pathValues != nil {
		return this.pathValues
	}

	this.pathValues = make(Values)
	var path = this.GetRequest().URL.Path
	var parts = strings.Split(path, "/")

	var template = this.CurrentFilter().template

	if len(parts) == len(template) {
		for k, v := range template {
			if strings.HasPrefix(v, ":") {
				this.pathValues[v[1:]] = []string{parts[k]}
			}
		}
	}

	return this.pathValues
}

func (this *Context) TEXT(value interface{}) error {
	var w = this.GetResponse()
	//w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	//w.Header().Set("Expires", "-1")
	if value != nil {
		var s = tk.ToString(value)
		if _, err := w.Write([]byte(s)); err != nil {
			return err
		}
	} else {
		w.WriteHeader(http.StatusOK)
	}

	return nil
}

func (this *Context) JSON(value interface{}) error {
	var w = this.GetResponse()
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Header().Set("Expires", "-1")
	if value != nil {
		result, err := json.Marshal(value)
		if err != nil {
			return err
		}
		// writing sets status to OK
		_, err = w.Write(result)
		if err != nil {
			return err
		}
	} else {
		w.WriteHeader(http.StatusOK)
	}

	return nil
}

type Filterer interface {
	Handle(ctx IContext) error
}

type Handler func(IContext) error

type Filter struct {
	rule           string
	template       []string
	allowedMethods []string

	handler Handler
}

func NewFilter(rule string, handler Handler) *Filter {
	var this = new(Filter)
	this.rule = rule
	this.handler = handler
	return this
}

func (this *Filter) IsValid(request *http.Request) bool {
	// verify if method is allowed
	var allowed bool
	if this.allowedMethods == nil {
		allowed = true
	} else {
		var method = request.Method
		if method == "" {
			method = "GET"
		}
		for _, v := range this.allowedMethods {
			if method == v {
				allowed = true
				break
			}
		}
	}

	if allowed {
		var path = request.URL.Path
		if strings.HasPrefix(this.rule, "*") {
			return strings.HasSuffix(path, this.rule[1:])
		} else if strings.HasSuffix(this.rule, "*") {
			return strings.HasPrefix(path, this.rule[:len(this.rule)-1])
		} else if this.template != nil {
			return this.validate(path)
		} else {
			return path == this.rule
		}
	}

	return false
}

// validate checks if its a valid match with the url template
func (this *Filter) validate(path string) bool {
	var parts = strings.Split(path, "/")

	if len(parts) != len(this.template) {
		return false
	}

	for k, v := range this.template {
		if !strings.HasPrefix(v, ":") && v != parts[k] {
			return false
		}
	}

	return true
}

func ConvertHandlers(handlers ...Handler) []*Filter {
	var filters = make([]*Filter, len(handlers))
	for k, v := range handlers {
		filters[k] = &Filter{handler: v}
	}
	return filters
}
<commit_msg>fixed port bug<commit_after>package maze

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/gorilla/schema"
	tk "github.com/quintans/toolkit"
	"github.com/quintans/toolkit/log"
	"github.com/quintans/toolkit/web"
)

var decoder = schema.NewDecoder()

func init() {
	decoder.SetAliasTag("json")
}

var logger log.ILogger

func SetLogger(lgr log.ILogger) {
	logger = lgr
}

// NewMaze creates maze with context factory. If nil, it uses a default context factory
func NewMaze(contextFactory func(w http.ResponseWriter, r *http.Request, filters []*Filter) IContext) *Maze {
	this := new(Maze)
	this.filters = make([]*Filter, 0)
	this.contextFactory = contextFactory
	return this
}

type Maze struct {
	filters        []*Filter
	contextFactory func(w http.ResponseWriter, r *http.Request, filters []*Filter) IContext
	lastRule       string
}

func (this *Maze) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if len(this.filters) > 0 {
		var ctx IContext
		if this.contextFactory == nil {
			// default
			ctx = NewContext(w, r, this.filters)
		} else {
			ctx = this.contextFactory(w, r, this.filters)
		}
		err := ctx.Proceed()
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
		}
	}
}

func (this *Maze) GET(rule string, filters ...Handler) {
	this.PushMethod([]string{"GET"}, rule, filters...)
}

func (this *Maze) POST(rule string, filters ...Handler) {
	this.PushMethod([]string{"POST"}, rule, filters...)
}

func (this *Maze) PUT(rule string, filters ...Handler) {
	this.PushMethod([]string{"PUT"}, rule, filters...)
}

func (this *Maze) DELETE(rule string, filters ...Handler) {
	this.PushMethod([]string{"DELETE"}, rule, filters...)
}

func (this *Maze) Push(rule string, filters ...Handler) {
	this.PushMethod(nil, rule, filters...)
}

// PushMethod adds the filters to the end of the last filters.
// If the rule does NOT start with '/' the applied rule will be
// the concatenation of the last rule that started with '/' and ended with a '*'
// with this one (the '*' is omitted).
// ex: /greet/* + sayHi/:Id = /greet/sayHi/:Id
func (this *Maze) PushMethod(methods []string, rule string, handlers ...Handler) {
	if strings.HasPrefix(rule, "/") {
		if strings.HasSuffix(rule, "*") {
			this.lastRule = rule[:len(rule)-1]
			logger.Tracef("Last main rule set as %s", this.lastRule)
		} else {
			// resets lastRule
			this.lastRule = ""
		}
	} else if !strings.HasPrefix(rule, "*") {
		if this.lastRule == "" {
			rule = "/" + rule
		} else {
			rule = this.lastRule + rule
		}
	}

	if len(handlers) > 0 {
		f := ConvertHandlers(handlers...)
		// rule is only set for the first filter
		if rule != "" {
			logger.Infof("registering rule %s", rule)
			f[0].rule = rule
			if i := strings.Index(rule, ":"); i != -1 {
				f[0].template = strings.Split(rule, "/")
			}
		}
		f[0].allowedMethods = methods

		this.filters = append(this.filters, f...)
	}
}

func (this *Maze) Add(filters ...*Filter) {
	this.filters = append(this.filters, filters...)
}

// Static serves static content.
// rule defines the rule and dir the relative path
func (this *Maze) Static(rule string, dir string) {
	// delivering static content and preventing malicious access
	var fs = web.OnlyFilesFS{http.Dir(dir)}
	var fileServer = http.FileServer(fs)
	this.GET(rule, func(ctx IContext) error {
		fileServer.ServeHTTP(ctx.GetResponse(), ctx.GetRequest())
		return nil
	})
}

func (this *Maze) ListenAndServe(addr string) error {
	mux := http.NewServeMux()
	mux.Handle("/", this)

	logger.Infof("Listening http at %s", addr)
	return http.ListenAndServe(addr, mux)
}

type IContext interface {
	Proceed() error
	GetResponse() http.ResponseWriter
	SetResponse(http.ResponseWriter)
	GetRequest() *http.Request
	GetAttribute(interface{}) interface{}
	SetAttribute(interface{}, interface{})
	CurrentFilter() *Filter

	// Payload put the json string in the request body into the struct passed as an interface{}
	Payload(interface{}) error
	// PathVars put the path parameters in a url into the struct passed as an interface{}
	PathVars(interface{}) error
	// QueryVars put the parameters in the query part of a url into the struct passed as an interface{}
	QueryVars(interface{}) error
	// Vars
	Vars(interface{}) error
	// Values gets a path parameter converter
	PathValues() Values
	// Values gets a parameter converter (path + query)
	Values() Values
	// Load calls Vars and Payload
	Load(value interface{}) error
	// TEXT converts to string the interface{} value and sends it into the response
	TEXT(interface{}) error
	// JSON marshals the interface{} value into a json string and sends it into the response
	JSON(interface{}) error
}

var _ IContext = &Context{}

type Context struct {
	Response   http.ResponseWriter
	Request    *http.Request
	Attributes map[interface{}]interface{} // attributes only valid in this request
	filters    []*Filter
	filterPos  int
	values     Values
	pathValues Values
}

func NewContext(w http.ResponseWriter, r *http.Request, filters []*Filter) *Context {
	var this = new(Context)
	this.Response = w
	this.Request = r
	this.filterPos = -1
	this.filters = filters
	this.Attributes = make(map[interface{}]interface{})
	return this
}

func (this *Context) nextFilter() *Filter {
	this.filterPos++
	if this.filterPos < len(this.filters) {
		return this.filters[this.filterPos]
	}
	// don't let ir go higher than the max
	this.filterPos = len(this.filters)

	return nil
}

// Proceed proceeds to the next valid rule
// This method should be reimplemented in specialized Context,
// extending this one
func (this *Context) Proceed() error {
	return this.Next(this)
}

func (this *Context) Next(c IContext) error {
	var next = this.nextFilter()
	if next != nil {
		if next.rule == "" {
			logger.Debugf("executing filter without rule")
			return next.handler(this)
		} else {
			// go to the next valid filter.
			// I don't use recursivity for this, because it can be very deep
			for i := this.filterPos; i < len(this.filters); i++ {
				var n = this.filters[i]
				if n.rule != "" && n.IsValid(c.GetRequest()) {
					this.filterPos = i
					logger.Debugf("executing filter %s", n.rule)
					return n.handler(this)
				}
			}
		}
	}

	return nil
}

func (this *Context) GetResponse() http.ResponseWriter {
	return this.Response
}

func (this *Context) SetResponse(w http.ResponseWriter) {
	this.Response = w
}

func (this *Context) GetRequest() *http.Request {
	return this.Request
}

func (this *Context) GetAttribute(key interface{}) interface{} {
	return this.Attributes[key]
}

func (this *Context) SetAttribute(key interface{}, value interface{}) {
	this.Attributes[key] = value
}

func (this *Context) CurrentFilter() *Filter {
	if this.filterPos < len(this.filters) {
		return this.filters[this.filterPos]
	}
	return nil
}

func (this *Context) Payload(value interface{}) error {
	if this.Request.Body != nil {
		payload, err := ioutil.ReadAll(this.Request.Body)
		if err != nil {
			return err
		}

		return json.Unmarshal(payload, value)
	}

	return nil
}

// PathVars put the path parameters in a url into the struct passed as an interface{}
func (this *Context) PathVars(value interface{}) error {
	var values = this.PathValues()
	if len(values) > 0 {
		return decoder.Decode(value, values)
	}

	return nil
}

// QueryVars put the parameters in the query part of a url into the struct passed as an interface{}
func (this *Context) QueryVars(value interface{}) error {
	var values = this.GetRequest().URL.Query()
	if len(values) > 0 {
		return decoder.Decode(value, values)
	}

	return nil
}

func (this *Context) Vars(value interface{}) error {
	var values = this.Values()
	if len(values) > 0 {
		return decoder.Decode(value, values)
	}

	return nil
}

func (this *Context) Load(value interface{}) error {
	if err := this.Vars(value); err != nil {
		return err
	}

	if err := this.Payload(value); err != nil {
		return err
	}

	return nil
}

func (this *Context) Values() Values {
	if this.values != nil {
		return this.values
	}

	this.values = make(Values)

	// path parameters
	for k, v := range this.PathValues() {
		this.values[k] = v
	}

	// query parameters
	var query = this.GetRequest().URL.Query()
	if query != nil {
		for k, v := range query {
			this.values[k] = v
		}
	}

	return this.values
}

func (this *Context) PathValues() Values {
	if this.pathValues != nil {
		return this.pathValues
	}

	this.pathValues = make(Values)
	var path = this.GetRequest().URL.Path
	var parts = strings.Split(path, "/")

	var template = this.CurrentFilter().template

	if len(parts) == len(template) {
		for k, v := range template {
			if strings.HasPrefix(v, ":") {
				this.pathValues[v[1:]] = []string{parts[k]}
			}
		}
	}

	return this.pathValues
}

func (this *Context) TEXT(value interface{}) error {
	var w = this.GetResponse()
	//w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	//w.Header().Set("Expires", "-1")
	if value != nil {
		var s = tk.ToString(value)
		if _, err := w.Write([]byte(s)); err != nil {
			return err
		}
	} else {
		w.WriteHeader(http.StatusOK)
	}

	return nil
}

func (this *Context) JSON(value interface{}) error {
	var w = this.GetResponse()
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.Header().Set("Expires", "-1")
	if value != nil {
		result, err := json.Marshal(value)
		if err != nil {
			return err
		}
		// writing sets status to OK
		_, err = w.Write(result)
		if err != nil {
			return err
		}
	} else {
		w.WriteHeader(http.StatusOK)
	}

	return nil
}

type Filterer interface {
	Handle(ctx IContext) error
}

type Handler func(IContext) error

type Filter struct {
	rule           string
	template       []string
	allowedMethods []string

	handler Handler
}

func NewFilter(rule string, handler Handler) *Filter {
	var this = new(Filter)
	this.rule = rule
	this.handler = handler
	return this
}

func (this *Filter) IsValid(request *http.Request) bool {
	// verify if method is allowed
	var allowed bool
	if this.allowedMethods == nil {
		allowed = true
	} else {
		var method = request.Method
		if method == "" {
			method = "GET"
		}
		for _, v := range this.allowedMethods {
			if method == v {
				allowed = true
				break
			}
		}
	}

	if allowed {
		var path = request.URL.Path
		if strings.HasPrefix(this.rule, "*") {
			return strings.HasSuffix(path, this.rule[1:])
		} else if strings.HasSuffix(this.rule, "*") {
			return strings.HasPrefix(path, this.rule[:len(this.rule)-1])
		} else if this.template != nil {
			return this.validate(path)
		} else {
			return path == this.rule
		}
	}

	return false
}

// validate checks if its a valid match with the url template
func (this *Filter) validate(path string) bool {
	var parts = strings.Split(path, "/")

	if len(parts) != len(this.template) {
		return false
	}

	for k, v := range this.template {
		if !strings.HasPrefix(v, ":") && v != parts[k] {
			return false
		}
	}

	return true
}

func ConvertHandlers(handlers ...Handler) []*Filter {
	var filters = make([]*Filter, len(handlers))
	for k, v := range handlers {
		filters[k] = &Filter{handler: v}
	}
	return filters
}
<|endoftext|>


<commit_before>package menu

import (
	gltext "github.com/4ydx/gltext"
	glfw "github.com/go-gl/glfw3"
	"github.com/go-gl/glow/gl-core/3.3/gl"
	"github.com/go-gl/mathgl/mgl32"
	"os"
)

type Point struct {
	X, Y float32
}

var vertexShaderSource string = `
#version 330

uniform mat4 matrix;

in vec4 position;

void main() {
  gl_Position = matrix * position;
}
` + "\x00"

var fragmentShaderSource string = `
#version 330

out vec4 fragment_color;

void main() {
  fragment_color = vec4(1,1,1,1);
}
` + "\x00"

type Menu struct {
	// options
	Visible      bool
	ShowOn       glfw.Key
	Height       float32
	Width        float32
	IsAutoCenter bool
	LowerLeft    Point

	// interactive objects
	Font          *gltext.Font
	Labels        []Label
	TextScaleRate float32 // increment during a scale operation

	// opengl oriented
	windowWidth   float32
	windowHeight  float32
	program       uint32 // shader program
	glMatrix      int32  // ortho matrix
	position      uint32 // index location
	vao           uint32
	vbo           uint32
	ebo           uint32
	ortho         mgl32.Mat4
	vboData       []float32
	vboIndexCount int
	eboData       []int32
	eboIndexCount int
}

func (menu *Menu) Toggle() {
	menu.Visible = !menu.Visible
}

func (menu *Menu) SetDimension(w, h float32) {
	menu.Width = w
	menu.Height = h
}

func (menu *Menu) Load(scale int32, lowerLeft Point) (err error) {
	glfloat_size := 4
	glint_size := 4

	menu.Visible = false
	menu.ShowOn = glfw.KeyM
	menu.LowerLeft = lowerLeft

	// load font
	fd, err := os.Open("font/luximr.ttf")
	if err != nil {
		return
	}
	defer fd.Close()

	menu.Font, err = gltext.LoadTruetype(fd, scale, 32, 127)
	if err != nil {
		return
	}

	// 2DO: make this time dependent rather than fps dependent
	menu.TextScaleRate = 0.01

	// create shader program and define attributes and uniforms
	menu.program, err = gltext.NewProgram(vertexShaderSource, fragmentShaderSource)
	if err != nil {
		return
	}
	menu.glMatrix = gl.GetUniformLocation(menu.program, gl.Str("matrix\x00"))
	menu.position = uint32(gl.GetAttribLocation(menu.program, gl.Str("position\x00")))

	gl.GenVertexArrays(1, &menu.vao)
	gl.GenBuffers(1, &menu.vbo)
	gl.GenBuffers(1, &menu.ebo)

	// vao
	gl.BindVertexArray(menu.vao)

	// vbo
	// specify the buffer for which the VertexAttribPointer calls apply
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)

	gl.EnableVertexAttribArray(menu.position)
	gl.VertexAttribPointer(
		menu.position,
		2,
		gl.FLOAT,
		false,
		0, // no stride... yet
		gl.PtrOffset(0),
	)

	// ebo
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)

	// i am guessing that order is important here
	gl.BindVertexArray(0)
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)

	// ebo, vbo data
	menu.vboIndexCount = 4 * 2 // four indices (2 points per index)
	menu.eboIndexCount = 6     // 6 triangle indices for a quad
	menu.vboData = make([]float32, menu.vboIndexCount, menu.vboIndexCount)
	menu.eboData = make([]int32, menu.eboIndexCount, menu.eboIndexCount)
	menu.makeBufferData()

	// setup context
	gl.BindVertexArray(menu.vao)
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)
	gl.BufferData(
		gl.ARRAY_BUFFER, glfloat_size*menu.vboIndexCount, gl.Ptr(menu.vboData), gl.DYNAMIC_DRAW)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)
	gl.BufferData(
		gl.ELEMENT_ARRAY_BUFFER, glint_size*menu.eboIndexCount, gl.Ptr(menu.eboData), gl.DYNAMIC_DRAW)
	gl.BindVertexArray(0)

	// not necesssary, but i just want to better understand using vertex arrays
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
	return nil
}

func (menu *Menu) ResizeWindow(width float32, height float32) {
	menu.windowWidth = width
	menu.windowHeight = height
	menu.Font.ResizeWindow(width, height)
	menu.ortho = mgl32.Ortho2D(0, menu.windowWidth, 0, menu.windowHeight)
}

func (menu *Menu) makeBufferData() {
	// index (0,0)
	menu.vboData[0] = menu.LowerLeft.X // position
	menu.vboData[1] = menu.LowerLeft.Y

	// index (1,0)
	menu.vboData[2] = menu.LowerLeft.X + menu.Width
	menu.vboData[3] = menu.LowerLeft.Y

	// index (1,1)
	menu.vboData[4] = menu.LowerLeft.X + menu.Width
	menu.vboData[5] = menu.LowerLeft.Y + menu.Height

	// index (0,1)
	menu.vboData[6] = menu.LowerLeft.X
	menu.vboData[7] = menu.LowerLeft.Y + menu.Height

	menu.eboData[0] = 0
	menu.eboData[1] = 1
	menu.eboData[2] = 2
	menu.eboData[3] = 0
	menu.eboData[4] = 2
	menu.eboData[5] = 3
}

func (menu *Menu) Release() {
	gl.DeleteBuffers(1, &menu.vbo)
	gl.DeleteBuffers(1, &menu.ebo)
	gl.DeleteBuffers(1, &menu.vao)
}

func (menu *Menu) Draw() bool {
	if !menu.Visible {
		return menu.Visible
	}
	gl.UseProgram(menu.program)

	gl.UniformMatrix4fv(menu.glMatrix, 1, false, &menu.ortho[0])

	gl.BindVertexArray(menu.vao)
	gl.DrawElements(gl.TRIANGLES, int32(menu.eboIndexCount), gl.UNSIGNED_INT, nil)
	gl.BindVertexArray(0)
	for i, label := range menu.Labels {
		if !label.IsHover {
			label.Text.AddScale(-menu.TextScaleRate)
			menu.Labels[i] = label
		}
		label.Text.Draw()
	}
	return menu.Visible
}

func (menu *Menu) ScreenClick(xPos, yPos float64) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.windowHeight) - yPos
	if xPos > float64(menu.LowerLeft.X) && xPos < float64(menu.LowerLeft.X+menu.Width) && yPos > float64(menu.LowerLeft.Y) && yPos < float64(menu.LowerLeft.Y+menu.Height) {
		for _, label := range menu.Labels {
			label.IsClicked(xPos, yPos)
		}
	}
}

func (menu *Menu) ScreenHover(xPos, yPos float64) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.windowHeight) - yPos
	if xPos > float64(menu.LowerLeft.X) && xPos < float64(menu.LowerLeft.X+menu.Width) && yPos > float64(menu.LowerLeft.Y) && yPos < float64(menu.LowerLeft.Y+menu.Height) {
		for i, label := range menu.Labels {
			label.IsHovered(xPos, yPos)
			menu.Labels[i] = label
		}
	}
}

func (menu *Menu) FindCenter() (lowerLeft Point) {
	windowWidthHalf := menu.windowWidth / 2
	windowHeightHalf := menu.windowHeight / 2

	menuWidthHalf := menu.Width / 2
	menuHeightHalf := menu.Height / 2

	lowerLeft.X = float32(windowWidthHalf) - menuWidthHalf
	lowerLeft.Y = float32(windowHeightHalf) - menuHeightHalf
	return
}
<commit_msg>Load menu api changes.  Trying to sort out what is most convenient.<commit_after>package menu

import (
	gltext "github.com/4ydx/gltext"
	glfw "github.com/go-gl/glfw3"
	"github.com/go-gl/glow/gl-core/3.3/gl"
	"github.com/go-gl/mathgl/mgl32"
	"os"
)

type Point struct {
	X, Y float32
}

var vertexShaderSource string = `
#version 330

uniform mat4 matrix;

in vec4 position;

void main() {
  gl_Position = matrix * position;
}
` + "\x00"

var fragmentShaderSource string = `
#version 330

out vec4 fragment_color;

void main() {
  fragment_color = vec4(1,1,1,1);
}
` + "\x00"

type Menu struct {
	// options
	Visible      bool
	ShowOn       glfw.Key
	Height       float32
	Width        float32
	IsAutoCenter bool
	LowerLeft    Point

	// interactive objects
	Font          *gltext.Font
	Labels        []Label
	TextScaleRate float32 // increment during a scale operation

	// opengl oriented
	windowWidth   float32
	windowHeight  float32
	program       uint32 // shader program
	glMatrix      int32  // ortho matrix
	position      uint32 // index location
	vao           uint32
	vbo           uint32
	ebo           uint32
	ortho         mgl32.Mat4
	vboData       []float32
	vboIndexCount int
	eboData       []int32
	eboIndexCount int
}

func (menu *Menu) AddLabel(str string, x, y float32) (label Label) {
	label.Text = gltext.LoadText(menu.Font)
	_, _ = label.Text.SetString(str)
	label.Text.SetScale(1)
	label.Text.SetPosition(0, 0)
	label.Text.SetColor(0, 0, 0, 1)
	menu.Labels = append(menu.Labels, label)
	return
}

func (menu *Menu) Toggle() {
	menu.Visible = !menu.Visible
}

func (menu *Menu) Load(lowerLeft Point, width float32, height float32, scale int32) (err error) {
	glfloat_size := 4
	glint_size := 4

	menu.Visible = false
	menu.ShowOn = glfw.KeyM
	menu.LowerLeft = lowerLeft
	menu.Width = width
	menu.Height = height

	// load font
	fd, err := os.Open("font/luximr.ttf")
	if err != nil {
		return
	}
	defer fd.Close()

	menu.Font, err = gltext.LoadTruetype(fd, scale, 32, 127)
	if err != nil {
		return
	}

	// 2DO: make this time dependent rather than fps dependent
	menu.TextScaleRate = 0.01

	// create shader program and define attributes and uniforms
	menu.program, err = gltext.NewProgram(vertexShaderSource, fragmentShaderSource)
	if err != nil {
		return
	}
	menu.glMatrix = gl.GetUniformLocation(menu.program, gl.Str("matrix\x00"))
	menu.position = uint32(gl.GetAttribLocation(menu.program, gl.Str("position\x00")))

	gl.GenVertexArrays(1, &menu.vao)
	gl.GenBuffers(1, &menu.vbo)
	gl.GenBuffers(1, &menu.ebo)

	// vao
	gl.BindVertexArray(menu.vao)

	// vbo
	// specify the buffer for which the VertexAttribPointer calls apply
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)

	gl.EnableVertexAttribArray(menu.position)
	gl.VertexAttribPointer(
		menu.position,
		2,
		gl.FLOAT,
		false,
		0, // no stride... yet
		gl.PtrOffset(0),
	)

	// ebo
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)

	// i am guessing that order is important here
	gl.BindVertexArray(0)
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)

	// ebo, vbo data
	menu.vboIndexCount = 4 * 2 // four indices (2 points per index)
	menu.eboIndexCount = 6     // 6 triangle indices for a quad
	menu.vboData = make([]float32, menu.vboIndexCount, menu.vboIndexCount)
	menu.eboData = make([]int32, menu.eboIndexCount, menu.eboIndexCount)
	menu.makeBufferData()

	// setup context
	gl.BindVertexArray(menu.vao)
	gl.BindBuffer(gl.ARRAY_BUFFER, menu.vbo)
	gl.BufferData(
		gl.ARRAY_BUFFER, glfloat_size*menu.vboIndexCount, gl.Ptr(menu.vboData), gl.DYNAMIC_DRAW)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, menu.ebo)
	gl.BufferData(
		gl.ELEMENT_ARRAY_BUFFER, glint_size*menu.eboIndexCount, gl.Ptr(menu.eboData), gl.DYNAMIC_DRAW)
	gl.BindVertexArray(0)

	// not necesssary, but i just want to better understand using vertex arrays
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
	return nil
}

func (menu *Menu) ResizeWindow(width float32, height float32) {
	menu.windowWidth = width
	menu.windowHeight = height
	menu.Font.ResizeWindow(width, height)
	menu.ortho = mgl32.Ortho2D(0, menu.windowWidth, 0, menu.windowHeight)
}

func (menu *Menu) makeBufferData() {
	// index (0,0)
	menu.vboData[0] = menu.LowerLeft.X // position
	menu.vboData[1] = menu.LowerLeft.Y

	// index (1,0)
	menu.vboData[2] = menu.LowerLeft.X + menu.Width
	menu.vboData[3] = menu.LowerLeft.Y

	// index (1,1)
	menu.vboData[4] = menu.LowerLeft.X + menu.Width
	menu.vboData[5] = menu.LowerLeft.Y + menu.Height

	// index (0,1)
	menu.vboData[6] = menu.LowerLeft.X
	menu.vboData[7] = menu.LowerLeft.Y + menu.Height

	menu.eboData[0] = 0
	menu.eboData[1] = 1
	menu.eboData[2] = 2
	menu.eboData[3] = 0
	menu.eboData[4] = 2
	menu.eboData[5] = 3
}

func (menu *Menu) Release() {
	gl.DeleteBuffers(1, &menu.vbo)
	gl.DeleteBuffers(1, &menu.ebo)
	gl.DeleteBuffers(1, &menu.vao)
}

func (menu *Menu) Draw() bool {
	if !menu.Visible {
		return menu.Visible
	}
	gl.UseProgram(menu.program)

	gl.UniformMatrix4fv(menu.glMatrix, 1, false, &menu.ortho[0])

	gl.BindVertexArray(menu.vao)
	gl.DrawElements(gl.TRIANGLES, int32(menu.eboIndexCount), gl.UNSIGNED_INT, nil)
	gl.BindVertexArray(0)
	for i, label := range menu.Labels {
		if !label.IsHover {
			label.Text.AddScale(-menu.TextScaleRate)
			menu.Labels[i] = label
		}
		label.Text.Draw()
	}
	return menu.Visible
}

func (menu *Menu) ScreenClick(xPos, yPos float64) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.windowHeight) - yPos
	if xPos > float64(menu.LowerLeft.X) && xPos < float64(menu.LowerLeft.X+menu.Width) && yPos > float64(menu.LowerLeft.Y) && yPos < float64(menu.LowerLeft.Y+menu.Height) {
		for _, label := range menu.Labels {
			label.IsClicked(xPos, yPos)
		}
	}
}

func (menu *Menu) ScreenHover(xPos, yPos float64) {
	if !menu.Visible {
		return
	}
	yPos = float64(menu.windowHeight) - yPos
	if xPos > float64(menu.LowerLeft.X) && xPos < float64(menu.LowerLeft.X+menu.Width) && yPos > float64(menu.LowerLeft.Y) && yPos < float64(menu.LowerLeft.Y+menu.Height) {
		for i, label := range menu.Labels {
			label.IsHovered(xPos, yPos)
			menu.Labels[i] = label
		}
	}
}

func (menu *Menu) FindCenter() (lowerLeft Point) {
	windowWidthHalf := menu.windowWidth / 2
	windowHeightHalf := menu.windowHeight / 2

	menuWidthHalf := menu.Width / 2
	menuHeightHalf := menu.Height / 2

	lowerLeft.X = float32(windowWidthHalf) - menuWidthHalf
	lowerLeft.Y = float32(windowHeightHalf) - menuHeightHalf
	return
}
<|endoftext|>


<commit_before>package nmea

import (
	"fmt"
	"strings"
)

const (
	// The token to indicate the start of a sentence.
	sentenceStart = "$"
	// The token to delimit fields of a sentence.
	fieldSep = ","
	// The token to delimit the checksum of a sentence.
	checksumSep = "*"
)

//SentenceI interface for all NMEA sentence
type SentenceI interface {
	GetSentence() Sentence
}

// Sentence contains the information about the NMEA sentence
type Sentence struct {
	Type     string   // The sentence type (e.g $GPGSA)
	Fields   []string // Array of fields
	Checksum string   // Checksum
	Raw      string   // The raw NMEA sentence received
}

// GetSentence getter
func (s Sentence) GetSentence() Sentence {
	return s
}

func (s *Sentence) parse(input string) error {
	s.Raw = input

	if strings.Count(s.Raw, checksumSep) != 1 {
		return fmt.Errorf("Sentence does not contain single checksum separator")
	}

	if !strings.Contains(s.Raw, sentenceStart) {
		return fmt.Errorf("Sentence does not contain a '$'")
	}

	// remove the $ character
	sentence := strings.Split(s.Raw, sentenceStart)[1]

	fieldSum := strings.Split(sentence, checksumSep)
	fields := strings.Split(fieldSum[0], fieldSep)
	s.Type = fields[0]
	s.Fields = fields[1:]
	s.Checksum = strings.ToUpper(fieldSum[1])

	if err := s.sumOk(); err != nil {
		return fmt.Errorf("Sentence checksum mismatch %s", err)
	}

	return nil
}

// SumOk returns whether the calculated checksum matches the message checksum.
func (s *Sentence) sumOk() error {
	var checksum uint8
	for i := 1; i < len(s.Raw) && string(s.Raw[i]) != checksumSep; i++ {
		checksum ^= s.Raw[i]
	}

	calculated := fmt.Sprintf("%X", checksum)
	if len(calculated) == 1 {
		calculated = "0" + calculated
	}
	if calculated != s.Checksum {
		return fmt.Errorf("[%s != %s]", calculated, s.Checksum)
	}
	return nil
}

// Parse parses the given string into the correct sentence type.
func Parse(s string) (SentenceI, error) {
	sentence := Sentence{}
	if err := sentence.parse(s); err != nil {
		return nil, err
	}

	if sentence.Type == PrefixGPRMC {
		gprmc := NewGPRMC(sentence)
		if err := gprmc.parse(); err != nil {
			return nil, err
		}
		return gprmc, nil
	} else if sentence.Type == PrefixGPGGA {
		gpgga := GPGGA{Sentence: sentence}
		if err := gpgga.parse(); err != nil {
			return nil, err
		}
		return gpgga, nil
	}

	err := fmt.Errorf("Sentence type '%s' not implemented", sentence.Type)
	return nil, err
}
<commit_msg>parser to use GPGGA constructor<commit_after>package nmea

import (
	"fmt"
	"strings"
)

const (
	// The token to indicate the start of a sentence.
	sentenceStart = "$"
	// The token to delimit fields of a sentence.
	fieldSep = ","
	// The token to delimit the checksum of a sentence.
	checksumSep = "*"
)

//SentenceI interface for all NMEA sentence
type SentenceI interface {
	GetSentence() Sentence
}

// Sentence contains the information about the NMEA sentence
type Sentence struct {
	Type     string   // The sentence type (e.g $GPGSA)
	Fields   []string // Array of fields
	Checksum string   // Checksum
	Raw      string   // The raw NMEA sentence received
}

// GetSentence getter
func (s Sentence) GetSentence() Sentence {
	return s
}

func (s *Sentence) parse(input string) error {
	s.Raw = input

	if strings.Count(s.Raw, checksumSep) != 1 {
		return fmt.Errorf("Sentence does not contain single checksum separator")
	}

	if !strings.Contains(s.Raw, sentenceStart) {
		return fmt.Errorf("Sentence does not contain a '$'")
	}

	// remove the $ character
	sentence := strings.Split(s.Raw, sentenceStart)[1]

	fieldSum := strings.Split(sentence, checksumSep)
	fields := strings.Split(fieldSum[0], fieldSep)
	s.Type = fields[0]
	s.Fields = fields[1:]
	s.Checksum = strings.ToUpper(fieldSum[1])

	if err := s.sumOk(); err != nil {
		return fmt.Errorf("Sentence checksum mismatch %s", err)
	}

	return nil
}

// SumOk returns whether the calculated checksum matches the message checksum.
func (s *Sentence) sumOk() error {
	var checksum uint8
	for i := 1; i < len(s.Raw) && string(s.Raw[i]) != checksumSep; i++ {
		checksum ^= s.Raw[i]
	}

	calculated := fmt.Sprintf("%X", checksum)
	if len(calculated) == 1 {
		calculated = "0" + calculated
	}
	if calculated != s.Checksum {
		return fmt.Errorf("[%s != %s]", calculated, s.Checksum)
	}
	return nil
}

// Parse parses the given string into the correct sentence type.
func Parse(s string) (SentenceI, error) {
	sentence := Sentence{}
	if err := sentence.parse(s); err != nil {
		return nil, err
	}

	if sentence.Type == PrefixGPRMC {
		gprmc := NewGPRMC(sentence)
		if err := gprmc.parse(); err != nil {
			return nil, err
		}
		return gprmc, nil
	} else if sentence.Type == PrefixGPGGA {
		gpgga := NewGPGGA(sentence)
		if err := gpgga.parse(); err != nil {
			return nil, err
		}
		return gpgga, nil
	}

	err := fmt.Errorf("Sentence type '%s' not implemented", sentence.Type)
	return nil, err
}
<|endoftext|>


<commit_before>package main

import (
	. "gopkg.in/check.v1"

	"bytes"
	gzipp "compress/gzip"
	"encoding/base64"
	gifp "image/gif"
	jpegp "image/jpeg"
	pngp "image/png"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"github.com/ahmetalpbalkan/go-httpbin"
	"github.com/barnacs/compy/proxy"
	tc "github.com/barnacs/compy/transcoder"
	"github.com/chai2010/webp"
	brotlidec "gopkg.in/kothar/brotli-go.v0/dec"
)

func Test(t *testing.T) {
	TestingT(t)
}

type CompyTest struct {
	client *http.Client
	server *httptest.Server
	proxy  *proxy.Proxy
}

var _ = Suite(&CompyTest{})

func (s *CompyTest) SetUpSuite(c *C) {
	s.server = httptest.NewServer(httpbin.GetMux())

	s.proxy = proxy.New("localhost"+*host, nil)
	s.proxy.AddTranscoder("image/gif", &tc.Gif{})
	s.proxy.AddTranscoder("image/jpeg", tc.NewJpeg(50))
	s.proxy.AddTranscoder("image/png", &tc.Png{})
	s.proxy.AddTranscoder("text/html", &tc.Zip{&tc.Identity{}, *brotli, *gzip, true})
	go func() {
		err := s.proxy.Start(*host)
		if err != nil {
			c.Fatal(err)
		}
	}()

	proxyUrl := &url.URL{Scheme: "http", Host: "localhost" + *host}
	tr := &http.Transport{
		DisableCompression: true,
		Proxy:              http.ProxyURL(proxyUrl),
	}
	s.client = &http.Client{Transport: tr}
}

func (s *CompyTest) TearDownSuite(c *C) {
	s.server.Close()

	// TODO: Go 1.8 will provide http.Server.Shutdown for proxy.Proxy
}

func (s *CompyTest) TestHttpBin(c *C) {
	resp, err := s.client.Get(s.server.URL + "/status/200")
	c.Assert(err, IsNil)
	c.Assert(resp.StatusCode, Equals, 200)
}

func (s *CompyTest) TestNoGzip(c *C) {
	resp, err := http.Get(s.server.URL + "/html")
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Encoding"), Equals, "")

	_, err = gzipp.NewReader(resp.Body)
	c.Assert(err, NotNil)
}

func (s *CompyTest) TestGzip(c *C) {
	req, err := http.NewRequest("GET", s.server.URL+"/html", nil)
	c.Assert(err, IsNil)
	req.Header.Add("Accept-Encoding", "gzip")

	resp, err := s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Encoding"), Equals, "gzip")

	gzr, err := gzipp.NewReader(resp.Body)
	c.Assert(err, IsNil)
	defer gzr.Close()
	_, err = ioutil.ReadAll(gzr)
	c.Assert(err, IsNil)
}

func (s *CompyTest) TestBrotli(c *C) {
	req, err := http.NewRequest("GET", s.server.URL+"/html", nil)
	c.Assert(err, IsNil)
	req.Header.Add("Accept-Encoding", "br, gzip")

	resp, err := s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Encoding"), Equals, "br")

	brr := brotlidec.NewBrotliReader(resp.Body)
	defer brr.Close()
	_, err = ioutil.ReadAll(brr)
	c.Assert(err, IsNil)
}

func (s *CompyTest) TestGif(c *C) {
	resp, err := http.Get(s.server.URL + "/image/gif")
	c.Assert(err, IsNil)
	uncompressedLength, err := new(bytes.Buffer).ReadFrom(resp.Body)
	c.Assert(err, IsNil)
	resp.Body.Close()

	resp, err = s.client.Get(s.server.URL + "/image/gif")
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/gif")

	_, err = gifp.Decode(resp.Body)
	c.Assert(err, IsNil)
	compressedLength := resp.ContentLength
	c.Assert(uncompressedLength > compressedLength, Equals, true)
}

func (s *CompyTest) TestGifToWebP(c *C) {
	req, err := http.NewRequest("GET", s.server.URL+"/image/gif", nil)
	c.Assert(err, IsNil)
	req.Header.Add("Accept", "image/webp")

	resp, err := s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/webp")

	_, err = webp.Decode(resp.Body)
	c.Assert(err, IsNil)
}

func (s *CompyTest) TestJpeg(c *C) {
	resp, err := s.client.Get(s.server.URL + "/image/jpeg")
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/jpeg")

	_, err = jpegp.Decode(resp.Body)
	c.Assert(err, IsNil)
}

func (s *CompyTest) TestJpegQuality(c *C) {
	req, err := http.NewRequest("GET", s.server.URL+"/image/jpeg", nil)
	c.Assert(err, IsNil)
	req.Header.Add("X-Compy-Quality", "10")

	resp, err := s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/jpeg")
	len10, err := new(bytes.Buffer).ReadFrom(resp.Body)
	c.Assert(err, IsNil)

	req, err = http.NewRequest("GET", s.server.URL+"/image/jpeg", nil)
	c.Assert(err, IsNil)
	req.Header.Add("X-Compy-Quality", "90")

	resp, err = s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/jpeg")
	len90, err := new(bytes.Buffer).ReadFrom(resp.Body)
	c.Assert(err, IsNil)

	c.Assert(len10 < len90, Equals, true)
}

func (s *CompyTest) TestJpegToWebP(c *C) {
	req, err := http.NewRequest("GET", s.server.URL+"/image/jpeg", nil)
	c.Assert(err, IsNil)
	req.Header.Add("Accept", "image/webp,image/jpeg")

	resp, err := s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/webp")

	_, err = webp.Decode(resp.Body)
	c.Assert(err, IsNil)
}

func (s *CompyTest) TestPng(c *C) {
	resp, err := s.client.Get(s.server.URL + "/image/png")
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/png")

	_, err = pngp.Decode(resp.Body)
	c.Assert(err, IsNil)
}
func (s *CompyTest) TestPngToWebP(c *C) {
	req, err := http.NewRequest("GET", s.server.URL+"/image/png", nil)
	c.Assert(err, IsNil)
	req.Header.Add("Accept", "image/webp")

	resp, err := s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/webp")

	_, err = webp.Decode(resp.Body)
	c.Assert(err, IsNil)
}

func (s *CompyTest) TestAuthentication(c *C) {
	s.proxy.SetAuthentication("user", "pass")
	defer s.proxy.SetAuthentication("", "")

	// no password
	resp, err := s.client.Get(s.server.URL + "/status/200")
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 407)

	// incorrect password
	req, err := http.NewRequest("GET", s.server.URL+"/status/200", nil)
	c.Assert(err, IsNil)
	req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("wrong:bad")))

	resp, err = s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 407)

	// correct password
	req, err = http.NewRequest("GET", s.server.URL+"/status/200", nil)
	c.Assert(err, IsNil)
	req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("user:pass")))

	resp, err = s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
}

func (s *CompyTest) TestAdmin(c *C) {
	resp, err := s.client.Get("http://localhost" + *host)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)

	resp, err = s.client.Get("http://localhost" + *host + "/cacert")
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 404)

	resp, err = s.client.Get("http://localhost" + *host + "/fake")
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 501)
}
<commit_msg>Provide correct default value to Proxy.New<commit_after>package main

import (
	. "gopkg.in/check.v1"

	"bytes"
	gzipp "compress/gzip"
	"encoding/base64"
	gifp "image/gif"
	jpegp "image/jpeg"
	pngp "image/png"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"github.com/ahmetalpbalkan/go-httpbin"
	"github.com/barnacs/compy/proxy"
	tc "github.com/barnacs/compy/transcoder"
	"github.com/chai2010/webp"
	brotlidec "gopkg.in/kothar/brotli-go.v0/dec"
)

func Test(t *testing.T) {
	TestingT(t)
}

type CompyTest struct {
	client *http.Client
	server *httptest.Server
	proxy  *proxy.Proxy
}

var _ = Suite(&CompyTest{})

func (s *CompyTest) SetUpSuite(c *C) {
	s.server = httptest.NewServer(httpbin.GetMux())

	s.proxy = proxy.New("localhost"+*host, "")
	s.proxy.AddTranscoder("image/gif", &tc.Gif{})
	s.proxy.AddTranscoder("image/jpeg", tc.NewJpeg(50))
	s.proxy.AddTranscoder("image/png", &tc.Png{})
	s.proxy.AddTranscoder("text/html", &tc.Zip{&tc.Identity{}, *brotli, *gzip, true})
	go func() {
		err := s.proxy.Start(*host)
		if err != nil {
			c.Fatal(err)
		}
	}()

	proxyUrl := &url.URL{Scheme: "http", Host: "localhost" + *host}
	tr := &http.Transport{
		DisableCompression: true,
		Proxy:              http.ProxyURL(proxyUrl),
	}
	s.client = &http.Client{Transport: tr}
}

func (s *CompyTest) TearDownSuite(c *C) {
	s.server.Close()

	// TODO: Go 1.8 will provide http.Server.Shutdown for proxy.Proxy
}

func (s *CompyTest) TestHttpBin(c *C) {
	resp, err := s.client.Get(s.server.URL + "/status/200")
	c.Assert(err, IsNil)
	c.Assert(resp.StatusCode, Equals, 200)
}

func (s *CompyTest) TestNoGzip(c *C) {
	resp, err := http.Get(s.server.URL + "/html")
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Encoding"), Equals, "")

	_, err = gzipp.NewReader(resp.Body)
	c.Assert(err, NotNil)
}

func (s *CompyTest) TestGzip(c *C) {
	req, err := http.NewRequest("GET", s.server.URL+"/html", nil)
	c.Assert(err, IsNil)
	req.Header.Add("Accept-Encoding", "gzip")

	resp, err := s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Encoding"), Equals, "gzip")

	gzr, err := gzipp.NewReader(resp.Body)
	c.Assert(err, IsNil)
	defer gzr.Close()
	_, err = ioutil.ReadAll(gzr)
	c.Assert(err, IsNil)
}

func (s *CompyTest) TestBrotli(c *C) {
	req, err := http.NewRequest("GET", s.server.URL+"/html", nil)
	c.Assert(err, IsNil)
	req.Header.Add("Accept-Encoding", "br, gzip")

	resp, err := s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Encoding"), Equals, "br")

	brr := brotlidec.NewBrotliReader(resp.Body)
	defer brr.Close()
	_, err = ioutil.ReadAll(brr)
	c.Assert(err, IsNil)
}

func (s *CompyTest) TestGif(c *C) {
	resp, err := http.Get(s.server.URL + "/image/gif")
	c.Assert(err, IsNil)
	uncompressedLength, err := new(bytes.Buffer).ReadFrom(resp.Body)
	c.Assert(err, IsNil)
	resp.Body.Close()

	resp, err = s.client.Get(s.server.URL + "/image/gif")
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/gif")

	_, err = gifp.Decode(resp.Body)
	c.Assert(err, IsNil)
	compressedLength := resp.ContentLength
	c.Assert(uncompressedLength > compressedLength, Equals, true)
}

func (s *CompyTest) TestGifToWebP(c *C) {
	req, err := http.NewRequest("GET", s.server.URL+"/image/gif", nil)
	c.Assert(err, IsNil)
	req.Header.Add("Accept", "image/webp")

	resp, err := s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/webp")

	_, err = webp.Decode(resp.Body)
	c.Assert(err, IsNil)
}

func (s *CompyTest) TestJpeg(c *C) {
	resp, err := s.client.Get(s.server.URL + "/image/jpeg")
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/jpeg")

	_, err = jpegp.Decode(resp.Body)
	c.Assert(err, IsNil)
}

func (s *CompyTest) TestJpegQuality(c *C) {
	req, err := http.NewRequest("GET", s.server.URL+"/image/jpeg", nil)
	c.Assert(err, IsNil)
	req.Header.Add("X-Compy-Quality", "10")

	resp, err := s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/jpeg")
	len10, err := new(bytes.Buffer).ReadFrom(resp.Body)
	c.Assert(err, IsNil)

	req, err = http.NewRequest("GET", s.server.URL+"/image/jpeg", nil)
	c.Assert(err, IsNil)
	req.Header.Add("X-Compy-Quality", "90")

	resp, err = s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/jpeg")
	len90, err := new(bytes.Buffer).ReadFrom(resp.Body)
	c.Assert(err, IsNil)

	c.Assert(len10 < len90, Equals, true)
}

func (s *CompyTest) TestJpegToWebP(c *C) {
	req, err := http.NewRequest("GET", s.server.URL+"/image/jpeg", nil)
	c.Assert(err, IsNil)
	req.Header.Add("Accept", "image/webp,image/jpeg")

	resp, err := s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/webp")

	_, err = webp.Decode(resp.Body)
	c.Assert(err, IsNil)
}

func (s *CompyTest) TestPng(c *C) {
	resp, err := s.client.Get(s.server.URL + "/image/png")
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/png")

	_, err = pngp.Decode(resp.Body)
	c.Assert(err, IsNil)
}
func (s *CompyTest) TestPngToWebP(c *C) {
	req, err := http.NewRequest("GET", s.server.URL+"/image/png", nil)
	c.Assert(err, IsNil)
	req.Header.Add("Accept", "image/webp")

	resp, err := s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
	c.Assert(resp.Header.Get("Content-Type"), Equals, "image/webp")

	_, err = webp.Decode(resp.Body)
	c.Assert(err, IsNil)
}

func (s *CompyTest) TestAuthentication(c *C) {
	s.proxy.SetAuthentication("user", "pass")
	defer s.proxy.SetAuthentication("", "")

	// no password
	resp, err := s.client.Get(s.server.URL + "/status/200")
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 407)

	// incorrect password
	req, err := http.NewRequest("GET", s.server.URL+"/status/200", nil)
	c.Assert(err, IsNil)
	req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("wrong:bad")))

	resp, err = s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 407)

	// correct password
	req, err = http.NewRequest("GET", s.server.URL+"/status/200", nil)
	c.Assert(err, IsNil)
	req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("user:pass")))

	resp, err = s.client.Do(req)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)
}

func (s *CompyTest) TestAdmin(c *C) {
	resp, err := s.client.Get("http://localhost" + *host)
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 200)

	resp, err = s.client.Get("http://localhost" + *host + "/cacert")
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 404)

	resp, err = s.client.Get("http://localhost" + *host + "/fake")
	c.Assert(err, IsNil)
	defer resp.Body.Close()
	c.Assert(resp.StatusCode, Equals, 501)
}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"os"
	"sort"
	"text/tabwriter"

	"github.com/bgentry/heroku-go"
)

var cmdOrgs = &Command{
	Run:      runOrgs,
	Usage:    "orgs",
	Category: "orgs",
	Short:    "list Heroku orgs",
	Long:     "Lists Heroku organizations that the user belongs to.",
}

func runOrgs(cmd *Command, args []string) {
	w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
	defer w.Flush()

	orgs, err := client.OrganizationList(nil)
	must(err)
	printOrgsList(w, orgs)
}

func printOrgsList(w io.Writer, orgs []heroku.Organization) {
	sort.Sort(orgsByName(orgs))
	for _, org := range orgs {
		listRec(w,
			org.Name,
			org.Role,
		)
	}
}

type orgsByName []heroku.Organization

func (o orgsByName) Len() int           { return len(o) }
func (o orgsByName) Swap(i, j int)      { o[i], o[j] = o[j], o[i] }
func (o orgsByName) Less(i, j int) bool { return o[i].Name < o[j].Name }

// Returns true if the app is in an org, and false otherwise.
func isAppInOrg(app *heroku.OrganizationApp) bool {
	return app.Organization != nil
}

// This function uses must(err), so the program will exit if an error is
// encountered.
func mustGetOrgApp(appname string) *heroku.OrganizationApp {
	app, err := client.OrganizationAppInfo(appname)
	must(err)
	return app
}
<commit_msg>don't need to sort orgs manually anymore, the API will do it<commit_after>package main

import (
	"io"
	"os"
	"text/tabwriter"

	"github.com/bgentry/heroku-go"
)

var cmdOrgs = &Command{
	Run:      runOrgs,
	Usage:    "orgs",
	Category: "orgs",
	Short:    "list Heroku orgs",
	Long:     "Lists Heroku organizations that the user belongs to.",
}

func runOrgs(cmd *Command, args []string) {
	w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
	defer w.Flush()

	orgs, err := client.OrganizationList(&heroku.ListRange{Field: "name"})
	must(err)
	printOrgsList(w, orgs)
}

func printOrgsList(w io.Writer, orgs []heroku.Organization) {
	for _, org := range orgs {
		listRec(w,
			org.Name,
			org.Role,
		)
	}
}

// Returns true if the app is in an org, and false otherwise.
func isAppInOrg(app *heroku.OrganizationApp) bool {
	return app.Organization != nil
}

// This function uses must(err), so the program will exit if an error is
// encountered.
func mustGetOrgApp(appname string) *heroku.OrganizationApp {
	app, err := client.OrganizationAppInfo(appname)
	must(err)
	return app
}
<|endoftext|>


<commit_before>// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package clientutil implements common utilities shared by clients and those
// who act as clients, such as a DirServer being a client of a StoreServer.
package clientutil // import "upspin.io/client/clientutil"

import (
	"upspin.io/access"
	"upspin.io/bind"
	"upspin.io/errors"
	"upspin.io/pack"
	"upspin.io/path"
	"upspin.io/upspin"
)

// ReadAll reads the entire contents of a DirEntry. The reader must have
// the necessary keys loaded in the config to unpack the cipher if the entry
// is encrypted.
func ReadAll(cfg upspin.Config, entry *upspin.DirEntry) ([]byte, error) {
	const op = "client/clientutil.ReadAll"

	if entry.IsLink() {
		return nil, errors.E(op, entry.Name, errors.Invalid, errors.Str("can't read a link entry"))
	}
	if entry.IsIncomplete() {
		return nil, errors.E(op, entry.Name, errors.Permission)
	}
	if access.IsAccessFile(entry.Name) {
		// Access files must be written by their owners only.
		p, _ := path.Parse(entry.Name)
		if p.User() != entry.Writer {
			return nil, errors.E(errors.Invalid, p.User(), errors.Str("writer of Access file does not match owner"))
		}
	}

	var data []byte
	packer := pack.Lookup(entry.Packing)
	if packer == nil {
		return nil, errors.E(op, entry.Name, errors.Errorf("unrecognized Packing %d", entry.Packing))
	}
	bu, err := packer.Unpack(cfg, entry)
	if err != nil {
		return nil, errors.E(op, entry.Name, err) // Showstopper.
	}
	for {
		block, ok := bu.NextBlock()
		if !ok {
			break // EOF
		}
		// block is known valid as per valid.DirEntry above.

		cipher, err := ReadLocation(cfg, block.Location)
		if err != nil {
			return nil, errors.E(op, err)
		}
		clear, err := bu.Unpack(cipher)
		if err != nil {
			return nil, errors.E(op, entry.Name, err)
		}
		data = append(data, clear...) // TODO: Could avoid a copy if only one block.
	}
	return data, nil
}

// ReadLocation uses the provided Config to fetch the contents of the given
// Location, following any StoreServer.Get redirects.
func ReadLocation(cfg upspin.Config, loc upspin.Location) ([]byte, error) {
	const op = "client/clientutil.ReadLocation"

	// firstError remembers the first error we saw.
	// If we fail completely we return it.
	var firstError error
	// isError reports whether err is non-nil and remembers it if it is.
	isError := func(err error) bool {
		if err == nil {
			return false
		}
		if firstError == nil {
			firstError = err
		}
		return true
	}

	// knownLocs stores the known Locations for this block. Value is
	// ignored.
	knownLocs := make(map[upspin.Location]bool)
	// Get the data for this block.
	// where is the list of locations to examine. It is updated in the loop.
	where := []upspin.Location{loc}
	for i := 0; i < len(where); i++ { // Not range loop - where changes as we run.
		loc := where[i]
		store, err := bind.StoreServer(cfg, loc.Endpoint)
		if isError(err) {
			continue
		}
		data, _, locs, err := store.Get(loc.Reference)
		if isError(err) {
			continue // locs guaranteed to be nil.
		}
		if locs == nil && err == nil {
			return data, nil
		}
		// Add new locs to the list. Skip ones already there - they've been processed.
		for _, newLoc := range locs {
			if _, found := knownLocs[newLoc]; !found {
				where = append(where, newLoc)
				knownLocs[newLoc] = true
			}
		}
	}

	// If we arrive here, we have failed to find a block.
	if firstError != nil {
		return nil, errors.E(op, firstError)
	}
	return nil, errors.E(op, errors.IO, errors.Errorf("data for location %v not found on any store server", loc))
}
<commit_msg>client/clientutil: use SignedName when validating Access file owner<commit_after>// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package clientutil implements common utilities shared by clients and those
// who act as clients, such as a DirServer being a client of a StoreServer.
package clientutil // import "upspin.io/client/clientutil"

import (
	"upspin.io/access"
	"upspin.io/bind"
	"upspin.io/errors"
	"upspin.io/pack"
	"upspin.io/path"
	"upspin.io/upspin"
)

// ReadAll reads the entire contents of a DirEntry. The reader must have
// the necessary keys loaded in the config to unpack the cipher if the entry
// is encrypted.
func ReadAll(cfg upspin.Config, entry *upspin.DirEntry) ([]byte, error) {
	const op = "client/clientutil.ReadAll"

	if entry.IsLink() {
		return nil, errors.E(op, entry.Name, errors.Invalid, errors.Str("can't read a link entry"))
	}
	if entry.IsIncomplete() {
		return nil, errors.E(op, entry.Name, errors.Permission)
	}
	if access.IsAccessFile(entry.SignedName) {
		// Access files must be written by their owners only.
		p, _ := path.Parse(entry.SignedName)
		if p.User() != entry.Writer {
			return nil, errors.E(errors.Invalid, p.User(), errors.Str("writer of Access file does not match owner"))
		}
	}

	var data []byte
	packer := pack.Lookup(entry.Packing)
	if packer == nil {
		return nil, errors.E(op, entry.Name, errors.Errorf("unrecognized Packing %d", entry.Packing))
	}
	bu, err := packer.Unpack(cfg, entry)
	if err != nil {
		return nil, errors.E(op, entry.Name, err) // Showstopper.
	}
	for {
		block, ok := bu.NextBlock()
		if !ok {
			break // EOF
		}
		// block is known valid as per valid.DirEntry above.

		cipher, err := ReadLocation(cfg, block.Location)
		if err != nil {
			return nil, errors.E(op, err)
		}
		clear, err := bu.Unpack(cipher)
		if err != nil {
			return nil, errors.E(op, entry.Name, err)
		}
		data = append(data, clear...) // TODO: Could avoid a copy if only one block.
	}
	return data, nil
}

// ReadLocation uses the provided Config to fetch the contents of the given
// Location, following any StoreServer.Get redirects.
func ReadLocation(cfg upspin.Config, loc upspin.Location) ([]byte, error) {
	const op = "client/clientutil.ReadLocation"

	// firstError remembers the first error we saw.
	// If we fail completely we return it.
	var firstError error
	// isError reports whether err is non-nil and remembers it if it is.
	isError := func(err error) bool {
		if err == nil {
			return false
		}
		if firstError == nil {
			firstError = err
		}
		return true
	}

	// knownLocs stores the known Locations for this block. Value is
	// ignored.
	knownLocs := make(map[upspin.Location]bool)
	// Get the data for this block.
	// where is the list of locations to examine. It is updated in the loop.
	where := []upspin.Location{loc}
	for i := 0; i < len(where); i++ { // Not range loop - where changes as we run.
		loc := where[i]
		store, err := bind.StoreServer(cfg, loc.Endpoint)
		if isError(err) {
			continue
		}
		data, _, locs, err := store.Get(loc.Reference)
		if isError(err) {
			continue // locs guaranteed to be nil.
		}
		if locs == nil && err == nil {
			return data, nil
		}
		// Add new locs to the list. Skip ones already there - they've been processed.
		for _, newLoc := range locs {
			if _, found := knownLocs[newLoc]; !found {
				where = append(where, newLoc)
				knownLocs[newLoc] = true
			}
		}
	}

	// If we arrive here, we have failed to find a block.
	if firstError != nil {
		return nil, errors.E(op, firstError)
	}
	return nil, errors.E(op, errors.IO, errors.Errorf("data for location %v not found on any store server", loc))
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package outboundmiddleware

import (
	"bytes"
	"context"
	"errors"
	"io/ioutil"
	"testing"

	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
	"go.uber.org/yarpc/api/middleware"
	"go.uber.org/yarpc/api/transport"
	"go.uber.org/yarpc/api/transport/transporttest"
	"go.uber.org/yarpc/internal/testtime"
)

type countOutboundMiddleware struct{ Count int }

func (c *countOutboundMiddleware) Call(
	ctx context.Context, req *transport.Request, o transport.UnaryOutbound) (*transport.Response, error) {
	c.Count++
	return o.Call(ctx, req)
}

func (c *countOutboundMiddleware) CallOneway(ctx context.Context, req *transport.Request, o transport.OnewayOutbound) (transport.Ack, error) {
	c.Count++
	return o.CallOneway(ctx, req)
}

var retryUnaryOutbound middleware.UnaryOutboundFunc = func(
	ctx context.Context, req *transport.Request, o transport.UnaryOutbound) (*transport.Response, error) {
	res, err := o.Call(ctx, req)
	if err != nil {
		res, err = o.Call(ctx, req)
	}
	return res, err
}

func TestUnaryChain(t *testing.T) {
	before := &countOutboundMiddleware{}
	after := &countOutboundMiddleware{}

	tests := []struct {
		desc string
		mw   middleware.UnaryOutbound
	}{
		{"flat chain", UnaryChain(before, retryUnaryOutbound, nil, after)},
		{"nested chain", UnaryChain(before, UnaryChain(retryUnaryOutbound, after, nil))},
	}

	for _, tt := range tests {
		t.Run(tt.desc, func(t *testing.T) {
			before.Count, after.Count = 0, 0
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()
			ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
			defer cancel()

			req := &transport.Request{
				Caller:    "somecaller",
				Service:   "someservice",
				Encoding:  transport.Encoding("raw"),
				Procedure: "hello",
				Body:      bytes.NewReader([]byte{1, 2, 3}),
			}
			res := &transport.Response{
				Body: ioutil.NopCloser(bytes.NewReader([]byte{4, 5, 6})),
			}
			o := transporttest.NewMockUnaryOutbound(mockCtrl)
			o.EXPECT().Call(ctx, req).After(
				o.EXPECT().Call(ctx, req).Return(nil, errors.New("great sadness")),
			).Return(res, nil)

			gotRes, err := middleware.ApplyUnaryOutbound(o, tt.mw).Call(ctx, req)

			assert.NoError(t, err, "expected success")
			assert.Equal(t, 1, before.Count, "expected outer middleware to be called once")
			assert.Equal(t, 2, after.Count, "expected inner middleware to be called twice")
			assert.Equal(t, res, gotRes, "expected response to match")
		})
	}
}

var retryOnewayOutbound middleware.OnewayOutboundFunc = func(
	ctx context.Context, req *transport.Request, o transport.OnewayOutbound) (transport.Ack, error) {
	res, err := o.CallOneway(ctx, req)
	if err != nil {
		res, err = o.CallOneway(ctx, req)
	}
	return res, err
}

func TestOnewayChain(t *testing.T) {
	before := &countOutboundMiddleware{}
	after := &countOutboundMiddleware{}

	tests := []struct {
		desc string
		mw   middleware.OnewayOutbound
	}{
		{"flat chain", OnewayChain(before, retryOnewayOutbound, nil, after)},
		{"flat chain", OnewayChain(before, OnewayChain(retryOnewayOutbound, after, nil))},
	}

	for _, tt := range tests {
		t.Run(tt.desc, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()
			ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
			defer cancel()

			var res transport.Ack
			req := &transport.Request{
				Caller:    "somecaller",
				Service:   "someservice",
				Encoding:  transport.Encoding("raw"),
				Procedure: "hello",
				Body:      bytes.NewReader([]byte{1, 2, 3}),
			}
			o := transporttest.NewMockOnewayOutbound(mockCtrl)
			before.Count, after.Count = 0, 0
			o.EXPECT().CallOneway(ctx, req).After(
				o.EXPECT().CallOneway(ctx, req).Return(nil, errors.New("great sadness")),
			).Return(res, nil)

			gotRes, err := middleware.ApplyOnewayOutbound(o, tt.mw).CallOneway(ctx, req)

			assert.NoError(t, err, "expected success")
			assert.Equal(t, 1, before.Count, "expected outer middleware to be called once")
			assert.Equal(t, 2, after.Count, "expected inner middleware to be called twice")
			assert.Equal(t, res, gotRes, "expected response to match")
		})
	}
}
<commit_msg>Increase outboundmiddleware chain test coverage (#1383)<commit_after>// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package outboundmiddleware

import (
	"bytes"
	"context"
	"errors"
	"io/ioutil"
	"testing"

	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/yarpc/api/middleware"
	"go.uber.org/yarpc/api/middleware/middlewaretest"
	"go.uber.org/yarpc/api/transport"
	"go.uber.org/yarpc/api/transport/transporttest"
	"go.uber.org/yarpc/internal/introspection"
	"go.uber.org/yarpc/internal/testtime"
)

type countOutboundMiddleware struct{ Count int }

func (c *countOutboundMiddleware) Call(
	ctx context.Context, req *transport.Request, o transport.UnaryOutbound) (*transport.Response, error) {
	c.Count++
	return o.Call(ctx, req)
}

func (c *countOutboundMiddleware) CallOneway(ctx context.Context, req *transport.Request, o transport.OnewayOutbound) (transport.Ack, error) {
	c.Count++
	return o.CallOneway(ctx, req)
}

var retryUnaryOutbound middleware.UnaryOutboundFunc = func(
	ctx context.Context, req *transport.Request, o transport.UnaryOutbound) (*transport.Response, error) {
	res, err := o.Call(ctx, req)
	if err != nil {
		res, err = o.Call(ctx, req)
	}
	return res, err
}

func TestUnaryChain(t *testing.T) {
	before := &countOutboundMiddleware{}
	after := &countOutboundMiddleware{}

	tests := []struct {
		desc string
		mw   middleware.UnaryOutbound
	}{
		{"flat chain", UnaryChain(before, retryUnaryOutbound, nil, after)},
		{"nested chain", UnaryChain(before, UnaryChain(retryUnaryOutbound, after, nil))},
	}

	for _, tt := range tests {
		t.Run(tt.desc, func(t *testing.T) {
			before.Count, after.Count = 0, 0
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()
			ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
			defer cancel()

			req := &transport.Request{
				Caller:    "somecaller",
				Service:   "someservice",
				Encoding:  transport.Encoding("raw"),
				Procedure: "hello",
				Body:      bytes.NewReader([]byte{1, 2, 3}),
			}
			res := &transport.Response{
				Body: ioutil.NopCloser(bytes.NewReader([]byte{4, 5, 6})),
			}
			o := transporttest.NewMockUnaryOutbound(mockCtrl)
			o.EXPECT().Call(ctx, req).After(
				o.EXPECT().Call(ctx, req).Return(nil, errors.New("great sadness")),
			).Return(res, nil)

			gotRes, err := middleware.ApplyUnaryOutbound(o, tt.mw).Call(ctx, req)

			assert.NoError(t, err, "expected success")
			assert.Equal(t, 1, before.Count, "expected outer middleware to be called once")
			assert.Equal(t, 2, after.Count, "expected inner middleware to be called twice")
			assert.Equal(t, res, gotRes, "expected response to match")
		})
	}
}

var retryOnewayOutbound middleware.OnewayOutboundFunc = func(
	ctx context.Context, req *transport.Request, o transport.OnewayOutbound) (transport.Ack, error) {
	res, err := o.CallOneway(ctx, req)
	if err != nil {
		res, err = o.CallOneway(ctx, req)
	}
	return res, err
}

func TestOnewayChain(t *testing.T) {
	before := &countOutboundMiddleware{}
	after := &countOutboundMiddleware{}

	tests := []struct {
		desc string
		mw   middleware.OnewayOutbound
	}{
		{"flat chain", OnewayChain(before, retryOnewayOutbound, nil, after)},
		{"flat chain", OnewayChain(before, OnewayChain(retryOnewayOutbound, after, nil))},
	}

	for _, tt := range tests {
		t.Run(tt.desc, func(t *testing.T) {
			mockCtrl := gomock.NewController(t)
			defer mockCtrl.Finish()
			ctx, cancel := context.WithTimeout(context.Background(), testtime.Second)
			defer cancel()

			var res transport.Ack
			req := &transport.Request{
				Caller:    "somecaller",
				Service:   "someservice",
				Encoding:  transport.Encoding("raw"),
				Procedure: "hello",
				Body:      bytes.NewReader([]byte{1, 2, 3}),
			}
			o := transporttest.NewMockOnewayOutbound(mockCtrl)
			before.Count, after.Count = 0, 0
			o.EXPECT().CallOneway(ctx, req).After(
				o.EXPECT().CallOneway(ctx, req).Return(nil, errors.New("great sadness")),
			).Return(res, nil)

			gotRes, err := middleware.ApplyOnewayOutbound(o, tt.mw).CallOneway(ctx, req)

			assert.NoError(t, err, "expected success")
			assert.Equal(t, 1, before.Count, "expected outer middleware to be called once")
			assert.Equal(t, 2, after.Count, "expected inner middleware to be called twice")
			assert.Equal(t, res, gotRes, "expected response to match")
		})
	}
}

func TestEmptyChain(t *testing.T) {
	errMsg := "expected nop Outbound"

	t.Run("unary", func(t *testing.T) {
		require.Equal(t, middleware.NopUnaryOutbound, UnaryChain(), errMsg)
	})

	t.Run("oneway", func(t *testing.T) {
		require.Equal(t, middleware.NopOnewayOutbound, OnewayChain(), errMsg)
	})
}

func TestSingleOutboundChain(t *testing.T) {
	ctrl := gomock.NewController(t)

	t.Run("unary", func(t *testing.T) {
		out := middlewaretest.NewMockUnaryOutbound(ctrl)
		require.Equal(t, out, UnaryChain(out))
	})

	t.Run("oneway", func(t *testing.T) {
		out := middlewaretest.NewMockOnewayOutbound(ctrl)
		require.Equal(t, out, OnewayChain(out))
	})
}

func TestUnaryChainExec(t *testing.T) {
	ctrl := gomock.NewController(t)
	out := transporttest.NewMockUnaryOutbound(ctrl)

	chain := &unaryChainExec{Final: out}

	// start
	out.EXPECT().Start().Return(nil)
	assert.NoError(t, chain.Start(), "could not start outbound")

	// transports
	out.EXPECT().Transports()
	chain.Transports()

	// is running
	out.EXPECT().IsRunning().Return(true)
	assert.True(t, chain.IsRunning(), "expected outbound to be running")

	// stop
	out.EXPECT().Stop().Return(nil)
	assert.NoError(t, chain.Stop(), "unexpected error stopping outbound")
}

func TestOnewayChainExec(t *testing.T) {
	ctrl := gomock.NewController(t)
	out := transporttest.NewMockOnewayOutbound(ctrl)

	chain := &onewayChainExec{Final: out}

	// start
	out.EXPECT().Start().Return(nil)
	assert.NoError(t, chain.Start(), "could not start outbound")

	// transports
	out.EXPECT().Transports()
	chain.Transports()

	// is running
	out.EXPECT().IsRunning().Return(true)
	assert.True(t, chain.IsRunning(), "expected outbound to be running")

	// stop
	out.EXPECT().Stop().Return(nil)
	assert.NoError(t, chain.Stop(), "unexpected error stopping outbound")
}

func TestIntrospect(t *testing.T) {
	ctrl := gomock.NewController(t)
	expectStatus := introspection.OutboundStatusNotSupported
	errMsg := "expected not supported status"

	t.Run("unary", func(t *testing.T) {
		out := transporttest.NewMockUnaryOutbound(ctrl)
		chain := &unaryChainExec{Final: out}
		assert.Equal(t, expectStatus, chain.Introspect(), errMsg)
	})

	t.Run("oneway", func(t *testing.T) {
		out := transporttest.NewMockOnewayOutbound(ctrl)
		chain := &onewayChainExec{Final: out}
		assert.Equal(t, expectStatus, chain.Introspect(), errMsg)
	})
}
<|endoftext|>