<commit_before>// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.

package elastic

import (
	"fmt"
)

// QueryStringQuery uses the query parser in order to parse its content.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/6.0/query-dsl-query-string-query.html
type QueryStringQuery struct {
	queryString              string
	defaultField             string
	defaultOperator          string
	analyzer                 string
	quoteAnalyzer            string
	quoteFieldSuffix         string
	allowLeadingWildcard     *bool
	lowercaseExpandedTerms   *bool
	enablePositionIncrements *bool
	analyzeWildcard          *bool
	locale                   string
	boost                    *float64
	fuzziness                string
	fuzzyPrefixLength        *int
	fuzzyMaxExpansions       *int
	fuzzyRewrite             string
	phraseSlop               *int
	fields                   []string
	fieldBoosts              map[string]*float64
	tieBreaker               *float64
	rewrite                  string
	minimumShouldMatch       string
	lenient                  *bool
	queryName                string
	timeZone                 string
	maxDeterminizedStates    *int
	escape                   *bool
}

// NewQueryStringQuery creates and initializes a new QueryStringQuery.
func NewQueryStringQuery(queryString string) *QueryStringQuery {
	return &QueryStringQuery{
		queryString: queryString,
		fields:      make([]string, 0),
		fieldBoosts: make(map[string]*float64),
	}
}

// DefaultField specifies the field to run against when no prefix field
// is specified. Only relevant when not explicitly adding fields the query
// string will run against.
func (q *QueryStringQuery) DefaultField(defaultField string) *QueryStringQuery {
	q.defaultField = defaultField
	return q
}

// Field adds a field to run the query string against.
func (q *QueryStringQuery) Field(field string) *QueryStringQuery {
	q.fields = append(q.fields, field)
	return q
}

// FieldWithBoost adds a field to run the query string against with a specific boost.
func (q *QueryStringQuery) FieldWithBoost(field string, boost float64) *QueryStringQuery {
	q.fields = append(q.fields, field)
	q.fieldBoosts[field] = &boost
	return q
}

// TieBreaker is used when more than one field is used with the query string,
// and combined queries are using dismax.
func (q *QueryStringQuery) TieBreaker(tieBreaker float64) *QueryStringQuery {
	q.tieBreaker = &tieBreaker
	return q
}

// DefaultOperator sets the boolean operator of the query parser used to
// parse the query string.
//
// In default mode (OR) terms without any modifiers
// are considered optional, e.g. "capital of Hungary" is equal to
// "capital OR of OR Hungary".
//
// In AND mode, terms are considered to be in conjunction. The above mentioned
// query is then parsed as "capital AND of AND Hungary".
func (q *QueryStringQuery) DefaultOperator(operator string) *QueryStringQuery {
	q.defaultOperator = operator
	return q
}

// Analyzer is an optional analyzer used to analyze the query string.
// Note, if a field has search analyzer defined for it, then it will be used
// automatically. Defaults to the smart search analyzer.
func (q *QueryStringQuery) Analyzer(analyzer string) *QueryStringQuery {
	q.analyzer = analyzer
	return q
}

// QuoteAnalyzer is an optional analyzer to be used to analyze the query string
// for phrase searches. Note, if a field has search analyzer defined for it,
// then it will be used automatically. Defaults to the smart search analyzer.
func (q *QueryStringQuery) QuoteAnalyzer(quoteAnalyzer string) *QueryStringQuery {
	q.quoteAnalyzer = quoteAnalyzer
	return q
}

// MaxDeterminizedState protects against too-difficult regular expression queries.
func (q *QueryStringQuery) MaxDeterminizedState(maxDeterminizedStates int) *QueryStringQuery {
	q.maxDeterminizedStates = &maxDeterminizedStates
	return q
}

// AllowLeadingWildcard specifies whether leading wildcards should be allowed
// or not (defaults to true).
func (q *QueryStringQuery) AllowLeadingWildcard(allowLeadingWildcard bool) *QueryStringQuery {
	q.allowLeadingWildcard = &allowLeadingWildcard
	return q
}

// LowercaseExpandedTerms indicates whether terms of wildcard, prefix, fuzzy
// and range queries are automatically lower-cased or not. Default is true.
func (q *QueryStringQuery) LowercaseExpandedTerms(lowercaseExpandedTerms bool) *QueryStringQuery {
	q.lowercaseExpandedTerms = &lowercaseExpandedTerms
	return q
}

// EnablePositionIncrements indicates whether to enable position increments
// in result query. Defaults to true.
//
// When set, result phrase and multi-phrase queries will be aware of position
// increments. Useful when e.g. a StopFilter increases the position increment
// of the token that follows an omitted token.
func (q *QueryStringQuery) EnablePositionIncrements(enablePositionIncrements bool) *QueryStringQuery {
	q.enablePositionIncrements = &enablePositionIncrements
	return q
}

// Fuzziness sets the edit distance for fuzzy queries. Default is "AUTO".
func (q *QueryStringQuery) Fuzziness(fuzziness string) *QueryStringQuery {
	q.fuzziness = fuzziness
	return q
}

// FuzzyPrefixLength sets the minimum prefix length for fuzzy queries.
// Default is 1.
func (q *QueryStringQuery) FuzzyPrefixLength(fuzzyPrefixLength int) *QueryStringQuery {
	q.fuzzyPrefixLength = &fuzzyPrefixLength
	return q
}

func (q *QueryStringQuery) FuzzyMaxExpansions(fuzzyMaxExpansions int) *QueryStringQuery {
	q.fuzzyMaxExpansions = &fuzzyMaxExpansions
	return q
}

func (q *QueryStringQuery) FuzzyRewrite(fuzzyRewrite string) *QueryStringQuery {
	q.fuzzyRewrite = fuzzyRewrite
	return q
}

// PhraseSlop sets the default slop for phrases. If zero, then exact matches
// are required. Default value is zero.
func (q *QueryStringQuery) PhraseSlop(phraseSlop int) *QueryStringQuery {
	q.phraseSlop = &phraseSlop
	return q
}

// AnalyzeWildcard indicates whether to enabled analysis on wildcard and prefix queries.
func (q *QueryStringQuery) AnalyzeWildcard(analyzeWildcard bool) *QueryStringQuery {
	q.analyzeWildcard = &analyzeWildcard
	return q
}

func (q *QueryStringQuery) Rewrite(rewrite string) *QueryStringQuery {
	q.rewrite = rewrite
	return q
}

func (q *QueryStringQuery) MinimumShouldMatch(minimumShouldMatch string) *QueryStringQuery {
	q.minimumShouldMatch = minimumShouldMatch
	return q
}

// Boost sets the boost for this query.
func (q *QueryStringQuery) Boost(boost float64) *QueryStringQuery {
	q.boost = &boost
	return q
}

// QuoteFieldSuffix is an optional field name suffix to automatically
// try and add to the field searched when using quoted text.
func (q *QueryStringQuery) QuoteFieldSuffix(quoteFieldSuffix string) *QueryStringQuery {
	q.quoteFieldSuffix = quoteFieldSuffix
	return q
}

// Lenient indicates whether the query string parser should be lenient
// when parsing field values. It defaults to the index setting and if not
// set, defaults to false.
func (q *QueryStringQuery) Lenient(lenient bool) *QueryStringQuery {
	q.lenient = &lenient
	return q
}

// QueryName sets the query name for the filter that can be used when
// searching for matched_filters per hit.
func (q *QueryStringQuery) QueryName(queryName string) *QueryStringQuery {
	q.queryName = queryName
	return q
}

func (q *QueryStringQuery) Locale(locale string) *QueryStringQuery {
	q.locale = locale
	return q
}

// TimeZone can be used to automatically adjust to/from fields using a
// timezone. Only used with date fields, of course.
func (q *QueryStringQuery) TimeZone(timeZone string) *QueryStringQuery {
	q.timeZone = timeZone
	return q
}

// Escape performs escaping of the query string.
func (q *QueryStringQuery) Escape(escape bool) *QueryStringQuery {
	q.escape = &escape
	return q
}

// Source returns JSON for the query.
func (q *QueryStringQuery) Source() (interface{}, error) {
	source := make(map[string]interface{})
	query := make(map[string]interface{})
	source["query_string"] = query

	query["query"] = q.queryString

	if q.defaultField != "" {
		query["default_field"] = q.defaultField
	}

	if len(q.fields) > 0 {
		var fields []string
		for _, field := range q.fields {
			if boost, found := q.fieldBoosts[field]; found {
				if boost != nil {
					fields = append(fields, fmt.Sprintf("%s^%f", field, *boost))
				} else {
					fields = append(fields, field)
				}
			} else {
				fields = append(fields, field)
			}
		}
		query["fields"] = fields
	}

	if q.tieBreaker != nil {
		query["tie_breaker"] = *q.tieBreaker
	}
	if q.defaultOperator != "" {
		query["default_operator"] = q.defaultOperator
	}
	if q.analyzer != "" {
		query["analyzer"] = q.analyzer
	}
	if q.quoteAnalyzer != "" {
		query["quote_analyzer"] = q.quoteAnalyzer
	}
	if q.maxDeterminizedStates != nil {
		query["max_determinized_states"] = *q.maxDeterminizedStates
	}
	if q.allowLeadingWildcard != nil {
		query["allow_leading_wildcard"] = *q.allowLeadingWildcard
	}
	if q.lowercaseExpandedTerms != nil {
		query["lowercase_expanded_terms"] = *q.lowercaseExpandedTerms
	}
	if q.enablePositionIncrements != nil {
		query["enable_position_increments"] = *q.enablePositionIncrements
	}
	if q.fuzziness != "" {
		query["fuzziness"] = q.fuzziness
	}
	if q.boost != nil {
		query["boost"] = *q.boost
	}
	if q.fuzzyPrefixLength != nil {
		query["fuzzy_prefix_length"] = *q.fuzzyPrefixLength
	}
	if q.fuzzyMaxExpansions != nil {
		query["fuzzy_max_expansions"] = *q.fuzzyMaxExpansions
	}
	if q.fuzzyRewrite != "" {
		query["fuzzy_rewrite"] = q.fuzzyRewrite
	}
	if q.phraseSlop != nil {
		query["phrase_slop"] = *q.phraseSlop
	}
	if q.analyzeWildcard != nil {
		query["analyze_wildcard"] = *q.analyzeWildcard
	}
	if q.rewrite != "" {
		query["rewrite"] = q.rewrite
	}
	if q.minimumShouldMatch != "" {
		query["minimum_should_match"] = q.minimumShouldMatch
	}
	if q.quoteFieldSuffix != "" {
		query["quote_field_suffix"] = q.quoteFieldSuffix
	}
	if q.lenient != nil {
		query["lenient"] = *q.lenient
	}
	if q.queryName != "" {
		query["_name"] = q.queryName
	}
	if q.locale != "" {
		query["locale"] = q.locale
	}
	if q.timeZone != "" {
		query["time_zone"] = q.timeZone
	}
	if q.escape != nil {
		query["escape"] = *q.escape
	}

	return source, nil
}
<commit_msg>Update QueryStringQuery to latest version<commit_after>// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.

package elastic

import (
	"fmt"
)

// QueryStringQuery uses the query parser in order to parse its content.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/6.0/query-dsl-query-string-query.html
type QueryStringQuery struct {
	queryString              string
	defaultField             string
	defaultOperator          string
	analyzer                 string
	quoteAnalyzer            string
	quoteFieldSuffix         string
	allowLeadingWildcard     *bool
	lowercaseExpandedTerms   *bool // Deprecated: Decision is now made by the analyzer
	enablePositionIncrements *bool
	analyzeWildcard          *bool
	locale                   string // Deprecated: Decision is now made by the analyzer
	boost                    *float64
	fuzziness                string
	fuzzyPrefixLength        *int
	fuzzyMaxExpansions       *int
	fuzzyRewrite             string
	phraseSlop               *int
	fields                   []string
	fieldBoosts              map[string]*float64
	tieBreaker               *float64
	rewrite                  string
	minimumShouldMatch       string
	lenient                  *bool
	queryName                string
	timeZone                 string
	maxDeterminizedStates    *int
	escape                   *bool
	typ                      string
}

// NewQueryStringQuery creates and initializes a new QueryStringQuery.
func NewQueryStringQuery(queryString string) *QueryStringQuery {
	return &QueryStringQuery{
		queryString: queryString,
		fields:      make([]string, 0),
		fieldBoosts: make(map[string]*float64),
	}
}

// DefaultField specifies the field to run against when no prefix field
// is specified. Only relevant when not explicitly adding fields the query
// string will run against.
func (q *QueryStringQuery) DefaultField(defaultField string) *QueryStringQuery {
	q.defaultField = defaultField
	return q
}

// Field adds a field to run the query string against.
func (q *QueryStringQuery) Field(field string) *QueryStringQuery {
	q.fields = append(q.fields, field)
	return q
}

// Type sets how multiple fields should be combined to build textual part queries,
// e.g. "best_fields".
func (q *QueryStringQuery) Type(typ string) *QueryStringQuery {
	q.typ = typ
	return q
}

// FieldWithBoost adds a field to run the query string against with a specific boost.
func (q *QueryStringQuery) FieldWithBoost(field string, boost float64) *QueryStringQuery {
	q.fields = append(q.fields, field)
	q.fieldBoosts[field] = &boost
	return q
}

// TieBreaker is used when more than one field is used with the query string,
// and combined queries are using dismax.
func (q *QueryStringQuery) TieBreaker(tieBreaker float64) *QueryStringQuery {
	q.tieBreaker = &tieBreaker
	return q
}

// DefaultOperator sets the boolean operator of the query parser used to
// parse the query string.
//
// In default mode (OR) terms without any modifiers
// are considered optional, e.g. "capital of Hungary" is equal to
// "capital OR of OR Hungary".
//
// In AND mode, terms are considered to be in conjunction. The above mentioned
// query is then parsed as "capital AND of AND Hungary".
func (q *QueryStringQuery) DefaultOperator(operator string) *QueryStringQuery {
	q.defaultOperator = operator
	return q
}

