<commit_before>// Copyright 2014 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 main

import (
	"fmt"
	"os"
	"regexp"
	"sort"
	"strings"
	"unicode"
	"unicode/utf8"
)

func cmdMail(args []string) {
	var (
		diff    = flags.Bool("diff", false, "show change commit diff and don't upload or mail")
		force   = flags.Bool("f", false, "mail even if there are staged changes")
		topic   = flags.String("topic", "", "set Gerrit topic")
		trybot  = flags.Bool("trybot", false, "run trybots on the uploaded CLs")
		rList   = new(stringList) // installed below
		ccList  = new(stringList) // installed below
		tagList = new(stringList) // installed below
	)
	flags.Var(rList, "r", "comma-separated list of reviewers")
	flags.Var(ccList, "cc", "comma-separated list of people to CC:")
	flags.Var(tagList, "hashtag", "comma-separated list of tags to set")

	flags.Usage = func() {
		fmt.Fprintf(stderr(), "Usage: %s mail %s [-r reviewer,...] [-cc mail,...] [-topic topic] [-trybot] [-hashtag tag,...] [commit]\n", os.Args[0], globalFlags)
	}
	flags.Parse(args)
	if len(flags.Args()) > 1 {
		flags.Usage()
		os.Exit(2)
	}

	b := CurrentBranch()

	var c *Commit
	if len(flags.Args()) == 1 {
		c = b.CommitByRev("mail", flags.Arg(0))
	} else {
		c = b.DefaultCommit("mail", "must specify commit on command line or use mail HEAD for everything")
	}

	if *diff {
		run("git", "diff", b.Branchpoint()[:7]+".."+c.ShortHash, "--")
		return
	}

	if len(ListFiles(c)) == 0 {
		dief("cannot mail: commit %s is empty", c.ShortHash)
	}

	foundCommit := false
	for _, c1 := range b.Pending() {
		if c1 == c {
			foundCommit = true
		}
		if !foundCommit {
			continue
		}
		if strings.Contains(strings.ToLower(c1.Message), "do not mail") {
			dief("%s: CL says DO NOT MAIL", c1.ShortHash)
		}
		if strings.HasPrefix(c1.Message, "fixup!") {
			dief("%s: CL is a fixup! commit", c1.ShortHash)
		}
		if strings.HasPrefix(c1.Message, "squash!") {
			dief("%s: CL is a squash! commit", c1.ShortHash)
		}

		for _, f := range ListFiles(c1) {
			if strings.HasPrefix(f, ".#") || strings.HasSuffix(f, "~") ||
				(strings.HasPrefix(f, "#") && strings.HasSuffix(f, "#")) {
				dief("cannot mail temporary files: %s", f)
			}
		}
	}
	if !foundCommit {
		// b.CommitByRev and b.DefaultCommit both return a commit on b.
		dief("internal error: did not find chosen commit on current branch")
	}

	if !*force && HasStagedChanges() {
		dief("there are staged changes; aborting.\n"+
			"Use '%s change' to include them or '%s mail -f' to force it.", os.Args[0], os.Args[0])
	}

	if !utf8.ValidString(c.Message) {
		dief("cannot mail message with invalid UTF-8")
	}
	for _, r := range c.Message {
		if !unicode.IsPrint(r) && !unicode.IsSpace(r) {
			dief("cannot mail message with non-printable rune %q", r)
		}
	}

	// for side effect of dying with a good message if origin is GitHub
	loadGerritOrigin()

	refSpec := b.PushSpec(c)
	start := "%"
	if *rList != "" {
		refSpec += mailList(start, "r", string(*rList))
		start = ","
	}
	if *ccList != "" {
		refSpec += mailList(start, "cc", string(*ccList))
		start = ","
	}
	if *tagList != "" {
		for _, tag := range strings.Split(string(*tagList), ",") {
			if tag == "" {
				dief("hashtag may not contain empty tags")
			}
			refSpec += start + "hashtag=" + tag
			start = ","
		}
	}
	if *topic != "" {
		// There's no way to escape the topic, but the only
		// ambiguous character is ',' (though other characters
		// like ' ' will be rejected outright by git).
		if strings.Contains(*topic, ",") {
			dief("topic may not contain a comma")
		}
		refSpec += start + "topic=" + *topic
		start = ","
	}
	if *trybot {
		refSpec += start + "l=Run-TryBot"
		start = ","
	}
	run("git", "push", "-q", "origin", refSpec)

	// Create local tag for mailed change.
	// If in the 'work' branch, this creates or updates work.mailed.
	// Older mailings are in the reflog, so work.mailed is newest,
	// work.mailed@{1} is the one before that, work.mailed@{2} before that,
	// and so on.
	// Git doesn't actually have a concept of a local tag,
	// but Gerrit won't let people push tags to it, so the tag
	// can't propagate out of the local client into the official repo.
	// There is no conflict with the branch names people are using
	// for work, because git change rejects any name containing a dot.
	// The space of names with dots is ours (the Go team's) to define.
	run("git", "tag", "-f", b.Name+".mailed", c.ShortHash)
}

// PushSpec returns the spec for a Gerrit push command to publish the change c in b.
// If c is nil, PushSpec returns a spec for pushing all changes in b.
func (b *Branch) PushSpec(c *Commit) string {
	local := "HEAD"
	if c != nil && (len(b.Pending()) == 0 || b.Pending()[0].Hash != c.Hash) {
		local = c.ShortHash
	}
	return local + ":refs/for/" + strings.TrimPrefix(b.OriginBranch(), "origin/")
}

// mailAddressRE matches the mail addresses we admit. It's restrictive but admits
// all the addresses in the Go CONTRIBUTORS file at time of writing (tested separately).
var mailAddressRE = regexp.MustCompile(`^([a-zA-Z0-9][-_.a-zA-Z0-9]*)(@[-_.a-zA-Z0-9]+)?$`)

// mailList turns the list of mail addresses from the flag value into the format
// expected by gerrit. The start argument is a % or , depending on where we
// are in the processing sequence.
func mailList(start, tag string, flagList string) string {
	errors := false
	spec := start
	short := ""
	long := ""
	for i, addr := range strings.Split(flagList, ",") {
		m := mailAddressRE.FindStringSubmatch(addr)
		if m == nil {
			printf("invalid reviewer mail address: %s", addr)
			errors = true
			continue
		}
		if m[2] == "" {
			email := mailLookup(addr)
			if email == "" {
				printf("unknown reviewer: %s", addr)
				errors = true
				continue
			}
			short += "," + addr
			long += "," + email
			addr = email
		}
		if i > 0 {
			spec += ","
		}
		spec += tag + "=" + addr
	}
	if short != "" {
		verbosef("expanded %s to %s", short[1:], long[1:])
	}
	if errors {
		die()
	}
	return spec
}

// reviewers is the list of reviewers for the current repository,
// sorted by how many reviews each has done.
var reviewers []reviewer

type reviewer struct {
	addr  string
	count int
}

// mailLookup translates the short name (like adg) into a full
// email address (like adg@golang.org).
// It returns "" if no translation is found.
// The algorithm for expanding short user names is as follows:
// Look at the git commit log for the current repository,
// extracting all the email addresses in Reviewed-By lines
// and sorting by how many times each address appears.
// For each short user name, walk the list, most common
// address first, and use the first address found that has
// the short user name on the left side of the @.
func mailLookup(short string) string {
	loadReviewers()

	short += "@"
	for _, r := range reviewers {
		if strings.HasPrefix(r.addr, short) {
			return r.addr
		}
	}
	return ""
}

// loadReviewers reads the reviewer list from the current git repo
// and leaves it in the global variable reviewers.
// See the comment on mailLookup for a description of how the
// list is generated and used.
func loadReviewers() {
	if reviewers != nil {
		return
	}
	countByAddr := map[string]int{}
	for _, line := range nonBlankLines(cmdOutput("git", "log", "--format=format:%B", "-n", "1000")) {
		if strings.HasPrefix(line, "Reviewed-by:") {
			f := strings.Fields(line)
			addr := f[len(f)-1]
			if strings.HasPrefix(addr, "<") && strings.Contains(addr, "@") && strings.HasSuffix(addr, ">") {
				countByAddr[addr[1:len(addr)-1]]++
			}
		}
	}

	reviewers = []reviewer{}
	for addr, count := range countByAddr {
		reviewers = append(reviewers, reviewer{addr, count})
	}
	sort.Sort(reviewersByCount(reviewers))
}

type reviewersByCount []reviewer

func (x reviewersByCount) Len() int      { return len(x) }
func (x reviewersByCount) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x reviewersByCount) Less(i, j int) bool {
	if x[i].count != x[j].count {
		return x[i].count > x[j].count
	}
	return x[i].addr < x[j].addr
}

// stringList is a flag.Value that is like flag.String, but if repeated
// keeps appending to the old value, inserting commas as separators.
// This allows people to write -r rsc,adg (like the old hg command)
// but also -r rsc -r adg (like standard git commands).
// This does change the meaning of -r rsc -r adg (it used to mean just adg).
type stringList string

func (x *stringList) String() string {
	return string(*x)
}

func (x *stringList) Set(s string) error {
	if *x != "" && s != "" {
		*x += ","
	}
	*x += stringList(s)
	return nil
}
<commit_msg>git-codereview: add mail -nokeycheck option<commit_after>// Copyright 2014 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 main

import (
	"fmt"
	"os"
	"regexp"
	"sort"
	"strings"
	"unicode"
	"unicode/utf8"
)

