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

package util

import (
	"bytes"
	"fmt"
	"regexp"
	"strings"
	"sync"

	"github.com/square/metrics/api"
	"github.com/square/metrics/log"
	"gopkg.in/yaml.v2"
)

var defaultRegex = "[^.]+"

// RawRule is the input provided by the YAML file to specify the rul.
type RawRule struct {
	Pattern          string            `yaml:"pattern"`
	MetricKeyPattern string            `yaml:"metric_key"`
	Regex            map[string]string `yaml:"regex,omitempty"`
	DoNotMatch       map[string]string `yaml:"do_not_match,omitempty"`
}

// RawRules is list of RawRule
type RawRules struct {
	RawRules []RawRule `yaml:"rules"`
}

// Rule is a sanitized version of RawRule. Only valid rules
// can be converted to Rule.
type Rule struct {
	raw                  RawRule
	graphitePatternRegex *regexp.Regexp
	MetricKeyRegex       *regexp.Regexp
	doNotMatch           map[string]*regexp.Regexp
	graphitePatternTags  []string // tags extracted from the raw graphite string, in the order of appearance.
	metricKeyTags        []string // tags extracted from MetricKey, in the order of appearance.
	Statistics           RuleStatistics
	file                 string // for diagnostic messages, the location of the rule's file
}

func (rule Rule) Description() string {
	return fmt.Sprintf("%s\n\t=> %s\n\tfrom %s", rule.raw.Pattern, rule.raw.MetricKeyPattern, rule.file)
}

type RuleStatistics struct {
	mutex             *sync.Mutex
	statisticsEnabled bool
	Matches           int
	SuccessfulMatches []string
}

// RuleSet is a sanitized version of RawRules.
// Rules are matched sequentially until a correct one is matched.
type RuleSet struct {
	Rules             []Rule
	statisticsEnabled bool
}

// Compile a given RawRule into a regex and exposed tagset.
// error is an instance of RuleError.
func Compile(rule RawRule) (Rule, error) {
	if len(rule.MetricKeyPattern) == 0 {
		return Rule{}, newInvalidMetricKey(rule.MetricKeyPattern)
	}
	graphitePatternTags := extractTags(rule.Pattern)
	if graphitePatternTags == nil {
		return Rule{}, newInvalidPattern(rule.MetricKeyPattern)
	}
	metricKeyTags := extractTags(string(rule.MetricKeyPattern))
	if !isSubset(metricKeyTags, graphitePatternTags) {
		return Rule{}, newInvalidMetricKey(rule.MetricKeyPattern)
	}
	if metricKeyTags == nil {
		return Rule{}, newInvalidMetricKey(rule.MetricKeyPattern)
	}
	if !rule.checkTagRegexes() {
		return Rule{}, newInvalidCustomRegex(rule.MetricKeyPattern)
	}
	regex := rule.toRegexp(rule.Pattern)
	if regex == nil {
		return Rule{}, newInvalidPattern(rule.MetricKeyPattern)
	}
	if regex.NumSubexp() != len(graphitePatternTags) {
		return Rule{}, newInvalidCustomRegex(rule.MetricKeyPattern)
	}
	metricKeyRegex := rule.toRegexp(rule.MetricKeyPattern)
	if metricKeyRegex == nil {
		return Rule{}, newInvalidPattern(rule.MetricKeyPattern)
	}
	if metricKeyRegex.NumSubexp() != len(metricKeyTags) {
		return Rule{}, newInvalidCustomRegex(rule.MetricKeyPattern)
	}

	doNotMatch := map[string]*regexp.Regexp{}
	for key, avoid := range rule.DoNotMatch {
		compiled, err := regexp.Compile(avoid)
		if err != nil {
			return Rule{}, newInvalidCustomRegex(avoid)
		}
		doNotMatch[key] = compiled
	}

	stats := RuleStatistics{}
	stats.mutex = &sync.Mutex{}

	return Rule{
		raw:                  rule,
		graphitePatternRegex: regex,
		MetricKeyRegex:       metricKeyRegex,
		graphitePatternTags:  graphitePatternTags,
		doNotMatch:           doNotMatch,
		metricKeyTags:        metricKeyTags,
		Statistics:           stats,
	}, nil
}

func (rule *Rule) AddMatch(matchedResult string) {
	rule.Statistics.AddMatch(matchedResult)
}

func (ruleStat *RuleStatistics) AddMatch(matchedResult string) {
	if !ruleStat.statisticsEnabled {
		return
	}
	ruleStat.mutex.Lock()
	defer ruleStat.mutex.Unlock()
	ruleStat.Matches++
	ruleStat.SuccessfulMatches = append(ruleStat.SuccessfulMatches, matchedResult)
}

// MatchRule sees if a given graphite string matches the rule, and if so, returns the generated tag.
func (rule *Rule) MatchRule(input string) (api.TaggedMetric, bool) {
	if strings.Contains(input, "\x00") {
		log.Errorf("MatchRule (graphite string => metric name) has been given bad metric: `%s`", input)
	}
	tagSet := extractTagValues(rule.graphitePatternRegex, rule.graphitePatternTags, input)
	if tagSet == nil {
		return api.TaggedMetric{}, false
	}
	for key, value := range tagSet {
		// If the 'doNotMatch' field has been set for this tag, but it matches- reject the conversion.
		if rule.doNotMatch[key] != nil && rule.doNotMatch[key].MatchString(value) {
			return api.TaggedMetric{}, false
		}
	}
	interpolatedKey, err := interpolateTags(rule.raw.MetricKeyPattern, tagSet, false)
	if err != nil {
		return api.TaggedMetric{}, false
	}
	// Do not output tags appearing in both graphite metric & metric key.
	// for exmaple, if graphite metric is
	//   `foo.%a%.%b%`
	// and metric key is
	//   `bar.%b%`
	// the resulting tag set should only contain {a} after the matching
	// because %b% is already encoded.
	for _, metricKeyTag := range rule.metricKeyTags {
		if _, containsKey := tagSet[metricKeyTag]; containsKey {
			delete(tagSet, metricKeyTag)
		}
	}
	rule.AddMatch(input)
	if strings.Contains(interpolatedKey, "\x00") {
		log.Errorf("MatchRule (graphite string => metric name) is returning bad metric: `%s` from input `%s`", interpolatedKey, input)
	}
	return api.TaggedMetric{
		api.MetricKey(interpolatedKey),
		tagSet,
	}, true
}

// ToGraphiteName transforms the given tagged metric back to its graphite metric.
func (rule Rule) ToGraphiteName(taggedMetric api.TaggedMetric) (GraphiteMetric, error) {
	extractedTagSet := extractTagValues(rule.MetricKeyRegex, rule.metricKeyTags, string(taggedMetric.MetricKey))
	if extractedTagSet == nil {
		// no match found. not a correct rule to interpolate.
		return "", newCannotInterpolate(taggedMetric)
	}
	// Merge the tags in the provided tag set, and tags extracted from the metric.
	// This is necessary because tags embedded in the metric are not
	// exported to the tagset.
	mergedTagSet := taggedMetric.TagSet.Merge(extractedTagSet)
	interpolated, err := interpolateTags(rule.raw.Pattern, mergedTagSet, true)
	if err != nil {
		return "", err
	}
	return GraphiteMetric(interpolated), nil
}

// MatchRule sees if a given graphite string matches
// any of the specified rules.
func (ruleSet *RuleSet) MatchRule(input string) (api.TaggedMetric, bool) {
	for i := 0; i < len(ruleSet.Rules); i++ {
		value, matched := ruleSet.Rules[i].MatchRule(input)
		if matched {
			return value, matched
		}
	}
	return api.TaggedMetric{}, false
}

func (rule *Rule) EnableStats() {
	rule.Statistics.statisticsEnabled = true
}

func (rule *Rule) DisableStats() {
	rule.Statistics.statisticsEnabled = false
}

func (ruleSet *RuleSet) EnableStats() {
	for i := 0; i < len(ruleSet.Rules); i++ {
		ruleSet.Rules[i].EnableStats()
	}
	ruleSet.statisticsEnabled = true
}

func (ruleSet *RuleSet) DisableStats() {
	for i := 0; i < len(ruleSet.Rules); i++ {
		ruleSet.Rules[i].DisableStats()
	}
	ruleSet.statisticsEnabled = false
}

// GraphitePatternTags return a list of tags available in the original metric.
func (rule Rule) GraphitePatternTags() []string {
	return rule.graphitePatternTags
}

// ToGraphiteName transforms the given tagged metric back to its graphite name,
// checking against all the rules.
func (ruleSet RuleSet) ToGraphiteName(taggedMetric api.TaggedMetric) (GraphiteMetric, error) {
	for _, rule := range ruleSet.Rules {
		reversed, err := rule.ToGraphiteName(taggedMetric)
		if err == nil {
			return reversed, nil
		}
	}
	return "", newCannotInterpolate(taggedMetric)
}

// checkTagRegexes sees if any of the custom regular expressions are invalid.
func (rule RawRule) checkTagRegexes() bool {
	for _, regex := range rule.Regex {
		compiled, err := regexp.Compile(regex)
		if err != nil {
			return false
		}
		if compiled.NumSubexp() > 0 {
			return false // do not allow subexpressions.
		}
	}
	return true
}

func (rule RawRule) toRegexp(pattern string) *regexp.Regexp {
	splitted := strings.Split(pattern, "%")
	buffer := new(bytes.Buffer)
	if len(splitted)%2 == 0 {
		// invalid pattern - even number of parts mean odd number of %.
		return nil
	}
	buffer.WriteString("^")
	for index, token := range splitted {
		if isTagPortion(index) {
			regex, contains := rule.Regex[token]
			if !contains {
				// use the defuault regex
				regex = defaultRegex
			}
			// wrap the regex in parenthesis, so that it can be matched.
			// if the regex contains matching groups, bad things will happen.
			buffer.WriteString("(" + regex + ")")
		} else {
			buffer.WriteString(regexp.QuoteMeta(token))
		}
	}
	buffer.WriteString("$")
	compiled, err := regexp.Compile(buffer.String())
	if err != nil {
		return nil
	}
	return compiled
}

// extractTagValues extracts the tagset using the given regex and the list of tags.
func extractTagValues(regex *regexp.Regexp, tagList []string, input string) api.TagSet {
	matches := regex.FindStringSubmatch(input)
	if matches == nil {
		return nil
	}
	tagSet := api.NewTagSet()
	for index, tagValue := range matches {
		if index == 0 {
			continue
		}
		tagKey := tagList[index-1]
		tagSet[tagKey] = tagValue
	}
	return tagSet
}

// extractTags extracts list of tags in the given pattern string.
func extractTags(pattern string) []string {
	if len(pattern) == 0 {
		return nil // empty pattern is not allowed.
	}
	splitted := strings.Split(pattern, "%")
	if len(splitted)%2 == 0 {
		return nil // invalid tags.
	}

	result := make([]string, len(splitted)/2, len(splitted)/2)
	for index, token := range splitted {
		if isTagPortion(index) {
			if len(token) == 0 {
				return nil // no empty tag
			}
			result[index/2] = token
		}
	}

	// check for duplicates.
	exists := make(map[string]bool)
	for _, token := range result {
		if exists[token] {
			return nil // no duplicate
		}
		exists[token] = true
	}
	return result
}

// LoadYAML loads a RuleSet from the byte array of the YAML file.
// error is an interface of RuleError.
func LoadYAML(input []byte) (RuleSet, error) {
	rawRules := RawRules{}
	if err := yaml.Unmarshal(input, &rawRules); err != nil {
		return RuleSet{}, ruleError{
			code:    InvalidYaml,
			message: err.Error(),
		}
	}
	rules := make([]Rule, len(rawRules.RawRules))
	for index, rawRule := range rawRules.RawRules {
		rule, err := Compile(rawRule)
		if err != nil {
			return RuleSet{}, err
		}
		rules[index] = rule
	}
	return RuleSet{
		Rules:             rules,
		statisticsEnabled: false,
	}, nil
}

// check if setA is subset of setB.
func isSubset(setA, setB []string) bool {
	set := make(map[string]bool)
	for _, v := range setB {
		set[v] = true
	}
	for _, v := range setA {
		if !set[v] {
			return false
		}
	}
	return true
}

