<commit_before>package ditaconvert

import (
	"encoding/xml"
	"fmt"
	"io"
	"net/url"
	"path"
	"strings"

	"github.com/raintreeinc/ditaconvert/html"
)

func TODO(context *Context, dec *xml.Decoder, start xml.StartElement) error {
	context.Encoder.WriteRaw(`<div class="conversion-error">TODO ` + start.Name.Local + `</div>`)
	dec.Skip()
	return nil
}

/* TODO: unify all rules
func Rename(tag string, attrs... xml.Attr) TokenProcessor {
	return func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
		start.Name.Local = tag
		return context.EmitWithChildren(dec, start)
	}
}

func RenameWithClass(tag string, class string) TokenProcessor {
	return func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
		start.Name.Local = tag
		setAttr(&start, "class", class)
		return context.EmitWithChildren(dec, start)
	}
}*/

func NewDefaultRules() *Rules {
	return &Rules{
		Rename: map[string]Renaming{
			// conversion
			"xref": {"a", ""},
			"link": {"a", ""},

			//lists
			"choices":         {"ul", ""},
			"choice":          {"li", ""},
			"steps-unordered": {"ul", ""},
			"steps":           {"ol", ""},
			"step":            {"li", ""}, // handled with a custom rule
			"substeps":        {"ol", ""},
			"substep":         {"li", ""}, // handled with a custom rule

			"b":     {"strong", ""},
			"i":     {"em", ""},
			"lines": {"pre", ""},

			"codeblock": {"pre", "codeblock"},
			"pre":       {"pre", "pre"},

			"codeph":      {"samp", "codeph"},
			"cmdname":     {"span", "cmdname"},
			"cmd":         {"span", "cmd"},
			"shortcut":    {"span", "shortcut"},
			"wintitle":    {"span", "wintitle"},
			"filepath":    {"span", "filepath"},
			"menucascade": {"span", "menucascade"},
			"msgph":       {"span", "msgph"},
			"reportitem":  {"span", "reportitem"},

			"option":   {"span", "option"},
			"synph":    {"span", ""},
			"delim":    {"span", ""},
			"sep":      {"span", ""},
			"parmname": {"span", ""},

			"userinput": {"kbd", "userinput"},

			"image": {"img", ""},

			// ui
			"uicontrol": {"b", ""},

			// divs
			"context":    {"div", ""},
			"result":     {"div", ""},
			"stepresult": {"div", ""},
			"stepxmp":    {"div", ""},
			"info":       {"div", ""},
			"note":       {"div", ""},
			"refsyn":     {"div", ""},
			"bodydiv":    {"div", ""},
			"fig":        {"div", ""},

			"prereq":  {"div", ""},
			"postreq": {"div", ""},

			"colspec": {"colgroup", ""},

			"row":   {"tr", ""},
			"entry": {"td", ""},

			// RAINTREE SPECIFIC
			"keystroke": {"b", "key"},
			"secright":  {"span", "secright"},

			// faq
			"faq":          {"dl", ""},
			"faq-item":     {"div", ""},
			"faq-question": {"dt", "dlterm"},
			"faq-answer":   {"dd", ""},

			//UI items
			"ui-item-list": {"dl", ""},
			"ui-item":      {"div", ""},
			//"ui-item-name":        {"dt", "dlterm"},
			"ui-item-description": {"dd", ""},

			// setup options
			"setup-options":            {"dl", ""},
			"setup-option":             {"div", ""},
			"setup-option-name":        {"dt", "dlterm"},
			"setup-option-description": {"dd", ""},

			"settingdesc": {"div", ""},
			"settingname": {"h2", ""},

			"section":    {"div", "section"},
			"example":    {"div", ""},
			"sectiondiv": {"div", ""},
			"title":      {"h2", "sectiontitle"},

			// ??
			"dlentry": {"div", ""},
			"dt":      {"dt", "dlterm"},

			"settings": {"div", "settings"},
			"setting":  {"div", "setting"},
		},
		Skip: map[string]bool{
			"br":            true,
			"draft-comment": true,

			// RAINTREE SPECIFIC
			"settinghead": true,
		},
		Unwrap: map[string]bool{
			"tgroup": true,
		},
		Custom: map[string]TokenProcessor{
			"a": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				var href, desc string
				var internal bool

				href = getAttr(&start, "href")
				if href != "" {
					href, _, desc, internal = context.ResolveLinkInfo(href)
					setAttr(&start, "href", href)
				}

				if desc != "" && getAttr(&start, "title") == "" {
					setAttr(&start, "title", desc)
				}

				setAttr(&start, "scope", "")
				if internal && href != "" {
					//setAttr(&start, "data-link", href)
				}

				if getAttr(&start, "format") != "" && href != "" {
					setAttr(&start, "format", "")
					ext := strings.ToLower(path.Ext(href))
					if ext == ".pdf" || ext == ".doc" || ext == ".xml" || ext == ".rtf" || ext == ".zip" || ext == ".exe" {
						setAttr(&start, "download", path.Base(href))
					} else {
						setAttr(&start, "target", "_blank")
					}
				}

				return context.EmitWithChildren(dec, start)
			},
			"img": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				href := getAttr(&start, "href")
				//setAttr(&start, "src", context.InlinedImageURL(href))
				setAttr(&start, "src", href)
				setAttr(&start, "href", "")

				placement := getAttr(&start, "placement")
				setAttr(&start, "placement", "")
				if placement == "break" {
					context.Encoder.WriteStart("p",
						xml.Attr{Name: xml.Name{Local: "class"}, Value: "image"})
				}

				err := context.EmitWithChildren(dec, start)

				if placement == "break" {
					context.Encoder.WriteEnd("p")
				}

				return err
			},
			"data": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				datatype := strings.ToLower(getAttr(&start, "datatype"))
				if datatype == "rttutorial" {
					dec.Skip()
					href := getAttr(&start, "href")

					const videof = `` +
						`<video controls>` +
						`	<source src="%s" type="video/mp4">` +
						`	<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" ` +
						`		<param name="SRC" value="http://ie.microsoft.com/testdrive/IEBlog/Common/player.swf?file=%s">` +
						`		<p>Video playback not supported</p>` +
						`	</object>` +
						`</video>`

					srcurl := html.NormalizeURL(href)
					urlarg := url.QueryEscape(href)

					context.Encoder.WriteRaw(fmt.Sprintf(videof, srcurl, urlarg))
					return nil
				}

				return context.EmitWithChildren(dec, start)
			},
			"imagemap": ConvertImageMap,

			// RAINTREE SPECIFIC
			"settingdefault": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				val, _ := html.XMLText(dec)
				if val != "" {
					return context.Encoder.WriteRaw("
Default value: " + val + "
")
				}
				return nil
			},
			"settinglevels": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				context.check(context.Encoder.WriteRaw("
Levels where it can be defined:
"))
				return context.EmitWithChildren(dec, start)
			},
			"settingsample": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				context.check(context.Encoder.WriteRaw("
Example:
"))
				return context.EmitWithChildren(dec, start)
			},

			"note": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				typ := getAttr(&start, "type")
				if typ == "other" {
					typ = getAttr(&start, "othertype")
				}
				if typ == "" {
					typ = "note"
				}
				setAttr(&start, "type", "")
				setAttr(&start, "othertype", "")
				setAttr(&start, "class", "note")
				mdiclass := "note-outline"
				switch typ {
				case "tip":
					mdiclass = "lightbulb-outline"
				case "caution":
					mdiclass = "alert"
				case "Extra":
					mdiclass = "key"
				case "Rev-Edition":
					mdiclass = "elevation-rise"
				case "PDF":
					mdiclass = "book-open"
				}

				context.check(context.Encoder.WriteStart("div", start.Attr...))
				context.check(context.Encoder.WriteRaw(`<i class="mdi mdi-` + mdiclass + `" title="` + typ + `"></i> `))
				context.check(context.Encoder.WriteStart("span"))
				err := context.Recurse(dec)
				context.check(context.Encoder.WriteEnd("span"))
				context.check(context.Encoder.WriteEnd("div"))
				return err
			},
			"menucascade": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				start.Name.Local = "span"

				setAttr(&start, "class", "menucascade")
				// encode starting tag and attributes
				if err := context.Encoder.Encode(start); err != nil {
					return err
				}

				first := true

				// recurse on child tokens
				for {
					token, err := dec.Token()
					if err != nil {
						if err == io.EOF {
							return nil
						}
						return err
					}

					if _, ended := token.(xml.EndElement); ended {
						break
					}
					if _, starting := token.(xml.StartElement); starting {
						if !first {
							context.Encoder.WriteRaw(">")
						}
						first = false
					}
					if err := context.Handle(dec, token); err != nil {
						return err
					}
				}

				// always encode ending tag
				context.check(context.Encoder.Encode(xml.EndElement{start.Name}))

				return nil
			},

			"ui-item-name": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				start.Name.Local = "dt"

				setAttr(&start, "class", "ui-item-name")
				// encode starting tag and attributes
				if err := context.Encoder.Encode(start); err != nil {
					return err
				}

				first := true

				// recurse on child tokens
				for {
					token, err := dec.Token()
					if err != nil {
						if err == io.EOF {
							return nil
						}
						return err
					}

					if _, ended := token.(xml.EndElement); ended {
						break
					}
					if _, starting := token.(xml.StartElement); starting {
						if !first {
							context.Encoder.WriteRaw(", ")
						}
						first = false
					}
					if err := context.Handle(dec, token); err != nil {
						return err
					}
				}

				// always encode ending tag
				context.check(context.Encoder.Encode(xml.EndElement{start.Name}))

				return nil
			},

			"simpletable": HandleSimpleTable,
			"table":       HandleTable,

			"step": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				start.Name.Local = "li"

				context.check(context.Encoder.Encode(start))
				if getAttr(&start, "importance") == "optional" {
					context.Encoder.WriteRaw("(Optional) ")
				}
				err := context.Recurse(dec)
				context.check(context.Encoder.Encode(xml.EndElement{start.Name}))

				return err
			},
			"substep": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				start.Name.Local = "li"

				context.check(context.Encoder.Encode(start))
				if getAttr(&start, "importance") == "optional" {
					context.Encoder.WriteRaw("(Optional) ")
				}
				err := context.Recurse(dec)
				context.check(context.Encoder.Encode(xml.EndElement{start.Name}))

				return err
			},
		},
	}
}
<commit_msg>Add varname support<commit_after>package ditaconvert

import (
	"encoding/xml"
	"fmt"
	"io"
	"net/url"
	"path"
	"strings"

	"github.com/raintreeinc/ditaconvert/html"
)

func TODO(context *Context, dec *xml.Decoder, start xml.StartElement) error {
	context.Encoder.WriteRaw(`<div class="conversion-error">TODO ` + start.Name.Local + `</div>`)
	dec.Skip()
	return nil
}

/* TODO: unify all rules
func Rename(tag string, attrs... xml.Attr) TokenProcessor {
	return func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
		start.Name.Local = tag
		return context.EmitWithChildren(dec, start)
	}
}

func RenameWithClass(tag string, class string) TokenProcessor {
	return func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
		start.Name.Local = tag
		setAttr(&start, "class", class)
		return context.EmitWithChildren(dec, start)
	}
}*/