func cmdMail(args []string) {
	var (
		diff       = flags.Bool("diff", false, "show change commit diff and don't upload or mail")
		force      = flags.Bool("f", false, "mail even if there are staged changes")
		topic      = flags.String("topic", "", "set Gerrit topic")
		trybot     = flags.Bool("trybot", false, "run trybots on the uploaded CLs")
		rList      = new(stringList) // installed below
		ccList     = new(stringList) // installed below
		tagList    = new(stringList) // installed below
		noKeyCheck = flags.Bool("nokeycheck", false, "set 'git push -o nokeycheck', to prevent Gerrit from checking for private keys")
	)
	flags.Var(rList, "r", "comma-separated list of reviewers")
	flags.Var(ccList, "cc", "comma-separated list of people to CC:")
	flags.Var(tagList, "hashtag", "comma-separated list of tags to set")

	flags.Usage = func() {
		fmt.Fprintf(stderr(), "Usage: %s mail %s [-r reviewer,...] [-cc mail,...] [-nokeycheck] [-topic topic] [-trybot] [commit]\n", os.Args[0], globalFlags)
	}
	flags.Parse(args)
	if len(flags.Args()) > 1 {
		flags.Usage()
		os.Exit(2)
	}

	b := CurrentBranch()

	var c *Commit
	if len(flags.Args()) == 1 {
		c = b.CommitByRev("mail", flags.Arg(0))
	} else {
		c = b.DefaultCommit("mail", "must specify commit on command line or use mail HEAD for everything")
	}

	if *diff {
		run("git", "diff", b.Branchpoint()[:7]+".."+c.ShortHash, "--")
		return
	}

	if len(ListFiles(c)) == 0 {
		dief("cannot mail: commit %s is empty", c.ShortHash)
	}

	foundCommit := false
	for _, c1 := range b.Pending() {
		if c1 == c {
			foundCommit = true
		}
		if !foundCommit {
			continue
		}
		if strings.Contains(strings.ToLower(c1.Message), "do not mail") {
			dief("%s: CL says DO NOT MAIL", c1.ShortHash)
		}
		if strings.HasPrefix(c1.Message, "fixup!") {
			dief("%s: CL is a fixup! commit", c1.ShortHash)
		}
		if strings.HasPrefix(c1.Message, "squash!") {
			dief("%s: CL is a squash! commit", c1.ShortHash)
		}

		for _, f := range ListFiles(c1) {
			if strings.HasPrefix(f, ".#") || strings.HasSuffix(f, "~") ||
				(strings.HasPrefix(f, "#") && strings.HasSuffix(f, "#")) {
				dief("cannot mail temporary files: %s", f)
			}
		}
	}
	if !foundCommit {
		// b.CommitByRev and b.DefaultCommit both return a commit on b.
		dief("internal error: did not find chosen commit on current branch")
	}

	if !*force && HasStagedChanges() {
		dief("there are staged changes; aborting.\n"+
			"Use '%s change' to include them or '%s mail -f' to force it.", os.Args[0], os.Args[0])
	}

	if !utf8.ValidString(c.Message) {
		dief("cannot mail message with invalid UTF-8")
	}
	for _, r := range c.Message {
		if !unicode.IsPrint(r) && !unicode.IsSpace(r) {
			dief("cannot mail message with non-printable rune %q", r)
		}
	}

	// for side effect of dying with a good message if origin is GitHub
	loadGerritOrigin()

	refSpec := b.PushSpec(c)
	start := "%"
	if *rList != "" {
		refSpec += mailList(start, "r", string(*rList))
		start = ","
	}
	if *ccList != "" {
		refSpec += mailList(start, "cc", string(*ccList))
		start = ","
	}
	if *tagList != "" {
		for _, tag := range strings.Split(string(*tagList), ",") {
			if tag == "" {
				dief("hashtag may not contain empty tags")
			}
			refSpec += start + "hashtag=" + tag
			start = ","
		}
	}
	if *topic != "" {
		// There's no way to escape the topic, but the only
		// ambiguous character is ',' (though other characters
		// like ' ' will be rejected outright by git).
		if strings.Contains(*topic, ",") {
			dief("topic may not contain a comma")
		}
		refSpec += start + "topic=" + *topic
		start = ","
	}
	if *trybot {
		refSpec += start + "l=Run-TryBot"
		start = ","
	}
	args = []string{"push", "-q"}
	if *noKeyCheck {
		args = append(args, "-o", "nokeycheck")
	}
	args = append(args, "origin", refSpec)
	run("git", args...)

	// Create local tag for mailed change.
	// If in the 'work' branch, this creates or updates work.mailed.
	// Older mailings are in the reflog, so work.mailed is newest,
	// work.mailed@{1} is the one before that, work.mailed@{2} before that,
	// and so on.
	// Git doesn't actually have a concept of a local tag,
	// but Gerrit won't let people push tags to it, so the tag
	// can't propagate out of the local client into the official repo.
	// There is no conflict with the branch names people are using
	// for work, because git change rejects any name containing a dot.
	// The space of names with dots is ours (the Go team's) to define.
	run("git", "tag", "-f", b.Name+".mailed", c.ShortHash)
}

// PushSpec returns the spec for a Gerrit push command to publish the change c in b.
// If c is nil, PushSpec returns a spec for pushing all changes in b.
func (b *Branch) PushSpec(c *Commit) string {
	local := "HEAD"
	if c != nil && (len(b.Pending()) == 0 || b.Pending()[0].Hash != c.Hash) {
		local = c.ShortHash
	}
	return local + ":refs/for/" + strings.TrimPrefix(b.OriginBranch(), "origin/")
}

// mailAddressRE matches the mail addresses we admit. It's restrictive but admits
// all the addresses in the Go CONTRIBUTORS file at time of writing (tested separately).
var mailAddressRE = regexp.MustCompile(`^([a-zA-Z0-9][-_.a-zA-Z0-9]*)(@[-_.a-zA-Z0-9]+)?$`)

// mailList turns the list of mail addresses from the flag value into the format
// expected by gerrit. The start argument is a % or , depending on where we
// are in the processing sequence.
func mailList(start, tag string, flagList string) string {
	errors := false
	spec := start
	short := ""
	long := ""
	for i, addr := range strings.Split(flagList, ",") {
		m := mailAddressRE.FindStringSubmatch(addr)
		if m == nil {
			printf("invalid reviewer mail address: %s", addr)
			errors = true
			continue
		}
		if m[2] == "" {
			email := mailLookup(addr)
			if email == "" {
				printf("unknown reviewer: %s", addr)
				errors = true
				continue
			}
			short += "," + addr
			long += "," + email
			addr = email
		}
		if i > 0 {
			spec += ","
		}
		spec += tag + "=" + addr
	}
	if short != "" {
		verbosef("expanded %s to %s", short[1:], long[1:])
	}
	if errors {
		die()
	}
	return spec
}

// reviewers is the list of reviewers for the current repository,
// sorted by how many reviews each has done.
var reviewers []reviewer

type reviewer struct {
	addr  string
	count int
}

// mailLookup translates the short name (like adg) into a full
// email address (like adg@golang.org).
// It returns "" if no translation is found.
// The algorithm for expanding short user names is as follows:
// Look at the git commit log for the current repository,
// extracting all the email addresses in Reviewed-By lines
// and sorting by how many times each address appears.
// For each short user name, walk the list, most common
// address first, and use the first address found that has
// the short user name on the left side of the @.
func mailLookup(short string) string {
	loadReviewers()

	short += "@"
	for _, r := range reviewers {
		if strings.HasPrefix(r.addr, short) {
			return r.addr
		}
	}
	return ""
}

// loadReviewers reads the reviewer list from the current git repo
// and leaves it in the global variable reviewers.
// See the comment on mailLookup for a description of how the
// list is generated and used.
func loadReviewers() {
	if reviewers != nil {
		return
	}
	countByAddr := map[string]int{}
	for _, line := range nonBlankLines(cmdOutput("git", "log", "--format=format:%B", "-n", "1000")) {
		if strings.HasPrefix(line, "Reviewed-by:") {
			f := strings.Fields(line)
			addr := f[len(f)-1]
			if strings.HasPrefix(addr, "<") && strings.Contains(addr, "@") && strings.HasSuffix(addr, ">") {
				countByAddr[addr[1:len(addr)-1]]++
			}
		}
	}

	reviewers = []reviewer{}
	for addr, count := range countByAddr {
		reviewers = append(reviewers, reviewer{addr, count})
	}
	sort.Sort(reviewersByCount(reviewers))
}

type reviewersByCount []reviewer

func (x reviewersByCount) Len() int      { return len(x) }
func (x reviewersByCount) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x reviewersByCount) Less(i, j int) bool {
	if x[i].count != x[j].count {
		return x[i].count > x[j].count
	}
	return x[i].addr < x[j].addr
}

// stringList is a flag.Value that is like flag.String, but if repeated
// keeps appending to the old value, inserting commas as separators.
// This allows people to write -r rsc,adg (like the old hg command)
// but also -r rsc -r adg (like standard git commands).
// This does change the meaning of -r rsc -r adg (it used to mean just adg).
type stringList string

func (x *stringList) String() string {
	return string(*x)
}

func (x *stringList) Set(s string) error {
	if *x != "" && s != "" {
		*x += ","
	}
	*x += stringList(s)
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"encoding/json"
	"errors"
	"net/http"
	"regexp"
	"strconv"
	"time"
)

type gitLabAPIConnection struct {
	baseURL string
	token   string
	client  *http.Client
}

type changeDescription struct {
	Type    string
	Scope   string
	Subject string
	Body    string
	Footer  string
}

type commit struct {
	ID         string `json:"id"`
	ShortID    string `json:"short_id"`
	Title      string `json:"title"`
	Author     string `json:"author_name"`
	Email      string `json:"author_email"`
	Message    string `json:"message"`
	CommitDate string `json:"created_at"`
	ChageDesc  *changeDescription
}

const apiURL = "/api/v3"

var reCommitMsg = regexp.MustCompile(`(?m:(feat|fix|docs|style|refactor|perf|test|chore)\(([^\(\)]+)\): ([^\n]+)$\n^$\n((?:\n|.)+)^$\n((?:(?:[Rr]efs|[Cc]loses) #\d+\n)+))$`)

func newGitLabAPIConnection(gitLabBaseURL, privateToken string) *gitLabAPIConnection {
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}

	return &gitLabAPIConnection{
		baseURL: gitLabBaseURL,
		token:   privateToken,
		client:  &http.Client{Transport: tr},
	}
}

func (c *gitLabAPIConnection) getRequest(endPoint string) (*http.Request, error) {
	req, err := http.NewRequest("GET", c.baseURL+apiURL+endPoint, nil)

	if err != nil {
		return nil, err
	}

	req.Header.Set("PRIVATE-TOKEN", c.token)

	return req, nil
}

func (c *gitLabAPIConnection) projectIDFromName(projectName string) (int, error) {
	req, err := c.getRequest("/projects")

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

	res, err := c.client.Do(req)

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

	defer res.Body.Close()

	var projectInfo interface{}

	err = json.NewDecoder(res.Body).Decode(&projectInfo)

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

	projects := projectInfo.([]interface{})

	for _, project := range projects {
		m := project.(map[string]interface{})

		for k, v := range m {
			if k == "name" {
				if v.(string) == projectName {
					return int(m["id"].(float64)), nil
				}
			}
		}
	}

	return -1, errors.New("No project named '" + projectName + "' found")
}

// allCommits returns all commits in the configured range that match the message format given by the regex.
func (c *gitLabAPIConnection) allCommits() ([]commit, error) {
	pid, err := c.projectIDFromName(configInfo.ProjectName)

	if err != nil {
		return nil, err
	}

	var commitInfo []commit

	pageNo := 0

	isInRange := false

TotalLoop:
	for {
		req, err := c.getRequest("/projects/" + strconv.Itoa(pid) + "/repository/commits?page=" + strconv.Itoa(pageNo))

		if err != nil {
			return nil, err
		}

		res, err := c.client.Do(req)

		if res.StatusCode != http.StatusOK || err != nil {
			res.Body.Close()

			return nil, err
		}

		var commits []commit

		err = json.NewDecoder(res.Body).Decode(&commits)

		if err != nil {
			res.Body.Close()

			return nil, err
		}

		if len(commits) == 0 {
			res.Body.Close()

			break
		}

		for _, commit := range commits {
			if commit.ID == configInfo.ToSha || configInfo.ToSha == "HEAD" || isInRange {
				isInRange = true

				match := reCommitMsg.Match([]byte(commit.Message))

				if match {
					matches := reCommitMsg.FindAllStringSubmatch(commit.Message, -1)

					chgDesc := changeDescription{
						Type:    matches[0][1],
						Scope:   matches[0][2],
						Subject: matches[0][3],
						Body:    matches[0][4],
						Footer:  matches[0][5],
					}

					commit.ChageDesc = &chgDesc

					commitInfo = append(commitInfo, commit)
				}
			}

			if commit.ID == configInfo.FromSha {
				isInRange = false

				changeLogStartDate, err = time.Parse(time.RFC3339Nano, commit.CommitDate)

				if err != nil {
					return nil, err
				}

				break TotalLoop
			}
		}

		pageNo++
		res.Body.Close()
	}

	return commitInfo, nil
}
<commit_msg>Hack to somehow take care of project pagination.<commit_after>package main