// Analyzer is an optional analyzer used to analyze the query string.
// Note, if a field has search analyzer defined for it, then it will be used
// automatically. Defaults to the smart search analyzer.
func (q *QueryStringQuery) Analyzer(analyzer string) *QueryStringQuery {
	q.analyzer = analyzer
	return q
}

// QuoteAnalyzer is an optional analyzer to be used to analyze the query string
// for phrase searches. Note, if a field has search analyzer defined for it,
// then it will be used automatically. Defaults to the smart search analyzer.
func (q *QueryStringQuery) QuoteAnalyzer(quoteAnalyzer string) *QueryStringQuery {
	q.quoteAnalyzer = quoteAnalyzer
	return q
}

// MaxDeterminizedState protects against too-difficult regular expression queries.
func (q *QueryStringQuery) MaxDeterminizedState(maxDeterminizedStates int) *QueryStringQuery {
	q.maxDeterminizedStates = &maxDeterminizedStates
	return q
}

// AllowLeadingWildcard specifies whether leading wildcards should be allowed
// or not (defaults to true).
func (q *QueryStringQuery) AllowLeadingWildcard(allowLeadingWildcard bool) *QueryStringQuery {
	q.allowLeadingWildcard = &allowLeadingWildcard
	return q
}

// LowercaseExpandedTerms indicates whether terms of wildcard, prefix, fuzzy
// and range queries are automatically lower-cased or not. Default is true.
//
// Deprecated: Decision is now made by the analyzer.
func (q *QueryStringQuery) LowercaseExpandedTerms(lowercaseExpandedTerms bool) *QueryStringQuery {
	q.lowercaseExpandedTerms = &lowercaseExpandedTerms
	return q
}

// EnablePositionIncrements indicates whether to enable position increments
// in result query. Defaults to true.
//
// When set, result phrase and multi-phrase queries will be aware of position
// increments. Useful when e.g. a StopFilter increases the position increment
// of the token that follows an omitted token.
func (q *QueryStringQuery) EnablePositionIncrements(enablePositionIncrements bool) *QueryStringQuery {
	q.enablePositionIncrements = &enablePositionIncrements
	return q
}

// Fuzziness sets the edit distance for fuzzy queries. Default is "AUTO".
func (q *QueryStringQuery) Fuzziness(fuzziness string) *QueryStringQuery {
	q.fuzziness = fuzziness
	return q
}

// FuzzyPrefixLength sets the minimum prefix length for fuzzy queries.
// Default is 1.
func (q *QueryStringQuery) FuzzyPrefixLength(fuzzyPrefixLength int) *QueryStringQuery {
	q.fuzzyPrefixLength = &fuzzyPrefixLength
	return q
}

func (q *QueryStringQuery) FuzzyMaxExpansions(fuzzyMaxExpansions int) *QueryStringQuery {
	q.fuzzyMaxExpansions = &fuzzyMaxExpansions
	return q
}

func (q *QueryStringQuery) FuzzyRewrite(fuzzyRewrite string) *QueryStringQuery {
	q.fuzzyRewrite = fuzzyRewrite
	return q
}

// PhraseSlop sets the default slop for phrases. If zero, then exact matches
// are required. Default value is zero.
func (q *QueryStringQuery) PhraseSlop(phraseSlop int) *QueryStringQuery {
	q.phraseSlop = &phraseSlop
	return q
}

// AnalyzeWildcard indicates whether to enabled analysis on wildcard and prefix queries.
func (q *QueryStringQuery) AnalyzeWildcard(analyzeWildcard bool) *QueryStringQuery {
	q.analyzeWildcard = &analyzeWildcard
	return q
}

func (q *QueryStringQuery) Rewrite(rewrite string) *QueryStringQuery {
	q.rewrite = rewrite
	return q
}

func (q *QueryStringQuery) MinimumShouldMatch(minimumShouldMatch string) *QueryStringQuery {
	q.minimumShouldMatch = minimumShouldMatch
	return q
}

// Boost sets the boost for this query.
func (q *QueryStringQuery) Boost(boost float64) *QueryStringQuery {
	q.boost = &boost
	return q
}

// QuoteFieldSuffix is an optional field name suffix to automatically
// try and add to the field searched when using quoted text.
func (q *QueryStringQuery) QuoteFieldSuffix(quoteFieldSuffix string) *QueryStringQuery {
	q.quoteFieldSuffix = quoteFieldSuffix
	return q
}

// Lenient indicates whether the query string parser should be lenient
// when parsing field values. It defaults to the index setting and if not
// set, defaults to false.
func (q *QueryStringQuery) Lenient(lenient bool) *QueryStringQuery {
	q.lenient = &lenient
	return q
}

// QueryName sets the query name for the filter that can be used when
// searching for matched_filters per hit.
func (q *QueryStringQuery) QueryName(queryName string) *QueryStringQuery {
	q.queryName = queryName
	return q
}

// Locale specifies the locale to be used for string conversions.
//
// Deprecated: Decision is now made by the analyzer.
func (q *QueryStringQuery) Locale(locale string) *QueryStringQuery {
	q.locale = locale
	return q
}

// TimeZone can be used to automatically adjust to/from fields using a
// timezone. Only used with date fields, of course.
func (q *QueryStringQuery) TimeZone(timeZone string) *QueryStringQuery {
	q.timeZone = timeZone
	return q
}

// Escape performs escaping of the query string.
func (q *QueryStringQuery) Escape(escape bool) *QueryStringQuery {
	q.escape = &escape
	return q
}

// Source returns JSON for the query.
func (q *QueryStringQuery) Source() (interface{}, error) {
	source := make(map[string]interface{})
	query := make(map[string]interface{})
	source["query_string"] = query

	query["query"] = q.queryString

	if q.defaultField != "" {
		query["default_field"] = q.defaultField
	}

	if len(q.fields) > 0 {
		var fields []string
		for _, field := range q.fields {
			if boost, found := q.fieldBoosts[field]; found {
				if boost != nil {
					fields = append(fields, fmt.Sprintf("%s^%f", field, *boost))
				} else {
					fields = append(fields, field)
				}
			} else {
				fields = append(fields, field)
			}
		}
		query["fields"] = fields
	}

	if q.tieBreaker != nil {
		query["tie_breaker"] = *q.tieBreaker
	}
	if q.defaultOperator != "" {
		query["default_operator"] = q.defaultOperator
	}
	if q.analyzer != "" {
		query["analyzer"] = q.analyzer
	}
	if q.quoteAnalyzer != "" {
		query["quote_analyzer"] = q.quoteAnalyzer
	}
	if q.maxDeterminizedStates != nil {
		query["max_determinized_states"] = *q.maxDeterminizedStates
	}
	if q.allowLeadingWildcard != nil {
		query["allow_leading_wildcard"] = *q.allowLeadingWildcard
	}
	if q.lowercaseExpandedTerms != nil {
		query["lowercase_expanded_terms"] = *q.lowercaseExpandedTerms
	}
	if q.enablePositionIncrements != nil {
		query["enable_position_increments"] = *q.enablePositionIncrements
	}
	if q.fuzziness != "" {
		query["fuzziness"] = q.fuzziness
	}
	if q.boost != nil {
		query["boost"] = *q.boost
	}
	if q.fuzzyPrefixLength != nil {
		query["fuzzy_prefix_length"] = *q.fuzzyPrefixLength
	}
	if q.fuzzyMaxExpansions != nil {
		query["fuzzy_max_expansions"] = *q.fuzzyMaxExpansions
	}
	if q.fuzzyRewrite != "" {
		query["fuzzy_rewrite"] = q.fuzzyRewrite
	}
	if q.phraseSlop != nil {
		query["phrase_slop"] = *q.phraseSlop
	}
	if q.analyzeWildcard != nil {
		query["analyze_wildcard"] = *q.analyzeWildcard
	}
	if q.rewrite != "" {
		query["rewrite"] = q.rewrite
	}
	if q.minimumShouldMatch != "" {
		query["minimum_should_match"] = q.minimumShouldMatch
	}
	if q.quoteFieldSuffix != "" {
		query["quote_field_suffix"] = q.quoteFieldSuffix
	}
	if q.lenient != nil {
		query["lenient"] = *q.lenient
	}
	if q.queryName != "" {
		query["_name"] = q.queryName
	}
	if q.locale != "" {
		query["locale"] = q.locale
	}
	if q.timeZone != "" {
		query["time_zone"] = q.timeZone
	}
	if q.escape != nil {
		query["escape"] = *q.escape
	}
	if q.typ != "" {
		query["type"] = q.typ
	}

	return source, nil
}
<|endoftext|>


<commit_before>// Copyright 2014 The Chihaya Authors. All rights reserved.
// Use of this source code is governed by the BSD 2-Clause license,
// which can be found in the LICENSE file.

package http

import (
	"net/http"
	"time"

	"github.com/golang/glog"
	"github.com/julienschmidt/httprouter"
	"github.com/stretchr/graceful"

	"github.com/chihaya/bencode"
	"github.com/chihaya/chihaya/config"
	"github.com/chihaya/chihaya/drivers/backend"
	"github.com/chihaya/chihaya/drivers/tracker"
)

type Tracker struct {
	cfg *config.Config
	tp  tracker.Pool
	bc  backend.Conn
}

func NewTracker(cfg *config.Config) (*Tracker, error) {
	tp, err := tracker.Open(&cfg.Tracker)
	if err != nil {
		return nil, err
	}

	bc, err := backend.Open(&cfg.Backend)
	if err != nil {
		return nil, err
	}

	return &Tracker{
		cfg: cfg,
		tp:  tp,
		bc:  bc,
	}, nil
}

type ResponseHandler func(http.ResponseWriter, *http.Request, httprouter.Params) (int, error)

func makeHandler(handler ResponseHandler) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		start := time.Now()
		code, err := handler(w, r, p)
		if err != nil {
			http.Error(w, err.Error(), code)
		}
		glog.Infof(
			"Completed %v %s %s in %v",
			code,
			http.StatusText(code),
			r.URL.Path,
			time.Since(start),
		)
	}
}

func setupRoutes(t *Tracker, cfg *config.Config) *httprouter.Router {
	r := httprouter.New()

	if cfg.Private {
		r.GET("/users/:passkey/announce", makeHandler(t.ServeAnnounce))
		r.GET("/users/:passkey/scrape", makeHandler(t.ServeScrape))

		r.PUT("/users/:passkey", makeHandler(t.putUser))
		r.DELETE("/users/:passkey", makeHandler(t.delUser))
	} else {
		r.GET("/announce", makeHandler(t.ServeAnnounce))
		r.GET("/scrape", makeHandler(t.ServeScrape))
	}

	if cfg.Whitelist {
		r.PUT("/clients/:clientID", makeHandler(t.putClient))
		r.DELETE("/clients/:clientID", makeHandler(t.delClient))
	}

	r.GET("/torrents/:infohash", makeHandler(t.getTorrent))
	r.PUT("/torrents/:infohash", makeHandler(t.putTorrent))
	r.DELETE("/torrents/:infohash", makeHandler(t.delTorrent))

	return r
}

func Serve(cfg *config.Config) {
	t, err := NewTracker(cfg)
	if err != nil {
		glog.Fatal("New: ", err)
	}

	graceful.Run(cfg.Addr, cfg.RequestTimeout.Duration, setupRoutes(t, cfg))
}

func fail(w http.ResponseWriter, r *http.Request, err error) {
	dict := bencode.NewDict()
	dict["failure reason"] = err.Error()

	bencoder := bencode.NewEncoder(w)
	bencoder.Encode(dict)
}
<commit_msg>docs for http package<commit_after>// Copyright 2014 The Chihaya Authors. All rights reserved.
// Use of this source code is governed by the BSD 2-Clause license,
// which can be found in the LICENSE file.

// Package http implements an http-serving BitTorrent tracker.
package http

import (
	"net/http"
	"time"

	"github.com/golang/glog"
	"github.com/julienschmidt/httprouter"
	"github.com/stretchr/graceful"

	"github.com/chihaya/bencode"
	"github.com/chihaya/chihaya/config"
	"github.com/chihaya/chihaya/drivers/backend"
	"github.com/chihaya/chihaya/drivers/tracker"
)

type Tracker struct {
	cfg *config.Config
	tp  tracker.Pool
	bc  backend.Conn
}

func NewTracker(cfg *config.Config) (*Tracker, error) {
	tp, err := tracker.Open(&cfg.Tracker)
	if err != nil {
		return nil, err
	}

	bc, err := backend.Open(&cfg.Backend)
	if err != nil {
		return nil, err
	}

	return &Tracker{
		cfg: cfg,
		tp:  tp,
		bc:  bc,
	}, nil
}

type ResponseHandler func(http.ResponseWriter, *http.Request, httprouter.Params) (int, error)

func makeHandler(handler ResponseHandler) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		start := time.Now()
		code, err := handler(w, r, p)
		if err != nil {
			http.Error(w, err.Error(), code)
		}
		glog.Infof(
			"Completed %v %s %s in %v",
			code,
			http.StatusText(code),
			r.URL.Path,
			time.Since(start),
		)
	}
}

func setupRoutes(t *Tracker, cfg *config.Config) *httprouter.Router {
	r := httprouter.New()

	if cfg.Private {
		r.GET("/users/:passkey/announce", makeHandler(t.ServeAnnounce))
		r.GET("/users/:passkey/scrape", makeHandler(t.ServeScrape))

		r.PUT("/users/:passkey", makeHandler(t.putUser))
		r.DELETE("/users/:passkey", makeHandler(t.delUser))
	} else {
		r.GET("/announce", makeHandler(t.ServeAnnounce))
		r.GET("/scrape", makeHandler(t.ServeScrape))
	}

	if cfg.Whitelist {
		r.PUT("/clients/:clientID", makeHandler(t.putClient))
		r.DELETE("/clients/:clientID", makeHandler(t.delClient))
	}

	r.GET("/torrents/:infohash", makeHandler(t.getTorrent))
	r.PUT("/torrents/:infohash", makeHandler(t.putTorrent))
	r.DELETE("/torrents/:infohash", makeHandler(t.delTorrent))

	return r
}

