<commit_before>package holux

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"strconv"
	"strings"
)

const CMDFMT = "$%s*%02X\r\n"

// Conn manages the line-based and binary transmission with any
// io.Reader. In particular, that Reader can be a serial connection
// with the GPS tracker.
type Conn struct {
	rw    io.ReadWriter
	lines *bufio.Scanner
}

func NewConn(rw io.ReadWriter) Conn {
	lines := bufio.NewScanner(rw)
	lines.Split(split)
	return Conn{rw, lines}
}

// TODO: These two should take errors.
func (c Conn) Send(cmd string) {
	cs := foldXOR([]byte(cmd))
	fmt.Fprintf(c.rw, CMDFMT, cmd, cs)
}

func (c Conn) Sendf(format string, a ...interface{}) {
	c.Send(fmt.Sprintf(format, a...))
}

// ReadReply reads a message from the tracker and validates that
// the prefix matches.
// TODO: There's no string reply reading yet. Consider renaming to
// Read0, Read1, Read3 and ReadS.
// TODO: Better syntax is ReadReply1("PHLX601", &tcount)
// Closer to the line format.
// Also make methods to Expect, ReadOffsetCount, &c.
func (c Conn) ReadReply(p string) error {
	_, err := c.receiveLine(p)
	return err
}

// ReadReply1 reads a message from the tracker and returns
// the numeric parameter.
func (c Conn) ReadReply1(p string) (int64, error) {
	args, err := c.receiveLine(p)
	if err != nil {
		return 0, err
	}

	vals, err := parseIntArgs(args, 10)
	return vals[0], err
}

func (c Conn) ReadReply1H(p string) (int64, uint32, error) {
	args, err := c.receiveLine(p)
	if err != nil {
		return 0, 0, err
	}

	vals, err := parseIntArgs(args, 10, 16)
	return vals[0], uint32(vals[1]), err
}

// ReadReply3 reads a message from the tracker containing
// two decimal parameters and a checksum.
func (c Conn) ReadReply3(p string) (int64, int64, uint32, error) {
	args, err := c.receiveLine(p)
	if err != nil {
		return 0, 0, 0, err
	}

	vals, err := parseIntArgs(args, 10, 10, 16)
	return vals[0], vals[1], uint32(vals[2]), err

}

// TODO: All the information about n is carried by len(block)
func (c Conn) ReadBlock(block []byte, checksum uint32) error {
	n := int64(len(block))
	h := NewHash()
	src := io.TeeReader(c.rw, h)
	dst := bytes.NewBuffer(block)

	_, err := io.CopyN(dst, src, n)

	if cs := h.Sum32(); err == nil && cs != checksum {
		err = fmt.Errorf("expected CRC %08x, got %08x", checksum, cs)
	}
	return err
}

// receiveLine expects a prefix of the expected reply as a string
// and returns split out data as args for further processing
func (c Conn) receiveLine(p string) ([]string, error) {
	c.lines.Scan()

	if c.lines.Err() != nil {
		return nil, c.lines.Err()
	}

	reply := c.lines.Text()
	if !strings.HasPrefix(reply, p) {
		return nil, fmt.Errorf(
			"expected prefix %s, got %s",
			p,
			reply)
	}

	parts := strings.Split(reply, ",")
	return parts[1:], nil
}

// parseIntArgs converts a group of integer strings at once.
// The slice always has the same number of elements as the
// number of bases given.
func parseIntArgs(args []string, bases ...int) ([]int64, error) {
	A := len(args)
	B := len(bases)
	vals := make([]int64, B)

	if A != B {
		err := fmt.Errorf("Expected %d args, got %d", B, A)
		return vals, err
	}

	for k, s := range args {
		v, err := strconv.ParseInt(s, bases[k], 0)
		if err != nil {
			return nil, err
		}
		vals[k] = v
	}
	return vals, nil
}

func foldXOR(cmd []byte) byte {
	var c byte

	for _, b := range cmd {
		c ^= b
	}
	return c
}

// split validates the checksum and strips out
// $ and other bits from the command
func split(data []byte, atEOF bool) (int, []byte, error) {
	advance, token, err := bufio.ScanLines(data, atEOF)
	if err != nil || token == nil {
		return advance, token, err
	}

	L := len(token)

	if token[0] != '$' || token[L-3] != '*' || L < 3 {
		return advance, token, fmt.Errorf(
			"format mismatch in command %s",
			token)
	}

	cmd := token[1 : L-3]
	check := string(token[L-2 : L])
	cs, err := strconv.ParseUint(check, 16, 8)

	if err != nil {
		return advance, token, fmt.Errorf("bad checksum: %s", check)
	}

	if foldXOR(cmd) != byte(cs) {
		return advance, cmd, fmt.Errorf(
			"checksum mismatch: computed %02x, received %02x",
			foldXOR(cmd),
			cs)
	}

	return advance, cmd, err
}
<commit_msg>Clarify error message about block CRC<commit_after>package holux

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"strconv"
	"strings"
)

const CMDFMT = "$%s*%02X\r\n"

// Conn manages the line-based and binary transmission with any
// io.Reader. In particular, that Reader can be a serial connection
// with the GPS tracker.
type Conn struct {
	rw    io.ReadWriter
	lines *bufio.Scanner
}

func NewConn(rw io.ReadWriter) Conn {
	lines := bufio.NewScanner(rw)
	lines.Split(split)
	return Conn{rw, lines}
}

// TODO: These two should take errors.
func (c Conn) Send(cmd string) {
	cs := foldXOR([]byte(cmd))
	fmt.Fprintf(c.rw, CMDFMT, cmd, cs)
}

func (c Conn) Sendf(format string, a ...interface{}) {
	c.Send(fmt.Sprintf(format, a...))
}

// ReadReply reads a message from the tracker and validates that
// the prefix matches.
// TODO: There's no string reply reading yet. Consider renaming to
// Read0, Read1, Read3 and ReadS.
// TODO: Better syntax is ReadReply1("PHLX601", &tcount)
// Closer to the line format.
// Also make methods to Expect, ReadOffsetCount, &c.
func (c Conn) ReadReply(p string) error {
	_, err := c.receiveLine(p)
	return err
}

// ReadReply1 reads a message from the tracker and returns
// the numeric parameter.
func (c Conn) ReadReply1(p string) (int64, error) {
	args, err := c.receiveLine(p)
	if err != nil {
		return 0, err
	}

	vals, err := parseIntArgs(args, 10)
	return vals[0], err
}

func (c Conn) ReadReply1H(p string) (int64, uint32, error) {
	args, err := c.receiveLine(p)
	if err != nil {
		return 0, 0, err
	}

	vals, err := parseIntArgs(args, 10, 16)
	return vals[0], uint32(vals[1]), err
}

// ReadReply3 reads a message from the tracker containing
// two decimal parameters and a checksum.
func (c Conn) ReadReply3(p string) (int64, int64, uint32, error) {
	args, err := c.receiveLine(p)
	if err != nil {
		return 0, 0, 0, err
	}

	vals, err := parseIntArgs(args, 10, 10, 16)
	return vals[0], vals[1], uint32(vals[2]), err

}

// TODO: All the information about n is carried by len(block)
func (c Conn) ReadBlock(block []byte, checksum uint32) error {
	n := int64(len(block))
	h := NewHash()
	src := io.TeeReader(c.rw, h)
	dst := bytes.NewBuffer(block)

	_, err := io.CopyN(dst, src, n)

	if cs := h.Sum32(); err == nil && cs != checksum {
		err = fmt.Errorf("expected block CRC %08x, got %08x", checksum, cs)
	}
	return err
}

// receiveLine expects a prefix of the expected reply as a string
// and returns split out data as args for further processing
func (c Conn) receiveLine(p string) ([]string, error) {
	c.lines.Scan()

	if c.lines.Err() != nil {
		return nil, c.lines.Err()
	}

	reply := c.lines.Text()
	if !strings.HasPrefix(reply, p) {
		return nil, fmt.Errorf(
			"expected prefix %s, got %s",
			p,
			reply)
	}

	parts := strings.Split(reply, ",")
	return parts[1:], nil
}

// parseIntArgs converts a group of integer strings at once.
// The slice always has the same number of elements as the
// number of bases given.
func parseIntArgs(args []string, bases ...int) ([]int64, error) {
	A := len(args)
	B := len(bases)
	vals := make([]int64, B)

	if A != B {
		err := fmt.Errorf("Expected %d args, got %d", B, A)
		return vals, err
	}

	for k, s := range args {
		v, err := strconv.ParseInt(s, bases[k], 0)
		if err != nil {
			return nil, err
		}
		vals[k] = v
	}
	return vals, nil
}

func foldXOR(cmd []byte) byte {
	var c byte

	for _, b := range cmd {
		c ^= b
	}
	return c
}

// split validates the checksum and strips out
// $ and other bits from the command
func split(data []byte, atEOF bool) (int, []byte, error) {
	advance, token, err := bufio.ScanLines(data, atEOF)
	if err != nil || token == nil {
		return advance, token, err
	}

	L := len(token)

	if token[0] != '$' || token[L-3] != '*' || L < 3 {
		return advance, token, fmt.Errorf(
			"format mismatch in command %s",
			token)
	}

	cmd := token[1 : L-3]
	check := string(token[L-2 : L])
	cs, err := strconv.ParseUint(check, 16, 8)

	if err != nil {
		return advance, token, fmt.Errorf("bad checksum: %s", check)
	}

	if foldXOR(cmd) != byte(cs) {
		return advance, cmd, fmt.Errorf(
			"checksum mismatch: computed %02x, received %02x",
			foldXOR(cmd),
			cs)
	}

	return advance, cmd, err
}
<|endoftext|>


<commit_before>package epp

import (
	"bytes"
	"encoding/binary"
	"encoding/xml"
	"io"
	"net"
)

// IgnoreEOF returns err unless err == io.EOF,
// in which case it returns nil.
func IgnoreEOF(err error) error {
	if err == io.EOF {
		return nil
	}
	return err
}

// Conn represents a single connection to an EPP server.
// This implementation is not safe for concurrent use.
type Conn struct {
	net.Conn
	buf     bytes.Buffer
	decoder *xml.Decoder
	saved   xml.Decoder

	// Greeting holds the last received greeting message from the server,
	// indicating server name, status, data policy and capabilities.
	Greeting
}

// NewConn initializes an epp.Conn from a net.Conn and performs the EPP
// handshake. It reads and stores the initial EPP <greeting> message.
// https://tools.ietf.org/html/rfc5730#section-2.4
func NewConn(conn net.Conn) (*Conn, error) {
	c := newConn(conn)
	err := c.readGreeting()
	return c, err
}

// Close sends an EPP <logout> command and closes the connection c.
func (c *Conn) Close() error {
	c.Logout()
	return c.Conn.Close()
}

// newConn initializes an epp.Conn from a net.Conn.
// Used internally for testing.
func newConn(conn net.Conn) *Conn {
	c := Conn{Conn: conn}
	c.decoder = xml.NewDecoder(&c.buf)
	c.saved = *c.decoder
	return &c
}

// reset resets the underlying xml.Decoder and bytes.Buffer.
func (c *Conn) reset() {
	c.resetDecoder()
	c.buf.Reset()
}

// resetDecoder restores the original state of the underlying
// xml.Decoder (pos 1, line 1, stack, etc.) using a hack.
func (c *Conn) resetDecoder() {
	*c.decoder = c.saved // Heh.
}

// flushDataUnit writes bytes from c.buf to c using writeDataUnit.
func (c *Conn) flushDataUnit() error {
	return c.writeDataUnit(c.buf.Bytes())
}

// writeDataUnit writes a slice of bytes to c.
// Bytes written are prefixed with 32-bit header specifying the total size
// of the data unit (message + 4 byte header), in network (big-endian) order.
// http://www.ietf.org/rfc/rfc4934.txt
func (c *Conn) writeDataUnit(x []byte) error {
	logXML("<-- WRITE DATA UNIT -->", x)
	s := uint32(4 + len(x))
	err := binary.Write(c.Conn, binary.BigEndian, s)
	if err != nil {
		return err
	}
	_, err = c.Conn.Write(x)
	return err
}

// readResponse reads a single EPP response from c and parses the XML into req.
// It returns an error if the EPP response contains an error Result.
func (c *Conn) readResponse(res *response_) error {
	err := c.readDataUnit()
	if err != nil {
		return err
	}
	err = IgnoreEOF(scanResponse.Scan(c.decoder, res))
	if err != nil {
		return err
	}
	if res.Result.IsError() {
		return res.Result
	}
	return nil
}

