<commit_before>package grx

import (
	"sync"
	"time"
)

// Observer is a "sentinel" object consisting of three methods to handle event stream.
type Observer struct {
	OnNext      func(e *Event)
	OnError     func(e *Event)
	OnCompleted func(e *Event)
}

// Event either emits a value, an error, or notify as completed.
type Event struct {
	Value     interface{}
	Error     error
	Completed bool
}

// Observable is a stream of events
// TODO: Consider removing Name
type Observable struct {
	Name      string
	Stream    chan *Event
	Observer  *Observer
}

func (o *Observable) isCompleted() bool {
	if len(o.Stream) > 0 {
		return false
	}
	return true
}

// NewObservable constructs an empty Observable
func NewObservable(name string) *Observable {
	return &Observable{
		Name: name,
		Stream: make(chan *Event),
	}
}

// Add adds an Event to the Observable
func (o *Observable) Add(ev *Event) {
	go func() {
		o.Stream <- ev
	}()
}

// Empty creates an Observable with one last item marked as "completed"
func Empty() *Observable {
	o := &Observable{
		Stream: make(chan *Event, 1),
	}
	go func() {
		o.Stream <- &Event{ Completed: true }
		close(o.Stream)
	}()
	return o
}

func Interval(d time.Duration) *Observable {
	o := &Observable{
		Stream: make(chan *Event),
	}

	i := 0
	go func() {
		for {
			o.Stream <- &Event{Value: i}
			<-time.After(d)
			i++
		}
	}()
	return o
}

// Just creates an observable with only one item and emit "as-is".
func Just(item interface{}) *Observable {
	o := &Observable{ Stream: make(chan *Event, 1) }
	go func() {
		o.Stream <- &Event{Value: item}
		close(o.Stream)
	}()
	return o
}

// From creates an observable from a slice of items and emit them in order.
func From(items []interface{}) *Observable {
	o := &Observable{
		Stream: make(chan *Event, len(items)),
	}
	for _, item := range items {
		o.Stream <- &Event{Value: item}
	}
	close(o.Stream)
	return o
}

// Start creates an Observable from a directive-like function's returned event.
func Start(fx ...func() *Event) *Observable {
	o := &Observable{
		Stream: make(chan *Event, len(fx)),
	}

	var wg sync.WaitGroup
	wg.Add(len(fx))
	for _, f := range fx {
		go func(f func() *Event) {
			defer wg.Done()
			o.Stream <-f()
		}(f)
	}

	// Must wait in a goroutine, else it will block and not returning
	// until go of the above goroutines have returned.
	go func() {
		wg.Wait()
		close(o.Stream)
	}()

	return o
}

// Subscribe subscribes an Observer to the Observable and starts it.
func (o *Observable) Subscribe(ob *Observer) *Observable {
	o.Observer = ob

	if o.Stream == nil {
		return o
	}

	// Loop over the Observable's stream.
	for ev := range o.Stream {

		if ev.Value != nil {
			ob.OnNext(ev)
		} else if ev.Error != nil {
			ob.OnError(ev)
			return o
		}
	}

	// A hack for empty, finite Observable--emit a "terminal" event to signal stream's termination.
	o.Observer.OnCompleted(&Event{Completed: true})
	return o
}

<commit_msg>Remove monolithic file<commit_after><|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
)

func main() {
	sourceFile, err := ioutil.ReadFile(os.Args[1])
	var s string
	var cFile bool = (filepath.Ext(os.Args[1]) == "c")

	if err == nil {
		s = string(sourceFile)
		//fmt.Println(s)
	}
	s = Removecomments(s);
	splitFile := strings.Split(s, "\n")
	splitFile = RemoveNewlines(splitFile)
	lineSlice := CreateLines(splitFile)
	MarkInvalid(lineSlice)
	debugFile, _ := os.Create("testfiles/c++/debug.cpp")

	for i := 0; i < len(lineSlice); i++ {
		AddPrint(debugFile, lineSlice[i], cFile)
	}
	debugFile.Close()
	CompileAndRun()
}

type lineType struct {
	s    string
	code int
	info string
}

func AddPrint(file *os.File, line lineType, cFile bool) {
	if cFile {
		if line.code == 0 {
			file.WriteString("printf(\"" + line.s + "\n\");\n")
			file.WriteString(line.s + "\n")
		}
	} else {
		switch line.code {
		case 0:
			file.WriteString("std::cout << \"" + line.s + "\" << std::endl;\n")
			file.WriteString(line.s + "\n")
		case 343: //function
			file.WriteString(line.s + "\n")
			file.WriteString("Entering " + line.info + "\n")
		case 666: //if
			file.WriteString("std::cout << \"" + line.s + "\" << std::endl;\n")
			file.WriteString(line.s + "\n")
			file.WriteString(line.info + "evaluates to true\n")
		case 752: //else if
			file.WriteString(line.s + "\n")
			file.WriteString(line.info + "evaluates to true\n")
		case 580: //else
			file.WriteString(line.s + "\n")
			file.WriteString("Else\n")
		case 4: //for
			file.WriteString("std::cout << \"" + line.s + "\" << std::endl;\n")
			file.WriteString(line.s + "\n")
			file.WriteString("Looping\n")
		case 603: //while
			file.WriteString("std::cout << \"" + line.s + "\" << std::endl;\n")
			file.WriteString(line.s + "\n")
			file.WriteString(line.info + "evaluates to true, looping")
		case 1:
			file.WriteString(line.s + "\n")
		}
	}
}

func MarkInvalid(lineSlice []lineType) {

	r, _ := regexp.Compile(`(.*)\((.*)\)(.*)\{`)
	opencount := 0
	closecount := 0

	for i := 0; i < len(lineSlice); i++ {
		fmt.Println(opencount, closecount, lineSlice[i].code)
		if opencount <= closecount {
			lineSlice[i].code = 1
		}
		if r.MatchString(lineSlice[i].s) {
			opencount++
		} else if strings.TrimSpace(lineSlice[i].s)[0] == 125 {
			closecount++
			lineSlice[i].code = 1
		}
	}
}

func CreateLines(stringSlice []string) []lineType {
	var lineSlice []lineType
	for i := 0; i < len(stringSlice); i++ {
		lineSlice = append(lineSlice, lineType{stringSlice[i], 0, ""})
	}
	return lineSlice
}

func RemoveNewlines(stringSlice []string) []string {
	var newSlice []string
	for i := 0; i < len(stringSlice); i++ {
		if stringSlice[i] != "" && stringSlice[i][0] != 10 {
			newSlice = append(newSlice, stringSlice[i])
		}
	}
	return newSlice
}

func CompileAndRun() {
	gccCmd := exec.Command("g++", "-Wall", "testfiles/c++/debug.cpp", "-o", "testfiles/c++/out")
	gccCmd.Run()
	runCmd := exec.Command("./testfiles/c++/out")
	output, err := runCmd.Output()
	fmt.Println(string(output))
	fmt.Println(err)
}

func Removecomments(s string) string{
     r, _ := regexp.Compile(`/\*(.*?)\*/|//(.*?)\n`)
     ret := r.ReplaceAllString(s, "")
     fmt.Println("TEST\n" + ret + "\nENDTEST\n")
     return ret
}
<commit_msg>Added flag to only print last line<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
)

func main() {
	sourceFile, err := ioutil.ReadFile(os.Args[1])
	var s string
	var cFile bool = (filepath.Ext(os.Args[1]) == "c")
	var lastLine bool
	if len(os.Args) > 2 && os.Args[2] == "--lastline=true" {
		lastLine = true
	}

	if err == nil {
		s = string(sourceFile)
		//fmt.Println(s)
	}
	s = Removecomments(s);
	splitFile := strings.Split(s, "\n")
	splitFile = RemoveNewlines(splitFile)
	lineSlice := CreateLines(splitFile)
	MarkInvalid(lineSlice)
	debugFile, _ := os.Create("testfiles/c++/debug.cpp")

	for i := 0; i < len(lineSlice); i++ {
		AddPrint(debugFile, lineSlice[i], cFile)
	}
	debugFile.Close()
	CompileAndRun(lastLine)
}

type lineType struct {
	s    string
	code int
	info string
}

func AddPrint(file *os.File, line lineType, cFile bool) {
	if cFile {
		if line.code == 0 {
			file.WriteString("printf(\"" + line.s + "\n\");\n")
			file.WriteString(line.s + "\n")
		}
	} else {
		switch line.code {
		case 0:
			file.WriteString("std::cout << \"" + line.s + "\" << std::endl;\n")
			file.WriteString(line.s + "\n")
		case 343: //function
			file.WriteString(line.s + "\n")
			file.WriteString("Entering " + line.info + "\n")
		case 666: //if
			file.WriteString("std::cout << \"" + line.s + "\" << std::endl;\n")
			file.WriteString(line.s + "\n")
			file.WriteString(line.info + "evaluates to true\n")
		case 752: //else if
			file.WriteString(line.s + "\n")
			file.WriteString(line.info + "evaluates to true\n")
		case 580: //else
			file.WriteString(line.s + "\n")
			file.WriteString("Else\n")
		case 4: //for
			file.WriteString("std::cout << \"" + line.s + "\" << std::endl;\n")
			file.WriteString(line.s + "\n")
			file.WriteString("Looping\n")
		case 603: //while
			file.WriteString("std::cout << \"" + line.s + "\" << std::endl;\n")
			file.WriteString(line.s + "\n")
			file.WriteString(line.info + "evaluates to true, looping")
		case 1:
			file.WriteString(line.s + "\n")
		}
	}
}

func MarkInvalid(lineSlice []lineType) {

	r, _ := regexp.Compile(`(.*)\((.*)\)(.*)\{`)
	opencount := 0
	closecount := 0

	for i := 0; i < len(lineSlice); i++ {
		fmt.Println(opencount, closecount, lineSlice[i].code)
		if opencount <= closecount {
			lineSlice[i].code = 1
		}
		if r.MatchString(lineSlice[i].s) {
			opencount++
		} else if strings.TrimSpace(lineSlice[i].s)[0] == 125 {
			closecount++
			lineSlice[i].code = 1
		}
	}
}

func CreateLines(stringSlice []string) []lineType {
	var lineSlice []lineType
	for i := 0; i < len(stringSlice); i++ {
		lineSlice = append(lineSlice, lineType{stringSlice[i], 0, ""})
	}
	return lineSlice
}

func RemoveNewlines(stringSlice []string) []string {
	var newSlice []string
	for i := 0; i < len(stringSlice); i++ {
		if stringSlice[i] != "" && stringSlice[i][0] != 10 {
			newSlice = append(newSlice, stringSlice[i])
		}
	}
	return newSlice
}

func CompileAndRun(lastLine bool) {
	gccCmd := exec.Command("g++", "-Wall", "testfiles/c++/debug.cpp", "-o", "testfiles/c++/out")
	gccCmd.Run()
	runCmd := exec.Command("./testfiles/c++/out")
	output, err := runCmd.Output()
	if !lastLine {
		fmt.Println(string(output))
	} else {
		outSlice := strings.Split(strings.TrimSpace(string(output)), "\n")
		fmt.Println(outSlice[len(outSlice)-1])
	}
	fmt.Println(err)
}

func Removecomments(s string) string{
     r, _ := regexp.Compile(`/\*(.*?)\*/|//(.*?)\n`)
     ret := r.ReplaceAllString(s, "")
     fmt.Println("TEST\n" + ret + "\nENDTEST\n")
     return ret
}
<|endoftext|>


<commit_before>package hmn

import (
	"bytes"
	"reflect"
	"fmt"
	"time"
	"strconv"
	"strings"
	"regexp"
)

var camelRegex = regexp.MustCompile("[0-9A-Za-z]+")