import (
	"crypto/tls"
	"encoding/json"
	"errors"
	"net/http"
	"regexp"
	"strconv"
	"time"
)

type gitLabAPIConnection struct {
	baseURL string
	token   string
	client  *http.Client
}

type changeDescription struct {
	Type    string
	Scope   string
	Subject string
	Body    string
	Footer  string
}

type commit struct {
	ID         string `json:"id"`
	ShortID    string `json:"short_id"`
	Title      string `json:"title"`
	Author     string `json:"author_name"`
	Email      string `json:"author_email"`
	Message    string `json:"message"`
	CommitDate string `json:"created_at"`
	ChageDesc  *changeDescription
}

const apiURL = "/api/v3"

var reCommitMsg = regexp.MustCompile(`(?m:(feat|fix|docs|style|refactor|perf|test|chore)\(([^\(\)]+)\): ([^\n]+)$\n^$\n((?:\n|.)+)^$\n((?:(?:[Rr]efs|[Cc]loses) #\d+\n)+))$`)

func newGitLabAPIConnection(gitLabBaseURL, privateToken string) *gitLabAPIConnection {
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}

	return &gitLabAPIConnection{
		baseURL: gitLabBaseURL,
		token:   privateToken,
		client:  &http.Client{Transport: tr},
	}
}

func (c *gitLabAPIConnection) getRequest(endPoint string) (*http.Request, error) {
	req, err := http.NewRequest("GET", c.baseURL+apiURL+endPoint + "?per_page=100", nil)

	if err != nil {
		return nil, err
	}

	req.Header.Set("PRIVATE-TOKEN", c.token)

	return req, nil
}

func (c *gitLabAPIConnection) projectIDFromName(projectName string) (int, error) {
	req, err := c.getRequest("/projects")

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

	res, err := c.client.Do(req)

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

	defer res.Body.Close()

	var projectInfo interface{}

	err = json.NewDecoder(res.Body).Decode(&projectInfo)

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

	projects := projectInfo.([]interface{})

	for _, project := range projects {
		m := project.(map[string]interface{})

		for k, v := range m {
			if k == "name" {
				if v.(string) == projectName {
					return int(m["id"].(float64)), nil
				}
			}
		}
	}

	return -1, errors.New("No project named '" + projectName + "' found")
}

// allCommits returns all commits in the configured range that match the message format given by the regex.
func (c *gitLabAPIConnection) allCommits() ([]commit, error) {
	pid, err := c.projectIDFromName(configInfo.ProjectName)

	if err != nil {
		return nil, err
	}

	var commitInfo []commit

	pageNo := 0

	isInRange := false

TotalLoop:
	for {
		req, err := c.getRequest("/projects/" + strconv.Itoa(pid) + "/repository/commits?page=" + strconv.Itoa(pageNo))

		if err != nil {
			return nil, err
		}

		res, err := c.client.Do(req)

		if res.StatusCode != http.StatusOK || err != nil {
			res.Body.Close()

			return nil, err
		}

		var commits []commit

		err = json.NewDecoder(res.Body).Decode(&commits)

		if err != nil {
			res.Body.Close()

			return nil, err
		}

		if len(commits) == 0 {
			res.Body.Close()

			break
		}

		for _, commit := range commits {
			if commit.ID == configInfo.ToSha || configInfo.ToSha == "HEAD" || isInRange {
				isInRange = true

				match := reCommitMsg.Match([]byte(commit.Message))

				if match {
					matches := reCommitMsg.FindAllStringSubmatch(commit.Message, -1)

					chgDesc := changeDescription{
						Type:    matches[0][1],
						Scope:   matches[0][2],
						Subject: matches[0][3],
						Body:    matches[0][4],
						Footer:  matches[0][5],
					}

					commit.ChageDesc = &chgDesc

					commitInfo = append(commitInfo, commit)
				}
			}

			if commit.ID == configInfo.FromSha {
				isInRange = false

				changeLogStartDate, err = time.Parse(time.RFC3339Nano, commit.CommitDate)

				if err != nil {
					return nil, err
				}

				break TotalLoop
			}
		}

		pageNo++
		res.Body.Close()
	}

	return commitInfo, nil
}
<|endoftext|>


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

package gitea

import (
	"bytes"
	"encoding/json"
	"fmt"
	"time"
)

// Comment represents a comment in commit and issue page.
type Comment struct {
	ID       int64     `json:"id"`
	HTMLURL  string    `json:"html_url"`
	PRURL    string    `json:"pull_request_url"`
	IssueURL string    `json:"issue_url"`
	Poster   *User     `json:"user"`
	Body     string    `json:"body"`
	Created  time.Time `json:"created_at"`
	Updated  time.Time `json:"updated_at"`
}

// ListIssueComments list comments on an issue.
func (c *Client) ListIssueComments(owner, repo string, index int64) ([]*Comment, error) {
	comments := make([]*Comment, 0, 10)
	return comments, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index), nil, nil, &comments)
}

// ListRepoIssueComments list comments for a given repo.
func (c *Client) ListRepoIssueComments(owner, repo string) ([]*Comment, error) {
	comments := make([]*Comment, 0, 10)
	return comments, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments", owner, repo), nil, nil, &comments)
}

// CreateIssueCommentOption is option when creating an issue comment.
type CreateIssueCommentOption struct {
	Body string `json:"body" binding:"Required"`
}

// CreateIssueComment create comment on an issue.
func (c *Client) CreateIssueComment(owner, repo string, index int64, opt CreateIssueCommentOption) (*Comment, error) {
	body, err := json.Marshal(&opt)
	if err != nil {
		return nil, err
	}
	comment := new(Comment)
	return comment, c.getParsedResponse("POST", fmt.Sprintf("/repos/:%s/:%s/issues/%d/comments", owner, repo, index), jsonHeader, bytes.NewReader(body), comment)
}

// EditIssueCommentOption is option when editing an issue comment.
type EditIssueCommentOption struct {
	Body string `json:"body" binding:"Required"`
}

// EditIssueComment edits an issue comment.
func (c *Client) EditIssueComment(owner, repo string, index, commentID int64, opt EditIssueCommentOption) (*Comment, error) {
	body, err := json.Marshal(&opt)
	if err != nil {
		return nil, err
	}
	comment := new(Comment)
	return comment, c.getParsedResponse("PATCH", fmt.Sprintf("/repos/:%s/:%s/issues/%d/comments/%d", owner, repo, index, commentID), jsonHeader, bytes.NewReader(body), comment)
}

// DeleteIssueComment deletes an issue comment.
func (c *Client) DeleteIssueComment(owner, repo string, index, commentID int64) error {
	_, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/comments/%d", owner, repo, index, commentID), nil, nil)
	return err
}
<commit_msg>fixed backend path for CreateIssueComment (#46)<commit_after>// Copyright 2016 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package gitea

import (
	"bytes"
	"encoding/json"
	"fmt"
	"time"
)

// Comment represents a comment in commit and issue page.
type Comment struct {
	ID       int64     `json:"id"`
	HTMLURL  string    `json:"html_url"`
	PRURL    string    `json:"pull_request_url"`
	IssueURL string    `json:"issue_url"`
	Poster   *User     `json:"user"`
	Body     string    `json:"body"`
	Created  time.Time `json:"created_at"`
	Updated  time.Time `json:"updated_at"`
}

// ListIssueComments list comments on an issue.
func (c *Client) ListIssueComments(owner, repo string, index int64) ([]*Comment, error) {
	comments := make([]*Comment, 0, 10)
	return comments, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index), nil, nil, &comments)
}

// ListRepoIssueComments list comments for a given repo.
func (c *Client) ListRepoIssueComments(owner, repo string) ([]*Comment, error) {
	comments := make([]*Comment, 0, 10)
	return comments, c.getParsedResponse("GET", fmt.Sprintf("/repos/%s/%s/issues/comments", owner, repo), nil, nil, &comments)
}

// CreateIssueCommentOption is option when creating an issue comment.
type CreateIssueCommentOption struct {
	Body string `json:"body" binding:"Required"`
}

// CreateIssueComment create comment on an issue.
func (c *Client) CreateIssueComment(owner, repo string, index int64, opt CreateIssueCommentOption) (*Comment, error) {
	body, err := json.Marshal(&opt)
	if err != nil {
		return nil, err
	}
	comment := new(Comment)
	return comment, c.getParsedResponse("POST", fmt.Sprintf("/repos/%s/%s/issues/%d/comments", owner, repo, index), jsonHeader, bytes.NewReader(body), comment)
}

// EditIssueCommentOption is option when editing an issue comment.
type EditIssueCommentOption struct {
	Body string `json:"body" binding:"Required"`
}

// EditIssueComment edits an issue comment.
func (c *Client) EditIssueComment(owner, repo string, index, commentID int64, opt EditIssueCommentOption) (*Comment, error) {
	body, err := json.Marshal(&opt)
	if err != nil {
		return nil, err
	}
	comment := new(Comment)
	return comment, c.getParsedResponse("PATCH", fmt.Sprintf("/repos/:%s/:%s/issues/%d/comments/%d", owner, repo, index, commentID), jsonHeader, bytes.NewReader(body), comment)
}

// DeleteIssueComment deletes an issue comment.
func (c *Client) DeleteIssueComment(owner, repo string, index, commentID int64) error {
	_, err := c.getResponse("DELETE", fmt.Sprintf("/repos/%s/%s/issues/%d/comments/%d", owner, repo, index, commentID), nil, nil)
	return err
}
<|endoftext|>


<commit_before>package fetcher

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

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