func NewDefaultRules() *Rules {
	return &Rules{
		Rename: map[string]Renaming{
			// conversion
			"xref": {"a", ""},
			"link": {"a", ""},

			//lists
			"choices":         {"ul", ""},
			"choice":          {"li", ""},
			"steps-unordered": {"ul", ""},
			"steps":           {"ol", ""},
			"step":            {"li", ""}, // handled with a custom rule
			"substeps":        {"ol", ""},
			"substep":         {"li", ""}, // handled with a custom rule

			"b":     {"strong", ""},
			"i":     {"em", ""},
			"lines": {"pre", ""},

			"codeblock": {"pre", "codeblock"},
			"pre":       {"pre", "pre"},

			"codeph":      {"samp", "codeph"},
			"cmdname":     {"span", "cmdname"},
			"cmd":         {"span", "cmd"},
			"shortcut":    {"span", "shortcut"},
			"wintitle":    {"span", "wintitle"},
			"filepath":    {"span", "filepath"},
			"menucascade": {"span", "menucascade"},
			"msgph":       {"span", "msgph"},
			"reportitem":  {"span", "reportitem"},
			"varname":     {"span", "varname"},

			"option":   {"span", "option"},
			"synph":    {"span", ""},
			"delim":    {"span", ""},
			"sep":      {"span", ""},
			"parmname": {"span", ""},

			"userinput": {"kbd", "userinput"},

			"image": {"img", ""},

			// ui
			"uicontrol": {"b", ""},

			// divs
			"context":    {"div", ""},
			"result":     {"div", ""},
			"stepresult": {"div", ""},
			"stepxmp":    {"div", ""},
			"info":       {"div", ""},
			"note":       {"div", ""},
			"refsyn":     {"div", ""},
			"bodydiv":    {"div", ""},
			"fig":        {"div", ""},

			"prereq":  {"div", ""},
			"postreq": {"div", ""},

			"colspec": {"colgroup", ""},

			"row":   {"tr", ""},
			"entry": {"td", ""},

			// RAINTREE SPECIFIC
			"keystroke": {"b", "key"},
			"secright":  {"span", "secright"},

			// faq
			"faq":          {"dl", ""},
			"faq-item":     {"div", ""},
			"faq-question": {"dt", "dlterm"},
			"faq-answer":   {"dd", ""},

			//UI items
			"ui-item-list": {"dl", ""},
			"ui-item":      {"div", ""},
			//"ui-item-name":        {"dt", "dlterm"},
			"ui-item-description": {"dd", ""},

			// setup options
			"setup-options":            {"dl", ""},
			"setup-option":             {"div", ""},
			"setup-option-name":        {"dt", "dlterm"},
			"setup-option-description": {"dd", ""},

			"settingdesc": {"div", ""},
			"settingname": {"h2", ""},

			"section":    {"div", "section"},
			"example":    {"div", ""},
			"sectiondiv": {"div", ""},
			"title":      {"h2", "sectiontitle"},

			// ??
			"dlentry": {"div", ""},
			"dt":      {"dt", "dlterm"},

			"settings": {"div", "settings"},
			"setting":  {"div", "setting"},
		},
		Skip: map[string]bool{
			"br":            true,
			"draft-comment": true,

			// RAINTREE SPECIFIC
			"settinghead": true,
		},
		Unwrap: map[string]bool{
			"tgroup": true,
		},
		Custom: map[string]TokenProcessor{
			"a": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				var href, desc string
				var internal bool

				href = getAttr(&start, "href")
				if href != "" {
					href, _, desc, internal = context.ResolveLinkInfo(href)
					setAttr(&start, "href", href)
				}

				if desc != "" && getAttr(&start, "title") == "" {
					setAttr(&start, "title", desc)
				}

				setAttr(&start, "scope", "")
				if internal && href != "" {
					//setAttr(&start, "data-link", href)
				}

				if getAttr(&start, "format") != "" && href != "" {
					setAttr(&start, "format", "")
					ext := strings.ToLower(path.Ext(href))
					if ext == ".pdf" || ext == ".doc" || ext == ".xml" || ext == ".rtf" || ext == ".zip" || ext == ".exe" {
						setAttr(&start, "download", path.Base(href))
					} else {
						setAttr(&start, "target", "_blank")
					}
				}

				return context.EmitWithChildren(dec, start)
			},
			"img": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				href := getAttr(&start, "href")
				//setAttr(&start, "src", context.InlinedImageURL(href))
				setAttr(&start, "src", href)
				setAttr(&start, "href", "")

				placement := getAttr(&start, "placement")
				setAttr(&start, "placement", "")
				if placement == "break" {
					context.Encoder.WriteStart("p",
						xml.Attr{Name: xml.Name{Local: "class"}, Value: "image"})
				}

				err := context.EmitWithChildren(dec, start)

				if placement == "break" {
					context.Encoder.WriteEnd("p")
				}

				return err
			},
			"data": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				datatype := strings.ToLower(getAttr(&start, "datatype"))
				if datatype == "rttutorial" {
					dec.Skip()
					href := getAttr(&start, "href")

					const videof = `` +
						`<video controls>` +
						`	<source src="%s" type="video/mp4">` +
						`	<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" ` +
						`		<param name="SRC" value="http://ie.microsoft.com/testdrive/IEBlog/Common/player.swf?file=%s">` +
						`		<p>Video playback not supported</p>` +
						`	</object>` +
						`</video>`

					srcurl := html.NormalizeURL(href)
					urlarg := url.QueryEscape(href)

					context.Encoder.WriteRaw(fmt.Sprintf(videof, srcurl, urlarg))
					return nil
				}

				return context.EmitWithChildren(dec, start)
			},
			"imagemap": ConvertImageMap,

			// RAINTREE SPECIFIC
			"settingdefault": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				val, _ := html.XMLText(dec)
				if val != "" {
					return context.Encoder.WriteRaw("
Default value: " + val + "
")
				}
				return nil
			},
			"settinglevels": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				context.check(context.Encoder.WriteRaw("
Levels where it can be defined:
"))
				return context.EmitWithChildren(dec, start)
			},
			"settingsample": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				context.check(context.Encoder.WriteRaw("
Example:
"))
				return context.EmitWithChildren(dec, start)
			},

			"note": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				typ := getAttr(&start, "type")
				if typ == "other" {
					typ = getAttr(&start, "othertype")
				}
				if typ == "" {
					typ = "note"
				}
				setAttr(&start, "type", "")
				setAttr(&start, "othertype", "")
				setAttr(&start, "class", "note")
				mdiclass := "note-outline"
				switch typ {
				case "tip":
					mdiclass = "lightbulb-outline"
				case "caution":
					mdiclass = "alert"
				case "Extra":
					mdiclass = "key"
				case "Rev-Edition":
					mdiclass = "elevation-rise"
				case "PDF":
					mdiclass = "book-open"
				}

				context.check(context.Encoder.WriteStart("div", start.Attr...))
				context.check(context.Encoder.WriteRaw(`<i class="mdi mdi-` + mdiclass + `" title="` + typ + `"></i> `))
				context.check(context.Encoder.WriteStart("span"))
				err := context.Recurse(dec)
				context.check(context.Encoder.WriteEnd("span"))
				context.check(context.Encoder.WriteEnd("div"))
				return err
			},
			"menucascade": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				start.Name.Local = "span"

				setAttr(&start, "class", "menucascade")
				// encode starting tag and attributes
				if err := context.Encoder.Encode(start); err != nil {
					return err
				}

				first := true

				// recurse on child tokens
				for {
					token, err := dec.Token()
					if err != nil {
						if err == io.EOF {
							return nil
						}
						return err
					}

					if _, ended := token.(xml.EndElement); ended {
						break
					}
					if _, starting := token.(xml.StartElement); starting {
						if !first {
							context.Encoder.WriteRaw(">")
						}
						first = false
					}
					if err := context.Handle(dec, token); err != nil {
						return err
					}
				}

				// always encode ending tag
				context.check(context.Encoder.Encode(xml.EndElement{start.Name}))

				return nil
			},

			"ui-item-name": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				start.Name.Local = "dt"

				setAttr(&start, "class", "ui-item-name")
				// encode starting tag and attributes
				if err := context.Encoder.Encode(start); err != nil {
					return err
				}

				first := true

				// recurse on child tokens
				for {
					token, err := dec.Token()
					if err != nil {
						if err == io.EOF {
							return nil
						}
						return err
					}

					if _, ended := token.(xml.EndElement); ended {
						break
					}
					if _, starting := token.(xml.StartElement); starting {
						if !first {
							context.Encoder.WriteRaw(", ")
						}
						first = false
					}
					if err := context.Handle(dec, token); err != nil {
						return err
					}
				}

				// always encode ending tag
				context.check(context.Encoder.Encode(xml.EndElement{start.Name}))

				return nil
			},

			"simpletable": HandleSimpleTable,
			"table":       HandleTable,

			"step": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				start.Name.Local = "li"

				context.check(context.Encoder.Encode(start))
				if getAttr(&start, "importance") == "optional" {
					context.Encoder.WriteRaw("(Optional) ")
				}
				err := context.Recurse(dec)
				context.check(context.Encoder.Encode(xml.EndElement{start.Name}))

				return err
			},
			"substep": func(context *Context, dec *xml.Decoder, start xml.StartElement) error {
				start.Name.Local = "li"

				context.check(context.Encoder.Encode(start))
				if getAttr(&start, "importance") == "optional" {
					context.Encoder.WriteRaw("(Optional) ")
				}
				err := context.Recurse(dec)
				context.check(context.Encoder.Encode(xml.EndElement{start.Name}))

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


<commit_before>package string_array

func Contains(test string, set []string) bool {
	for _, member := range set {
		if test == member {
			return true
		}
	}

	return false
}

// Compares array1 against a2 and returns the values in array1 that are not present in a2.
func Diff(tests, set []string) []string {

	missing := make([]string, 0)

	for _, test := range tests {
		if Contains(test, set) {
			continue
		}

		missing = append(missing, test)
	}

	return missing
}
<commit_msg>Add string_array.ReadFile() and WriteFile()<commit_after>package string_array

import (
	"bufio"
	"io"
	"os"
)

func Contains(test string, set []string) bool {
	for _, member := range set {
		if test == member {
			return true
		}
	}

	return false
}

// Compares array1 against a2 and returns the values in array1 that are not present in a2.
func Diff(tests, set []string) []string {

	missing := make([]string, 0)

	for _, test := range tests {
		if Contains(test, set) {
			continue
		}

		missing = append(missing, test)
	}

	return missing
}

func WriteFile(filename string, string_sets ...[]string) error {

	var f *os.File

	if f, err := os.Create(filename); err != nil {
		return err
	}

	defer f.Close()

	for _, set := range string_sets {
		for _, lines := range set {
			if _, err := f.WriteString(line); err != nil {
				return err
			}
		}
	}

	return nil
}

func ReadFile(filename string) (lines []string) {
	var (
		f    *os.File
		line string
		err  error
	)

	if file, err := os.Open(filename); err != nil {
		panic(err)
	}

	defer file.Close()

	reader := bufio.NewReader(file)

	for {
		if line, err = reader.ReadString('\n'); err != nil {
			break
		}

		lines = append(lines, line)
	}

	if err != io.EOF {
		panic(err)
	}

	return lines
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/gorilla/mux"

	"labix.org/v2/mgo/bson"
)

var r *mux.Router

func init() {
	r = mux.NewRouter()
	r.HandleFunc("/contacts", getContactsHandler).Methods("GET")
	r.HandleFunc("/contacts", postContactsHandler).Methods("POST")
	r.HandleFunc("/contacts/{id}", getContactHandler).Methods("GET")
}

// getContactsHandler returns a json response containing all
// the contacts.
//
// URL: GET /contacts
//
// For example, If there were only two contacts in total,
// GET /contacts and
// would yield the following response.
//
// Response:
// [
//  {
//    "id": "53a14760e3bdea4286000003",
//    "company": "Encom Inc.",
//    "person": "Sam Flynn",
//    "email": "samflynn@encom.com",
//    "phone": "103-345-456",
//    "skypeid": "sam_flynn",
//    "country": "USA"
//  },
//  {
//    "id": "53a14760e3bdea4286000004",
//    "company": "Encom Inc.",
//    "person": "Kevin Flynn",
//    "email": "kevinflynn@encom.com",
//    "phone": "234-877-988",
//    "skypeid": "kevin_flynn",
//    "country": "USA"
//  }
// ]
func getContactsHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	c, err := GetAllContacts()
	if err != nil {
		log.Println(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	enc := json.NewEncoder(w)
	if err = enc.Encode(c); err != nil {
		log.Println(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

// postContactsHandler creates a new contact in the database.
// URL: POST /contacts
// Request:
// {
//   "company": "Encom Inc.",
//   "person": "Sam Flynn",
//   "email": "samflynn@encom.com",
//   "phone": "103-345-456",
//   "skypeid": "sam_flynn",
//   "country": "USA"
// }
// Response: HTTP 200
func postContactsHandler(w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
	var c contact
	err := decoder.Decode(&c)
	if err != nil {
		log.Println(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	_, err = NewContact(c.Company, c.Person, c.Email, c.Phone, c.SkypeId, c.Country)
	if err != nil {
		log.Println(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

// getContactHandler returns json data pertaining to a specific contact.
//
// URL: GET /contacts/{id}
//
// For example,
// GET /contacts/53a15a07e3bdea53d0000002
// Response:
// {
//   "id": "53a15a07e3bdea53d0000002",
//   "company": "Encom Inc.",
//   "person": "Flynn",
//   "email": "flynn@encom.com",
//   "country": "USA"
// }
func getContactHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	vars := mux.Vars(r)
	i := vars["id"]
	id := bson.ObjectIdHex(i)
	c, err := GetContact(id)
	if err != nil {
		log.Println(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	enc := json.NewEncoder(w)
	if err = enc.Encode(c); err != nil {
		log.Println(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}
<commit_msg>Early Return when http Error, fixes #3<commit_after>package main

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/gorilla/mux"

	"labix.org/v2/mgo/bson"
)

var r *mux.Router

func init() {
	r = mux.NewRouter()
	r.HandleFunc("/contacts", getContactsHandler).Methods("GET")
	r.HandleFunc("/contacts", postContactsHandler).Methods("POST")
	r.HandleFunc("/contacts/{id}", getContactHandler).Methods("GET")
}

// getContactsHandler returns a json response containing all
// the contacts.
//
// URL: GET /contacts
//
// For example, If there were only two contacts in total,
// GET /contacts and
// would yield the following response.
//
// Response:
// [
//  {
//    "id": "53a14760e3bdea4286000003",
//    "company": "Encom Inc.",
//    "person": "Sam Flynn",
//    "email": "samflynn@encom.com",
//    "phone": "103-345-456",
//    "skypeid": "sam_flynn",
//    "country": "USA"
//  },
//  {
//    "id": "53a14760e3bdea4286000004",
//    "company": "Encom Inc.",
//    "person": "Kevin Flynn",
//    "email": "kevinflynn@encom.com",
//    "phone": "234-877-988",
//    "skypeid": "kevin_flynn",
//    "country": "USA"
//  }
// ]
func getContactsHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	c, err := GetAllContacts()
	if err != nil {
		log.Println(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	enc := json.NewEncoder(w)
	if err = enc.Encode(c); err != nil {
		log.Println(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

// postContactsHandler creates a new contact in the database.
// URL: POST /contacts
// Request:
// {
//   "company": "Encom Inc.",
//   "person": "Sam Flynn",
//   "email": "samflynn@encom.com",
//   "phone": "103-345-456",
//   "skypeid": "sam_flynn",
//   "country": "USA"
// }
// Response: HTTP 200
func postContactsHandler(w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
	var c contact
	err := decoder.Decode(&c)
	if err != nil {
		log.Println(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	_, err = NewContact(c.Company, c.Person, c.Email, c.Phone, c.SkypeId, c.Country)
	if err != nil {
		log.Println(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.WriteHeader(http.StatusCreated)
}

// getContactHandler returns json data pertaining to a specific contact.
//
// URL: GET /contacts/{id}
//
// For example,
// GET /contacts/53a15a07e3bdea53d0000002
// Response:
// {
//   "id": "53a15a07e3bdea53d0000002",
//   "company": "Encom Inc.",
//   "person": "Flynn",
//   "email": "flynn@encom.com",
//   "country": "USA"
// }
func getContactHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	vars := mux.Vars(r)
	i := vars["id"]
	id := bson.ObjectIdHex(i)
	c, err := GetContact(id)
	if err != nil {
		log.Println(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
	enc := json.NewEncoder(w)
	if err = enc.Encode(c); err != nil {
		log.Println(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}
<|endoftext|>


<commit_before>package assertions

import (
	"testing"
)

func TestShouldPanic(t *testing.T) {
	fail(t, so(func() {}, ShouldPanic, 1), "This assertion requires exactly 0 comparison values (you provided 1).")
	fail(t, so(func() {}, ShouldPanic, 1, 2, 3), "This assertion requires exactly 0 comparison values (you provided 3).")

	fail(t, so(1, ShouldPanic), shouldUseVoidNiladicFunction)
	fail(t, so(func(i int) {}, ShouldPanic), shouldUseVoidNiladicFunction)

	fail(t, so(func() {}, ShouldPanic), shouldHavePanicked)
	pass(t, so(func() { panic("hi") }, ShouldPanic))
}

func TestShouldNotPanic(t *testing.T) {
	fail(t, so(func() {}, ShouldNotPanic, 1), "This assertion requires exactly 0 comparison values (you provided 1).")
	fail(t, so(func() {}, ShouldNotPanic, 1, 2, 3), "This assertion requires exactly 0 comparison values (you provided 3).")

	fail(t, so(1, ShouldNotPanic), shouldUseVoidNiladicFunction)
	fail(t, so(func(i int) {}, ShouldNotPanic), shouldUseVoidNiladicFunction)

	fail(t, so(func() { panic("hi") }, ShouldNotPanic), shouldNotHavePanicked)
	pass(t, so(func() {}, ShouldNotPanic))
}

func TestShouldPanicWith(t *testing.T) {
	fail(t, so(func() {}, ShouldPanicWith), "This assertion requires exactly 1 comparison values (you provided 0).")
	fail(t, so(func() {}, ShouldPanicWith, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")

	fail(t, so(1, ShouldPanicWith, 1), shouldUseVoidNiladicFunction)
	fail(t, so(func(i int) {}, ShouldPanicWith, "hi"), shouldUseVoidNiladicFunction)
	fail(t, so(func() {}, ShouldPanicWith, "bye"), shouldHavePanicked)
	fail(t, so(func() { panic("hi") }, ShouldPanicWith, "bye"), "bye|hi|Expected func() to panic with 'bye' (but it panicked with 'hi')!")

	pass(t, so(func() { panic("hi") }, ShouldPanicWith, "hi"))
}

func TestShouldNotPanicWith(t *testing.T) {
	fail(t, so(func() {}, ShouldNotPanicWith), "This assertion requires exactly 1 comparison values (you provided 0).")
	fail(t, so(func() {}, ShouldNotPanicWith, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")

	fail(t, so(1, ShouldNotPanicWith, 1), shouldUseVoidNiladicFunction)
	fail(t, so(func(i int) {}, ShouldNotPanicWith, "hi"), shouldUseVoidNiladicFunction)
	fail(t, so(func() { panic("hi") }, ShouldNotPanicWith, "hi"), "Expected func() NOT to panic with 'hi' (but it did)!")

	pass(t, so(func() {}, ShouldNotPanicWith, "bye"))
	pass(t, so(func() { panic("hi") }, ShouldNotPanicWith, "bye"))
}
<commit_msg>Added one more test to the ShouldPanic assertion.<commit_after>package assertions

import (
	"testing"
)

func TestShouldPanic(t *testing.T) {
	fail(t, so(func() {}, ShouldPanic, 1), "This assertion requires exactly 0 comparison values (you provided 1).")
	fail(t, so(func() {}, ShouldPanic, 1, 2, 3), "This assertion requires exactly 0 comparison values (you provided 3).")

	fail(t, so(1, ShouldPanic), shouldUseVoidNiladicFunction)
	fail(t, so(func(i int) {}, ShouldPanic), shouldUseVoidNiladicFunction)
	fail(t, so(func() int { panic("hi") }, ShouldPanic), shouldUseVoidNiladicFunction)

	fail(t, so(func() {}, ShouldPanic), shouldHavePanicked)
	pass(t, so(func() { panic("hi") }, ShouldPanic))
}

func TestShouldNotPanic(t *testing.T) {
	fail(t, so(func() {}, ShouldNotPanic, 1), "This assertion requires exactly 0 comparison values (you provided 1).")
	fail(t, so(func() {}, ShouldNotPanic, 1, 2, 3), "This assertion requires exactly 0 comparison values (you provided 3).")

	fail(t, so(1, ShouldNotPanic), shouldUseVoidNiladicFunction)
	fail(t, so(func(i int) {}, ShouldNotPanic), shouldUseVoidNiladicFunction)

	fail(t, so(func() { panic("hi") }, ShouldNotPanic), shouldNotHavePanicked)
	pass(t, so(func() {}, ShouldNotPanic))
}

func TestShouldPanicWith(t *testing.T) {
	fail(t, so(func() {}, ShouldPanicWith), "This assertion requires exactly 1 comparison values (you provided 0).")
	fail(t, so(func() {}, ShouldPanicWith, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")

	fail(t, so(1, ShouldPanicWith, 1), shouldUseVoidNiladicFunction)
	fail(t, so(func(i int) {}, ShouldPanicWith, "hi"), shouldUseVoidNiladicFunction)
	fail(t, so(func() {}, ShouldPanicWith, "bye"), shouldHavePanicked)
	fail(t, so(func() { panic("hi") }, ShouldPanicWith, "bye"), "bye|hi|Expected func() to panic with 'bye' (but it panicked with 'hi')!")

	pass(t, so(func() { panic("hi") }, ShouldPanicWith, "hi"))
}

func TestShouldNotPanicWith(t *testing.T) {
	fail(t, so(func() {}, ShouldNotPanicWith), "This assertion requires exactly 1 comparison values (you provided 0).")
	fail(t, so(func() {}, ShouldNotPanicWith, 1, 2, 3), "This assertion requires exactly 1 comparison values (you provided 3).")

	fail(t, so(1, ShouldNotPanicWith, 1), shouldUseVoidNiladicFunction)
	fail(t, so(func(i int) {}, ShouldNotPanicWith, "hi"), shouldUseVoidNiladicFunction)
	fail(t, so(func() { panic("hi") }, ShouldNotPanicWith, "hi"), "Expected func() NOT to panic with 'hi' (but it did)!")

	pass(t, so(func() {}, ShouldNotPanicWith, "bye"))
	pass(t, so(func() { panic("hi") }, ShouldNotPanicWith, "bye"))
}
<|endoftext|>


<commit_before>package customerio

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"strings"
)

const DefaultUserAgent = "Customer.io Go Client/" + Version

// CustomerIO wraps the customer.io track API, see: https://customer.io/docs/api/#apitrackintroduction
type CustomerIO struct {
	siteID    string
	apiKey    string
	URL       string
	UserAgent string
	Client    *http.Client
}

// CustomerIOError is returned by any method that fails at the API level
type CustomerIOError struct {
	status int
	url    string
	body   []byte
}

func (e *CustomerIOError) Error() string {
	return fmt.Sprintf("%v: %v %v", e.status, e.url, string(e.body))
}

// ParamError is an error returned if a parameter to the track API is invalid.
type ParamError struct {
	Param string // Param is the name of the parameter.
}

func (e ParamError) Error() string { return e.Param + ": missing" }

// NewTrackClient prepares a client for use with the Customer.io track API, see: https://customer.io/docs/api/#apitrackintroduction
// using a Tracking Site ID and API Key pair from https://fly.customer.io/settings/api_credentials
func NewTrackClient(siteID, apiKey string, opts ...option) *CustomerIO {
	client := &http.Client{
		Transport: &http.Transport{
			MaxIdleConnsPerHost: 100,
		},
	}
	c := &CustomerIO{
		siteID:    siteID,
		apiKey:    apiKey,
		URL:       "https://track.customer.io",
		UserAgent: "Customer.io Go Client/3.0",
		Client:    client,
	}

	for _, opt := range opts {
		opt.track(c)
	}

	return c
}

// NewCustomerIO prepares a client for use with the Customer.io track API, see: https://customer.io/docs/api/#apitrackintroduction
// deprecated in favour of NewTrackClient
func NewCustomerIO(siteID, apiKey string) *CustomerIO {
	return NewTrackClient(siteID, apiKey)
}

// Identify identifies a customer and sets their attributes
func (c *CustomerIO) Identify(customerID string, attributes map[string]interface{}) error {
	if customerID == "" {
		return ParamError{Param: "customerID"}
	}
	return c.request("PUT",
		fmt.Sprintf("%s/api/v1/customers/%s", c.URL, url.PathEscape(customerID)),
		attributes)
}

// Track sends a single event to Customer.io for the supplied user
func (c *CustomerIO) Track(customerID string, eventName string, data map[string]interface{}) error {
	if customerID == "" {
		return ParamError{Param: "customerID"}
	}
	if eventName == "" {
		return ParamError{Param: "eventName"}
	}
	return c.request("POST",
		fmt.Sprintf("%s/api/v1/customers/%s/events", c.URL, url.PathEscape(customerID)),
		map[string]interface{}{
			"name": eventName,
			"data": data,
		})
}

// TrackAnonymous sends a single event to Customer.io for the anonymous user
func (c *CustomerIO) TrackAnonymous(anonymousID, eventName string, data map[string]interface{}) error {
	if anonymousID == "" {
		return ParamError{Param: "anonymousID"}
	}
	if eventName == "" {
		return ParamError{Param: "eventName"}
	}
	return c.request("POST",
		fmt.Sprintf("%s/api/v1/events", c.URL),
		map[string]interface{}{
			"name":         eventName,
			"anonymous_id": anonymousID,
			"data":         data,
		})
}

// Delete deletes a customer
func (c *CustomerIO) Delete(customerID string) error {
	if customerID == "" {
		return ParamError{Param: "customerID"}
	}
	return c.request("DELETE",
		fmt.Sprintf("%s/api/v1/customers/%s", c.URL, url.PathEscape(customerID)),
		nil)
}

// AddDevice adds a device for a customer
func (c *CustomerIO) AddDevice(customerID string, deviceID string, platform string, data map[string]interface{}) error {
	if customerID == "" {
		return ParamError{Param: "customerID"}
	}
	if deviceID == "" {
		return ParamError{Param: "deviceID"}
	}
	if platform == "" {
		return ParamError{Param: "platform"}
	}

	body := map[string]map[string]interface{}{
		"device": {
			"id":       deviceID,
			"platform": platform,
		},
	}
	for k, v := range data {
		body["device"][k] = v
	}
	return c.request("PUT",
		fmt.Sprintf("%s/api/v1/customers/%s/devices", c.URL, url.PathEscape(customerID)),
		body)
}

// DeleteDevice deletes a device for a customer
func (c *CustomerIO) DeleteDevice(customerID string, deviceID string) error {
	if customerID == "" {
		return ParamError{Param: "customerID"}
	}
	if deviceID == "" {
		return ParamError{Param: "deviceID"}
	}
	return c.request("DELETE",
		fmt.Sprintf("%s/api/v1/customers/%s/devices/%s", c.URL, url.PathEscape(customerID), url.PathEscape(deviceID)),
		nil)
}

func (c *CustomerIO) auth() string {
	return base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf("%v:%v", c.siteID, c.apiKey)))
}

func (c *CustomerIO) request(method, url string, body interface{}) error {
	var req *http.Request
	if body != nil {
		j, err := json.Marshal(body)
		if err != nil {
			return err
		}

		req, err = http.NewRequest(method, url, bytes.NewBuffer(j))
		if err != nil {
			return err
		}

		req.Header.Add("User-Agent", c.UserAgent)
		req.Header.Add("Content-Type", "application/json")
		req.Header.Add("Content-Length", strconv.Itoa(len(j)))
	} else {
		var err error
		req, err = http.NewRequest(method, url, nil)
		if err != nil {
			return err
		}
	}

	req.Header.Add("Authorization", fmt.Sprintf("Basic %v", c.auth()))

	resp, err := c.Client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		return &CustomerIOError{
			status: resp.StatusCode,
			url:    url,
			body:   responseBody,
		}
	}

	return nil
}

type IdentifierType string

const (
	IdentifierTypeID    IdentifierType = "id"
	IdentifierTypeEmail IdentifierType = "email"
	IdentifierTypeCioID IdentifierType = "cio_id"
)

type Identifier struct {
	Type  IdentifierType
	Value string
}

func (id Identifier) kv() map[string]string {
	return map[string]string{
		string(id.Type): id.Value,
	}
}

func (id Identifier) validate() error {
	if !(id.Type == IdentifierTypeID ||
		id.Type == IdentifierTypeEmail ||
		id.Type == IdentifierTypeCioID) {
		return errors.New("invalid id type")
	}

	if strings.TrimSpace(id.Value) == "" {
		return errors.New("invalid id")
	}

	return nil
}

// MergeCustomers sends a request to Customer.io to merge two customer profiles together.
func (c *CustomerIO) MergeCustomers(primary Identifier, secondary Identifier) error {
	if primary.validate() != nil {
		return ParamError{Param: "primary"}
	}
	if secondary.validate() != nil {
		return ParamError{Param: "secondary"}
	}

	return c.request("POST",
		fmt.Sprintf("%s/api/v1/merge_customers", c.URL),
		map[string]interface{}{
			"primary":   primary.kv(),
			"secondary": secondary.kv(),
		})
}
<commit_msg>fix: use DefaultUserAgent<commit_after>package customerio

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"strings"
)

const DefaultUserAgent = "Customer.io Go Client/" + Version

// CustomerIO wraps the customer.io track API, see: https://customer.io/docs/api/#apitrackintroduction
type CustomerIO struct {
	siteID    string
	apiKey    string
	URL       string
	UserAgent string
	Client    *http.Client
}

// CustomerIOError is returned by any method that fails at the API level
type CustomerIOError struct {
	status int
	url    string
	body   []byte
}

func (e *CustomerIOError) Error() string {
	return fmt.Sprintf("%v: %v %v", e.status, e.url, string(e.body))
}

// ParamError is an error returned if a parameter to the track API is invalid.
type ParamError struct {
	Param string // Param is the name of the parameter.
}

func (e ParamError) Error() string { return e.Param + ": missing" }

// NewTrackClient prepares a client for use with the Customer.io track API, see: https://customer.io/docs/api/#apitrackintroduction
// using a Tracking Site ID and API Key pair from https://fly.customer.io/settings/api_credentials
func NewTrackClient(siteID, apiKey string, opts ...option) *CustomerIO {
	client := &http.Client{
		Transport: &http.Transport{
			MaxIdleConnsPerHost: 100,
		},
	}
	c := &CustomerIO{
		siteID:    siteID,
		apiKey:    apiKey,
		URL:       "https://track.customer.io",
		UserAgent: DefaultUserAgent,
		Client:    client,
	}

	for _, opt := range opts {
		opt.track(c)
	}

	return c
}

// NewCustomerIO prepares a client for use with the Customer.io track API, see: https://customer.io/docs/api/#apitrackintroduction
// deprecated in favour of NewTrackClient
func NewCustomerIO(siteID, apiKey string) *CustomerIO {
	return NewTrackClient(siteID, apiKey)
}

// Identify identifies a customer and sets their attributes
func (c *CustomerIO) Identify(customerID string, attributes map[string]interface{}) error {
	if customerID == "" {
		return ParamError{Param: "customerID"}
	}
	return c.request("PUT",
		fmt.Sprintf("%s/api/v1/customers/%s", c.URL, url.PathEscape(customerID)),
		attributes)
}

// Track sends a single event to Customer.io for the supplied user
func (c *CustomerIO) Track(customerID string, eventName string, data map[string]interface{}) error {
	if customerID == "" {
		return ParamError{Param: "customerID"}
	}
	if eventName == "" {
		return ParamError{Param: "eventName"}
	}
	return c.request("POST",
		fmt.Sprintf("%s/api/v1/customers/%s/events", c.URL, url.PathEscape(customerID)),
		map[string]interface{}{
			"name": eventName,
			"data": data,
		})
}

// TrackAnonymous sends a single event to Customer.io for the anonymous user
func (c *CustomerIO) TrackAnonymous(anonymousID, eventName string, data map[string]interface{}) error {
	if anonymousID == "" {
		return ParamError{Param: "anonymousID"}
	}
	if eventName == "" {
		return ParamError{Param: "eventName"}
	}
	return c.request("POST",
		fmt.Sprintf("%s/api/v1/events", c.URL),
		map[string]interface{}{
			"name":         eventName,
			"anonymous_id": anonymousID,
			"data":         data,
		})
}

// Delete deletes a customer
func (c *CustomerIO) Delete(customerID string) error {
	if customerID == "" {
		return ParamError{Param: "customerID"}
	}
	return c.request("DELETE",
		fmt.Sprintf("%s/api/v1/customers/%s", c.URL, url.PathEscape(customerID)),
		nil)
}

// AddDevice adds a device for a customer
func (c *CustomerIO) AddDevice(customerID string, deviceID string, platform string, data map[string]interface{}) error {
	if customerID == "" {
		return ParamError{Param: "customerID"}
	}
	if deviceID == "" {
		return ParamError{Param: "deviceID"}
	}
	if platform == "" {
		return ParamError{Param: "platform"}
	}

	body := map[string]map[string]interface{}{
		"device": {
			"id":       deviceID,
			"platform": platform,
		},
	}
	for k, v := range data {
		body["device"][k] = v
	}
	return c.request("PUT",
		fmt.Sprintf("%s/api/v1/customers/%s/devices", c.URL, url.PathEscape(customerID)),
		body)
}

// DeleteDevice deletes a device for a customer
func (c *CustomerIO) DeleteDevice(customerID string, deviceID string) error {
	if customerID == "" {
		return ParamError{Param: "customerID"}
	}
	if deviceID == "" {
		return ParamError{Param: "deviceID"}
	}
	return c.request("DELETE",
		fmt.Sprintf("%s/api/v1/customers/%s/devices/%s", c.URL, url.PathEscape(customerID), url.PathEscape(deviceID)),
		nil)
}

func (c *CustomerIO) auth() string {
	return base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf("%v:%v", c.siteID, c.apiKey)))
}

func (c *CustomerIO) request(method, url string, body interface{}) error {
	var req *http.Request
	if body != nil {
		j, err := json.Marshal(body)
		if err != nil {
			return err
		}

		req, err = http.NewRequest(method, url, bytes.NewBuffer(j))
		if err != nil {
			return err
		}

		req.Header.Add("User-Agent", c.UserAgent)
		req.Header.Add("Content-Type", "application/json")
		req.Header.Add("Content-Length", strconv.Itoa(len(j)))
	} else {
		var err error
		req, err = http.NewRequest(method, url, nil)
		if err != nil {
			return err
		}
	}

	req.Header.Add("Authorization", fmt.Sprintf("Basic %v", c.auth()))

	resp, err := c.Client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		return &CustomerIOError{
			status: resp.StatusCode,
			url:    url,
			body:   responseBody,
		}
	}

	return nil
}

type IdentifierType string

const (
	IdentifierTypeID    IdentifierType = "id"
	IdentifierTypeEmail IdentifierType = "email"
	IdentifierTypeCioID IdentifierType = "cio_id"
)

type Identifier struct {
	Type  IdentifierType
	Value string
}

func (id Identifier) kv() map[string]string {
	return map[string]string{
		string(id.Type): id.Value,
	}
}

func (id Identifier) validate() error {
	if !(id.Type == IdentifierTypeID ||
		id.Type == IdentifierTypeEmail ||
		id.Type == IdentifierTypeCioID) {
		return errors.New("invalid id type")
	}

	if strings.TrimSpace(id.Value) == "" {
		return errors.New("invalid id")
	}

	return nil
}

// MergeCustomers sends a request to Customer.io to merge two customer profiles together.
func (c *CustomerIO) MergeCustomers(primary Identifier, secondary Identifier) error {
	if primary.validate() != nil {
		return ParamError{Param: "primary"}
	}
	if secondary.validate() != nil {
		return ParamError{Param: "secondary"}
	}

	return c.request("POST",
		fmt.Sprintf("%s/api/v1/merge_customers", c.URL),
		map[string]interface{}{
			"primary":   primary.kv(),
			"secondary": secondary.kv(),
		})
}
<|endoftext|>


<commit_before>package main

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

	"github.com/litl/shuttle/client"
	"github.com/litl/shuttle/log"

	"github.com/gorilla/mux"
)

func getConfig(w http.ResponseWriter, r *http.Request) {
	w.Write(marshal(Registry.Config()))
}

func getStats(w http.ResponseWriter, r *http.Request) {
	if len(Registry.Config().Services) == 0 {
		w.WriteHeader(503)
	}
	w.Write(marshal(Registry.Stats()))
}

func getService(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	serviceStats, err := Registry.ServiceStats(vars["service"])
	if err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}

	w.Write(marshal(serviceStats))
}

// Update the global config
func postConfig(w http.ResponseWriter, r *http.Request) {
	cfg := client.Config{}

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Errorln(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer r.Body.Close()

	err = json.Unmarshal(body, &cfg)
	if err != nil {
		log.Errorln(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if err := Registry.UpdateConfig(cfg); err != nil {
		log.Errorln(err)
		// TODO: differentiate between ServerError and BadRequest
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

// Update a service and/or backends.
func postService(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Errorln(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer r.Body.Close()

	svcCfg := client.ServiceConfig{Name: vars["service"]}
	err = json.Unmarshal(body, &svcCfg)
	if err != nil {
		log.Errorln(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// don't let someone update the wrong service
	if svcCfg.Name != vars["service"] {
		errMsg := "Mismatched service name in API call"
		log.Error(errMsg)
		http.Error(w, errMsg, http.StatusBadRequest)
		return
	}

	cfg := client.Config{
		Services: []client.ServiceConfig{svcCfg},
	}

	err = Registry.UpdateConfig(cfg)
	//FIXME: this doesn't return an error for an empty or broken service
	if err != nil {
		log.Error(err)
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	w.Write(marshal(Registry.Config()))
}

func deleteService(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	err := Registry.RemoveService(vars["service"])
	if err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}
	go writeStateConfig()
	w.Write(marshal(Registry.Config()))
}

func getBackend(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	serviceName := vars["service"]
	backendName := vars["backend"]

	backend, err := Registry.BackendStats(serviceName, backendName)
	if err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}

	w.Write(marshal(backend))
}

func postBackend(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Errorln(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer r.Body.Close()

	backendName := vars["backend"]
	serviceName := vars["service"]

	backendCfg := client.BackendConfig{Name: backendName}
	err = json.Unmarshal(body, &backendCfg)
	if err != nil {
		log.Errorln(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if err := Registry.AddBackend(serviceName, backendCfg); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	go writeStateConfig()
	w.Write(marshal(Registry.Config()))
}

func deleteBackend(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	serviceName := vars["service"]
	backendName := vars["backend"]

	if err := Registry.RemoveBackend(serviceName, backendName); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	go writeStateConfig()
	w.Write(marshal(Registry.Config()))
}

func addHandlers() {
	r := mux.NewRouter()
	r.HandleFunc("/", getStats).Methods("GET")
	r.HandleFunc("/", postConfig).Methods("PUT", "POST")
	r.HandleFunc("/_config", getConfig).Methods("GET")
	r.HandleFunc("/_config", postConfig).Methods("PUT", "POST")
	r.HandleFunc("/_stats", getStats).Methods("GET")
	r.HandleFunc("/{service}", getService).Methods("GET")
	r.HandleFunc("/{service}", postService).Methods("PUT", "POST")
	r.HandleFunc("/{service}", deleteService).Methods("DELETE")
	r.HandleFunc("/{service}/{backend}", getBackend).Methods("GET")
	r.HandleFunc("/{service}/{backend}", postBackend).Methods("PUT", "POST")
	r.HandleFunc("/{service}/{backend}", deleteBackend).Methods("DELETE")
	http.Handle("/", r)
}

func startAdminHTTPServer(wg *sync.WaitGroup) {
	defer wg.Done()
	addHandlers()
	log.Println("Admin server listening on", adminListenAddr)

	netw := "tcp"

	if strings.HasPrefix(adminListenAddr, "/") {
		netw = "unix"

		// remove our old socket if we left it lying around
		if stats, err := os.Stat(adminListenAddr); err == nil {
			if stats.Mode()&os.ModeSocket != 0 {
				os.Remove(adminListenAddr)
			}
		}

		defer os.Remove(adminListenAddr)
	}

	listener, err := net.Listen(netw, adminListenAddr)
	if err != nil {
		log.Fatalln(err)
	}

	http.Serve(listener, nil)
}
<commit_msg>add /{service}/_config endpoint<commit_after>package main

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

	"github.com/litl/shuttle/client"
	"github.com/litl/shuttle/log"

	"github.com/gorilla/mux"
)

func getConfig(w http.ResponseWriter, r *http.Request) {
	w.Write(marshal(Registry.Config()))
}

func getStats(w http.ResponseWriter, r *http.Request) {
	if len(Registry.Config().Services) == 0 {
		w.WriteHeader(503)
	}
	w.Write(marshal(Registry.Stats()))
}

func getServiceStats(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	serviceStats, err := Registry.ServiceStats(vars["service"])
	if err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}

	w.Write(marshal(serviceStats))
}

func getServiceConfig(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	serviceStats, err := Registry.ServiceConfig(vars["service"])
	if err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}

	w.Write(marshal(serviceStats))
}

// Update the global config
func postConfig(w http.ResponseWriter, r *http.Request) {
	cfg := client.Config{}

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Errorln(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer r.Body.Close()

	err = json.Unmarshal(body, &cfg)
	if err != nil {
		log.Errorln(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if err := Registry.UpdateConfig(cfg); err != nil {
		log.Errorln(err)
		// TODO: differentiate between ServerError and BadRequest
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

// Update a service and/or backends.
func postService(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Errorln(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer r.Body.Close()

	svcCfg := client.ServiceConfig{Name: vars["service"]}
	err = json.Unmarshal(body, &svcCfg)
	if err != nil {
		log.Errorln(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// don't let someone update the wrong service
	if svcCfg.Name != vars["service"] {
		errMsg := "Mismatched service name in API call"
		log.Error(errMsg)
		http.Error(w, errMsg, http.StatusBadRequest)
		return
	}

	cfg := client.Config{
		Services: []client.ServiceConfig{svcCfg},
	}

	err = Registry.UpdateConfig(cfg)
	//FIXME: this doesn't return an error for an empty or broken service
	if err != nil {
		log.Error(err)
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	w.Write(marshal(Registry.Config()))
}

func deleteService(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	err := Registry.RemoveService(vars["service"])
	if err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}
	go writeStateConfig()
	w.Write(marshal(Registry.Config()))
}

func getBackendStats(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	serviceName := vars["service"]
	backendName := vars["backend"]

	backend, err := Registry.BackendStats(serviceName, backendName)
	if err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}

	w.Write(marshal(backend))
}

func getBackend(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	serviceName := vars["service"]
	backendName := vars["backend"]

	backend, err := Registry.BackendStats(serviceName, backendName)
	if err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}

	w.Write(marshal(backend))
}

func postBackend(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Errorln(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer r.Body.Close()

	backendName := vars["backend"]
	serviceName := vars["service"]

	backendCfg := client.BackendConfig{Name: backendName}
	err = json.Unmarshal(body, &backendCfg)
	if err != nil {
		log.Errorln(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if err := Registry.AddBackend(serviceName, backendCfg); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	go writeStateConfig()
	w.Write(marshal(Registry.Config()))
}

func deleteBackend(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	serviceName := vars["service"]
	backendName := vars["backend"]

	if err := Registry.RemoveBackend(serviceName, backendName); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	go writeStateConfig()
	w.Write(marshal(Registry.Config()))
}

func addHandlers() {
	r := mux.NewRouter()
	r.HandleFunc("/", getStats).Methods("GET")
	r.HandleFunc("/", postConfig).Methods("PUT", "POST")
	r.HandleFunc("/_config", getConfig).Methods("GET")
	r.HandleFunc("/_config", postConfig).Methods("PUT", "POST")
	r.HandleFunc("/_stats", getStats).Methods("GET")
	r.HandleFunc("/{service}", getServiceStats).Methods("GET")
	r.HandleFunc("/{service}/_config", getServiceConfig).Methods("GET")
	r.HandleFunc("/{service}/_stats", getServiceStats).Methods("GET")
	r.HandleFunc("/{service}", postService).Methods("PUT", "POST")
	r.HandleFunc("/{service}", deleteService).Methods("DELETE")
	r.HandleFunc("/{service}/{backend}", getBackend).Methods("GET")
	r.HandleFunc("/{service}/{backend}", postBackend).Methods("PUT", "POST")
	r.HandleFunc("/{service}/{backend}", deleteBackend).Methods("DELETE")
	http.Handle("/", r)
}

func startAdminHTTPServer(wg *sync.WaitGroup) {
	defer wg.Done()
	addHandlers()
	log.Println("Admin server listening on", adminListenAddr)

	netw := "tcp"

	if strings.HasPrefix(adminListenAddr, "/") {
		netw = "unix"

		// remove our old socket if we left it lying around
		if stats, err := os.Stat(adminListenAddr); err == nil {
			if stats.Mode()&os.ModeSocket != 0 {
				os.Remove(adminListenAddr)
			}
		}

		defer os.Remove(adminListenAddr)
	}

	listener, err := net.Listen(netw, adminListenAddr)
	if err != nil {
		log.Fatalln(err)
	}

	http.Serve(listener, nil)
}
<|endoftext|>


<commit_before>package gorelic

import (
	"errors"
	"fmt"
	metrics "github.com/yvasiyarov/go-metrics"
	"github.com/yvasiyarov/newrelic_platform_go"
	"log"
	"net/http"
)

const (
	// Send data to newrelic every 60 seconds
	NEWRELIC_POLL_INTERVAL = 60
	// Get garbage collector run statistic every 10 seconds
	// During GC stat pooling - mheap will be locked, so be carefull changing this value
	GC_POLL_INTERVAL_IN_SECONDS = 10
	// Get memory allocator statistic every 60 seconds
	// During this process stoptheword() is called, so be carefull changing this value
	MEMORY_ALLOCATOR_POLL_INTERVAL_IN_SECONDS = 60

	AGENT_GUID    = "com.github.yvasiyarov.GoRelic"
	AGENT_VERSION = "0.0.4"
	AGENT_NAME    = "Go daemon"
)

type Agent struct {
	NewrelicName                string
	NewrelicLicense             string
	NewrelicPollInterval        int
	Verbose                     bool
	CollectGcStat               bool
	CollectMemoryStat           bool
	CollectHttpStat             bool
	GCPollInterval              int
	MemoryAllocatorPollInterval int
	AgentGUID                   string
	AgentVersion                string
	plugin                      *newrelic_platform_go.NewrelicPlugin
	HttpTimer                   metrics.Timer
}

func NewAgent() *Agent {
	agent := &Agent{
		NewrelicName:                AGENT_NAME,
		NewrelicPollInterval:        NEWRELIC_POLL_INTERVAL,
		Verbose:                     false,
		CollectGcStat:               true,
		CollectMemoryStat:           true,
		GCPollInterval:              GC_POLL_INTERVAL_IN_SECONDS,
		MemoryAllocatorPollInterval: MEMORY_ALLOCATOR_POLL_INTERVAL_IN_SECONDS,
		AgentGUID:                   AGENT_GUID,
		AgentVersion:                AGENT_VERSION,
	}
	return agent
}

func (agent *Agent) WrapHttpHandlerFunc(h THttpHandlerFunc) THttpHandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		proxy := NewHttpHandlerFunc(h)
		proxy.timer = agent.HttpTimer
		proxy.ServeHTTP(w, req)
	}
}

func (agent *Agent) WrapHttpHandler(h http.Handler) http.Handler {
	proxy := NewHttpHandler(h)
	proxy.timer = agent.HttpTimer
	return proxy
}

func (agent *Agent) Run() error {
	if agent.NewrelicLicense == "" {
		return errors.New("Please, pass a valid newrelic license key.")
	}

	agent.plugin = newrelic_platform_go.NewNewrelicPlugin(agent.AgentVersion, agent.NewrelicLicense, agent.NewrelicPollInterval)
	component := newrelic_platform_go.NewPluginComponent(agent.NewrelicName, agent.AgentGUID)
	agent.plugin.AddComponent(component)

	addRuntimeMericsToComponent(component)

	if agent.CollectGcStat {
		addGCMericsToComponent(component, agent.GCPollInterval)
		agent.Debug(fmt.Sprintf("Init GC metrics collection. Poll interval %d seconds.", agent.GCPollInterval))
	}
	if agent.CollectMemoryStat {
		addMemoryMericsToComponent(component, agent.MemoryAllocatorPollInterval)
		agent.Debug(fmt.Sprintf("Init memory allocator metrics collection. Poll interval %d seconds.", agent.MemoryAllocatorPollInterval))
	}

	if agent.CollectHttpStat {
		agent.HttpTimer = metrics.NewTimer()
		addHttpMericsToComponent(component, agent.HttpTimer)
		agent.Debug(fmt.Sprintf("Init HTTP metrics collection."))
	}

	agent.plugin.Verbose = agent.Verbose
	go agent.plugin.Run()
	return nil
}

func (agent *Agent) Debug(msg string) {
	if agent.Verbose {
		log.Println(msg)
	}
}
<commit_msg>Fix bug in Agent initialization<commit_after>package gorelic

import (
	"errors"
	"fmt"
	metrics "github.com/yvasiyarov/go-metrics"
	"github.com/yvasiyarov/newrelic_platform_go"
	"log"
	"net/http"
)

const (
	// Send data to newrelic every 60 seconds
	NEWRELIC_POLL_INTERVAL = 60
	// Get garbage collector run statistic every 10 seconds
	// During GC stat pooling - mheap will be locked, so be carefull changing this value
	GC_POLL_INTERVAL_IN_SECONDS = 10
	// Get memory allocator statistic every 60 seconds
	// During this process stoptheword() is called, so be carefull changing this value
	MEMORY_ALLOCATOR_POLL_INTERVAL_IN_SECONDS = 60

	AGENT_GUID    = "com.github.yvasiyarov.GoRelic"
	AGENT_VERSION = "0.0.4"
	AGENT_NAME    = "Go daemon"
)

type Agent struct {
	NewrelicName                string
	NewrelicLicense             string
	NewrelicPollInterval        int
	Verbose                     bool
	CollectGcStat               bool
	CollectMemoryStat           bool
	CollectHttpStat             bool
	GCPollInterval              int
	MemoryAllocatorPollInterval int
	AgentGUID                   string
	AgentVersion                string
	plugin                      *newrelic_platform_go.NewrelicPlugin
	HttpTimer                   metrics.Timer
}

func NewAgent() *Agent {
	agent := &Agent{
		NewrelicName:                AGENT_NAME,
		NewrelicPollInterval:        NEWRELIC_POLL_INTERVAL,
		Verbose:                     false,
		CollectGcStat:               true,
		CollectMemoryStat:           true,
		GCPollInterval:              GC_POLL_INTERVAL_IN_SECONDS,
		MemoryAllocatorPollInterval: MEMORY_ALLOCATOR_POLL_INTERVAL_IN_SECONDS,
		AgentGUID:                   AGENT_GUID,
		AgentVersion:                AGENT_VERSION,
	}
	return agent
}

func (agent *Agent) initTimer() {
	if agent.HttpTimer == nil {
		agent.HttpTimer = metrics.NewTimer()
	}

	agent.CollectHttpStat = true
}

func (agent *Agent) WrapHttpHandlerFunc(h THttpHandlerFunc) THttpHandlerFunc {
	agent.initTimer()
	return func(w http.ResponseWriter, req *http.Request) {
		proxy := NewHttpHandlerFunc(h)
		proxy.timer = agent.HttpTimer
		proxy.ServeHTTP(w, req)
	}
}

func (agent *Agent) WrapHttpHandler(h http.Handler) http.Handler {
	agent.initTimer()

	proxy := NewHttpHandler(h)
	proxy.timer = agent.HttpTimer
	return proxy
}

func (agent *Agent) Run() error {
	if agent.NewrelicLicense == "" {
		return errors.New("Please, pass a valid newrelic license key.")
	}

	agent.plugin = newrelic_platform_go.NewNewrelicPlugin(agent.AgentVersion, agent.NewrelicLicense, agent.NewrelicPollInterval)
	component := newrelic_platform_go.NewPluginComponent(agent.NewrelicName, agent.AgentGUID)
	agent.plugin.AddComponent(component)

	addRuntimeMericsToComponent(component)

	if agent.CollectGcStat {
		addGCMericsToComponent(component, agent.GCPollInterval)
		agent.Debug(fmt.Sprintf("Init GC metrics collection. Poll interval %d seconds.", agent.GCPollInterval))
	}
	if agent.CollectMemoryStat {
		addMemoryMericsToComponent(component, agent.MemoryAllocatorPollInterval)
		agent.Debug(fmt.Sprintf("Init memory allocator metrics collection. Poll interval %d seconds.", agent.MemoryAllocatorPollInterval))
	}

	if agent.CollectHttpStat {
		agent.initTimer()
		addHttpMericsToComponent(component, agent.HttpTimer)
		agent.Debug(fmt.Sprintf("Init HTTP metrics collection."))
	}

	agent.plugin.Verbose = agent.Verbose
	go agent.plugin.Run()
	return nil
}

func (agent *Agent) Debug(msg string) {
	if agent.Verbose {
		log.Println(msg)
	}
}
<|endoftext|>


<commit_before>package model

import (
	"fmt"
)

type AgentReportRequest struct {
	Hostname      string
	IP            string
	AgentVersion  string
	PluginVersion string
}

func (this *AgentReportRequest) String() string {
	return fmt.Sprintf(
		"<Hostname:%s, IP:%s, AgentVersion:%s, PluginVersion:%s>",
		this.Hostname,
		this.IP,
		this.AgentVersion,
		this.PluginVersion,
	)
}

type AgentUpdateInfo struct {
	LastUpdate    int64
	ReportRequest *AgentReportRequest
}

type AgentHeartbeatRequest struct {
	Hostname string
	Checksum string
}

func (this *AgentHeartbeatRequest) String() string {
	return fmt.Sprintf(
		"<Hostname: %s, Checksum: %s>",
		this.Hostname,
		this.Checksum,
	)
}

type AgentPluginsResponse struct {
	Plugins   []string
	Checksum  string
	Timestamp int64
}

func (this *AgentPluginsResponse) String() string {
	return fmt.Sprintf(
		"<Plugins:%v, Checksum:%s, Timestamp:%v>",
		this.Plugins,
		this.Checksum,
		this.Timestamp,
	)
}

// e.g. net.port.listen or proc.num
type BuiltinMetric struct {
	Metric string
	Tags   string
}

func (this *BuiltinMetric) String() string {
	return fmt.Sprintf(
		"%s/%s",
		this.Metric,
		this.Tags,
	)
}

type BuiltinMetricResponse struct {
	Metrics   []*BuiltinMetric
	Checksum  string
	Timestamp int64
}

func (this *BuiltinMetricResponse) String() string {
	return fmt.Sprintf(
		"<Metrics:%v, Checksum:%s, Timestamp:%v>",
		this.Metrics,
		this.Checksum,
		this.Timestamp,
	)
}

type BuiltinMetricSlice []*BuiltinMetric

func (this BuiltinMetricSlice) Len() int {
	return len(this)
}
func (this BuiltinMetricSlice) Swap(i, j int) {
	this[i], this[j] = this[j], this[i]
}
func (this BuiltinMetricSlice) Less(i, j int) bool {
	return this[i].String() < this[j].String()
}
<commit_msg>remove Checksum in PluginsResponse<commit_after>package model

import (
	"fmt"
)

type AgentReportRequest struct {
	Hostname      string
	IP            string
	AgentVersion  string
	PluginVersion string
}

func (this *AgentReportRequest) String() string {
	return fmt.Sprintf(
		"<Hostname:%s, IP:%s, AgentVersion:%s, PluginVersion:%s>",
		this.Hostname,
		this.IP,
		this.AgentVersion,
		this.PluginVersion,
	)
}

type AgentUpdateInfo struct {
	LastUpdate    int64
	ReportRequest *AgentReportRequest
}

type AgentHeartbeatRequest struct {
	Hostname string
	Checksum string
}

func (this *AgentHeartbeatRequest) String() string {
	return fmt.Sprintf(
		"<Hostname: %s, Checksum: %s>",
		this.Hostname,
		this.Checksum,
	)
}

type AgentPluginsResponse struct {
	Plugins   []string
	Timestamp int64
}

func (this *AgentPluginsResponse) String() string {
	return fmt.Sprintf(
		"<Plugins:%v, Checksum:%s, Timestamp:%v>",
		this.Plugins,
		this.Checksum,
		this.Timestamp,
	)
}

// e.g. net.port.listen or proc.num
type BuiltinMetric struct {
	Metric string
	Tags   string
}

func (this *BuiltinMetric) String() string {
	return fmt.Sprintf(
		"%s/%s",
		this.Metric,
		this.Tags,
	)
}

type BuiltinMetricResponse struct {
	Metrics   []*BuiltinMetric
	Checksum  string
	Timestamp int64
}

func (this *BuiltinMetricResponse) String() string {
	return fmt.Sprintf(
		"<Metrics:%v, Checksum:%s, Timestamp:%v>",
		this.Metrics,
		this.Checksum,
		this.Timestamp,
	)
}

type BuiltinMetricSlice []*BuiltinMetric

func (this BuiltinMetricSlice) Len() int {
	return len(this)
}
func (this BuiltinMetricSlice) Swap(i, j int) {
	this[i], this[j] = this[j], this[i]
}
func (this BuiltinMetricSlice) Less(i, j int) bool {
	return this[i].String() < this[j].String()
}
<|endoftext|>


<commit_before>package model

import (
	"bytes"
	"database/sql"
	"log"
	"net/http"
	"regexp"
	"time"

	"github.com/ViBiOh/funds/crawler"
	"github.com/ViBiOh/funds/db"
	"github.com/ViBiOh/funds/jsonHttp"
	"github.com/ViBiOh/funds/tools"
)

const refreshDelayInHours = 6

var listRequest = regexp.MustCompile(`^/list$`)
var performanceURL string
var performanceMap *tools.ConcurrentMap

type results struct {
	Results interface{} `json:"results"`
}

// Init start concurrent map and init it from crawling
func Init(url string) {
	performanceURL = url
	performanceMap = tools.CreateConcurrentMap(len(performanceIds), crawler.MaxConcurrentFetcher)

	go func() {
		refreshData()
		c := time.Tick(refreshDelayInHours * time.Hour)
		for range c {
			refreshData()
		}
	}()
}

func refreshData() {
	log.Print(`Data refresh - start`)
	defer log.Print(`Data refresh - end`)

	results, errors := crawler.Crawl(performanceIds, func(ID []byte) (interface{}, error) {
		return fetchPerformance(ID)
	})

	go func() {
		ids := make([][]byte, 0)

		for id := range errors {
			ids = append(ids, id)
		}

		if len(ids) > 0 {
			log.Printf(`Errors while refreshing ids %s`, bytes.Join(ids, []byte(`, `)))
		}
	}()

	for performance := range results {
		performanceMap.Push(performance.(tools.MapContent))
	}

	if db.DB != nil {
		if err := saveData(); err != nil {
			log.Printf(`Error while saving data: %v`, err)
		}
	}
}

func saveData() error {
	var tx *sql.Tx
	var err error

	if tx, err = db.GetTx(nil); err != nil {
		return err
	}

	defer func() {
		db.EndTx(tx, err)
	}()

	for performance := range performanceMap.List() {
		if err = SavePerformance(performance.(Performance), tx); err != nil {
			log.Printf(`Error while saving Performance %v: %v`, performance, err)
		}
	}
}

// ListPerformances return content of performances' map
func ListPerformances() []Performance {
	performances := make([]Performance, 0, len(performanceIds))
	for perf := range performanceMap.List() {
		performances = append(performances, perf.(Performance))
	}

	return performances
}

func listHandler(w http.ResponseWriter, r *http.Request) {
	jsonHttp.ResponseJSON(w, results{ListPerformances()})
}

// Handler for model request. Should be use with net/http
type Handler struct {
}

func (handler Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Add(`Access-Control-Allow-Origin`, `*`)
	w.Header().Add(`Access-Control-Allow-Headers`, `Content-Type`)
	w.Header().Add(`Access-Control-Allow-Methods`, `GET`)
	w.Header().Add(`X-Content-Type-Options`, `nosniff`)

	if r.Method == http.MethodOptions {
		w.Write(nil)
		return
	}

	urlPath := []byte(r.URL.Path)

	if listRequest.Match(urlPath) {
		if r.Method == http.MethodGet {
			listHandler(w, r)
		} else {
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	} else {
		w.WriteHeader(http.StatusNotFound)
	}
}
<commit_msg>Update model.go<commit_after>package model

import (
	"bytes"
	"database/sql"
	"log"
	"net/http"
	"regexp"
	"time"

	"github.com/ViBiOh/funds/crawler"
	"github.com/ViBiOh/funds/db"
	"github.com/ViBiOh/funds/jsonHttp"
	"github.com/ViBiOh/funds/tools"
)

const refreshDelayInHours = 6

var listRequest = regexp.MustCompile(`^/list$`)
var performanceURL string
var performanceMap *tools.ConcurrentMap

type results struct {
	Results interface{} `json:"results"`
}

// Init start concurrent map and init it from crawling
func Init(url string) {
	performanceURL = url
	performanceMap = tools.CreateConcurrentMap(len(performanceIds), crawler.MaxConcurrentFetcher)

	go func() {
		refreshData()
		c := time.Tick(refreshDelayInHours * time.Hour)
		for range c {
			refreshData()
		}
	}()
}

func refreshData() {
	log.Print(`Data refresh - start`)
	defer log.Print(`Data refresh - end`)

	results, errors := crawler.Crawl(performanceIds, func(ID []byte) (interface{}, error) {
		return fetchPerformance(ID)
	})

	go func() {
		ids := make([][]byte, 0)

		for id := range errors {
			ids = append(ids, id)
		}

		if len(ids) > 0 {
			log.Printf(`Errors while refreshing ids %s`, bytes.Join(ids, []byte(`, `)))
		}
	}()

	for performance := range results {
		performanceMap.Push(performance.(tools.MapContent))
	}

	if db.DB != nil {
		if err := saveData(); err != nil {
			log.Printf(`Error while saving data: %v`, err)
		}
	}
}

func saveData() error {
	var tx *sql.Tx
	var err error

	if tx, err = db.GetTx(nil); err != nil {
		return err
	}

	defer func() {
		db.EndTx(tx, err)
	}()

	for performance := range performanceMap.List() {
		if err = SavePerformance(performance.(Performance), tx); err != nil {
			log.Printf(`Error while saving Performance %v: %v`, performance, err)
		}
	}

	return err
}

// ListPerformances return content of performances' map
func ListPerformances() []Performance {
	performances := make([]Performance, 0, len(performanceIds))
	for perf := range performanceMap.List() {
		performances = append(performances, perf.(Performance))
	}

	return performances
}

func listHandler(w http.ResponseWriter, r *http.Request) {
	jsonHttp.ResponseJSON(w, results{ListPerformances()})
}

// Handler for model request. Should be use with net/http
type Handler struct {
}

func (handler Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Add(`Access-Control-Allow-Origin`, `*`)
	w.Header().Add(`Access-Control-Allow-Headers`, `Content-Type`)
	w.Header().Add(`Access-Control-Allow-Methods`, `GET`)
	w.Header().Add(`X-Content-Type-Options`, `nosniff`)

	if r.Method == http.MethodOptions {
		w.Write(nil)
		return
	}

	urlPath := []byte(r.URL.Path)

	if listRequest.Match(urlPath) {
		if r.Method == http.MethodGet {
			listHandler(w, r)
		} else {
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	} else {
		w.WriteHeader(http.StatusNotFound)
	}
}
<|endoftext|>


<commit_before>package mansion

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"runtime"
	"strings"

	"github.com/itchio/butler/comm"
	itchio "github.com/itchio/go-itchio"
)

func (ctx *Context) UpdateBaseURL(variant VersionVariant) string {
	channel := fmt.Sprintf("%s-%s", runtime.GOOS, runtime.GOARCH)
	if variant == VersionVariantHead {
		channel += "-head"
	}
	return fmt.Sprintf("https://broth.itch.ovh/butler/%s", channel)
}

func (ctx *Context) DoVersionCheck() {
	vinfo, err := ctx.QueryLatestVersion(ctx.CurrentVariant())
	if err != nil {
		comm.Logf("Version check failed: %s", err.Error())
		return
	}

	if vinfo.Current == nil || vinfo.Latest == nil {
		return
	}

	if vinfo.Current != vinfo.Latest {
		comm.Notice("New version available",
			[]string{
				fmt.Sprintf("Current version: %s", vinfo.Current),
				fmt.Sprintf("Latest version:  %s", vinfo.Latest),
				"",
				"Run `butler upgrade` to get it.",
			})
	}
}

type Version struct {
	Name    string
	Variant VersionVariant
}

func (v *Version) String() string {
	return fmt.Sprintf("%s (%s)", v.Name, v.Variant)
}

func (v *Version) Equal(v2 *Version) bool {
	if v == nil {
		return false
	}
	if v2 == nil {
		return false
	}
	if v.Variant != v2.Variant {
		return false
	}
	if v.Name != v2.Name {
		return false
	}
	return true
}

func normalizeVersionName(name string) string {
	return strings.TrimLeft(name, "v")
}

type VersionCheckResult struct {
	Current *Version
	Latest  *Version
}

type VersionVariant string

const (
	VersionVariantStable = "stable"
	VersionVariantHead   = "head"
)

func (ctx *Context) CurrentVersion() *Version {
	variant := ctx.CurrentVariant()
	switch variant {
	case VersionVariantHead:
		return &Version{
			Name:    ctx.Commit,
			Variant: variant,
		}
	default:
		return &Version{
			Name:    normalizeVersionName(ctx.Version),
			Variant: variant,
		}
	}
}

func (ctx *Context) CurrentVariant() VersionVariant {
	switch ctx.Version {
	case "head":
		return VersionVariantHead
	default:
		return VersionVariantStable
	}
}

func (ctx *Context) QueryLatestVersion(variant VersionVariant) (*VersionCheckResult, error) {
	if ctx.Quiet {
		return nil, nil
	}

	res := &VersionCheckResult{
		Current: ctx.CurrentVersion(),
	}

	c := itchio.ClientWithKey("x")

	latestURL := fmt.Sprintf("%s/LATEST", ctx.UpdateBaseURL(variant))
	req, err := http.NewRequest("GET", latestURL, nil)
	if err != nil {
		return nil, err
	}

	latestRes, err := c.Do(req)
	if err != nil {
		return nil, err
	}

	if latestRes.StatusCode != 200 {
		err = fmt.Errorf("HTTP %d: %s", latestRes.StatusCode, latestURL)
		return nil, err
	}

	buf, err := ioutil.ReadAll(latestRes.Body)
	if err != nil {
		return nil, err
	}

	latestVersion := strings.Trim(string(buf), " \r\n")
	res.Latest = &Version{
		Name:    normalizeVersionName(latestVersion),
		Variant: variant,
	}

	return res, nil
}
<commit_msg>Don't suggest upgrade if versions are equal<commit_after>package mansion

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"runtime"
	"strings"

	"github.com/itchio/butler/comm"
	itchio "github.com/itchio/go-itchio"
)

func (ctx *Context) UpdateBaseURL(variant VersionVariant) string {
	channel := fmt.Sprintf("%s-%s", runtime.GOOS, runtime.GOARCH)
	if variant == VersionVariantHead {
		channel += "-head"
	}
	return fmt.Sprintf("https://broth.itch.ovh/butler/%s", channel)
}

func (ctx *Context) DoVersionCheck() {
	vinfo, err := ctx.QueryLatestVersion(ctx.CurrentVariant())
	if err != nil {
		comm.Logf("Version check failed: %s", err.Error())
		return
	}

	if vinfo.Current == nil || vinfo.Latest == nil {
		return
	}

	if vinfo.Current.Equal(vinfo.Latest) {
		return
	}

	if vinfo.Current != vinfo.Latest {
		comm.Notice("New version available",
			[]string{
				fmt.Sprintf("Current version: %s", vinfo.Current),
				fmt.Sprintf("Latest version:  %s", vinfo.Latest),
				"",
				"Run `butler upgrade` to get it.",
			})
	}
}

type Version struct {
	Name    string
	Variant VersionVariant
}

func (v *Version) String() string {
	return fmt.Sprintf("%s (%s)", v.Name, v.Variant)
}

func (v *Version) Equal(v2 *Version) bool {
	if v == nil {
		return false
	}
	if v2 == nil {
		return false
	}
	if v.Variant != v2.Variant {
		return false
	}
	if v.Name != v2.Name {
		return false
	}
	return true
}

func normalizeVersionName(name string) string {
	return strings.TrimLeft(name, "v")
}

type VersionCheckResult struct {
	Current *Version
	Latest  *Version
}

type VersionVariant string

const (
	VersionVariantStable = "stable"
	VersionVariantHead   = "head"
)

func (ctx *Context) CurrentVersion() *Version {
	variant := ctx.CurrentVariant()
	switch variant {
	case VersionVariantHead:
		return &Version{
			Name:    ctx.Commit,
			Variant: variant,
		}
	default:
		return &Version{
			Name:    normalizeVersionName(ctx.Version),
			Variant: variant,
		}
	}
}

func (ctx *Context) CurrentVariant() VersionVariant {
	switch ctx.Version {
	case "head":
		return VersionVariantHead
	default:
		return VersionVariantStable
	}
}

func (ctx *Context) QueryLatestVersion(variant VersionVariant) (*VersionCheckResult, error) {
	if ctx.Quiet {
		return nil, nil
	}

	res := &VersionCheckResult{
		Current: ctx.CurrentVersion(),
	}

	c := itchio.ClientWithKey("x")

	latestURL := fmt.Sprintf("%s/LATEST", ctx.UpdateBaseURL(variant))
	req, err := http.NewRequest("GET", latestURL, nil)
	if err != nil {
		return nil, err
	}

	latestRes, err := c.Do(req)
	if err != nil {
		return nil, err
	}

	if latestRes.StatusCode != 200 {
		err = fmt.Errorf("HTTP %d: %s", latestRes.StatusCode, latestURL)
		return nil, err
	}

	buf, err := ioutil.ReadAll(latestRes.Body)
	if err != nil {
		return nil, err
	}

	latestVersion := strings.Trim(string(buf), " \r\n")
	res.Latest = &Version{
		Name:    normalizeVersionName(latestVersion),
		Variant: variant,
	}

	return res, nil
}
<|endoftext|>


<commit_before>/*
   Copyright 2015 CoreOS, Inc.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package command

import (
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
)

func UpgradeCommand() cli.Command {
	return cli.Command{
		Name:  "upgrade",
		Usage: "upgrade an old version etcd cluster to a new version",
		Flags: []cli.Flag{
			cli.StringFlag{Name: "old-version", Value: "1", Usage: "Old internal version"},
			cli.StringFlag{Name: "new-version", Value: "2", Usage: "New internal version"},
			cli.StringFlag{Name: "peer-url", Value: "", Usage: "An etcd peer url string"},
		},
		Action: handleUpgrade,
	}
}

func handleUpgrade(c *cli.Context) {
	if c.String("old-version") != "1" {
		fmt.Printf("Do not support upgrade from version %s\n", c.String("old-version"))
		os.Exit(1)
	}
	if c.String("new-version") != "2" {
		fmt.Printf("Do not support upgrade to version %s\n", c.String("new-version"))
		os.Exit(1)
	}
	t, err := getTransport(c)
	if err != nil {
		log.Fatal(err)
	}
	client := http.Client{Transport: t}
	resp, err := client.Get(c.String("peer-url") + "/v2/admin/next-internal-version")
	if err != nil {
		fmt.Printf("Failed to send upgrade request to %s: %v\n", c.String("peer-url"), err)
		return
	}
	if resp.StatusCode == http.StatusOK {
		fmt.Println("Cluster will start upgrading from internal version 1 to 2 in 10 seconds.")
		return
	}
	if resp.StatusCode == http.StatusNotFound {
		fmt.Println("Cluster cannot upgrade to 2: version is not 0.4.7")
		return
	}
	fmt.Printf("Faild to send upgrade request to %s: bad status code %d\n", c.String("cluster-url"), resp.StatusCode)
}
<commit_msg>etcdctl/upgrade: use peer flags for peer transport<commit_after>/*
   Copyright 2015 CoreOS, Inc.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package command

import (
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
	"github.com/coreos/etcd/pkg/transport"
)

func UpgradeCommand() cli.Command {
	return cli.Command{
		Name:  "upgrade",
		Usage: "upgrade an old version etcd cluster to a new version",
		Flags: []cli.Flag{
			cli.StringFlag{Name: "old-version", Value: "1", Usage: "Old internal version"},
			cli.StringFlag{Name: "new-version", Value: "2", Usage: "New internal version"},
			cli.StringFlag{Name: "peer-url", Value: "", Usage: "An etcd peer url string"},
			cli.StringFlag{Name: "peer-cert-file", Value: "", Usage: "identify HTTPS peer using this SSL certificate file"},
			cli.StringFlag{Name: "peer-key-file", Value: "", Usage: "identify HTTPS peer using this SSL key file"},
			cli.StringFlag{Name: "peer-ca-file", Value: "", Usage: "verify certificates of HTTPS-enabled peers using this CA bundle"},
		},
		Action: handleUpgrade,
	}
}

func handleUpgrade(c *cli.Context) {
	if c.String("old-version") != "1" {
		fmt.Printf("Do not support upgrade from version %s\n", c.String("old-version"))
		os.Exit(1)
	}
	if c.String("new-version") != "2" {
		fmt.Printf("Do not support upgrade to version %s\n", c.String("new-version"))
		os.Exit(1)
	}
	tls := transport.TLSInfo{
		CAFile:   c.String("peer-ca-file"),
		CertFile: c.String("peer-cert-file"),
		KeyFile:  c.String("peer-key-file"),
	}
	t, err := transport.NewTransport(tls)
	if err != nil {
		log.Fatal(err)
	}
	client := http.Client{Transport: t}
	resp, err := client.Get(c.String("peer-url") + "/v2/admin/next-internal-version")
	if err != nil {
		fmt.Printf("Failed to send upgrade request to %s: %v\n", c.String("peer-url"), err)
		return
	}
	if resp.StatusCode == http.StatusOK {
		fmt.Println("Cluster will start upgrading from internal version 1 to 2 in 10 seconds.")
		return
	}
	if resp.StatusCode == http.StatusNotFound {
		fmt.Println("Cluster cannot upgrade to 2: version is not 0.4.7")
		return
	}
	fmt.Printf("Faild to send upgrade request to %s: bad status code %d\n", c.String("cluster-url"), resp.StatusCode)
}
<|endoftext|>


<commit_before>package service_test

import (
	"github.com/stretchr/testify/assert"
	"github.com/vlad-doru/experimento/utils/test"
	"testing"
)

// TestGetAllVariablesBasic tests a simple usecase which should always yield the
// same value for an AB testing service.
func TestABTestingBasic(t *testing.T) {
	s, err := test.GetABTestingService()
	assert.Nil(t, err, "Error while getting the experimento service")

	v, err := s.GetAllVariables("doru")

	assert.Nil(t, err, "Error while getting all variables %v", err)
	assert.NotNil(t, v, "Empty all variables map")
	assert.NotNil(t, v["experiment"], "Empty experiment variables")
	assert.Equal(t, "test", v["experiment"]["group"], "Wrong group in experiment")
	assert.Equal(t, "b", v["experiment"]["var"], "Wrong variable choice")
}

func TestABTestingDeterminism(t *testing.T) {
	test.SetSeed(1) // set the seed so that we can easily replicate results
	s, err := test.GetABTestingService()
	assert.Nil(t, err, "Error while getting the experimento service")
	initialGroup := map[string]string{}
	// Generate 1000 random string and check that
	// if we call the function 1000 times for each we get the same group.
	for i := 0; i < 100; i++ {
		randID := test.RandString(6, 10)
		v, err := s.GetAllVariables(randID)
		assert.Nil(t, err, "Could not get all the variables: %v", err)
		group := v["experiment"]["group"]
		if group == "" {
			// Not participating
			continue
		}
		assert.True(t,
			group == "control" || group == "test",
			"Invalid group variable value %v", group)
		initialGroup[randID] = group
	}
	// Check for determinism.
	for i := 0; i < 1000; i++ {
		for randID, expectedGroup := range initialGroup {
			v, err := s.GetAllVariables(randID)
			assert.Nil(t, err, "Could not get all the variables: %v", err)
			group := v["experiment"]["group"]
			assert.Equal(t, expectedGroup, group, "Non deterministic behaviour of A/B testing for id", randID)
		}
	}
}

func TestABTestingDistribution(t *testing.T) {
	test.SetSeed(1) // set the seed so that we can easily replicate results
	s, err := test.GetABTestingService()
	assert.Nil(t, err, "Error while getting the experimento service")
	groupASize := test.ABExpTestGroupASize
	groupBSize := test.ABExpTestGroupBSize
	expSize := test.ABExpTestSize
	// Generate 100K random strings and call the GetAllVariables method.
	// Count the result from each group.
	groupACount := 0.0
	groupBCount := 0.0
	noGroupCount := 0.0
	sampleSize := 100000
	for i := 0; i < sampleSize; i++ {
		randID := test.RandString(6, 10)
		v, err := s.GetAllVariables(randID)
		assert.Nil(t, err, "Could not get all the variables: %v", err)
		group := v["experiment"]["group"]
		switch group {
		case "control":
			groupACount++
			break
		case "test":
			groupBCount++
			break
		case "":
			noGroupCount++
			break
		default:
			assert.Fail(t, "Invalid group %s", group)
		}
	}
	groupARatio := groupACount / float64(expSize*float64(sampleSize))
	groupBRatio := groupBCount / float64(expSize*float64(sampleSize))
	noGroupRatio := noGroupCount / float64(sampleSize)
	assert.InEpsilon(t, groupASize, groupARatio, 0.01, "Invalid ratio difference for control group")
	assert.InEpsilon(t, groupBSize, groupBRatio, 0.01, "Invalid ratio difference for test group")
	assert.InEpsilon(t, 1-expSize, noGroupRatio, 0.01, "Invalid ratio difference for non participating group")
}

func BenchmarkABTesting(b *testing.B) {
	// Stop the benchmark timer.
	b.StopTimer()
	test.SetSeed(1)                          // set the seed so that we can easily replicate results
	s, err := test.GetABTestingServiceN(100) // get a service with 100 experiments
	if err != nil {
		b.Logf("Error while creating the service: %s", err)
		b.FailNow()
	}
	// Generate 1000 random ids.
	randIDs := []string{}
	const sampleSize int = 10000
	for i := 0; i < sampleSize; i++ {
		id := test.RandString(6, 10)
		randIDs = append(randIDs, id)
	}
	currentIndex := 0
	incrementIndex := func() {
		currentIndex++
		if currentIndex == sampleSize {
			currentIndex = 0
		}
	}
	// Restart the timer.
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		id := randIDs[currentIndex]
		incrementIndex()
		_, err := s.GetAllVariables(id)
		if err != nil {
			b.Logf("What %v", err)
			b.FailNow()

		}
	}
}
<commit_msg>Have only 1000 unique ids so that the benchmark can be run without the benchtime option.<commit_after>package service_test

import (
	"github.com/stretchr/testify/assert"
	"github.com/vlad-doru/experimento/utils/test"
	"testing"
)

// TestGetAllVariablesBasic tests a simple usecase which should always yield the
// same value for an AB testing service.
func TestABTestingBasic(t *testing.T) {
	s, err := test.GetABTestingService()
	assert.Nil(t, err, "Error while getting the experimento service")

	v, err := s.GetAllVariables("doru")

	assert.Nil(t, err, "Error while getting all variables %v", err)
	assert.NotNil(t, v, "Empty all variables map")
	assert.NotNil(t, v["experiment"], "Empty experiment variables")
	assert.Equal(t, "test", v["experiment"]["group"], "Wrong group in experiment")
	assert.Equal(t, "b", v["experiment"]["var"], "Wrong variable choice")
}

func TestABTestingDeterminism(t *testing.T) {
	test.SetSeed(1) // set the seed so that we can easily replicate results
	s, err := test.GetABTestingService()
	assert.Nil(t, err, "Error while getting the experimento service")
	initialGroup := map[string]string{}
	// Generate 1000 random string and check that
	// if we call the function 1000 times for each we get the same group.
	for i := 0; i < 100; i++ {
		randID := test.RandString(6, 10)
		v, err := s.GetAllVariables(randID)
		assert.Nil(t, err, "Could not get all the variables: %v", err)
		group := v["experiment"]["group"]
		if group == "" {
			// Not participating
			continue
		}
		assert.True(t,
			group == "control" || group == "test",
			"Invalid group variable value %v", group)
		initialGroup[randID] = group
	}
	// Check for determinism.
	for i := 0; i < 1000; i++ {
		for randID, expectedGroup := range initialGroup {
			v, err := s.GetAllVariables(randID)
			assert.Nil(t, err, "Could not get all the variables: %v", err)
			group := v["experiment"]["group"]
			assert.Equal(t, expectedGroup, group, "Non deterministic behaviour of A/B testing for id", randID)
		}
	}
}

func TestABTestingDistribution(t *testing.T) {
	test.SetSeed(1) // set the seed so that we can easily replicate results
	s, err := test.GetABTestingService()
	assert.Nil(t, err, "Error while getting the experimento service")
	groupASize := test.ABExpTestGroupASize
	groupBSize := test.ABExpTestGroupBSize
	expSize := test.ABExpTestSize
	// Generate 100K random strings and call the GetAllVariables method.
	// Count the result from each group.
	groupACount := 0.0
	groupBCount := 0.0
	noGroupCount := 0.0
	sampleSize := 100000
	for i := 0; i < sampleSize; i++ {
		randID := test.RandString(6, 10)
		v, err := s.GetAllVariables(randID)
		assert.Nil(t, err, "Could not get all the variables: %v", err)
		group := v["experiment"]["group"]
		switch group {
		case "control":
			groupACount++
			break
		case "test":
			groupBCount++
			break
		case "":
			noGroupCount++
			break
		default:
			assert.Fail(t, "Invalid group %s", group)
		}
	}
	groupARatio := groupACount / float64(expSize*float64(sampleSize))
	groupBRatio := groupBCount / float64(expSize*float64(sampleSize))
	noGroupRatio := noGroupCount / float64(sampleSize)
	assert.InEpsilon(t, groupASize, groupARatio, 0.01, "Invalid ratio difference for control group")
	assert.InEpsilon(t, groupBSize, groupBRatio, 0.01, "Invalid ratio difference for test group")
	assert.InEpsilon(t, 1-expSize, noGroupRatio, 0.01, "Invalid ratio difference for non participating group")
}

func BenchmarkABTesting(b *testing.B) {
	// Stop the benchmark timer.
	b.StopTimer()
	test.SetSeed(1)                          // set the seed so that we can easily replicate results
	s, err := test.GetABTestingServiceN(100) // get a service with 100 experiments
	if err != nil {
		b.Logf("Error while creating the service: %s", err)
		b.FailNow()
	}
	// Generate 1000 random ids.
	randIDs := []string{}
	const sampleSize int = 1000
	for i := 0; i < sampleSize; i++ {
		id := test.RandString(6, 10)
		randIDs = append(randIDs, id)
	}
	currentIndex := 0
	incrementIndex := func() {
		currentIndex++
		if currentIndex == sampleSize {
			currentIndex = 0
		}
	}
	// Restart the timer.
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		id := randIDs[currentIndex]
		incrementIndex()
		_, err := s.GetAllVariables(id)
		if err != nil {
			b.Logf("What %v", err)
			b.FailNow()

		}
	}
}
<|endoftext|>