func Serve(cfg *config.Config) {
	t, err := NewTracker(cfg)
	if err != nil {
		glog.Fatal("New: ", err)
	}

	graceful.Run(cfg.Addr, cfg.RequestTimeout.Duration, setupRoutes(t, cfg))
}

func fail(w http.ResponseWriter, r *http.Request, err error) {
	dict := bencode.NewDict()
	dict["failure reason"] = err.Error()

	bencoder := bencode.NewEncoder(w)
	bencoder.Encode(dict)
}
<|endoftext|>


<commit_before>package main

import (
	"bitbucket.org/firstrow/logvoyage/models"

	validation "github.com/go-ozzo/ozzo-validation"
	iris "gopkg.in/kataras/iris.v6"
)

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

func (p projectData) Validate() error {
	return validation.ValidateStruct(&p,
		validation.Field(&p.Name, validation.Required, validation.Length(1, 255)),
	)

}

func ProjectsCreate(ctx *iris.Context) {
	var data projectData
	ctx.ReadJSON(&data)

	err := data.Validate()

	if err != nil {
		response.Error(ctx, err)
		return
	}

	project, err := models.CreateProject(data.Name, ctx.Get("user").(*models.User))

	if err != nil {
		response.Panic(ctx, err)
		return
	}

	response.Success(ctx, project)
}

func ProjectsList(ctx *iris.Context) {
	user := ctx.Get("user").(*models.User)
	projects, err := models.FindAllProjectsByUser(user)

	if err != nil {
		response.Panic(ctx, err)
		return
	}

	response.Success(ctx, map[string]interface{}{"projects": projects})
}
<commit_msg>Added comments<commit_after>package main

import (
	"bitbucket.org/firstrow/logvoyage/models"

	validation "github.com/go-ozzo/ozzo-validation"
	iris "gopkg.in/kataras/iris.v6"
)

type projectData struct {
	Name string
}

func (p projectData) Validate() error {
	return validation.ValidateStruct(&p,
		validation.Field(&p.Name, validation.Required, validation.Length(1, 255)),
	)

}

// ProjectsCreate creates new project and generates its uuid.
func ProjectsCreate(ctx *iris.Context) {
	var data projectData
	ctx.ReadJSON(&data)

	err := data.Validate()

	if err != nil {
		response.Error(ctx, err)
		return
	}

	project, err := models.CreateProject(data.Name, ctx.Get("user").(*models.User))

	if err != nil {
		response.Panic(ctx, err)
		return
	}

	response.Success(ctx, project)
}

// ProjectsList displays list of user owned projects and projects where
// user is invited.
func ProjectsList(ctx *iris.Context) {
	user := ctx.Get("user").(*models.User)
	projects, err := models.FindAllProjectsByUser(user)

	if err != nil {
		response.Panic(ctx, err)
		return
	}

	response.Success(ctx, map[string]interface{}{"projects": projects})
}
<|endoftext|>


<commit_before>package collection

import (
	"fmt"
	"sync"
)

func ExampleAsEnumerable() {
	original := []int{1, 2, 3, 4, 5}
	wrapped := AsEnumerable(original)

	for entry := range wrapped.Enumerate(nil) {
		fmt.Println(entry)
	}
	// Output:
	// 1
	// 2
	// 3
	// 4
	// 5
}

func ExampleEnumerator_Count() {
	subject := AsEnumerable("str1", "str1", "str2")
	count1 := subject.Enumerate(nil).Count(func(a interface{}) bool {
		return a == "str1"
	})
	fmt.Println(count1)
	// Output: 2
}

func ExampleEnumerator_CountAll() {
	subject := AsEnumerable('a', 'b', 'c', 'd', 'e')
	fmt.Println(subject.Enumerate(nil).CountAll())
	// Ouput: 5
}

func ExampleEnumerator_ElementAt() {
	done := make(chan struct{})
	defer close(done)
	fmt.Print(Fibonacci.Enumerate(done).ElementAt(4))
	// Output: 3
}

func ExampleFirst() {
	empty := NewQueue()
	notEmpty := NewQueue(1, 2, 3, 4)

	fmt.Println(First(empty))
	fmt.Println(First(notEmpty))
	// Output:
	// <nil> Enumerator encountered no elements
	// 1 <nil>
}

func ExampleLast() {
	subject := NewList(1, 2, 3, 4)
	fmt.Println(Last(subject))
	// Output: 4
}

func ExampleEnumerator_Last() {
	subject := AsEnumerable(1, 2, 3)
	fmt.Print(subject.Enumerate(nil).Last())
	//Output: 3
}

func ExampleMerge() {
	a := AsEnumerable(1, 2, 4)
	b := AsEnumerable(8, 16, 32)
	c := Merge(a, b)
	sum := 0
	for x := range c.Enumerate(nil) {
		sum += x.(int)
	}
	fmt.Println(sum)

	product := 1
	for y := range a.Enumerate(nil) {
		product *= y.(int)
	}
	fmt.Println(product)
	// Output:
	// 63
	// 8
}

func ExampleEnumerator_Reverse() {
	a := AsEnumerable(1, 2, 3).Enumerate(nil)
	a = a.Reverse()
	fmt.Println(a.ToSlice())
	// Output: [3 2 1]
}

func ExampleSelect() {
	const offset = 'a' - 1

	subject := AsEnumerable('a', 'b', 'c')
	subject = Select(subject, func(a interface{}) interface{} {
		return a.(rune) - offset
	})

	fmt.Println(ToSlice(subject))
	// Output: [1 2 3]
}

func ExampleEnumerator_Select() {
	subject := AsEnumerable('a', 'b', 'c').Enumerate(nil)
	const offset = 'a' - 1
	results := subject.Select(func(a interface{}) interface{} {
		return a.(rune) - offset
	})

	fmt.Println(results.ToSlice())
	// Output: [1 2 3]
}

func ExampleEnumerator_SelectMany() {

	type BrewHouse struct {
		Name  string
		Beers Enumerable
	}

	breweries := AsEnumerable(
		BrewHouse{
			"Mac & Jacks",
			AsEnumerable(
				"African Amber",
				"Ibis IPA",
			),
		},
		BrewHouse{
			"Post Doc",
			AsEnumerable(
				"Prereq Pale",
			),
		},
		BrewHouse{
			"Resonate",
			AsEnumerable(
				"Comfortably Numb IPA",
				"Lithium Altbier",
			),
		},
		BrewHouse{
			"Triplehorn",
			AsEnumerable(
				"Samson",
				"Pepper Belly",
			),
		},
	)

	beers := breweries.Enumerate(nil).SelectMany(func(brewer interface{}) Enumerator {
		return brewer.(BrewHouse).Beers.Enumerate(nil)
	})

	for beer := range beers {
		fmt.Println(beer)
	}

	// Output:
	// African Amber
	// Ibis IPA
	// Prereq Pale
	// Comfortably Numb IPA
	// Lithium Altbier
	// Samson
	// Pepper Belly
}

func ExampleSkip() {
	done := make(chan struct{})
	defer close(done)

	trimmed := Take(Skip(Fibonacci, 1), 3)
	for entry := range trimmed.Enumerate(done) {
		fmt.Println(entry)
	}
	// Output:
	// 1
	// 1
	// 2
}

func ExampleEnumerator_Skip() {
	subject := AsEnumerable(1, 2, 3, 4, 5, 6, 7)
	skipped := subject.Enumerate(nil).Skip(5)
	for entry := range skipped {
		fmt.Println(entry)
	}
	// Output:
	// 6
	// 7
}

func ExampleTake() {
	done := make(chan struct{})
	defer close(done)

	taken := Take(Fibonacci, 4)
	for entry := range taken.Enumerate(done) {
		fmt.Println(entry)
	}
	// Output:
	// 0
	// 1
	// 1
	// 2
}

func ExampleEnumerator_Take() {
	done := make(chan struct{})
	defer close(done)

	taken := Fibonacci.Enumerate(done).Skip(4).Take(2)
	for entry := range taken {
		fmt.Println(entry)
	}
	// Output:
	// 3
	// 5
}

func ExampleTakeWhile() {
	taken := TakeWhile(Fibonacci, func(x interface{}, n uint) bool {
		return x.(int) < 10
	})
	for entry := range taken.Enumerate(nil) {
		fmt.Println(entry)
	}
	// Output:
	// 0
	// 1
	// 1
	// 2
	// 3
	// 5
	// 8
}

func ExampleEnumerator_TakeWhile() {
	taken := Fibonacci.Enumerate(nil).TakeWhile(func(x interface{}, n uint) bool {
		return x.(int) < 6
	})
	for entry := range taken {
		fmt.Println(entry)
	}
	// Output:
	// 0
	// 1
	// 1
	// 2
	// 3
	// 5
}

func ExampleEnumerator_Tee() {
	base := AsEnumerable(1, 2, 4)
	left, right := base.Enumerate(nil).Tee()
	var wg sync.WaitGroup
	wg.Add(2)

	product := 1
	go func() {
		for x := range left {
			product *= x.(int)
		}
		wg.Done()
	}()

	sum := 0
	go func() {
		for x := range right {
			sum += x.(int)
		}
		wg.Done()
	}()

	wg.Wait()

	fmt.Printf("Sum: %d\n", sum)
	fmt.Printf("Product: %d\n", product)
	// Output:
	// Sum: 7
	// Product: 8
}

func ExampleUCount() {
	subject := NewStack(9, 'a', "str1")
	result := UCount(subject, func(a interface{}) bool {
		_, ok := a.(string)
		return ok
	})
	fmt.Println(result)
	// Output: 1
}

func ExampleEnumerator_UCount() {
	subject := AsEnumerable("str1", "str1", "str2")
	count1 := subject.Enumerate(nil).UCount(func(a interface{}) bool {
		return a == "str1"
	})
	fmt.Println(count1)
	// Output: 2
}

func ExampleUCountAll() {
	subject := NewStack(8, 9, 10, 11)
	fmt.Println(UCountAll(subject))
	// Output: 4
}

func ExampleEnumerator_UCountAll() {
	subject := AsEnumerable('a', 2, "str1")
	fmt.Println(subject.Enumerate(nil).UCountAll())
	// Output: 3
}

func ExampleEnumerator_Where() {
	done := make(chan struct{})
	defer close(done)
	results := Fibonacci.Enumerate(done).Where(func(a interface{}) bool {
		return a.(int) > 8
	}).Take(3)
	fmt.Println(results.ToSlice())
	// Output: [13 21 34]
}

func ExampleWhere() {
	results := Where(AsEnumerable(1, 2, 3, 4, 5), func(a interface{}) bool {
		return a.(int) < 3
	})
	fmt.Println(ToSlice(results))
	// Output: [1 2]
}
<commit_msg>Improving AsEnumerable example<commit_after>package collection

import (
	"fmt"
	"sync"
)

func ExampleAsEnumerable() {
	// When a single value is provided, and it is an array or slice, each value in the array or slice is treated as an enumerable value.
	original := []int{1, 2, 3, 4, 5}
	wrapped := AsEnumerable(original)

	for entry := range wrapped.Enumerate(nil) {
		fmt.Print(entry)
	}
	fmt.Println()

	// When multiple values are provided, regardless of their type, they are each treated as enumerable values.
	wrapped = AsEnumerable("red", "orange", "yellow", "green", "blue", "indigo", "violet")
	for entry := range wrapped.Enumerate(nil) {
		fmt.Println(entry)
	}
	// Output:
	// 12345
	// red
	// orange
	// yellow
	// green
	// blue
	// indigo
	// violet
}

func ExampleEnumerator_Count() {
	subject := AsEnumerable("str1", "str1", "str2")
	count1 := subject.Enumerate(nil).Count(func(a interface{}) bool {
		return a == "str1"
	})
	fmt.Println(count1)
	// Output: 2
}

func ExampleEnumerator_CountAll() {
	subject := AsEnumerable('a', 'b', 'c', 'd', 'e')
	fmt.Println(subject.Enumerate(nil).CountAll())
	// Ouput: 5
}

func ExampleEnumerator_ElementAt() {
	done := make(chan struct{})
	defer close(done)
	fmt.Print(Fibonacci.Enumerate(done).ElementAt(4))
	// Output: 3
}

func ExampleFirst() {
	empty := NewQueue()
	notEmpty := NewQueue(1, 2, 3, 4)

	fmt.Println(First(empty))
	fmt.Println(First(notEmpty))
	// Output:
	// <nil> Enumerator encountered no elements
	// 1 <nil>
}

func ExampleLast() {
	subject := NewList(1, 2, 3, 4)
	fmt.Println(Last(subject))
	// Output: 4
}

func ExampleEnumerator_Last() {
	subject := AsEnumerable(1, 2, 3)
	fmt.Print(subject.Enumerate(nil).Last())
	//Output: 3
}

func ExampleMerge() {
	a := AsEnumerable(1, 2, 4)
	b := AsEnumerable(8, 16, 32)
	c := Merge(a, b)
	sum := 0
	for x := range c.Enumerate(nil) {
		sum += x.(int)
	}
	fmt.Println(sum)

	product := 1
	for y := range a.Enumerate(nil) {
		product *= y.(int)
	}
	fmt.Println(product)
	// Output:
	// 63
	// 8
}

func ExampleEnumerator_Reverse() {
	a := AsEnumerable(1, 2, 3).Enumerate(nil)
	a = a.Reverse()
	fmt.Println(a.ToSlice())
	// Output: [3 2 1]
}