func isTagPortion(index int) bool {
	return index%2 == 1
}

func interpolateTags(pattern string, tagSet api.TagSet, enforceAllTagsUsed bool) (string, error) {

	usedTags := make(map[string]bool)

	splitted := strings.Split(pattern, "%")
	buffer := new(bytes.Buffer)
	for index, token := range splitted {
		if isTagPortion(index) {
			usedTags[token] = true
			tagValue, hasTag := tagSet[token]
			if hasTag {
				buffer.WriteString(tagValue)
			} else {
				return "", newMissingTag(token)
			}
		} else {
			buffer.WriteString(token)
		}
	}

	if enforceAllTagsUsed {
		for key := range tagSet {
			if !usedTags[key] {
				return "", newUnusedTag(key)
			}
		}
	}

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

package util

import (
	"bytes"
	"fmt"
	"regexp"
	"strings"
	"sync"

	"github.com/square/metrics/api"
	"github.com/square/metrics/log"
	"gopkg.in/yaml.v2"
)

var defaultRegex = "[^.]+"

// RawRule is the input provided by the YAML file to specify the rul.
type RawRule struct {
	Pattern          string            `yaml:"pattern"`
	MetricKeyPattern string            `yaml:"metric_key"`
	Regex            map[string]string `yaml:"regex,omitempty"`
	DoNotMatch       map[string]string `yaml:"do_not_match,omitempty"`
}

// RawRules is list of RawRule
type RawRules struct {
	RawRules []RawRule `yaml:"rules"`
}

// Rule is a sanitized version of RawRule. Only valid rules
// can be converted to Rule.
type Rule struct {
	raw                  RawRule
	graphitePatternRegex *regexp.Regexp
	MetricKeyRegex       *regexp.Regexp
	doNotMatch           map[string]*regexp.Regexp
	graphitePatternTags  []string // tags extracted from the raw graphite string, in the order of appearance.
	metricKeyTags        []string // tags extracted from MetricKey, in the order of appearance.
	Statistics           RuleStatistics
	file                 string // for diagnostic messages, the location of the rule's file
}

func (rule Rule) Description() string {
	return fmt.Sprintf("%s\n\t=> %s\n\tfrom %s", rule.raw.Pattern, rule.raw.MetricKeyPattern, rule.file)
}

type RuleStatistics struct {
	mutex             *sync.Mutex
	statisticsEnabled bool
	Matches           int
	SuccessfulMatches []string
}

// RuleSet is a sanitized version of RawRules.
// Rules are matched sequentially until a correct one is matched.
type RuleSet struct {
	Rules             []Rule
	statisticsEnabled bool
}

// Compile a given RawRule into a regex and exposed tagset.
// error is an instance of RuleError.
func Compile(rule RawRule) (Rule, error) {
	if len(rule.MetricKeyPattern) == 0 {
		return Rule{}, newInvalidMetricKey(rule.MetricKeyPattern)
	}
	graphitePatternTags := extractTags(rule.Pattern)
	if graphitePatternTags == nil {
		return Rule{}, newInvalidPattern(rule.MetricKeyPattern)
	}
	metricKeyTags := extractTags(string(rule.MetricKeyPattern))
	if !isSubset(metricKeyTags, graphitePatternTags) {
		return Rule{}, newInvalidMetricKey(rule.MetricKeyPattern)
	}
	if metricKeyTags == nil {
		return Rule{}, newInvalidMetricKey(rule.MetricKeyPattern)
	}
	if !rule.checkTagRegexes() {
		return Rule{}, newInvalidCustomRegex(rule.MetricKeyPattern)
	}
	regex := rule.toRegexp(rule.Pattern)
	if regex == nil {
		return Rule{}, newInvalidPattern(rule.MetricKeyPattern)
	}
	if regex.NumSubexp() != len(graphitePatternTags) {
		return Rule{}, newInvalidCustomRegex(rule.MetricKeyPattern)
	}
	metricKeyRegex := rule.toRegexp(rule.MetricKeyPattern)
	if metricKeyRegex == nil {
		return Rule{}, newInvalidPattern(rule.MetricKeyPattern)
	}
	if metricKeyRegex.NumSubexp() != len(metricKeyTags) {
		return Rule{}, newInvalidCustomRegex(rule.MetricKeyPattern)
	}

	doNotMatch := map[string]*regexp.Regexp{}
	for key, avoid := range rule.DoNotMatch {
		compiled, err := regexp.Compile(avoid)
		if err != nil {
			return Rule{}, newInvalidCustomRegex(avoid)
		}
		doNotMatch[key] = compiled
	}

	stats := RuleStatistics{}
	stats.mutex = &sync.Mutex{}

	return Rule{
		raw:                  rule,
		graphitePatternRegex: regex,
		MetricKeyRegex:       metricKeyRegex,
		graphitePatternTags:  graphitePatternTags,
		doNotMatch:           doNotMatch,
		metricKeyTags:        metricKeyTags,
		Statistics:           stats,
	}, nil
}

func (rule *Rule) AddMatch(matchedResult string) {
	rule.Statistics.AddMatch(matchedResult)
}

func (ruleStat *RuleStatistics) AddMatch(matchedResult string) {
	if !ruleStat.statisticsEnabled {
		return
	}
	ruleStat.mutex.Lock()
	defer ruleStat.mutex.Unlock()
	ruleStat.Matches++
	ruleStat.SuccessfulMatches = append(ruleStat.SuccessfulMatches, matchedResult)
}

// MatchRule sees if a given graphite string matches the rule, and if so, returns the generated tag.
func (rule *Rule) MatchRule(input string) (api.TaggedMetric, bool) {
	if strings.Contains(input, "\x00") {
		log.Errorf("MatchRule (graphite string => metric name) has been given bad metric: `%s`", input)
	}
	tagSet := extractTagValues(rule.graphitePatternRegex, rule.graphitePatternTags, input)
	if tagSet == nil {
		return api.TaggedMetric{}, false
	}
	for key, value := range tagSet {
		// If the 'doNotMatch' field has been set for this tag, but it matches- reject the conversion.
		if rule.doNotMatch[key] != nil && rule.doNotMatch[key].MatchString(value) {
			return api.TaggedMetric{}, false
		}
	}
	interpolatedKey, err := interpolateTags(rule.raw.MetricKeyPattern, tagSet, false)
	if err != nil {
		return api.TaggedMetric{}, false
	}
	// Do not output tags appearing in both graphite metric & metric key.
	// for exmaple, if graphite metric is
	//   `foo.%a%.%b%`
	// and metric key is
	//   `bar.%b%`
	// the resulting tag set should only contain {a} after the matching
	// because %b% is already encoded.
	for _, metricKeyTag := range rule.metricKeyTags {
		if _, containsKey := tagSet[metricKeyTag]; containsKey {
			delete(tagSet, metricKeyTag)
		}
	}
	rule.AddMatch(input)
	if strings.Contains(interpolatedKey, "\x00") {
		log.Errorf("MatchRule (graphite string => metric name) is returning bad metric: `%s` from input `%s`", interpolatedKey, input)
	}
	return api.TaggedMetric{
		api.MetricKey(interpolatedKey),
		tagSet,
	}, true
}

// ToGraphiteName transforms the given tagged metric back to its graphite metric.
func (rule Rule) ToGraphiteName(taggedMetric api.TaggedMetric) (GraphiteMetric, error) {
	extractedTagSet := extractTagValues(rule.MetricKeyRegex, rule.metricKeyTags, string(taggedMetric.MetricKey))
	if extractedTagSet == nil {
		// no match found. not a correct rule to interpolate.
		return "", newCannotInterpolate(taggedMetric)
	}
	// Merge the tags in the provided tag set, and tags extracted from the metric.
	// This is necessary because tags embedded in the metric are not
	// exported to the tagset.
	mergedTagSet := taggedMetric.TagSet.Merge(extractedTagSet)
	interpolated, err := interpolateTags(rule.raw.Pattern, mergedTagSet, true)
	if err != nil {
		return "", err
	}
	for key, regex := range rule.doNotMatch {
		if value, ok := taggedMetric.TagSet[key]; !ok || regex.MatchString(value) {
			return "", newCannotInterpolate("Cannot reverse if matching do-not-match")
		}
	}

	return GraphiteMetric(interpolated), nil
}

// MatchRule sees if a given graphite string matches
// any of the specified rules.
func (ruleSet *RuleSet) MatchRule(input string) (api.TaggedMetric, bool) {
	for i := 0; i < len(ruleSet.Rules); i++ {
		value, matched := ruleSet.Rules[i].MatchRule(input)
		if matched {
			return value, matched
		}
	}
	return api.TaggedMetric{}, false
}

func (rule *Rule) EnableStats() {
	rule.Statistics.statisticsEnabled = true
}

func (rule *Rule) DisableStats() {
	rule.Statistics.statisticsEnabled = false
}

func (ruleSet *RuleSet) EnableStats() {
	for i := 0; i < len(ruleSet.Rules); i++ {
		ruleSet.Rules[i].EnableStats()
	}
	ruleSet.statisticsEnabled = true
}

func (ruleSet *RuleSet) DisableStats() {
	for i := 0; i < len(ruleSet.Rules); i++ {
		ruleSet.Rules[i].DisableStats()
	}
	ruleSet.statisticsEnabled = false
}

// GraphitePatternTags return a list of tags available in the original metric.
func (rule Rule) GraphitePatternTags() []string {
	return rule.graphitePatternTags
}

// ToGraphiteName transforms the given tagged metric back to its graphite name,
// checking against all the rules.
func (ruleSet RuleSet) ToGraphiteName(taggedMetric api.TaggedMetric) (GraphiteMetric, error) {
	for _, rule := range ruleSet.Rules {
		reversed, err := rule.ToGraphiteName(taggedMetric)
		if err == nil {
			return reversed, nil
		}
	}
	return "", newCannotInterpolate(taggedMetric)
}

// checkTagRegexes sees if any of the custom regular expressions are invalid.
func (rule RawRule) checkTagRegexes() bool {
	for _, regex := range rule.Regex {
		compiled, err := regexp.Compile(regex)
		if err != nil {
			return false
		}
		if compiled.NumSubexp() > 0 {
			return false // do not allow subexpressions.
		}
	}
	return true
}

func (rule RawRule) toRegexp(pattern string) *regexp.Regexp {
	splitted := strings.Split(pattern, "%")
	buffer := new(bytes.Buffer)
	if len(splitted)%2 == 0 {
		// invalid pattern - even number of parts mean odd number of %.
		return nil
	}
	buffer.WriteString("^")
	for index, token := range splitted {
		if isTagPortion(index) {
			regex, contains := rule.Regex[token]
			if !contains {
				// use the defuault regex
				regex = defaultRegex
			}
			// wrap the regex in parenthesis, so that it can be matched.
			// if the regex contains matching groups, bad things will happen.
			buffer.WriteString("(" + regex + ")")
		} else {
			buffer.WriteString(regexp.QuoteMeta(token))
		}
	}
	buffer.WriteString("$")
	compiled, err := regexp.Compile(buffer.String())
	if err != nil {
		return nil
	}
	return compiled
}

// extractTagValues extracts the tagset using the given regex and the list of tags.
func extractTagValues(regex *regexp.Regexp, tagList []string, input string) api.TagSet {
	matches := regex.FindStringSubmatch(input)
	if matches == nil {
		return nil
	}
	tagSet := api.NewTagSet()
	for index, tagValue := range matches {
		if index == 0 {
			continue
		}
		tagKey := tagList[index-1]
		tagSet[tagKey] = tagValue
	}
	return tagSet
}

// extractTags extracts list of tags in the given pattern string.
func extractTags(pattern string) []string {
	if len(pattern) == 0 {
		return nil // empty pattern is not allowed.
	}
	splitted := strings.Split(pattern, "%")
	if len(splitted)%2 == 0 {
		return nil // invalid tags.
	}

	result := make([]string, len(splitted)/2, len(splitted)/2)
	for index, token := range splitted {
		if isTagPortion(index) {
			if len(token) == 0 {
				return nil // no empty tag
			}
			result[index/2] = token
		}
	}

	// check for duplicates.
	exists := make(map[string]bool)
	for _, token := range result {
		if exists[token] {
			return nil // no duplicate
		}
		exists[token] = true
	}
	return result
}

// LoadYAML loads a RuleSet from the byte array of the YAML file.
// error is an interface of RuleError.
func LoadYAML(input []byte) (RuleSet, error) {
	rawRules := RawRules{}
	if err := yaml.Unmarshal(input, &rawRules); err != nil {
		return RuleSet{}, ruleError{
			code:    InvalidYaml,
			message: err.Error(),
		}
	}
	rules := make([]Rule, len(rawRules.RawRules))
	for index, rawRule := range rawRules.RawRules {
		rule, err := Compile(rawRule)
		if err != nil {
			return RuleSet{}, err
		}
		rules[index] = rule
	}
	return RuleSet{
		Rules:             rules,
		statisticsEnabled: false,
	}, nil
}

// check if setA is subset of setB.
func isSubset(setA, setB []string) bool {
	set := make(map[string]bool)
	for _, v := range setB {
		set[v] = true
	}
	for _, v := range setA {
		if !set[v] {
			return false
		}
	}
	return true
}

func isTagPortion(index int) bool {
	return index%2 == 1
}

func interpolateTags(pattern string, tagSet api.TagSet, enforceAllTagsUsed bool) (string, error) {

	usedTags := make(map[string]bool)

	splitted := strings.Split(pattern, "%")
	buffer := new(bytes.Buffer)
	for index, token := range splitted {
		if isTagPortion(index) {
			usedTags[token] = true
			tagValue, hasTag := tagSet[token]
			if hasTag {
				buffer.WriteString(tagValue)
			} else {
				return "", newMissingTag(token)
			}
		} else {
			buffer.WriteString(token)
		}
	}

	if enforceAllTagsUsed {
		for key := range tagSet {
			if !usedTags[key] {
				return "", newUnusedTag(key)
			}
		}
	}

	return buffer.String(), nil
}
<|endoftext|>


<commit_before>package core_test

import (
	"testing"

	. "v2ray.com/core"
	"v2ray.com/core/app/proxyman"
	"v2ray.com/core/common/dice"
	"v2ray.com/core/common/net"
	"v2ray.com/core/common/protocol"
	"v2ray.com/core/common/serial"
	"v2ray.com/core/common/uuid"
	_ "v2ray.com/core/main/distro/all"
	"v2ray.com/core/proxy/dokodemo"
	"v2ray.com/core/proxy/vmess"
	"v2ray.com/core/proxy/vmess/outbound"
	. "v2ray.com/ext/assert"
)

func TestV2RayClose(t *testing.T) {
	assert := With(t)

	port := net.Port(dice.RollUint16())
	config := &Config{
		Inbound: []*core.InboundHandlerConfig{
			{
				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
					PortRange: net.SinglePortRange(port),
					Listen:    net.NewIPOrDomain(net.LocalHostIP),
				}),
				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
					Address: net.NewIPOrDomain(net.LocalHostIP),
					Port:    uint32(0),
					NetworkList: &net.NetworkList{
						Network: []net.Network{net.Network_TCP, net.Network_UDP},
					},
				}),
			},
		},
		Outbound: []*core.OutboundHandlerConfig{
			{
				ProxySettings: serial.ToTypedMessage(&outbound.Config{
					Receiver: []*protocol.ServerEndpoint{
						{
							Address: net.NewIPOrDomain(net.LocalHostIP),
							Port:    uint32(0),
							User: []*protocol.User{
								{
									Account: serial.ToTypedMessage(&vmess.Account{
										Id: uuid.New().String(),
									}),
								},
							},
						},
					},
				}),
			},
		},
	}

	server, err := New(config)
	assert(err, IsNil)

	server.Close()
}
<commit_msg>fix test break<commit_after>package core_test