// readDataUnit reads a single EPP message from c into
// c.buf. The bytes in c.buf are valid until the next
// call to readDataUnit.
func (c *Conn) readDataUnit() error {
	c.reset()
	var s int32
	err := binary.Read(c.Conn, binary.BigEndian, &s)
	if err != nil {
		return err
	}
	s -= 4
	if s < 0 {
		return io.ErrUnexpectedEOF
	}
	lr := io.LimitedReader{R: c.Conn, N: int64(s)}
	n, err := c.buf.ReadFrom(&lr)
	if err != nil {
		return err
	}
	if n != int64(s) || lr.N != 0 {
		return io.ErrUnexpectedEOF
	}
	logXML("<-- READ DATA UNIT -->", c.buf.Bytes())
	return nil
}

func deleteRange(s, pfx, sfx []byte) []byte {
	start := bytes.Index(s, pfx)
	if start < 0 {
		return s
	}
	end := bytes.Index(s[start+len(pfx):], sfx)
	if end < 0 {
		return s
	}
	end += start + len(pfx) + len(sfx)
	size := len(s) - (end - start)
	copy(s[start:size], s[end:])
	return s[:size]
}

func deleteBufferRange(buf *bytes.Buffer, pfx, sfx []byte) {
	v := deleteRange(buf.Bytes(), pfx, sfx)
	buf.Truncate(len(v))
}
<commit_msg>Instantiate new xml.Decoder on reset.<commit_after>package epp

import (
	"bytes"
	"encoding/binary"
	"encoding/xml"
	"io"
	"net"
)

// IgnoreEOF returns err unless err == io.EOF,
// in which case it returns nil.
func IgnoreEOF(err error) error {
	if err == io.EOF {
		return nil
	}
	return err
}

// Conn represents a single connection to an EPP server.
// This implementation is not safe for concurrent use.
type Conn struct {
	net.Conn
	buf     bytes.Buffer
	decoder *xml.Decoder

	// Greeting holds the last received greeting message from the server,
	// indicating server name, status, data policy and capabilities.
	Greeting
}

// NewConn initializes an epp.Conn from a net.Conn and performs the EPP
// handshake. It reads and stores the initial EPP <greeting> message.
// https://tools.ietf.org/html/rfc5730#section-2.4
func NewConn(conn net.Conn) (*Conn, error) {
	c := newConn(conn)
	err := c.readGreeting()
	return c, err
}

// Close sends an EPP <logout> command and closes the connection c.
func (c *Conn) Close() error {
	c.Logout()
	return c.Conn.Close()
}

// newConn initializes an epp.Conn from a net.Conn.
// Used internally for testing.
func newConn(conn net.Conn) *Conn {
	c := Conn{Conn: conn}
	c.resetDecoder()
	return &c
}

// reset resets the underlying xml.Decoder and bytes.Buffer.
func (c *Conn) reset() {
	c.resetDecoder()
	c.buf.Reset()
}

// resetDecoder restores the original state of the underlying
// xml.Decoder (pos 1, line 1, stack, etc.) using a hack.
func (c *Conn) resetDecoder() {
	c.decoder = xml.NewDecoder(&c.buf)
}

// flushDataUnit writes bytes from c.buf to c using writeDataUnit.
func (c *Conn) flushDataUnit() error {
	return c.writeDataUnit(c.buf.Bytes())
}

// writeDataUnit writes a slice of bytes to c.
// Bytes written are prefixed with 32-bit header specifying the total size
// of the data unit (message + 4 byte header), in network (big-endian) order.
// http://www.ietf.org/rfc/rfc4934.txt
func (c *Conn) writeDataUnit(x []byte) error {
	logXML("<-- WRITE DATA UNIT -->", x)
	s := uint32(4 + len(x))
	err := binary.Write(c.Conn, binary.BigEndian, s)
	if err != nil {
		return err
	}
	_, err = c.Conn.Write(x)
	return err
}

// readResponse reads a single EPP response from c and parses the XML into req.
// It returns an error if the EPP response contains an error Result.
func (c *Conn) readResponse(res *response_) error {
	err := c.readDataUnit()
	if err != nil {
		return err
	}
	err = IgnoreEOF(scanResponse.Scan(c.decoder, res))
	if err != nil {
		return err
	}
	if res.Result.IsError() {
		return res.Result
	}
	return nil
}

// readDataUnit reads a single EPP message from c into
// c.buf. The bytes in c.buf are valid until the next
// call to readDataUnit.
func (c *Conn) readDataUnit() error {
	c.reset()
	var s int32
	err := binary.Read(c.Conn, binary.BigEndian, &s)
	if err != nil {
		return err
	}
	s -= 4
	if s < 0 {
		return io.ErrUnexpectedEOF
	}
	lr := io.LimitedReader{R: c.Conn, N: int64(s)}
	n, err := c.buf.ReadFrom(&lr)
	if err != nil {
		return err
	}
	if n != int64(s) || lr.N != 0 {
		return io.ErrUnexpectedEOF
	}
	logXML("<-- READ DATA UNIT -->", c.buf.Bytes())
	return nil
}

func deleteRange(s, pfx, sfx []byte) []byte {
	start := bytes.Index(s, pfx)
	if start < 0 {
		return s
	}
	end := bytes.Index(s[start+len(pfx):], sfx)
	if end < 0 {
		return s
	}
	end += start + len(pfx) + len(sfx)
	size := len(s) - (end - start)
	copy(s[start:size], s[end:])
	return s[:size]
}

func deleteBufferRange(buf *bytes.Buffer, pfx, sfx []byte) {
	v := deleteRange(buf.Bytes(), pfx, sfx)
	buf.Truncate(len(v))
}
<|endoftext|>


<commit_before>package main

import (
	"net/http"
	"encoding/json"
	"fmt"
	"os"
)

type Operation struct {
	Name	string	`json:"name"`
}

func main() {
	bindHandlers()
	printWelcomeMessage()
	startServer()
}

func bindHandlers() {
	http.HandleFunc("/list", listHttpHandler)
	http.HandleFunc("/quit", quitHttpHandler)
}

func printWelcomeMessage() {
	fmt.Println("Now listening on :9993")
}

func startServer() {
	http.ListenAndServe(":9993", nil)
}

func listHttpHandler(writer http.ResponseWriter, response *http.Request) {
	json.NewEncoder(writer).Encode(operationList())
}

func operationList() ([]Operation) {
	type Operations []Operation

	operations := Operations {
		Operation{ Name: "/list" },
		Operation{ Name: "/quit" },
	}

	return operations
}

//TODO figure out how to get the writer to flush before the aplication shuts down
func quitHttpHandler(writer http.ResponseWriter, response *http.Request) {
	defer shutdown()
	sendStatusOK(writer)
	sendByeMessageToClient(writer)
}

func sendStatusOK(writer http.ResponseWriter) {
	writer.WriteHeader(http.StatusOK)
}

func sendByeMessageToClient(writer http.ResponseWriter) {
	writer.Write([]byte("bye"))
}

func sendByeMessageToConsole() {
	fmt.Println("See you next time");
}

func shutdown() {
	os.Exit(0)	// 0 == everything is ok
}

<commit_msg>refactored func name<commit_after>package main

import (
	"net/http"
	"encoding/json"
	"fmt"
	"os"
)

type Operation struct {
	Name	string	`json:"name"`
}

func main() {
	bindHandlers()
	printWelcomeMessage()
	startServer()
}

func bindHandlers() {
	http.HandleFunc("/list", listHttpHandler)
	http.HandleFunc("/quit", quitHttpHandler)
}

func printWelcomeMessage() {
	fmt.Println("Now listening on :9993")
}

func startServer() {
	http.ListenAndServe(":9993", nil)
}

func listHttpHandler(writer http.ResponseWriter, response *http.Request) {
	json.NewEncoder(writer).Encode(operationList())
}

func operationList() ([]Operation) {
	type Operations []Operation

	operations := Operations {
		Operation{ Name: "/list" },
		Operation{ Name: "/quit" },
	}

	return operations
}

//TODO figure out how to get the writer to flush before the aplication shuts down
func quitHttpHandler(writer http.ResponseWriter, response *http.Request) {
	defer shutdown()
	sendHttpStatusOk(writer)
	sendByeMessageToClient(writer)
}

func sendHttpStatusOk(writer http.ResponseWriter) {
	writer.WriteHeader(http.StatusOK)
}

func sendByeMessageToClient(writer http.ResponseWriter) {
	writer.Write([]byte("bye"))
}

func sendByeMessageToConsole() {
	fmt.Println("See you next time");
}

func shutdown() {
	os.Exit(0)	// 0 == everything is ok
}

<|endoftext|>


<commit_before>package cors

import (
	"errors"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
)

// Config represents all available options for the middleware.
type Config struct {
	AllowAllOrigins bool

	// AllowedOrigins is a list of origins a cross-domain request can be executed from.
	AllowOrigins []string

	// AllowOriginFunc is a custom function to validate the origin. It take the origin
	// as argument and returns true if allowed or false otherwise. If this option is
	// set, the content of AllowedOrigins is ignored.
	AllowOriginFunc func(origin string) bool

	// AllowedMethods is a list of methods the client is allowed to use with
	// cross-domain requests. Default value is simple methods (GET and POST)
	AllowMethods []string

	// AllowedHeaders is list of non simple headers the client is allowed to use with
	// cross-domain requests.
	AllowHeaders []string

	// AllowCredentials indicates whether the request can include user credentials like
	// cookies, HTTP authentication or client side SSL certificates.
	AllowCredentials bool

	// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
	// API specification
	ExposeHeaders []string

	// MaxAge indicates how long (in seconds) the results of a preflight request
	// can be cached
	MaxAge time.Duration
}

// AddAllowMethods is allowed to add custom methods
func (c *Config) AddAllowMethods(methods ...string) {
	c.AllowMethods = append(c.AllowMethods, methods...)
}

// AddAllowHeaders is allowed to add custom headers
func (c *Config) AddAllowHeaders(headers ...string) {
	c.AllowHeaders = append(c.AllowHeaders, headers...)
}

// AddExposeHeaders is allowed to add custom expose headers
func (c *Config) AddExposeHeaders(headers ...string) {
	c.ExposeHeaders = append(c.ExposeHeaders, headers...)
}

// Validate is check configuration of user defined.
func (c Config) Validate() error {
	if c.AllowAllOrigins && (c.AllowOriginFunc != nil || len(c.AllowOrigins) > 0) {
		return errors.New("conflict settings: all origins are allowed. AllowOriginFunc or AllowedOrigins is not needed")
	}
	if !c.AllowAllOrigins && c.AllowOriginFunc == nil && len(c.AllowOrigins) == 0 {
		return errors.New("conflict settings: all origins disabled")
	}
	for _, origin := range c.AllowOrigins {
		if !strings.HasPrefix(origin, "http://") && !strings.HasPrefix(origin, "https://") {
			return errors.New("bad origin: origins must include http:// or https://")
		}
	}
	return nil
}

// DefaultConfig returns a generic default configuration mapped to localhost.
func DefaultConfig() Config {
	return Config{
		AllowMethods:     []string{"GET", "POST", "PUT", "HEAD"},
		AllowHeaders:     []string{"Origin", "Content-Length", "Content-Type"},
		AllowCredentials: false,
		MaxAge:           12 * time.Hour,
	}
}

// Default returns the location middleware with default configuration.
func Default() gin.HandlerFunc {
	config := DefaultConfig()
	config.AllowAllOrigins = true
	return New(config)
}

// New returns the location middleware with user-defined custom configuration.
func New(config Config) gin.HandlerFunc {
	cors := newCors(config)
	return func(c *gin.Context) {
		cors.applyCors(c)
	}
}
<commit_msg>Change comments to ‘Default value is []’<commit_after>package cors

import (
	"errors"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
)

// Config represents all available options for the middleware.
type Config struct {
	AllowAllOrigins bool

	// AllowedOrigins is a list of origins a cross-domain request can be executed from.
	// Default value is []
	AllowOrigins []string

	// AllowOriginFunc is a custom function to validate the origin. It take the origin
	// as argument and returns true if allowed or false otherwise. If this option is
	// set, the content of AllowedOrigins is ignored.
	AllowOriginFunc func(origin string) bool

	// AllowedMethods is a list of methods the client is allowed to use with
	// cross-domain requests. Default value is simple methods (GET and POST)
	AllowMethods []string

	// AllowedHeaders is list of non simple headers the client is allowed to use with
	// cross-domain requests.
	AllowHeaders []string

	// AllowCredentials indicates whether the request can include user credentials like
	// cookies, HTTP authentication or client side SSL certificates.
	AllowCredentials bool

	// ExposedHeaders indicates which headers are safe to expose to the API of a CORS
	// API specification
	ExposeHeaders []string

	// MaxAge indicates how long (in seconds) the results of a preflight request
	// can be cached
	MaxAge time.Duration
}