func ExampleSelect() {
	const offset = 'a' - 1

	subject := AsEnumerable('a', 'b', 'c')
	subject = Select(subject, func(a interface{}) interface{} {
		return a.(rune) - offset
	})

	fmt.Println(ToSlice(subject))
	// Output: [1 2 3]
}

func ExampleEnumerator_Select() {
	subject := AsEnumerable('a', 'b', 'c').Enumerate(nil)
	const offset = 'a' - 1
	results := subject.Select(func(a interface{}) interface{} {
		return a.(rune) - offset
	})

	fmt.Println(results.ToSlice())
	// Output: [1 2 3]
}

func ExampleEnumerator_SelectMany() {

	type BrewHouse struct {
		Name  string
		Beers Enumerable
	}

	breweries := AsEnumerable(
		BrewHouse{
			"Mac & Jacks",
			AsEnumerable(
				"African Amber",
				"Ibis IPA",
			),
		},
		BrewHouse{
			"Post Doc",
			AsEnumerable(
				"Prereq Pale",
			),
		},
		BrewHouse{
			"Resonate",
			AsEnumerable(
				"Comfortably Numb IPA",
				"Lithium Altbier",
			),
		},
		BrewHouse{
			"Triplehorn",
			AsEnumerable(
				"Samson",
				"Pepper Belly",
			),
		},
	)

	beers := breweries.Enumerate(nil).SelectMany(func(brewer interface{}) Enumerator {
		return brewer.(BrewHouse).Beers.Enumerate(nil)
	})

	for beer := range beers {
		fmt.Println(beer)
	}

	// Output:
	// African Amber
	// Ibis IPA
	// Prereq Pale
	// Comfortably Numb IPA
	// Lithium Altbier
	// Samson
	// Pepper Belly
}

func ExampleSkip() {
	done := make(chan struct{})
	defer close(done)

	trimmed := Take(Skip(Fibonacci, 1), 3)
	for entry := range trimmed.Enumerate(done) {
		fmt.Println(entry)
	}
	// Output:
	// 1
	// 1
	// 2
}

func ExampleEnumerator_Skip() {
	subject := AsEnumerable(1, 2, 3, 4, 5, 6, 7)
	skipped := subject.Enumerate(nil).Skip(5)
	for entry := range skipped {
		fmt.Println(entry)
	}
	// Output:
	// 6
	// 7
}

func ExampleTake() {
	done := make(chan struct{})
	defer close(done)

	taken := Take(Fibonacci, 4)
	for entry := range taken.Enumerate(done) {
		fmt.Println(entry)
	}
	// Output:
	// 0
	// 1
	// 1
	// 2
}

func ExampleEnumerator_Take() {
	done := make(chan struct{})
	defer close(done)

	taken := Fibonacci.Enumerate(done).Skip(4).Take(2)
	for entry := range taken {
		fmt.Println(entry)
	}
	// Output:
	// 3
	// 5
}

func ExampleTakeWhile() {
	taken := TakeWhile(Fibonacci, func(x interface{}, n uint) bool {
		return x.(int) < 10
	})
	for entry := range taken.Enumerate(nil) {
		fmt.Println(entry)
	}
	// Output:
	// 0
	// 1
	// 1
	// 2
	// 3
	// 5
	// 8
}

func ExampleEnumerator_TakeWhile() {
	taken := Fibonacci.Enumerate(nil).TakeWhile(func(x interface{}, n uint) bool {
		return x.(int) < 6
	})
	for entry := range taken {
		fmt.Println(entry)
	}
	// Output:
	// 0
	// 1
	// 1
	// 2
	// 3
	// 5
}

func ExampleEnumerator_Tee() {
	base := AsEnumerable(1, 2, 4)
	left, right := base.Enumerate(nil).Tee()
	var wg sync.WaitGroup
	wg.Add(2)

	product := 1
	go func() {
		for x := range left {
			product *= x.(int)
		}
		wg.Done()
	}()

	sum := 0
	go func() {
		for x := range right {
			sum += x.(int)
		}
		wg.Done()
	}()

	wg.Wait()

	fmt.Printf("Sum: %d\n", sum)
	fmt.Printf("Product: %d\n", product)
	// Output:
	// Sum: 7
	// Product: 8
}

func ExampleUCount() {
	subject := NewStack(9, 'a', "str1")
	result := UCount(subject, func(a interface{}) bool {
		_, ok := a.(string)
		return ok
	})
	fmt.Println(result)
	// Output: 1
}

func ExampleEnumerator_UCount() {
	subject := AsEnumerable("str1", "str1", "str2")
	count1 := subject.Enumerate(nil).UCount(func(a interface{}) bool {
		return a == "str1"
	})
	fmt.Println(count1)
	// Output: 2
}

func ExampleUCountAll() {
	subject := NewStack(8, 9, 10, 11)
	fmt.Println(UCountAll(subject))
	// Output: 4
}

func ExampleEnumerator_UCountAll() {
	subject := AsEnumerable('a', 2, "str1")
	fmt.Println(subject.Enumerate(nil).UCountAll())
	// Output: 3
}

func ExampleEnumerator_Where() {
	done := make(chan struct{})
	defer close(done)
	results := Fibonacci.Enumerate(done).Where(func(a interface{}) bool {
		return a.(int) > 8
	}).Take(3)
	fmt.Println(results.ToSlice())
	// Output: [13 21 34]
}

func ExampleWhere() {
	results := Where(AsEnumerable(1, 2, 3, 4, 5), func(a interface{}) bool {
		return a.(int) < 3
	})
	fmt.Println(ToSlice(results))
	// Output: [1 2]
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"html/template"
	"log"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/nlopes/slack"
)

const (
	MessageGroupDisplayTimestampFormat = "3:04pm"
	MessageTextBlockquotePrefix1       = ">"
	MessageTextBlockquotePrefix2       = ">>>"
	MessageTextControlRegexp           = "<(.*?)>"
	MessageTextEmojiRegexp             = ":([a-z0-9_\\-+]+):"
	MessageTextBoldRegexp              = "\\*([^*<<>]+)\\*"
	MessageTextItalicRegexp            = "_([^_<>]+)_"
	MessageTextStrikethroughRegexp     = "~([^~<>]+)~"
	MessageTextInlineCodeRegexp        = "`([^`<>]+)`"
)

var controlRegexp *regexp.Regexp
var emojiRegexp *regexp.Regexp
var boldRegexp *regexp.Regexp
var italicRegexp *regexp.Regexp
var strikethroughRegexp *regexp.Regexp
var inlineCodeRegexp *regexp.Regexp

func init() {
	controlRegexp = regexp.MustCompile(MessageTextControlRegexp)
	emojiRegexp = regexp.MustCompile(MessageTextEmojiRegexp)
	boldRegexp = regexp.MustCompile(MessageTextBoldRegexp)
	italicRegexp = regexp.MustCompile(MessageTextItalicRegexp)
	strikethroughRegexp = regexp.MustCompile(MessageTextStrikethroughRegexp)
	inlineCodeRegexp = regexp.MustCompile(MessageTextInlineCodeRegexp)
}

func textToHtml(text string, truncate bool, slackClient *slack.Client) template.HTML {
	if truncate && len(text) > 700 {
		text = fmt.Sprintf("%s...", text[:700])
	}
	lines := strings.Split(text, "\n")
	if truncate && len(lines) > 5 {
		lines = append(lines[:5], "...")
	}
	htmlPieces := []string{}
	for i, line := range lines {
		linePrefix := ""
		lineSuffix := ""
		if strings.HasPrefix(line, MessageTextBlockquotePrefix1) ||
			strings.HasPrefix(line, MessageTextBlockquotePrefix2) {
			line = strings.TrimPrefix(line, MessageTextBlockquotePrefix1)
			line = strings.TrimPrefix(line, MessageTextBlockquotePrefix2)
			if line == "" {
				// Ensure that even empty blockquote lines get rendered.
				line = "\u200b"
			}
			linePrefix = fmt.Sprintf("
",
				Style("message.blockquote"))
			lineSuffix = "
"
		} else {
			if i != 0 {
				lineSuffix = "
"
			}
		}
		line = controlRegexp.ReplaceAllStringFunc(line, func(control string) string {
			control = control[1 : len(control)-1]
			anchorText := ""
			pipeIndex := strings.LastIndex(control, "|")
			if pipeIndex != -1 {
				anchorText = control[pipeIndex+1:]
				control = control[:pipeIndex]
			}
			if strings.HasPrefix(control, "@U") {
				userId := strings.TrimPrefix(control, "@")
				userLookup, err := newUserLookup(slackClient)
				if err == nil {
					user, err := userLookup.GetUser(userId)
					if err == nil {
						anchorText = fmt.Sprintf("@%s", user.Name)
						authTest, err := slackClient.AuthTest()
						if err == nil {
							control = fmt.Sprintf("%s/team/%s", authTest.URL, user.Name)
						} else {
							log.Printf("Could get team URL: %s", err)
						}
					} else {
						log.Printf("Could not render user mention: %s", err)
					}
				} else {
					log.Printf("Could not render user mention: %s", err)
				}
			} else if strings.HasPrefix(control, "#C") {
				channelId := strings.TrimPrefix(control, "#")
				channel, err := slackClient.GetChannelInfo(channelId)
				if err == nil {
					anchorText = fmt.Sprintf("#%s", channel.Name)
					authTest, err := slackClient.AuthTest()
					if err == nil {
						control = fmt.Sprintf("%s/team/%s", authTest.URL, channel.Name)
					} else {
						log.Printf("Could get team URL: %s", err)
					}
				} else {
					log.Printf("Could not render channel mention: %s", err)
				}
			} else if strings.HasPrefix(control, "!") {
				command := strings.TrimPrefix(control, "!")
				return fmt.Sprintf("@%s", command)
			}
			if anchorText == "" {
				anchorText = control
			}
			return fmt.Sprintf("%s",
				control, Style("message.link"), anchorText)
		})
		line = emojiRegexp.ReplaceAllStringFunc(line, func(emojiString string) string {
			shortName := emojiString[1 : len(emojiString)-1]
			if emojiHtml, err := getEmojiHtml(shortName, slackClient); err == nil {
				return fmt.Sprintf("%s", emojiString, emojiHtml)
			}
			return emojiString
		})
		line = boldRegexp.ReplaceAllString(line, "$1")
		line = italicRegexp.ReplaceAllString(line, "$1")
		line = strikethroughRegexp.ReplaceAllString(line, "$1")
		line = inlineCodeRegexp.ReplaceAllString(
			line,
			fmt.Sprintf("$1", Style("message.code")))

		htmlPieces = append(htmlPieces, linePrefix)
		// Slack's API claims that all HTML is already escaped
		htmlPieces = append(htmlPieces, line)
		htmlPieces = append(htmlPieces, lineSuffix)
	}
	return template.HTML(strings.Join(htmlPieces, ""))
}

type Message struct {
	*slack.Message
	slackClient *slack.Client
	account     *Account
}

func (m *Message) TimestampTime() time.Time {
	floatTimestamp, err := strconv.ParseFloat(m.Timestamp, 64)
	if err != nil {
		log.Println("Could not parse timestamp \"%s\".", m.Timestamp, err)
		return time.Time{}
	}
	return time.Unix(int64(floatTimestamp), 0).In(m.account.TimezoneLocation)
}

func (m *Message) TextHtml() template.HTML {
	return textToHtml(m.Text, false, m.slackClient)
}

func (m *Message) StylePath() string {
	if strings.HasPrefix(m.SubType, "channel_") || strings.HasPrefix(m.SubType, "group_") {
		return "message.automated"
	}
	if m.SubType == "me_message" {
		return "message.me"
	}
	return ""
}

func (m *Message) MessageAttachments() []*MessageAttachment {
	attachments := make([]*MessageAttachment, 0, len(m.Attachments))
	for i := range m.Attachments {
		attachments = append(
			attachments, &MessageAttachment{&m.Attachments[i], m.slackClient})
	}
	return attachments
}

func (m *Message) MessageFile() *MessageFile {
	if m.File == nil {
		return nil
	}
	return &MessageFile{m.File, m.slackClient, m.account}
}

func (m *Message) MessageReactions() []*MessageReaction {
	reactions := make([]*MessageReaction, 0, len(m.Reactions))
	for i := range m.Reactions {
		reactions = append(
			reactions, &MessageReaction{&m.Reactions[i], m.slackClient})
	}
	return reactions
}

type MessageAttachment struct {
	*slack.Attachment
	slackClient *slack.Client
}

func (a *MessageAttachment) TitleHtml() template.HTML {
	return textToHtml(a.Title, false, a.slackClient)
}

func (a *MessageAttachment) PretextHtml() template.HTML {
	return textToHtml(a.Pretext, false, a.slackClient)
}

func (a *MessageAttachment) TextHtml() template.HTML {
	return textToHtml(a.Text, true, a.slackClient)
}