func TestUrlFetcher(t *testing.T) {
	ctx := &mockContext{
		Context: context.Background(),
	}

	// HACK: Reduce backOff.MaxDelay for the duration of this test
	maxDelay := backOff.MaxDelay
	backOff.MaxDelay = 100 * time.Millisecond
	defer func() { backOff.MaxDelay = maxDelay }()

	// Count number of request and setup a test server
	count := 0
	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		count++
		switch r.URL.Path {
		case "/ok":
			w.WriteHeader(200)
			w.Write([]byte("status-ok"))

		case "/slow":
			w.Header().Set("Content-Length", "60")
			w.WriteHeader(200)
			debug("starting slow response")
			for i := 0; i < 10; i++ {
				time.Sleep(100 * time.Millisecond)
				w.Write([]byte("hello\n"))
				w.(http.Flusher).Flush()
			}
			debug("finished slow response")

		case "/client-error":
			w.WriteHeader(400)
			w.Write([]byte("client-error"))

		case "/unauthorized":
			w.WriteHeader(401)
			w.Write([]byte("unauthorized"))

		case "/forbidden":
			w.WriteHeader(403)
			w.Write([]byte("forbidden"))

		case "/not-found":
			w.WriteHeader(404)
			w.Write([]byte("not-found"))

		case "/server-error":
			w.WriteHeader(500)
			w.Write([]byte("server-error"))

		default:
			panic("Unhandled path: " + r.URL.Path)
		}
	}))
	defer s.Close()

	t.Run("status-ok", func(t *testing.T) {
		count = 0
		w := &mockWriteSeekReseter{}
		ref, err := URL.NewReference(ctx, s.URL+"/ok")
		require.NoError(t, err)
		err = ref.Fetch(ctx, w)
		require.NoError(t, err)
		require.Equal(t, "status-ok", w.String())
		require.Equal(t, 1, count)
	})

	t.Run("slow progress reports", func(t *testing.T) {
		// Spoof progress report for this test case
		origProgressReportInterval := progressReportInterval
		progressReportInterval = 100 * time.Millisecond
		defer func() {
			progressReportInterval = origProgressReportInterval
		}()

		ctx2 := &mockContext{Context: context.Background()}
		count = 0
		w := &mockWriteSeekReseter{}
		ref, err := URL.NewReference(ctx2, s.URL+"/slow")
		require.NoError(t, err)
		err = ref.Fetch(ctx2, w)
		require.NoError(t, err)
		require.Equal(t, 1, count)
		require.True(t, len(ctx2.ProgressReports()) > 3, "expected more than 3 progress reports")
		var prev float64
		for _, report := range ctx2.ProgressReports() {
			require.True(t, prev <= report, "Reporting shouldn't go backwards")
			prev = report
		}
	})

	t.Run("client-error", func(t *testing.T) {
		count = 0
		w := &mockWriteSeekReseter{}
		ref, err := URL.NewReference(ctx, s.URL+"/client-error")
		require.NoError(t, err)
		err = ref.Fetch(ctx, w)
		require.Error(t, err)
		require.Contains(t, err.Error(), "client-error")
		require.Equal(t, "", w.String())
		require.Equal(t, 1, count)
	})

	t.Run("unauthorized", func(t *testing.T) {
		count = 0
		w := &mockWriteSeekReseter{}
		ref, err := URL.NewReference(ctx, s.URL+"/unauthorized")
		require.NoError(t, err)
		err = ref.Fetch(ctx, w)
		require.Error(t, err)
		require.Contains(t, err.Error(), "unauthorized")
		require.Equal(t, "", w.String())
		require.Equal(t, 1, count)
	})

	t.Run("forbidden", func(t *testing.T) {
		count = 0
		w := &mockWriteSeekReseter{}
		ref, err := URL.NewReference(ctx, s.URL+"/forbidden")
		require.NoError(t, err)
		err = ref.Fetch(ctx, w)
		require.Error(t, err)
		require.Contains(t, err.Error(), "forbidden")
		require.Equal(t, "", w.String())
		require.Equal(t, 1, count)
	})

	t.Run("not-found", func(t *testing.T) {
		count = 0
		w := &mockWriteSeekReseter{}
		ref, err := URL.NewReference(ctx, s.URL+"/not-found")
		require.NoError(t, err)
		err = ref.Fetch(ctx, w)
		require.Error(t, err)
		require.Contains(t, err.Error(), "not-found")
		require.Equal(t, "", w.String())
		require.Equal(t, 1, count)
	})

	t.Run("server-error", func(t *testing.T) {
		count = 0
		w := &mockWriteSeekReseter{}
		ref, err := URL.NewReference(ctx, s.URL+"/server-error")
		require.NoError(t, err)
		err = ref.Fetch(ctx, w)
		require.Error(t, err)
		require.Contains(t, err.Error(), "server-error")
		require.Equal(t, "", w.String())
		require.Equal(t, maxRetries+1, count)
	})
}
<commit_msg>Added another test case for fetcher<commit_after>package fetcher

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

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

func TestUrlFetcher(t *testing.T) {
	ctx := &mockContext{
		Context: context.Background(),
	}

	// HACK: Reduce backOff.MaxDelay for the duration of this test
	maxDelay := backOff.MaxDelay
	backOff.MaxDelay = 100 * time.Millisecond
	defer func() { backOff.MaxDelay = maxDelay }()

	// Count number of request and setup a test server
	count := 0
	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		count++
		switch r.URL.Path {
		case "/ok":
			w.WriteHeader(200)
			w.Write([]byte("status-ok"))

		case "/streaming":
			// streaming without Content-Length
			w.WriteHeader(200)
			for i := 0; i < 10; i++ {
				time.Sleep(10 * time.Millisecond)
				w.Write([]byte("hello\n"))
				w.(http.Flusher).Flush()
			}

		case "/slow":
			w.Header().Set("Content-Length", "60")
			w.WriteHeader(200)
			debug("starting slow response")
			for i := 0; i < 10; i++ {
				time.Sleep(100 * time.Millisecond)
				w.Write([]byte("hello\n"))
				w.(http.Flusher).Flush()
			}
			debug("finished slow response")

		case "/client-error":
			w.WriteHeader(400)
			w.Write([]byte("client-error"))

		case "/unauthorized":
			w.WriteHeader(401)
			w.Write([]byte("unauthorized"))

		case "/forbidden":
			w.WriteHeader(403)
			w.Write([]byte("forbidden"))

		case "/not-found":
			w.WriteHeader(404)
			w.Write([]byte("not-found"))

		case "/server-error":
			w.WriteHeader(500)
			w.Write([]byte("server-error"))

		default:
			panic("Unhandled path: " + r.URL.Path)
		}
	}))
	defer s.Close()

	t.Run("status-ok", func(t *testing.T) {
		count = 0
		w := &mockWriteSeekReseter{}
		ref, err := URL.NewReference(ctx, s.URL+"/ok")
		require.NoError(t, err)
		err = ref.Fetch(ctx, w)
		require.NoError(t, err)
		require.Equal(t, "status-ok", w.String())
		require.Equal(t, 1, count)
	})

	t.Run("streaming-ok", func(t *testing.T) {
		count = 0
		w := &mockWriteSeekReseter{}
		ref, err := URL.NewReference(ctx, s.URL+"/streaming")
		require.NoError(t, err)
		err = ref.Fetch(ctx, w)
		require.NoError(t, err)
		require.Contains(t, w.String(), "hello")
		require.Equal(t, 1, count)
	})

	t.Run("slow progress reports", func(t *testing.T) {
		// Spoof progress report for this test case
		origProgressReportInterval := progressReportInterval
		progressReportInterval = 100 * time.Millisecond
		defer func() {
			progressReportInterval = origProgressReportInterval
		}()

		ctx2 := &mockContext{Context: context.Background()}
		count = 0
		w := &mockWriteSeekReseter{}
		ref, err := URL.NewReference(ctx2, s.URL+"/slow")
		require.NoError(t, err)
		err = ref.Fetch(ctx2, w)
		require.NoError(t, err)
		require.Equal(t, 1, count)
		require.True(t, len(ctx2.ProgressReports()) > 3, "expected more than 3 progress reports")
		var prev float64
		for _, report := range ctx2.ProgressReports() {
			require.True(t, prev <= report, "Reporting shouldn't go backwards")
			prev = report
		}
	})

	t.Run("client-error", func(t *testing.T) {
		count = 0
		w := &mockWriteSeekReseter{}
		ref, err := URL.NewReference(ctx, s.URL+"/client-error")
		require.NoError(t, err)
		err = ref.Fetch(ctx, w)
		require.Error(t, err)
		require.Contains(t, err.Error(), "client-error")
		require.Equal(t, "", w.String())
		require.Equal(t, 1, count)
	})

	t.Run("unauthorized", func(t *testing.T) {
		count = 0
		w := &mockWriteSeekReseter{}
		ref, err := URL.NewReference(ctx, s.URL+"/unauthorized")
		require.NoError(t, err)
		err = ref.Fetch(ctx, w)
		require.Error(t, err)
		require.Contains(t, err.Error(), "unauthorized")
		require.Equal(t, "", w.String())
		require.Equal(t, 1, count)
	})

	t.Run("forbidden", func(t *testing.T) {
		count = 0
		w := &mockWriteSeekReseter{}
		ref, err := URL.NewReference(ctx, s.URL+"/forbidden")
		require.NoError(t, err)
		err = ref.Fetch(ctx, w)
		require.Error(t, err)
		require.Contains(t, err.Error(), "forbidden")
		require.Equal(t, "", w.String())
		require.Equal(t, 1, count)
	})

	t.Run("not-found", func(t *testing.T) {
		count = 0
		w := &mockWriteSeekReseter{}
		ref, err := URL.NewReference(ctx, s.URL+"/not-found")
		require.NoError(t, err)
		err = ref.Fetch(ctx, w)
		require.Error(t, err)
		require.Contains(t, err.Error(), "not-found")
		require.Equal(t, "", w.String())
		require.Equal(t, 1, count)
	})

	t.Run("server-error", func(t *testing.T) {
		count = 0
		w := &mockWriteSeekReseter{}
		ref, err := URL.NewReference(ctx, s.URL+"/server-error")
		require.NoError(t, err)
		err = ref.Fetch(ctx, w)
		require.Error(t, err)
		require.Contains(t, err.Error(), "server-error")
		require.Equal(t, "", w.String())
		require.Equal(t, maxRetries+1, count)
	})
}
<|endoftext|>


<commit_before>package gocd

import (
	"context"
	"encoding/json"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"net/http"
	"strings"
)

// APIClientRequest helper struct to reduce amount of code.
type APIClientRequest struct {
	Method       string
	Path         string
	APIVersion   string
	RequestBody  interface{}
	ResponseType string
	ResponseBody interface{}
	Headers      map[string]string
}

// Handles any call to HEAD by returning whether or not we got a 2xx code.
func (c *Client) genericHeadAction(ctx context.Context, path string, apiversion string) (bool, *APIResponse, error) {
	_, resp, err := c.httpAction(ctx, &APIClientRequest{
		Method:       "HEAD",
		Path:         path,
		APIVersion:   apiversion,
		ResponseType: responseTypeJSON,
	})

	exists := resp.HTTP.StatusCode >= 300 || resp.HTTP.StatusCode < 200

	return exists, resp, err

}

func (c *Client) patchAction(ctx context.Context, r *APIClientRequest) (interface{}, *APIResponse, error) {
	r.Method = "PATCH"
	return c.httpAction(ctx, r)
}

func (c *Client) getAction(ctx context.Context, r *APIClientRequest) (interface{}, *APIResponse, error) {
	r.Method = "GET"
	return c.httpAction(ctx, r)
}

func (c *Client) postAction(ctx context.Context, r *APIClientRequest) (interface{}, *APIResponse, error) {
	r.Method = "POST"
	return c.httpAction(ctx, r)
}

func (c *Client) putAction(ctx context.Context, r *APIClientRequest) (interface{}, *APIResponse, error) {
	r.Method = "PUT"
	return c.httpAction(ctx, r)
}

// Returns a message from the DELETE action on the provided HTTP resource.
func (c *Client) deleteAction(ctx context.Context, path string, apiversion string) (string, *APIResponse, error) {
	a := StringResponse{}
	_, resp, err := c.httpAction(ctx, &APIClientRequest{
		Method:       "DELETE",
		Path:         path,
		APIVersion:   apiversion,
		ResponseType: responseTypeJSON,
		ResponseBody: &a,
	})

	return a.Message, resp, err
}