// AddAllowMethods is allowed to add custom methods
func (c *Config) AddAllowMethods(methods ...string) {
	c.AllowMethods = append(c.AllowMethods, methods...)
}

// AddAllowHeaders is allowed to add custom headers
func (c *Config) AddAllowHeaders(headers ...string) {
	c.AllowHeaders = append(c.AllowHeaders, headers...)
}

// AddExposeHeaders is allowed to add custom expose headers
func (c *Config) AddExposeHeaders(headers ...string) {
	c.ExposeHeaders = append(c.ExposeHeaders, headers...)
}

// Validate is check configuration of user defined.
func (c Config) Validate() error {
	if c.AllowAllOrigins && (c.AllowOriginFunc != nil || len(c.AllowOrigins) > 0) {
		return errors.New("conflict settings: all origins are allowed. AllowOriginFunc or AllowedOrigins is not needed")
	}
	if !c.AllowAllOrigins && c.AllowOriginFunc == nil && len(c.AllowOrigins) == 0 {
		return errors.New("conflict settings: all origins disabled")
	}
	for _, origin := range c.AllowOrigins {
		if !strings.HasPrefix(origin, "http://") && !strings.HasPrefix(origin, "https://") {
			return errors.New("bad origin: origins must include http:// or https://")
		}
	}
	return nil
}

// DefaultConfig returns a generic default configuration mapped to localhost.
func DefaultConfig() Config {
	return Config{
		AllowMethods:     []string{"GET", "POST", "PUT", "HEAD"},
		AllowHeaders:     []string{"Origin", "Content-Length", "Content-Type"},
		AllowCredentials: false,
		MaxAge:           12 * time.Hour,
	}
}

// Default returns the location middleware with default configuration.
func Default() gin.HandlerFunc {
	config := DefaultConfig()
	config.AllowAllOrigins = true
	return New(config)
}

// New returns the location middleware with user-defined custom configuration.
func New(config Config) gin.HandlerFunc {
	cors := newCors(config)
	return func(c *gin.Context) {
		cors.applyCors(c)
	}
}
<|endoftext|>


<commit_before>package sqlx

import (
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"strings"
	"time"
)

type DBI interface {
	NamedExec(query string, arg interface{}) (sql.Result, error)
	NamedQueryRow(query string, arg interface{}) *Row
	NamedQuery(query string, arg interface{}) (*Rows, error)
	QueryRowx(query string, args ...interface{}) *Row
	Queryx(query string, args ...interface{}) (*Rows, error)
	Rebind(query string) string
	Exec(query string, args ...interface{}) (sql.Result, error)
}

// Return the name of a Struct to tablename
func DefaultTableName(i interface{}) string {
	return strings.ToLower(reflect.TypeOf(reflect.Indirect(reflect.ValueOf(i)).Interface()).Name())
}

type Helper struct {
	DBI
}

type StructTable interface {
	TableName() string
	Validate() error
}

type SafeSelector map[string]interface{}

// expand expands the selector into a clause delimited by some space and a list of
// args to append into prepared statements
func expand(s map[string]interface{}, spacer string) (string, []interface{}) {
	args := []interface{}{}
	cnt := 0
	query := ""
	for key, value := range s {
		query += key
		query += "=?"
		if cnt != len(s)-1 {
			query += spacer
		}
		args = append(args, value)
		cnt += 1
	}
	return query, args
}

// extract takes in a struct object and extracts out the mapping
func extract(obj StructTable) (map[string]interface{}, error) {
	// Validate the schema.
	if err := obj.Validate(); err != nil {
		return nil, err
	}

	base := reflect.Indirect(reflect.ValueOf(obj)) // a parameter itself
	baseType := reflect.TypeOf(base.Interface())   // eg. Parameter
	items := map[string]interface{}{}
	for i := 0; i < baseType.NumField(); i++ {
		fieldName := baseType.Field(i).Name // eg. "Torsion"
		possiblePtrFieldValue := base.FieldByName(fieldName)

		if possiblePtrFieldValue.Kind() == reflect.Ptr && possiblePtrFieldValue.IsNil() {
			// pass
		} else {
			// we are not a nil pointer, then indirect would always work.
			fieldValue := reflect.Indirect(possiblePtrFieldValue)
			concreteValue := fieldValue.Interface()
			dbName, _ := parseTag(baseType.Field(i).Tag.Get("json"))
			// if tagOptions.Contains("nonzero") && isZeroValue(fieldValue) {
			// 	return nil, errors.New("Zero value found for tagged nonzero field:" + fieldName)
			// }
			switch item := concreteValue.(type) {
			default:
				items[dbName] = concreteValue
				// dbVals = append(dbVals, ":"+dbName)
			case time.Time:
				if isZeroValue(reflect.ValueOf(item)) {
					items[dbName] = "NOW"
				} else {
					items[dbName] = concreteValue
				}
			}
		}
	}
	return items, nil
}

/* START ripped from unexported std lib END */
type tagOptions string

func parseTag(tag string) (string, tagOptions) {
	if idx := strings.Index(tag, ","); idx != -1 {
		return tag[:idx], tagOptions(tag[idx+1:])
	}
	return tag, tagOptions("")
}

func (o tagOptions) Contains(optionName string) bool {
	if len(o) == 0 {
		return false
	}
	s := string(o)
	for s != "" {
		var next string
		i := strings.Index(s, ",")
		if i >= 0 {
			s, next = s[:i], s[i+1:]
		}
		if s == optionName {
			return true
		}
		s = next
	}
	return false
}

/* END ripped from unexported std lib END */

// MsiToStruct takes in a JSON serializable map[string]interface{} and converts
// it the actual object
func JsonToStruct(input map[string]interface{}, s StructTable) error {
	// YT: LOL
	b, err := json.Marshal(input)
	if err != nil {
		fmt.Println("dafuq?")
		return err
	}
	return json.Unmarshal(b, s)
}

func MakeStructTable(input map[string]interface{}, obj StructTable) error {
	base := reflect.Indirect(reflect.ValueOf(obj))
	baseType := reflect.TypeOf(base.Interface())
	for k, v := range input {
		_, ok := baseType.FieldByName(k)
		if !ok {
			return errors.New("Bad input name: " + k)
		}
		fv := base.FieldByName(k)
		ptr := reflect.New(reflect.TypeOf(v))
		reflect.Indirect(ptr).Set(reflect.ValueOf(v))
		fv.Set(ptr)
	}
	return nil
}

// MakeProjector creates a projector that is used to filter the columns returned, like its
// cousin MakeSelector, it validates against the given object.
func MakeProjector(input []string, obj interface{}) ([]string, error) {
	base := reflect.TypeOf(reflect.Indirect(reflect.ValueOf(obj)).Interface())
	cols := []string{}
	for _, fname := range input {
		b, ok := base.FieldByName(fname)
		if !ok {
			return nil, errors.New("Unknown Field Name")
		}
		new_key, _ := parseTag(b.Tag.Get("db"))
		cols = append(cols, new_key)
	}
	return cols, nil
}

// Checks to see if x is the Zero Value
// This is not fully implemented for Arrays, or Structs, or other weird stuff
func isZeroValue(v reflect.Value) bool {
	// v := reflect.ValueOf(x)
	switch v.Kind() {
	case reflect.Map, reflect.Slice:
		return v.IsNil()
	}
	return v.Interface() == reflect.Zero(v.Type()).Interface()
}

// special insertion rules:
// 		if type is time.Time, and the value is a Zero Value, then CURRENT_TIMESTAMP will be inserted
//		if type is a Pointer, and its indirected value is nil, then it is omitted.
func (h *Helper) CreateObject(obj StructTable) error {
	msi, err := extract(obj)
	if err != nil {
		return err
	}
	dbKeys := []string{}
	dbVals := []interface{}{}
	for k, v := range msi {
		dbKeys = append(dbKeys, k)
		dbVals = append(dbVals, v)
	}
	query := "INSERT INTO " + obj.TableName()
	query += " ("
	for idx, key := range dbKeys {
		query += key
		if idx != len(dbKeys)-1 {
			query += ","
		}
	}
	query += ") VALUES ("
	for idx, _ := range dbKeys {
		query += "?"
		if idx != len(dbKeys)-1 {
			query += ","
		}
	}
	query += ")"
	query = h.Rebind(query)
	_, err = h.Exec(query, dbVals...)
	return err
}

// DeleteAll removes all rows in the table matching condition.
// If no matching row was deleted, then an error is returned.
func (h *Helper) DeleteAll(condition StructTable) error {
	tableName := condition.TableName()
	msi, err := extract(condition)
	if err != nil {
		return err
	}
	query := "DELETE FROM " + tableName
	query += " WHERE "
	where, args := expand(msi, " AND ")
	query += where
	query = h.Rebind(query)
	res, err := h.Exec(query, args...)
	if err != nil {
		return err
	}
	cnt, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if cnt == 0 {
		return errors.New("No row was deleted.")
	}
	return nil
}

func (h *Helper) buildQuery(condition StructTable, projection []string) (string, []interface{}, error) {
	tableName := condition.TableName()
	projs, err := MakeProjector(projection, condition)
	if err != nil {
		return "", nil, err
	}
	query := "SELECT "
	if len(projs) > 0 {
		for idx, p := range projs {
			query += p
			if idx != len(projs)-1 {
				query += ","
			}
		}
	} else {
		query += "*"
	}
	query += " FROM "
	query += tableName
	query += " WHERE "
	msi, err := extract(condition)
	if err != nil {
		return "", nil, err
	}
	where, args := expand(msi, " AND ")
	query += where
	query += " LIMIT 1"
	query = h.Rebind(query)
	return query, args, nil
}

// QueryOne returns a scanned object corresponding to the first row matching condition. For
// more complicated tasks such as pagination, etc. It's more sensible to build your own SQL.
// objPtr must be some pointer to a StructTable to receive the deserialized value.
func (h *Helper) QueryOne(condition StructTable, objPtr StructTable, projection ...string) error {
	query, args, err := h.buildQuery(condition, projection)
	if err != nil {
		return err
	}
	return h.QueryRowx(query, args...).StructScan(objPtr)
}

func (h *Helper) QueryRows(condition StructTable, projection ...string) (*Rows, error) {
	query, args, err := h.buildQuery(condition, projection)
	if err != nil {
		return nil, err
	}
	return h.Queryx(query, args...)
}

// UpdateAll updates rows matching condition with new values given by updates.
// If no matching row was updated, then an error is returned.
func (h *Helper) UpdateAll(updates StructTable, conds StructTable) error {
	tableName := updates.TableName()

	msi1, err := extract(updates)
	if err != nil {
		return err
	}
	msi2, err := extract(conds)
	if err != nil {
		return err
	}
	query := "UPDATE " + tableName + " SET "
	expansion, args := expand(msi1, ",")
	query += expansion
	query += " WHERE "
	expansion2, args2 := expand(msi2, " AND ")
	query += expansion2
	all_args := append(args, args2...)
	query = h.Rebind(query)
	res, err := h.Exec(query, all_args...)
	if err != nil {
		return err
	}
	cnt, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if cnt == 0 {
		return errors.New("No row was updated.")
	}
	return nil
}
<commit_msg>Remove MakeProjector and take in json tags directly<commit_after>package sqlx

import (
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"strings"
	"time"
)

type DBI interface {
	NamedExec(query string, arg interface{}) (sql.Result, error)
	NamedQueryRow(query string, arg interface{}) *Row
	NamedQuery(query string, arg interface{}) (*Rows, error)
	QueryRowx(query string, args ...interface{}) *Row
	Queryx(query string, args ...interface{}) (*Rows, error)
	Rebind(query string) string
	Exec(query string, args ...interface{}) (sql.Result, error)
}

// Return the name of a Struct to tablename
func DefaultTableName(i interface{}) string {
	return strings.ToLower(reflect.TypeOf(reflect.Indirect(reflect.ValueOf(i)).Interface()).Name())
}

type Helper struct {
	DBI
}

type StructTable interface {
	TableName() string
	Validate() error
}

type SafeSelector map[string]interface{}