import (
	"testing"

	. "v2ray.com/core"
	"v2ray.com/core/app/proxyman"
	"v2ray.com/core/common/dice"
	"v2ray.com/core/common/net"
	"v2ray.com/core/common/protocol"
	"v2ray.com/core/common/serial"
	"v2ray.com/core/common/uuid"
	_ "v2ray.com/core/main/distro/all"
	"v2ray.com/core/proxy/dokodemo"
	"v2ray.com/core/proxy/vmess"
	"v2ray.com/core/proxy/vmess/outbound"
	. "v2ray.com/ext/assert"
)

func TestV2RayClose(t *testing.T) {
	assert := With(t)

	port := net.Port(dice.RollUint16())
	config := &Config{
		App: []*serial.TypedMessage{
			serial.ToTypedMessage(&proxyman.InboundConfig{}),
			serial.ToTypedMessage(&proxyman.OutboundConfig{}),
		},
		Inbound: []*InboundHandlerConfig{
			{
				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
					PortRange: net.SinglePortRange(port),
					Listen:    net.NewIPOrDomain(net.LocalHostIP),
				}),
				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
					Address: net.NewIPOrDomain(net.LocalHostIP),
					Port:    uint32(0),
					NetworkList: &net.NetworkList{
						Network: []net.Network{net.Network_TCP, net.Network_UDP},
					},
				}),
			},
		},
		Outbound: []*OutboundHandlerConfig{
			{
				ProxySettings: serial.ToTypedMessage(&outbound.Config{
					Receiver: []*protocol.ServerEndpoint{
						{
							Address: net.NewIPOrDomain(net.LocalHostIP),
							Port:    uint32(0),
							User: []*protocol.User{
								{
									Account: serial.ToTypedMessage(&vmess.Account{
										Id: uuid.New().String(),
									}),
								},
							},
						},
					},
				}),
			},
		},
	}

	server, err := New(config)
	assert(err, IsNil)

	server.Close()
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"github.com/fsouza/go-dockerclient"
	"github.com/gorilla/mux"
	"net/http"
)

type MyServer struct {
	r *mux.Router
}

var endpoint = "unix:///var/run/docker.sock"
var client, _ = docker.NewClient(endpoint)

//wrap server handler function to activate CORS
func (s *MyServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	//if origin := req.Header.Get("Origin"); origin != "" {
	rw.Header().Set("Content-Type", "application/json, text/html")
	rw.Header().Set("Accept", "application/json")
	rw.Header().Set("Accept-Charset", "utf-8")
	rw.Header().Set("Access-Control-Allow-Credentials", "true")
	rw.Header().Set("Access-Control-Allow-Origin", "*")
	rw.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
	rw.Header().Set("Access-Control-Allow-Headers",
		"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
	//}
	// Stop here if its Preflighted OPTIONS request
	if req.Method == "OPTIONS" {
		return
	}
	//the real library handler
	s.r.ServeHTTP(rw, req)
}

func main() {
	ip := ""
	port := "7777"
	path := "./static/"

	receive := ip + ":" + port
	fmt.Println("server listen on : %s\n", receive)

	r := mux.NewRouter()
	// r.HandleFunc("/", HelloHandler)
	//serv staticly index.html
	fs := http.FileServer(http.Dir(path))
	r.Handle("/", fs)
	r.HandleFunc("/containers", getContainerHandler).Methods("GET")

	http.Handle("/", &MyServer{r})

	http.ListenAndServe(receive, nil)
}

func getContainerHandler(w http.ResponseWriter, req *http.Request) {
	fmt.Println("on fait un gros GET")

	containers, _ := client.ListContainers(docker.ListContainersOptions{All: false})
	b, err := json.Marshal(containers)
	if err != nil {
		http.Error(w, err.Error(), 500)
	}
	fmt.Fprintf(w, "%s", b)

}

}
<commit_msg>adding handler for new node<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"github.com/fsouza/go-dockerclient"
	"github.com/gorilla/mux"
	"net/http"
)

type MyServer struct {
	r *mux.Router
}

var endpoint = "unix:///var/run/docker.sock"
var client, _ = docker.NewClient(endpoint)