func (c *Client) httpAction(ctx context.Context, r *APIClientRequest) (interface{}, *APIResponse, error) {

	log.Debugf("HTTP Request")
	log.Debugf("%s %s", r.Method, r.Path)

	if r.ResponseType == "" {
		r.ResponseType = responseTypeJSON
	}

	if ver, isVersioned := (r.RequestBody).(Versioned); isVersioned {
		if r.Headers == nil {
			r.Headers = map[string]string{}
		}
		r.Headers["If-Match"] = fmt.Sprintf("\"%s\"", ver.GetVersion())
	}

	// Build the request
	req, err := c.NewRequest(r.Method, r.Path, r.RequestBody, r.APIVersion)
	if err != nil {
		return false, nil, err
	}

	if len(r.Headers) > 0 {
		for key, value := range r.Headers {
			req.HTTP.Header.Set(key, value)
		}
	}

	for header, values := range req.HTTP.Header {
		for _, value := range values {
			log.Debugf("%s: %s", header, value)
		}
	}
	if r.RequestBody != nil {
		log.Debugf("\n%s\n\n", r.RequestBody)
	}

	resp, err := c.Do(ctx, req, r.ResponseBody, r.ResponseType)
	if err != nil {
		log.Error(err)
		return r.ResponseBody, resp, err
	}

	if ver, isVersioned := (r.ResponseBody).(Versioned); isVersioned {
		parseVersions(resp.HTTP, ver)
	}

	if r.ResponseType == responseTypeJSON {
		log.Debugf("\nResponse\n%s %s\n", resp.HTTP.Proto, resp.HTTP.Status)
		for header, values := range resp.HTTP.Header {
			for _, value := range values {
				log.Debugf("%s: %s", header, value)
			}
		}
		b, _ := json.Marshal(r.ResponseBody)
		log.Debugf("\n%s", b)
	}

	return r.ResponseBody, resp, err
}

func parseVersions(response *http.Response, versioned Versioned) {
	etag := response.Header.Get("Etag")
	versioned.SetVersion(
		strings.Replace(etag, "\"", "", -1),
	)
}
<commit_msg>Reduced duplicate code<commit_after>package gocd

import (
	"context"
	"encoding/json"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"net/http"
	"strings"
)

// APIClientRequest helper struct to reduce amount of code.
type APIClientRequest struct {
	Method       string
	Path         string
	APIVersion   string
	RequestBody  interface{}
	ResponseType string
	ResponseBody interface{}
	Headers      map[string]string
}

// Handles any call to HEAD by returning whether or not we got a 2xx code.
func (c *Client) genericHeadAction(ctx context.Context, path string, apiversion string) (bool, *APIResponse, error) {
	_, resp, err := c.httpAction(ctx, &APIClientRequest{
		Method:       "HEAD",
		Path:         path,
		APIVersion:   apiversion,
		ResponseType: responseTypeJSON,
	})

	exists := resp.HTTP.StatusCode >= 300 || resp.HTTP.StatusCode < 200

	return exists, resp, err

}

func (c *Client) patchAction(ctx context.Context, r *APIClientRequest) (interface{}, *APIResponse, error) {
	r.Method = "PATCH"
	return c.httpAction(ctx, r)
}

func (c *Client) getAction(ctx context.Context, r *APIClientRequest) (interface{}, *APIResponse, error) {
	r.Method = "GET"
	return c.httpAction(ctx, r)
}

func (c *Client) postAction(ctx context.Context, r *APIClientRequest) (interface{}, *APIResponse, error) {
	r.Method = "POST"
	return c.httpAction(ctx, r)
}

func (c *Client) putAction(ctx context.Context, r *APIClientRequest) (interface{}, *APIResponse, error) {
	r.Method = "PUT"
	return c.httpAction(ctx, r)
}

// Returns a message from the DELETE action on the provided HTTP resource.
func (c *Client) deleteAction(ctx context.Context, path string, apiversion string) (string, *APIResponse, error) {
	a := StringResponse{}
	_, resp, err := c.httpAction(ctx, &APIClientRequest{
		Method:       "DELETE",
		Path:         path,
		APIVersion:   apiversion,
		ResponseType: responseTypeJSON,
		ResponseBody: &a,
	})

	return a.Message, resp, err
}

func (c *Client) httpAction(ctx context.Context, r *APIClientRequest) (responeBody interface{}, resp *APIResponse, err error) {

	log.Debugf("HTTP Request\n%s %s", r.Method, r.Path)

	if r.ResponseType == "" {
		r.ResponseType = responseTypeJSON
	}

	if ver, isVersioned := (r.RequestBody).(Versioned); isVersioned {
		if r.Headers == nil {
			r.Headers = map[string]string{}
		}
		r.Headers["If-Match"] = fmt.Sprintf("\"%s\"", ver.GetVersion())
	}

	// Build the request
	var req *APIRequest
	if req, err = c.NewRequest(r.Method, r.Path, r.RequestBody, r.APIVersion); err != nil {
		return false, nil, err
	}
	if r.RequestBody != nil {
		log.Debugf("\n%s\n\n", r.RequestBody)
	}

	if len(r.Headers) > 0 {
		for key, value := range r.Headers {
			req.HTTP.Header.Set(key, value)
		}
	}

	printHeaderDebug(req.HTTP.Header)

	if resp, err = c.Do(ctx, req, r.ResponseBody, r.ResponseType); err != nil {
		log.Error(err)
		return r.ResponseBody, resp, err
	}

	if ver, isVersioned := (r.ResponseBody).(Versioned); isVersioned {
		parseVersions(resp.HTTP, ver)
	}

	if r.ResponseType == responseTypeJSON {
		log.Debugf("\nResponse\n%s %s\n", resp.HTTP.Proto, resp.HTTP.Status)
		printHeaderDebug(resp.HTTP.Header)
		b, _ := json.Marshal(r.ResponseBody)
		log.Debugf("\n%s", b)
	}

	return r.ResponseBody, resp, err
}

func printHeaderDebug(headers http.Header) {
	for header, values := range headers {
		for _, value := range values {
			log.Debugf("%s: %s", header, value)
		}
	}
}

func parseVersions(response *http.Response, versioned Versioned) {
	etag := response.Header.Get("Etag")
	versioned.SetVersion(
		strings.Replace(etag, "\"", "", -1),
	)
}
<|endoftext|>


<commit_before>package ipc

import (
	"fmt"
	"io"
	"os"
	"strings"
	"testing"
	"time"

	"veyron.io/veyron/veyron2/naming"

	"veyron.io/veyron/veyron/lib/expect"
	"veyron.io/veyron/veyron/runtimes/google/ipc/stream/vc"

	"veyron.io/veyron/veyron/lib/modules"
	imanager "veyron.io/veyron/veyron/runtimes/google/ipc/stream/manager"
	"veyron.io/veyron/veyron/runtimes/google/ipc/stream/proxy"
	"veyron.io/veyron/veyron/runtimes/google/ipc/stream/sectest"
	inaming "veyron.io/veyron/veyron/runtimes/google/naming"
	tnaming "veyron.io/veyron/veyron/runtimes/google/testing/mocks/naming"
)