// expand expands the selector into a clause delimited by some space and a list of
// args to append into prepared statements
func expand(s map[string]interface{}, spacer string) (string, []interface{}) {
	args := []interface{}{}
	cnt := 0
	query := ""
	for key, value := range s {
		query += key
		query += "=?"
		if cnt != len(s)-1 {
			query += spacer
		}
		args = append(args, value)
		cnt += 1
	}
	return query, args
}

// extract takes in a struct object and extracts out the mapping
func extract(obj StructTable) (map[string]interface{}, error) {
	// Validate the schema.
	if err := obj.Validate(); err != nil {
		return nil, err
	}

	base := reflect.Indirect(reflect.ValueOf(obj)) // a parameter itself
	baseType := reflect.TypeOf(base.Interface())   // eg. Parameter
	items := map[string]interface{}{}
	for i := 0; i < baseType.NumField(); i++ {
		fieldName := baseType.Field(i).Name // eg. "Torsion"
		possiblePtrFieldValue := base.FieldByName(fieldName)

		if possiblePtrFieldValue.Kind() == reflect.Ptr && possiblePtrFieldValue.IsNil() {
			// pass
		} else {
			// we are not a nil pointer, then indirect would always work.
			fieldValue := reflect.Indirect(possiblePtrFieldValue)
			concreteValue := fieldValue.Interface()
			dbName, _ := parseTag(baseType.Field(i).Tag.Get("json"))
			// if tagOptions.Contains("nonzero") && isZeroValue(fieldValue) {
			// 	return nil, errors.New("Zero value found for tagged nonzero field:" + fieldName)
			// }
			switch item := concreteValue.(type) {
			default:
				items[dbName] = concreteValue
				// dbVals = append(dbVals, ":"+dbName)
			case time.Time:
				if isZeroValue(reflect.ValueOf(item)) {
					items[dbName] = "NOW"
				} else {
					items[dbName] = concreteValue
				}
			}
		}
	}
	return items, nil
}

/* START ripped from unexported std lib END */
type tagOptions string

func parseTag(tag string) (string, tagOptions) {
	if idx := strings.Index(tag, ","); idx != -1 {
		return tag[:idx], tagOptions(tag[idx+1:])
	}
	return tag, tagOptions("")
}

func (o tagOptions) Contains(optionName string) bool {
	if len(o) == 0 {
		return false
	}
	s := string(o)
	for s != "" {
		var next string
		i := strings.Index(s, ",")
		if i >= 0 {
			s, next = s[:i], s[i+1:]
		}
		if s == optionName {
			return true
		}
		s = next
	}
	return false
}

/* END ripped from unexported std lib END */

// MsiToStruct takes in a JSON serializable map[string]interface{} and converts
// it the actual object
func JsonToStruct(input map[string]interface{}, s StructTable) error {
	// YT: LOL
	b, err := json.Marshal(input)
	if err != nil {
		fmt.Println("dafuq?")
		return err
	}
	return json.Unmarshal(b, s)
}

func MakeStructTable(input map[string]interface{}, obj StructTable) error {
	base := reflect.Indirect(reflect.ValueOf(obj))
	baseType := reflect.TypeOf(base.Interface())
	for k, v := range input {
		_, ok := baseType.FieldByName(k)
		if !ok {
			return errors.New("Bad input name: " + k)
		}
		fv := base.FieldByName(k)
		ptr := reflect.New(reflect.TypeOf(v))
		reflect.Indirect(ptr).Set(reflect.ValueOf(v))
		fv.Set(ptr)
	}
	return nil
}

// Checks to see if x is the Zero Value
// This is not fully implemented for Arrays, or Structs, or other weird stuff
func isZeroValue(v reflect.Value) bool {
	// v := reflect.ValueOf(x)
	switch v.Kind() {
	case reflect.Map, reflect.Slice:
		return v.IsNil()
	}
	return v.Interface() == reflect.Zero(v.Type()).Interface()
}

// special insertion rules:
// 		if type is time.Time, and the value is a Zero Value, then CURRENT_TIMESTAMP will be inserted
//		if type is a Pointer, and its indirected value is nil, then it is omitted.
func (h *Helper) CreateObject(obj StructTable) error {
	msi, err := extract(obj)
	if err != nil {
		return err
	}
	dbKeys := []string{}
	dbVals := []interface{}{}
	for k, v := range msi {
		dbKeys = append(dbKeys, k)
		dbVals = append(dbVals, v)
	}
	query := "INSERT INTO " + obj.TableName()
	query += " ("
	for idx, key := range dbKeys {
		query += key
		if idx != len(dbKeys)-1 {
			query += ","
		}
	}
	query += ") VALUES ("
	for idx, _ := range dbKeys {
		query += "?"
		if idx != len(dbKeys)-1 {
			query += ","
		}
	}
	query += ")"
	query = h.Rebind(query)
	_, err = h.Exec(query, dbVals...)
	return err
}

// DeleteAll removes all rows in the table matching condition.
// If no matching row was deleted, then an error is returned.
func (h *Helper) DeleteAll(condition StructTable) error {
	tableName := condition.TableName()
	msi, err := extract(condition)
	if err != nil {
		return err
	}
	query := "DELETE FROM " + tableName
	query += " WHERE "
	where, args := expand(msi, " AND ")
	query += where
	query = h.Rebind(query)
	res, err := h.Exec(query, args...)
	if err != nil {
		return err
	}
	cnt, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if cnt == 0 {
		return errors.New("No row was deleted.")
	}
	return nil
}

func (h *Helper) buildQuery(condition StructTable, projection []string) (string, []interface{}, error) {
	tableName := condition.TableName()
	query := "SELECT "
	if len(projection) > 0 {
		for idx, p := range projection {
			query += p
			if idx != len(projection)-1 {
				query += ","
			}
		}
	} else {
		query += "*"
	}
	query += " FROM "
	query += tableName
	msi, err := extract(condition)
	if err != nil {
		return "", nil, err
	}
	args := []interface{}{}
	if len(msi) > 0 {
		query += " WHERE "
		var where string
		where, args = expand(msi, " AND ")
		query += where
	}
	return query, args, nil
}

// QueryOne returns a scanned object corresponding to the first row matching condition. For
// more complicated tasks such as pagination, etc. It's more sensible to build your own SQL.
// objPtr must be some pointer to a StructTable to receive the deserialized value. Projection
// should be json tags.
func (h *Helper) QueryOne(condition StructTable, objPtr StructTable, projection ...string) error {
	query, args, err := h.buildQuery(condition, projection)
	if err != nil {
		return err
	}
	query += " LIMIT 1"
	query = h.Rebind(query)
	return h.QueryRowx(query, args...).StructScan(objPtr)
}

// QueryRows returns a pointer to a sql.Rows object that can iterated over and scanned. Projection
// should be json tags.
func (h *Helper) QueryRows(condition StructTable, projection ...string) (*Rows, error) {
	query, args, err := h.buildQuery(condition, projection)
	if err != nil {
		return nil, err
	}
	query = h.Rebind(query)
	return h.Queryx(query, args...)
}