func Load(t interface{}, line string) error {
	// Find the actual value of the object
	v := reflect.ValueOf(t)
	if v.Kind() == reflect.Ptr && !v.IsNil() {
		v = v.Elem()
	}

	// Split using a whitespace
	keyVals := strings.Split(line, " ")
	for i := range keyVals {
		// Take key, val pairs so 2 at a time
		i = 2 * i
		if i >= len(keyVals) {
			break
		}

		// Convert key to CamelCase
		key := CamelCase(keyVals[i])
		val := keyVals[i + 1]

		field := v.FieldByName(key)
		if err := LoadField(t, field, key, val); err != nil {
			return err
		}
	}
	return nil
}

// LoadField ...
func LoadField(t interface{}, field reflect.Value, key, val string) error {
	// Figure out the kind, then load it up
	switch field.Kind() {
	case reflect.Int32, reflect.Int64, reflect.Int:
		value, err := strconv.ParseInt(val, 10, 64)
		if err != nil {
			return err
		}
		field.SetInt(value)
	case reflect.Float64, reflect.Float32:
		value, err := strconv.ParseFloat(val, 64)
		if err != nil {
			return err
		}
		field.SetFloat(value)
	case reflect.String:
		field.SetString(val)
	case reflect.Struct:
		LoadStruct(t, field, val)
	default:
		panic(
			fmt.Sprintf(
				"Unsure how to handle Kind `%v.\nGo to github.com/johnmcconnell/hmn/hmm.go to add handling.",
				field.Kind()))
	}
	return nil
}

// LoadStruct ...
func LoadStruct(t interface{}, field reflect.Value, val string) {
	// Figure out the type, then load it up
	name := field.Type().Name()
	switch name {
	case "Time":
		time, err := time.Parse("2006-01-02", val)
		if err != nil {
			panic(err)
		}
		field.Set(reflect.ValueOf(time))
	default:
		panic(
			fmt.Sprintf(
				"Unsure how to handle `%s.\nType `%s\nGo to github.com/johnmcconnell/hmn/hmm.go to add handling.",
				name,
				t))
	}
}

// CamelCase ...
func CamelCase(src string) string {
  byteSrc := []byte(src)
  chunks := camelRegex.FindAll(byteSrc, -1)
  for idx, val := range chunks {
    chunks[idx] = bytes.Title(val)
  }
  return string(bytes.Join(chunks, nil))
}

<commit_msg>added better zero type error message<commit_after>package hmn

import (
	"bytes"
	"reflect"
	"fmt"
	"time"
	"strconv"
	"strings"
	"regexp"
)

var camelRegex = regexp.MustCompile("[0-9A-Za-z]+")

func Load(t interface{}, line string) error {
	// Find the actual value of the object
	v := reflect.ValueOf(t)
	if v.Kind() == reflect.Ptr && !v.IsNil() {
		v = v.Elem()
	}

	// Split using a whitespace
	keyVals := strings.Split(line, " ")
	for i := range keyVals {
		// Take key, val pairs so 2 at a time
		i = 2 * i
		if i >= len(keyVals) {
			break
		}

		// Convert key to CamelCase
		key := CamelCase(keyVals[i])
		val := keyVals[i + 1]

		field := v.FieldByName(key)
		if err := LoadField(t, field, key, val); err != nil {
			return err
		}
	}
	return nil
}

// LoadField ...
func LoadField(t interface{}, field reflect.Value, key, val string) error {
	// Figure out the kind, then load it up
	switch field.Kind() {
	case reflect.Int32, reflect.Int64, reflect.Int:
		value, err := strconv.ParseInt(val, 10, 64)
		if err != nil {
			return err
		}
		field.SetInt(value)
	case reflect.Float64, reflect.Float32:
		value, err := strconv.ParseFloat(val, 64)
		if err != nil {
			return err
		}
		field.SetFloat(value)
	case reflect.String:
		field.SetString(val)
	case reflect.Struct:
		LoadStruct(t, field, val)
	case reflect.Invalid:
		panic(
			fmt.Sprintf(
				"Invalid type, check key name\nKey `%s\nGo to github.com/johnmcconnell/hmn/hmm.go to add handling.",
				key))
	default:
		panic(
			fmt.Sprintf(
				"Unsure how to handle Kind `%s.\nKind `%s\nGo to github.com/johnmcconnell/hmn/hmm.go to add handling.",
				field,
				field.Kind()))
	}
	return nil
}

// LoadStruct ...
func LoadStruct(t interface{}, field reflect.Value, val string) {
	// Figure out the type, then load it up
	name := field.Type().Name()
	switch name {
	case "Time":
		time, err := time.Parse("2006-01-02", val)
		if err != nil {
			panic(err)
		}
		field.Set(reflect.ValueOf(time))
	default:
		panic(
			fmt.Sprintf(
				"Unsure how to handle `%s.\nType `%s\nGo to github.com/johnmcconnell/hmn/hmm.go to add handling.",
				name,
				t))
	}
}

// CamelCase ...
func CamelCase(src string) string {
  byteSrc := []byte(src)
  chunks := camelRegex.FindAll(byteSrc, -1)
  for idx, val := range chunks {
    chunks[idx] = bytes.Title(val)
  }
  return string(bytes.Join(chunks, nil))
}

<|endoftext|>


<commit_before>package main

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/gob"
	"encoding/hex"
	"flag"
	"fmt"
	"github.com/bmizerany/pat"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
)

type CacheEntry struct {
	ContentType string
	Body        []byte
}

// The directory for caching files
var directory string

// The secret for checking the HMAC on requests
var secret []byte

// Fetch image from cache
func fetchImageFromCache(filename string) (contentType string, body []byte, ok bool) {
	ok = false

	f, err := os.Open(filename)
	if err != nil {
		return
	}
	defer f.Close()

	var entry CacheEntry
	dec := gob.NewDecoder(f)
	err = dec.Decode(&entry)
	if err != nil {
		log.Printf("Error while decoding %s\n", filename)
	} else {
		contentType = entry.ContentType
		body = entry.Body
		ok = true
	}

	return
}

// Save the body and the headers in cache (on disk)
func saveImageInCache(filename string, contentType string, body []byte) {
	dirname := path.Dir(filename)
	err := os.MkdirAll(dirname, 0755)
	if err != nil {
		return
	}

	f, err := os.OpenFile(filename+".tmp", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
	if err != nil {
		log.Printf("Error while opening %s\n", filename)
		return
	}
	defer f.Close()

	enc := gob.NewEncoder(f)
	err = enc.Encode(CacheEntry{contentType, body})
	if err != nil {
		log.Printf("Error while encoding %s\n", filename)
		os.Remove(filename + ".tmp")
		return
	}
	err = os.Rename(filename+".tmp", filename)
	if err != nil {
		log.Printf("Error while renaming %s\n", filename)
		os.Remove(filename + ".tmp")
	}
}

// Generate a key for cache from a string
func generateKeyForCache(s string) string {
	h := sha1.New()
	io.WriteString(h, s)
	key := h.Sum(nil)

	// Use 3 levels of hasing to avoid having too many files in the same directory
	return fmt.Sprintf("%s/%x/%x/%x/%x", directory, key[0:1], key[1:2], key[2:3], key[3:])
}

// Fetch the image from the distant server
func fetchImageFromServer(url string) (contentType string, body []byte, err error) {
	res, err := http.Get(url)
	if err != nil {
		return
	}

	defer res.Body.Close()
	body, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}
	contentType = res.Header.Get("Content-Type")
	log.Printf("Fetch %s (%s)\n", url, contentType)

	return
}

// Fetch image from cache if available, or from the server
func fetchImage(url string) (contentType string, body []byte, err error) {
	key := generateKeyForCache(url)

	contentType, body, ok := fetchImageFromCache(key)
	if ok {
		return
	}

	contentType, body, err = fetchImageFromServer(url)
	if err == nil {
		go saveImageInCache(key, contentType, body)
	}

	return
}

// Decode the URL and compute the associated HMAC
func decodeUrl(encoded_url string) (url []byte, actual string, err error) {
	url, err = hex.DecodeString(encoded_url)
	if err != nil {
		return
	}

	h := hmac.New(sha1.New, secret)
	_, err = h.Write(url)
	if err != nil {
		return
	}
	actual = fmt.Sprintf("%x", h.Sum(nil))

	return
}

// Fetch the image from the URL (or cache) and respond with it
func Img(w http.ResponseWriter, r *http.Request) {
	expected := r.URL.Query().Get(":hmac")
	encoded_url := r.URL.Query().Get(":encoded_url")
	url, actual, err := decodeUrl(encoded_url)
	if err != nil {
		log.Printf("Invalid URL %s%s\n", encoded_url)
		http.Error(w, "Invalid parameters", 400)
		return
	}
	if expected != actual {
		log.Printf("Invalid HMAC expected=%s actual=%s\n", expected, actual)
		http.Error(w, "Invalid HMAC", 403)
		return
	}
	contentType, body, err := fetchImage(string(url))
	if err != nil {
		http.NotFound(w, r)
		return
	}
	w.Header().Add("Content-Type", contentType)
	w.Write(body)
}

// Returns 200 OK if the server is running (for monitoring)
func Status(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "OK")
}