// TestReconnect verifies that the client transparently re-establishes the
// connection to the server if the server dies and comes back (on the same
// endpoint).
func TestReconnect(t *testing.T) {
	b := createBundle(t, sectest.NewPrincipal("client"), nil, nil) // We only need the client from the bundle.
	defer b.cleanup(t)
	sh := modules.NewShell()
	defer sh.Cleanup(os.Stderr, os.Stderr)
	server, err := sh.Start("runServer", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	session := expect.NewSession(t, server.Stdout(), time.Minute)
	addr := session.ReadLine()
	ep, err := inaming.NewEndpoint(addr)
	if err != nil {
		t.Fatalf("inaming.NewEndpoint(%q): %v", addr, err)
	}
	serverName := naming.JoinAddressName(ep.String(), "suffix")
	makeCall := func() (string, error) {
		ctx, _ := testContext().WithDeadline(time.Now().Add(5 * time.Second))
		call, err := b.client.StartCall(ctx, serverName, "Echo", []interface{}{"bratman"})
		if err != nil {
			return "", fmt.Errorf("START: %s", err)
		}
		var result string
		if err = call.Finish(&result); err != nil {
			return "", err
		}
		return result, nil
	}
	expected := `method:"Echo",suffix:"suffix",arg:"bratman"`
	if result, err := makeCall(); err != nil || result != expected {
		t.Errorf("Got (%q, %v) want (%q, nil)", result, err, expected)
	}
	// Kill the server, verify client can't talk to it anymore.
	server.Shutdown(nil, nil)
	if _, err := makeCall(); err == nil || !strings.HasPrefix(err.Error(), "START") {
		t.Fatalf(`Got (%v) want ("START: <err>") as server is down`, err)
	}

	// Resurrect the server with the same address, verify client
	// re-establishes the connection.
	server, err = sh.Start("runServer", addr)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	session = expect.NewSession(t, server.Stdout(), time.Minute)
	defer server.Shutdown(nil, nil)
	session.Expect(addr)
	if result, err := makeCall(); err != nil || result != expected {
		t.Errorf("Got (%q, %v) want (%q, nil)", result, err, expected)
	}
}

type proxyHandle struct {
	ns      naming.Namespace
	process modules.Handle
	session *expect.Session
	mount   string
}

func (h *proxyHandle) Start(t *testing.T) error {
	sh := modules.NewShell()
	server, err := sh.Start("runProxy")
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	h.process = server
	h.session = expect.NewSession(t, server.Stdout(), time.Minute)
	h.mount = h.session.ReadLine()
	if err := h.session.Error(); err != nil {
		return err
	}
	if err := h.ns.Mount(testContext(), "proxy", h.mount, time.Hour); err != nil {
		return err
	}
	return nil
}

func (h *proxyHandle) Stop() error {
	if h.process == nil {
		return nil
	}
	h.process.Shutdown(os.Stderr, os.Stderr)
	h.process = nil
	if len(h.mount) == 0 {
		return nil
	}
	return h.ns.Unmount(testContext(), "proxy", h.mount)
}

func TestProxy(t *testing.T) {
	sm := imanager.InternalNew(naming.FixedRoutingID(0x555555555))
	ns := tnaming.NewSimpleNamespace()
	client, err := InternalNewClient(sm, ns, vc.LocalPrincipal{sectest.NewPrincipal("client")})
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()
	server, err := InternalNewServer(testContext(), sm, ns, vc.LocalPrincipal{sectest.NewPrincipal("server")})
	if err != nil {
		t.Fatal(err)
	}
	defer server.Stop()

	name := "mountpoint/server/suffix"
	makeCall := func() (string, error) {
		ctx, _ := testContext().WithDeadline(time.Now().Add(5 * time.Second))
		// Let's fail fast so that the tests don't take as long to run.
		call, err := client.StartCall(ctx, name, "Echo", []interface{}{"batman"})
		if err != nil {
			// proxy is down, we should return here/.... prepend
			// the error with a well known string so that we can test for that.
			return "", fmt.Errorf("RESOLVE: %s", err)
		}
		var result string
		if err = call.Finish(&result); err != nil {
			return "", err
		}
		return result, nil
	}
	proxy := &proxyHandle{ns: ns}
	if err := proxy.Start(t); err != nil {
		t.Fatal(err)
	}
	defer proxy.Stop()
	spec := listenSpec
	spec.Proxy = "proxy"
	if _, err := server.ListenX(&spec); err != nil {
		t.Fatal(err)
	}
	if err := server.Serve("mountpoint/server", testServerDisp{&testServer{}}); err != nil {
		t.Fatal(err)
	}
	verifyMount(t, ns, name)
	// Proxied endpoint should be published and RPC should succeed (through proxy)
	const expected = `method:"Echo",suffix:"suffix",arg:"batman"`
	if result, err := makeCall(); result != expected || err != nil {
		t.Fatalf("Got (%v, %v) want (%v, nil)", result, err, expected)
	}
	// Proxy dies, calls should fail and the name should be unmounted.
	if err := proxy.Stop(); err != nil {
		t.Fatal(err)
	}
	if result, err := makeCall(); err == nil || !strings.HasPrefix(err.Error(), "RESOLVE") {
		t.Fatalf(`Got (%v, %v) want ("", "RESOLVE: <err>") as proxy is down`, result, err)
	}
	for {
		if _, err := ns.Resolve(testContext(), name); err != nil {
			break
		}
	}
	verifyMountMissing(t, ns, name)

	// Proxy restarts, calls should eventually start succeeding.
	if err := proxy.Start(t); err != nil {
		t.Fatal(err)
	}

	for {
		if result, err := makeCall(); err == nil {
			if result != expected {
				t.Errorf("Got (%v, %v) want (%v, nil)", result, err, expected)
			}
			break
		}
	}
}

func runServer(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
	mgr := imanager.InternalNew(naming.FixedRoutingID(0x1111111))
	ns := tnaming.NewSimpleNamespace()
	server, err := InternalNewServer(testContext(), mgr, ns, vc.LocalPrincipal{sectest.NewPrincipal("server")})
	if err != nil {
		return fmt.Errorf("InternalNewServer failed: %v", err)
	}
	disp := testServerDisp{new(testServer)}
	if err := server.Serve("server", disp); err != nil {
		return fmt.Errorf("server.Register failed: %v", err)
	}
	spec := listenSpec
	spec.Address = args[1]
	ep, err := server.ListenX(&spec)
	if err != nil {
		return fmt.Errorf("server.Listen failed: %v", err)
	}
	fmt.Fprintf(stdout, "%s\n", ep.Addr())
	// parent process should explicitly shut us down by closing stdin.
	modules.WaitForEOF(stdin)
	return nil
}

func runProxy(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
	rid, err := naming.NewRoutingID()
	if err != nil {
		return err
	}
	proxy, err := proxy.New(rid, sectest.NewPrincipal("proxy"), "tcp", "127.0.0.1:0", "")
	if err != nil {
		return err
	}
	fmt.Fprintf(stdout, "/%s\n", proxy.Endpoint().String())
	// parent process should explicitly shut us down by closing stdin.
	modules.WaitForEOF(stdin)
	return nil
}

// Required by modules framework.
func TestHelperProcess(t *testing.T) {
	modules.DispatchInTest()
}

func init() {
	modules.RegisterChild("runServer", "[address]", runServer)
	modules.RegisterChild("runProxy", "", runProxy)
}
<commit_msg>veyron/runtimes/google/ipc: Fix flaky test<commit_after>package ipc

import (
	"fmt"
	"io"
	"os"
	"strings"
	"testing"
	"time"

	"veyron.io/veyron/veyron2/naming"

	"veyron.io/veyron/veyron/lib/expect"
	"veyron.io/veyron/veyron/runtimes/google/ipc/stream/vc"

	"veyron.io/veyron/veyron/lib/modules"
	imanager "veyron.io/veyron/veyron/runtimes/google/ipc/stream/manager"
	"veyron.io/veyron/veyron/runtimes/google/ipc/stream/proxy"
	"veyron.io/veyron/veyron/runtimes/google/ipc/stream/sectest"
	inaming "veyron.io/veyron/veyron/runtimes/google/naming"
	tnaming "veyron.io/veyron/veyron/runtimes/google/testing/mocks/naming"
)

// TestReconnect verifies that the client transparently re-establishes the
// connection to the server if the server dies and comes back (on the same
// endpoint).
func TestReconnect(t *testing.T) {
	b := createBundle(t, sectest.NewPrincipal("client"), nil, nil) // We only need the client from the bundle.
	defer b.cleanup(t)
	sh := modules.NewShell()
	defer sh.Cleanup(os.Stderr, os.Stderr)
	server, err := sh.Start("runServer", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	session := expect.NewSession(t, server.Stdout(), time.Minute)
	addr := session.ReadLine()
	ep, err := inaming.NewEndpoint(addr)
	if err != nil {
		t.Fatalf("inaming.NewEndpoint(%q): %v", addr, err)
	}
	serverName := naming.JoinAddressName(ep.String(), "suffix")
	makeCall := func() (string, error) {
		ctx, _ := testContext().WithDeadline(time.Now().Add(5 * time.Second))
		call, err := b.client.StartCall(ctx, serverName, "Echo", []interface{}{"bratman"})
		if err != nil {
			return "", fmt.Errorf("START: %s", err)
		}
		var result string
		if err = call.Finish(&result); err != nil {
			return "", err
		}
		return result, nil
	}
	expected := `method:"Echo",suffix:"suffix",arg:"bratman"`
	if result, err := makeCall(); err != nil || result != expected {
		t.Errorf("Got (%q, %v) want (%q, nil)", result, err, expected)
	}
	// Kill the server, verify client can't talk to it anymore.
	server.Shutdown(nil, nil)
	if _, err := makeCall(); err == nil || !strings.HasPrefix(err.Error(), "START") {
		t.Fatalf(`Got (%v) want ("START: <err>") as server is down`, err)
	}

	// Resurrect the server with the same address, verify client
	// re-establishes the connection.
	server, err = sh.Start("runServer", addr)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	session = expect.NewSession(t, server.Stdout(), time.Minute)
	defer server.Shutdown(nil, nil)
	session.Expect(addr)
	if result, err := makeCall(); err != nil || result != expected {
		t.Errorf("Got (%q, %v) want (%q, nil)", result, err, expected)
	}
}

type proxyHandle struct {
	ns      naming.Namespace
	process modules.Handle
	session *expect.Session
	mount   string
}

func (h *proxyHandle) Start(t *testing.T) error {
	sh := modules.NewShell()
	server, err := sh.Start("runProxy")
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	h.process = server
	h.session = expect.NewSession(t, server.Stdout(), time.Minute)
	h.mount = h.session.ReadLine()
	if err := h.session.Error(); err != nil {
		return err
	}
	if err := h.ns.Mount(testContext(), "proxy", h.mount, time.Hour); err != nil {
		return err
	}
	return nil
}

func (h *proxyHandle) Stop() error {
	if h.process == nil {
		return nil
	}
	h.process.Shutdown(os.Stderr, os.Stderr)
	h.process = nil
	if len(h.mount) == 0 {
		return nil
	}
	return h.ns.Unmount(testContext(), "proxy", h.mount)
}

func TestProxy(t *testing.T) {
	sm := imanager.InternalNew(naming.FixedRoutingID(0x555555555))
	ns := tnaming.NewSimpleNamespace()
	client, err := InternalNewClient(sm, ns, vc.LocalPrincipal{sectest.NewPrincipal("client")})
	if err != nil {
		t.Fatal(err)
	}
	defer client.Close()
	server, err := InternalNewServer(testContext(), sm, ns, vc.LocalPrincipal{sectest.NewPrincipal("server")})
	if err != nil {
		t.Fatal(err)
	}
	defer server.Stop()

	name := "mountpoint/server/suffix"
	makeCall := func() (string, error) {
		ctx, _ := testContext().WithDeadline(time.Now().Add(5 * time.Second))
		// Let's fail fast so that the tests don't take as long to run.
		call, err := client.StartCall(ctx, name, "Echo", []interface{}{"batman"})
		if err != nil {
			// proxy is down, we should return here/.... prepend
			// the error with a well known string so that we can test for that.
			return "", fmt.Errorf("RESOLVE: %s", err)
		}
		var result string
		if err = call.Finish(&result); err != nil {
			return "", err
		}
		return result, nil
	}
	proxy := &proxyHandle{ns: ns}
	if err := proxy.Start(t); err != nil {
		t.Fatal(err)
	}
	defer proxy.Stop()
	spec := listenSpec
	spec.Proxy = "proxy"
	if _, err := server.ListenX(&spec); err != nil {
		t.Fatal(err)
	}
	if err := server.Serve("mountpoint/server", testServerDisp{&testServer{}}); err != nil {
		t.Fatal(err)
	}
	verifyMount(t, ns, name)
	// Proxied endpoint should be published and RPC should succeed (through proxy)
	const expected = `method:"Echo",suffix:"suffix",arg:"batman"`
	if result, err := makeCall(); result != expected || err != nil {
		t.Fatalf("Got (%v, %v) want (%v, nil)", result, err, expected)
	}
	// Proxy dies, calls should fail and the name should be unmounted.
	if err := proxy.Stop(); err != nil {
		t.Fatal(err)
	}
	for {
		if _, err := ns.Resolve(testContext(), name); err != nil {
			break
		}
	}
	if result, err := makeCall(); err == nil || !strings.HasPrefix(err.Error(), "RESOLVE") {
		t.Fatalf(`Got (%v, %v) want ("", "RESOLVE: <err>") as proxy is down`, result, err)
	}
	verifyMountMissing(t, ns, name)

	// Proxy restarts, calls should eventually start succeeding.
	if err := proxy.Start(t); err != nil {
		t.Fatal(err)
	}

	for {
		if result, err := makeCall(); err == nil {
			if result != expected {
				t.Errorf("Got (%v, %v) want (%v, nil)", result, err, expected)
			}
			break
		}
	}
}

func runServer(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
	mgr := imanager.InternalNew(naming.FixedRoutingID(0x1111111))
	ns := tnaming.NewSimpleNamespace()
	server, err := InternalNewServer(testContext(), mgr, ns, vc.LocalPrincipal{sectest.NewPrincipal("server")})
	if err != nil {
		return fmt.Errorf("InternalNewServer failed: %v", err)
	}
	disp := testServerDisp{new(testServer)}
	if err := server.Serve("server", disp); err != nil {
		return fmt.Errorf("server.Register failed: %v", err)
	}
	spec := listenSpec
	spec.Address = args[1]
	ep, err := server.ListenX(&spec)
	if err != nil {
		return fmt.Errorf("server.Listen failed: %v", err)
	}
	fmt.Fprintf(stdout, "%s\n", ep.Addr())
	// parent process should explicitly shut us down by closing stdin.
	modules.WaitForEOF(stdin)
	return nil
}

func runProxy(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
	rid, err := naming.NewRoutingID()
	if err != nil {
		return err
	}
	proxy, err := proxy.New(rid, sectest.NewPrincipal("proxy"), "tcp", "127.0.0.1:0", "")
	if err != nil {
		return err
	}
	fmt.Fprintf(stdout, "/%s\n", proxy.Endpoint().String())
	// parent process should explicitly shut us down by closing stdin.
	modules.WaitForEOF(stdin)
	return nil
}

// Required by modules framework.
func TestHelperProcess(t *testing.T) {
	modules.DispatchInTest()
}

func init() {
	modules.RegisterChild("runServer", "[address]", runServer)
	modules.RegisterChild("runProxy", "", runProxy)
}
<|endoftext|>


<commit_before>package jsonapi;

import (
    "github.com/gorilla/pat"
    "net/http"
    "encoding/json"
    "fmt"
    "reflect"
    "strings"
);

type API struct{
    Resources map[string]*MountedResource;
    Linkages map[string]map[string]*MountedLinkage
    Router *pat.Router
}

type MountedResource struct{
    R Resource
    P Permissions
}

type MountedLinkage struct{
    DstR string
    Behavior Behavior
}

func NewAPI() *API {
    api := &API{
        Router: pat.New(),
        Resources: make(map[string]*MountedResource),
        Linkages: make(map[string]map[string]*MountedLinkage),
    };
    api.InitRouter();
    return api;
}

func(a *API) MountResource(name string, r Resource, p Permissions) {
    a.Resources[name] = &MountedResource{R: r, P: p};
}

func(a *API) MountLinkage(name, srcR, dstR string, behavior Behavior) {
    if(a.Linkages[srcR] == nil) {
        a.Linkages[srcR] = make(map[string]*MountedLinkage);
    }
    a.Linkages[srcR][name] = &MountedLinkage{
        DstR: dstR,
        Behavior: behavior,
    };
}

func(a *API) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    defer a.CatchResponses(w,r);
    a.Router.ServeHTTP(w, r);
}

func(a *API) CatchResponses(w http.ResponseWriter, r *http.Request) {
    if raw := recover(); raw != nil {
        switch r := raw.(type) {
        case error:
            res := &TopLevel{};
            res.Errors = []error{r};
            a.Send(res, w);
            panic(r);
        case *ResponseReply:
            a.Send(r.Reply, w);
        default:
            w.Write([]byte(fmt.Sprintf("Error handling request: %#v\n", r)));
        }
    }
}

func(a *API) Send(obj interface{}, w http.ResponseWriter) {
    str, err := json.Marshal(obj);
    Check(err);
    w.Write(str);
}

func(a *API) InitRouter() {
    //a.Router.Get("/:resource")
    a.Router.Get("/{resource}/{id}", a.FindOne)
}

func(a *API) FindOne(w http.ResponseWriter, r *http.Request) {
    resource_str := r.URL.Query().Get(":resource");
    id_str := r.URL.Query().Get(":id");

    resource := a.Resources[resource_str];

    if(resource == nil) {
        panic(&ErrorResourceDoesNotExist{Resource:resource_str});
    }

    resource.P.Check(resource_str+".FindAll", id_str, w, r);

    data, err := resource.R.FindOne(id_str, r);
    Check(err);

    Reply(a.PrepareResponse(data, resource_str))
}

func(a *API) PrepareResponse(data HasId, resource_str string) *TopLevel {
    res := &TopLevel{};
    fmt.Printf("Adding linkages %#v\n", data);
    res.Data = a.AddLinkages(data, resource_str);
    return res;
}

func(a *API) AddLinkages(data HasId, resource_str string) interface{} {
    // this probably causes performance problems
    res := DenatureObject(data);

    delete(res, "Id");
    delete(res, "ID");
    delete(res, "iD");
    res["id"] = data.GetId();

    fmt.Printf("Res: %#v\n", res);

    return res;
}

func DenatureObject(data interface{}) map[string]interface{} {
    v := reflect.Indirect(reflect.ValueOf(data));
    t := v.Type();

    values := make(map[string]interface{}, t.NumField());

    for i := 0; i < t.NumField(); i++ {
        tag := strings.Split(t.Field(i).Tag.Get("json"), ",");
        if len(tag[0]) == 0 {
            tag[0] = t.Field(i).Name
        }
        if len(tag) > 1 && len(tag[1]) > 0 {
            if(tag[1] == "omitempty") {
                if(reflect.DeepEqual(reflect.Zero(v.Field(i).Type()), v.Field(i).Interface())) {
                    fmt.Printf("Omitting empty field %v\n",tag[0]);
                    continue;
                }
            }
        }
        values[tag[0]] = v.Field(i).Interface();
    }

    return values;
}
<commit_msg>Fixing omitempty<commit_after>package jsonapi;

import (
    "github.com/gorilla/pat"
    "net/http"
    "encoding/json"
    "fmt"
    "reflect"
    "strings"
);

type API struct{
    Resources map[string]*MountedResource;
    Linkages map[string]map[string]*MountedLinkage
    Router *pat.Router
}

type MountedResource struct{
    R Resource
    P Permissions
}

type MountedLinkage struct{
    DstR string
    Behavior Behavior
}

func NewAPI() *API {
    api := &API{
        Router: pat.New(),
        Resources: make(map[string]*MountedResource),
        Linkages: make(map[string]map[string]*MountedLinkage),
    };
    api.InitRouter();
    return api;
}

func(a *API) MountResource(name string, r Resource, p Permissions) {
    a.Resources[name] = &MountedResource{R: r, P: p};
}

func(a *API) MountLinkage(name, srcR, dstR string, behavior Behavior) {
    if(a.Linkages[srcR] == nil) {
        a.Linkages[srcR] = make(map[string]*MountedLinkage);
    }
    a.Linkages[srcR][name] = &MountedLinkage{
        DstR: dstR,
        Behavior: behavior,
    };
}

func(a *API) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    defer a.CatchResponses(w,r);
    a.Router.ServeHTTP(w, r);
}