// UpdateAll updates rows matching condition with new values given by updates.
// If no matching row was updated, then an error is returned.
func (h *Helper) UpdateAll(updates StructTable, conds StructTable) error {
	tableName := updates.TableName()

	msi1, err := extract(updates)
	if err != nil {
		return err
	}
	msi2, err := extract(conds)
	if err != nil {
		return err
	}
	query := "UPDATE " + tableName + " SET "
	expansion, args := expand(msi1, ",")
	query += expansion
	query += " WHERE "
	expansion2, args2 := expand(msi2, " AND ")
	query += expansion2
	all_args := append(args, args2...)
	query = h.Rebind(query)
	res, err := h.Exec(query, all_args...)
	if err != nil {
		return err
	}
	cnt, err := res.RowsAffected()
	if err != nil {
		return err
	}
	if cnt == 0 {
		return errors.New("No row was updated.")
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Liam Stanley <me@liamstanley.io>. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

package girc

import (
	"runtime"
	"strings"
	"sync"
	"time"
)

// ctcpDelim if the delimiter used for CTCP formatted events/messages.
const ctcpDelim byte = 0x01 // Prefix and suffix for CTCP messages.

// CTCPEvent is the necessary information from an IRC message.
type CTCPEvent struct {
	// Source is the author of the CTCP event.
	Source *Source
	// Command is the type of CTCP event. E.g. PING, TIME, VERSION.
	Command string
	// Text is the raw arguments following the command.
	Text string
	// Reply is true if the CTCP event is intended to be a reply to a
	// previous CTCP (e.g, if we sent one).
	Reply bool
}

// decodeCTCP decodes an incoming CTCP event, if it is CTCP. nil is returned
// if the incoming event does not match a valid CTCP.
func decodeCTCP(e *Event) *CTCPEvent {
	// http://www.irchelp.org/protocol/ctcpspec.html

	// Must be targetting a user/channel, AND trailing must have
	// DELIM+TAG+DELIM minimum (at least 3 chars).
	if len(e.Params) != 1 || len(e.Trailing) < 3 {
		return nil
	}

	if (e.Command != "PRIVMSG" && e.Command != "NOTICE") || !IsValidNick(e.Params[0]) {
		return nil
	}

	if e.Trailing[0] != ctcpDelim || e.Trailing[len(e.Trailing)-1] != ctcpDelim {
		return nil
	}

	// Strip delimiters.
	text := e.Trailing[1 : len(e.Trailing)-1]

	s := strings.IndexByte(text, space)

	// Check to see if it only contains a tag.
	if s < 0 {
		for i := 0; i < len(text); i++ {
			// Check for A-Z, 0-9.
			if (text[i] < 0x41 || text[i] > 0x5A) && (text[i] < 0x30 || text[i] > 0x39) {
				return nil
			}
		}

		return &CTCPEvent{
			Source:  e.Source,
			Command: text,
			Reply:   e.Command == "NOTICE",
		}
	}

	// Loop through checking the tag first.
	for i := 0; i < s; i++ {
		// Check for A-Z, 0-9.
		if (text[i] < 0x41 || text[i] > 0x5A) && (text[i] < 0x30 || text[i] > 0x39) {
			return nil
		}
	}

	return &CTCPEvent{
		Source:  e.Source,
		Command: text[0:s],
		Text:    text[s+1 : len(text)],
		Reply:   e.Command == "NOTICE",
	}
}

// encodeCTCP encodes a CTCP event into a string, including delimiters.
func encodeCTCP(ctcp *CTCPEvent) (out string) {
	if ctcp == nil {
		return ""
	}

	return encodeCTCPRaw(ctcp.Command, ctcp.Text)
}

// encodeCTCPRaw is much like encodeCTCP, however accepts a raw command and
// string as input.
func encodeCTCPRaw(cmd, text string) (out string) {
	if len(cmd) <= 0 {
		return ""
	}

	out = string(ctcpDelim) + cmd

	if len(text) > 0 {
		out += string(space) + text
	}

	return out + string(ctcpDelim)
}

// CTCP handles the storage and execution of CTCP handlers against incoming
// CTCP events.
type CTCP struct {
	disableDefault bool
	// mu is the mutex that should be used when accessing callbacks.
	mu sync.RWMutex
	// handlers is a map of CTCP message -> functions.
	handlers map[string]CTCPHandler
}

// newCTCP returns a new clean CTCP handler.
func newCTCP() *CTCP {
	return &CTCP{handlers: map[string]CTCPHandler{}}
}

// call executes the necessary CTCP handler for the incoming event/CTCP
// command.
func (c *CTCP) call(event *CTCPEvent, client *Client) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	// Support wildcard CTCP event handling. Gets executed first before
	// regular event handlers.
	if _, ok := c.handlers["*"]; ok {
		c.handlers["*"](client, *event)
	}

	if _, ok := c.handlers[event.Command]; !ok {
		// Send a ERRMSG reply.
		client.SendCTCPReply(event.Source.Name, CTCP_ERRMSG, "that is an unknown CTCP query")
		return
	}

	c.handlers[event.Command](client, *event)
}

// parseCMD parses a CTCP command/tag, ensuring it's valid. If not, an empty
// string is returned.
func (c *CTCP) parseCMD(cmd string) string {
	// Check if wildcard.
	if cmd == "*" {
		return "*"
	}

	cmd = strings.ToUpper(cmd)

	for i := 0; i < len(cmd); i++ {
		// Check for A-Z, 0-9.
		if (cmd[i] < 0x41 || cmd[i] > 0x5A) && (cmd[i] < 0x30 || cmd[i] > 0x39) {
			return ""
		}
	}

	return cmd
}

// Set saves handler for execution upon a matching incoming CTCP event.
// Use SetBg if the handler may take an extended period of time to execute.
// If you would like to have a handler which will catch ALL CTCP requests,
// simply use "*" in place of the command.
func (c *CTCP) Set(cmd string, handler func(client *Client, ctcp CTCPEvent)) {
	if cmd = c.parseCMD(cmd); cmd == "" {
		return
	}

	c.mu.Lock()
	c.handlers[cmd] = CTCPHandler(handler)
	c.mu.Unlock()
}

// SetBg is much like Set, however the handler is executed in the background,
// ensuring that event handling isn't hung during long running tasks. See Set
// for more information.
func (c *CTCP) SetBg(cmd string, handler func(client *Client, ctcp CTCPEvent)) {
	c.Set(cmd, func(client *Client, ctcp CTCPEvent) {
		go handler(client, ctcp)
	})
}

// Clear removes currently setup handler for cmd, if one is set. This will
// also disable default handlers for a specific cmd.
func (c *CTCP) Clear(cmd string) {
	if cmd = c.parseCMD(cmd); cmd == "" {
		return
	}

	c.mu.Lock()
	delete(c.handlers, cmd)
	c.mu.Unlock()
}

// ClearAll removes all currently setup and re-sets the default handlers,
// unless configured not to. See Client.Config.DisableDefaultCTCP.
func (c *CTCP) ClearAll() {
	c.mu.Lock()
	c.handlers = map[string]CTCPHandler{}
	c.mu.Unlock()

	// Register necessary handlers.
	c.addDefaultHandlers()
}

// CTCPHandler is a type that represents the function necessary to
// implement a CTCP handler.
type CTCPHandler func(client *Client, ctcp CTCPEvent)

// addDefaultHandlers adds some useful default CTCP response handlers, unless
// requested by the client not to.
func (c *CTCP) addDefaultHandlers() {
	if c.disableDefault {
		return
	}

	c.SetBg(CTCP_PING, handleCTCPPing)
	c.SetBg(CTCP_PONG, handleCTCPPong)
	c.SetBg(CTCP_VERSION, handleCTCPVersion)
	c.SetBg(CTCP_SOURCE, handleCTCPSource)
	c.SetBg(CTCP_TIME, handleCTCPTime)
}

// handleCTCPPing replies with a ping and whatever was originally requested.
func handleCTCPPing(client *Client, ctcp CTCPEvent) {
	if ctcp.Reply {
		return
	}
	client.SendCTCPReply(ctcp.Source.Name, CTCP_PING, ctcp.Text)
}

// handleCTCPPong replies with a pong.
func handleCTCPPong(client *Client, ctcp CTCPEvent) {
	if ctcp.Reply {
		return
	}
	client.SendCTCPReply(ctcp.Source.Name, CTCP_PONG, "")
}

// handleCTCPVersion replies with the name of the client, Go version, as well
// as the os type (darwin, linux, windows, etc) and architecture type (x86,
// arm, etc).
func handleCTCPVersion(client *Client, ctcp CTCPEvent) {
	client.SendCTCPReplyf(
		ctcp.Source.Name, CTCP_VERSION,
		"girc (github.com/lrstanley/girc) using %s (%s, %s)",
		runtime.Version(), runtime.GOOS, runtime.GOARCH,
	)
}

// handleCTCPSource replies with the public git location of this library.
func handleCTCPSource(client *Client, ctcp CTCPEvent) {
	client.SendCTCPReply(ctcp.Source.Name, CTCP_SOURCE, "https://github.com/lrstanley/girc")
}

// handleCTCPTime replies with a RFC 1123 (Z) formatted version of Go's
// local time.
func handleCTCPTime(client *Client, ctcp CTCPEvent) {
	client.SendCTCPReply(ctcp.Source.Name, CTCP_TIME, ":"+time.Now().Format(time.RFC1123Z))
}
<commit_msg>help prevent against panics during testing or malformed CTCP request<commit_after>// Copyright 2016 Liam Stanley <me@liamstanley.io>. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

package girc

import (
	"runtime"
	"strings"
	"sync"
	"time"
)

// ctcpDelim if the delimiter used for CTCP formatted events/messages.
const ctcpDelim byte = 0x01 // Prefix and suffix for CTCP messages.

// CTCPEvent is the necessary information from an IRC message.
type CTCPEvent struct {
	// Source is the author of the CTCP event.
	Source *Source
	// Command is the type of CTCP event. E.g. PING, TIME, VERSION.
	Command string
	// Text is the raw arguments following the command.
	Text string
	// Reply is true if the CTCP event is intended to be a reply to a
	// previous CTCP (e.g, if we sent one).
	Reply bool
}

// decodeCTCP decodes an incoming CTCP event, if it is CTCP. nil is returned
// if the incoming event does not match a valid CTCP.
func decodeCTCP(e *Event) *CTCPEvent {
	// http://www.irchelp.org/protocol/ctcpspec.html

	// Must be targetting a user/channel, AND trailing must have
	// DELIM+TAG+DELIM minimum (at least 3 chars).
	if len(e.Params) != 1 || len(e.Trailing) < 3 {
		return nil
	}

	if (e.Command != "PRIVMSG" && e.Command != "NOTICE") || !IsValidNick(e.Params[0]) {
		return nil
	}

	if e.Trailing[0] != ctcpDelim || e.Trailing[len(e.Trailing)-1] != ctcpDelim {
		return nil
	}

	// Strip delimiters.
	text := e.Trailing[1 : len(e.Trailing)-1]

	s := strings.IndexByte(text, space)

	// Check to see if it only contains a tag.
	if s < 0 {
		for i := 0; i < len(text); i++ {
			// Check for A-Z, 0-9.
			if (text[i] < 0x41 || text[i] > 0x5A) && (text[i] < 0x30 || text[i] > 0x39) {
				return nil
			}
		}

		return &CTCPEvent{
			Source:  e.Source,
			Command: text,
			Reply:   e.Command == "NOTICE",
		}
	}

	// Loop through checking the tag first.
	for i := 0; i < s; i++ {
		// Check for A-Z, 0-9.
		if (text[i] < 0x41 || text[i] > 0x5A) && (text[i] < 0x30 || text[i] > 0x39) {
			return nil
		}
	}

	return &CTCPEvent{
		Source:  e.Source,
		Command: text[0:s],
		Text:    text[s+1 : len(text)],
		Reply:   e.Command == "NOTICE",
	}
}

// encodeCTCP encodes a CTCP event into a string, including delimiters.
func encodeCTCP(ctcp *CTCPEvent) (out string) {
	if ctcp == nil {
		return ""
	}

	return encodeCTCPRaw(ctcp.Command, ctcp.Text)
}

// encodeCTCPRaw is much like encodeCTCP, however accepts a raw command and
// string as input.
func encodeCTCPRaw(cmd, text string) (out string) {
	if len(cmd) <= 0 {
		return ""
	}

	out = string(ctcpDelim) + cmd

	if len(text) > 0 {
		out += string(space) + text
	}

	return out + string(ctcpDelim)
}

// CTCP handles the storage and execution of CTCP handlers against incoming
// CTCP events.
type CTCP struct {
	disableDefault bool
	// mu is the mutex that should be used when accessing callbacks.
	mu sync.RWMutex
	// handlers is a map of CTCP message -> functions.
	handlers map[string]CTCPHandler
}

// newCTCP returns a new clean CTCP handler.
func newCTCP() *CTCP {
	return &CTCP{handlers: map[string]CTCPHandler{}}
}

// call executes the necessary CTCP handler for the incoming event/CTCP
// command.
func (c *CTCP) call(event *CTCPEvent, client *Client) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	// Support wildcard CTCP event handling. Gets executed first before
	// regular event handlers.
	if _, ok := c.handlers["*"]; ok {
		c.handlers["*"](client, *event)
	}

	if _, ok := c.handlers[event.Command]; !ok {
		// Send a ERRMSG reply, if we know who sent it.
		if event.Source != nil && IsValidNick(event.Source.Name) {
			client.SendCTCPReply(event.Source.Name, CTCP_ERRMSG, "that is an unknown CTCP query")
		}
		return
	}

	c.handlers[event.Command](client, *event)
}

// parseCMD parses a CTCP command/tag, ensuring it's valid. If not, an empty
// string is returned.
func (c *CTCP) parseCMD(cmd string) string {
	// Check if wildcard.
	if cmd == "*" {
		return "*"
	}

	cmd = strings.ToUpper(cmd)

	for i := 0; i < len(cmd); i++ {
		// Check for A-Z, 0-9.
		if (cmd[i] < 0x41 || cmd[i] > 0x5A) && (cmd[i] < 0x30 || cmd[i] > 0x39) {
			return ""
		}
	}

	return cmd
}

// Set saves handler for execution upon a matching incoming CTCP event.
// Use SetBg if the handler may take an extended period of time to execute.
// If you would like to have a handler which will catch ALL CTCP requests,
// simply use "*" in place of the command.
func (c *CTCP) Set(cmd string, handler func(client *Client, ctcp CTCPEvent)) {
	if cmd = c.parseCMD(cmd); cmd == "" {
		return
	}

	c.mu.Lock()
	c.handlers[cmd] = CTCPHandler(handler)
	c.mu.Unlock()
}

// SetBg is much like Set, however the handler is executed in the background,
// ensuring that event handling isn't hung during long running tasks. See Set
// for more information.
func (c *CTCP) SetBg(cmd string, handler func(client *Client, ctcp CTCPEvent)) {
	c.Set(cmd, func(client *Client, ctcp CTCPEvent) {
		go handler(client, ctcp)
	})
}

// Clear removes currently setup handler for cmd, if one is set. This will
// also disable default handlers for a specific cmd.
func (c *CTCP) Clear(cmd string) {
	if cmd = c.parseCMD(cmd); cmd == "" {
		return
	}

	c.mu.Lock()
	delete(c.handlers, cmd)
	c.mu.Unlock()
}

// ClearAll removes all currently setup and re-sets the default handlers,
// unless configured not to. See Client.Config.DisableDefaultCTCP.
func (c *CTCP) ClearAll() {
	c.mu.Lock()
	c.handlers = map[string]CTCPHandler{}
	c.mu.Unlock()

	// Register necessary handlers.
	c.addDefaultHandlers()
}

// CTCPHandler is a type that represents the function necessary to
// implement a CTCP handler.
type CTCPHandler func(client *Client, ctcp CTCPEvent)

// addDefaultHandlers adds some useful default CTCP response handlers, unless
// requested by the client not to.
func (c *CTCP) addDefaultHandlers() {
	if c.disableDefault {
		return
	}

	c.SetBg(CTCP_PING, handleCTCPPing)
	c.SetBg(CTCP_PONG, handleCTCPPong)
	c.SetBg(CTCP_VERSION, handleCTCPVersion)
	c.SetBg(CTCP_SOURCE, handleCTCPSource)
	c.SetBg(CTCP_TIME, handleCTCPTime)
}

// handleCTCPPing replies with a ping and whatever was originally requested.
func handleCTCPPing(client *Client, ctcp CTCPEvent) {
	if ctcp.Reply {
		return
	}
	client.SendCTCPReply(ctcp.Source.Name, CTCP_PING, ctcp.Text)
}

// handleCTCPPong replies with a pong.
func handleCTCPPong(client *Client, ctcp CTCPEvent) {
	if ctcp.Reply {
		return
	}
	client.SendCTCPReply(ctcp.Source.Name, CTCP_PONG, "")
}

// handleCTCPVersion replies with the name of the client, Go version, as well
// as the os type (darwin, linux, windows, etc) and architecture type (x86,
// arm, etc).
func handleCTCPVersion(client *Client, ctcp CTCPEvent) {
	client.SendCTCPReplyf(
		ctcp.Source.Name, CTCP_VERSION,
		"girc (github.com/lrstanley/girc) using %s (%s, %s)",
		runtime.Version(), runtime.GOOS, runtime.GOARCH,
	)
}

// handleCTCPSource replies with the public git location of this library.
func handleCTCPSource(client *Client, ctcp CTCPEvent) {
	client.SendCTCPReply(ctcp.Source.Name, CTCP_SOURCE, "https://github.com/lrstanley/girc")
}

// handleCTCPTime replies with a RFC 1123 (Z) formatted version of Go's
// local time.
func handleCTCPTime(client *Client, ctcp CTCPEvent) {
	client.SendCTCPReply(ctcp.Source.Name, CTCP_TIME, ":"+time.Now().Format(time.RFC1123Z))
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/madcowfred/gopoststuff/simplenntp"
	"os"
	"path/filepath"
	"sync"
	"time"
)

type FileData struct {
	path string
	size int64
}

type Totals struct {
	start time.Time
	end   time.Time
	bytes int64
}