//wrap server handler function to activate CORS
func (s *MyServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	//if origin := req.Header.Get("Origin"); origin != "" {
	rw.Header().Set("Content-Type", "application/json, text/html")
	rw.Header().Set("Accept", "application/json")
	rw.Header().Set("Accept-Charset", "utf-8")
	rw.Header().Set("Access-Control-Allow-Credentials", "true")
	rw.Header().Set("Access-Control-Allow-Origin", "*")
	rw.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
	rw.Header().Set("Access-Control-Allow-Headers",
		"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
	//}
	// Stop here if its Preflighted OPTIONS request
	if req.Method == "OPTIONS" {
		return
	}
	//the real library handler
	s.r.ServeHTTP(rw, req)
}

func main() {
	ip := ""
	port := "7777"
	path := "./static/"

	receive := ip + ":" + port
	fmt.Println("server listen on : %s\n", receive)

	r := mux.NewRouter()
	// r.HandleFunc("/", HelloHandler)
	//serv staticly index.html
	fs := http.FileServer(http.Dir(path))
	r.Handle("/", fs)
	r.HandleFunc("/containers", getContainerHandler).Methods("GET")
	r.HandleFunc("/containers", createContainerHandler).Methods("Post")

	http.Handle("/", &MyServer{r})

	http.ListenAndServe(receive, nil)
}

func getContainerHandler(w http.ResponseWriter, req *http.Request) {
	fmt.Println("on fait un gros GET")

	containers, _ := client.ListContainers(docker.ListContainersOptions{All: false})
	b, err := json.Marshal(containers)
	if err != nil {
		http.Error(w, err.Error(), 500)
	}
	fmt.Fprintf(w, "%s", b)

}

func createContainerHandler(w http.ResponseWriter, req *http.Request) {
	fmt.Println("on fait un gros POST")
	id := "truc"
	opts := docker.CreateContainerOptions{
		Name: id,
		Config: &docker.Config{

			PortSpecs: []string{
				"4444:4321",
				"4444:4321/udp",
			},
			Cmd:   []string{"-s", "/static/"},
			Image: "tgermain/repo_sky:v2",
		},
	}
	container, err := client.CreateContainer(opts)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	fmt.Println("-------AVANT --------")
	fmt.Println("ID: ", container.ID)
	fmt.Println("Image: ", container.Image)
	fmt.Println("Created: ", container.Created)
	fmt.Println("hostConf: ", container.HostConfig)
	err2 := client.StartContainer(id, &docker.HostConfig{
		NetworkMode: "bridge",
		PortBindings: map[docker.Port][]docker.PortBinding{
			"4321/tcp": []docker.PortBinding{
				docker.PortBinding{
					HostPort: "5555",
				},
			},
			"4321/udp": []docker.PortBinding{
				docker.PortBinding{
					HostPort: "5555",
				},
			},
		},
	})
	if err2 != nil {
		http.Error(w, err2.Error(), 500)
		return
	}
	fmt.Println("-------Après --------")

	fmt.Println("ID: ", container.ID)
	fmt.Println("Image: ", container.Image)
	fmt.Println("Created: ", container.Created)
	fmt.Println("hostConf: ", container.HostConfig)
}
<|endoftext|>


<commit_before>package core

import (
	"flag"
	"fmt"
	"os"

	"github.com/hexbotio/hex/models"
)

func Params(config *models.Config) {

	// capture the flag
	configFile := flag.String("config", "", "Location of the config file")
	validate := flag.Bool("validate", false, "Validate the config file")
	showVersion := flag.Bool("version", false, "Version of HexBot")
	startup := flag.Bool("startup", false, "Startup file for HexBot")
	flag.Parse()

	// set the config values
	config.ConfigFile = *configFile
	config.Validate = *validate

	// operate on any flags
	if *showVersion {
		fmt.Print("HexBot " + config.Version + "\n")
		os.Exit(0)
	}

	// startup file
	if *startup {
		fmt.Print("\n")
		fmt.Print("1. Create file: /etc/systemd/system/hex.service\n")
		fmt.Print("--------------------------------\n")
		fmt.Print("[Unit]\n")
		fmt.Print("Description=HexBot\n")
		fmt.Print("\n")
		fmt.Print("[Service]\n")
		fmt.Print("Type=simple\n")
		fmt.Print("ExecStart=/usr/local/bin/hex\n")
		fmt.Print("Restart=on-failure\n")
		fmt.Print("--------------------------------\n")
		fmt.Print("\n")
		fmt.Print("2. Run: systemctl enable hex.service\n")
		fmt.Print("\n")
		os.Exit(0)
	}

}
<commit_msg>minor fix to systemd example<commit_after>package core

import (
	"flag"
	"fmt"
	"os"

	"github.com/hexbotio/hex/models"
)

func Params(config *models.Config) {

	// capture the flag
	configFile := flag.String("config", "", "Location of the config file")
	validate := flag.Bool("validate", false, "Validate the config file")
	showVersion := flag.Bool("version", false, "Version of HexBot")
	startup := flag.Bool("startup", false, "Startup file for HexBot")
	flag.Parse()

	// set the config values
	config.ConfigFile = *configFile
	config.Validate = *validate

	// operate on any flags
	if *showVersion {
		fmt.Print("HexBot " + config.Version + "\n")
		os.Exit(0)
	}

	// startup file
	if *startup {
		fmt.Print("\n")
		fmt.Print("1. Create file: /etc/systemd/system/hex.service\n")
		fmt.Print("--------------------------------\n")
		fmt.Print("[Unit]\n")
		fmt.Print("Description=HexBot\n")
		fmt.Print("\n")
		fmt.Print("[Service]\n")
		fmt.Print("Type=simple\n")
		fmt.Print("ExecStart=/usr/local/bin/hex\n")
		fmt.Print("Restart=on-failure\n")
		fmt.Print("\n")
		fmt.Print("[Install]\n")
		fmt.Print("WantedBy=multi-user.target\n")
		fmt.Print("--------------------------------\n")
		fmt.Print("\n")
		fmt.Print("2. Run: systemctl enable hex.service\n")
		fmt.Print("\n")
		os.Exit(0)
	}

}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Knative Authors

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 main

import (
	"fmt"
	"path/filepath"

	"github.com/google/go-github/github"
	"knative.dev/pkg/test/cmd"

	"knative.dev/test-infra/tools/prow-config-updater/config"
)

type Client struct {
	githubmainhandler *GitHubMainHandler
	githubcommenter   *GitHubCommenter
	forkOrgName       string
	pr                *github.PullRequest
	files             []string
	dryrun            bool
}

func (cli *Client) initialize() error {
	pr, err := cli.githubmainhandler.getLatestPullRequest()
	if err != nil {
		return fmt.Errorf("error getting the latest PR number: %v", err)
	}
	cli.pr = pr

	fs, err := cli.githubmainhandler.getChangedFiles(*pr.Number)
	if err != nil {
		return fmt.Errorf("error getting changed files in PR %q: %v", *pr.Number, err)
	}
	cli.files = fs

	return nil
}

func (cli *Client) runProwConfigUpdate() error {
	// If no staging process if needed, we can directly update the changed Prow configs.
	if !cli.needsStaging() {
		// Try to update both staging and production Prow.
		// If no config changes need to be updated, the function call will be just a no-op.
		for _, env := range []config.ProwEnv{config.StagingProwEnv, config.ProdProwEnv} {
			if err := cli.updateProw(env); err != nil {
				return fmt.Errorf("error updating Prow configs: %v", err)
			}
		}
	} else {
		if err := cli.startStaging(); err != nil {
			// Best effort, won't fail the process if the comment fails.
			cli.githubcommenter.commentOnStagingFailure(*cli.pr.Number, err)
			return fmt.Errorf("error running Prow staging process: %v", err)
		} else {
			// Best effort, won't fail the process if the comment fails.
			cli.githubcommenter.commentOnStagingSuccess(*cli.pr.Number)
		}

		newpr, err := cli.rollOutToProd()
		if err != nil {
			// Best effort, won't fail the process if the comment fails.
			cli.githubcommenter.commentOnRolloutFailure(*cli.pr.Number, err)
			return fmt.Errorf("error rolling out the staging change to production: %v", err)
		} else {
			// Best effort, won't fail the process if the comment fails.
			cli.githubcommenter.commentOnRolloutSuccess(*cli.pr.Number, *newpr.Number)
		}
	}
	return nil
}

// Decide if we need staging process by checking the PR.
func (cli *Client) needsStaging() bool {
	// If the PR is created by the main github bot, we should be confident to blindly update production Prow configs.
	if *cli.pr.User.Login == cli.githubmainhandler.info.UserID {
		return false
	}
	// If any key config files for staging Prow are changed, the staging process will be needed.
	fs := config.CollectRelevantConfigFiles(cli.files, config.StagingProwKeyConfigPaths)
	return len(fs) != 0
}

// Update Prow with the changed config files and send message for the update result.
// If no config changes need to be updated, this function call will be just a no-op.
func (cli *Client) updateProw(env config.ProwEnv) error {
	updatedFiles, err := cli.doProwUpdate(env)
	prnumber := *cli.pr.Number
	if err == nil {
		if len(updatedFiles) != 0 {
			// Best effort, won't fail the process if the comment fails.
			cli.githubcommenter.commentOnUpdateConfigsSuccess(prnumber, env, updatedFiles)
		}
	} else {
		// Best effort, won't fail the process if the comment fails.
		cli.githubcommenter.commentOnUpdateConfigsFailure(prnumber, env, updatedFiles, err)
	}
	return err
}

// Start running the staging process to update and test staging Prow.
func (cli *Client) startStaging() error {
	// Update staging Prow.
	if err := cli.updateProw(config.StagingProwEnv); err != nil {
		return fmt.Errorf("error updating staging Prow configs: %v", err)
	}
	// Create pull request in the fork repository for the testing of staging Prow.
	fpr, err := cli.githubmainhandler.createForkPullRequest(cli.forkOrgName)
	if err != nil {
		return fmt.Errorf("error creating pull request in the fork repository: %v", err)
	}
	// Create comment on the fork pull request to get it tested by staging Prow and merged.
	forkprnumber := *fpr.Number
	if err = cli.githubcommenter.mergeForkPullRequest(cli.forkOrgName, forkprnumber); err != nil {
		return fmt.Errorf("error creating comment on the fork pull request: %v", err)
	}
	// Wait for the fork pull request to be automatically merged by staging Prow.
	if err := cli.githubmainhandler.waitForForkPullRequestMerged(cli.forkOrgName, forkprnumber); err != nil {
		return fmt.Errorf("error waiting for the fork pull request to be merged: %v", err)
	}
	return nil
}

// Run the `make` command to update Prow configs.
// path is the Prow config root folder.
func (cli *Client) doProwUpdate(env config.ProwEnv) ([]string, error) {
	relevantFiles := make([]string, 0)
	switch env {
	case config.ProdProwEnv:
		relevantFiles = append(relevantFiles, config.CollectRelevantConfigFiles(cli.files, config.ProdProwConfigPaths)...)
	case config.StagingProwEnv:
		relevantFiles = append(relevantFiles, config.CollectRelevantConfigFiles(cli.files, config.StagingProwConfigPaths)...)
	default:
		return relevantFiles, fmt.Errorf("unsupported Prow environement: %q, cannot make the update", env)
	}
	if len(relevantFiles) != 0 {
		if err := config.UpdateProw(env, cli.dryrun); err != nil {
			return relevantFiles, fmt.Errorf("error updating Prow configs for %q environment: %v", env, err)
		}
	}

	// For production Prow, we also need to update Testgrid config if it's changed.
	tfs := config.CollectRelevantConfigFiles(cli.files, []string{config.ProdTestgridConfigPath})
	if len(tfs) != 0 {
		relevantFiles = append(relevantFiles, tfs...)
		if err := config.UpdateTestgrid(env, cli.dryrun); err != nil {
			return relevantFiles, fmt.Errorf("error updating Testgrid configs for %q environment: %v", env, err)
		}
	}
	return relevantFiles, nil
}

// Roll out the staging config files to production.
func (cli *Client) rollOutToProd() (*github.PullRequest, error) {
	// Copy staging config files to production.
	for i, stagingPath := range config.StagingProwKeyConfigPaths {
		// Get the absolute paths.
		// We do not need to check the error here as the unit tests can guarantee the paths exist.
		stagingPath, _ = filepath.Abs(stagingPath)
		prodPath, _ := filepath.Abs(config.ProdProwKeyConfigPaths[i])
		// The wildcard '*' needs to be expanded by the shell, so the cp command needs to be run in a shell process.
		cpCmd := fmt.Sprintf("/bin/bash -c 'cp -r %s/* %s'", stagingPath, prodPath)
		if _, err := cmd.RunCommand(cpCmd); err != nil {
			return nil, fmt.Errorf("error copying staging config files to production: %v", err)
		}
	}

	// Try generating new config files.
	if err := config.GenerateConfigFiles(); err != nil {
		return nil, fmt.Errorf("error generating Prow config files for production: %v", err)
	}

	// Create a pull request to update production Prow.
	commitMsg := fmt.Sprintf("[Auto]Roll out staging Prow change in #%d to production", *cli.pr.Number)
	body := fmt.Sprintf(
		"This is a PR auto-synced from #%d, it will be automatically merged after all tests pass.",
		*cli.pr.Number)
	pr, err := cli.githubmainhandler.createAutoMergePullRequest(commitMsg, body)
	if err != nil {
		return nil, fmt.Errorf("error creating pull request to roll out staging Prow to production: %v", err)
	}
	return pr, nil
}
<commit_msg>Mirror copy from staging to prod configs while auto update (#1997)<commit_after>/*
Copyright 2020 The Knative Authors

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 main

import (
	"errors"
	"fmt"
	"path/filepath"
	"strings"

	"github.com/google/go-github/github"
	"knative.dev/pkg/test/cmd"

	"knative.dev/test-infra/tools/prow-config-updater/config"
)

type Client struct {
	githubmainhandler *GitHubMainHandler
	githubcommenter   *GitHubCommenter
	forkOrgName       string
	pr                *github.PullRequest
	files             []string
	dryrun            bool
}

func (cli *Client) initialize() error {
	pr, err := cli.githubmainhandler.getLatestPullRequest()
	if err != nil {
		return fmt.Errorf("error getting the latest PR number: %v", err)
	}
	cli.pr = pr

	fs, err := cli.githubmainhandler.getChangedFiles(*pr.Number)
	if err != nil {
		return fmt.Errorf("error getting changed files in PR %q: %v", *pr.Number, err)
	}
	cli.files = fs

	return nil
}

func (cli *Client) runProwConfigUpdate() error {
	// If no staging process if needed, we can directly update the changed Prow configs.
	if !cli.needsStaging() {
		// Try to update both staging and production Prow.
		// If no config changes need to be updated, the function call will be just a no-op.
		for _, env := range []config.ProwEnv{config.StagingProwEnv, config.ProdProwEnv} {
			if err := cli.updateProw(env); err != nil {
				return fmt.Errorf("error updating Prow configs: %v", err)
			}
		}
	} else {
		if err := cli.startStaging(); err != nil {
			// Best effort, won't fail the process if the comment fails.
			cli.githubcommenter.commentOnStagingFailure(*cli.pr.Number, err)
			return fmt.Errorf("error running Prow staging process: %v", err)
		} else {
			// Best effort, won't fail the process if the comment fails.
			cli.githubcommenter.commentOnStagingSuccess(*cli.pr.Number)
		}

		newpr, err := cli.rollOutToProd()
		if err != nil {
			// Best effort, won't fail the process if the comment fails.
			cli.githubcommenter.commentOnRolloutFailure(*cli.pr.Number, err)
			return fmt.Errorf("error rolling out the staging change to production: %v", err)
		} else {
			// Best effort, won't fail the process if the comment fails.
			cli.githubcommenter.commentOnRolloutSuccess(*cli.pr.Number, *newpr.Number)
		}
	}
	return nil
}

// Decide if we need staging process by checking the PR.
func (cli *Client) needsStaging() bool {
	// If the PR is created by the main github bot, we should be confident to blindly update production Prow configs.
	if *cli.pr.User.Login == cli.githubmainhandler.info.UserID {
		return false
	}
	// If any key config files for staging Prow are changed, the staging process will be needed.
	fs := config.CollectRelevantConfigFiles(cli.files, config.StagingProwKeyConfigPaths)
	return len(fs) != 0
}

// Update Prow with the changed config files and send message for the update result.
// If no config changes need to be updated, this function call will be just a no-op.
func (cli *Client) updateProw(env config.ProwEnv) error {
	updatedFiles, err := cli.doProwUpdate(env)
	prnumber := *cli.pr.Number
	if err == nil {
		if len(updatedFiles) != 0 {
			// Best effort, won't fail the process if the comment fails.
			cli.githubcommenter.commentOnUpdateConfigsSuccess(prnumber, env, updatedFiles)
		}
	} else {
		// Best effort, won't fail the process if the comment fails.
		cli.githubcommenter.commentOnUpdateConfigsFailure(prnumber, env, updatedFiles, err)
	}
	return err
}

// Start running the staging process to update and test staging Prow.
func (cli *Client) startStaging() error {
	// Update staging Prow.
	if err := cli.updateProw(config.StagingProwEnv); err != nil {
		return fmt.Errorf("error updating staging Prow configs: %v", err)
	}
	// Create pull request in the fork repository for the testing of staging Prow.
	fpr, err := cli.githubmainhandler.createForkPullRequest(cli.forkOrgName)
	if err != nil {
		return fmt.Errorf("error creating pull request in the fork repository: %v", err)
	}
	// Create comment on the fork pull request to get it tested by staging Prow and merged.
	forkprnumber := *fpr.Number
	if err = cli.githubcommenter.mergeForkPullRequest(cli.forkOrgName, forkprnumber); err != nil {
		return fmt.Errorf("error creating comment on the fork pull request: %v", err)
	}
	// Wait for the fork pull request to be automatically merged by staging Prow.
	if err := cli.githubmainhandler.waitForForkPullRequestMerged(cli.forkOrgName, forkprnumber); err != nil {
		return fmt.Errorf("error waiting for the fork pull request to be merged: %v", err)
	}
	return nil
}

// Run the `make` command to update Prow configs.
// path is the Prow config root folder.
func (cli *Client) doProwUpdate(env config.ProwEnv) ([]string, error) {
	relevantFiles := make([]string, 0)
	switch env {
	case config.ProdProwEnv:
		relevantFiles = append(relevantFiles, config.CollectRelevantConfigFiles(cli.files, config.ProdProwConfigPaths)...)
	case config.StagingProwEnv:
		relevantFiles = append(relevantFiles, config.CollectRelevantConfigFiles(cli.files, config.StagingProwConfigPaths)...)
	default:
		return relevantFiles, fmt.Errorf("unsupported Prow environement: %q, cannot make the update", env)
	}
	if len(relevantFiles) != 0 {
		if err := config.UpdateProw(env, cli.dryrun); err != nil {
			return relevantFiles, fmt.Errorf("error updating Prow configs for %q environment: %v", env, err)
		}
	}

	// For production Prow, we also need to update Testgrid config if it's changed.
	tfs := config.CollectRelevantConfigFiles(cli.files, []string{config.ProdTestgridConfigPath})
	if len(tfs) != 0 {
		relevantFiles = append(relevantFiles, tfs...)
		if err := config.UpdateTestgrid(env, cli.dryrun); err != nil {
			return relevantFiles, fmt.Errorf("error updating Testgrid configs for %q environment: %v", env, err)
		}
	}
	return relevantFiles, nil
}

// Roll out the staging config files to production.
func (cli *Client) rollOutToProd() (*github.PullRequest, error) {
	// Copy staging config files to production.
	for i, stagingPath := range config.StagingProwKeyConfigPaths {
		// Get the absolute paths.
		// We do not need to check the error here as the unit tests can guarantee the paths exist.
		stagingPath, _ = filepath.Abs(stagingPath)
		prodPath, _ := filepath.Abs(config.ProdProwKeyConfigPaths[i])
		if strings.TrimSpace(prodPath) == "" {
			return nil, errors.New("prod path can't be empty")
		}
		// The wildcard '*' needs to be expanded by the shell, so the cp command
		// needs to be run in a shell process.
		syncCmds := []string{
			fmt.Sprintf("/bin/bash -c 'rm -rf %s/*'", prodPath),
			fmt.Sprintf("/bin/bash -c 'cp -r %s/* %s'", stagingPath, prodPath),
		}
		if _, err := cmd.RunCommands(syncCmds...); err != nil {
			return nil, fmt.Errorf("error copying staging config files to production: %v", err)
		}
	}

	// Try generating new config files.
	if err := config.GenerateConfigFiles(); err != nil {
		return nil, fmt.Errorf("error generating Prow config files for production: %v", err)
	}

	// Create a pull request to update production Prow.
	commitMsg := fmt.Sprintf("[Auto]Roll out staging Prow change in #%d to production", *cli.pr.Number)
	body := fmt.Sprintf(
		"This is a PR auto-synced from #%d, it will be automatically merged after all tests pass.",
		*cli.pr.Number)
	pr, err := cli.githubmainhandler.createAutoMergePullRequest(commitMsg, body)
	if err != nil {
		return nil, fmt.Errorf("error creating pull request to roll out staging Prow to production: %v", err)
	}
	return pr, nil
}
<|endoftext|>


<commit_before><commit_msg>Add the crawl package to crawl.go so it will build.<commit_after>/*
Package for crawling for tweets, getting their sentiment score, and writing them
to the database.
*/
package crawl
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"regexp"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sts"
)

const (
	maxSessionNameLen int = 32
)

var (
	// matches char that is not valid in a STS role session name
	invalidSessionNameRegexp = regexp.MustCompile(`[^\w+=,.@-]`)
)

type credentials struct {
	AccessKey   string
	Expiration  time.Time
	GeneratedAt time.Time
	RoleArn     roleArn
	SecretKey   string
	Token       string
}

func (c credentials) ExpiredNow() bool {
	return c.ExpiredAt(time.Now())
}

func (c credentials) ExpiredAt(at time.Time) bool {
	return at.After(c.Expiration)
}

func (c credentials) ExpiresIn(d time.Duration) bool {
	return c.ExpiredAt(time.Now().Add(-d))
}

type containerCredentials struct {
	containerInfo
	credentials
}

func (c containerCredentials) IsValid(container containerInfo) bool {
	return c.containerInfo.IamRole.Equals(container.IamRole) &&
		c.containerInfo.ID == container.ID &&
		!c.credentials.ExpiresIn(5*time.Minute)
}

type credentialsProvider struct {
	container            containerService
	awsSts               *sts.STS
	defaultIamRoleArn    roleArn
	defaultIamPolicy     string
	containerCredentials map[string]containerCredentials
	lock                 sync.Mutex
}

func newCredentialsProvider(awsSession *session.Session, container containerService, defaultIamRoleArn roleArn, defaultIamPolicy string) *credentialsProvider {
	return &credentialsProvider{
		container:            container,
		awsSts:               sts.New(awsSession),
		defaultIamRoleArn:    defaultIamRoleArn,
		defaultIamPolicy:     defaultIamPolicy,
		containerCredentials: make(map[string]containerCredentials),
	}
}

func (c *credentialsProvider) CredentialsForIP(containerIP string) (credentials, error) {
	c.lock.Lock()
	defer c.lock.Unlock()

	container, err := c.container.ContainerForIP(containerIP)

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

	oldCredentials, found := c.containerCredentials[containerIP]

	if !found || !oldCredentials.IsValid(container) {
		roleArn := container.IamRole
		iamPolicy := container.IamPolicy

		if roleArn.Empty() {
			roleArn = c.defaultIamRoleArn

			if len(iamPolicy) == 0 {
				iamPolicy = c.defaultIamPolicy
			}
		}

		role, err := c.AssumeRole(roleArn, iamPolicy, generateSessionName(c.container.TypeName(), container.ID))

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

		oldCredentials = containerCredentials{container, role}
		c.containerCredentials[containerIP] = oldCredentials
	}

	return oldCredentials.credentials, nil
}

func (c *credentialsProvider) AssumeRole(roleArn roleArn, iamPolicy, sessionName string) (credentials, error) {
	var policy *string

	if len(iamPolicy) > 0 {
		policy = aws.String(iamPolicy)
	}

	resp, err := c.awsSts.AssumeRole(&sts.AssumeRoleInput{
		DurationSeconds: aws.Int64(3600), // Max is 1 hour
		Policy:          policy,
		RoleArn:         aws.String(roleArn.String()),
		RoleSessionName: aws.String(sessionName),
	})

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

	return credentials{
		AccessKey:   *resp.Credentials.AccessKeyId,
		SecretKey:   *resp.Credentials.SecretAccessKey,
		Token:       *resp.Credentials.SessionToken,
		Expiration:  *resp.Credentials.Expiration,
		GeneratedAt: time.Now(),
		RoleArn:     roleArn,
	}, nil
}

func generateSessionName(platform, containerID string) string {
	sessionName := fmt.Sprintf("%s-%s", platform, containerID)
	return invalidSessionNameRegexp.ReplaceAllString(sessionName, "_")[0:maxSessionNameLen]
}
<commit_msg>Fix session expiration calculation<commit_after>package main

import (
	"fmt"
	"regexp"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sts"
)

const (
	maxSessionNameLen int = 32
)

var (
	// matches char that is not valid in a STS role session name
	invalidSessionNameRegexp = regexp.MustCompile(`[^\w+=,.@-]`)

	sessionExpiration = 5 * time.Minute
)

type credentials struct {
	AccessKey   string
	Expiration  time.Time
	GeneratedAt time.Time
	RoleArn     roleArn
	SecretKey   string
	Token       string
}

func (c credentials) ExpiredNow() bool {
	return c.ExpiredAt(time.Now())
}

func (c credentials) ExpiredAt(at time.Time) bool {
	return at.After(c.Expiration)
}

func (c credentials) ExpiresIn(d time.Duration) bool {
	return c.ExpiredAt(time.Now().Add(d))
}

type containerCredentials struct {
	containerInfo
	credentials
}

func (c containerCredentials) IsValid(container containerInfo) bool {
	return c.containerInfo.IamRole.Equals(container.IamRole) &&
		c.containerInfo.ID == container.ID &&
		!c.credentials.ExpiresIn(sessionExpiration)
}

type credentialsProvider struct {
	container            containerService
	awsSts               *sts.STS
	defaultIamRoleArn    roleArn
	defaultIamPolicy     string
	containerCredentials map[string]containerCredentials
	lock                 sync.Mutex
}

func newCredentialsProvider(awsSession *session.Session, container containerService, defaultIamRoleArn roleArn, defaultIamPolicy string) *credentialsProvider {
	return &credentialsProvider{
		container:            container,
		awsSts:               sts.New(awsSession),
		defaultIamRoleArn:    defaultIamRoleArn,
		defaultIamPolicy:     defaultIamPolicy,
		containerCredentials: make(map[string]containerCredentials),
	}
}

func (c *credentialsProvider) CredentialsForIP(containerIP string) (credentials, error) {
	c.lock.Lock()
	defer c.lock.Unlock()

	container, err := c.container.ContainerForIP(containerIP)

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

	oldCredentials, found := c.containerCredentials[containerIP]

	if !found || !oldCredentials.IsValid(container) {
		roleArn := container.IamRole
		iamPolicy := container.IamPolicy

		if roleArn.Empty() {
			roleArn = c.defaultIamRoleArn

			if len(iamPolicy) == 0 {
				iamPolicy = c.defaultIamPolicy
			}
		}

		role, err := c.AssumeRole(roleArn, iamPolicy, generateSessionName(c.container.TypeName(), container.ID))

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

		oldCredentials = containerCredentials{container, role}
		c.containerCredentials[containerIP] = oldCredentials
	}

	return oldCredentials.credentials, nil
}

func (c *credentialsProvider) AssumeRole(roleArn roleArn, iamPolicy, sessionName string) (credentials, error) {
	var policy *string

	if len(iamPolicy) > 0 {
		policy = aws.String(iamPolicy)
	}

	resp, err := c.awsSts.AssumeRole(&sts.AssumeRoleInput{
		DurationSeconds: aws.Int64(3600), // Max is 1 hour
		Policy:          policy,
		RoleArn:         aws.String(roleArn.String()),
		RoleSessionName: aws.String(sessionName),
	})

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

	return credentials{
		AccessKey:   *resp.Credentials.AccessKeyId,
		SecretKey:   *resp.Credentials.SecretAccessKey,
		Token:       *resp.Credentials.SessionToken,
		Expiration:  *resp.Credentials.Expiration,
		GeneratedAt: time.Now(),
		RoleArn:     roleArn,
	}, nil
}

func generateSessionName(platform, containerID string) string {
	sessionName := fmt.Sprintf("%s-%s", platform, containerID)
	return invalidSessionNameRegexp.ReplaceAllString(sessionName, "_")[0:maxSessionNameLen]
}
<|endoftext|>


<commit_before>package system

import (
	"testing"
)

// Test_JoinLeaderNode tests a join operation between a leader and a new node.
func Test_JoinLeaderNode(t *testing.T) {
	leader := mustNewLeaderNode()
	defer leader.Deprovision()

	node := mustNewNode(false)
	defer node.Deprovision()
	if err := node.Join(leader); err != nil {
		t.Fatalf("node failed to join leader: %s", err.Error())
	}
	_, err := node.WaitForLeader()
	if err != nil {
		t.Fatalf("failed waiting for leader: %s", err.Error())
	}
}

// Test_MultiNodeCluster tests formation of a 3-node cluster, and its operation.
func Test_MultiNodeCluster(t *testing.T) {
	node1 := mustNewLeaderNode()
	defer node1.Deprovision()

	node2 := mustNewNode(false)
	defer node2.Deprovision()
	if err := node2.Join(node1); err != nil {
		t.Fatalf("node failed to join leader: %s", err.Error())
	}
	_, err := node2.WaitForLeader()
	if err != nil {
		t.Fatalf("failed waiting for leader: %s", err.Error())
	}

	// Get the new leader, in case it changed.
	c := Cluster{node1, node2}
	leader, err := c.Leader()
	if err != nil {
		t.Fatalf("failed to find cluster leader: %s", err.Error())
	}

	node3 := mustNewNode(false)
	defer node3.Deprovision()
	if err := node3.Join(leader); err != nil {
		t.Fatalf("node failed to join leader: %s", err.Error())
	}
	node3.WaitForLeader()
	_, err = node3.WaitForLeader()
	if err != nil {
		t.Fatalf("failed waiting for leader: %s", err.Error())
	}

	// Get the new leader, in case it changed.
	c = Cluster{node1, node2, node3}
	leader, err = c.Leader()
	if err != nil {
		t.Fatalf("failed to find cluster leader: %s", err.Error())
	}

	// Run queries against cluster.
	tests := []struct {
		stmt     string
		expected string
		execute  bool
	}{
		{
			stmt:     `CREATE TABLE foo (id integer not null primary key, name text)`,
			expected: `{"results":[{}]}`,
			execute:  true,
		},
		{
			stmt:     `INSERT INTO foo(name) VALUES("fiona")`,
			expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
			execute:  true,
		},
		{
			stmt:     `SELECT * FROM foo`,
			expected: `{"results":[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]}`,
			execute:  false,
		},
	}

	for i, tt := range tests {
		var r string
		var err error
		if tt.execute {
			r, err = leader.Execute(tt.stmt)
		} else {
			r, err = leader.Query(tt.stmt)
		}
		if err != nil {
			t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
		}
		if r != tt.expected {
			t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
		}
	}

	// Kill the leader and wait for the new leader.
	leader.Deprovision()
	c.RemoveNode(leader)
	leader, err = c.WaitForNewLeader(leader)
	if err != nil {
		t.Fatalf("failed to find new cluster leader after killing leader: %s", err.Error())
	}

	// Run queries against 2-node cluster.
	tests = []struct {
		stmt     string
		expected string
		execute  bool
	}{
		{
			stmt:     `CREATE TABLE foo (id integer not null primary key, name text)`,
			expected: `{"results":[{"error":"table foo already exists"}]}`,
			execute:  true,
		},
		{
			stmt:     `INSERT INTO foo(name) VALUES("sinead")`,
			expected: `{"results":[{"last_insert_id":2,"rows_affected":1}]}`,
			execute:  true,
		},
		{
			stmt:     `SELECT * FROM foo`,
			expected: `{"results":[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"],[2,"sinead"]]}]}`,
			execute:  false,
		},
	}

	for i, tt := range tests {
		var r string
		var err error
		if tt.execute {
			r, err = leader.Execute(tt.stmt)
		} else {
			r, err = leader.Query(tt.stmt)
		}
		if err != nil {
			t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
		}
		if r != tt.expected {
			t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
		}
	}
}
<commit_msg>Remove repeated statement in tests<commit_after>package system

import (
	"testing"
)

// Test_JoinLeaderNode tests a join operation between a leader and a new node.
func Test_JoinLeaderNode(t *testing.T) {
	leader := mustNewLeaderNode()
	defer leader.Deprovision()

	node := mustNewNode(false)
	defer node.Deprovision()
	if err := node.Join(leader); err != nil {
		t.Fatalf("node failed to join leader: %s", err.Error())
	}
	_, err := node.WaitForLeader()
	if err != nil {
		t.Fatalf("failed waiting for leader: %s", err.Error())
	}
}

// Test_MultiNodeCluster tests formation of a 3-node cluster, and its operation.
func Test_MultiNodeCluster(t *testing.T) {
	node1 := mustNewLeaderNode()
	defer node1.Deprovision()

	node2 := mustNewNode(false)
	defer node2.Deprovision()
	if err := node2.Join(node1); err != nil {
		t.Fatalf("node failed to join leader: %s", err.Error())
	}
	_, err := node2.WaitForLeader()
	if err != nil {
		t.Fatalf("failed waiting for leader: %s", err.Error())
	}

	// Get the new leader, in case it changed.
	c := Cluster{node1, node2}
	leader, err := c.Leader()
	if err != nil {
		t.Fatalf("failed to find cluster leader: %s", err.Error())
	}

	node3 := mustNewNode(false)
	defer node3.Deprovision()
	if err := node3.Join(leader); err != nil {
		t.Fatalf("node failed to join leader: %s", err.Error())
	}
	_, err = node3.WaitForLeader()
	if err != nil {
		t.Fatalf("failed waiting for leader: %s", err.Error())
	}

	// Get the new leader, in case it changed.
	c = Cluster{node1, node2, node3}
	leader, err = c.Leader()
	if err != nil {
		t.Fatalf("failed to find cluster leader: %s", err.Error())
	}

	// Run queries against cluster.
	tests := []struct {
		stmt     string
		expected string
		execute  bool
	}{
		{
			stmt:     `CREATE TABLE foo (id integer not null primary key, name text)`,
			expected: `{"results":[{}]}`,
			execute:  true,
		},
		{
			stmt:     `INSERT INTO foo(name) VALUES("fiona")`,
			expected: `{"results":[{"last_insert_id":1,"rows_affected":1}]}`,
			execute:  true,
		},
		{
			stmt:     `SELECT * FROM foo`,
			expected: `{"results":[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"]]}]}`,
			execute:  false,
		},
	}

	for i, tt := range tests {
		var r string
		var err error
		if tt.execute {
			r, err = leader.Execute(tt.stmt)
		} else {
			r, err = leader.Query(tt.stmt)
		}
		if err != nil {
			t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
		}
		if r != tt.expected {
			t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
		}
	}

	// Kill the leader and wait for the new leader.
	leader.Deprovision()
	c.RemoveNode(leader)
	leader, err = c.WaitForNewLeader(leader)
	if err != nil {
		t.Fatalf("failed to find new cluster leader after killing leader: %s", err.Error())
	}

	// Run queries against 2-node cluster.
	tests = []struct {
		stmt     string
		expected string
		execute  bool
	}{
		{
			stmt:     `CREATE TABLE foo (id integer not null primary key, name text)`,
			expected: `{"results":[{"error":"table foo already exists"}]}`,
			execute:  true,
		},
		{
			stmt:     `INSERT INTO foo(name) VALUES("sinead")`,
			expected: `{"results":[{"last_insert_id":2,"rows_affected":1}]}`,
			execute:  true,
		},
		{
			stmt:     `SELECT * FROM foo`,
			expected: `{"results":[{"columns":["id","name"],"types":["integer","text"],"values":[[1,"fiona"],[2,"sinead"]]}]}`,
			execute:  false,
		},
	}

	for i, tt := range tests {
		var r string
		var err error
		if tt.execute {
			r, err = leader.Execute(tt.stmt)
		} else {
			r, err = leader.Query(tt.stmt)
		}
		if err != nil {
			t.Fatalf(`test %d failed "%s": %s`, i, tt.stmt, err.Error())
		}
		if r != tt.expected {
			t.Fatalf(`test %d received wrong result "%s" got: %s exp: %s`, i, tt.stmt, r, tt.expected)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"

	"github.com/danielkrainas/tinkersnest/api/client"
	"github.com/danielkrainas/tinkersnest/api/v1"
)

func main() {
	const ENDPOINT = "http://localhost:9240"

	// Create a new client
	c := client.New(ENDPOINT, http.DefaultClient)
	fmt.Printf("created new client to %q\n", ENDPOINT)

	// Check V1 endpoint is good and healthy
	//=====================================
	err := c.Ping()
	if err != nil {
		panic("error sending ping")
	}

	fmt.Println("sent ping")

	// TODO: blog example
}
<commit_msg>add client example for blog posts<commit_after>package main

import (
	"fmt"
	"net/http"

	"github.com/danielkrainas/tinkersnest/api/client"
	"github.com/danielkrainas/tinkersnest/api/v1"
)

func main() {
	const ENDPOINT = "http://localhost:9240"

	// Create a new client
	c := client.New(ENDPOINT, http.DefaultClient)
	fmt.Printf("created new client to %q\n", ENDPOINT)

	// Check V1 endpoint is good and healthy
	//=====================================
	err := c.Ping()
	if err != nil {
		panic("error sending ping")
	}

	fmt.Println("sent ping")

	// Create a new blog post
	//=====================================
	post, err := c.Blog().CreatePost(&v1.CreatePostRequest{
		Title: "Example Post",
		Body:  "This was an example post using the tinkersnest/api/client package.",
	})

	if err != nil {
		panic("error creating post")
	}

	fmt.Printf("created %q post.\n", post.Title)

	// Search the blog posts
	//=====================================
	posts, err := c.Blog().SearchPosts()
	if err != nil {
		panic("error searching posts")
	}

	fmt.Printf("found %d posts\n", len(posts))
}
<|endoftext|>


<commit_before>package kocha

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"

	"github.com/naoina/kocha/util"
)

// Controller is the interface that the request controller.
type Controller interface {
	Getter
	Poster
	Putter
	Deleter
	Header
	Patcher
}

// Getter interface is an interface representing a handler for HTTP GET request.
type Getter interface {
	GET(c *Context) error
}

// Poster interface is an interface representing a handler for HTTP POST request.
type Poster interface {
	POST(c *Context) error
}

// Putter interface is an interface representing a handler for HTTP PUT request.
type Putter interface {
	PUT(c *Context) error
}

// Deleter interface is an interface representing a handler for HTTP DELETE request.
type Deleter interface {
	DELETE(c *Context) error
}

// Header interface is an interface representing a handler for HTTP HEAD request.
type Header interface {
	HEAD(c *Context) error
}

// Patcher interface is an interface representing a handler for HTTP PATCH request.
type Patcher interface {
	PATCH(c *Context) error
}

type requestHandler func(c *Context) error

// DefaultController implements Controller interface.
// This can be used to save the trouble to implement all of the methods of
// Controller interface.
type DefaultController struct {
}

// GET implements Getter interface that renders the HTTP 405 Method Not Allowed.
func (dc *DefaultController) GET(c *Context) error {
	return c.RenderError(http.StatusMethodNotAllowed, nil)
}

// POST implements Poster interface that renders the HTTP 405 Method Not Allowed.
func (dc *DefaultController) POST(c *Context) error {
	return c.RenderError(http.StatusMethodNotAllowed, nil)
}

// PUT implements Putter interface that renders the HTTP 405 Method Not Allowed.
func (dc *DefaultController) PUT(c *Context) error {
	return c.RenderError(http.StatusMethodNotAllowed, nil)
}

// DELETE implements Deleter interface that renders the HTTP 405 Method Not Allowed.
func (dc *DefaultController) DELETE(c *Context) error {
	return c.RenderError(http.StatusMethodNotAllowed, nil)
}

// HEAD implements Header interface that renders the HTTP 405 Method Not Allowed.
func (dc *DefaultController) HEAD(c *Context) error {
	return c.RenderError(http.StatusMethodNotAllowed, nil)
}

// PATCH implements Patcher interface that renders the HTTP 405 Method Not Allowed.
func (dc *DefaultController) PATCH(c *Context) error {
	return c.RenderError(http.StatusMethodNotAllowed, nil)
}

type mimeTypeFormats map[string]string

// MimeTypeFormats is relation between mime type and file extension.
var MimeTypeFormats = mimeTypeFormats{
	"application/json": "json",
	"application/xml":  "xml",
	"text/html":        "html",
	"text/plain":       "txt",
}

// Get returns the file extension from the mime type.
func (m mimeTypeFormats) Get(mimeType string) string {
	return m[mimeType]
}

// Set set the file extension to the mime type.
func (m mimeTypeFormats) Set(mimeType, format string) {
	m[mimeType] = format
}

// Del delete the mime type and file extension.
func (m mimeTypeFormats) Del(mimeType string) {
	delete(m, mimeType)
}

// Context represents a context of each request.
type Context struct {
	Name     string       // controller name.
	Layout   string       // layout name.
	Format   string       // format of response.
	Data     interface{}  // data for template.
	Request  *Request     // request.
	Response *Response    // response.
	Params   *Params      // parameters of form values.
	Session  Session      // session.
	Flash    Flash        // flash messages.
	App      *Application // an application.

	// Errors represents the map of errors that related to the form values.
	// A map key is field name, and value is slice of errors.
	// Errors will be set by Context.Params.Bind().
	Errors map[string][]*ParamError
}

// ErrorWithLine returns error that added the filename and line to err.
func ErrorWithLine(err error) error {
	return errorWithLine(err, 2)
}

func errorWithLine(err error, calldepth int) error {
	if _, file, line, ok := runtime.Caller(calldepth); ok {
		return fmt.Errorf("%s:%d: %v", file, line, err)
	}
	return err
}

// Render renders a template.
//
// A data to used will be determined the according to the following rules.
//
// 1. If data of any map type is given, it will be merged to Context.Data if possible.
//
// 2. If data of another type is given, it will be set to Context.Data.
//
// 3. If data is nil, Context.Data as is.
//
// Render retrieve a template file from controller name and c.Response.ContentType.
// e.g. If controller name is "root" and ContentType is "application/xml", Render will
// try to retrieve the template file "root.xml".
// Also ContentType set to "text/html" if not specified.
func (c *Context) Render(data interface{}) error {
	if err := c.setData(data); err != nil {
		return c.errorWithLine(err)
	}
	c.setContentTypeIfNotExists("text/html")
	if err := c.setFormatFromContentTypeIfNotExists(); err != nil {
		return c.errorWithLine(err)
	}
	t, err := c.App.Template.Get(c.App.Config.AppName, c.Layout, c.Name, c.Format)
	if err != nil {
		return c.errorWithLine(err)
	}
	var buf bytes.Buffer
	if err := t.Execute(&buf, c); err != nil {
		return c.errorWithLine(err)
	}
	if err := c.render(&buf); err != nil {
		return c.errorWithLine(err)
	}
	return nil
}

// RenderJSON renders the data as JSON.
//
// RenderJSON is similar to Render but data will be encoded to JSON.
// ContentType set to "application/json" if not specified.
func (c *Context) RenderJSON(data interface{}) error {
	if err := c.setData(data); err != nil {
		return c.errorWithLine(err)
	}
	c.setContentTypeIfNotExists("application/json")
	buf, err := json.Marshal(c.Data)
	if err != nil {
		return c.errorWithLine(err)
	}
	if err := c.render(bytes.NewReader(buf)); err != nil {
		return c.errorWithLine(err)
	}
	return nil
}

// RenderXML renders the data as XML.
//
// RenderXML is similar to Render but data will be encoded to XML.
// ContentType set to "application/xml" if not specified.
func (c *Context) RenderXML(data interface{}) error {
	if err := c.setData(data); err != nil {
		return c.errorWithLine(err)
	}
	c.setContentTypeIfNotExists("application/xml")
	buf, err := xml.Marshal(c.Data)
	if err != nil {
		return c.errorWithLine(err)
	}
	if err := c.render(bytes.NewReader(buf)); err != nil {
		return c.errorWithLine(err)
	}
	return nil
}

// RenderText renders the content.
//
// ContentType set to "text/plain" if not specified.
func (c *Context) RenderText(content string) error {
	c.setContentTypeIfNotExists("text/plain")
	if err := c.render(strings.NewReader(content)); err != nil {
		return c.errorWithLine(err)
	}
	return nil
}

// RenderError renders an error page with statusCode.
//
// RenderError is similar to Render, but there is a point where some different.
// Render retrieve a template file from statusCode and c.Response.ContentType.
// e.g. If statusCode is 500 and ContentType is "application/xml", Render will
// try to retrieve the template file "errors/500.xml".
// If failed to retrieve the template file, it returns result of text with statusCode.
// Also ContentType set to "text/html" if not specified.
func (c *Context) RenderError(statusCode int, data interface{}) error {
	if err := c.setData(data); err != nil {
		return c.errorWithLine(err)
	}
	c.setContentTypeIfNotExists("text/html")
	if err := c.setFormatFromContentTypeIfNotExists(); err != nil {
		return c.errorWithLine(err)
	}
	c.Response.StatusCode = statusCode
	c.Name = errorTemplateName(statusCode)
	t, err := c.App.Template.Get(c.App.Config.AppName, c.Layout, c.Name, c.Format)
	if err != nil {
		c.Response.ContentType = "text/plain"
		if err := c.render(bytes.NewReader([]byte(http.StatusText(statusCode)))); err != nil {
			return c.errorWithLine(err)
		}
		return nil
	}
	var buf bytes.Buffer
	if err := t.Execute(&buf, c); err != nil {
		return c.errorWithLine(err)
	}
	if err := c.render(&buf); err != nil {
		return c.errorWithLine(err)
	}
	return nil
}

// SendFile sends a content.
//
// The path argument specifies an absolute or relative path.
// If absolute path, read the content from the path as it is.
// If relative path, First, Try to get the content from included resources and
// returns it if successful. Otherwise, Add AppPath and StaticDir to the prefix
// of the path and then will read the content from the path that.
// Also, set ContentType detect from content if c.Response.ContentType is empty.
func (c *Context) SendFile(path string) error {
	var file io.ReadSeeker
	path = filepath.FromSlash(path)
	if rc := c.App.ResourceSet.Get(path); rc != nil {
		switch b := rc.(type) {
		case string:
			file = strings.NewReader(b)
		case []byte:
			file = bytes.NewReader(b)
		}
	}
	if file == nil {
		if !filepath.IsAbs(path) {
			path = filepath.Join(c.App.Config.AppPath, StaticDir, path)
		}
		if _, err := os.Stat(path); err != nil {
			if err := c.RenderError(http.StatusNotFound, nil); err != nil {
				return c.errorWithLine(err)
			}
			return nil
		}
		f, err := os.Open(path)
		if err != nil {
			return c.errorWithLine(err)
		}
		defer f.Close()
		file = f
	}
	c.Response.ContentType = util.DetectContentTypeByExt(path)
	if c.Response.ContentType == "" {
		c.Response.ContentType = util.DetectContentTypeByBody(file)
	}
	if err := c.render(file); err != nil {
		return c.errorWithLine(err)
	}
	return nil
}

// Redirect renders result of redirect.
//
// If permanently is true, redirect to url with 301. (http.StatusMovedPermanently)
// Otherwise redirect to url with 302. (http.StatusFound)
func (c *Context) Redirect(url string, permanently bool) error {
	if permanently {
		c.Response.StatusCode = http.StatusMovedPermanently
	} else {
		c.Response.StatusCode = http.StatusFound
	}
	http.Redirect(c.Response, c.Request.Request, url, c.Response.StatusCode)
	return nil
}

// Invoke is shorthand of c.App.Invoke.
func (c *Context) Invoke(unit Unit, newFunc func(), defaultFunc func()) {
	c.App.Invoke(unit, newFunc, defaultFunc)
}

// ErrorWithLine returns error that added the filename and line to err.
func (c *Context) ErrorWithLine(err error) error {
	return c.errorWithLine(err)
}

func (c *Context) render(r io.Reader) error {
	c.Response.Header().Set("Content-Type", c.Response.ContentType)
	c.Response.WriteHeader(c.Response.StatusCode)
	_, err := io.Copy(c.Response, r)
	return err
}

func (c *Context) setContentTypeIfNotExists(contentType string) {
	if c.Response.ContentType == "" {
		c.Response.ContentType = contentType
	}
}

func (c *Context) setData(data interface{}) error {
	if data == nil {
		return nil
	}
	srcType := reflect.TypeOf(data)
	if srcType.Kind() != reflect.Map {
		c.Data = data
		return nil
	}
	if c.Data == nil {
		c.Data = map[interface{}]interface{}{}
	}
	destType := reflect.TypeOf(c.Data)
	if sk, dk := srcType.Key(), destType.Key(); !sk.AssignableTo(dk) {
		return fmt.Errorf("kocha: context: key of type %v is not assignable to type %v", sk, dk)
	}
	src := reflect.ValueOf(data)
	dest := reflect.ValueOf(c.Data)
	dtype := destType.Elem()
	for _, k := range src.MapKeys() {
		v := src.MapIndex(k)
		if vtype := v.Type(); !vtype.AssignableTo(dtype) {
			if !vtype.ConvertibleTo(dtype) {
				return fmt.Errorf("kocha: context: value of type %v is not assignable to type %v", vtype, dtype)
			}
			v = v.Convert(dtype)
		}
		dest.SetMapIndex(k, v)
	}
	return nil
}

func (c *Context) setFormatFromContentTypeIfNotExists() error {
	if c.Format != "" {
		return nil
	}
	if c.Format = MimeTypeFormats.Get(c.Response.ContentType); c.Format == "" {
		return fmt.Errorf("kocha: unknown Content-Type: %v", c.Response.ContentType)
	}
	return nil
}

func (c *Context) newParams() *Params {
	if c.Request.Form == nil {
		c.Request.Form = url.Values{}
	}
	return newParams(c, c.Request.Form, "")
}

func (c *Context) errorWithLine(err error) error {
	return errorWithLine(err, 3)
}

// StaticServe is generic controller for serve a static file.
type StaticServe struct {
	*DefaultController
}

func (ss *StaticServe) GET(c *Context) error {
	path, err := url.Parse(c.Params.Get("path"))
	if err != nil {
		return c.RenderError(http.StatusBadRequest, nil)
	}
	return c.SendFile(path.Path)
}

var internalServerErrorController = &ErrorController{
	StatusCode: http.StatusInternalServerError,
}

// ErrorController is generic controller for error response.
type ErrorController struct {
	*DefaultController

	StatusCode int
}

func (ec *ErrorController) GET(c *Context) error {
	return c.RenderError(ec.StatusCode, nil)
}
<commit_msg>render: improve error message when an error occurs in a template file<commit_after>package kocha

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"

	"github.com/naoina/kocha/util"
)

// Controller is the interface that the request controller.
type Controller interface {
	Getter
	Poster
	Putter
	Deleter
	Header
	Patcher
}

// Getter interface is an interface representing a handler for HTTP GET request.
type Getter interface {
	GET(c *Context) error
}

// Poster interface is an interface representing a handler for HTTP POST request.
type Poster interface {
	POST(c *Context) error
}

// Putter interface is an interface representing a handler for HTTP PUT request.
type Putter interface {
	PUT(c *Context) error
}

// Deleter interface is an interface representing a handler for HTTP DELETE request.
type Deleter interface {
	DELETE(c *Context) error
}

// Header interface is an interface representing a handler for HTTP HEAD request.
type Header interface {
	HEAD(c *Context) error
}

// Patcher interface is an interface representing a handler for HTTP PATCH request.
type Patcher interface {
	PATCH(c *Context) error
}

type requestHandler func(c *Context) error

// DefaultController implements Controller interface.
// This can be used to save the trouble to implement all of the methods of
// Controller interface.
type DefaultController struct {
}

// GET implements Getter interface that renders the HTTP 405 Method Not Allowed.
func (dc *DefaultController) GET(c *Context) error {
	return c.RenderError(http.StatusMethodNotAllowed, nil)
}

// POST implements Poster interface that renders the HTTP 405 Method Not Allowed.
func (dc *DefaultController) POST(c *Context) error {
	return c.RenderError(http.StatusMethodNotAllowed, nil)
}

// PUT implements Putter interface that renders the HTTP 405 Method Not Allowed.
func (dc *DefaultController) PUT(c *Context) error {
	return c.RenderError(http.StatusMethodNotAllowed, nil)
}

// DELETE implements Deleter interface that renders the HTTP 405 Method Not Allowed.
func (dc *DefaultController) DELETE(c *Context) error {
	return c.RenderError(http.StatusMethodNotAllowed, nil)
}

// HEAD implements Header interface that renders the HTTP 405 Method Not Allowed.
func (dc *DefaultController) HEAD(c *Context) error {
	return c.RenderError(http.StatusMethodNotAllowed, nil)
}

// PATCH implements Patcher interface that renders the HTTP 405 Method Not Allowed.
func (dc *DefaultController) PATCH(c *Context) error {
	return c.RenderError(http.StatusMethodNotAllowed, nil)
}

type mimeTypeFormats map[string]string

// MimeTypeFormats is relation between mime type and file extension.
var MimeTypeFormats = mimeTypeFormats{
	"application/json": "json",
	"application/xml":  "xml",
	"text/html":        "html",
	"text/plain":       "txt",
}

// Get returns the file extension from the mime type.
func (m mimeTypeFormats) Get(mimeType string) string {
	return m[mimeType]
}

// Set set the file extension to the mime type.
func (m mimeTypeFormats) Set(mimeType, format string) {
	m[mimeType] = format
}

// Del delete the mime type and file extension.
func (m mimeTypeFormats) Del(mimeType string) {
	delete(m, mimeType)
}

// Context represents a context of each request.
type Context struct {
	Name     string       // controller name.
	Layout   string       // layout name.
	Format   string       // format of response.
	Data     interface{}  // data for template.
	Request  *Request     // request.
	Response *Response    // response.
	Params   *Params      // parameters of form values.
	Session  Session      // session.
	Flash    Flash        // flash messages.
	App      *Application // an application.

	// Errors represents the map of errors that related to the form values.
	// A map key is field name, and value is slice of errors.
	// Errors will be set by Context.Params.Bind().
	Errors map[string][]*ParamError
}

// ErrorWithLine returns error that added the filename and line to err.
func ErrorWithLine(err error) error {
	return errorWithLine(err, 2)
}

func errorWithLine(err error, calldepth int) error {
	if _, file, line, ok := runtime.Caller(calldepth); ok {
		return fmt.Errorf("%s:%d: %v", file, line, err)
	}
	return err
}

// Render renders a template.
//
// A data to used will be determined the according to the following rules.
//
// 1. If data of any map type is given, it will be merged to Context.Data if possible.
//
// 2. If data of another type is given, it will be set to Context.Data.
//
// 3. If data is nil, Context.Data as is.
//
// Render retrieve a template file from controller name and c.Response.ContentType.
// e.g. If controller name is "root" and ContentType is "application/xml", Render will
// try to retrieve the template file "root.xml".
// Also ContentType set to "text/html" if not specified.
func (c *Context) Render(data interface{}) error {
	if err := c.setData(data); err != nil {
		return c.errorWithLine(err)
	}
	c.setContentTypeIfNotExists("text/html")
	if err := c.setFormatFromContentTypeIfNotExists(); err != nil {
		return c.errorWithLine(err)
	}
	t, err := c.App.Template.Get(c.App.Config.AppName, c.Layout, c.Name, c.Format)
	if err != nil {
		return c.errorWithLine(err)
	}
	var buf bytes.Buffer
	if err := t.Execute(&buf, c); err != nil {
		return fmt.Errorf("%s: %v", t.Name(), err)
	}
	if err := c.render(&buf); err != nil {
		return c.errorWithLine(err)
	}
	return nil
}

// RenderJSON renders the data as JSON.
//
// RenderJSON is similar to Render but data will be encoded to JSON.
// ContentType set to "application/json" if not specified.
func (c *Context) RenderJSON(data interface{}) error {
	if err := c.setData(data); err != nil {
		return c.errorWithLine(err)
	}
	c.setContentTypeIfNotExists("application/json")
	buf, err := json.Marshal(c.Data)
	if err != nil {
		return c.errorWithLine(err)
	}
	if err := c.render(bytes.NewReader(buf)); err != nil {
		return c.errorWithLine(err)
	}
	return nil
}

// RenderXML renders the data as XML.
//
// RenderXML is similar to Render but data will be encoded to XML.
// ContentType set to "application/xml" if not specified.
func (c *Context) RenderXML(data interface{}) error {
	if err := c.setData(data); err != nil {
		return c.errorWithLine(err)
	}
	c.setContentTypeIfNotExists("application/xml")
	buf, err := xml.Marshal(c.Data)
	if err != nil {
		return c.errorWithLine(err)
	}
	if err := c.render(bytes.NewReader(buf)); err != nil {
		return c.errorWithLine(err)
	}
	return nil
}

// RenderText renders the content.
//
// ContentType set to "text/plain" if not specified.
func (c *Context) RenderText(content string) error {
	c.setContentTypeIfNotExists("text/plain")
	if err := c.render(strings.NewReader(content)); err != nil {
		return c.errorWithLine(err)
	}
	return nil
}

// RenderError renders an error page with statusCode.
//
// RenderError is similar to Render, but there is a point where some different.
// Render retrieve a template file from statusCode and c.Response.ContentType.
// e.g. If statusCode is 500 and ContentType is "application/xml", Render will
// try to retrieve the template file "errors/500.xml".
// If failed to retrieve the template file, it returns result of text with statusCode.
// Also ContentType set to "text/html" if not specified.
func (c *Context) RenderError(statusCode int, data interface{}) error {
	if err := c.setData(data); err != nil {
		return c.errorWithLine(err)
	}
	c.setContentTypeIfNotExists("text/html")
	if err := c.setFormatFromContentTypeIfNotExists(); err != nil {
		return c.errorWithLine(err)
	}
	c.Response.StatusCode = statusCode
	c.Name = errorTemplateName(statusCode)
	t, err := c.App.Template.Get(c.App.Config.AppName, c.Layout, c.Name, c.Format)
	if err != nil {
		c.Response.ContentType = "text/plain"
		if err := c.render(bytes.NewReader([]byte(http.StatusText(statusCode)))); err != nil {
			return c.errorWithLine(err)
		}
		return nil
	}
	var buf bytes.Buffer
	if err := t.Execute(&buf, c); err != nil {
		return fmt.Errorf("%s: %v", t.Name(), err)
	}
	if err := c.render(&buf); err != nil {
		return c.errorWithLine(err)
	}
	return nil
}

// SendFile sends a content.
//
// The path argument specifies an absolute or relative path.
// If absolute path, read the content from the path as it is.
// If relative path, First, Try to get the content from included resources and
// returns it if successful. Otherwise, Add AppPath and StaticDir to the prefix
// of the path and then will read the content from the path that.
// Also, set ContentType detect from content if c.Response.ContentType is empty.
func (c *Context) SendFile(path string) error {
	var file io.ReadSeeker
	path = filepath.FromSlash(path)
	if rc := c.App.ResourceSet.Get(path); rc != nil {
		switch b := rc.(type) {
		case string:
			file = strings.NewReader(b)
		case []byte:
			file = bytes.NewReader(b)
		}
	}
	if file == nil {
		if !filepath.IsAbs(path) {
			path = filepath.Join(c.App.Config.AppPath, StaticDir, path)
		}
		if _, err := os.Stat(path); err != nil {
			if err := c.RenderError(http.StatusNotFound, nil); err != nil {
				return c.errorWithLine(err)
			}
			return nil
		}
		f, err := os.Open(path)
		if err != nil {
			return c.errorWithLine(err)
		}
		defer f.Close()
		file = f
	}
	c.Response.ContentType = util.DetectContentTypeByExt(path)
	if c.Response.ContentType == "" {
		c.Response.ContentType = util.DetectContentTypeByBody(file)
	}
	if err := c.render(file); err != nil {
		return c.errorWithLine(err)
	}
	return nil
}

// Redirect renders result of redirect.
//
// If permanently is true, redirect to url with 301. (http.StatusMovedPermanently)
// Otherwise redirect to url with 302. (http.StatusFound)
func (c *Context) Redirect(url string, permanently bool) error {
	if permanently {
		c.Response.StatusCode = http.StatusMovedPermanently
	} else {
		c.Response.StatusCode = http.StatusFound
	}
	http.Redirect(c.Response, c.Request.Request, url, c.Response.StatusCode)
	return nil
}

// Invoke is shorthand of c.App.Invoke.
func (c *Context) Invoke(unit Unit, newFunc func(), defaultFunc func()) {
	c.App.Invoke(unit, newFunc, defaultFunc)
}

// ErrorWithLine returns error that added the filename and line to err.
func (c *Context) ErrorWithLine(err error) error {
	return c.errorWithLine(err)
}

func (c *Context) render(r io.Reader) error {
	c.Response.Header().Set("Content-Type", c.Response.ContentType)
	c.Response.WriteHeader(c.Response.StatusCode)
	_, err := io.Copy(c.Response, r)
	return err
}

func (c *Context) setContentTypeIfNotExists(contentType string) {
	if c.Response.ContentType == "" {
		c.Response.ContentType = contentType
	}
}

func (c *Context) setData(data interface{}) error {
	if data == nil {
		return nil
	}
	srcType := reflect.TypeOf(data)
	if srcType.Kind() != reflect.Map {
		c.Data = data
		return nil
	}
	if c.Data == nil {
		c.Data = map[interface{}]interface{}{}
	}
	destType := reflect.TypeOf(c.Data)
	if sk, dk := srcType.Key(), destType.Key(); !sk.AssignableTo(dk) {
		return fmt.Errorf("kocha: context: key of type %v is not assignable to type %v", sk, dk)
	}
	src := reflect.ValueOf(data)
	dest := reflect.ValueOf(c.Data)
	dtype := destType.Elem()
	for _, k := range src.MapKeys() {
		v := src.MapIndex(k)
		if vtype := v.Type(); !vtype.AssignableTo(dtype) {
			if !vtype.ConvertibleTo(dtype) {
				return fmt.Errorf("kocha: context: value of type %v is not assignable to type %v", vtype, dtype)
			}
			v = v.Convert(dtype)
		}
		dest.SetMapIndex(k, v)
	}
	return nil
}

func (c *Context) setFormatFromContentTypeIfNotExists() error {
	if c.Format != "" {
		return nil
	}
	if c.Format = MimeTypeFormats.Get(c.Response.ContentType); c.Format == "" {
		return fmt.Errorf("kocha: unknown Content-Type: %v", c.Response.ContentType)
	}
	return nil
}

func (c *Context) newParams() *Params {
	if c.Request.Form == nil {
		c.Request.Form = url.Values{}
	}
	return newParams(c, c.Request.Form, "")
}

func (c *Context) errorWithLine(err error) error {
	return errorWithLine(err, 3)
}

// StaticServe is generic controller for serve a static file.
type StaticServe struct {
	*DefaultController
}

func (ss *StaticServe) GET(c *Context) error {
	path, err := url.Parse(c.Params.Get("path"))
	if err != nil {
		return c.RenderError(http.StatusBadRequest, nil)
	}
	return c.SendFile(path.Path)
}

var internalServerErrorController = &ErrorController{
	StatusCode: http.StatusInternalServerError,
}

// ErrorController is generic controller for error response.
type ErrorController struct {
	*DefaultController

	StatusCode int
}

func (ec *ErrorController) GET(c *Context) error {
	return c.RenderError(ec.StatusCode, nil)
}
<|endoftext|>