func(a *API) CatchResponses(w http.ResponseWriter, r *http.Request) {
    if raw := recover(); raw != nil {
        switch r := raw.(type) {
        case error:
            res := &TopLevel{};
            res.Errors = []error{r};
            a.Send(res, w);
            panic(r);
        case *ResponseReply:
            a.Send(r.Reply, w);
        default:
            w.Write([]byte(fmt.Sprintf("Error handling request: %#v\n", r)));
        }
    }
}

func(a *API) Send(obj interface{}, w http.ResponseWriter) {
    str, err := json.Marshal(obj);
    Check(err);
    w.Write(str);
}

func(a *API) InitRouter() {
    //a.Router.Get("/:resource")
    a.Router.Get("/{resource}/{id}", a.FindOne)
}

func(a *API) FindOne(w http.ResponseWriter, r *http.Request) {
    resource_str := r.URL.Query().Get(":resource");
    id_str := r.URL.Query().Get(":id");

    resource := a.Resources[resource_str];

    if(resource == nil) {
        panic(&ErrorResourceDoesNotExist{Resource:resource_str});
    }

    resource.P.Check(resource_str+".FindAll", id_str, w, r);

    data, err := resource.R.FindOne(id_str, r);
    Check(err);

    Reply(a.PrepareResponse(data, resource_str))
}

func(a *API) PrepareResponse(data HasId, resource_str string) *TopLevel {
    res := &TopLevel{};
    fmt.Printf("Adding linkages %#v\n", data);
    res.Data = a.AddLinkages(data, resource_str);
    return res;
}

func(a *API) AddLinkages(data HasId, resource_str string) interface{} {
    // this probably causes performance problems
    res := DenatureObject(data);

    delete(res, "Id");
    delete(res, "ID");
    delete(res, "iD");
    res["id"] = data.GetId();

    fmt.Printf("Res: %#v\n", res);

    return res;
}

func DenatureObject(data interface{}) map[string]interface{} {
    v := reflect.Indirect(reflect.ValueOf(data));
    t := v.Type();

    values := make(map[string]interface{}, t.NumField());

    for i := 0; i < t.NumField(); i++ {
        tag := strings.Split(t.Field(i).Tag.Get("json"), ",");
        if len(tag[0]) == 0 {
            tag[0] = t.Field(i).Name
        }
        if len(tag) > 1 && len(tag[1]) > 0 {
            if(tag[1] == "omitempty") {
                if(IsZeroOfUnderlyingType(v.Field(i).Interface())) {
                    continue;
                }
            }
        }
        values[tag[0]] = v.Field(i).Interface();
    }

    return values;
}