func main() {
	// Parse the command-line
	var addr string
	var secr string
	flag.StringVar(&addr, "a", "127.0.0.1:8000", "Bind to this address:port")
	flag.StringVar(&secr, "s", "252c38cdb9f638908fab5df7263d156c759d590b1251785fa612e7874ee9bbcc32a61f8d795e7593ca31f8f47396c497b215e1abde6e947d7e25772f30115a7e", "The secret for HMAC check")
	flag.StringVar(&directory, "d", "cache", "The directory for the caching files")
	flag.Parse()
	secret = []byte(secr)

	// Routing
	m := pat.New()
	m.Get("/status", http.HandlerFunc(Status))
	m.Get("/img/:hmac/:encoded_url", http.HandlerFunc(Img))
	http.Handle("/", m)

	// Start the HTTP server
	log.Printf("Listening on http://%s/\n", addr)
	err := http.ListenAndServe(addr, nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
<commit_msg>Add some checks: max size and content-type<commit_after>package main

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/gob"
	"encoding/hex"
	"errors"
	"flag"
	"fmt"
	"github.com/bmizerany/pat"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path"
)

// An entry of cache
type CacheEntry struct {
	ContentType string
	Body        []byte
}

// The maximal size for an image is 5MB
const maxSize = 5 * (1 << 20)

// The directory for caching files
var directory string

// The secret for checking the HMAC on requests
var secret []byte

// Fetch image from cache
func fetchImageFromCache(filename string) (contentType string, body []byte, ok bool) {
	ok = false

	f, err := os.Open(filename)
	if err != nil {
		return
	}
	defer f.Close()

	var entry CacheEntry
	dec := gob.NewDecoder(f)
	err = dec.Decode(&entry)
	if err != nil {
		log.Printf("Error while decoding %s\n", filename)
	} else {
		contentType = entry.ContentType
		body = entry.Body
		ok = true
	}

	return
}

// Save the body and the headers in cache (on disk)
func saveImageInCache(filename string, contentType string, body []byte) {
	dirname := path.Dir(filename)
	err := os.MkdirAll(dirname, 0755)
	if err != nil {
		return
	}

	f, err := os.OpenFile(filename+".tmp", os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
	if err != nil {
		log.Printf("Error while opening %s\n", filename)
		return
	}
	defer f.Close()

	enc := gob.NewEncoder(f)
	err = enc.Encode(CacheEntry{contentType, body})
	if err != nil {
		log.Printf("Error while encoding %s\n", filename)
		os.Remove(filename + ".tmp")
		return
	}
	err = os.Rename(filename+".tmp", filename)
	if err != nil {
		log.Printf("Error while renaming %s\n", filename)
		os.Remove(filename + ".tmp")
	}
}

// Generate a key for cache from a string
func generateKeyForCache(s string) string {
	h := sha1.New()
	io.WriteString(h, s)
	key := h.Sum(nil)

	// Use 3 levels of hasing to avoid having too many files in the same directory
	return fmt.Sprintf("%s/%x/%x/%x/%x", directory, key[0:1], key[1:2], key[2:3], key[3:])
}

// Fetch the image from the distant server
func fetchImageFromServer(uri string) (contentType string, body []byte, err error) {
	res, err := http.Get(uri)
	if err != nil {
		return
	}
	if res.StatusCode != 200 {
		log.Printf("Status code of %s is: %d\n", uri, res.StatusCode)
		err = errors.New("Unexpected status code")
		return
	}

	defer res.Body.Close()
	body, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}
	if res.ContentLength > maxSize {
		log.Printf("Exceeded max size for %s: %d\n", uri, res.ContentLength)
		err = errors.New("Exceeded max size")
		return
	}
	contentType = res.Header.Get("Content-Type")
	if contentType[0:5] != "image" {
		log.Printf("%s has an invalid content-type: %s\n", uri, contentType)
		err = errors.New("Invalid content-type")
		return
	}
	log.Printf("Fetch %s (%s)\n", uri, contentType)

	return
}

// Fetch image from cache if available, or from the server
func fetchImage(uri string) (contentType string, body []byte, err error) {
	key := generateKeyForCache(uri)

	contentType, body, ok := fetchImageFromCache(key)
	if ok {
		return
	}

	contentType, body, err = fetchImageFromServer(uri)
	if err == nil {
		go saveImageInCache(key, contentType, body)
	}

	return
}

// Decode the URL and compute the associated HMAC
func decodeUrl(encoded_url string) (uri string, actual string, err error) {
	chars, err := hex.DecodeString(encoded_url)
	if err != nil {
		return
	}

	h := hmac.New(sha1.New, secret)
	_, err = h.Write(chars)
	if err != nil {
		return
	}
	actual = fmt.Sprintf("%x", h.Sum(nil))

	uri = string(chars)
	return
}

// Fetch the image from the URL (or cache) and respond with it
func Img(w http.ResponseWriter, r *http.Request) {
	expected := r.URL.Query().Get(":hmac")
	encoded_url := r.URL.Query().Get(":encoded_url")
	uri, actual, err := decodeUrl(encoded_url)
	if err != nil {
		log.Printf("Invalid URL %s\n", encoded_url)
		http.Error(w, "Invalid parameters", 400)
		return
	}

	// Check the validity of the URL
	u, err := url.Parse(uri)
	if err != nil {
		log.Printf("Invalid URL %s\n", uri)
		http.Error(w, "Invalid parameters", 400)
		return
	}
	h := u.Host + "XXXXXXX" // Be sure to have enough chars to avoid out of bounds
	if h[0:3] == "10." || h[0:4] == "127." || h[0:7] == "169.254" || h[0:7] == "192.168" {
		log.Printf("Invalid IP %s\n", uri)
		http.Error(w, "Invalid parameters", 400)
		return
	}
	if expected != actual {
		log.Printf("Invalid HMAC expected=%s actual=%s\n", expected, actual)
		http.Error(w, "Invalid HMAC", 403)
		return
	}

	contentType, body, err := fetchImage(uri)
	if err != nil {
		http.NotFound(w, r)
		return
	}
	w.Header().Add("Content-Type", contentType)
	w.Write(body)
}

// Returns 200 OK if the server is running (for monitoring)
func Status(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "OK")
}