func Spawner(filenames []string) {
	var wg sync.WaitGroup
	files := make([]FileData, 0)

	log.Debug("Spawner started")

	// Walk any directories and collect files
	for _, filename := range filenames {
		err := filepath.Walk(filename, func(path string, fi os.FileInfo, err error) error {
			if !fi.IsDir() && fi.Size() > 0 {
				files = append(files, FileData{path: path, size: fi.Size()})
			}
			return err
		})
		if err != nil {
			log.Fatalf("Spawner walk error: %s", err)
		}
	}

	// Make a channel to stuff TimeDatas into
	tdchan := make(chan *simplenntp.TimeData, 100000)

	// Iterate over configured servers
	for name, server := range Config.Server {
		log.Info("[%s] Starting %d connections", name, server.Connections)

		// Make a channel to stuff Articles into
		achan := make(chan *Article, server.Connections)

		// Make a channel to stuff Totals into
		tchan := make(chan *Totals, server.Connections)

		// Start a goroutine to generate articles
		wg.Add(1)
		go func(c chan *Article, files []FileData) {
			defer wg.Done()

			log.Debug("[%s] Article generator started", name)

			mc := NewMmapCache()

			for filenum, fd := range files {
				// Open and mmap the file
				md, err := mc.MapFile(fd.path, len(Config.Server))
				if err != nil {
					log.Fatalf("MapFile error: %s", err)
				}

				// Work out how many parts we need
				parts := fd.size / Config.Global.ArticleSize
				rem := fd.size % Config.Global.ArticleSize
				if rem > 0 {
					parts++
				}

				// Build some articles
				for partnum := int64(0); partnum < parts; partnum++ {
					start := partnum * Config.Global.ArticleSize
					end := min((partnum+1)*Config.Global.ArticleSize, fd.size)

					ad := &ArticleData{
						PartNum:   partnum + 1,
						PartTotal: parts,
						PartSize:  end - start,
						PartBegin: start,
						PartEnd:   end,
						FileNum:   filenum + 1,
						FileTotal: len(files),
						FileSize:  fd.size,
						FileName:  filepath.Base(fd.path),
					}

					var subject string
					if *dirSubjectFlag {
						subject = filepath.Base(filepath.Dir(fd.path))
					} else {
						subject = *subjectFlag
					}

					a := NewArticle(md.data[start:end], ad, subject)
					c <- a

					//log.Debug("%s %d = %d -> %d", fd.path, i, start, end)
				}

				if md.Decrement() {
					err = mc.CloseFile(fd.path)
					if err != nil {
						log.Fatalf("CloseFile error: %s", err)
					}
					log.Debug("[%s] Closed file %s", name, fd.path)
				}
			}

			close(c)
		}(achan, files)

		// Start a goroutine for each individual connection
		for i := 0; i < server.Connections; i++ {
			connID := i + 1

			// Increment the WaitGroup counters
			wg.Add(1)
			go func(achan chan *Article, tchan chan *Totals) {
				// Decrement the counter when the goroutine completes
				defer wg.Done()

				// Connect
				log.Debug("[%s:%02d] Connecting...", name, connID)
				conn, err := simplenntp.Dial(server.Address, server.Port, server.TLS, server.InsecureSSL, tdchan)
				if err != nil {
					log.Fatalf("[%s] Error while connecting: %s", name, err)
				}
				log.Debug("[%s:%02d] Connected", name, connID)

				// Authenticate if required
				if len(server.Username) > 0 {
					log.Debug("[%s:%02d] Authenticating...", name, connID)
					err := conn.Authenticate(server.Username, server.Password)
					if err != nil {
						log.Fatalf("[%s:%02d] Error while authenticating: %s", name, connID, err)
					}
					log.Debug("[%s:%02d] Authenticated", name, connID)
				}

				log.Info("[%s:%02d] Ready", name, connID)

				t := Totals{start: time.Now()}

				// Begin consuming
				for article := range achan {
					err := conn.Post(article.Body, Config.Global.ChunkSize)
					if err != nil {
						log.Warning("[%s:%02d] Post error: %s", name, connID, err)
					}

					t.bytes += int64(len(article.Body))
				}

				t.end = time.Now()
				tchan <- &t

				// dur := t.end.Sub(t.start)
				// speed := float64(t.bytes) / dur.Seconds() / 1024
				// log.Info("[%s:%02d] Posted %d bytes in %s at %.1fKB/s", name, connID, t.bytes, dur.String(), speed)

				// Close the connection
				log.Debug("[%s:%02d] Closing connection", name, connID)
				err = conn.Quit()
				if err != nil {
					log.Warning("[%s:%02d] Error while closing connection: %s", name, connID, err)
				}
			}(achan, tchan)

		}

		// Start a goroutine to print some stats when done, sigh
		wg.Add(1)
		go func(tchan chan *Totals) {
			defer wg.Done()

			minStart := time.Now()
			var maxEnd time.Time
			var totalBytes int64

			for i := 0; i < server.Connections; i++ {
				t := <-tchan
				if t.start.Before(minStart) {
					minStart = t.start
				}
				if t.end.After(maxEnd) {
					maxEnd = t.end
				}
				totalBytes += t.bytes
			}

			dur := maxEnd.Sub(minStart)
			speedKB := float64(totalBytes) / dur.Seconds() / 1024
			totalMB := float64(totalBytes) / 1024 / 1024
			log.Info("[%s] Posted %.1fMiB in %s at %.1fKB/s", name, totalMB, dur.String(), speedKB)
		}(tchan)
	}

	// Start our weird status goroutine
	statusTicker := time.NewTicker(time.Second * 1)
	go StatusLogger(statusTicker, tdchan)

	// Wait for all connections to complete
	wg.Wait()

	// Stop the status ticker
	statusTicker.Stop()
}

func min(a, b int64) int64 {
	if a < b {
		return a
	} else {
		return b
	}
}
<commit_msg>Cleanups<commit_after>package main

import (
	"github.com/madcowfred/gopoststuff/simplenntp"
	"os"
	"path/filepath"
	"sync"
	"time"
)

type FileData struct {
	path string
	size int64
}

type Totals struct {
	start time.Time
	end   time.Time
	bytes int64
}

func Spawner(filenames []string) {
	var wg sync.WaitGroup
	files := make([]FileData, 0)

	log.Debug("Spawner started")

	// Walk any directories and collect files
	for _, filename := range filenames {
		err := filepath.Walk(filename, func(path string, fi os.FileInfo, err error) error {
			if !fi.IsDir() && fi.Size() > 0 {
				files = append(files, FileData{path: path, size: fi.Size()})
			}
			return err
		})
		if err != nil {
			log.Fatalf("Spawner walk error: %s", err)
		}
	}

	// Make a channel to stuff TimeDatas into
	tdchan := make(chan *simplenntp.TimeData, 100000)

	// Iterate over configured servers
	for name, server := range Config.Server {
		log.Info("[%s] Starting %d connections", name, server.Connections)

		// Make a channel to stuff Articles into
		achan := make(chan *Article, server.Connections)

		// Make a channel to stuff Totals into
		tchan := make(chan *Totals, server.Connections)

		// Start a goroutine to generate articles
		wg.Add(1)
		go func(c chan *Article, files []FileData) {
			defer wg.Done()

			log.Debug("[%s] Article generator started", name)

			mc := NewMmapCache()

			for filenum, fd := range files {
				// Open and mmap the file
				md, err := mc.MapFile(fd.path, len(Config.Server))
				if err != nil {
					log.Fatalf("MapFile error: %s", err)
				}

				// Work out how many parts we need
				parts := fd.size / Config.Global.ArticleSize
				rem := fd.size % Config.Global.ArticleSize
				if rem > 0 {
					parts++
				}

				// Build some articles
				for partnum := int64(0); partnum < parts; partnum++ {
					start := partnum * Config.Global.ArticleSize
					end := min((partnum+1)*Config.Global.ArticleSize, fd.size)

					ad := &ArticleData{
						PartNum:   partnum + 1,
						PartTotal: parts,
						PartSize:  end - start,
						PartBegin: start,
						PartEnd:   end,
						FileNum:   filenum + 1,
						FileTotal: len(files),
						FileSize:  fd.size,
						FileName:  filepath.Base(fd.path),
					}

					var subject string
					if *dirSubjectFlag {
						subject = filepath.Base(filepath.Dir(fd.path))
					} else {
						subject = *subjectFlag
					}

					a := NewArticle(md.data[start:end], ad, subject)
					c <- a

					//log.Debug("%s %d = %d -> %d", fd.path, i, start, end)
				}

				if md.Decrement() {
					err = mc.CloseFile(fd.path)
					if err != nil {
						log.Fatalf("CloseFile error: %s", err)
					}
					log.Debug("[%s] Closed file %s", name, fd.path)
				}
			}

			close(c)
		}(achan, files)

		// Start a goroutine for each individual connection
		for i := 0; i < server.Connections; i++ {
			connID := i + 1

			// Increment the WaitGroup counters
			wg.Add(1)
			go func(achan chan *Article, tchan chan *Totals) {
				// Decrement the counter when the goroutine completes
				defer wg.Done()

				// Connect
				log.Debug("[%s:%02d] Connecting...", name, connID)
				conn, err := simplenntp.Dial(server.Address, server.Port, server.TLS, server.InsecureSSL, tdchan)
				if err != nil {
					log.Fatalf("[%s] Error while connecting: %s", name, err)
				}
				log.Debug("[%s:%02d] Connected", name, connID)

				// Authenticate if required
				if len(server.Username) > 0 {
					log.Debug("[%s:%02d] Authenticating...", name, connID)
					err := conn.Authenticate(server.Username, server.Password)
					if err != nil {
						log.Fatalf("[%s:%02d] Error while authenticating: %s", name, connID, err)
					}
					log.Debug("[%s:%02d] Authenticated", name, connID)
				}

				log.Info("[%s:%02d] Ready", name, connID)

				t := Totals{start: time.Now()}

				// Begin consuming
				for article := range achan {
					err := conn.Post(article.Body, Config.Global.ChunkSize)
					if err != nil {
						log.Warning("[%s:%02d] Post error: %s", name, connID, err)
					}

					t.bytes += int64(len(article.Body))
				}

				// Stick our totals struct into the channel
				t.end = time.Now()
				tchan <- &t

				// Close the connection
				log.Debug("[%s:%02d] Closing connection", name, connID)
				err = conn.Quit()
				if err != nil {
					log.Warning("[%s:%02d] Error while closing connection: %s", name, connID, err)
				}
			}(achan, tchan)
		}

		// Start a goroutine to print some stats when done, sigh
		wg.Add(1)
		go func(tchan chan *Totals) {
			defer wg.Done()

			minStart := time.Now()
			var maxEnd time.Time
			var totalBytes int64

			for i := 0; i < server.Connections; i++ {
				t := <-tchan
				if t.start.Before(minStart) {
					minStart = t.start
				}
				if t.end.After(maxEnd) {
					maxEnd = t.end
				}
				totalBytes += t.bytes
			}

			// Calculate and log the result
			dur := maxEnd.Sub(minStart)
			speedKB := float64(totalBytes) / dur.Seconds() / 1024
			totalMB := float64(totalBytes) / 1024 / 1024
			log.Info("[%s] Posted %.1fMiB in %s at %.1fKB/s", name, totalMB, dur.String(), speedKB)
		}(tchan)
	}

	// Start our weird status goroutine
	statusTicker := time.NewTicker(time.Second * 1)
	go StatusLogger(statusTicker, tdchan)

	// Wait for all connections to complete
	wg.Wait()

	// Stop the status ticker
	statusTicker.Stop()
}