func (a *MessageAttachment) FieldsHtml() template.HTML {
	htmlPieces := []string{}
	inTable := false
	currentRowCellCount := 0
	for _, field := range a.Fields {
		fieldHtml := fmt.Sprintf(
			"
%s

%s
",
			Style("message.attachment.field.title"),
			textToHtml(field.Title, false, a.slackClient),
			textToHtml(field.Value, false, a.slackClient))
		if field.Short {
			if !inTable {
				htmlPieces = append(htmlPieces, fmt.Sprintf(
					"

",
					Style("message.attachment.field.table")))
				inTable = true
				currentRowCellCount = 0
			}
			if currentRowCellCount == 2 {
				htmlPieces = append(htmlPieces, "

")
				currentRowCellCount = 0
			}
			htmlPieces = append(htmlPieces, "")
			htmlPieces = append(htmlPieces, fieldHtml)
			htmlPieces = append(htmlPieces, "	")
			currentRowCellCount += 1
		} else {
			if inTable {
				htmlPieces = append(htmlPieces, "

")
				inTable = false
			}
			htmlPieces = append(htmlPieces, fieldHtml)
		}
	}
	if inTable {
		htmlPieces = append(htmlPieces, "

")
	}
	return template.HTML(strings.Join(htmlPieces, ""))
}

type MessageFile struct {
	*slack.File
	slackClient *slack.Client
	account     *Account
}

func (f *MessageFile) ThumbnailUrl() (string, error) {
	if f.Thumb360 == "" {
		return "", nil
	}
	ref := FileUrlRef{f.ID, f.account.SlackUserId}
	encodedRef, err := ref.Encode()
	if err != nil {
		return "", err
	}
	return AbsoluteRouteUrl("archive-file-thumbnail", "ref", encodedRef)
}

func (f *MessageFile) ThumbnailWidth() int {
	return f.Thumb360W
}

func (f *MessageFile) ThumbnailHeight() int {
	return f.Thumb360H
}

func (f *MessageFile) PreviewHtml() template.HTML {
	if f.PreviewHighlight != "" {
		return template.HTML(f.PreviewHighlight)
	}
	return template.HTML(f.Preview)
}

type MessageReaction struct {
	*slack.ItemReaction
	slackClient *slack.Client
}

func (r *MessageReaction) Emoji() template.HTML {
	if emojiHtml, err := getEmojiHtml(r.Name, r.slackClient); err == nil {
		return template.HTML(emojiHtml)
	}
	return template.HTML(r.Name)
}

func (r *MessageReaction) Summary() (string, error) {
	userLookup, err := newUserLookup(r.slackClient)
	if err != nil {
		return "", err
	}
	names := make([]string, len(r.Users))
	for i := range r.Users {
		if user, err := userLookup.GetUser(r.Users[i]); err == nil {
			names[i] = user.Name
		} else {
			names[i] = r.Users[i]
		}
	}
	return fmt.Sprintf("%s reacted with :%s:",
		strings.Join(names, ", "), r.Name), nil
}

type MessageGroup struct {
	Messages []*Message
	Author   *slack.User
}

func safeFormattedDate(date string) string {
	// Insert zero-width spaces every few characters so that Apple Data
	// Detectors and Gmail's calendar event dection don't pick up on these
	// dates.
	var buffer bytes.Buffer
	dateLength := len(date)
	for i := 0; i < dateLength; i += 2 {
		if i == dateLength-1 {
			buffer.WriteString(date[i : i+1])
		} else {
			buffer.WriteString(date[i : i+2])
			if date[i] != ' ' && date[i+1] != ' ' && i < dateLength-2 {
				buffer.WriteString("\u200b")
			}
		}
	}
	return buffer.String()
}

func (mg *MessageGroup) shouldContainMessage(message *Message, messageAuthor *slack.User) bool {
	if messageAuthor.ID != mg.Author.ID {
		return false
	}
	lastMessage := mg.Messages[len(mg.Messages)-1]
	timestampDelta := message.TimestampTime().Sub(lastMessage.TimestampTime())
	if timestampDelta > time.Minute*10 {
		return false
	}
	return true
}

func (mg *MessageGroup) FromBot() bool {
	return mg.Messages[0].SubType == "bot_message"
}

func (mg *MessageGroup) DisplayTimestamp() string {
	return safeFormattedDate(mg.Messages[0].TimestampTime().Format(
		MessageGroupDisplayTimestampFormat))
}

func groupMessages(messages []*slack.Message, slackClient *slack.Client, account *Account) ([]*MessageGroup, error) {
	var currentGroup *MessageGroup = nil
	groups := make([]*MessageGroup, 0)
	userLookup, err := newUserLookup(slackClient)
	if err != nil {
		return nil, err
	}
	for i := range messages {
		message := &Message{messages[i], slackClient, account}
		if message.Hidden {
			continue
		}
		messageAuthor, _ := userLookup.GetUserForMessage(messages[i])
		if messageAuthor == nil {
			log.Printf("Could not determine author for message type %s "+
				"(subtype %s), skipping", message.Type, message.SubType)
			continue
		}
		if currentGroup == nil || !currentGroup.shouldContainMessage(message, messageAuthor) {
			currentGroup = &MessageGroup{
				Messages: make([]*Message, 0),
				Author:   messageAuthor,
			}
			groups = append(groups, currentGroup)
		}
		currentGroup.Messages = append(currentGroup.Messages, message)
	}
	return groups, nil
}
<commit_msg>Remove stray character.<commit_after>package main

import (
	"bytes"
	"fmt"
	"html/template"
	"log"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/nlopes/slack"
)

const (
	MessageGroupDisplayTimestampFormat = "3:04pm"
	MessageTextBlockquotePrefix1       = ">"
	MessageTextBlockquotePrefix2       = ">>>"
	MessageTextControlRegexp           = "<(.*?)>"
	MessageTextEmojiRegexp             = ":([a-z0-9_\\-+]+):"
	MessageTextBoldRegexp              = "\\*([^*<>]+)\\*"
	MessageTextItalicRegexp            = "_([^_<>]+)_"
	MessageTextStrikethroughRegexp     = "~([^~<>]+)~"
	MessageTextInlineCodeRegexp        = "`([^`<>]+)`"
)

var controlRegexp *regexp.Regexp
var emojiRegexp *regexp.Regexp
var boldRegexp *regexp.Regexp
var italicRegexp *regexp.Regexp
var strikethroughRegexp *regexp.Regexp
var inlineCodeRegexp *regexp.Regexp

func init() {
	controlRegexp = regexp.MustCompile(MessageTextControlRegexp)
	emojiRegexp = regexp.MustCompile(MessageTextEmojiRegexp)
	boldRegexp = regexp.MustCompile(MessageTextBoldRegexp)
	italicRegexp = regexp.MustCompile(MessageTextItalicRegexp)
	strikethroughRegexp = regexp.MustCompile(MessageTextStrikethroughRegexp)
	inlineCodeRegexp = regexp.MustCompile(MessageTextInlineCodeRegexp)
}

func textToHtml(text string, truncate bool, slackClient *slack.Client) template.HTML {
	if truncate && len(text) > 700 {
		text = fmt.Sprintf("%s...", text[:700])
	}
	lines := strings.Split(text, "\n")
	if truncate && len(lines) > 5 {
		lines = append(lines[:5], "...")
	}
	htmlPieces := []string{}
	for i, line := range lines {
		linePrefix := ""
		lineSuffix := ""
		if strings.HasPrefix(line, MessageTextBlockquotePrefix1) ||
			strings.HasPrefix(line, MessageTextBlockquotePrefix2) {
			line = strings.TrimPrefix(line, MessageTextBlockquotePrefix1)
			line = strings.TrimPrefix(line, MessageTextBlockquotePrefix2)
			if line == "" {
				// Ensure that even empty blockquote lines get rendered.
				line = "\u200b"
			}
			linePrefix = fmt.Sprintf("
",
				Style("message.blockquote"))
			lineSuffix = "
"
		} else {
			if i != 0 {
				lineSuffix = "
"
			}
		}
		line = controlRegexp.ReplaceAllStringFunc(line, func(control string) string {
			control = control[1 : len(control)-1]
			anchorText := ""
			pipeIndex := strings.LastIndex(control, "|")
			if pipeIndex != -1 {
				anchorText = control[pipeIndex+1:]
				control = control[:pipeIndex]
			}
			if strings.HasPrefix(control, "@U") {
				userId := strings.TrimPrefix(control, "@")
				userLookup, err := newUserLookup(slackClient)
				if err == nil {
					user, err := userLookup.GetUser(userId)
					if err == nil {
						anchorText = fmt.Sprintf("@%s", user.Name)
						authTest, err := slackClient.AuthTest()
						if err == nil {
							control = fmt.Sprintf("%s/team/%s", authTest.URL, user.Name)
						} else {
							log.Printf("Could get team URL: %s", err)
						}
					} else {
						log.Printf("Could not render user mention: %s", err)
					}
				} else {
					log.Printf("Could not render user mention: %s", err)
				}
			} else if strings.HasPrefix(control, "#C") {
				channelId := strings.TrimPrefix(control, "#")
				channel, err := slackClient.GetChannelInfo(channelId)
				if err == nil {
					anchorText = fmt.Sprintf("#%s", channel.Name)
					authTest, err := slackClient.AuthTest()
					if err == nil {
						control = fmt.Sprintf("%s/team/%s", authTest.URL, channel.Name)
					} else {
						log.Printf("Could get team URL: %s", err)
					}
				} else {
					log.Printf("Could not render channel mention: %s", err)
				}
			} else if strings.HasPrefix(control, "!") {
				command := strings.TrimPrefix(control, "!")
				return fmt.Sprintf("@%s", command)
			}
			if anchorText == "" {
				anchorText = control
			}
			return fmt.Sprintf("%s",
				control, Style("message.link"), anchorText)
		})
		line = emojiRegexp.ReplaceAllStringFunc(line, func(emojiString string) string {
			shortName := emojiString[1 : len(emojiString)-1]
			if emojiHtml, err := getEmojiHtml(shortName, slackClient); err == nil {
				return fmt.Sprintf("%s", emojiString, emojiHtml)
			}
			return emojiString
		})
		line = boldRegexp.ReplaceAllString(line, "$1")
		line = italicRegexp.ReplaceAllString(line, "$1")
		line = strikethroughRegexp.ReplaceAllString(line, "$1")
		line = inlineCodeRegexp.ReplaceAllString(
			line,
			fmt.Sprintf("$1", Style("message.code")))

		htmlPieces = append(htmlPieces, linePrefix)
		// Slack's API claims that all HTML is already escaped
		htmlPieces = append(htmlPieces, line)
		htmlPieces = append(htmlPieces, lineSuffix)
	}
	return template.HTML(strings.Join(htmlPieces, ""))
}

type Message struct {
	*slack.Message
	slackClient *slack.Client
	account     *Account
}

func (m *Message) TimestampTime() time.Time {
	floatTimestamp, err := strconv.ParseFloat(m.Timestamp, 64)
	if err != nil {
		log.Println("Could not parse timestamp \"%s\".", m.Timestamp, err)
		return time.Time{}
	}
	return time.Unix(int64(floatTimestamp), 0).In(m.account.TimezoneLocation)
}

func (m *Message) TextHtml() template.HTML {
	return textToHtml(m.Text, false, m.slackClient)
}

func (m *Message) StylePath() string {
	if strings.HasPrefix(m.SubType, "channel_") || strings.HasPrefix(m.SubType, "group_") {
		return "message.automated"
	}
	if m.SubType == "me_message" {
		return "message.me"
	}
	return ""
}

func (m *Message) MessageAttachments() []*MessageAttachment {
	attachments := make([]*MessageAttachment, 0, len(m.Attachments))
	for i := range m.Attachments {
		attachments = append(
			attachments, &MessageAttachment{&m.Attachments[i], m.slackClient})
	}
	return attachments
}

func (m *Message) MessageFile() *MessageFile {
	if m.File == nil {
		return nil
	}
	return &MessageFile{m.File, m.slackClient, m.account}
}

func (m *Message) MessageReactions() []*MessageReaction {
	reactions := make([]*MessageReaction, 0, len(m.Reactions))
	for i := range m.Reactions {
		reactions = append(
			reactions, &MessageReaction{&m.Reactions[i], m.slackClient})
	}
	return reactions
}

type MessageAttachment struct {
	*slack.Attachment
	slackClient *slack.Client
}

func (a *MessageAttachment) TitleHtml() template.HTML {
	return textToHtml(a.Title, false, a.slackClient)
}

func (a *MessageAttachment) PretextHtml() template.HTML {
	return textToHtml(a.Pretext, false, a.slackClient)
}

func (a *MessageAttachment) TextHtml() template.HTML {
	return textToHtml(a.Text, true, a.slackClient)
}

func (a *MessageAttachment) FieldsHtml() template.HTML {
	htmlPieces := []string{}
	inTable := false
	currentRowCellCount := 0
	for _, field := range a.Fields {
		fieldHtml := fmt.Sprintf(
			"
%s

%s
",
			Style("message.attachment.field.title"),
			textToHtml(field.Title, false, a.slackClient),
			textToHtml(field.Value, false, a.slackClient))
		if field.Short {
			if !inTable {
				htmlPieces = append(htmlPieces, fmt.Sprintf(
					"

",
					Style("message.attachment.field.table")))
				inTable = true
				currentRowCellCount = 0
			}
			if currentRowCellCount == 2 {
				htmlPieces = append(htmlPieces, "

")
				currentRowCellCount = 0
			}
			htmlPieces = append(htmlPieces, "")
			htmlPieces = append(htmlPieces, fieldHtml)
			htmlPieces = append(htmlPieces, "	")
			currentRowCellCount += 1
		} else {
			if inTable {
				htmlPieces = append(htmlPieces, "

")
				inTable = false
			}
			htmlPieces = append(htmlPieces, fieldHtml)
		}
	}
	if inTable {
		htmlPieces = append(htmlPieces, "

")
	}
	return template.HTML(strings.Join(htmlPieces, ""))
}

type MessageFile struct {
	*slack.File
	slackClient *slack.Client
	account     *Account
}

func (f *MessageFile) ThumbnailUrl() (string, error) {
	if f.Thumb360 == "" {
		return "", nil
	}
	ref := FileUrlRef{f.ID, f.account.SlackUserId}
	encodedRef, err := ref.Encode()
	if err != nil {
		return "", err
	}
	return AbsoluteRouteUrl("archive-file-thumbnail", "ref", encodedRef)
}

func (f *MessageFile) ThumbnailWidth() int {
	return f.Thumb360W
}

func (f *MessageFile) ThumbnailHeight() int {
	return f.Thumb360H
}

func (f *MessageFile) PreviewHtml() template.HTML {
	if f.PreviewHighlight != "" {
		return template.HTML(f.PreviewHighlight)
	}
	return template.HTML(f.Preview)
}

type MessageReaction struct {
	*slack.ItemReaction
	slackClient *slack.Client
}

func (r *MessageReaction) Emoji() template.HTML {
	if emojiHtml, err := getEmojiHtml(r.Name, r.slackClient); err == nil {
		return template.HTML(emojiHtml)
	}
	return template.HTML(r.Name)
}

func (r *MessageReaction) Summary() (string, error) {
	userLookup, err := newUserLookup(r.slackClient)
	if err != nil {
		return "", err
	}
	names := make([]string, len(r.Users))
	for i := range r.Users {
		if user, err := userLookup.GetUser(r.Users[i]); err == nil {
			names[i] = user.Name
		} else {
			names[i] = r.Users[i]
		}
	}
	return fmt.Sprintf("%s reacted with :%s:",
		strings.Join(names, ", "), r.Name), nil
}

type MessageGroup struct {
	Messages []*Message
	Author   *slack.User
}

func safeFormattedDate(date string) string {
	// Insert zero-width spaces every few characters so that Apple Data
	// Detectors and Gmail's calendar event dection don't pick up on these
	// dates.
	var buffer bytes.Buffer
	dateLength := len(date)
	for i := 0; i < dateLength; i += 2 {
		if i == dateLength-1 {
			buffer.WriteString(date[i : i+1])
		} else {
			buffer.WriteString(date[i : i+2])
			if date[i] != ' ' && date[i+1] != ' ' && i < dateLength-2 {
				buffer.WriteString("\u200b")
			}
		}
	}
	return buffer.String()
}

func (mg *MessageGroup) shouldContainMessage(message *Message, messageAuthor *slack.User) bool {
	if messageAuthor.ID != mg.Author.ID {
		return false
	}
	lastMessage := mg.Messages[len(mg.Messages)-1]
	timestampDelta := message.TimestampTime().Sub(lastMessage.TimestampTime())
	if timestampDelta > time.Minute*10 {
		return false
	}
	return true
}

func (mg *MessageGroup) FromBot() bool {
	return mg.Messages[0].SubType == "bot_message"
}

func (mg *MessageGroup) DisplayTimestamp() string {
	return safeFormattedDate(mg.Messages[0].TimestampTime().Format(
		MessageGroupDisplayTimestampFormat))
}

func groupMessages(messages []*slack.Message, slackClient *slack.Client, account *Account) ([]*MessageGroup, error) {
	var currentGroup *MessageGroup = nil
	groups := make([]*MessageGroup, 0)
	userLookup, err := newUserLookup(slackClient)
	if err != nil {
		return nil, err
	}
	for i := range messages {
		message := &Message{messages[i], slackClient, account}
		if message.Hidden {
			continue
		}
		messageAuthor, _ := userLookup.GetUserForMessage(messages[i])
		if messageAuthor == nil {
			log.Printf("Could not determine author for message type %s "+
				"(subtype %s), skipping", message.Type, message.SubType)
			continue
		}
		if currentGroup == nil || !currentGroup.shouldContainMessage(message, messageAuthor) {
			currentGroup = &MessageGroup{
				Messages: make([]*Message, 0),
				Author:   messageAuthor,
			}
			groups = append(groups, currentGroup)
		}
		currentGroup.Messages = append(currentGroup.Messages, message)
	}
	return groups, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes 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 sessionaffinity

import (
	"regexp"

	networking "k8s.io/api/networking/v1beta1"
	"k8s.io/klog/v2"

	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
	"k8s.io/ingress-nginx/internal/ingress/resolver"
)

const (
	annotationAffinityType = "affinity"
	annotationAffinityMode = "affinity-mode"
	// If a cookie with this name exists,
	// its value is used as an index into the list of available backends.
	annotationAffinityCookieName = "session-cookie-name"

	defaultAffinityCookieName = "INGRESSCOOKIE"

	// This is used to control the cookie expires, its value is a number of seconds until the
	// cookie expires
	annotationAffinityCookieExpires = "session-cookie-expires"

	// This is used to control the cookie expires, its value is a number of seconds until the
	// cookie expires
	annotationAffinityCookieMaxAge = "session-cookie-max-age"

	// This is used to control the cookie path when use-regex is set to true
	annotationAffinityCookiePath = "session-cookie-path"

	// This is used to control the SameSite attribute of the cookie
	annotationAffinityCookieSameSite = "session-cookie-samesite"

	// This is used to control whether SameSite=None should be conditionally applied based on the User-Agent
	annotationAffinityCookieConditionalSameSiteNone = "session-cookie-conditional-samesite-none"

	// This is used to control the cookie change after request failure
	annotationAffinityCookieChangeOnFailure = "session-cookie-change-on-failure"
)

var (
	affinityCookieExpiresRegex = regexp.MustCompile(`(^0|-?[1-9]\d*$)`)
)

// Config describes the per ingress session affinity config
type Config struct {
	// The type of affinity that will be used
	Type string `json:"type"`
	// The affinity mode, i.e. how sticky a session is
	Mode string `json:"mode"`
	Cookie
}

// Cookie describes the Config of cookie type affinity
type Cookie struct {
	// The name of the cookie that will be used in case of cookie affinity type.
	Name string `json:"name"`
	// The time duration to control cookie expires
	Expires string `json:"expires"`
	// The number of seconds until the cookie expires
	MaxAge string `json:"maxage"`
	// The path that a cookie will be set on
	Path string `json:"path"`
	// Flag that allows cookie regeneration on request failure
	ChangeOnFailure bool `json:"changeonfailure"`
	// SameSite attribute value
	SameSite string `json:"samesite"`
	// Flag that conditionally applies SameSite=None attribute on cookie if user agent accepts it.
	ConditionalSameSiteNone bool `json:"conditional-samesite-none"`
}

// cookieAffinityParse gets the annotation values related to Cookie Affinity
// It also sets default values when no value or incorrect value is found
func (a affinity) cookieAffinityParse(ing *networking.Ingress) *Cookie {
	var err error

	cookie := &Cookie{}

	cookie.Name, err = parser.GetStringAnnotation(annotationAffinityCookieName, ing)
	if err != nil {
		klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieExpires, "default", defaultAffinityCookieName)
		cookie.Name = defaultAffinityCookieName
	}

	cookie.Expires, err = parser.GetStringAnnotation(annotationAffinityCookieExpires, ing)
	if err != nil || !affinityCookieExpiresRegex.MatchString(cookie.Expires) {
		klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieExpires)
		cookie.Expires = ""
	}

	cookie.MaxAge, err = parser.GetStringAnnotation(annotationAffinityCookieMaxAge, ing)
	if err != nil || !affinityCookieExpiresRegex.MatchString(cookie.MaxAge) {
		klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieMaxAge)
		cookie.MaxAge = ""
	}

	cookie.Path, err = parser.GetStringAnnotation(annotationAffinityCookiePath, ing)
	if err != nil {
		klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieMaxAge)
	}

	cookie.SameSite, err = parser.GetStringAnnotation(annotationAffinityCookieSameSite, ing)
	if err != nil {
		klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieSameSite)
	}

	cookie.ConditionalSameSiteNone, err = parser.GetBoolAnnotation(annotationAffinityCookieConditionalSameSiteNone, ing)
	if err != nil {
		klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieConditionalSameSiteNone)
	}

	cookie.ChangeOnFailure, err = parser.GetBoolAnnotation(annotationAffinityCookieChangeOnFailure, ing)
	if err != nil {
		klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieChangeOnFailure)
	}

	return cookie
}