func main() {
	// Parse the command-line
	var addr string
	var secr string
	flag.StringVar(&addr, "a", "127.0.0.1:8000", "Bind to this address:port")
	flag.StringVar(&secr, "s", "252c38cdb9f638908fab5df7263d156c759d590b1251785fa612e7874ee9bbcc32a61f8d795e7593ca31f8f47396c497b215e1abde6e947d7e25772f30115a7e", "The secret for HMAC check")
	flag.StringVar(&directory, "d", "cache", "The directory for the caching files")
	flag.Parse()
	secret = []byte(secr)

	// Routing
	m := pat.New()
	m.Get("/status", http.HandlerFunc(Status))
	m.Get("/img/:hmac/:encoded_url", http.HandlerFunc(Img))
	http.Handle("/", m)

	// Start the HTTP server
	log.Printf("Listening on http://%s/\n", addr)
	err := http.ListenAndServe(addr, nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"fmt"

	"git.zxq.co/ripple/rippleapi/common"
	"github.com/gin-gonic/gin"
)

func ircGenToken(c *gin.Context) {
	ctx := getContext(c)
	if ctx.User.ID == 0 {
		addMessage(c, errorMessage{"You need to be logged in!"})
		getSession(c).Save()
		c.Redirect(302, "/")
		return
	}

	db.Exec("DELETE FROM irc_tokens WHERE userid = ?", ctx.User.ID)

	var s, m string
	for {
		s = common.RandomString(32)
		m = cmd5(s)
		fmt.Println(s, m)
		if db.QueryRow("SELECT 1 FROM irc_tokens WHERE token = ? LIMIT 1", m).
			Scan(new(int)) == sql.ErrNoRows {
			break
		}
	}

	db.Exec("INSERT INTO irc_tokens(userid, token) VALUES (?, ?)", ctx.User.ID, m)
	simple(c, getSimple("/irc"), []message{successMessage{
		fmt.Sprintf("Your new IRC token is %s. The old IRC token is not valid anymore."+
			"
Keep it safe, don't show it around, and store it now! "+
			"We won't show it to you again.", s),
	}})
}
<commit_msg>Oopps, forgot to remove this fmt.Println<commit_after>package main

import (
	"database/sql"
	"fmt"

	"git.zxq.co/ripple/rippleapi/common"
	"github.com/gin-gonic/gin"
)

func ircGenToken(c *gin.Context) {
	ctx := getContext(c)
	if ctx.User.ID == 0 {
		addMessage(c, errorMessage{"You need to be logged in!"})
		getSession(c).Save()
		c.Redirect(302, "/")
		return
	}

	db.Exec("DELETE FROM irc_tokens WHERE userid = ?", ctx.User.ID)

	var s, m string
	for {
		s = common.RandomString(32)
		m = cmd5(s)
		if db.QueryRow("SELECT 1 FROM irc_tokens WHERE token = ? LIMIT 1", m).
			Scan(new(int)) == sql.ErrNoRows {
			break
		}
	}

	db.Exec("INSERT INTO irc_tokens(userid, token) VALUES (?, ?)", ctx.User.ID, m)
	simple(c, getSimple("/irc"), []message{successMessage{
		fmt.Sprintf("Your new IRC token is %s. The old IRC token is not valid anymore."+
			"
Keep it safe, don't show it around, and store it now! "+
			"We won't show it to you again.", s),
	}})
}
<|endoftext|>


<commit_before>package etcd

import (
	"context"
	"errors"
	"reflect"
	"testing"
	"time"

	etcd "go.etcd.io/etcd/client/v2"
)

func TestNewClient(t *testing.T) {
	client, err := NewClient(
		context.Background(),
		[]string{"http://irrelevant:12345"},
		ClientOptions{
			DialTimeout:             2 * time.Second,
			DialKeepAlive:           2 * time.Second,
			HeaderTimeoutPerRequest: 2 * time.Second,
		},
	)
	if err != nil {
		t.Fatalf("unexpected error creating client: %v", err)
	}
	if client == nil {
		t.Fatal("expected new Client, got nil")
	}
}

// NewClient should fail when providing invalid or missing endpoints.
func TestOptions(t *testing.T) {
	a, err := NewClient(
		context.Background(),
		[]string{},
		ClientOptions{
			Cert:                    "",
			Key:                     "",
			CACert:                  "",
			DialTimeout:             2 * time.Second,
			DialKeepAlive:           2 * time.Second,
			HeaderTimeoutPerRequest: 2 * time.Second,
		},
	)
	if err == nil {
		t.Errorf("expected error: %v", err)
	}
	if a != nil {
		t.Fatalf("expected client to be nil on failure")
	}

	_, err = NewClient(
		context.Background(),
		[]string{"http://irrelevant:12345"},
		ClientOptions{
			Cert:                    "blank.crt",
			Key:                     "blank.key",
			CACert:                  "blank.CACert",
			DialTimeout:             2 * time.Second,
			DialKeepAlive:           2 * time.Second,
			HeaderTimeoutPerRequest: 2 * time.Second,
		},
	)
	if err == nil {
		t.Errorf("expected error: %v", err)
	}
}

// Mocks of the underlying etcd.KeysAPI interface that is called by the methods we want to test

// fakeKeysAPI implements etcd.KeysAPI, event and err are channels used to emulate
// an etcd event or error, getres will be returned when etcd.KeysAPI.Get is called.
type fakeKeysAPI struct {
	event  chan bool
	err    chan bool
	getres *getResult
}

type getResult struct {
	resp *etcd.Response
	err  error
}

// Get return the content of getres or nil, nil
func (fka *fakeKeysAPI) Get(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) {
	if fka.getres == nil {
		return nil, nil
	}
	return fka.getres.resp, fka.getres.err
}

// Set is not used in the tests
func (fka *fakeKeysAPI) Set(ctx context.Context, key, value string, opts *etcd.SetOptions) (*etcd.Response, error) {
	return nil, nil
}

// Delete is not used in the tests
func (fka *fakeKeysAPI) Delete(ctx context.Context, key string, opts *etcd.DeleteOptions) (*etcd.Response, error) {
	return nil, nil
}

// Create is not used in the tests
func (fka *fakeKeysAPI) Create(ctx context.Context, key, value string) (*etcd.Response, error) {
	return nil, nil
}

// CreateInOrder is not used in the tests
func (fka *fakeKeysAPI) CreateInOrder(ctx context.Context, dir, value string, opts *etcd.CreateInOrderOptions) (*etcd.Response, error) {
	return nil, nil
}

// Update is not used in the tests
func (fka *fakeKeysAPI) Update(ctx context.Context, key, value string) (*etcd.Response, error) {
	return nil, nil
}

// Watcher return a fakeWatcher that will forward event and error received on the channels
func (fka *fakeKeysAPI) Watcher(key string, opts *etcd.WatcherOptions) etcd.Watcher {
	return &fakeWatcher{fka.event, fka.err}
}

// fakeWatcher implements etcd.Watcher
type fakeWatcher struct {
	event chan bool
	err   chan bool
}

// Next blocks until an etcd event or error is emulated.
// When an event occurs it just return nil response and error.
// When an error occur it return a non nil error.
func (fw *fakeWatcher) Next(context.Context) (*etcd.Response, error) {
	for {
		select {
		case <-fw.event:
			return nil, nil
		case <-fw.err:
			return nil, errors.New("error from underlying etcd watcher")
		default:
		}
	}
}

// newFakeClient return a new etcd.Client built on top of the mocked interfaces
func newFakeClient(event, err chan bool, getres *getResult) Client {
	return &client{
		keysAPI: &fakeKeysAPI{event, err, getres},
		ctx:     context.Background(),
	}
}

// Register should fail when the provided service has an empty key or value
func TestRegisterClient(t *testing.T) {
	client := newFakeClient(nil, nil, nil)

	err := client.Register(Service{Key: "", Value: "value", DeleteOptions: nil})
	if want, have := ErrNoKey, err; want != have {
		t.Fatalf("want %v, have %v", want, have)
	}

	err = client.Register(Service{Key: "key", Value: "", DeleteOptions: nil})
	if want, have := ErrNoValue, err; want != have {
		t.Fatalf("want %v, have %v", want, have)
	}

	err = client.Register(Service{Key: "key", Value: "value", DeleteOptions: nil})
	if err != nil {
		t.Fatal(err)
	}
}

// Deregister should fail if the input service has an empty key
func TestDeregisterClient(t *testing.T) {
	client := newFakeClient(nil, nil, nil)

	err := client.Deregister(Service{Key: "", Value: "value", DeleteOptions: nil})
	if want, have := ErrNoKey, err; want != have {
		t.Fatalf("want %v, have %v", want, have)
	}

	err = client.Deregister(Service{Key: "key", Value: "", DeleteOptions: nil})
	if err != nil {
		t.Fatal(err)
	}
}

// WatchPrefix notify the caller by writing on the channel if an etcd event occurs
// or return in case of an underlying error
func TestWatchPrefix(t *testing.T) {
	err := make(chan bool)
	event := make(chan bool)
	watchPrefixReturned := make(chan bool, 1)
	client := newFakeClient(event, err, nil)

	ch := make(chan struct{})
	go func() {
		client.WatchPrefix("prefix", ch) // block until an etcd event or error occurs
		watchPrefixReturned <- true
	}()

	// WatchPrefix force the caller to read once from the channel before actually
	// sending notification, emulate that first read.
	<-ch

	// Emulate an etcd event
	event <- true
	if want, have := struct{}{}, <-ch; want != have {
		t.Fatalf("want %v, have %v", want, have)
	}

	// Emulate an error, WatchPrefix should return
	err <- true
	select {
	case <-watchPrefixReturned:
		break
	case <-time.After(1 * time.Second):
		t.Fatal("WatchPrefix not returning on errors")
	}
}

var errKeyAPI = errors.New("emulate error returned by KeysAPI.Get")

// table of test cases for method GetEntries
var getEntriesTestTable = []struct {
	input getResult // value returned by the underlying etcd.KeysAPI.Get
	resp  []string  // response expected in output of GetEntries
	err   error     //error expected in output of GetEntries

}{
	// test case: an error is returned by etcd.KeysAPI.Get
	{getResult{nil, errKeyAPI}, nil, errKeyAPI},
	// test case: return a single leaf node, with an empty value
	{getResult{&etcd.Response{
		Action: "get",
		Node: &etcd.Node{
			Key:           "nodekey",
			Dir:           false,
			Value:         "",
			Nodes:         nil,
			CreatedIndex:  0,
			ModifiedIndex: 0,
			Expiration:    nil,
			TTL:           0,
		},
		PrevNode: nil,
		Index:    0,
	}, nil}, []string{}, nil},
	// test case: return a single leaf node, with a value
	{getResult{&etcd.Response{
		Action: "get",
		Node: &etcd.Node{
			Key:           "nodekey",
			Dir:           false,
			Value:         "nodevalue",
			Nodes:         nil,
			CreatedIndex:  0,
			ModifiedIndex: 0,
			Expiration:    nil,
			TTL:           0,
		},
		PrevNode: nil,
		Index:    0,
	}, nil}, []string{"nodevalue"}, nil},
	// test case: return a node with two childs
	{getResult{&etcd.Response{
		Action: "get",
		Node: &etcd.Node{
			Key:   "nodekey",
			Dir:   true,
			Value: "nodevalue",
			Nodes: []*etcd.Node{
				{
					Key:           "childnode1",
					Dir:           false,
					Value:         "childvalue1",
					Nodes:         nil,
					CreatedIndex:  0,
					ModifiedIndex: 0,
					Expiration:    nil,
					TTL:           0,
				},
				{
					Key:           "childnode2",
					Dir:           false,
					Value:         "childvalue2",
					Nodes:         nil,
					CreatedIndex:  0,
					ModifiedIndex: 0,
					Expiration:    nil,
					TTL:           0,
				},
			},
			CreatedIndex:  0,
			ModifiedIndex: 0,
			Expiration:    nil,
			TTL:           0,
		},
		PrevNode: nil,
		Index:    0,
	}, nil}, []string{"childvalue1", "childvalue2"}, nil},
}

func TestGetEntries(t *testing.T) {
	for _, et := range getEntriesTestTable {
		client := newFakeClient(nil, nil, &et.input)
		resp, err := client.GetEntries("prefix")
		if want, have := et.resp, resp; !reflect.DeepEqual(want, have) {
			t.Fatalf("want %v, have %v", want, have)
		}
		if want, have := et.err, err; want != have {
			t.Fatalf("want %v, have %v", want, have)
		}
	}
}
<commit_msg>Remove empty default in select<commit_after>package etcd

import (
	"context"
	"errors"
	"reflect"
	"testing"
	"time"

	etcd "go.etcd.io/etcd/client/v2"
)

func TestNewClient(t *testing.T) {
	client, err := NewClient(
		context.Background(),
		[]string{"http://irrelevant:12345"},
		ClientOptions{
			DialTimeout:             2 * time.Second,
			DialKeepAlive:           2 * time.Second,
			HeaderTimeoutPerRequest: 2 * time.Second,
		},
	)
	if err != nil {
		t.Fatalf("unexpected error creating client: %v", err)
	}
	if client == nil {
		t.Fatal("expected new Client, got nil")
	}
}

// NewClient should fail when providing invalid or missing endpoints.
func TestOptions(t *testing.T) {
	a, err := NewClient(
		context.Background(),
		[]string{},
		ClientOptions{
			Cert:                    "",
			Key:                     "",
			CACert:                  "",
			DialTimeout:             2 * time.Second,
			DialKeepAlive:           2 * time.Second,
			HeaderTimeoutPerRequest: 2 * time.Second,
		},
	)
	if err == nil {
		t.Errorf("expected error: %v", err)
	}
	if a != nil {
		t.Fatalf("expected client to be nil on failure")
	}

	_, err = NewClient(
		context.Background(),
		[]string{"http://irrelevant:12345"},
		ClientOptions{
			Cert:                    "blank.crt",
			Key:                     "blank.key",
			CACert:                  "blank.CACert",
			DialTimeout:             2 * time.Second,
			DialKeepAlive:           2 * time.Second,
			HeaderTimeoutPerRequest: 2 * time.Second,
		},
	)
	if err == nil {
		t.Errorf("expected error: %v", err)
	}
}

// Mocks of the underlying etcd.KeysAPI interface that is called by the methods we want to test

// fakeKeysAPI implements etcd.KeysAPI, event and err are channels used to emulate
// an etcd event or error, getres will be returned when etcd.KeysAPI.Get is called.
type fakeKeysAPI struct {
	event  chan bool
	err    chan bool
	getres *getResult
}

type getResult struct {
	resp *etcd.Response
	err  error
}

// Get return the content of getres or nil, nil
func (fka *fakeKeysAPI) Get(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) {
	if fka.getres == nil {
		return nil, nil
	}
	return fka.getres.resp, fka.getres.err
}

// Set is not used in the tests
func (fka *fakeKeysAPI) Set(ctx context.Context, key, value string, opts *etcd.SetOptions) (*etcd.Response, error) {
	return nil, nil
}

// Delete is not used in the tests
func (fka *fakeKeysAPI) Delete(ctx context.Context, key string, opts *etcd.DeleteOptions) (*etcd.Response, error) {
	return nil, nil
}

// Create is not used in the tests
func (fka *fakeKeysAPI) Create(ctx context.Context, key, value string) (*etcd.Response, error) {
	return nil, nil
}

// CreateInOrder is not used in the tests
func (fka *fakeKeysAPI) CreateInOrder(ctx context.Context, dir, value string, opts *etcd.CreateInOrderOptions) (*etcd.Response, error) {
	return nil, nil
}

// Update is not used in the tests
func (fka *fakeKeysAPI) Update(ctx context.Context, key, value string) (*etcd.Response, error) {
	return nil, nil
}

// Watcher return a fakeWatcher that will forward event and error received on the channels
func (fka *fakeKeysAPI) Watcher(key string, opts *etcd.WatcherOptions) etcd.Watcher {
	return &fakeWatcher{fka.event, fka.err}
}

// fakeWatcher implements etcd.Watcher
type fakeWatcher struct {
	event chan bool
	err   chan bool
}

// Next blocks until an etcd event or error is emulated.
// When an event occurs it just return nil response and error.
// When an error occur it return a non nil error.
func (fw *fakeWatcher) Next(context.Context) (*etcd.Response, error) {
	for {
		select {
		case <-fw.event:
			return nil, nil
		case <-fw.err:
			return nil, errors.New("error from underlying etcd watcher")

		}
	}
}

// newFakeClient return a new etcd.Client built on top of the mocked interfaces
func newFakeClient(event, err chan bool, getres *getResult) Client {
	return &client{
		keysAPI: &fakeKeysAPI{event, err, getres},
		ctx:     context.Background(),
	}
}

// Register should fail when the provided service has an empty key or value
func TestRegisterClient(t *testing.T) {
	client := newFakeClient(nil, nil, nil)

	err := client.Register(Service{Key: "", Value: "value", DeleteOptions: nil})
	if want, have := ErrNoKey, err; want != have {
		t.Fatalf("want %v, have %v", want, have)
	}

	err = client.Register(Service{Key: "key", Value: "", DeleteOptions: nil})
	if want, have := ErrNoValue, err; want != have {
		t.Fatalf("want %v, have %v", want, have)
	}

	err = client.Register(Service{Key: "key", Value: "value", DeleteOptions: nil})
	if err != nil {
		t.Fatal(err)
	}
}

// Deregister should fail if the input service has an empty key
func TestDeregisterClient(t *testing.T) {
	client := newFakeClient(nil, nil, nil)

	err := client.Deregister(Service{Key: "", Value: "value", DeleteOptions: nil})
	if want, have := ErrNoKey, err; want != have {
		t.Fatalf("want %v, have %v", want, have)
	}

	err = client.Deregister(Service{Key: "key", Value: "", DeleteOptions: nil})
	if err != nil {
		t.Fatal(err)
	}
}

// WatchPrefix notify the caller by writing on the channel if an etcd event occurs
// or return in case of an underlying error
func TestWatchPrefix(t *testing.T) {
	err := make(chan bool)
	event := make(chan bool)
	watchPrefixReturned := make(chan bool, 1)
	client := newFakeClient(event, err, nil)

	ch := make(chan struct{})
	go func() {
		client.WatchPrefix("prefix", ch) // block until an etcd event or error occurs
		watchPrefixReturned <- true
	}()

	// WatchPrefix force the caller to read once from the channel before actually
	// sending notification, emulate that first read.
	<-ch

	// Emulate an etcd event
	event <- true
	if want, have := struct{}{}, <-ch; want != have {
		t.Fatalf("want %v, have %v", want, have)
	}

	// Emulate an error, WatchPrefix should return
	err <- true
	select {
	case <-watchPrefixReturned:
		break
	case <-time.After(1 * time.Second):
		t.Fatal("WatchPrefix not returning on errors")
	}
}

var errKeyAPI = errors.New("emulate error returned by KeysAPI.Get")

// table of test cases for method GetEntries
var getEntriesTestTable = []struct {
	input getResult // value returned by the underlying etcd.KeysAPI.Get
	resp  []string  // response expected in output of GetEntries
	err   error     //error expected in output of GetEntries

}{
	// test case: an error is returned by etcd.KeysAPI.Get
	{getResult{nil, errKeyAPI}, nil, errKeyAPI},
	// test case: return a single leaf node, with an empty value
	{getResult{&etcd.Response{
		Action: "get",
		Node: &etcd.Node{
			Key:           "nodekey",
			Dir:           false,
			Value:         "",
			Nodes:         nil,
			CreatedIndex:  0,
			ModifiedIndex: 0,
			Expiration:    nil,
			TTL:           0,
		},
		PrevNode: nil,
		Index:    0,
	}, nil}, []string{}, nil},
	// test case: return a single leaf node, with a value
	{getResult{&etcd.Response{
		Action: "get",
		Node: &etcd.Node{
			Key:           "nodekey",
			Dir:           false,
			Value:         "nodevalue",
			Nodes:         nil,
			CreatedIndex:  0,
			ModifiedIndex: 0,
			Expiration:    nil,
			TTL:           0,
		},
		PrevNode: nil,
		Index:    0,
	}, nil}, []string{"nodevalue"}, nil},
	// test case: return a node with two childs
	{getResult{&etcd.Response{
		Action: "get",
		Node: &etcd.Node{
			Key:   "nodekey",
			Dir:   true,
			Value: "nodevalue",
			Nodes: []*etcd.Node{
				{
					Key:           "childnode1",
					Dir:           false,
					Value:         "childvalue1",
					Nodes:         nil,
					CreatedIndex:  0,
					ModifiedIndex: 0,
					Expiration:    nil,
					TTL:           0,
				},
				{
					Key:           "childnode2",
					Dir:           false,
					Value:         "childvalue2",
					Nodes:         nil,
					CreatedIndex:  0,
					ModifiedIndex: 0,
					Expiration:    nil,
					TTL:           0,
				},
			},
			CreatedIndex:  0,
			ModifiedIndex: 0,
			Expiration:    nil,
			TTL:           0,
		},
		PrevNode: nil,
		Index:    0,
	}, nil}, []string{"childvalue1", "childvalue2"}, nil},
}

func TestGetEntries(t *testing.T) {
	for _, et := range getEntriesTestTable {
		client := newFakeClient(nil, nil, &et.input)
		resp, err := client.GetEntries("prefix")
		if want, have := et.resp, resp; !reflect.DeepEqual(want, have) {
			t.Fatalf("want %v, have %v", want, have)
		}
		if want, have := et.err, err; want != have {
			t.Fatalf("want %v, have %v", want, have)
		}
	}
}
<|endoftext|>


<commit_before>// Package kinesis provide GOlang API for http://aws.amazon.com/kinesis/
package kinesis

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
)