// math.Min wants float64s, zzz
func min(a, b int64) int64 {
	if a < b {
		return a
	} else {
		return b
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 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 relay

import (
	"fmt"
	"sync"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type noopStats struct{}

// NewNoopStats returns a no-op implementation of Stats.
func NewNoopStats() Stats {
	return noopStats{}
}

func (n noopStats) Begin(_ CallFrame) CallStats {
	return noopCallStats{}
}

type noopCallStats struct{}

func (n noopCallStats) Succeeded() CallStats      { return noopCallStats{} }
func (n noopCallStats) Failed(_ string) CallStats { return noopCallStats{} }
func (n noopCallStats) End()                      {}

// MockCallStats is a testing spy for the CallStats interface.
type MockCallStats struct {
	sync.Mutex

	succeeded  int
	failedMsgs []string
	ended      int
}

// Succeeded marks the RPC as succeeded.
func (m *MockCallStats) Succeeded() CallStats {
	m.Lock()
	m.succeeded++
	m.Unlock()
	return m
}

// Failed marks the RPC as failed for the provided reason.
func (m *MockCallStats) Failed(reason string) CallStats {
	m.Lock()
	m.failedMsgs = append(m.failedMsgs, reason)
	m.Unlock()
	return m
}

// End halts timer and metric collection for the RPC.
func (m *MockCallStats) End() {
	m.Lock()
	m.ended++
	m.Unlock()
}

// MockStats is a testing spy for the Stats interface.
type MockStats struct {
	mu    sync.Mutex
	stats map[string][]*MockCallStats
}

// NewMockStats constructs a MockStats.
func NewMockStats() *MockStats {
	return &MockStats{
		stats: make(map[string][]*MockCallStats),
	}
}

// Begin starts collecting metrics for an RPC.
func (m *MockStats) Begin(f CallFrame) CallStats {
	return m.Add(f.Caller(), f.Service(), f.Method())
}

// Add explicitly adds a new call along an edge of the call graph.
func (m *MockStats) Add(caller, callee, procedure string) CallStats {
	cs := &MockCallStats{}
	key := m.tripleToKey(caller, callee, procedure)
	m.mu.Lock()
	m.stats[key] = append(m.stats[key], cs)
	m.mu.Unlock()
	return cs
}

// AssertEqual asserts that two MockStats describe the same call graph.
func (m *MockStats) AssertEqual(t testing.TB, expected *MockStats) {
	m.mu.Lock()
	defer m.mu.Unlock()

	expected.mu.Lock()
	defer expected.mu.Unlock()

	if assert.Equal(t, len(expected.stats), len(m.stats), "Found calls along unexpected edges.") {
		for edge := range expected.stats {
			m.assertEdgeEqual(t, expected, edge)
		}
	}
}

func (m *MockStats) assertEdgeEqual(t testing.TB, expected *MockStats, edge string) {
	expectedCalls := expected.stats[edge]
	actualCalls := m.stats[edge]
	if assert.Equal(t, len(expectedCalls), len(actualCalls), "Unexpected number of calls along %s edge.", edge) {
		for i := range expectedCalls {
			m.assertCallEqual(t, expectedCalls[i], actualCalls[i])
		}
	}
}

func (m *MockStats) assertCallEqual(t testing.TB, expected *MockCallStats, actual *MockCallStats) {
	// FIXME: Shouldn't need these locks.
	actual.Lock()
	defer actual.Unlock()

	// Revisit these assertions if we ever need to assert zero or many calls to
	// End.
	require.Equal(t, 1, expected.ended, "Expected call must assert exactly one call to End.")
	require.False(
		t,
		expected.succeeded <= 0 && len(expected.failedMsgs) == 0,
		"Expectation must indicate whether RPC should succeed or fail.",
	)

	assert.Equal(t, expected.succeeded, actual.succeeded, "Unexpected number of successes.")
	assert.Equal(t, expected.failedMsgs, actual.failedMsgs, "Unexpected reasons for RPC failure.")
	assert.Equal(t, expected.ended, actual.ended, "Unexpected number of calls to End.")
}

func (m *MockStats) tripleToKey(caller, callee, procedure string) string {
	return fmt.Sprintf("%s->%s::%s", caller, callee, procedure)
}
<commit_msg>Use waitgroup to order calls and assertions<commit_after>// Copyright (c) 2015 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 relay

import (
	"fmt"
	"sync"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type noopStats struct{}

// NewNoopStats returns a no-op implementation of Stats.
func NewNoopStats() Stats {
	return noopStats{}
}

func (n noopStats) Begin(_ CallFrame) CallStats {
	return noopCallStats{}
}

type noopCallStats struct{}

func (n noopCallStats) Succeeded() CallStats      { return noopCallStats{} }
func (n noopCallStats) Failed(_ string) CallStats { return noopCallStats{} }
func (n noopCallStats) End()                      {}

// MockCallStats is a testing spy for the CallStats interface.
type MockCallStats struct {
	sync.Mutex

	succeeded  int
	failedMsgs []string
	ended      int
	wg         *sync.WaitGroup
}

// Succeeded marks the RPC as succeeded.
func (m *MockCallStats) Succeeded() CallStats {
	m.succeeded++
	return m
}

// Failed marks the RPC as failed for the provided reason.
func (m *MockCallStats) Failed(reason string) CallStats {
	m.failedMsgs = append(m.failedMsgs, reason)
	return m
}

// End halts timer and metric collection for the RPC.
func (m *MockCallStats) End() {
	m.ended++
	m.wg.Done()
}

// MockStats is a testing spy for the Stats interface.
type MockStats struct {
	mu    sync.Mutex
	wg    sync.WaitGroup
	stats map[string][]*MockCallStats
}

// NewMockStats constructs a MockStats.
func NewMockStats() *MockStats {
	return &MockStats{
		stats: make(map[string][]*MockCallStats),
	}
}

// Begin starts collecting metrics for an RPC.
func (m *MockStats) Begin(f CallFrame) CallStats {
	return m.Add(f.Caller(), f.Service(), f.Method())
}

// Add explicitly adds a new call along an edge of the call graph.
func (m *MockStats) Add(caller, callee, procedure string) CallStats {
	m.wg.Add(1)
	cs := &MockCallStats{wg: &m.wg}
	key := m.tripleToKey(caller, callee, procedure)
	m.mu.Lock()
	m.stats[key] = append(m.stats[key], cs)
	m.mu.Unlock()
	return cs
}

// AssertEqual asserts that two MockStats describe the same call graph.
func (m *MockStats) AssertEqual(t testing.TB, expected *MockStats) {
	// Wait for any outstandanding CallStats to end.
	m.wg.Wait()

	m.mu.Lock()
	defer m.mu.Unlock()

	expected.mu.Lock()
	defer expected.mu.Unlock()

	if assert.Equal(t, len(expected.stats), len(m.stats), "Found calls along unexpected edges.") {
		for edge := range expected.stats {
			m.assertEdgeEqual(t, expected, edge)
		}
	}
}

func (m *MockStats) assertEdgeEqual(t testing.TB, expected *MockStats, edge string) {
	expectedCalls := expected.stats[edge]
	actualCalls := m.stats[edge]
	if assert.Equal(t, len(expectedCalls), len(actualCalls), "Unexpected number of calls along %s edge.", edge) {
		for i := range expectedCalls {
			m.assertCallEqual(t, expectedCalls[i], actualCalls[i])
		}
	}
}

func (m *MockStats) assertCallEqual(t testing.TB, expected *MockCallStats, actual *MockCallStats) {
	// Revisit these assertions if we ever need to assert zero or many calls to
	// End.
	require.Equal(t, 1, expected.ended, "Expected call must assert exactly one call to End.")
	require.False(
		t,
		expected.succeeded <= 0 && len(expected.failedMsgs) == 0,
		"Expectation must indicate whether RPC should succeed or fail.",
	)

	assert.Equal(t, expected.succeeded, actual.succeeded, "Unexpected number of successes.")
	assert.Equal(t, expected.failedMsgs, actual.failedMsgs, "Unexpected reasons for RPC failure.")
	assert.Equal(t, expected.ended, actual.ended, "Unexpected number of calls to End.")
}

func (m *MockStats) tripleToKey(caller, callee, procedure string) string {
	return fmt.Sprintf("%s->%s::%s", caller, callee, procedure)
}
<|endoftext|>


<commit_before>// Copyright (c) Jeevanandam M (https://github.com/jeevatkm)
// go-aah/aah source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.

package router

import (
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"aahframework.org/aah/ahttp"
	"aahframework.org/essentials"
	"aahframework.org/test/assert"
)

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Test Path Params methods
//___________________________________

func TestPathParamGet(t *testing.T) {
	pathParameters := PathParams{
		PathParam{"dir", "js"},
		PathParam{"filepath", "/inc/framework.js"},
	}

	fp := pathParameters.Get("filepath")
	assert.Equal(t, "/inc/framework.js", fp)

	notfound := pathParameters.Get("notfound")
	assert.Equal(t, "", notfound)
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Test Router methods
//___________________________________

func TestRouterLoadConfiguration(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	// After loading just couple assertion

	reqCancelBooking1 := createHTTPRequest("localhost:8000", "/hotels/12345/cancel")
	reqCancelBooking1.Method = ahttp.MethodPost
	domain := FindDomain(reqCancelBooking1)
	route, pathParam, rts := domain.Lookup(reqCancelBooking1)
	assert.Equal(t, "cancel_booking", route.Name)
	assert.Equal(t, "12345", pathParam.Get("id"))
	assert.False(t, rts)

	// possible redirect trailing slash
	reqCancelBooking2 := createHTTPRequest("localhost:8000", "/hotels/12345/cancel/")
	reqCancelBooking2.Method = ahttp.MethodPost
	domain = FindDomain(reqCancelBooking2)
	_, _, rts = domain.Lookup(reqCancelBooking2)
	assert.True(t, rts)

	// Lookup by name
	cancelBooking := domain.LookupByName("cancel_booking")
	assert.Equal(t, "hotels_group", cancelBooking.ParentName)
	assert.Equal(t, "cancel_booking", cancelBooking.Name)
	assert.Equal(t, "Hotels", cancelBooking.Controller)
	assert.Equal(t, "POST", cancelBooking.Method)
}

func TestRouterStaticLoadConfiguration(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	// After loading just couple assertion for static

	// /favicon.ico
	req1 := createHTTPRequest("localhost:8000", "/favicon.ico")
	req1.Method = ahttp.MethodGet
	domain := FindDomain(req1)
	route, pathParam, rts := domain.Lookup(req1)
	assert.NotNil(t, pathParam)
	assert.False(t, rts)
	assert.True(t, route.IsStatic)
	assert.Equal(t, "/public/img/favicon.png", route.File)
	assert.Equal(t, "", route.Dir)

	// /static/img/aahframework.png
	req2 := createHTTPRequest("localhost:8000", "/static/img/aahframework.png")
	req2.Method = ahttp.MethodGet
	domain = FindDomain(req2)
	route, pathParam, rts = domain.Lookup(req2)
	assert.NotNil(t, pathParam)
	assert.False(t, rts)
	assert.True(t, route.IsStatic)
	assert.Equal(t, "/public", route.Dir)
	assert.Equal(t, "/img/aahframework.png", pathParam.Get("filepath"))
	assert.Equal(t, "", route.File)
}

func TestRouterErrorLoadConfiguration(t *testing.T) {
	err := createRouter("routes-error.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-error.conf")
}

func TestRouterErrorHostLoadConfiguration(t *testing.T) {
	err := createRouter("routes-no-hostname.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-no-hostname.conf")
}

func TestRouterErrorPathLoadConfiguration(t *testing.T) {
	err := createRouter("routes-path-error.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-path-error.conf")
}

func TestRouterErrorControllerLoadConfiguration(t *testing.T) {
	err := createRouter("routes-controller-error.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-controller-error.conf")
}

func TestRouterErrorStaticPathLoadConfiguration(t *testing.T) {
	err := createRouter("routes-static-path-error.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-static-path-error.conf")
}

func TestRouterErrorStaticPathPatternLoadConfiguration(t *testing.T) {
	err := createRouter("routes-static-path-pattern-error.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-static-path-pattern-error.conf")
}

func TestRouterErrorStaticDirFileLoadConfiguration(t *testing.T) {
	err := createRouter("routes-static-dir-file-error.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-static-dir-file-error.conf")
}

func TestRouterErrorStaticNoDirFileLoadConfiguration(t *testing.T) {
	err := createRouter("routes-static-no-dir-file-error.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-static-no-dir-file-error.conf")
}

func TestRouterNoDomainRoutesFound(t *testing.T) {
	err := createRouter("routes-no-domains.conf")
	assert.Equal(t, ErrNoRoutesConfigFound, err)
}

func TestRouterReloadConfiguration(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	err = Reload()
	assert.FailNowOnError(t, err, "")

	// TODO validate routes after reload
}

func TestRouterDomainConfig(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	domain := FindDomain(createHTTPRequest("localhost:8000", ""))
	assert.NotNil(t, domain)

	domain = FindDomain(createHTTPRequest("www.aahframework.org", ""))
	assert.Nil(t, domain)
}

func TestRouterDomainAddresses(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	addresses := DomainAddresses()
	assert.Equal(t, "localhost:8000", addresses[0])
}

func TestAllControllerMethods(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	methods := AllControllerMethods()
	assert.NotNil(t, methods)
}

func TestIsDefaultAction(t *testing.T) {
	v1 := IsDefaultAction("Index")
	assert.True(t, v1)

	v2 := IsDefaultAction("Head")
	assert.True(t, v2)

	v3 := IsDefaultAction("Show")
	assert.False(t, v3)
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Test Domain methods
//___________________________________

func TestDomainAllowed(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	req := createHTTPRequest("localhost:8000", "/")
	domain := FindDomain(req)
	allow := domain.Allowed(ahttp.MethodGet, "/")
	assert.NotNil(t, allow)
	assert.True(t, strings.Contains(allow, ahttp.MethodOptions))

	domain = FindDomain(req)
	allow = domain.Allowed(ahttp.MethodPost, "*")
	assert.NotNil(t, allow)
	assert.True(t, strings.Contains(allow, ahttp.MethodOptions))

	// domain not exists
	reqNotExists := createHTTPRequest("notexists:8000", "/")
	domain = FindDomain(reqNotExists)
	assert.Nil(t, domain)

	// TODO do more
}

func TestDomainReverseURL(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	req := createHTTPRequest("localhost:8000", "/")
	domain := FindDomain(req)

	// route name not exists
	emptyURL := domain.Reverse("not_exists_routename")
	assert.Equal(t, "", emptyURL)

	// non key-value pair value leads to error
	emptyURL = domain.Reverse("book_hotels", 12345678)
	assert.Equal(t, "", emptyURL)

	// not enough arguments
	emptyURL = domain.Reverse("book_hotels")
	assert.Equal(t, "", emptyURL)

	// incorrect key name scenario
	emptyURL = domain.Reverse("book_hotels", map[string]string{
		"idvalue": "12345678",
	})
	assert.Equal(t, "", emptyURL)

	// static URL
	loginURL := domain.Reverse("login")
	assert.Equal(t, "/login", loginURL)

	// success scenario
	bookingURL := domain.Reverse("book_hotels", map[string]string{
		"id": "12345678",
	})
	assert.Equal(t, "/hotels/12345678/booking", bookingURL)

	bookingURL = domain.Reverse("book_hotels", map[string]string{
		"id":     "12345678",
		"param1": "param1value",
		"param2": "param2value",
	})
	assert.Equal(t, "/hotels/12345678/booking?param1=param1value&param2=param2value", bookingURL)
}

func TestDomainAddRoute(t *testing.T) {
	domain := &Domain{
		Host: "aahframework.org",
	}

	route1 := &Route{
		Name:       "route1",
		Path:       "/info/:user/project/:project",
		Method:     "GET",
		Controller: "Info",
		Action:     "ShowProject",
	}
	err := domain.addRoute(route1)
	assert.FailNowOnError(t, err, "unexpected error")

	route2 := &Route{
		Name:       "index",
		Path:       "/",
		Method:     "GET",
		Controller: "App",
		Action:     "Index",
	}
	err = domain.addRoute(route2)
	assert.FailNowOnError(t, err, "unexpected error")

	routeError := &Route{
		Name:       "route_error",
		Path:       "/",
		Method:     "GET",
		Controller: "App",
		Action:     "Index",
	}
	err = domain.addRoute(routeError)
	assert.True(t, strings.Contains(err.Error(), "value is already registered"))
}

func createRouter(filename string) error {
	wd, _ := os.Getwd()
	return Load(filepath.Join(wd, "testdata", filename))
}

func createHTTPRequest(host, path string) *ahttp.Request {
	req := &ahttp.Request{
		Raw: &http.Request{Host: host},
	}

	req.Host = req.Raw.Host

	if !ess.IsStrEmpty(path) {
		req.Raw.URL = &url.URL{Path: path}
		req.Path = req.Raw.URL.Path
	}

	return req
}
<commit_msg>test case fix<commit_after>// Copyright (c) Jeevanandam M (https://github.com/jeevatkm)
// go-aah/aah source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.

package router

import (
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"aahframework.org/aah/ahttp"
	"aahframework.org/essentials"
	"aahframework.org/test/assert"
)

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Test Path Params methods
//___________________________________

func TestPathParamGet(t *testing.T) {
	pathParameters := PathParams{
		PathParam{"dir", "js"},
		PathParam{"filepath", "/inc/framework.js"},
	}

	fp := pathParameters.Get("filepath")
	assert.Equal(t, "/inc/framework.js", fp)

	notfound := pathParameters.Get("notfound")
	assert.Equal(t, "", notfound)
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Test Router methods
//___________________________________

func TestRouterLoadConfiguration(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	// After loading just couple assertion

	reqCancelBooking1 := createHTTPRequest("localhost:8000", "/hotels/12345/cancel")
	reqCancelBooking1.Method = ahttp.MethodPost
	domain := FindDomain(reqCancelBooking1)
	route, pathParam, rts := domain.Lookup(reqCancelBooking1)
	assert.Equal(t, "cancel_booking", route.Name)
	assert.Equal(t, "12345", pathParam.Get("id"))
	assert.False(t, rts)

	// possible redirect trailing slash
	reqCancelBooking2 := createHTTPRequest("localhost:8000", "/hotels/12345/cancel/")
	reqCancelBooking2.Method = ahttp.MethodPost
	domain = FindDomain(reqCancelBooking2)
	_, _, rts = domain.Lookup(reqCancelBooking2)
	assert.True(t, rts)

	// Lookup by name
	cancelBooking := domain.LookupByName("cancel_booking")
	assert.Equal(t, "hotels_group", cancelBooking.ParentName)
	assert.Equal(t, "cancel_booking", cancelBooking.Name)
	assert.Equal(t, "Hotels", cancelBooking.Controller)
	assert.Equal(t, "POST", cancelBooking.Method)
}

func TestRouterStaticLoadConfiguration(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	// After loading just couple assertion for static

	// /favicon.ico
	req1 := createHTTPRequest("localhost:8000", "/favicon.ico")
	req1.Method = ahttp.MethodGet
	domain := FindDomain(req1)
	route, pathParam, rts := domain.Lookup(req1)
	assert.NotNil(t, pathParam)
	assert.False(t, rts)
	assert.True(t, route.IsStatic)
	assert.Equal(t, "/public/img/favicon.png", route.File)
	assert.Equal(t, "", route.Dir)

	// /static/img/aahframework.png
	req2 := createHTTPRequest("localhost:8000", "/static/img/aahframework.png")
	req2.Method = ahttp.MethodGet
	domain = FindDomain(req2)
	route, pathParam, rts = domain.Lookup(req2)
	assert.NotNil(t, pathParam)
	assert.False(t, rts)
	assert.True(t, route.IsStatic)
	assert.Equal(t, "/public", route.Dir)
	assert.Equal(t, "/img/aahframework.png", pathParam.Get("filepath"))
	assert.Equal(t, "", route.File)
}

func TestRouterErrorLoadConfiguration(t *testing.T) {
	err := createRouter("routes-error.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-error.conf")
}

func TestRouterErrorHostLoadConfiguration(t *testing.T) {
	err := createRouter("routes-no-hostname.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-no-hostname.conf")
}

func TestRouterErrorPathLoadConfiguration(t *testing.T) {
	err := createRouter("routes-path-error.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-path-error.conf")
}

func TestRouterErrorControllerLoadConfiguration(t *testing.T) {
	err := createRouter("routes-controller-error.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-controller-error.conf")
}

func TestRouterErrorStaticPathLoadConfiguration(t *testing.T) {
	err := createRouter("routes-static-path-error.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-static-path-error.conf")
}

func TestRouterErrorStaticPathPatternLoadConfiguration(t *testing.T) {
	err := createRouter("routes-static-path-pattern-error.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-static-path-pattern-error.conf")
}

func TestRouterErrorStaticDirFileLoadConfiguration(t *testing.T) {
	err := createRouter("routes-static-dir-file-error.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-static-dir-file-error.conf")
}

func TestRouterErrorStaticNoDirFileLoadConfiguration(t *testing.T) {
	err := createRouter("routes-static-no-dir-file-error.conf")
	assert.NotNilf(t, err, "expected error loading '%v'", "routes-static-no-dir-file-error.conf")
}

func TestRouterNoDomainRoutesFound(t *testing.T) {
	err := createRouter("routes-no-domains.conf")
	assert.Equal(t, ErrNoRoutesConfigFound, err)
}

func TestRouterReloadConfiguration(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	err = Reload()
	assert.FailNowOnError(t, err, "")

	// TODO validate routes after reload
}

func TestRouterDomainConfig(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	domain := FindDomain(createHTTPRequest("localhost:8000", ""))
	assert.NotNil(t, domain)

	domain = FindDomain(createHTTPRequest("www.aahframework.org", ""))
	assert.Nil(t, domain)
}

func TestRouterDomainAddresses(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	addresses := DomainAddresses()
	assert.Equal(t, "localhost:8000", addresses[0])
}

func TestRegisteredActions(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	methods := RegisteredActions()
	assert.NotNil(t, methods)
}

func TestIsDefaultAction(t *testing.T) {
	v1 := IsDefaultAction("Index")
	assert.True(t, v1)

	v2 := IsDefaultAction("Head")
	assert.True(t, v2)

	v3 := IsDefaultAction("Show")
	assert.False(t, v3)
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Test Domain methods
//___________________________________

func TestDomainAllowed(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	req := createHTTPRequest("localhost:8000", "/")
	domain := FindDomain(req)
	allow := domain.Allowed(ahttp.MethodGet, "/")
	assert.NotNil(t, allow)
	assert.True(t, strings.Contains(allow, ahttp.MethodOptions))

	domain = FindDomain(req)
	allow = domain.Allowed(ahttp.MethodPost, "*")
	assert.NotNil(t, allow)
	assert.True(t, strings.Contains(allow, ahttp.MethodOptions))

	// domain not exists
	reqNotExists := createHTTPRequest("notexists:8000", "/")
	domain = FindDomain(reqNotExists)
	assert.Nil(t, domain)

	// TODO do more
}

func TestDomainReverseURL(t *testing.T) {
	err := createRouter("routes.conf")
	assert.FailNowOnError(t, err, "")

	req := createHTTPRequest("localhost:8000", "/")
	domain := FindDomain(req)

	// route name not exists
	emptyURL := domain.Reverse("not_exists_routename")
	assert.Equal(t, "", emptyURL)

	// non key-value pair value leads to error
	emptyURL = domain.Reverse("book_hotels", 12345678)
	assert.Equal(t, "", emptyURL)

	// not enough arguments
	emptyURL = domain.Reverse("book_hotels")
	assert.Equal(t, "", emptyURL)

	// incorrect key name scenario
	emptyURL = domain.Reverse("book_hotels", map[string]string{
		"idvalue": "12345678",
	})
	assert.Equal(t, "", emptyURL)

	// static URL
	loginURL := domain.Reverse("login")
	assert.Equal(t, "/login", loginURL)

	// success scenario
	bookingURL := domain.Reverse("book_hotels", map[string]string{
		"id": "12345678",
	})
	assert.Equal(t, "/hotels/12345678/booking", bookingURL)

	bookingURL = domain.Reverse("book_hotels", map[string]string{
		"id":     "12345678",
		"param1": "param1value",
		"param2": "param2value",
	})
	assert.Equal(t, "/hotels/12345678/booking?param1=param1value&param2=param2value", bookingURL)
}

func TestDomainAddRoute(t *testing.T) {
	domain := &Domain{
		Host: "aahframework.org",
	}

	route1 := &Route{
		Name:       "route1",
		Path:       "/info/:user/project/:project",
		Method:     "GET",
		Controller: "Info",
		Action:     "ShowProject",
	}
	err := domain.addRoute(route1)
	assert.FailNowOnError(t, err, "unexpected error")

	route2 := &Route{
		Name:       "index",
		Path:       "/",
		Method:     "GET",
		Controller: "App",
		Action:     "Index",
	}
	err = domain.addRoute(route2)
	assert.FailNowOnError(t, err, "unexpected error")

	routeError := &Route{
		Name:       "route_error",
		Path:       "/",
		Method:     "GET",
		Controller: "App",
		Action:     "Index",
	}
	err = domain.addRoute(routeError)
	assert.True(t, strings.Contains(err.Error(), "value is already registered"))
}

func createRouter(filename string) error {
	wd, _ := os.Getwd()
	return Load(filepath.Join(wd, "testdata", filename))
}

func createHTTPRequest(host, path string) *ahttp.Request {
	req := &ahttp.Request{
		Raw: &http.Request{Host: host},
	}

	req.Host = req.Raw.Host

	if !ess.IsStrEmpty(path) {
		req.Raw.URL = &url.URL{Path: path}
		req.Path = req.Raw.URL.Path
	}

	return req
}
<|endoftext|>