// NewParser creates a new Affinity annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
	return affinity{r}
}

type affinity struct {
	r resolver.Resolver
}

// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure the affinity directives
func (a affinity) Parse(ing *networking.Ingress) (interface{}, error) {
	cookie := &Cookie{}
	// Check the type of affinity that will be used
	at, err := parser.GetStringAnnotation(annotationAffinityType, ing)
	if err != nil {
		at = ""
	}

	// Check the affinity mode that will be used
	am, err := parser.GetStringAnnotation(annotationAffinityMode, ing)
	if err != nil {
		am = ""
	}

	switch at {
	case "cookie":
		cookie = a.cookieAffinityParse(ing)
	default:
		klog.V(3).InfoS("No default affinity found", "ingress", ing.Name)

	}

	return &Config{
		Type:   at,
		Mode:   am,
		Cookie: *cookie,
	}, nil
}
<commit_msg>Fix log printing error<commit_after>/*
Copyright 2016 The Kubernetes 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 sessionaffinity

import (
	"regexp"

	networking "k8s.io/api/networking/v1beta1"
	"k8s.io/klog/v2"

	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
	"k8s.io/ingress-nginx/internal/ingress/resolver"
)

const (
	annotationAffinityType = "affinity"
	annotationAffinityMode = "affinity-mode"
	// If a cookie with this name exists,
	// its value is used as an index into the list of available backends.
	annotationAffinityCookieName = "session-cookie-name"

	defaultAffinityCookieName = "INGRESSCOOKIE"

	// This is used to control the cookie expires, its value is a number of seconds until the
	// cookie expires
	annotationAffinityCookieExpires = "session-cookie-expires"

	// This is used to control the cookie expires, its value is a number of seconds until the
	// cookie expires
	annotationAffinityCookieMaxAge = "session-cookie-max-age"

	// This is used to control the cookie path when use-regex is set to true
	annotationAffinityCookiePath = "session-cookie-path"

	// This is used to control the SameSite attribute of the cookie
	annotationAffinityCookieSameSite = "session-cookie-samesite"

	// This is used to control whether SameSite=None should be conditionally applied based on the User-Agent
	annotationAffinityCookieConditionalSameSiteNone = "session-cookie-conditional-samesite-none"

	// This is used to control the cookie change after request failure
	annotationAffinityCookieChangeOnFailure = "session-cookie-change-on-failure"
)

var (
	affinityCookieExpiresRegex = regexp.MustCompile(`(^0|-?[1-9]\d*$)`)
)

// Config describes the per ingress session affinity config
type Config struct {
	// The type of affinity that will be used
	Type string `json:"type"`
	// The affinity mode, i.e. how sticky a session is
	Mode string `json:"mode"`
	Cookie
}

// Cookie describes the Config of cookie type affinity
type Cookie struct {
	// The name of the cookie that will be used in case of cookie affinity type.
	Name string `json:"name"`
	// The time duration to control cookie expires
	Expires string `json:"expires"`
	// The number of seconds until the cookie expires
	MaxAge string `json:"maxage"`
	// The path that a cookie will be set on
	Path string `json:"path"`
	// Flag that allows cookie regeneration on request failure
	ChangeOnFailure bool `json:"changeonfailure"`
	// SameSite attribute value
	SameSite string `json:"samesite"`
	// Flag that conditionally applies SameSite=None attribute on cookie if user agent accepts it.
	ConditionalSameSiteNone bool `json:"conditional-samesite-none"`
}

// cookieAffinityParse gets the annotation values related to Cookie Affinity
// It also sets default values when no value or incorrect value is found
func (a affinity) cookieAffinityParse(ing *networking.Ingress) *Cookie {
	var err error

	cookie := &Cookie{}

	cookie.Name, err = parser.GetStringAnnotation(annotationAffinityCookieName, ing)
	if err != nil {
		klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieName, "default", defaultAffinityCookieName)
		cookie.Name = defaultAffinityCookieName
	}

	cookie.Expires, err = parser.GetStringAnnotation(annotationAffinityCookieExpires, ing)
	if err != nil || !affinityCookieExpiresRegex.MatchString(cookie.Expires) {
		klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieExpires)
		cookie.Expires = ""
	}

	cookie.MaxAge, err = parser.GetStringAnnotation(annotationAffinityCookieMaxAge, ing)
	if err != nil || !affinityCookieExpiresRegex.MatchString(cookie.MaxAge) {
		klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieMaxAge)
		cookie.MaxAge = ""
	}

	cookie.Path, err = parser.GetStringAnnotation(annotationAffinityCookiePath, ing)
	if err != nil {
		klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookiePath)
	}

	cookie.SameSite, err = parser.GetStringAnnotation(annotationAffinityCookieSameSite, ing)
	if err != nil {
		klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieSameSite)
	}

	cookie.ConditionalSameSiteNone, err = parser.GetBoolAnnotation(annotationAffinityCookieConditionalSameSiteNone, ing)
	if err != nil {
		klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieConditionalSameSiteNone)
	}

	cookie.ChangeOnFailure, err = parser.GetBoolAnnotation(annotationAffinityCookieChangeOnFailure, ing)
	if err != nil {
		klog.V(3).InfoS("Invalid or no annotation value found. Ignoring", "ingress", klog.KObj(ing), "annotation", annotationAffinityCookieChangeOnFailure)
	}

	return cookie
}

// NewParser creates a new Affinity annotation parser
func NewParser(r resolver.Resolver) parser.IngressAnnotation {
	return affinity{r}
}

type affinity struct {
	r resolver.Resolver
}

// ParseAnnotations parses the annotations contained in the ingress
// rule used to configure the affinity directives
func (a affinity) Parse(ing *networking.Ingress) (interface{}, error) {
	cookie := &Cookie{}
	// Check the type of affinity that will be used
	at, err := parser.GetStringAnnotation(annotationAffinityType, ing)
	if err != nil {
		at = ""
	}

	// Check the affinity mode that will be used
	am, err := parser.GetStringAnnotation(annotationAffinityMode, ing)
	if err != nil {
		am = ""
	}

	switch at {
	case "cookie":
		cookie = a.cookieAffinityParse(ing)
	default:
		klog.V(3).InfoS("No default affinity found", "ingress", ing.Name)

	}

	return &Config{
		Type:   at,
		Mode:   am,
		Cookie: *cookie,
	}, nil
}
<|endoftext|>


<commit_before>package chain_test

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/codemodus/chain"
	"golang.org/x/net/context"
)

var (
	bTxt0   = []byte("0")
	bTxt1   = []byte("1")
	bTxtA   = []byte("A")
	bTxtEnd = []byte("_END_")
)

func ctxWrapper0(n chain.Handler) chain.Handler {
	return chain.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		w.Write(bTxt0)
		n.ServeHTTPContext(ctx, w, r)
		w.Write(bTxt0)
	})
}

func ctxWrapper1(n chain.Handler) chain.Handler {
	return chain.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		w.Write(bTxt1)
		n.ServeHTTPContext(ctx, w, r)
		w.Write(bTxt1)
	})
}

func stdWrapperA(n http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write(bTxtA)
		n.ServeHTTP(w, r)
		w.Write(bTxtA)
	})
}

func emptyCtxWrapper(n chain.Handler) chain.Handler {
	return chain.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		n.ServeHTTPContext(ctx, w, r)
	})
}

func ctxEndPoint(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	w.Write(bTxtEnd)
	return
}

func TestChain(t *testing.T) {
	c0 := chain.New(context.Background(), ctxWrapper0)
	c1 := c0.Append(ctxWrapper1, chain.Meld(stdWrapperA))
	m := http.NewServeMux()
	r0 := "/0"
	r1 := "/1"
	m.Handle(r0, c0.EndFn(ctxEndPoint))
	m.Handle(r1, c1.EndFn(ctxEndPoint))
	s := httptest.NewServer(m)

	re0, err := http.Get(s.URL + r0)
	if err != nil {
		t.Error(err)
	}
	defer re0.Body.Close()
	rb0, err := ioutil.ReadAll(re0.Body)
	if err != nil {
		t.Error(err)
	}

	re1, err := http.Get(s.URL + r1)
	if err != nil {
		t.Error(err)
	}
	defer re1.Body.Close()
	rb1, err := ioutil.ReadAll(re1.Body)
	if err != nil {
		t.Error(err)
	}

	bb := &bytes.Buffer{}
	bb.Write(bTxt0)
	bb.Write(bTxtEnd)
	bb.Write(bTxt0)
	bb.Write(bTxt0)
	bb.Write(bTxt1)
	bb.Write(bTxtA)
	bb.Write(bTxtEnd)
	bb.Write(bTxtA)
	bb.Write(bTxt1)
	bb.Write(bTxt0)

	want := string(bb.Bytes())
	got := string(rb0) + string(rb1)
	if got != want {
		t.Errorf("Body = %v, want %v", got, want)
	}
}

func TestNilEnd(t *testing.T) {
	c0 := chain.New(context.Background(), emptyCtxWrapper)
	m := http.NewServeMux()
	r0 := "/0"
	r1 := "/1"
	m.Handle(r0, c0.End(nil))
	m.Handle(r1, c0.EndFn(nil))
	s := httptest.NewServer(m)

	re0, err := http.Get(s.URL + r0)
	if err != nil {
		t.Error(err)
	}
	defer re0.Body.Close()
	rs0 := re0.StatusCode

	want := http.StatusOK
	got := rs0
	if got != want {
		t.Errorf("Status Code = %v, want %v", got, want)
	}

	re1, err := http.Get(s.URL + r1)
	if err != nil {
		t.Error(err)
	}
	defer re1.Body.Close()
	rs1 := re1.StatusCode

	got = rs1
	if got != want {
		t.Errorf("Status Code = %v, want %v", got, want)
	}
}

func Example() {
	// ctxWrapper0 writes "0" to the response body before and after
	// ServeHTTPContext() is called.
	// ctxWrapper1 writes "1", and stdWrapperA writes "A", in the same manner
	// (though, stdWrapperA calls ServeHTTP()).
	// ctxEndPoint writes "_END_" to the response body and returns.
	chain0 := chain.New(context.Background(), ctxWrapper0)
	chain1 := chain0.Append(ctxWrapper1, chain.Meld(stdWrapperA))

	m := http.NewServeMux()
	m.Handle("/test0", chain0.EndFn(ctxEndPoint))
	m.Handle("/test01AEnd", chain1.EndFn(ctxEndPoint))

	s := httptest.NewServer(m)

	resp0, err := http.Get(s.URL + "/test0")
	if err != nil {
		fmt.Println(err)
	}
	defer resp0.Body.Close()
	rBody0, err := ioutil.ReadAll(resp0.Body)
	if err != nil {
		fmt.Println(err)
	}

	resp1, err := http.Get(s.URL + "/test01AEnd")
	if err != nil {
		fmt.Println(err)
	}
	defer resp1.Body.Close()
	rBody1, err := ioutil.ReadAll(resp1.Body)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Println("Chain 0:", string(rBody0))
	fmt.Println("Chain 1:", string(rBody1))

	// Output:
	// Chain 0: 0_END_0
	// Chain 1: 01A_END_A10
}
<commit_msg>Changed helper test function names again, and updated example comments.<commit_after>package chain_test

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/codemodus/chain"
	"golang.org/x/net/context"
)

var (
	bTxt0   = []byte("0")
	bTxt1   = []byte("1")
	bTxtA   = []byte("A")
	bTxtEnd = []byte("_END_")
)

func ctxHandlerWrapper0(n chain.Handler) chain.Handler {
	return chain.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		w.Write(bTxt0)
		n.ServeHTTPContext(ctx, w, r)
		w.Write(bTxt0)
	})
}

func ctxHandlerWrapper1(n chain.Handler) chain.Handler {
	return chain.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		w.Write(bTxt1)
		n.ServeHTTPContext(ctx, w, r)
		w.Write(bTxt1)
	})
}

func httpHandlerWrapperA(n http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write(bTxtA)
		n.ServeHTTP(w, r)
		w.Write(bTxtA)
	})
}

func emptyCtxHandlerWrapper(n chain.Handler) chain.Handler {
	return chain.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		n.ServeHTTPContext(ctx, w, r)
	})
}

func ctxHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	w.Write(bTxtEnd)
	return
}

func TestChain(t *testing.T) {
	c0 := chain.New(context.Background(), ctxHandlerWrapper0)
	c1 := c0.Append(ctxHandlerWrapper1, chain.Meld(httpHandlerWrapperA))
	m := http.NewServeMux()
	r0 := "/0"
	r1 := "/1"
	m.Handle(r0, c0.EndFn(ctxHandler))
	m.Handle(r1, c1.EndFn(ctxHandler))
	s := httptest.NewServer(m)

	re0, err := http.Get(s.URL + r0)
	if err != nil {
		t.Error(err)
	}
	defer re0.Body.Close()
	rb0, err := ioutil.ReadAll(re0.Body)
	if err != nil {
		t.Error(err)
	}

	re1, err := http.Get(s.URL + r1)
	if err != nil {
		t.Error(err)
	}
	defer re1.Body.Close()
	rb1, err := ioutil.ReadAll(re1.Body)
	if err != nil {
		t.Error(err)
	}

	bb := &bytes.Buffer{}
	bb.Write(bTxt0)
	bb.Write(bTxtEnd)
	bb.Write(bTxt0)
	bb.Write(bTxt0)
	bb.Write(bTxt1)
	bb.Write(bTxtA)
	bb.Write(bTxtEnd)
	bb.Write(bTxtA)
	bb.Write(bTxt1)
	bb.Write(bTxt0)

	want := string(bb.Bytes())
	got := string(rb0) + string(rb1)
	if got != want {
		t.Errorf("Body = %v, want %v", got, want)
	}
}

func TestNilEnd(t *testing.T) {
	c0 := chain.New(context.Background(), emptyCtxHandlerWrapper)
	m := http.NewServeMux()
	r0 := "/0"
	r1 := "/1"
	m.Handle(r0, c0.End(nil))
	m.Handle(r1, c0.EndFn(nil))
	s := httptest.NewServer(m)

	re0, err := http.Get(s.URL + r0)
	if err != nil {
		t.Error(err)
	}
	defer re0.Body.Close()
	rs0 := re0.StatusCode

	want := http.StatusOK
	got := rs0
	if got != want {
		t.Errorf("Status Code = %v, want %v", got, want)
	}

	re1, err := http.Get(s.URL + r1)
	if err != nil {
		t.Error(err)
	}
	defer re1.Body.Close()
	rs1 := re1.StatusCode

	got = rs1
	if got != want {
		t.Errorf("Status Code = %v, want %v", got, want)
	}
}

func Example() {
	// ctxHandlerWrapper0 writes "0" to the response body before and after
	// ServeHTTPContext() is called.
	// ctxHandlerWrapper1 writes "1" to the response body before and after
	// ServeHTTPContext() is called.
	// httpHandlerWrapperA writes "A" to the response body before and after
	// ServeHTTP() is called.
	// ctxHandler writes "_END_" to the response body and returns.
	chain0 := chain.New(context.Background(), ctxHandlerWrapper0)
	chain1 := chain0.Append(ctxHandlerWrapper1, chain.Meld(httpHandlerWrapperA))

	m := http.NewServeMux()
	m.Handle("/test0", chain0.EndFn(ctxHandler))
	m.Handle("/test01AEnd", chain1.EndFn(ctxHandler))

	s := httptest.NewServer(m)

	resp0, err := http.Get(s.URL + "/test0")
	if err != nil {
		fmt.Println(err)
	}
	defer resp0.Body.Close()
	rBody0, err := ioutil.ReadAll(resp0.Body)
	if err != nil {
		fmt.Println(err)
	}

	resp1, err := http.Get(s.URL + "/test01AEnd")
	if err != nil {
		fmt.Println(err)
	}
	defer resp1.Body.Close()
	rBody1, err := ioutil.ReadAll(resp1.Body)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Println("Chain 0:", string(rBody0))
	fmt.Println("Chain 1:", string(rBody1))

	// Output:
	// Chain 0: 0_END_0
	// Chain 1: 01A_END_A10
}
<|endoftext|>


<commit_before>// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package discoverspaces_test

import (
	"errors"

	"github.com/juju/names"
	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"

	"github.com/juju/juju/apiserver/common"
	"github.com/juju/juju/apiserver/discoverspaces"
	"github.com/juju/juju/apiserver/params"
	apiservertesting "github.com/juju/juju/apiserver/testing"
	coretesting "github.com/juju/juju/testing"
)

type DiscoverSpacesSuite struct {
	coretesting.BaseSuite
	apiservertesting.StubNetwork

	resources  *common.Resources
	authorizer apiservertesting.FakeAuthorizer
	facade     *discoverspaces.DiscoverSpacesAPI
}

var _ = gc.Suite(&DiscoverSpacesSuite{})

func (s *DiscoverSpacesSuite) SetUpSuite(c *gc.C) {
	s.StubNetwork.SetUpSuite(c)
	s.BaseSuite.SetUpSuite(c)
}

func (s *DiscoverSpacesSuite) TearDownSuite(c *gc.C) {
	s.BaseSuite.TearDownSuite(c)
}

func (s *DiscoverSpacesSuite) SetUpTest(c *gc.C) {
	s.BaseSuite.SetUpTest(c)
	apiservertesting.BackingInstance.SetUp(
		c,
		apiservertesting.StubZonedEnvironName,
		apiservertesting.WithZones,
		apiservertesting.WithSpaces,
		apiservertesting.WithSubnets)

	s.resources = common.NewResources()
	s.authorizer = apiservertesting.FakeAuthorizer{
		Tag:            names.NewUserTag("admin"),
		EnvironManager: true,
	}

	var err error
	s.facade, err = discoverspaces.NewDiscoverSpacesAPIWithBacking(
		apiservertesting.BackingInstance, s.resources, s.authorizer,
	)
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(s.facade, gc.NotNil)
}

func (s *DiscoverSpacesSuite) TearDownTest(c *gc.C) {
	if s.resources != nil {
		s.resources.StopAll()
	}
	s.BaseSuite.TearDownTest(c)
}

func (s *DiscoverSpacesSuite) TestEnvironConfigFailure(c *gc.C) {
	apiservertesting.BackingInstance.SetErrors(errors.New("boom"))

	result, err := s.facade.EnvironConfig()
	c.Assert(err, gc.ErrorMatches, "boom")
	c.Assert(result, jc.DeepEquals, params.EnvironConfigResult{})

	apiservertesting.BackingInstance.CheckCallNames(c, "EnvironConfig")
}

func (s *DiscoverSpacesSuite) TestEnvironConfigSuccess(c *gc.C) {
	result, err := s.facade.EnvironConfig()
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(result, jc.DeepEquals, params.EnvironConfigResult{
		Config: nil,
	})

	apiservertesting.BackingInstance.CheckCallNames(c, "EnvironConfig")
}
<commit_msg>Test fix<commit_after>// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package discoverspaces_test

import (
	"errors"

	"github.com/juju/names"
	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"

	"github.com/juju/juju/apiserver/common"
	"github.com/juju/juju/apiserver/discoverspaces"
	"github.com/juju/juju/apiserver/params"
	apiservertesting "github.com/juju/juju/apiserver/testing"
	coretesting "github.com/juju/juju/testing"
)

type DiscoverSpacesSuite struct {
	coretesting.BaseSuite
	apiservertesting.StubNetwork

	resources  *common.Resources
	authorizer apiservertesting.FakeAuthorizer
	facade     *discoverspaces.DiscoverSpacesAPI
}

var _ = gc.Suite(&DiscoverSpacesSuite{})

func (s *DiscoverSpacesSuite) SetUpSuite(c *gc.C) {
	s.StubNetwork.SetUpSuite(c)
	s.BaseSuite.SetUpSuite(c)
}

func (s *DiscoverSpacesSuite) TearDownSuite(c *gc.C) {
	s.BaseSuite.TearDownSuite(c)
}

func (s *DiscoverSpacesSuite) SetUpTest(c *gc.C) {
	s.BaseSuite.SetUpTest(c)
	apiservertesting.BackingInstance.SetUp(
		c,
		apiservertesting.StubZonedEnvironName,
		apiservertesting.WithZones,
		apiservertesting.WithSpaces,
		apiservertesting.WithSubnets)

	s.resources = common.NewResources()
	s.authorizer = apiservertesting.FakeAuthorizer{
		Tag:            names.NewUserTag("admin"),
		EnvironManager: true,
	}

	var err error
	s.facade, err = discoverspaces.NewDiscoverSpacesAPIWithBacking(
		apiservertesting.BackingInstance, s.resources, s.authorizer,
	)
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(s.facade, gc.NotNil)
}

func (s *DiscoverSpacesSuite) TearDownTest(c *gc.C) {
	if s.resources != nil {
		s.resources.StopAll()
	}
	s.BaseSuite.TearDownTest(c)
}

func (s *DiscoverSpacesSuite) TestEnvironConfigFailure(c *gc.C) {
	apiservertesting.BackingInstance.SetErrors(errors.New("boom"))

	result, err := s.facade.EnvironConfig()
	c.Assert(err, gc.ErrorMatches, "boom")
	c.Assert(result, jc.DeepEquals, params.EnvironConfigResult{})

	apiservertesting.BackingInstance.CheckCallNames(c, "EnvironConfig")
}

func (s *DiscoverSpacesSuite) TestEnvironConfigSuccess(c *gc.C) {
	result, err := s.facade.EnvironConfig()
	c.Assert(err, jc.ErrorIsNil)
	c.Assert(result, jc.DeepEquals, params.EnvironConfigResult{
		Config: apiservertesting.BackingInstance.EnvConfig.AllAttrs(),
	})

	apiservertesting.BackingInstance.CheckCallNames(c, "EnvironConfig")
}
<|endoftext|>


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

// This file defines a refactoring to rename variables, functions, methods,
// structs, interfaces, and packages.

package refactoring

import (
	"go/ast"
	"regexp"
	//"fmt"
	"go/parser"
	"go/token"
	"io/ioutil"
	"path/filepath"
	"strings"

	"code.google.com/p/go.tools/go/types"
	"golang-refactoring.org/go-doctor/analysis/names"
	"golang-refactoring.org/go-doctor/filesystem"
	"golang-refactoring.org/go-doctor/text"
)

// A Rename refactoring is used to rename identifiers in Go programs.
type Rename struct {
	refactoringBase
	newName   string
	signature *types.Signature
}

func (r *Rename) Description() *Description {
	return &Description{
		Name: "Rename",
		Params: []Parameter{Parameter{
			Label:        "New Name:",
			Prompt:       "What to rename this identifier to.",
			DefaultValue: "",
		}},
		Quality: Development,
	}
}

func (r *Rename) Run(config *Config) *Result {
	if r.refactoringBase.Run(config); r.Log.ContainsErrors() {
		return &r.Result
	}

	if !validateArgs(config, r.Description(), r.Log) {
		return &r.Result
	}

	r.newName = config.Args[0].(string)
	if !r.isIdentifierValid(r.newName) {
		r.Log.Errorf("The new name %s is not a valid Go identifier", r.newName)
		return &r.Result
	}

	if r.selectedNode == nil {
		r.Log.Error("Please select an identifier to rename.")
		r.Log.AssociatePos(r.program.Fset, r.selectionStart, r.selectionEnd)
		return &r.Result
	}

	if r.newName == "" {
		r.Log.Error("newName cannot be empty")
		return &r.Result
	}

	switch ident := r.selectedNode.(type) {
	case *ast.Ident:
		if ast.IsExported(ident.Name) && !ast.IsExported(r.newName) {
			r.Log.Error("newName cannot be non Exportable if selected identifier name is Exportable")
			return &r.Result
		}
		if ident.Name == "main" && r.pkgInfo(r.fileContaining(ident)).Pkg.Name() == "main" {
			r.Log.Error("cannot rename main function inside main package ,it eliminates the program entry 							point")
			r.Log.AssociateNode(r.program, ident)
			return &r.Result
		}

		r.rename(ident)

	default:
		r.Log.Error("Please select an identifier to rename.")
		r.Log.AssociatePos(r.program.Fset, r.selectionStart, r.selectionEnd)
	}
	return &r.Result
}

func (r *Rename) isIdentifierValid(newName string) bool {
	matched, err := regexp.MatchString("^[A-Za-z_][0-9A-Za-z_]*$", newName)
	if matched && err == nil {
		keyword, err := regexp.MatchString("^(break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go|goto|if|import|interface|map|package|range|return|select|struct|switch|type|var)$", newName)
		return !keyword && err == nil
	}
	return false
}

func (r *Rename) rename(ident *ast.Ident) {
	if !r.identExists(ident) {
		search := names.NewSearchEngine(r.program)
		searchResult, err := search.FindOccurrences(ident)
		if err != nil {
			r.Log.Error(err)
			return
		}

		r.addOccurrences(searchResult)
		if search.IsPackageName(ident) {
			r.addFileSystemChanges(searchResult, ident)
		}
		//TODO: r.checkForErrors()
		return
	}

}

//IdentifierExists checks if there already exists an Identifier with the newName,with in the scope of the oldname.
func (r *Rename) identExists(ident *ast.Ident) bool {

	obj := r.pkgInfo(r.fileContaining(ident)).ObjectOf(ident)
	search := names.NewSearchEngine(r.program)

	if obj == nil && !search.IsPackageName(ident) {

		r.Log.Error("unable to find declaration of selected identifier")
		r.Log.AssociateNode(r.program, ident)
		return true
	}

	if search.IsPackageName(ident) {
		return false
	}
	identscope := obj.Parent()

	if names.IsMethod(obj) {
		objfound, _, pointerindirections := types.LookupFieldOrMethod(names.MethodReceiver(obj).Type(), obj.Pkg(), r.newName)
		if names.IsMethod(objfound) && pointerindirections {
			r.Log.Error("newname already exists in scope,please select other value for the newname")
			r.Log.AssociateNode(r.program, ident)
			return true
		} else {
			return false
		}
	}

	if identscope.LookupParent(r.newName) != nil {
		r.Log.Error("newname already exists in scope,please select other value for the newname")
		r.Log.AssociateNode(r.program, ident)
		return true
	}

	return false
}

//addOccurrences adds all the Occurences to the editset
func (r *Rename) addOccurrences(allOccurrences map[string][]text.Extent) {
	for filename, occurrences := range allOccurrences {
		for _, occurrence := range occurrences {
			if r.Edits[filename] == nil {
				r.Edits[filename] = text.NewEditSet()
			}
			r.Edits[filename].Add(occurrence, r.newName)

		}
	}
}

func (r *Rename) addFileSystemChanges(allOccurrences map[string][]text.Extent, ident *ast.Ident) {
	for filename, _ := range allOccurrences {

		if filepath.Base(filepath.Dir(filename)) == ident.Name && allFilesinDirectoryhaveSamePkg(filepath.Dir(filename), ident) {
			chg := &filesystem.Rename{filepath.Dir(filename), r.newName}
			r.FSChanges = append(r.FSChanges, chg)
		}
	}
}

func allFilesinDirectoryhaveSamePkg(directorypath string, ident *ast.Ident) bool {

	var renamefile bool = false
	fileInfos, _ := ioutil.ReadDir(directorypath)

	for _, file := range fileInfos {
		if strings.HasSuffix(file.Name(), ".go") {
			fset := token.NewFileSet()
			f, err := parser.ParseFile(fset, filepath.Join(directorypath, file.Name()), nil, 0)
			if err != nil {
				panic(err)
			}
			if f.Name.Name == ident.Name {
				renamefile = true
			}
		}
	}

	return renamefile
}
<commit_msg>Avoid renaming source files located in $GOROOT<commit_after>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file defines a refactoring to rename variables, functions, methods,
// structs, interfaces, and packages.

package refactoring

import (
	"go/ast"
	"regexp"
	"runtime"
	//"fmt"
	"go/parser"
	"go/token"
	"io/ioutil"
	"path/filepath"
	"strings"

	"code.google.com/p/go.tools/go/types"
	"golang-refactoring.org/go-doctor/analysis/names"
	"golang-refactoring.org/go-doctor/filesystem"
	"golang-refactoring.org/go-doctor/text"
)

// A Rename refactoring is used to rename identifiers in Go programs.
type Rename struct {
	refactoringBase
	newName   string
	signature *types.Signature
}

func (r *Rename) Description() *Description {
	return &Description{
		Name: "Rename",
		Params: []Parameter{Parameter{
			Label:        "New Name:",
			Prompt:       "What to rename this identifier to.",
			DefaultValue: "",
		}},
		Quality: Development,
	}
}

func (r *Rename) Run(config *Config) *Result {
	if r.refactoringBase.Run(config); r.Log.ContainsErrors() {
		return &r.Result
	}

	if !validateArgs(config, r.Description(), r.Log) {
		return &r.Result
	}

	r.newName = config.Args[0].(string)
	if !r.isIdentifierValid(r.newName) {
		r.Log.Errorf("The new name %s is not a valid Go identifier", r.newName)
		return &r.Result
	}

	if r.selectedNode == nil {
		r.Log.Error("Please select an identifier to rename.")
		r.Log.AssociatePos(r.program.Fset, r.selectionStart, r.selectionEnd)
		return &r.Result
	}

	if r.newName == "" {
		r.Log.Error("newName cannot be empty")
		return &r.Result
	}

	switch ident := r.selectedNode.(type) {
	case *ast.Ident:
		if ast.IsExported(ident.Name) && !ast.IsExported(r.newName) {
			r.Log.Error("newName cannot be non Exportable if selected identifier name is Exportable")
			return &r.Result
		}
		if ident.Name == "main" && r.pkgInfo(r.fileContaining(ident)).Pkg.Name() == "main" {
			r.Log.Error("cannot rename main function inside main package ,it eliminates the program entry 							point")
			r.Log.AssociateNode(r.program, ident)
			return &r.Result
		}

		r.rename(ident)

	default:
		r.Log.Error("Please select an identifier to rename.")
		r.Log.AssociatePos(r.program.Fset, r.selectionStart, r.selectionEnd)
	}
	return &r.Result
}

func (r *Rename) isIdentifierValid(newName string) bool {
	matched, err := regexp.MatchString("^[A-Za-z_][0-9A-Za-z_]*$", newName)
	if matched && err == nil {
		keyword, err := regexp.MatchString("^(break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go|goto|if|import|interface|map|package|range|return|select|struct|switch|type|var)$", newName)
		return !keyword && err == nil
	}
	return false
}

func (r *Rename) rename(ident *ast.Ident) {
	if !r.identExists(ident) {
		search := names.NewSearchEngine(r.program)
		searchResult, err := search.FindOccurrences(ident)
		if err != nil {
			r.Log.Error(err)
			return
		}

		r.addOccurrences(searchResult)
		if search.IsPackageName(ident) {
			r.addFileSystemChanges(searchResult, ident)
		}
		//TODO: r.checkForErrors()
		return
	}

}

//IdentifierExists checks if there already exists an Identifier with the newName,with in the scope of the oldname.
func (r *Rename) identExists(ident *ast.Ident) bool {

	obj := r.pkgInfo(r.fileContaining(ident)).ObjectOf(ident)
	search := names.NewSearchEngine(r.program)

	if obj == nil && !search.IsPackageName(ident) {

		r.Log.Error("unable to find declaration of selected identifier")
		r.Log.AssociateNode(r.program, ident)
		return true
	}

	if search.IsPackageName(ident) {
		return false
	}
	identscope := obj.Parent()

	if names.IsMethod(obj) {
		objfound, _, pointerindirections := types.LookupFieldOrMethod(names.MethodReceiver(obj).Type(), obj.Pkg(), r.newName)
		if names.IsMethod(objfound) && pointerindirections {
			r.Log.Error("newname already exists in scope,please select other value for the newname")
			r.Log.AssociateNode(r.program, ident)
			return true
		} else {
			return false
		}
	}

	if identscope.LookupParent(r.newName) != nil {
		r.Log.Error("newname already exists in scope,please select other value for the newname")
		r.Log.AssociateNode(r.program, ident)
		return true
	}

	return false
}

//addOccurrences adds all the Occurences to the editset
func (r *Rename) addOccurrences(allOccurrences map[string][]text.Extent) {
	for filename, occurrences := range allOccurrences {
		if isInGoRoot(filename) {
			r.Log.Warnf("Occurrences in %s will not be renamed",
				filename)
		} else {
			for _, occurrence := range occurrences {
				if r.Edits[filename] == nil {
					r.Edits[filename] = text.NewEditSet()
				}
				r.Edits[filename].Add(occurrence, r.newName)
			}
		}
	}
}

func isInGoRoot(absPath string) bool {
	return strings.HasPrefix(absPath, runtime.GOROOT())
}

func (r *Rename) addFileSystemChanges(allOccurrences map[string][]text.Extent, ident *ast.Ident) {
	for filename, _ := range allOccurrences {

		if filepath.Base(filepath.Dir(filename)) == ident.Name && allFilesinDirectoryhaveSamePkg(filepath.Dir(filename), ident) {
			chg := &filesystem.Rename{filepath.Dir(filename), r.newName}
			r.FSChanges = append(r.FSChanges, chg)
		}
	}
}

func allFilesinDirectoryhaveSamePkg(directorypath string, ident *ast.Ident) bool {

	var renamefile bool = false
	fileInfos, _ := ioutil.ReadDir(directorypath)

	for _, file := range fileInfos {
		if strings.HasSuffix(file.Name(), ".go") {
			fset := token.NewFileSet()
			f, err := parser.ParseFile(fset, filepath.Join(directorypath, file.Name()), nil, 0)
			if err != nil {
				panic(err)
			}
			if f.Name.Name == ident.Name {
				renamefile = true
			}
		}
	}

	return renamefile
}
<|endoftext|>