const (
	ActionKey     = "Action"
	RegionEnvName = "AWS_REGION_NAME"

	// Regions
	USEast1      = "us-east-1"
	USWest2      = "us-west-2"
	EUWest1      = "eu-west-1"
	EUCentral1   = "eu-central-1"
	APSouthEast1 = "ap-southeast-1"
	APSouthEast2 = "ap-southeast-2"
	APNortheast1 = "ap-northeast-1"

	kinesisURL = "https://kinesis.%s.amazonaws.com"
)

// NewRegionFromEnv creates a region from the an expected environment variable
func NewRegionFromEnv() string {
	return os.Getenv(RegionEnvName)
}

// Structure for kinesis client
type Kinesis struct {
	client   *Client
	endpoint string
	region   string
	version  string
}

// KinesisClient interface implemented by Kinesis
type KinesisClient interface {
	CreateStream(StreamName string, ShardCount int) error
	DeleteStream(StreamName string) error
	DescribeStream(args *RequestArgs) (resp *DescribeStreamResp, err error)
	GetRecords(args *RequestArgs) (resp *GetRecordsResp, err error)
	GetShardIterator(args *RequestArgs) (resp *GetShardIteratorResp, err error)
	ListStreams(args *RequestArgs) (resp *ListStreamsResp, err error)
	MergeShards(args *RequestArgs) error
	PutRecord(args *RequestArgs) (resp *PutRecordResp, err error)
	PutRecords(args *RequestArgs) (resp *PutRecordsResp, err error)
	SplitShard(args *RequestArgs) error
}

// New returns an initialized AWS Kinesis client using the canonical live “production” endpoint
// for AWS Kinesis, i.e. https://kinesis.{region}.amazonaws.com
func New(auth Auth, region string) *Kinesis {
	endpoint := fmt.Sprintf(kinesisURL, region)
	return NewWithEndpoint(auth, region, endpoint)
}

// NewWithClient returns an initialized AWS Kinesis client using the canonical live “production” endpoint
// for AWS Kinesis, i.e. https://kinesis.{region}.amazonaws.com but with the ability to create a custom client
// with specific configurations like a timeout
func NewWithClient(region string, client *Client) *Kinesis {
	endpoint := fmt.Sprintf(kinesisURL, region)
	return &Kinesis{client: client, version: "20131202", region: region, endpoint: endpoint}
}

// NewWithEndpoint returns an initialized AWS Kinesis client using the specified endpoint.
// This is generally useful for testing, so a local Kinesis server can be used.
func NewWithEndpoint(auth Auth, region string, endpoint string) *Kinesis {
	// TODO: remove trailing slash on endpoint if there is one? does it matter?
	// TODO: validate endpoint somehow?
	return &Kinesis{client: NewClient(auth), version: "20131202", region: region, endpoint: endpoint}
}

// Create params object for request
func makeParams(action string) map[string]string {
	params := make(map[string]string)
	params[ActionKey] = action
	return params
}

// RequestArgs store params for request
type RequestArgs struct {
	params  map[string]interface{}
	Records []Record
}

// NewFilter creates a new Filter.
func NewArgs() *RequestArgs {
	return &RequestArgs{
		params: make(map[string]interface{}),
	}
}

// Add appends a filtering parameter with the given name and value(s).
func (f *RequestArgs) Add(name string, value interface{}) {
	f.params[name] = value
}

func (f *RequestArgs) AddData(value []byte) {
	f.params["Data"] = value
}

// Error represent error from Kinesis API
type Error struct {
	// HTTP status code (200, 403, ...)
	StatusCode int
	// error code ("UnsupportedOperation", ...)
	Code string
	// The human-oriented error message
	Message   string
	RequestId string
}

// Return error message from error object
func (err *Error) Error() string {
	if err.Code == "" {
		return err.Message
	}
	return fmt.Sprintf("%s (%s)", err.Message, err.Code)
}

type jsonErrors struct {
	Code    string `json:"__type"`
	Message string
}

func buildError(r *http.Response) error {
	// Reading the body into a []byte because we might need to put it into an error
	// message after having the JSON decoding fail to produce a message.
	body, ioerr := ioutil.ReadAll(r.Body)
	if ioerr != nil {
		return fmt.Errorf("Could not read response body: %s", ioerr)
	}

	errors := jsonErrors{}
	json.NewDecoder(bytes.NewReader(body)).Decode(&errors)

	var err Error
	err.Message = errors.Message
	err.Code = errors.Code
	err.StatusCode = r.StatusCode
	if err.Message == "" {
		err.Message = fmt.Sprintf("%s: %s", r.Status, body)
	}
	return &err
}

// Query by AWS API
func (kinesis *Kinesis) query(params map[string]string, data interface{}, resp interface{}) error {
	jsonData, err := json.Marshal(data)
	if err != nil {
		return err
	}

	// request
	request, err := http.NewRequest(
		"POST",
		kinesis.endpoint,
		bytes.NewReader(jsonData),
	)

	if err != nil {
		return err
	}

	// headers
	request.Header.Set("Content-Type", "application/x-amz-json-1.1")
	request.Header.Set("X-Amz-Target", fmt.Sprintf("Kinesis_%s.%s", kinesis.version, params[ActionKey]))
	request.Header.Set("User-Agent", "Golang Kinesis")

	// response
	response, err := kinesis.client.Do(request)
	if err != nil {
		return err
	}
	defer response.Body.Close()

	if response.StatusCode != 200 {
		return buildError(response)
	}

	if resp == nil {
		return nil
	}

	return json.NewDecoder(response.Body).Decode(resp)
}

// CreateStream adds a new Amazon Kinesis stream to your AWS account
// StreamName is a name of stream, ShardCount is number of shards
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_CreateStream.html
func (kinesis *Kinesis) CreateStream(StreamName string, ShardCount int) error {
	params := makeParams("CreateStream")
	requestParams := struct {
		StreamName string
		ShardCount int
	}{
		StreamName,
		ShardCount,
	}
	err := kinesis.query(params, requestParams, nil)
	if err != nil {
		return err
	}
	return nil
}

// DeleteStream deletes a stream and all of its shards and data from your AWS account
// StreamName is a name of stream
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_DeleteStream.html
func (kinesis *Kinesis) DeleteStream(StreamName string) error {
	params := makeParams("DeleteStream")
	requestParams := struct {
		StreamName string
	}{
		StreamName,
	}
	err := kinesis.query(params, requestParams, nil)
	if err != nil {
		return err
	}
	return nil
}

// MergeShards merges two adjacent shards in a stream and combines them into a single shard to reduce the stream's capacity to ingest and transport data
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_MergeShards.html
func (kinesis *Kinesis) MergeShards(args *RequestArgs) error {
	params := makeParams("MergeShards")
	err := kinesis.query(params, args.params, nil)
	if err != nil {
		return err
	}
	return nil
}

// SplitShard splits a shard into two new shards in the stream, to increase the stream's capacity to ingest and transport data
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_SplitShard.html
func (kinesis *Kinesis) SplitShard(args *RequestArgs) error {
	params := makeParams("SplitShard")
	err := kinesis.query(params, args.params, nil)
	if err != nil {
		return err
	}
	return nil
}

// ListStreamsResp stores the information that provides by ListStreams API call
type ListStreamsResp struct {
	HasMoreStreams bool
	StreamNames    []string
}