func IsZeroOfUnderlyingType(x interface{}) bool {
    return x == reflect.Zero(reflect.TypeOf(x)).Interface()
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package forgetfs_test

import (
	"io"
	"os"
	"path"
	"testing"

	"github.com/jacobsa/fuse/samples"
	"github.com/jacobsa/fuse/samples/forgetfs"
	. "github.com/jacobsa/ogletest"
)

func TestForgetFS(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type ForgetFSTest struct {
	samples.SampleTest
	fs *forgetfs.ForgetFS
}

func init() { RegisterTestSuite(&ForgetFSTest{}) }

func (t *ForgetFSTest) SetUp(ti *TestInfo) {
	t.fs = forgetfs.NewFileSystem()
	t.Server = t.fs
	t.SampleTest.SetUp(ti)
}

func (t *ForgetFSTest) TearDown() {
	// Unmount.
	t.SampleTest.TearDown()

	// Crash if anything is left.
	t.fs.Check()
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *ForgetFSTest) Open_Foo() {
	var err error

	f, err := os.Open(path.Join(t.Dir, "foo"))
	AssertEq(nil, err)

	err = f.Close()
	AssertEq(nil, err)
}

func (t *ForgetFSTest) Open_Bar() {
	var err error

	f, err := os.Open(path.Join(t.Dir, "bar"))
	AssertEq(nil, err)

	err = f.Close()
	AssertEq(nil, err)
}

func (t *ForgetFSTest) Open_ManyTimes() {
	// Set up a slice of files that will be closed when we're done.
	var toClose []io.Closer
	defer func() {
		for _, c := range toClose {
			ExpectEq(nil, c.Close())
		}
	}()

	// Open foo many times.
	for i := 0; i < 100; i++ {
		f, err := os.Open(path.Join(t.Dir, "foo"))
		AssertEq(nil, err)
		toClose = append(toClose, f)
	}

	// Open bar many times.
	for i := 0; i < 100; i++ {
		f, err := os.Open(path.Join(t.Dir, "bar"))
		AssertEq(nil, err)
		toClose = append(toClose, f)
	}
}

func (t *ForgetFSTest) Stat_Foo() {
	var fi os.FileInfo
	var err error

	fi, err = os.Stat(path.Join(t.Dir, "foo"))
	AssertEq(nil, err)
	AssertEq("foo", fi.Name())
	AssertEq(os.FileMode(0777), fi.Mode())
}

func (t *ForgetFSTest) Stat_Bar() {
	var fi os.FileInfo
	var err error

	fi, err = os.Stat(path.Join(t.Dir, "bar"))
	AssertEq(nil, err)
	AssertEq("bar", fi.Name())
	AssertEq(0777|os.ModeDir, fi.Mode())
}

func (t *ForgetFSTest) Stat_ManyTimes() {
	var err error

	// Stat foo many times.
	for i := 0; i < 100; i++ {
		_, err = os.Stat(path.Join(t.Dir, "foo"))
		AssertEq(nil, err)
	}

	// Stat bar many times.
	for i := 0; i < 100; i++ {
		_, err = os.Stat(path.Join(t.Dir, "bar"))
		AssertEq(nil, err)
	}
}

func (t *ForgetFSTest) CreateFile() {
	AssertTrue(false, "TODO")
}

func (t *ForgetFSTest) MkDir() {
	AssertTrue(false, "TODO")
}
<commit_msg>ForgetFSTest.CreateFile<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package forgetfs_test

import (
	"io"
	"os"
	"path"
	"testing"

	"github.com/jacobsa/fuse/samples"
	"github.com/jacobsa/fuse/samples/forgetfs"
	. "github.com/jacobsa/ogletest"
)

func TestForgetFS(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type ForgetFSTest struct {
	samples.SampleTest
	fs *forgetfs.ForgetFS
}

func init() { RegisterTestSuite(&ForgetFSTest{}) }

func (t *ForgetFSTest) SetUp(ti *TestInfo) {
	t.fs = forgetfs.NewFileSystem()
	t.Server = t.fs
	t.SampleTest.SetUp(ti)
}

func (t *ForgetFSTest) TearDown() {
	// Unmount.
	t.SampleTest.TearDown()

	// Crash if anything is left.
	t.fs.Check()
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *ForgetFSTest) Open_Foo() {
	var err error

	f, err := os.Open(path.Join(t.Dir, "foo"))
	AssertEq(nil, err)

	err = f.Close()
	AssertEq(nil, err)
}

func (t *ForgetFSTest) Open_Bar() {
	var err error

	f, err := os.Open(path.Join(t.Dir, "bar"))
	AssertEq(nil, err)

	err = f.Close()
	AssertEq(nil, err)
}

func (t *ForgetFSTest) Open_ManyTimes() {
	// Set up a slice of files that will be closed when we're done.
	var toClose []io.Closer
	defer func() {
		for _, c := range toClose {
			ExpectEq(nil, c.Close())
		}
	}()

	// Open foo many times.
	for i := 0; i < 100; i++ {
		f, err := os.Open(path.Join(t.Dir, "foo"))
		AssertEq(nil, err)
		toClose = append(toClose, f)
	}

	// Open bar many times.
	for i := 0; i < 100; i++ {
		f, err := os.Open(path.Join(t.Dir, "bar"))
		AssertEq(nil, err)
		toClose = append(toClose, f)
	}
}

func (t *ForgetFSTest) Stat_Foo() {
	var fi os.FileInfo
	var err error

	fi, err = os.Stat(path.Join(t.Dir, "foo"))
	AssertEq(nil, err)
	AssertEq("foo", fi.Name())
	AssertEq(os.FileMode(0777), fi.Mode())
}

func (t *ForgetFSTest) Stat_Bar() {
	var fi os.FileInfo
	var err error

	fi, err = os.Stat(path.Join(t.Dir, "bar"))
	AssertEq(nil, err)
	AssertEq("bar", fi.Name())
	AssertEq(0777|os.ModeDir, fi.Mode())
}

func (t *ForgetFSTest) Stat_ManyTimes() {
	var err error

	// Stat foo many times.
	for i := 0; i < 100; i++ {
		_, err = os.Stat(path.Join(t.Dir, "foo"))
		AssertEq(nil, err)
	}

	// Stat bar many times.
	for i := 0; i < 100; i++ {
		_, err = os.Stat(path.Join(t.Dir, "bar"))
		AssertEq(nil, err)
	}
}

func (t *ForgetFSTest) CreateFile() {
	// Create and close many files within the root.
	for i := 0; i < 100; i++ {
		f, err := os.Create(path.Join(t.Dir, "blah"))
		AssertEq(nil, err)
		AssertEq(nil, f.Close())
	}

	// Create and close many files within the sub-directory.
	for i := 0; i < 100; i++ {
		f, err := os.Create(path.Join(t.Dir, "bar", "blah"))
		AssertEq(nil, err)
		AssertEq(nil, f.Close())
	}
}

func (t *ForgetFSTest) MkDir() {
	AssertTrue(false, "TODO")
}
<|endoftext|>


<commit_before>// Package jwtutil holds JWT related funcs
package jwtutil

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/dgrijalva/jwt-go"

	"github.com/LeKovr/go-base/logger"
)

const (
	// Bearer is a prefix of JWT header value
	Bearer = "Bearer"
)

// -----------------------------------------------------------------------------

// Flags is a package flags sample
// in form ready for use with github.com/jessevdk/go-flags
type Flags struct {
	Key      string   `long:"jwt_key" description:"Key to sign JWT results"`
	Age      int      `long:"jwt_age" default:"96" description:"JWT age to expire token (hours)"`
	Producer string   `long:"jwt_producer" default:"dbrpc" description:"JWT producer name"`
	Issuers  []string `long:"jwt_issuer" default:"*" description:"required JWT issuer name(s)"`
}

// -----------------------------------------------------------------------------

// App - Класс сервера API
type App struct {
	Log    *logger.Log
	Config *Flags
}

// -----------------------------------------------------------------------------
// Functional options

// Config sets config struct
func Config(c *Flags) func(a *App) error {
	return func(a *App) error {
		return a.setConfig(c)
	}
}

// -----------------------------------------------------------------------------
// Internal setters

func (a *App) setConfig(c *Flags) error {
	a.Config = c
	return nil
}

// -----------------------------------------------------------------------------

// New - Конструктор сервера API
func New(log *logger.Log, options ...func(a *App) error) (a *App, err error) {

	a = &App{Log: log.WithField("in", "jwt")}

	for _, option := range options {
		err := option(a)
		if err != nil {
			return nil, err
		}
	}
	return

}

// CustomClaims holds JWT fields
type CustomClaims struct {
	Data *json.RawMessage `json:"data"`
	jwt.StandardClaims
}

// CustomRes holds JWT creation result
type CustomRes struct {
	Token string `json:"token"`
}

// Session holds custom JWT fields
type Session map[string]interface{}

// SessionSlice holds parsed JWT data
type SessionSlice struct {
	Data []Session `json:"data"`
	jwt.StandardClaims
}

// -----------------------------------------------------------------------------

// Create - creates JWT
func (a *App) Create(method string, s *json.RawMessage) (*json.RawMessage, error) {

	a.Log.Debugf("Got JWT data: %s", s)

	// Create the Claims
	claims := CustomClaims{
		s,
		jwt.StandardClaims{
			ExpiresAt: time.Now().Add(time.Hour * time.Duration(a.Config.Age)).Unix(),
			Issuer:    fmt.Sprintf("%s:%s", a.Config.Producer, method),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	t, err := token.SignedString([]byte(a.Config.Key))

	a.Log.Debugf("JWT: %v", t)
	ts := CustomRes{Token: t}
	tjs, err := json.Marshal(ts)
	traw := json.RawMessage(tjs)
	return &traw, err

}

// -----------------------------------------------------------------------------

// Parse - parses JWT
func (a *App) Parse(s string) (*Session, error) {

	// to the callback, providing flexibility.
	token, err := jwt.ParseWithClaims(s, &SessionSlice{}, func(token *jwt.Token) (interface{}, error) {
		// Don't forget to validate the alg is what you expect:
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}

		return []byte(a.Config.Key), nil
	})
	if err != nil {
		return nil, err
	}

	if claims, ok := token.Claims.(*SessionSlice); ok && token.Valid {
		a.Log.Debugf("Got JWT session: %+v (%+v)", claims, token.Header)

		if !stringExists(a.Config.Issuers, claims.Issuer, "*") {
			return nil, fmt.Errorf("Uncorrect JWT Issuer %s", claims.Issuer)
		}

		return &claims.Data[0], nil
	}
	return nil, fmt.Errorf("Invalid JWT")

}

// -----------------------------------------------------------------------------

// Check if str or any exists in strings slice
func stringExists(strings []string, str string, any string) bool {
	if len(strings) > 0 { // lookup if host is allowed
		for _, s := range strings {
			if str == s || (any != "" && s == any) {
				return true
			}
		}
	}
	return false
}
<commit_msg>Added issuer error logging<commit_after>// Package jwtutil holds JWT related funcs
package jwtutil

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/dgrijalva/jwt-go"

	"github.com/LeKovr/go-base/logger"
)

const (
	// Bearer is a prefix of JWT header value
	Bearer = "Bearer"
)

// -----------------------------------------------------------------------------

// Flags is a package flags sample
// in form ready for use with github.com/jessevdk/go-flags
type Flags struct {
	Key      string   `long:"jwt_key" description:"Key to sign JWT results"`
	Age      int      `long:"jwt_age" default:"96" description:"JWT age to expire token (hours)"`
	Producer string   `long:"jwt_producer" default:"dbrpc" description:"JWT producer name"`
	Issuers  []string `long:"jwt_issuer" default:"*" description:"required JWT issuer name(s)"`
}

// -----------------------------------------------------------------------------

// App - Класс сервера API
type App struct {
	Log    *logger.Log
	Config *Flags
}

// -----------------------------------------------------------------------------
// Functional options

// Config sets config struct
func Config(c *Flags) func(a *App) error {
	return func(a *App) error {
		return a.setConfig(c)
	}
}

// -----------------------------------------------------------------------------
// Internal setters

func (a *App) setConfig(c *Flags) error {
	a.Config = c
	return nil
}

// -----------------------------------------------------------------------------

// New - Конструктор сервера API
func New(log *logger.Log, options ...func(a *App) error) (a *App, err error) {

	a = &App{Log: log.WithField("in", "jwt")}

	for _, option := range options {
		err := option(a)
		if err != nil {
			return nil, err
		}
	}
	return

}

// CustomClaims holds JWT fields
type CustomClaims struct {
	Data *json.RawMessage `json:"data"`
	jwt.StandardClaims
}

// CustomRes holds JWT creation result
type CustomRes struct {
	Token string `json:"token"`
}

// Session holds custom JWT fields
type Session map[string]interface{}

// SessionSlice holds parsed JWT data
type SessionSlice struct {
	Data []Session `json:"data"`
	jwt.StandardClaims
}

// -----------------------------------------------------------------------------

// Create - creates JWT
func (a *App) Create(method string, s *json.RawMessage) (*json.RawMessage, error) {

	a.Log.Debugf("Got JWT data: %s", s)

	// Create the Claims
	claims := CustomClaims{
		s,
		jwt.StandardClaims{
			ExpiresAt: time.Now().Add(time.Hour * time.Duration(a.Config.Age)).Unix(),
			Issuer:    fmt.Sprintf("%s:%s", a.Config.Producer, method),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	t, err := token.SignedString([]byte(a.Config.Key))

	a.Log.Debugf("JWT: %v", t)
	ts := CustomRes{Token: t}
	tjs, err := json.Marshal(ts)
	traw := json.RawMessage(tjs)
	return &traw, err

}

// -----------------------------------------------------------------------------

// Parse - parses JWT
func (a *App) Parse(s string) (*Session, error) {

	// to the callback, providing flexibility.
	token, err := jwt.ParseWithClaims(s, &SessionSlice{}, func(token *jwt.Token) (interface{}, error) {
		// Don't forget to validate the alg is what you expect:
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}

		return []byte(a.Config.Key), nil
	})
	if err != nil {
		return nil, err
	}

	if claims, ok := token.Claims.(*SessionSlice); ok && token.Valid {
		a.Log.Debugf("Got JWT session: %+v (%+v)", claims, token.Header)

		if !stringExists(a.Config.Issuers, claims.Issuer, "*") {
			a.Log.Debugf("Error: Issuer %s not in allowed list (%+v)", claims.Issuer, a.Config.Issuers)
			return nil, fmt.Errorf("Incorrect JWT Issuer %s", claims.Issuer)
		}

		return &claims.Data[0], nil
	}
	return nil, fmt.Errorf("Invalid JWT")

}

// -----------------------------------------------------------------------------

// Check if str or any exists in strings slice
func stringExists(strings []string, str string, any string) bool {
	if len(strings) > 0 { // lookup if host is allowed
		for _, s := range strings {
			if str == s || (any != "" && s == any) {
				return true
			}
		}
	}
	return false
}
<|endoftext|>