// ListStreams returns an array of the names of all the streams that are associated with the AWS account making the ListStreams request
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_ListStreams.html
func (kinesis *Kinesis) ListStreams(args *RequestArgs) (resp *ListStreamsResp, err error) {
	params := makeParams("ListStreams")
	resp = &ListStreamsResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// DescribeStreamShards stores the information about list of shards inside DescribeStreamResp
type DescribeStreamShards struct {
	AdjacentParentShardId string
	HashKeyRange          struct {
		EndingHashKey   string
		StartingHashKey string
	}
	ParentShardId       string
	SequenceNumberRange struct {
		EndingSequenceNumber   string
		StartingSequenceNumber string
	}
	ShardId string
}

// DescribeStreamResp stores the information that provides by DescribeStream API call
type DescribeStreamResp struct {
	StreamDescription struct {
		HasMoreShards bool
		Shards        []DescribeStreamShards
		StreamARN     string
		StreamName    string
		StreamStatus  string
	}
}

// DescribeStream returns the following information about the stream: the current status of the stream,
// the stream Amazon Resource Name (ARN), and an array of shard objects that comprise the stream.
// For each shard object there is information about the hash key and sequence number ranges that
// the shard spans, and the IDs of any earlier shards that played in a role in a MergeShards or
// SplitShard operation that created the shard
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_DescribeStream.html
func (kinesis *Kinesis) DescribeStream(args *RequestArgs) (resp *DescribeStreamResp, err error) {
	params := makeParams("DescribeStream")
	resp = &DescribeStreamResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// GetShardIteratorResp stores the information that provides by GetShardIterator API call
type GetShardIteratorResp struct {
	ShardIterator string
}

// GetShardIterator returns a shard iterator
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_GetShardIterator.html
func (kinesis *Kinesis) GetShardIterator(args *RequestArgs) (resp *GetShardIteratorResp, err error) {
	params := makeParams("GetShardIterator")
	resp = &GetShardIteratorResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// GetNextRecordsRecords stores the information that provides by GetNextRecordsResp
type GetRecordsRecords struct {
	ApproximateArrivalTimestamp int64
	Data                        []byte
	PartitionKey                string
	SequenceNumber              string
}

func (r GetRecordsRecords) GetData() []byte {
	return r.Data
}

// GetNextRecordsResp stores the information that provides by GetNextRecords API call
type GetRecordsResp struct {
	MillisBehindLatest int64
	NextShardIterator  string
	Records            []GetRecordsRecords
}

// GetRecords returns one or more data records from a shard
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_GetRecords.html
func (kinesis *Kinesis) GetRecords(args *RequestArgs) (resp *GetRecordsResp, err error) {
	params := makeParams("GetRecords")
	resp = &GetRecordsResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// PutRecordResp stores the information that provides by PutRecord API call
type PutRecordResp struct {
	SequenceNumber string
	ShardId        string
}

// PutRecord puts a data record into an Amazon Kinesis stream from a producer.
// args must contain a single record added with AddRecord.
// More info: http://docs.aws.amazon.com/kinesis/latest/APIReference/API_PutRecord.html
func (kinesis *Kinesis) PutRecord(args *RequestArgs) (resp *PutRecordResp, err error) {
	params := makeParams("PutRecord")

	if _, ok := args.params["Data"]; !ok && len(args.Records) == 0 {
		return nil, errors.New("PutRecord requires its args param to contain a record added with either AddRecord or AddData.")
	} else if ok && len(args.Records) > 0 {
		return nil, errors.New("PutRecord requires its args param to contain a record added with either AddRecord or AddData but not both.")
	} else if len(args.Records) > 1 {
		return nil, errors.New("PutRecord does not support more than one record.")
	}

	if len(args.Records) > 0 {
		args.AddData(args.Records[0].Data)
		args.Add("PartitionKey", args.Records[0].PartitionKey)
	}

	resp = &PutRecordResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// PutRecords puts multiple data records into an Amazon Kinesis stream from a producer
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_PutRecords.html
func (kinesis *Kinesis) PutRecords(args *RequestArgs) (resp *PutRecordsResp, err error) {
	params := makeParams("PutRecords")
	resp = &PutRecordsResp{}
	args.Add("Records", args.Records)
	err = kinesis.query(params, args.params, resp)

	if err != nil {
		return nil, err
	}
	return
}

// PutRecordsResp stores the information that provides by PutRecord API call
type PutRecordsResp struct {
	FailedRecordCount int
	Records           []PutRecordsRespRecord
}

// RecordResp stores individual Record information provided by PutRecords API call
type PutRecordsRespRecord struct {
	ErrorCode      string
	ErrorMessage   string
	SequenceNumber string
	ShardId        string
}

// Add data and partition for sending multiple Records to Kinesis in one API call
func (f *RequestArgs) AddRecord(value []byte, partitionKey string) {
	r := Record{
		Data:         value,
		PartitionKey: partitionKey,
	}
	f.Records = append(f.Records, r)
}

// Record stores the Data and PartitionKey for PutRecord or PutRecords calls to Kinesis API
type Record struct {
	Data         []byte
	PartitionKey string
}
<commit_msg>[BUG] GetRecordsRecords: ApproximateArrivalTimestamp is float, not int<commit_after>// Package kinesis provide GOlang API for http://aws.amazon.com/kinesis/
package kinesis

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
)

const (
	ActionKey     = "Action"
	RegionEnvName = "AWS_REGION_NAME"

	// Regions
	USEast1      = "us-east-1"
	USWest2      = "us-west-2"
	EUWest1      = "eu-west-1"
	EUCentral1   = "eu-central-1"
	APSouthEast1 = "ap-southeast-1"
	APSouthEast2 = "ap-southeast-2"
	APNortheast1 = "ap-northeast-1"

	kinesisURL = "https://kinesis.%s.amazonaws.com"
)

// NewRegionFromEnv creates a region from the an expected environment variable
func NewRegionFromEnv() string {
	return os.Getenv(RegionEnvName)
}

// Structure for kinesis client
type Kinesis struct {
	client   *Client
	endpoint string
	region   string
	version  string
}

// KinesisClient interface implemented by Kinesis
type KinesisClient interface {
	CreateStream(StreamName string, ShardCount int) error
	DeleteStream(StreamName string) error
	DescribeStream(args *RequestArgs) (resp *DescribeStreamResp, err error)
	GetRecords(args *RequestArgs) (resp *GetRecordsResp, err error)
	GetShardIterator(args *RequestArgs) (resp *GetShardIteratorResp, err error)
	ListStreams(args *RequestArgs) (resp *ListStreamsResp, err error)
	MergeShards(args *RequestArgs) error
	PutRecord(args *RequestArgs) (resp *PutRecordResp, err error)
	PutRecords(args *RequestArgs) (resp *PutRecordsResp, err error)
	SplitShard(args *RequestArgs) error
}

// New returns an initialized AWS Kinesis client using the canonical live “production” endpoint
// for AWS Kinesis, i.e. https://kinesis.{region}.amazonaws.com
func New(auth Auth, region string) *Kinesis {
	endpoint := fmt.Sprintf(kinesisURL, region)
	return NewWithEndpoint(auth, region, endpoint)
}

// NewWithClient returns an initialized AWS Kinesis client using the canonical live “production” endpoint
// for AWS Kinesis, i.e. https://kinesis.{region}.amazonaws.com but with the ability to create a custom client
// with specific configurations like a timeout
func NewWithClient(region string, client *Client) *Kinesis {
	endpoint := fmt.Sprintf(kinesisURL, region)
	return &Kinesis{client: client, version: "20131202", region: region, endpoint: endpoint}
}

// NewWithEndpoint returns an initialized AWS Kinesis client using the specified endpoint.
// This is generally useful for testing, so a local Kinesis server can be used.
func NewWithEndpoint(auth Auth, region string, endpoint string) *Kinesis {
	// TODO: remove trailing slash on endpoint if there is one? does it matter?
	// TODO: validate endpoint somehow?
	return &Kinesis{client: NewClient(auth), version: "20131202", region: region, endpoint: endpoint}
}

// Create params object for request
func makeParams(action string) map[string]string {
	params := make(map[string]string)
	params[ActionKey] = action
	return params
}

// RequestArgs store params for request
type RequestArgs struct {
	params  map[string]interface{}
	Records []Record
}

// NewFilter creates a new Filter.
func NewArgs() *RequestArgs {
	return &RequestArgs{
		params: make(map[string]interface{}),
	}
}

// Add appends a filtering parameter with the given name and value(s).
func (f *RequestArgs) Add(name string, value interface{}) {
	f.params[name] = value
}

func (f *RequestArgs) AddData(value []byte) {
	f.params["Data"] = value
}

// Error represent error from Kinesis API
type Error struct {
	// HTTP status code (200, 403, ...)
	StatusCode int
	// error code ("UnsupportedOperation", ...)
	Code string
	// The human-oriented error message
	Message   string
	RequestId string
}

// Return error message from error object
func (err *Error) Error() string {
	if err.Code == "" {
		return err.Message
	}
	return fmt.Sprintf("%s (%s)", err.Message, err.Code)
}

type jsonErrors struct {
	Code    string `json:"__type"`
	Message string
}

func buildError(r *http.Response) error {
	// Reading the body into a []byte because we might need to put it into an error
	// message after having the JSON decoding fail to produce a message.
	body, ioerr := ioutil.ReadAll(r.Body)
	if ioerr != nil {
		return fmt.Errorf("Could not read response body: %s", ioerr)
	}

	errors := jsonErrors{}
	json.NewDecoder(bytes.NewReader(body)).Decode(&errors)

	var err Error
	err.Message = errors.Message
	err.Code = errors.Code
	err.StatusCode = r.StatusCode
	if err.Message == "" {
		err.Message = fmt.Sprintf("%s: %s", r.Status, body)
	}
	return &err
}

// Query by AWS API
func (kinesis *Kinesis) query(params map[string]string, data interface{}, resp interface{}) error {
	jsonData, err := json.Marshal(data)
	if err != nil {
		return err
	}

	// request
	request, err := http.NewRequest(
		"POST",
		kinesis.endpoint,
		bytes.NewReader(jsonData),
	)

	if err != nil {
		return err
	}

	// headers
	request.Header.Set("Content-Type", "application/x-amz-json-1.1")
	request.Header.Set("X-Amz-Target", fmt.Sprintf("Kinesis_%s.%s", kinesis.version, params[ActionKey]))
	request.Header.Set("User-Agent", "Golang Kinesis")

	// response
	response, err := kinesis.client.Do(request)
	if err != nil {
		return err
	}
	defer response.Body.Close()

	if response.StatusCode != 200 {
		return buildError(response)
	}

	if resp == nil {
		return nil
	}

	return json.NewDecoder(response.Body).Decode(resp)
}

// CreateStream adds a new Amazon Kinesis stream to your AWS account
// StreamName is a name of stream, ShardCount is number of shards
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_CreateStream.html
func (kinesis *Kinesis) CreateStream(StreamName string, ShardCount int) error {
	params := makeParams("CreateStream")
	requestParams := struct {
		StreamName string
		ShardCount int
	}{
		StreamName,
		ShardCount,
	}
	err := kinesis.query(params, requestParams, nil)
	if err != nil {
		return err
	}
	return nil
}

// DeleteStream deletes a stream and all of its shards and data from your AWS account
// StreamName is a name of stream
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_DeleteStream.html
func (kinesis *Kinesis) DeleteStream(StreamName string) error {
	params := makeParams("DeleteStream")
	requestParams := struct {
		StreamName string
	}{
		StreamName,
	}
	err := kinesis.query(params, requestParams, nil)
	if err != nil {
		return err
	}
	return nil
}

// MergeShards merges two adjacent shards in a stream and combines them into a single shard to reduce the stream's capacity to ingest and transport data
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_MergeShards.html
func (kinesis *Kinesis) MergeShards(args *RequestArgs) error {
	params := makeParams("MergeShards")
	err := kinesis.query(params, args.params, nil)
	if err != nil {
		return err
	}
	return nil
}

// SplitShard splits a shard into two new shards in the stream, to increase the stream's capacity to ingest and transport data
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_SplitShard.html
func (kinesis *Kinesis) SplitShard(args *RequestArgs) error {
	params := makeParams("SplitShard")
	err := kinesis.query(params, args.params, nil)
	if err != nil {
		return err
	}
	return nil
}

// ListStreamsResp stores the information that provides by ListStreams API call
type ListStreamsResp struct {
	HasMoreStreams bool
	StreamNames    []string
}

// ListStreams returns an array of the names of all the streams that are associated with the AWS account making the ListStreams request
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_ListStreams.html
func (kinesis *Kinesis) ListStreams(args *RequestArgs) (resp *ListStreamsResp, err error) {
	params := makeParams("ListStreams")
	resp = &ListStreamsResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// DescribeStreamShards stores the information about list of shards inside DescribeStreamResp
type DescribeStreamShards struct {
	AdjacentParentShardId string
	HashKeyRange          struct {
		EndingHashKey   string
		StartingHashKey string
	}
	ParentShardId       string
	SequenceNumberRange struct {
		EndingSequenceNumber   string
		StartingSequenceNumber string
	}
	ShardId string
}

// DescribeStreamResp stores the information that provides by DescribeStream API call
type DescribeStreamResp struct {
	StreamDescription struct {
		HasMoreShards bool
		Shards        []DescribeStreamShards
		StreamARN     string
		StreamName    string
		StreamStatus  string
	}
}

// DescribeStream returns the following information about the stream: the current status of the stream,
// the stream Amazon Resource Name (ARN), and an array of shard objects that comprise the stream.
// For each shard object there is information about the hash key and sequence number ranges that
// the shard spans, and the IDs of any earlier shards that played in a role in a MergeShards or
// SplitShard operation that created the shard
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_DescribeStream.html
func (kinesis *Kinesis) DescribeStream(args *RequestArgs) (resp *DescribeStreamResp, err error) {
	params := makeParams("DescribeStream")
	resp = &DescribeStreamResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// GetShardIteratorResp stores the information that provides by GetShardIterator API call
type GetShardIteratorResp struct {
	ShardIterator string
}

// GetShardIterator returns a shard iterator
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_GetShardIterator.html
func (kinesis *Kinesis) GetShardIterator(args *RequestArgs) (resp *GetShardIteratorResp, err error) {
	params := makeParams("GetShardIterator")
	resp = &GetShardIteratorResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// GetNextRecordsRecords stores the information that provides by GetNextRecordsResp
type GetRecordsRecords struct {
	ApproximateArrivalTimestamp float64
	Data                        []byte
	PartitionKey                string
	SequenceNumber              string
}

func (r GetRecordsRecords) GetData() []byte {
	return r.Data
}

// GetNextRecordsResp stores the information that provides by GetNextRecords API call
type GetRecordsResp struct {
	MillisBehindLatest int64
	NextShardIterator  string
	Records            []GetRecordsRecords
}

// GetRecords returns one or more data records from a shard
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_GetRecords.html
func (kinesis *Kinesis) GetRecords(args *RequestArgs) (resp *GetRecordsResp, err error) {
	params := makeParams("GetRecords")
	resp = &GetRecordsResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// PutRecordResp stores the information that provides by PutRecord API call
type PutRecordResp struct {
	SequenceNumber string
	ShardId        string
}

// PutRecord puts a data record into an Amazon Kinesis stream from a producer.
// args must contain a single record added with AddRecord.
// More info: http://docs.aws.amazon.com/kinesis/latest/APIReference/API_PutRecord.html
func (kinesis *Kinesis) PutRecord(args *RequestArgs) (resp *PutRecordResp, err error) {
	params := makeParams("PutRecord")

	if _, ok := args.params["Data"]; !ok && len(args.Records) == 0 {
		return nil, errors.New("PutRecord requires its args param to contain a record added with either AddRecord or AddData.")
	} else if ok && len(args.Records) > 0 {
		return nil, errors.New("PutRecord requires its args param to contain a record added with either AddRecord or AddData but not both.")
	} else if len(args.Records) > 1 {
		return nil, errors.New("PutRecord does not support more than one record.")
	}

	if len(args.Records) > 0 {
		args.AddData(args.Records[0].Data)
		args.Add("PartitionKey", args.Records[0].PartitionKey)
	}

	resp = &PutRecordResp{}
	err = kinesis.query(params, args.params, resp)
	if err != nil {
		return nil, err
	}
	return
}

// PutRecords puts multiple data records into an Amazon Kinesis stream from a producer
// more info http://docs.aws.amazon.com/kinesis/latest/APIReference/API_PutRecords.html
func (kinesis *Kinesis) PutRecords(args *RequestArgs) (resp *PutRecordsResp, err error) {
	params := makeParams("PutRecords")
	resp = &PutRecordsResp{}
	args.Add("Records", args.Records)
	err = kinesis.query(params, args.params, resp)

	if err != nil {
		return nil, err
	}
	return
}

// PutRecordsResp stores the information that provides by PutRecord API call
type PutRecordsResp struct {
	FailedRecordCount int
	Records           []PutRecordsRespRecord
}

// RecordResp stores individual Record information provided by PutRecords API call
type PutRecordsRespRecord struct {
	ErrorCode      string
	ErrorMessage   string
	SequenceNumber string
	ShardId        string
}

// Add data and partition for sending multiple Records to Kinesis in one API call
func (f *RequestArgs) AddRecord(value []byte, partitionKey string) {
	r := Record{
		Data:         value,
		PartitionKey: partitionKey,
	}
	f.Records = append(f.Records, r)
}

// Record stores the Data and PartitionKey for PutRecord or PutRecords calls to Kinesis API
type Record struct {
	Data         []byte
	PartitionKey string
}
<|endoftext|>


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

// Package gerrit contains code to interact with Gerrit servers.
package gerrit

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

// Client is a Gerrit client.
type Client struct {
	url  string // URL prefix, e.g. "https://go-review.googlesource.com/a" (without trailing slash)
	auth Auth

	// HTTPClient optionally specifies an HTTP client to use
	// instead of http.DefaultClient.
	HTTPClient *http.Client
}

// NewClient returns a new Gerrit client with the given URL prefix
// and authentication mode.
// The url should be just the scheme and hostname.
// If auth is nil, a default is used, or requests are made unauthenticated.
func NewClient(url string, auth Auth) *Client {
	if auth == nil {
		// TODO(bradfitz): use GitCookies auth, once that exists
		auth = NoAuth
	}
	return &Client{
		url:  strings.TrimSuffix(url, "/"),
		auth: auth,
	}
}

func (c *Client) httpClient() *http.Client {
	if c.HTTPClient != nil {
		return c.HTTPClient
	}
	return http.DefaultClient
}

func (c *Client) do(dst interface{}, method, path string, arg url.Values, body interface{}) error {
	var bodyr io.Reader
	var contentType string
	if body != nil {
		v, err := json.MarshalIndent(body, "", "  ")
		if err != nil {
			return err
		}
		bodyr = bytes.NewReader(v)
		contentType = "application/json"
	}
	// slashA is either "/a" (for authenticated requests) or "" for unauthenticated.
	// See https://gerrit-review.googlesource.com/Documentation/rest-api.html#authentication
	slashA := "/a"
	if _, ok := c.auth.(noAuth); ok {
		slashA = ""
	}
	var err error
	u := c.url + slashA + path
	if arg != nil {
		u += "?" + arg.Encode()
	}
	req, err := http.NewRequest(method, u, bodyr)
	if err != nil {
		return err
	}
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}
	c.auth.setAuth(c, req)
	res, err := c.httpClient().Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		body, _ := ioutil.ReadAll(io.LimitReader(res.Body, 4<<10))
		return fmt.Errorf("HTTP status %s; %s", res.Status, body)
	}

	// The JSON response begins with an XSRF-defeating header
	// like ")]}\n". Read that and skip it.
	br := bufio.NewReader(res.Body)
	if _, err := br.ReadSlice('\n'); err != nil {
		return err
	}
	return json.NewDecoder(br).Decode(dst)
}

// ChangeInfo is a Gerrit data structure.
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-info
type ChangeInfo struct {
	// ID is the ID of the change in the format
	// "'<project>~<branch>~<Change-Id>'", where 'project',
	// 'branch' and 'Change-Id' are URL encoded. For 'branch' the
	// refs/heads/ prefix is omitted.
	ID           string `json:"id"`
	ChangeNumber int    `json:"_number"`

	Project string `json:"project"`

	// Branch is the name of the target branch.
	// The refs/heads/ prefix is omitted.
	Branch string `json:"branch"`

	ChangeID string `json:"change_id"`

	Subject string `json:"subject"`

	// Status is the status of the change (NEW, SUBMITTED, MERGED,
	// ABANDONED, DRAFT).
	Status string `json:"status"`

	Created  TimeStamp `json:"created"`
	Updated  TimeStamp `json:"updated"`
	Mergable bool      `json:"mergable"`

	// CurrentRevision is the commit ID of the current patch set
	// of this change.  This is only set if the current revision
	// is requested or if all revisions are requested (fields
	// "CURRENT_REVISION" or "ALL_REVISIONS").
	CurrentRevision string `json:"current_revision"`

	// Revisions maps a commit ID of the patch set to a
	// RevisionInfo entity.
	//
	// Only set if the current revision is requested (in which
	// case it will only contain a key for the current revision)
	// or if all revisions are requested.
	Revisions map[string]RevisionInfo `json:"revisions"`

	// Owner is the author of the change.
	// The details are only filled in if field "DETAILED_ACCOUNTS" is requested.
	Owner *AccountInfo `json:"owner"`

	// Messages are included if field "MESSAGES" is requested.
	Messages []ChangeMessageInfo `json:"messages"`

	Labels map[string]LabelInfo `json:"labels"`

	// TODO: more as needed

	// MoreChanges is set on the last change from QueryChanges if
	// the result set is truncated by an 'n' parameter.
	MoreChanges bool `json:"_more_changes"`
}

type AccountInfo struct {
	NumericID int64  `json:"_account_id"`
	Name      string `json:"name,omitempty"`
	Email     string `json:"email,omitempty"`
	Username  string `json:"username,omitempty"`
}

func (ai *AccountInfo) Equal(v *AccountInfo) bool {
	if ai == nil || v == nil {
		return false
	}
	return ai.NumericID == v.NumericID
}

type ChangeMessageInfo struct {
	ID             string       `json:"id"`
	Author         *AccountInfo `json:"author"`
	Time           TimeStamp    `json:"date"`
	Message        string       `json:"message"`
	RevisionNumber int          `json:"_revision_number"`
}

// The LabelInfo entity contains information about a label on a
// change, always corresponding to the current patch set.
//
// There are two options that control the contents of LabelInfo:
// LABELS and DETAILED_LABELS.
//
// For a quick summary of the state of labels, use LABELS.
//
// For detailed information about labels, including exact numeric
// votes for all users and the allowed range of votes for the current
// user, use DETAILED_LABELS.
type LabelInfo struct {
	// Optional means the label may be set, but it’s neither
	// necessary for submission nor does it block submission if
	// set.
	Optional bool `json:"optional"`

	// Fields set by LABELS field option:

	All []ApprovalInfo `json:"all"`
}

type ApprovalInfo struct {
	AccountInfo
	Value int       `json:"value"`
	Date  TimeStamp `json:"date"`
}

// The RevisionInfo entity contains information about a patch set. Not
// all fields are returned by default. Additional fields can be
// obtained by adding o parameters as described at:
// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
type RevisionInfo struct {
	Draft          bool                  `json:"draft"`
	PatchSetNumber int                   `json:"_number"`
	Created        TimeStamp             `json:"created"`
	Uploader       *AccountInfo          `json:"uploader"`
	Ref            string                `json:"ref"`
	Fetch          map[string]*FetchInfo `json:"fetch"`
	Commit         *CommitInfo           `json:"commit"`
	Files          map[string]*FileInfo  `json:"files"`
	// TODO: more
}

type CommitInfo struct {
	Author    GitPersonInfo `json:"author"`
	Committer GitPersonInfo `json:"committer"`
	CommitID  string        `json:"commit"`
	Subject   string        `json:"subject"`
	Message   string        `json:"message"`
}

type GitPersonInfo struct {
	Name     string    `json:"name"`
	Email    string    `json:"Email"`
	Date     TimeStamp `json:"date"`
	TZOffset int       `json:"tz"`
}

type FileInfo struct {
	Status        string `json:"status"`
	Binary        bool   `json:"binary"`
	OldPath       string `json:"old_path"`
	LinesInserted int    `json:"lines_inserted"`
	LinesDeleted  int    `json:"lines_deleted"`
}

type FetchInfo struct {
	URL      string            `json:"url"`
	Ref      string            `json:"ref"`
	Commands map[string]string `json:"commands"`
}

// QueryChangesOpt are options for QueryChanges.
type QueryChangesOpt struct {
	// N is the number of results to return.
	// If 0, the 'n' parameter is not sent to Gerrit.
	N int

	// Fields are optional fields to also return.
	// Example strings include "ALL_REVISIONS", "LABELS", "MESSAGES".
	// For a complete list, see:
	// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-info
	Fields []string
}

func condInt(n int) []string {
	if n != 0 {
		return []string{strconv.Itoa(n)}
	}
	return nil
}

// QueryChanges queries changes. The q parameter is a Gerrit search query.
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
// For the query syntax, see https://gerrit-review.googlesource.com/Documentation/user-search.html#_search_operators
func (c *Client) QueryChanges(q string, opts ...QueryChangesOpt) ([]*ChangeInfo, error) {
	var opt QueryChangesOpt
	switch len(opts) {
	case 0:
	case 1:
		opt = opts[0]
	default:
		return nil, errors.New("only 1 option struct supported")
	}
	var changes []*ChangeInfo
	err := c.do(&changes, "GET", "/changes/", url.Values{
		"q": {q},
		"n": condInt(opt.N),
		"o": opt.Fields,
	}, nil)
	return changes, err
}

// GetChangeDetail retrieves a change with labels, detailed labels, detailed
// accounts, and messages.
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-change-detail
func (c *Client) GetChangeDetail(changeID string) (*ChangeInfo, error) {
	var change ChangeInfo
	err := c.do(&change, "GET", "/changes/"+changeID+"/detail", nil, nil)
	if err != nil {
		return nil, err
	}
	return &change, nil
}

type ReviewInput struct {
	Message string         `json:"message,omitempty"`
	Labels  map[string]int `json:"labels,omitempty"`
}

type reviewInfo struct {
	Labels map[string]int `json:"labels,omitempty"`
}

// SetReview leaves a message on a change and/or modifies labels.
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#set-review
// The changeID is https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-id
// The revision is https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#revision-id
func (c *Client) SetReview(changeID, revision string, review ReviewInput) error {
	var res reviewInfo
	return c.do(&res, "POST", fmt.Sprintf("/changes/%s/revisions/%s/review", changeID, revision),
		nil, review)
}

// GetAccountInfo gets the specified account's information from Gerrit.
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-account
// The accountID is https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#account-id
//
// Note that getting "self" is a good way to validate host access, since it only requires peeker
// access to the host, not to any particular repository.
func (c *Client) GetAccountInfo(accountID string) (AccountInfo, error) {
	var res AccountInfo
	err := c.do(&res, "GET", fmt.Sprintf("/accounts/%s", accountID), nil, nil)
	return res, err
}

type TimeStamp time.Time

// Gerrit's timestamp layout is like time.RFC3339Nano, but with a space instead of the "T",
// and without a timezone (it's always in UTC).
const timeStampLayout = "2006-01-02 15:04:05.999999999"

func (ts *TimeStamp) UnmarshalJSON(p []byte) error {
	if len(p) < 2 {
		return errors.New("Timestamp too short")
	}
	if p[0] != '"' || p[len(p)-1] != '"' {
		return errors.New("not double-quoted")
	}
	s := strings.Trim(string(p), "\"")
	t, err := time.Parse(timeStampLayout, s)
	if err != nil {
		return err
	}
	*ts = TimeStamp(t)
	return nil
}

func (ts TimeStamp) Time() time.Time { return time.Time(ts) }
<commit_msg>gerrit: add parents field to CommitInfo<commit_after>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package gerrit contains code to interact with Gerrit servers.
package gerrit

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

// Client is a Gerrit client.
type Client struct {
	url  string // URL prefix, e.g. "https://go-review.googlesource.com/a" (without trailing slash)
	auth Auth

	// HTTPClient optionally specifies an HTTP client to use
	// instead of http.DefaultClient.
	HTTPClient *http.Client
}

// NewClient returns a new Gerrit client with the given URL prefix
// and authentication mode.
// The url should be just the scheme and hostname.
// If auth is nil, a default is used, or requests are made unauthenticated.
func NewClient(url string, auth Auth) *Client {
	if auth == nil {
		// TODO(bradfitz): use GitCookies auth, once that exists
		auth = NoAuth
	}
	return &Client{
		url:  strings.TrimSuffix(url, "/"),
		auth: auth,
	}
}

func (c *Client) httpClient() *http.Client {
	if c.HTTPClient != nil {
		return c.HTTPClient
	}
	return http.DefaultClient
}

func (c *Client) do(dst interface{}, method, path string, arg url.Values, body interface{}) error {
	var bodyr io.Reader
	var contentType string
	if body != nil {
		v, err := json.MarshalIndent(body, "", "  ")
		if err != nil {
			return err
		}
		bodyr = bytes.NewReader(v)
		contentType = "application/json"
	}
	// slashA is either "/a" (for authenticated requests) or "" for unauthenticated.
	// See https://gerrit-review.googlesource.com/Documentation/rest-api.html#authentication
	slashA := "/a"
	if _, ok := c.auth.(noAuth); ok {
		slashA = ""
	}
	var err error
	u := c.url + slashA + path
	if arg != nil {
		u += "?" + arg.Encode()
	}
	req, err := http.NewRequest(method, u, bodyr)
	if err != nil {
		return err
	}
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}
	c.auth.setAuth(c, req)
	res, err := c.httpClient().Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		body, _ := ioutil.ReadAll(io.LimitReader(res.Body, 4<<10))
		return fmt.Errorf("HTTP status %s; %s", res.Status, body)
	}

	// The JSON response begins with an XSRF-defeating header
	// like ")]}\n". Read that and skip it.
	br := bufio.NewReader(res.Body)
	if _, err := br.ReadSlice('\n'); err != nil {
		return err
	}
	return json.NewDecoder(br).Decode(dst)
}

// ChangeInfo is a Gerrit data structure.
// See https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-info
type ChangeInfo struct {
	// ID is the ID of the change in the format
	// "'<project>~<branch>~<Change-Id>'", where 'project',
	// 'branch' and 'Change-Id' are URL encoded. For 'branch' the
	// refs/heads/ prefix is omitted.
	ID           string `json:"id"`
	ChangeNumber int    `json:"_number"`

	Project string `json:"project"`

	// Branch is the name of the target branch.
	// The refs/heads/ prefix is omitted.
	Branch string `json:"branch"`

	ChangeID string `json:"change_id"`

	Subject string `json:"subject"`

	// Status is the status of the change (NEW, SUBMITTED, MERGED,
	// ABANDONED, DRAFT).
	Status string `json:"status"`

	Created  TimeStamp `json:"created"`
	Updated  TimeStamp `json:"updated"`
	Mergable bool      `json:"mergable"`

	// CurrentRevision is the commit ID of the current patch set
	// of this change.  This is only set if the current revision
	// is requested or if all revisions are requested (fields
	// "CURRENT_REVISION" or "ALL_REVISIONS").
	CurrentRevision string `json:"current_revision"`

	// Revisions maps a commit ID of the patch set to a
	// RevisionInfo entity.
	//
	// Only set if the current revision is requested (in which
	// case it will only contain a key for the current revision)
	// or if all revisions are requested.
	Revisions map[string]RevisionInfo `json:"revisions"`

	// Owner is the author of the change.
	// The details are only filled in if field "DETAILED_ACCOUNTS" is requested.
	Owner *AccountInfo `json:"owner"`

	// Messages are included if field "MESSAGES" is requested.
	Messages []ChangeMessageInfo `json:"messages"`

	Labels map[string]LabelInfo `json:"labels"`

	// TODO: more as needed

	// MoreChanges is set on the last change from QueryChanges if
	// the result set is truncated by an 'n' parameter.
	MoreChanges bool `json:"_more_changes"`
}

type AccountInfo struct {
	NumericID int64  `json:"_account_id"`
	Name      string `json:"name,omitempty"`
	Email     string `json:"email,omitempty"`
	Username  string `json:"username,omitempty"`
}

func (ai *AccountInfo) Equal(v *AccountInfo) bool {
	if ai == nil || v == nil {
		return false
	}
	return ai.NumericID == v.NumericID
}

type ChangeMessageInfo struct {
	ID             string       `json:"id"`
	Author         *AccountInfo `json:"author"`
	Time           TimeStamp    `json:"date"`
	Message        string       `json:"message"`
	RevisionNumber int          `json:"_revision_number"`
}

// The LabelInfo entity contains information about a label on a
// change, always corresponding to the current patch set.
//
// There are two options that control the contents of LabelInfo:
// LABELS and DETAILED_LABELS.
//
// For a quick summary of the state of labels, use LABELS.
//
// For detailed information about labels, including exact numeric
// votes for all users and the allowed range of votes for the current
// user, use DETAILED_LABELS.
type LabelInfo struct {
	// Optional means the label may be set, but it’s neither
	// necessary for submission nor does it block submission if
	// set.
	Optional bool `json:"optional"`

	// Fields set by LABELS field option:

	All []ApprovalInfo `json:"all"`
}

type ApprovalInfo struct {
	AccountInfo
	Value int       `json:"value"`
	Date  TimeStamp `json:"date"`
}

// The RevisionInfo entity contains information about a patch set. Not
// all fields are returned by default. Additional fields can be
// obtained by adding o parameters as described at:
// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
type RevisionInfo struct {
	Draft          bool                  `json:"draft"`
	PatchSetNumber int                   `json:"_number"`
	Created        TimeStamp             `json:"created"`
	Uploader       *AccountInfo          `json:"uploader"`
	Ref            string                `json:"ref"`
	Fetch          map[string]*FetchInfo `json:"fetch"`
	Commit         *CommitInfo           `json:"commit"`
	Files          map[string]*FileInfo  `json:"files"`
	// TODO: more
}

type CommitInfo struct {
	Author    GitPersonInfo `json:"author"`
	Committer GitPersonInfo `json:"committer"`
	CommitID  string        `json:"commit"`
	Subject   string        `json:"subject"`
	Message   string        `json:"message"`
	Parents   []CommitInfo  `json:"parents"`
}

type GitPersonInfo struct {
	Name     string    `json:"name"`
	Email    string    `json:"Email"`
	Date     TimeStamp `json:"date"`
	TZOffset int       `json:"tz"`
}

type FileInfo struct {
	Status        string `json:"status"`
	Binary        bool   `json:"binary"`
	OldPath       string `json:"old_path"`
	LinesInserted int    `json:"lines_inserted"`
	LinesDeleted  int    `json:"lines_deleted"`
}

type FetchInfo struct {
	URL      string            `json:"url"`
	Ref      string            `json:"ref"`
	Commands map[string]string `json:"commands"`
}

// QueryChangesOpt are options for QueryChanges.
type QueryChangesOpt struct {
	// N is the number of results to return.
	// If 0, the 'n' parameter is not sent to Gerrit.
	N int

	// Fields are optional fields to also return.
	// Example strings include "ALL_REVISIONS", "LABELS", "MESSAGES".
	// For a complete list, see:
	// https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-info
	Fields []string
}

func condInt(n int) []string {
	if n != 0 {
		return []string{strconv.Itoa(n)}
	}
	return nil
}

// QueryChanges queries changes. The q parameter is a Gerrit search query.
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
// For the query syntax, see https://gerrit-review.googlesource.com/Documentation/user-search.html#_search_operators
func (c *Client) QueryChanges(q string, opts ...QueryChangesOpt) ([]*ChangeInfo, error) {
	var opt QueryChangesOpt
	switch len(opts) {
	case 0:
	case 1:
		opt = opts[0]
	default:
		return nil, errors.New("only 1 option struct supported")
	}
	var changes []*ChangeInfo
	err := c.do(&changes, "GET", "/changes/", url.Values{
		"q": {q},
		"n": condInt(opt.N),
		"o": opt.Fields,
	}, nil)
	return changes, err
}

// GetChangeDetail retrieves a change with labels, detailed labels, detailed
// accounts, and messages.
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-change-detail
func (c *Client) GetChangeDetail(changeID string) (*ChangeInfo, error) {
	var change ChangeInfo
	err := c.do(&change, "GET", "/changes/"+changeID+"/detail", nil, nil)
	if err != nil {
		return nil, err
	}
	return &change, nil
}

type ReviewInput struct {
	Message string         `json:"message,omitempty"`
	Labels  map[string]int `json:"labels,omitempty"`
}

type reviewInfo struct {
	Labels map[string]int `json:"labels,omitempty"`
}

// SetReview leaves a message on a change and/or modifies labels.
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#set-review
// The changeID is https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-id
// The revision is https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#revision-id
func (c *Client) SetReview(changeID, revision string, review ReviewInput) error {
	var res reviewInfo
	return c.do(&res, "POST", fmt.Sprintf("/changes/%s/revisions/%s/review", changeID, revision),
		nil, review)
}

// GetAccountInfo gets the specified account's information from Gerrit.
// For the API call, see https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#get-account
// The accountID is https://gerrit-review.googlesource.com/Documentation/rest-api-accounts.html#account-id
//
// Note that getting "self" is a good way to validate host access, since it only requires peeker
// access to the host, not to any particular repository.
func (c *Client) GetAccountInfo(accountID string) (AccountInfo, error) {
	var res AccountInfo
	err := c.do(&res, "GET", fmt.Sprintf("/accounts/%s", accountID), nil, nil)
	return res, err
}

type TimeStamp time.Time

// Gerrit's timestamp layout is like time.RFC3339Nano, but with a space instead of the "T",
// and without a timezone (it's always in UTC).
const timeStampLayout = "2006-01-02 15:04:05.999999999"

func (ts *TimeStamp) UnmarshalJSON(p []byte) error {
	if len(p) < 2 {
		return errors.New("Timestamp too short")
	}
	if p[0] != '"' || p[len(p)-1] != '"' {
		return errors.New("not double-quoted")
	}
	s := strings.Trim(string(p), "\"")
	t, err := time.Parse(timeStampLayout, s)
	if err != nil {
		return err
	}
	*ts = TimeStamp(t)
	return nil
}

func (ts TimeStamp) Time() time.Time { return time.Time(ts) }
<|endoftext|>