<commit_before>package eval

import (
	"errors"
	"fmt"
	"math/big"
	"reflect"

	"github.com/elves/elvish/util"
)

// Definitions for Value interfaces, some simple Value types and some common
// Value helpers.

const (
	NoPretty = util.MinInt
)

// Value is an elvish value.
type Value interface {
	Kinder
	Reprer
}

// Kinder is anything with kind string.
type Kinder interface {
	Kind() string
}

// Reprer is anything with a Repr method.
type Reprer interface {
	// Repr returns a string that represents a Value. The string either be a
	// literal of that Value that is preferably deep-equal to it (like `[a b c]`
	// for a list), or a string enclosed in "<>" containing the kind and
	// identity of the Value(like `<fn 0xdeadcafe>`).
	//
	// If indent is at least 0, it should be pretty-printed with the current
	// indentation level of indent; the indent of the first line has already
	// been written and shall not be written in Repr. The returned string
	// should never contain a trailing newline.
	Repr(indent int) string
}

func IncIndent(indent, inc int) int {
	if indent < 0 {
		return indent
	}
	return indent + inc
}

// Booler is anything that can be converted to a bool.
type Booler interface {
	Bool() bool
}

// Stringer is anything that can be converted to a string.
type Stringer interface {
	String() string
}

// Lener is anything with a length.
type Lener interface {
	Len() int
}

// Iterator is anything that can be iterated.
type Iterator interface {
	Iterate(func(Value) bool)
}

type IteratorValue interface {
	Iterator
	Value
}

func collectFromIterator(it Iterator) []Value {
	var vs []Value
	if lener, ok := it.(Lener); ok {
		vs = make([]Value, 0, lener.Len())
	}
	it.Iterate(func(v Value) bool {
		vs = append(vs, v)
		return true
	})
	return vs
}

var NoOpts = map[string]Value{}

// Fn is anything may be called on an evalCtx with a list of Value's.
type Fn interface {
	Call(ec *EvalCtx, args []Value, opts map[string]Value)
}

type FnValue interface {
	Value
	Fn
}

func mustFn(v Value) Fn {
	caller, ok := getFn(v, true)
	if !ok {
		throw(fmt.Errorf("a %s is not callable", v.Kind()))
	}
	return caller
}

// getFn adapts a Value to a Fn if there is an adapter. It adapts an Indexer if
// adaptIndexer is true.
func getFn(v Value, adaptIndexer bool) (Fn, bool) {
	if caller, ok := v.(Fn); ok {
		return caller, true
	}
	if adaptIndexer {
		if indexer, ok := getIndexer(v, nil); ok {
			return IndexerAsFn{indexer}, true
		}
	}
	return nil, false
}

// IndexerAsFn adapts an Indexer to a Fn.
type IndexerAsFn struct {
	Indexer
}

var ErrIndexerOpts = errors.New("option not accepted")

func (ic IndexerAsFn) Call(ec *EvalCtx, args []Value, opts map[string]Value) {
	if len(opts) > 0 {
		throw(ErrIndexerOpts)
	}
	results := ic.Indexer.Index(args)
	for _, v := range results {
		ec.ports[1].Chan <- v
	}
}

// Indexer is anything that can be indexed by Values and yields Values.
type Indexer interface {
	Index(idx []Value) []Value
}

// IndexOneer is anything that can be indexed by one Value and yields one Value.
type IndexOneer interface {
	IndexOne(idx Value) Value
}

// IndexSetter is a Value whose elements can be get as well as set.
type IndexSetter interface {
	IndexOneer
	IndexSet(idx Value, v Value)
}

func mustIndexer(v Value, ec *EvalCtx) Indexer {
	indexer, ok := getIndexer(v, ec)
	if !ok {
		throw(fmt.Errorf("a %s is not indexable", v.Kind()))
	}
	return indexer
}

// getIndexer adapts a Value to an Indexer if there is an adapter. It adapts a
// Fn if ec is not nil.
func getIndexer(v Value, ec *EvalCtx) (Indexer, bool) {
	if indexer, ok := v.(Indexer); ok {
		return indexer, true
	}
	if indexOneer, ok := v.(IndexOneer); ok {
		return IndexOneerIndexer{indexOneer}, true
	}
	if ec != nil {
		if caller, ok := v.(Fn); ok {
			return FnAsIndexer{caller, ec}, true
		}
	}
	return nil, false
}

// IndexOneerIndexer adapts an IndexOneer to an Indexer by calling all the
// indicies on the IndexOner and collect the results.
type IndexOneerIndexer struct {
	IndexOneer
}

func (ioi IndexOneerIndexer) Index(vs []Value) []Value {
	results := make([]Value, len(vs))
	for i, v := range vs {
		results[i] = ioi.IndexOneer.IndexOne(v)
	}
	return results
}

// FnAsIndexer adapts a Fn to an Indexer.
type FnAsIndexer struct {
	Fn
	ec *EvalCtx
}

func (ci FnAsIndexer) Index(idx []Value) []Value {
	// XXX We don't have location information.
	return captureOutput(ci.ec, Op{func(ec *EvalCtx) {
		ci.Fn.Call(ec, idx, NoOpts)
	}, -1, -1})
}

// Error definitions.
var (
	ErrOnlyStrOrRat = errors.New("only str or rat may be converted to rat")
)

// Bool represents truthness.
type Bool bool

func (Bool) Kind() string {
	return "bool"
}

func (b Bool) Repr(int) string {
	if b {
		return "$true"
	}
	return "$false"
}

func (b Bool) Bool() bool {
	return bool(b)
}

// ToBool converts a Value to bool. When the Value type implements Bool(), it
// is used. Otherwise it is considered true.
func ToBool(v Value) bool {
	if b, ok := v.(Booler); ok {
		return b.Bool()
	}
	return true
}

// Rat is a rational number.
type Rat struct {
	b *big.Rat
}

func (Rat) Kind() string {
	return "string"
}

func (r Rat) Repr(int) string {
	return "(rat " + r.String() + ")"
}

func (r Rat) String() string {
	if r.b.IsInt() {
		return r.b.Num().String()
	}
	return r.b.String()
}

// ToRat converts a Value to rat. A str can be converted to a rat if it can be
// parsed. A rat is returned as-is. Other types of values cannot be converted.
func ToRat(v Value) (Rat, error) {
	switch v := v.(type) {
	case Rat:
		return v, nil
	case String:
		r := big.Rat{}
		_, err := fmt.Sscanln(string(v), &r)
		if err != nil {
			return Rat{}, fmt.Errorf("%s cannot be parsed as rat", v.Repr(NoPretty))
		}
		return Rat{&r}, nil
	default:
		return Rat{}, ErrOnlyStrOrRat
	}
}

// FromJSONInterface converts a interface{} that results from json.Unmarshal to
// a Value.
func FromJSONInterface(v interface{}) Value {
	if v == nil {
		// TODO Use a more appropriate type
		return String("")
	}
	switch v.(type) {
	case bool:
		return Bool(v.(bool))
	case float64, string:
		// TODO Use a numeric type for float64
		return String(fmt.Sprint(v))
	case []interface{}:
		a := v.([]interface{})
		vs := make([]Value, len(a))
		for i, v := range a {
			vs[i] = FromJSONInterface(v)
		}
		return List{&vs}
	case map[string]interface{}:
		m := v.(map[string]interface{})
		m_ := make(map[Value]Value)
		for k, v := range m {
			m_[String(k)] = FromJSONInterface(v)
		}
		return Map{&m_}
	default:
		throw(fmt.Errorf("unexpected json type: %T", v))
		return nil // not reached
	}
}

// DeepEq compares two Value's deeply.
func DeepEq(a, b Value) bool {
	return reflect.DeepEqual(a, b)
}
<commit_msg>Don't adapt fn to indexer.<commit_after>package eval

import (
	"errors"
	"fmt"
	"math/big"
	"reflect"

	"github.com/elves/elvish/util"
)

// Definitions for Value interfaces, some simple Value types and some common
// Value helpers.

const (
	NoPretty = util.MinInt
)

// Value is an elvish value.
type Value interface {
	Kinder
	Reprer
}

// Kinder is anything with kind string.
type Kinder interface {
	Kind() string
}

// Reprer is anything with a Repr method.
type Reprer interface {
	// Repr returns a string that represents a Value. The string either be a
	// literal of that Value that is preferably deep-equal to it (like `[a b c]`
	// for a list), or a string enclosed in "<>" containing the kind and
	// identity of the Value(like `<fn 0xdeadcafe>`).
	//
	// If indent is at least 0, it should be pretty-printed with the current
	// indentation level of indent; the indent of the first line has already
	// been written and shall not be written in Repr. The returned string
	// should never contain a trailing newline.
	Repr(indent int) string
}

func IncIndent(indent, inc int) int {
	if indent < 0 {
		return indent
	}
	return indent + inc
}

// Booler is anything that can be converted to a bool.
type Booler interface {
	Bool() bool
}

// Stringer is anything that can be converted to a string.
type Stringer interface {
	String() string
}

// Lener is anything with a length.
type Lener interface {
	Len() int
}

// Iterator is anything that can be iterated.
type Iterator interface {
	Iterate(func(Value) bool)
}

type IteratorValue interface {
	Iterator
	Value
}

func collectFromIterator(it Iterator) []Value {
	var vs []Value
	if lener, ok := it.(Lener); ok {
		vs = make([]Value, 0, lener.Len())
	}
	it.Iterate(func(v Value) bool {
		vs = append(vs, v)
		return true
	})
	return vs
}

var NoOpts = map[string]Value{}

// Fn is anything may be called on an evalCtx with a list of Value's.
type Fn interface {
	Call(ec *EvalCtx, args []Value, opts map[string]Value)
}

type FnValue interface {
	Value
	Fn
}

func mustFn(v Value) Fn {
	caller, ok := getFn(v)
	if !ok {
		throw(fmt.Errorf("a %s is not callable", v.Kind()))
	}
	return caller
}

// getFn adapts a Value to a Fn if there is an adapter. It adapts an Indexer if
// adaptIndexer is true.
func getFn(v Value) (Fn, bool) {
	if caller, ok := v.(Fn); ok {
		return caller, true
	}
	if indexer, ok := getIndexer(v, nil); ok {
		return IndexerAsFn{indexer}, true
	}
	return nil, false
}

// IndexerAsFn adapts an Indexer to a Fn.
type IndexerAsFn struct {
	Indexer
}

var ErrIndexerOpts = errors.New("option not accepted")

func (ic IndexerAsFn) Call(ec *EvalCtx, args []Value, opts map[string]Value) {
	if len(opts) > 0 {
		throw(ErrIndexerOpts)
	}
	results := ic.Indexer.Index(args)
	for _, v := range results {
		ec.ports[1].Chan <- v
	}
}

// Indexer is anything that can be indexed by Values and yields Values.
type Indexer interface {
	Index(idx []Value) []Value
}

// IndexOneer is anything that can be indexed by one Value and yields one Value.
type IndexOneer interface {
	IndexOne(idx Value) Value
}

// IndexSetter is a Value whose elements can be get as well as set.
type IndexSetter interface {
	IndexOneer
	IndexSet(idx Value, v Value)
}

func mustIndexer(v Value, ec *EvalCtx) Indexer {
	indexer, ok := getIndexer(v, ec)
	if !ok {
		throw(fmt.Errorf("a %s is not indexable", v.Kind()))
	}
	return indexer
}

// getIndexer adapts a Value to an Indexer if there is an adapter. It adapts a
// Fn if ec is not nil.
func getIndexer(v Value, ec *EvalCtx) (Indexer, bool) {
	if indexer, ok := v.(Indexer); ok {
		return indexer, true
	}
	if indexOneer, ok := v.(IndexOneer); ok {
		return IndexOneerIndexer{indexOneer}, true
	}
	return nil, false
}

// IndexOneerIndexer adapts an IndexOneer to an Indexer by calling all the
// indicies on the IndexOner and collect the results.
type IndexOneerIndexer struct {
	IndexOneer
}

func (ioi IndexOneerIndexer) Index(vs []Value) []Value {
	results := make([]Value, len(vs))
	for i, v := range vs {
		results[i] = ioi.IndexOneer.IndexOne(v)
	}
	return results
}

// Error definitions.
var (
	ErrOnlyStrOrRat = errors.New("only str or rat may be converted to rat")
)

// Bool represents truthness.
type Bool bool

func (Bool) Kind() string {
	return "bool"
}

func (b Bool) Repr(int) string {
	if b {
		return "$true"
	}
	return "$false"
}

func (b Bool) Bool() bool {
	return bool(b)
}

// ToBool converts a Value to bool. When the Value type implements Bool(), it
// is used. Otherwise it is considered true.
func ToBool(v Value) bool {
	if b, ok := v.(Booler); ok {
		return b.Bool()
	}
	return true
}

// Rat is a rational number.
type Rat struct {
	b *big.Rat
}

func (Rat) Kind() string {
	return "string"
}

func (r Rat) Repr(int) string {
	return "(rat " + r.String() + ")"
}

func (r Rat) String() string {
	if r.b.IsInt() {
		return r.b.Num().String()
	}
	return r.b.String()
}

// ToRat converts a Value to rat. A str can be converted to a rat if it can be
// parsed. A rat is returned as-is. Other types of values cannot be converted.
func ToRat(v Value) (Rat, error) {
	switch v := v.(type) {
	case Rat:
		return v, nil
	case String:
		r := big.Rat{}
		_, err := fmt.Sscanln(string(v), &r)
		if err != nil {
			return Rat{}, fmt.Errorf("%s cannot be parsed as rat", v.Repr(NoPretty))
		}
		return Rat{&r}, nil
	default:
		return Rat{}, ErrOnlyStrOrRat
	}
}

// FromJSONInterface converts a interface{} that results from json.Unmarshal to
// a Value.
func FromJSONInterface(v interface{}) Value {
	if v == nil {
		// TODO Use a more appropriate type
		return String("")
	}
	switch v.(type) {
	case bool:
		return Bool(v.(bool))
	case float64, string:
		// TODO Use a numeric type for float64
		return String(fmt.Sprint(v))
	case []interface{}:
		a := v.([]interface{})
		vs := make([]Value, len(a))
		for i, v := range a {
			vs[i] = FromJSONInterface(v)
		}
		return List{&vs}
	case map[string]interface{}:
		m := v.(map[string]interface{})
		m_ := make(map[Value]Value)
		for k, v := range m {
			m_[String(k)] = FromJSONInterface(v)
		}
		return Map{&m_}
	default:
		throw(fmt.Errorf("unexpected json type: %T", v))
		return nil // not reached
	}
}

// DeepEq compares two Value's deeply.
func DeepEq(a, b Value) bool {
	return reflect.DeepEqual(a, b)
}
<|endoftext|>


<commit_before>package dtf

import (
	"regexp"
)

var (
	year = "^[1-9][0-9]{3}$"

	yearAndMonth = "^[1-9][0-9]{3}-(1[0-2]|0[0-9])$"

	completeDate = "^[1-9][0-9]{3}-(1[0-2]|0[0-9])-([0-2][0-9]|3[0-1])$"

	withMinutes = "^[1-9][0-9]{3}-(1[0-2]|0[0-9])-([0-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]([-+]([01][0-9]|2[0-4])00|[A-Z]{3})$"

	withSeconds = "^[1-9][0-9]{3}-(1[0-2]|0[0-9])-([0-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)([-+]([01][0-9]|2[0-4])00|[A-Z]{3})$"

	withFractionOfSecond = "^[1-9][0-9]{3}-(1[0-2]|0[0-9])-([0-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60).[0-9]+([-+]([01][0-9]|2[0-4])00|[A-Z]{3})$"
)

// IsYear check timeStr is 'YYYY'
func IsYear(timeStr string) bool {
	match, _ := regexp.MatchString(year, timeStr)
	return match
}

// IsYearAndMonth check timeStr is 'YYYY-MM'
func IsYearAndMonth(timeStr string) bool {
	match, _ := regexp.MatchString(yearAndMonth, timeStr)
	return match
}

// IsCompleteDate check timeStr is 'YYYY-MM-DD'
func IsCompleteDate(timeStr string) bool {
	match, _ := regexp.MatchString(completeDate, timeStr)
	return match
}

// IsCompleteDateWithMinutes check timeStr is 'YYYY-MM-DDThh:mmTZD'
func IsCompleteDateWithMinutes(timeStr string) bool {
	match, _ := regexp.MatchString(withMinutes, timeStr)
	return match
}

// IsCompleteDateWithSeconds check timeStr is 'YYYY-MM-DDThh:mm:ssTZD'
func IsCompleteDateWithSeconds(timeStr string) bool {
	match, _ := regexp.MatchString(withSeconds, timeStr)
	return match
}

// IsCompleteDateWithFractionOfSecond check timeStr is 'YYYY-MM-DDThh:mm:ss.sTZD'
func IsCompleteDateWithFractionOfSecond(timeStr string) bool {
	match, _ := regexp.MatchString(withFractionOfSecond, timeStr)
	return match
}
<commit_msg>update pattern<commit_after>package dtf

import (
	"regexp"
)

var (
	timezone = "([-+]([01][0-9]|2[0-4])00|[A-Z]{3})"

	year = "[1-9][0-9]{3}"

	yearAndMonth = year + "-(1[0-2]|0[0-9])"

	completeDate = yearAndMonth + "-([0-2][0-9]|3[0-1])"

	hourMinutes = "([01][0-9]|2[0-3]):[0-5][0-9]"

	withMinutes = completeDate + "T" + hourMinutes + timezone

	withSeconds = completeDate + "T" + hourMinutes + ":([0-5][0-9]|60)" + timezone

	withFractionOfSecond = completeDate + "T" + hourMinutes + ":([0-5][0-9]|60).[0-9]+" + timezone
)

// IsYear check timeStr is 'YYYY'
func IsYear(timeStr string) bool {
	match, _ := regexp.MatchString("^"+year+"$", timeStr)
	return match
}

// IsYearAndMonth check timeStr is 'YYYY-MM'
func IsYearAndMonth(timeStr string) bool {
	match, _ := regexp.MatchString("^"+yearAndMonth+"$", timeStr)
	return match
}

// IsCompleteDate check timeStr is 'YYYY-MM-DD'
func IsCompleteDate(timeStr string) bool {
	match, _ := regexp.MatchString("^"+completeDate+"$", timeStr)
	return match
}

// IsCompleteDateWithMinutes check timeStr is 'YYYY-MM-DDThh:mmTZD'
func IsCompleteDateWithMinutes(timeStr string) bool {
	match, _ := regexp.MatchString("^"+withMinutes+"$", timeStr)
	return match
}

// IsCompleteDateWithSeconds check timeStr is 'YYYY-MM-DDThh:mm:ssTZD'
func IsCompleteDateWithSeconds(timeStr string) bool {
	match, _ := regexp.MatchString("^"+withSeconds+"$", timeStr)
	return match
}

// IsCompleteDateWithFractionOfSecond check timeStr is 'YYYY-MM-DDThh:mm:ss.sTZD'
func IsCompleteDateWithFractionOfSecond(timeStr string) bool {
	match, _ := regexp.MatchString("^"+withFractionOfSecond+"$", timeStr)
	return match
}
<|endoftext|>


<commit_before>// khan
// https://github.com/topfreegames/khan
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license
// Copyright © 2016 Top Free Games <backend@tfgco.com>

package api

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"runtime/debug"
	"time"

	"github.com/getsentry/raven-go"
	"github.com/labstack/echo"
	"github.com/topfreegames/khan/log"
	"github.com/uber-go/zap"
)

func getBodyFromNext(c echo.Context, next echo.HandlerFunc) (string, error) {
	res := c.Response()
	rw := res.Writer()
	buf := new(bytes.Buffer)
	mw := io.MultiWriter(rw, buf)
	res.SetWriter(mw)

	err := next(c)

	body := buf.String()
	return body, err
}

//NewBodyExtractionMiddleware with API version
func NewBodyExtractionMiddleware() *BodyExtractionMiddleware {
	return &BodyExtractionMiddleware{}
}

type BodyExtractionMiddleware struct{}

// Serve serves the middleware
func (v *BodyExtractionMiddleware) Serve(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		body, err := getBodyFromNext(c, next)
		c.Set("body", body)
		return err
	}
}

//NewVersionMiddleware with API version
func NewVersionMiddleware() *VersionMiddleware {
	return &VersionMiddleware{
		Version: VERSION,
	}
}

//VersionMiddleware inserts the current version in all requests
type VersionMiddleware struct {
	Version string
}

// Serve serves the middleware
func (v *VersionMiddleware) Serve(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		return WithSegment("middleware-version", c, func() error {
			c.Response().Header().Set(echo.HeaderServer, fmt.Sprintf("Khan/v%s", v.Version))
			c.Response().Header().Set("Khan-Server", fmt.Sprintf("Khan/v%s", v.Version))
			return next(c)
		})
	}
}

//NewSentryMiddleware returns a new sentry middleware
func NewSentryMiddleware(app *App) *SentryMiddleware {
	return &SentryMiddleware{
		App: app,
	}
}

//SentryMiddleware is responsible for sending all exceptions to sentry
type SentryMiddleware struct {
	App *App
}

// Serve serves the middleware
func (s *SentryMiddleware) Serve(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		return WithSegment("middleware-sentry", c, func() error {
			err := next(c)
			body := c.Get("body").(string)

			if err == nil {
				status := c.Response().Status()

				//request is ok, but server failed
				if status > 499 {
					err = fmt.Errorf("Server failed to process response with status code 500.", body)
				}
			}

			if err != nil {
				if httpErr, ok := err.(*echo.HTTPError); ok {
					if httpErr.Code < 500 {
						return err
					}
				}
				tags := map[string]string{
					"source": "app",
					"type":   "Internal server error",
					"url":    c.Request().URI(),
					"status": fmt.Sprintf("%d", c.Response().Status()),
					"body":   body,
				}
				raven.SetHttpContext(newHTTPFromCtx(c))
				raven.CaptureError(err, tags)
			}

			return err
		})
	}
}

func getHTTPParams(ctx echo.Context) (string, map[string]string, string) {
	qs := ""
	if len(ctx.QueryParams()) > 0 {
		qsBytes, _ := json.Marshal(ctx.QueryParams())
		qs = string(qsBytes)
	}

	headers := map[string]string{}
	for _, headerKey := range ctx.Response().Header().Keys() {
		headers[string(headerKey)] = string(ctx.Response().Header().Get(headerKey))
	}

	cookies := string(ctx.Response().Header().Get("Cookie"))
	return qs, headers, cookies
}

func newHTTPFromCtx(ctx echo.Context) *raven.Http {
	qs, headers, cookies := getHTTPParams(ctx)

	h := &raven.Http{
		Method:  string(ctx.Request().Method()),
		Cookies: cookies,
		Query:   qs,
		URL:     ctx.Request().URI(),
		Headers: headers,
	}
	return h
}

//NewRecoveryMiddleware returns a configured middleware
func NewRecoveryMiddleware(onError func(error, []byte)) *RecoveryMiddleware {
	return &RecoveryMiddleware{
		OnError: onError,
	}
}

//RecoveryMiddleware recovers from errors
type RecoveryMiddleware struct {
	OnError func(error, []byte)
}

//Serve executes on error handler when errors happen
func (r *RecoveryMiddleware) Serve(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		defer func() {
			if err := recover(); err != nil {
				eError, ok := err.(error)
				if !ok {
					eError = fmt.Errorf(fmt.Sprintf("%v", err))
				}
				if r.OnError != nil {
					r.OnError(eError, debug.Stack())
				}
				c.Error(eError)
			}
		}()
		return next(c)
	}
}

// NewLoggerMiddleware returns the logger middleware
func NewLoggerMiddleware(theLogger zap.Logger) *LoggerMiddleware {
	l := &LoggerMiddleware{Logger: theLogger}
	return l
}

//LoggerMiddleware is responsible for logging to Zap all requests
type LoggerMiddleware struct {
	Logger zap.Logger
}

// Serve serves the middleware
func (l *LoggerMiddleware) Serve(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		l := l.Logger.With(
			zap.String("source", "request"),
		)

		//all except latency to string
		var ip, method, path string
		var status int
		var latency time.Duration
		var startTime, endTime time.Time

		path = c.Path()
		method = c.Request().Method()

		startTime = time.Now()

		err := next(c)
		body := c.Get("body").(string)

		//no time.Since in order to format it well after
		endTime = time.Now()
		latency = endTime.Sub(startTime)

		status = c.Response().Status()
		ip = c.Request().RemoteAddress()

		route := c.Get("route")
		if route == nil {
			log.D(l, "Route does not have route set in ctx")
			return err
		}

		reqLog := l.With(
			zap.String("route", route.(string)),
			zap.Time("endTime", endTime),
			zap.Int("statusCode", status),
			zap.Duration("latency", latency),
			zap.String("ip", ip),
			zap.String("method", method),
			zap.String("path", path),
			zap.String("body", body),
		)

		//request failed
		if status > 399 && status < 500 {
			log.W(reqLog, "Request failed.")
			return err
		}

		//request is ok, but server failed
		if status > 499 {
			log.E(reqLog, "Response failed.")
			return err
		}

		//Everything went ok
		if cm := reqLog.Check(zap.InfoLevel, "Request successful."); cm.OK() {
			cm.Write()
		}

		return err
	}
}

//NewNewRelicMiddleware returns the logger middleware
func NewNewRelicMiddleware(app *App, theLogger zap.Logger) *NewRelicMiddleware {
	l := &NewRelicMiddleware{App: app, Logger: theLogger}
	return l
}

//NewRelicMiddleware is responsible for logging to Zap all requests
type NewRelicMiddleware struct {
	App    *App
	Logger zap.Logger
}

// Serve serves the middleware
func (nr *NewRelicMiddleware) Serve(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		method := c.Request().Method()
		route := fmt.Sprintf("%s %s", method, c.Path())
		txn := nr.App.NewRelic.StartTransaction(route, nil, nil)
		c.Set("txn", txn)
		defer func() {
			c.Set("txn", nil)
			txn.End()
		}()

		err := next(c)
		if err != nil {
			txn.NoticeError(err)
			return err
		}

		return nil
	}
}
<commit_msg>Minor issue with sentry middleware formatting<commit_after>// khan
// https://github.com/topfreegames/khan
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license
// Copyright © 2016 Top Free Games <backend@tfgco.com>

package api

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"runtime/debug"
	"time"

	"github.com/getsentry/raven-go"
	"github.com/labstack/echo"
	"github.com/topfreegames/khan/log"
	"github.com/uber-go/zap"
)

func getBodyFromNext(c echo.Context, next echo.HandlerFunc) (string, error) {
	res := c.Response()
	rw := res.Writer()
	buf := new(bytes.Buffer)
	mw := io.MultiWriter(rw, buf)
	res.SetWriter(mw)

	err := next(c)

	body := buf.String()
	return body, err
}

//NewBodyExtractionMiddleware with API version
func NewBodyExtractionMiddleware() *BodyExtractionMiddleware {
	return &BodyExtractionMiddleware{}
}

type BodyExtractionMiddleware struct{}

// Serve serves the middleware
func (v *BodyExtractionMiddleware) Serve(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		body, err := getBodyFromNext(c, next)
		c.Set("body", body)
		return err
	}
}

//NewVersionMiddleware with API version
func NewVersionMiddleware() *VersionMiddleware {
	return &VersionMiddleware{
		Version: VERSION,
	}
}

//VersionMiddleware inserts the current version in all requests
type VersionMiddleware struct {
	Version string
}

// Serve serves the middleware
func (v *VersionMiddleware) Serve(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		return WithSegment("middleware-version", c, func() error {
			c.Response().Header().Set(echo.HeaderServer, fmt.Sprintf("Khan/v%s", v.Version))
			c.Response().Header().Set("Khan-Server", fmt.Sprintf("Khan/v%s", v.Version))
			return next(c)
		})
	}
}

//NewSentryMiddleware returns a new sentry middleware
func NewSentryMiddleware(app *App) *SentryMiddleware {
	return &SentryMiddleware{
		App: app,
	}
}

//SentryMiddleware is responsible for sending all exceptions to sentry
type SentryMiddleware struct {
	App *App
}

// Serve serves the middleware
func (s *SentryMiddleware) Serve(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		return WithSegment("middleware-sentry", c, func() error {
			err := next(c)
			body := c.Get("body").(string)

			if err == nil {
				status := c.Response().Status()

				//request is ok, but server failed
				if status > 499 {
					err = fmt.Errorf("Server failed to process response with status code 500: %s", body)
				}
			}

			if err != nil {
				if httpErr, ok := err.(*echo.HTTPError); ok {
					if httpErr.Code < 500 {
						return err
					}
				}
				tags := map[string]string{
					"source": "app",
					"type":   "Internal server error",
					"url":    c.Request().URI(),
					"status": fmt.Sprintf("%d", c.Response().Status()),
					"body":   body,
				}
				raven.SetHttpContext(newHTTPFromCtx(c))
				raven.CaptureError(err, tags)
			}

			return err
		})
	}
}

func getHTTPParams(ctx echo.Context) (string, map[string]string, string) {
	qs := ""
	if len(ctx.QueryParams()) > 0 {
		qsBytes, _ := json.Marshal(ctx.QueryParams())
		qs = string(qsBytes)
	}

	headers := map[string]string{}
	for _, headerKey := range ctx.Response().Header().Keys() {
		headers[string(headerKey)] = string(ctx.Response().Header().Get(headerKey))
	}

	cookies := string(ctx.Response().Header().Get("Cookie"))
	return qs, headers, cookies
}

func newHTTPFromCtx(ctx echo.Context) *raven.Http {
	qs, headers, cookies := getHTTPParams(ctx)

	h := &raven.Http{
		Method:  string(ctx.Request().Method()),
		Cookies: cookies,
		Query:   qs,
		URL:     ctx.Request().URI(),
		Headers: headers,
	}
	return h
}

//NewRecoveryMiddleware returns a configured middleware
func NewRecoveryMiddleware(onError func(error, []byte)) *RecoveryMiddleware {
	return &RecoveryMiddleware{
		OnError: onError,
	}
}

//RecoveryMiddleware recovers from errors
type RecoveryMiddleware struct {
	OnError func(error, []byte)
}

//Serve executes on error handler when errors happen
func (r *RecoveryMiddleware) Serve(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		defer func() {
			if err := recover(); err != nil {
				eError, ok := err.(error)
				if !ok {
					eError = fmt.Errorf(fmt.Sprintf("%v", err))
				}
				if r.OnError != nil {
					r.OnError(eError, debug.Stack())
				}
				c.Error(eError)
			}
		}()
		return next(c)
	}
}

// NewLoggerMiddleware returns the logger middleware
func NewLoggerMiddleware(theLogger zap.Logger) *LoggerMiddleware {
	l := &LoggerMiddleware{Logger: theLogger}
	return l
}

//LoggerMiddleware is responsible for logging to Zap all requests
type LoggerMiddleware struct {
	Logger zap.Logger
}

// Serve serves the middleware
func (l *LoggerMiddleware) Serve(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		l := l.Logger.With(
			zap.String("source", "request"),
		)

		//all except latency to string
		var ip, method, path string
		var status int
		var latency time.Duration
		var startTime, endTime time.Time

		path = c.Path()
		method = c.Request().Method()

		startTime = time.Now()

		err := next(c)
		body := c.Get("body").(string)

		//no time.Since in order to format it well after
		endTime = time.Now()
		latency = endTime.Sub(startTime)

		status = c.Response().Status()
		ip = c.Request().RemoteAddress()

		route := c.Get("route")
		if route == nil {
			log.D(l, "Route does not have route set in ctx")
			return err
		}

		reqLog := l.With(
			zap.String("route", route.(string)),
			zap.Time("endTime", endTime),
			zap.Int("statusCode", status),
			zap.Duration("latency", latency),
			zap.String("ip", ip),
			zap.String("method", method),
			zap.String("path", path),
			zap.String("body", body),
		)

		//request failed
		if status > 399 && status < 500 {
			log.W(reqLog, "Request failed.")
			return err
		}

		//request is ok, but server failed
		if status > 499 {
			log.E(reqLog, "Response failed.")
			return err
		}

		//Everything went ok
		if cm := reqLog.Check(zap.InfoLevel, "Request successful."); cm.OK() {
			cm.Write()
		}

		return err
	}
}

//NewNewRelicMiddleware returns the logger middleware
func NewNewRelicMiddleware(app *App, theLogger zap.Logger) *NewRelicMiddleware {
	l := &NewRelicMiddleware{App: app, Logger: theLogger}
	return l
}

//NewRelicMiddleware is responsible for logging to Zap all requests
type NewRelicMiddleware struct {
	App    *App
	Logger zap.Logger
}

// Serve serves the middleware
func (nr *NewRelicMiddleware) Serve(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		method := c.Request().Method()
		route := fmt.Sprintf("%s %s", method, c.Path())
		txn := nr.App.NewRelic.StartTransaction(route, nil, nil)
		c.Set("txn", txn)
		defer func() {
			c.Set("txn", nil)
			txn.End()
		}()

		err := next(c)
		if err != nil {
			txn.NoticeError(err)
			return err
		}

		return nil
	}
}
<|endoftext|>


<commit_before>package exp

import (
	"reflect"
	"sort"

	"github.com/doug-martin/goqu/v9/internal/util"
)

// Alternative to writing map[string]interface{}. Can be used for Inserts, Updates or Deletes
type Record map[string]interface{}

func (r Record) Cols() []string {
	cols := make([]string, 0, len(r))
	for col := range r {
		cols = append(cols, col)
	}
	sort.Strings(cols)
	return cols
}

func NewRecordFromStruct(i interface{}, forInsert, forUpdate bool) (r Record, err error) {
	value := reflect.ValueOf(i)
	if value.IsValid() {
		cm, err := util.GetColumnMap(value.Interface())
		if err != nil {
			return nil, err
		}
		cols := cm.Cols()
		r = make(map[string]interface{}, len(cols))
		for _, col := range cols {
			f := cm[col]
			switch {
			case forInsert:
				if f.ShouldInsert {
					addFieldToRecord(r, value, f)
				}
			case forUpdate:
				if f.ShouldUpdate {
					addFieldToRecord(r, value, f)
				}
			default:
				addFieldToRecord(r, value, f)
			}
		}
	}
	return
}

func addFieldToRecord(r Record, val reflect.Value, f util.ColumnData) {
	v, isAvailable := util.SafeGetFieldByIndex(val, f.FieldIndex)
	if !isAvailable {
		return
	}
	switch {
	case f.DefaultIfEmpty && util.IsEmptyValue(v):
		r[f.ColumnName] = Default()
	case v.IsValid():
		r[f.ColumnName] = v.Interface()
	default:
		r[f.ColumnName] = reflect.Zero(f.GoType).Interface()
	}
}
<commit_msg>refactor: clean up NewRecrodFromStruct implementation<commit_after>package exp

import (
	"reflect"
	"sort"

	"github.com/doug-martin/goqu/v9/internal/util"
)

// Alternative to writing map[string]interface{}. Can be used for Inserts, Updates or Deletes
type Record map[string]interface{}

func (r Record) Cols() []string {
	cols := make([]string, 0, len(r))
	for col := range r {
		cols = append(cols, col)
	}
	sort.Strings(cols)
	return cols
}

func NewRecordFromStruct(i interface{}, forInsert, forUpdate bool) (r Record, err error) {
	value := reflect.ValueOf(i)
	if value.IsValid() {
		cm, err := util.GetColumnMap(value.Interface())
		if err != nil {
			return nil, err
		}
		cols := cm.Cols()
		r = make(map[string]interface{}, len(cols))
		for _, col := range cols {
			f := cm[col]
			if !shouldSkipField(f, forInsert, forUpdate) {
				if ok, fieldVal := getFieldValue(value, f); ok {
					r[f.ColumnName] = fieldVal
				}
			}
		}
	}
	return
}

func shouldSkipField(f util.ColumnData, forInsert, forUpdate bool) bool {
	shouldSkipInsert := forInsert && !f.ShouldInsert
	shouldSkipUpdate := forUpdate && !f.ShouldUpdate
	return shouldSkipInsert || shouldSkipUpdate
}

func getFieldValue(val reflect.Value, f util.ColumnData) (ok bool, fieldVal interface{}) {
	if v, isAvailable := util.SafeGetFieldByIndex(val, f.FieldIndex); !isAvailable {
		return false, nil
	} else if f.DefaultIfEmpty && util.IsEmptyValue(v) {
		return true, Default()
	} else if v.IsValid() {
		return true, v.Interface()
	} else {
		return true, reflect.Zero(f.GoType).Interface()
	}
}
<|endoftext|>


<commit_before>package php

import "stephensearles.com/php/ast"

/*

Valid Expression Patterns
Expr [Binary Op] Expr
[Unary Op] Expr
Expr [Unary Op]
Expr [Tertiary Op 1] Expr [Tertiary Op 2] Expr
Identifier
Literal
Function Call

Parentesis always triggers sub-expression

non-associative clone new clone and new
left  [ array()
right ++ -- ~ (int) (float) (string) (array) (object) (bool) @  types and increment/decrement
non-associative instanceof  types
right ! logical
left  * / % arithmetic
left  + - . arithmetic and string
left  << >> bitwise
non-associative < <= > >= comparison
non-associative == != === !== <>  comparison
left  & bitwise and references
left  ^ bitwise
left  | bitwise
left  &&  logical
left  ||  logical
left  ? : ternary
right = += -= *= /= .= %= &= |= ^= <<= >>= => assignment
left  and logical
left  xor logical
left  or  logical
left  , many uses

*/

var operatorPrecedence = map[ItemType]int{
	itemArrayLookupOperatorLeft:  19,
	itemArrayLookupOperatorRight: 19,
	itemUnaryOperator:            18,
	itemCastOperator:             18,
	itemInstanceofOperator:       17,
	itemNegationOperator:         16,
	itemMultOperator:             15,
	itemAdditionOperator:         14,
	itemSubtractionOperator:      14,
	itemConcatenationOperator:    14,

	itemBitwiseShiftOperator: 13,
	itemComparisonOperator:   12,
	itemEqualityOperator:     11,

	itemAmpersandOperator:  10,
	itemBitwiseXorOperator: 9,
	itemBitwiseOrOperator:  8,
	itemAndOperator:        7,
	itemOrOperator:         6,
	itemTernaryOperator1:   5,
	itemTernaryOperator2:   5,
	itemAssignmentOperator: 4,
	itemWrittenAndOperator: 3,
	itemWrittenXorOperator: 2,
	itemWrittenOrOperator:  1,
}

func (p *parser) parseExpression() (expr ast.Expression) {
	// consume expression
	originalParenLev := p.parenLevel
	switch p.current.typ {
	case itemNewOperator:
		return &ast.NewExpression{
			Expression: p.parseNextExpression(),
		}
	case itemUnaryOperator, itemNegationOperator, itemAmpersandOperator:
		op := p.current
		expr = p.parseUnaryExpressionRight(p.parseNextExpression(), op)
	case itemArray:
		return p.parseArrayDeclaration()
	case itemIdentifier:
		if p.peek().typ == itemAssignmentOperator {
			assignee := p.parseIdentifier().(ast.Assignable)
			p.next()
			return ast.AssignmentExpression{
				Assignee: assignee,
				Operator: p.current.val,
				Value:    p.parseNextExpression(),
			}
		}
		fallthrough
	case itemNonVariableIdentifier, itemStringLiteral, itemNumberLiteral, itemBooleanLiteral:
		expr = p.parseOperation(originalParenLev, p.expressionize())
	case itemOpenParen:
		p.parenLevel += 1
		p.next()
		expr = p.parseExpression()
		p.expect(itemCloseParen)
		p.parenLevel -= 1
	default:
		p.errorf("Expected expression. Found %s", p.current)
		return nil
	}
	if p.parenLevel != originalParenLev {
		p.errorf("unbalanced parens: %d prev: %d", p.parenLevel, originalParenLev)
		return nil
	}
	return
}

func (p *parser) parseOperation(originalParenLevel int, lhs ast.Expression) (expr ast.Expression) {
	p.next()
	switch p.current.typ {
	case itemUnaryOperator:
		expr = p.parseUnaryExpressionLeft(lhs, p.current)
	case itemAdditionOperator, itemSubtractionOperator, itemConcatenationOperator, itemComparisonOperator, itemMultOperator, itemAndOperator, itemOrOperator, itemAmpersandOperator, itemBitwiseXorOperator, itemBitwiseOrOperator, itemBitwiseShiftOperator, itemWrittenAndOperator, itemWrittenXorOperator, itemWrittenOrOperator:
		expr = p.parseBinaryOperation(lhs, p.current, originalParenLevel)
	case itemTernaryOperator1:
		expr = p.parseTernaryOperation(lhs)
	case itemCloseParen:
		if p.parenLevel <= originalParenLevel {
			p.backup()
			return lhs
		}
		p.parenLevel -= 1
		return p.parseOperation(originalParenLevel, lhs)
	default:
		p.backup()
		return lhs
	}
	return p.parseOperation(originalParenLevel, expr)
}

func (p *parser) parseBinaryOperation(lhs ast.Expression, operator Item, originalParenLevel int) ast.Expression {
	p.next()
	rhs := p.expressionize()
	for {
		p.next()
		nextOperator := p.current
		p.backup()
		nextOperatorPrecedence, ok := operatorPrecedence[nextOperator.typ]
		if ok && nextOperatorPrecedence > operatorPrecedence[operator.typ] {
			rhs = p.parseOperation(originalParenLevel, rhs)
		} else {
			break
		}
	}
	return newBinaryOperation(operator, lhs, rhs)
}

func (p *parser) parseTernaryOperation(lhs ast.Expression) ast.Expression {
	truthy := p.parseNextExpression()
	p.expect(itemTernaryOperator2)
	falsy := p.parseNextExpression()
	return &ast.OperatorExpression{
		Operand1: lhs,
		Operand2: truthy,
		Operand3: falsy,
		Type:     truthy.EvaluatesTo() | falsy.EvaluatesTo(),
		Operator: "?:",
	}
}

func (p *parser) parseUnaryExpressionRight(operand ast.Expression, operator Item) ast.Expression {
	return newUnaryOperation(operator, operand)
}

func (p *parser) parseUnaryExpressionLeft(operand ast.Expression, operator Item) ast.Expression {
	return newUnaryOperation(operator, operand)
}

// expressionize takes the current token and returns it as the simplest
// expression for that token. That means an expression with no operators
// except for the object operator.
func (p *parser) expressionize() ast.Expression {
	switch p.current.typ {
	case itemIdentifier:
		return p.parseIdentifier()
	case itemStringLiteral, itemBooleanLiteral, itemNumberLiteral:
		return p.parseLiteral()
	case itemNonVariableIdentifier:
		if p.peek().typ == itemOpenParen {
			expr := p.parseFunctionCall()
			if p.peek().typ == itemObjectOperator {
				return p.parseObjectLookup(expr)
			}
			return expr
		}
		if p.peek().typ == itemScopeResolutionOperator {
			p.expect(itemScopeResolutionOperator)
			return &ast.ClassExpression{
				Receiver:   p.current.val,
				Expression: p.parseNextExpression(),
			}
		}
		return ast.ConstantExpression{
			Identifier: ast.NewIdentifier(p.current.val),
		}
	case itemOpenParen:
		return p.parseExpression()
	}
	// error?
	return nil
}

func (p *parser) parseLiteral() *ast.Literal {
	switch p.current.typ {
	case itemStringLiteral:
		return &ast.Literal{Type: ast.String}
	case itemBooleanLiteral:
		return &ast.Literal{Type: ast.Boolean}
	case itemNumberLiteral:
		return &ast.Literal{Type: ast.Float}
	}
	p.errorf("Unknown literal type")
	return nil
}

func (p *parser) parseIdentifier() ast.Expression {
	ident := ast.NewIdentifier(p.current.val)
	switch pk := p.peek(); pk.typ {
	case itemObjectOperator:
		return p.parseObjectLookup(ident)
	case itemArrayLookupOperatorLeft:
		return p.parseArrayLookup(ident)
	}
	return ident
}

func (p *parser) parseObjectLookup(r ast.Expression) ast.Expression {
	p.expect(itemObjectOperator)
	p.expect(itemNonVariableIdentifier)
	if pk := p.peek(); pk.typ == itemOpenParen {
		expr := &ast.MethodCallExpression{
			Receiver:               r,
			FunctionCallExpression: p.parseFunctionCall(),
		}
		return expr
	}
	return &ast.PropertyExpression{
		Receiver: r,
		Name:     p.current.val,
	}
}

func (p *parser) parseArrayLookup(e ast.Expression) ast.Expression {
	p.expect(itemArrayLookupOperatorLeft)
	if p.peek().typ == itemArrayLookupOperatorRight {
		p.expect(itemArrayLookupOperatorRight)
		return ast.ArrayAppendExpression{Array: e}
	}
	p.next()
	expr := &ast.ArrayLookupExpression{
		Array: e,
		Index: p.parseExpression(),
	}
	p.expect(itemArrayLookupOperatorRight)
	if p.peek().typ == itemArrayLookupOperatorLeft {
		return p.parseArrayLookup(expr)
	}
	return expr
}

func (p *parser) parseArrayDeclaration() ast.Expression {
	pairs := make([]ast.ArrayPair, 0)
	p.expect(itemOpenParen)
	var key, val ast.Expression
ArrayLoop:
	for {
		p.next()
		switch p.current.typ {
		case itemArrayKeyOperator:
			if val == nil {
				p.errorf("expected array key before =>.")
				return nil
			}
			key = val
			p.next()
			val = p.parseExpression()
		case itemCloseParen:
			if val != nil {
				pairs = append(pairs, ast.ArrayPair{key, val})
			}
			break ArrayLoop
		case itemArgumentSeparator:
			pairs = append(pairs, ast.ArrayPair{key, val})
			key = nil
			val = nil
		default:
			val = p.parseExpression()
		}
	}
	return &ast.ArrayExpression{Pairs: pairs}
}
<commit_msg>Supporting a potential operation after a parethesized expression. e.g. (1 + 2) / 3<commit_after>package php

import "stephensearles.com/php/ast"

/*

Valid Expression Patterns
Expr [Binary Op] Expr
[Unary Op] Expr
Expr [Unary Op]
Expr [Tertiary Op 1] Expr [Tertiary Op 2] Expr
Identifier
Literal
Function Call

Parentesis always triggers sub-expression

non-associative clone new clone and new
left  [ array()
right ++ -- ~ (int) (float) (string) (array) (object) (bool) @  types and increment/decrement
non-associative instanceof  types
right ! logical
left  * / % arithmetic
left  + - . arithmetic and string
left  << >> bitwise
non-associative < <= > >= comparison
non-associative == != === !== <>  comparison
left  & bitwise and references
left  ^ bitwise
left  | bitwise
left  &&  logical
left  ||  logical
left  ? : ternary
right = += -= *= /= .= %= &= |= ^= <<= >>= => assignment
left  and logical
left  xor logical
left  or  logical
left  , many uses

*/

var operatorPrecedence = map[ItemType]int{
	itemArrayLookupOperatorLeft:  19,
	itemArrayLookupOperatorRight: 19,
	itemUnaryOperator:            18,
	itemCastOperator:             18,
	itemInstanceofOperator:       17,
	itemNegationOperator:         16,
	itemMultOperator:             15,
	itemAdditionOperator:         14,
	itemSubtractionOperator:      14,
	itemConcatenationOperator:    14,

	itemBitwiseShiftOperator: 13,
	itemComparisonOperator:   12,
	itemEqualityOperator:     11,

	itemAmpersandOperator:  10,
	itemBitwiseXorOperator: 9,
	itemBitwiseOrOperator:  8,
	itemAndOperator:        7,
	itemOrOperator:         6,
	itemTernaryOperator1:   5,
	itemTernaryOperator2:   5,
	itemAssignmentOperator: 4,
	itemWrittenAndOperator: 3,
	itemWrittenXorOperator: 2,
	itemWrittenOrOperator:  1,
}

func (p *parser) parseExpression() (expr ast.Expression) {
	// consume expression
	originalParenLev := p.parenLevel
	switch p.current.typ {
	case itemNewOperator:
		return &ast.NewExpression{
			Expression: p.parseNextExpression(),
		}
	case itemUnaryOperator, itemNegationOperator, itemAmpersandOperator:
		op := p.current
		expr = p.parseUnaryExpressionRight(p.parseNextExpression(), op)
	case itemArray:
		return p.parseArrayDeclaration()
	case itemIdentifier:
		if p.peek().typ == itemAssignmentOperator {
			assignee := p.parseIdentifier().(ast.Assignable)
			p.next()
			return ast.AssignmentExpression{
				Assignee: assignee,
				Operator: p.current.val,
				Value:    p.parseNextExpression(),
			}
		}
		fallthrough
	case itemNonVariableIdentifier, itemStringLiteral, itemNumberLiteral, itemBooleanLiteral:
		expr = p.parseOperation(originalParenLev, p.expressionize())
	case itemOpenParen:
		p.parenLevel += 1
		p.next()
		expr = p.parseExpression()
		p.expect(itemCloseParen)
		p.parenLevel -= 1
		expr = p.parseOperation(originalParenLev, expr)
	default:
		p.errorf("Expected expression. Found %s", p.current)
		return nil
	}
	if p.parenLevel != originalParenLev {
		p.errorf("unbalanced parens: %d prev: %d", p.parenLevel, originalParenLev)
		return nil
	}
	return
}

func (p *parser) parseOperation(originalParenLevel int, lhs ast.Expression) (expr ast.Expression) {
	p.next()
	switch p.current.typ {
	case itemUnaryOperator:
		expr = p.parseUnaryExpressionLeft(lhs, p.current)
	case itemAdditionOperator, itemSubtractionOperator, itemConcatenationOperator, itemComparisonOperator, itemMultOperator, itemAndOperator, itemOrOperator, itemAmpersandOperator, itemBitwiseXorOperator, itemBitwiseOrOperator, itemBitwiseShiftOperator, itemWrittenAndOperator, itemWrittenXorOperator, itemWrittenOrOperator:
		expr = p.parseBinaryOperation(lhs, p.current, originalParenLevel)
	case itemTernaryOperator1:
		expr = p.parseTernaryOperation(lhs)
	case itemCloseParen:
		if p.parenLevel <= originalParenLevel {
			p.backup()
			return lhs
		}
		p.parenLevel -= 1
		return p.parseOperation(originalParenLevel, lhs)
	default:
		p.backup()
		return lhs
	}
	return p.parseOperation(originalParenLevel, expr)
}

func (p *parser) parseBinaryOperation(lhs ast.Expression, operator Item, originalParenLevel int) ast.Expression {
	p.next()
	rhs := p.expressionize()
	for {
		p.next()
		nextOperator := p.current
		p.backup()
		nextOperatorPrecedence, ok := operatorPrecedence[nextOperator.typ]
		if ok && nextOperatorPrecedence > operatorPrecedence[operator.typ] {
			rhs = p.parseOperation(originalParenLevel, rhs)
		} else {
			break
		}
	}
	return newBinaryOperation(operator, lhs, rhs)
}

func (p *parser) parseTernaryOperation(lhs ast.Expression) ast.Expression {
	truthy := p.parseNextExpression()
	p.expect(itemTernaryOperator2)
	falsy := p.parseNextExpression()
	return &ast.OperatorExpression{
		Operand1: lhs,
		Operand2: truthy,
		Operand3: falsy,
		Type:     truthy.EvaluatesTo() | falsy.EvaluatesTo(),
		Operator: "?:",
	}
}

func (p *parser) parseUnaryExpressionRight(operand ast.Expression, operator Item) ast.Expression {
	return newUnaryOperation(operator, operand)
}

func (p *parser) parseUnaryExpressionLeft(operand ast.Expression, operator Item) ast.Expression {
	return newUnaryOperation(operator, operand)
}

// expressionize takes the current token and returns it as the simplest
// expression for that token. That means an expression with no operators
// except for the object operator.
func (p *parser) expressionize() ast.Expression {
	switch p.current.typ {
	case itemIdentifier:
		return p.parseIdentifier()
	case itemStringLiteral, itemBooleanLiteral, itemNumberLiteral:
		return p.parseLiteral()
	case itemNonVariableIdentifier:
		if p.peek().typ == itemOpenParen {
			expr := p.parseFunctionCall()
			if p.peek().typ == itemObjectOperator {
				return p.parseObjectLookup(expr)
			}
			return expr
		}
		if p.peek().typ == itemScopeResolutionOperator {
			p.expect(itemScopeResolutionOperator)
			return &ast.ClassExpression{
				Receiver:   p.current.val,
				Expression: p.parseNextExpression(),
			}
		}
		return ast.ConstantExpression{
			Identifier: ast.NewIdentifier(p.current.val),
		}
	case itemOpenParen:
		return p.parseExpression()
	}
	// error?
	return nil
}

func (p *parser) parseLiteral() *ast.Literal {
	switch p.current.typ {
	case itemStringLiteral:
		return &ast.Literal{Type: ast.String}
	case itemBooleanLiteral:
		return &ast.Literal{Type: ast.Boolean}
	case itemNumberLiteral:
		return &ast.Literal{Type: ast.Float}
	}
	p.errorf("Unknown literal type")
	return nil
}

func (p *parser) parseIdentifier() ast.Expression {
	ident := ast.NewIdentifier(p.current.val)
	switch pk := p.peek(); pk.typ {
	case itemObjectOperator:
		return p.parseObjectLookup(ident)
	case itemArrayLookupOperatorLeft:
		return p.parseArrayLookup(ident)
	}
	return ident
}

func (p *parser) parseObjectLookup(r ast.Expression) ast.Expression {
	p.expect(itemObjectOperator)
	p.expect(itemNonVariableIdentifier)
	if pk := p.peek(); pk.typ == itemOpenParen {
		expr := &ast.MethodCallExpression{
			Receiver:               r,
			FunctionCallExpression: p.parseFunctionCall(),
		}
		return expr
	}
	return &ast.PropertyExpression{
		Receiver: r,
		Name:     p.current.val,
	}
}

func (p *parser) parseArrayLookup(e ast.Expression) ast.Expression {
	p.expect(itemArrayLookupOperatorLeft)
	if p.peek().typ == itemArrayLookupOperatorRight {
		p.expect(itemArrayLookupOperatorRight)
		return ast.ArrayAppendExpression{Array: e}
	}
	p.next()
	expr := &ast.ArrayLookupExpression{
		Array: e,
		Index: p.parseExpression(),
	}
	p.expect(itemArrayLookupOperatorRight)
	if p.peek().typ == itemArrayLookupOperatorLeft {
		return p.parseArrayLookup(expr)
	}
	return expr
}

func (p *parser) parseArrayDeclaration() ast.Expression {
	pairs := make([]ast.ArrayPair, 0)
	p.expect(itemOpenParen)
	var key, val ast.Expression
ArrayLoop:
	for {
		p.next()
		switch p.current.typ {
		case itemArrayKeyOperator:
			if val == nil {
				p.errorf("expected array key before =>.")
				return nil
			}
			key = val
			p.next()
			val = p.parseExpression()
		case itemCloseParen:
			if val != nil {
				pairs = append(pairs, ast.ArrayPair{key, val})
			}
			break ArrayLoop
		case itemArgumentSeparator:
			pairs = append(pairs, ast.ArrayPair{key, val})
			key = nil
			val = nil
		default:
			val = p.parseExpression()
		}
	}
	return &ast.ArrayExpression{Pairs: pairs}
}
<|endoftext|>


<commit_before>package api

import (
	"encoding/xml"
	"fmt"
	"net/http"
	"time"

	"github.com/didip/tollbooth"
	"github.com/didip/tollbooth_chi"
	"github.com/go-chi/chi"
	"github.com/go-chi/chi/middleware"
	"github.com/go-chi/render"
	log "github.com/go-pkgz/lgr"
	"github.com/go-pkgz/rest"
	"github.com/go-pkgz/rest/logger"

	"github.com/umputun/feed-master/app/feed"
	"github.com/umputun/feed-master/app/proc"
)

type Server struct {
	Version    string
	Conf       proc.Conf
	Store      *proc.BoltDB
	httpServer *http.Server
}

func (s *Server) Run(port int) {

	router := chi.NewRouter()
	router.Use(middleware.RealIP, rest.Recoverer(log.Default()))
	router.Use(middleware.Throttle(1000), middleware.Timeout(60*time.Second))
	router.Use(rest.AppInfo("feed-master", "umputun", s.Version), rest.Ping)
	l := logger.New(logger.Flags(logger.All), logger.Log(log.Default()), logger.Prefix("[INFO]"))
	router.Use(l.Handler, tollbooth_chi.LimitHandler(tollbooth.NewLimiter(5, nil)))

	s.httpServer = &http.Server{
		Addr:              fmt.Sprintf(":%d", port),
		Handler:           router,
		ReadHeaderTimeout: 5 * time.Second,
		WriteTimeout:      30 * time.Second,
		IdleTimeout:       30 * time.Second,
	}

	router.Get("/rss/{name}", s.getFeedCtrl)
	router.Get("/list", s.getListCtrl)

	err := s.httpServer.ListenAndServe()
	log.Printf("[WARN] http server terminated, %s", err)
}

// GET /rss/{name} - returns rss for given feeds set
func (s *Server) getFeedCtrl(w http.ResponseWriter, r *http.Request) {
	feedName := chi.URLParam(r, "name")
	items, err := s.Store.Load(feedName, s.Conf.System.MaxTotal)
	if err != nil {
		rest.SendErrorJSON(w, r, log.Default(), http.StatusBadRequest, err, "failed to get feed")
		return
	}

	rss := feed.Rss2{
		Version:       "2.0",
		ItemList:      items,
		Title:         s.Conf.Feeds[feedName].Title,
		Description:   s.Conf.Feeds[feedName].Description,
		Language:      s.Conf.Feeds[feedName].Language,
		Link:          s.Conf.Feeds[feedName].Link,
		PubDate:       items[0].PubDate,
		LastBuildDate: time.Now().Format(time.RFC822Z),
	}

	b, err := xml.MarshalIndent(&rss, "", "  ")
	if err != nil {
		rest.SendErrorJSON(w, r, log.Default(), http.StatusInternalServerError, err, "failed to marshal rss")
		return
	}
	w.Header().Set("Content-Type", "application/xml; charset=UTF-8")
	fmt.Fprintf(w, "%s", string(b))
}

// GET /list - returns feed's image
func (s *Server) getListCtrl(w http.ResponseWriter, r *http.Request) {
	buckets, err := s.Store.Buckets()
	if err != nil {
		rest.SendErrorJSON(w, r, log.Default(), http.StatusInternalServerError, err, "failed to read list")
		return
	}
	render.JSON(w, r, buckets)
}
<commit_msg>restore images controller<commit_after>package api

import (
	"encoding/xml"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"

	"github.com/didip/tollbooth"
	"github.com/didip/tollbooth_chi"
	"github.com/go-chi/chi"
	"github.com/go-chi/chi/middleware"
	"github.com/go-chi/render"
	log "github.com/go-pkgz/lgr"
	"github.com/go-pkgz/rest"
	"github.com/go-pkgz/rest/logger"

	"github.com/umputun/feed-master/app/feed"
	"github.com/umputun/feed-master/app/proc"
)

// Server provides HTTP API
type Server struct {
	Version    string
	Conf       proc.Conf
	Store      *proc.BoltDB
	httpServer *http.Server
}

// Run starts http server for API with all routes
func (s *Server) Run(port int) {

	router := chi.NewRouter()
	router.Use(middleware.RealIP, rest.Recoverer(log.Default()))
	router.Use(middleware.Throttle(1000), middleware.Timeout(60*time.Second))
	router.Use(rest.AppInfo("feed-master", "umputun", s.Version), rest.Ping)
	l := logger.New(logger.Flags(logger.All), logger.Log(log.Default()), logger.Prefix("[INFO]"))
	router.Use(l.Handler, tollbooth_chi.LimitHandler(tollbooth.NewLimiter(5, nil)))

	s.httpServer = &http.Server{
		Addr:              fmt.Sprintf(":%d", port),
		Handler:           router,
		ReadHeaderTimeout: 5 * time.Second,
		WriteTimeout:      30 * time.Second,
		IdleTimeout:       30 * time.Second,
	}

	router.Get("/rss/{name}", s.getFeedCtrl)
	router.Get("/list", s.getListCtrl)
	router.Get("/image/{name}", s.getImageCtrl)

	err := s.httpServer.ListenAndServe()
	log.Printf("[WARN] http server terminated, %s", err)
}

// GET /rss/{name} - returns rss for given feeds set
func (s *Server) getFeedCtrl(w http.ResponseWriter, r *http.Request) {
	feedName := chi.URLParam(r, "name")
	items, err := s.Store.Load(feedName, s.Conf.System.MaxTotal)
	if err != nil {
		rest.SendErrorJSON(w, r, log.Default(), http.StatusBadRequest, err, "failed to get feed")
		return
	}

	rss := feed.Rss2{
		Version:       "2.0",
		ItemList:      items,
		Title:         s.Conf.Feeds[feedName].Title,
		Description:   s.Conf.Feeds[feedName].Description,
		Language:      s.Conf.Feeds[feedName].Language,
		Link:          s.Conf.Feeds[feedName].Link,
		PubDate:       items[0].PubDate,
		LastBuildDate: time.Now().Format(time.RFC822Z),
	}

	b, err := xml.MarshalIndent(&rss, "", "  ")
	if err != nil {
		rest.SendErrorJSON(w, r, log.Default(), http.StatusInternalServerError, err, "failed to marshal rss")
		return
	}
	w.Header().Set("Content-Type", "application/xml; charset=UTF-8")
	fmt.Fprintf(w, "%s", string(b))
}

// GET /image/{name}
func (s *Server) getImageCtrl(w http.ResponseWriter, r *http.Request) {

	http.HandleFunc("/image/", func(w http.ResponseWriter, r *http.Request) {
		fm := chi.URLParam(r, "name")
		fm = strings.TrimRight(fm, ".png")
		feedConf, found := s.Conf.Feeds[fm]
		if !found {
			rest.SendErrorJSON(w, r, log.Default(), http.StatusBadRequest,
				errors.New("image "+chi.URLParam(r, "name")+" not found"), "failed to load image")
			return
		}

		b, err := ioutil.ReadFile(feedConf.Image)
		if err != nil {
			rest.SendErrorJSON(w, r, log.Default(), http.StatusBadRequest,
				errors.New("can't read  "+chi.URLParam(r, "name")), "failed to read image")
			return
		}
		w.Header().Set("Content-Type", "image/png")
		if _, err := w.Write(b); err != nil {
			log.Printf("[WARN] failed to send image, %s", err)
		}
	})
}

// GET /list - returns feed's image
func (s *Server) getListCtrl(w http.ResponseWriter, r *http.Request) {
	buckets, err := s.Store.Buckets()
	if err != nil {
		rest.SendErrorJSON(w, r, log.Default(), http.StatusInternalServerError, err, "failed to read list")
		return
	}
	render.JSON(w, r, buckets)
}
<|endoftext|>


<commit_before>package goldmark_test

import (
	"bytes"
	"strings"
	"testing"
	"time"

	. "github.com/yuin/goldmark"
	"github.com/yuin/goldmark/ast"
	"github.com/yuin/goldmark/parser"
	"github.com/yuin/goldmark/renderer/html"
	"github.com/yuin/goldmark/testutil"
)

func TestExtras(t *testing.T) {
	markdown := New(WithRendererOptions(
		html.WithXHTML(),
		html.WithUnsafe(),
	))
	testutil.DoTestCaseFile(markdown, "_test/extra.txt", t, testutil.ParseCliCaseArg()...)
}

func TestEndsWithNonSpaceCharacters(t *testing.T) {
	markdown := New(WithRendererOptions(
		html.WithXHTML(),
		html.WithUnsafe(),
	))
	source := []byte("```\na\n```")
	var b bytes.Buffer
	err := markdown.Convert(source, &b)
	if err != nil {
		t.Error(err.Error())
	}
	if b.String() != "
a\n
\n" {
		t.Errorf("%s \n---------\n %s", source, b.String())
	}
}

func TestWindowsNewLine(t *testing.T) {
	markdown := New(WithRendererOptions(
		html.WithXHTML(),
	))
	source := []byte("a  \r\nb\n")
	var b bytes.Buffer
	err := markdown.Convert(source, &b)
	if err != nil {
		t.Error(err.Error())
	}
	if b.String() != "
a
\nb
\n" {
		t.Errorf("%s\n---------\n%s", source, b.String())
	}

	source = []byte("a\\\r\nb\r\n")
	var b2 bytes.Buffer
	err = markdown.Convert(source, &b2)
	if err != nil {
		t.Error(err.Error())
	}
	if b2.String() != "
a
\nb
\n" {
		t.Errorf("\n%s\n---------\n%s", source, b2.String())
	}
}

type myIDs struct {
}

func (s *myIDs) Generate(value []byte, kind ast.NodeKind) []byte {
	return []byte("my-id")
}

func (s *myIDs) Put(value []byte) {
}

func TestAutogeneratedIDs(t *testing.T) {
	ctx := parser.NewContext(parser.WithIDs(&myIDs{}))
	markdown := New(WithParserOptions(parser.WithAutoHeadingID()))
	source := []byte("# Title1\n## Title2")
	var b bytes.Buffer
	err := markdown.Convert(source, &b, parser.WithContext(ctx))
	if err != nil {
		t.Error(err.Error())
	}
	if b.String() != `
Title1

Title2

` {
		t.Errorf("%s\n---------\n%s", source, b.String())
	}
}

func nowMillis() int64 {
	// TODO: replace UnixNano to UnixMillis(drops Go1.16 support)
	return time.Now().UnixNano() / 1000000
}

func TestDeepNestedLabelPerformance(t *testing.T) {
	markdown := New(WithRendererOptions(
		html.WithXHTML(),
		html.WithUnsafe(),
	))

	started := nowMillis()
	n := 50000
	source := []byte(strings.Repeat("[", n) + strings.Repeat("]", n))
	var b bytes.Buffer
	_ = markdown.Convert(source, &b)
	finished := nowMillis()
	if (finished - started) > 5000 {
		t.Error("Parsing deep nested labels took more 5 secs")
	}
}

func TestManyProcessingInstructionPerformance(t *testing.T) {
	markdown := New(WithRendererOptions(
		html.WithXHTML(),
		html.WithUnsafe(),
	))

	started := nowMillis()
	n := 50000
	source := []byte("a " + strings.Repeat("<?", n))
	var b bytes.Buffer
	_ = markdown.Convert(source, &b)
	finished := nowMillis()
	if (finished - started) > 5000 {
		t.Error("Parsing processing instructions took more 5 secs")
	}
}

func TestManyCDATAPerformance(t *testing.T) {
	markdown := New(WithRendererOptions(
		html.WithXHTML(),
		html.WithUnsafe(),
	))

	started := nowMillis()
	n := 50000
	source := []byte(strings.Repeat("a <![CDATA[", n))
	var b bytes.Buffer
	_ = markdown.Convert(source, &b)
	finished := nowMillis()
	if (finished - started) > 5000 {
		t.Error("Parsing processing instructions took more 5 secs")
	}
}

func TestManyDeclPerformance(t *testing.T) {
	markdown := New(WithRendererOptions(
		html.WithXHTML(),
		html.WithUnsafe(),
	))

	started := nowMillis()
	n := 50000
	source := []byte(strings.Repeat("a <!A ", n))
	var b bytes.Buffer
	_ = markdown.Convert(source, &b)
	finished := nowMillis()
	if (finished - started) > 5000 {
		t.Error("Parsing processing instructions took more 5 secs")
	}
}

func TestManyCommentPerformance(t *testing.T) {
	markdown := New(WithRendererOptions(
		html.WithXHTML(),
		html.WithUnsafe(),
	))

	started := nowMillis()
	n := 50000
	source := []byte(strings.Repeat("a


<commit_before>package weedharvester

import (
	"bytes"
	"io/ioutil"
	"testing"
	"time"
)

func TestCreateFiler(t *testing.T) {
	filer := NewFiler("http://docker:8888")
	now := time.Now().UTC()
	timeAsString := now.Format(time.RFC3339Nano)
	err := createFile(timeAsString, "test/path", "only a test", &filer)
	if err != nil {
		t.Errorf("Error: %s", err)
	}
}

func TestReadFiler(t *testing.T) {
	filer := NewFiler("http://docker:8888")
	content := "Only a test"
	err := createFile("test", "test/read", content, &filer)
	if err != nil {
		t.Errorf("Error: %s", err)
	}

	reader := filer.Read("test", "test/read")
	data, err := ioutil.ReadAll(reader)
	if err != nil {
		t.Errorf("Error: %s", err)
	}

	if string(data) != content {
		t.Errorf("Expected %s but got %s", content, string(data))
	}

}

func TestReadDirectory(t *testing.T) {
	filer := NewFiler("http://docker:8888")
	content := "Only a test"
	err := createFile("test", "test/path", content, &filer)

	if err != nil {
		t.Errorf("Error: %s", err)
	}

	directory := filer.ReadDirectory("test", "")

	if directory.Directory != "test" {
		t.Errorf("Error: Retruned directory is not named test")
	}
}

func createFile(filename string, filepath string, content string, filer *Filer) error {
	data := bytes.NewReader([]byte(content))
	return filer.Create(data, filename, filepath)
}
<commit_msg>One more error within tests<commit_after>package weedharvester

import (
	"bytes"
	"io/ioutil"
	"testing"
	"time"
)

func TestCreateFiler(t *testing.T) {
	filer := NewFiler("http://docker:8888")
	now := time.Now().UTC()
	timeAsString := now.Format(time.RFC3339Nano)
	err := createFile(timeAsString, "test/path", "only a test", &filer)
	if err != nil {
		t.Errorf("Error: %s", err)
	}
}

func TestReadFiler(t *testing.T) {
	filer := NewFiler("http://docker:8888")
	content := "Only a test"
	err := createFile("test", "test/read", content, &filer)
	if err != nil {
		t.Errorf("Error: %s", err)
	}

	reader := filer.Read("test", "test/read")
	data, err := ioutil.ReadAll(reader)
	if err != nil {
		t.Errorf("Error: %s", err)
	}

	if string(data) != content {
		t.Errorf("Expected %s but got %s", content, string(data))
	}

}

func TestReadDirectory(t *testing.T) {
	filer := NewFiler("http://docker:8888")
	content := "Only a test"
	err := createFile("test", "test/path", content, &filer)

	if err != nil {
		t.Errorf("Error: %s", err)
	}

	directory := filer.ReadDirectory("test/path", "")

	if directory.Directory != "/test/path/" {
		t.Errorf("Error: Retruned directory is not named test but %s", directory.Directory)
	}
}

func createFile(filename string, filepath string, content string, filer *Filer) error {
	data := bytes.NewReader([]byte(content))
	return filer.Create(data, filename, filepath)
}
<|endoftext|>


<commit_before>package dskvs

import (
	"bytes"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"
)

var genericPage = &page{
	isDirty:   true,
	isDeleted: false,
	basepath:  "imdb",
	coll:      "FMJ",
	key:       "Is that you, John Wayne? Is this me?",
	value: []byte(`Who said that? WHO THE FUCK said that?! Who's the slimy
commode of shit twinkle toed cocksucker just signed his own death warrant?!
Nobody huh? The fairy fucking godmother said it! Out fucking standing! I will
PT you all until you fucking DIE! I will PT you until your assholes are
sucking buttermilk!`),
}

func TestWriteAndReadDirtyPage(t *testing.T) {
	expected := genericPage

	filename := generateFilename(expected)
	os.MkdirAll(filepath.Dir(filename), DIR_PERM)
	defer os.RemoveAll(expected.basepath)

	writeToFile(expected)

	// Make sure to clean up if something goes wrong
	defer os.Remove(filename)
	if expected.isDirty {
		t.Errorf("Should have been cleaned up on write")
	}

	actual, err := readFromFile(filename)

	if err != nil {
		t.Fatalf("Failed reading file. %v", err)
	}

	if actual.key != expected.key {
		t.Errorf("Expected key <%s> but was <%s>",
			expected.key,
			actual.key)
	}

	expectedLen := len(expected.value)
	actualLen := len(actual.value)

	if actualLen != expectedLen {
		t.Errorf("Expected len(value) %d but was %d",
			expectedLen,
			actualLen)
	}

	var minLen int
	if expectedLen < actualLen {
		if expectedLen > 20 {
			minLen = 20
		} else {
			minLen = expectedLen
		}
	} else {
		if actualLen > 20 {
			minLen = 20
		} else {
			minLen = actualLen
		}
	}

	if !bytes.Equal(actual.value, expected.value) {
		t.Errorf("Expected value (truncated) <%v> but was <%v>",
			expected.value[:minLen],
			actual.value[:minLen])
	}
}

func TestCreatesPageWithHugeKeys(t *testing.T) {

	expected := genericPage
	expected.key = `artist/this is a very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very long key name`

	filename := generateFilename(expected)
	os.MkdirAll(filepath.Dir(filename), DIR_PERM)
	defer os.RemoveAll(expected.basepath)

	if err := writeToFile(expected); err != nil {
		t.Errorf("Couldn't write page with long key, %v", err)
	}

	actual, err := readFromFile(filename)
	if err != nil {
		t.Errorf("Couldn't read page <%s> back : %v", filename, err)
	}

	if err := deleteFile(filename); err != nil {
		t.Fatalf("Couldn't delete page with long key, %v", err)
	}

	if err = os.Remove(filename); os.IsExist(err) {
		t.Errorf("Didn't delete file <%s> : %v", filename, err)
	}

	if !bytes.Equal(actual.value, expected.value) {
		t.Errorf("Read different value than what was written")
	}

}

func TestDeletingPageShouldDeleteFile(t *testing.T) {

	expected := genericPage

	filename := generateFilename(expected)
	os.MkdirAll(filepath.Dir(filename), DIR_PERM)
	defer os.RemoveAll(expected.basepath)

	writeToFile(expected)

	actual, err := readFromFile(filename)
	if err != nil {
		t.Errorf("Couldn't read page <%s> back : %v", filename, err)
	}

	actual.isDirty = true
	actual.isDeleted = true

	writeToFile(actual)

	if err = os.Remove(filename); os.IsExist(err) {
		t.Errorf("Didn't delete file <%s> : %v", filename, err)
	}

}

func TestErrorWhenReadingJunkFile(t *testing.T) {
	filename := "junk_file.test"
	ioutil.WriteFile(filename, []byte{0xDE, 0xAD, 0xBE, 0xEF}, FILE_PERM)
	defer os.Remove(filename)

	_, err := readFromFile(filename)
	if _, isRightType := err.(FileError); !isRightType {
		t.Errorf("Should have returned an error of type FileError"+
			", error was %v",
			err)
	}
	err.Error() // Call it to make gocov happy
}

func TestErrorWhenOpeningDifferentMajorVersion(t *testing.T) {
	filename := "incompatible_version.test"
	aPage := genericPage

	// Get the header as it will be written to disk
	currentHeader := newFileHeader(aPage)
	// Modify it
	currentHeader.Major = MAJOR_VERSION + 1
	headerBytes, err := headerToBytes(currentHeader)
	if err != nil {
		t.Fatalf("Couldn't get fake header, %v", err)
	}

	// Get the data
	pageBytes, err := fromPageToBytes(aPage)
	// Overwrite the header
	copy(pageBytes, headerBytes)

	if err := ioutil.WriteFile(filename, pageBytes, FILE_PERM); err != nil {
		t.Fatalf("Couldn't write file <%s> : %v", filename, err)
	}
	defer os.Remove(filename)

	_, err = readFromFile(filename)
	if _, isRightType := err.(FileError); !isRightType {
		t.Errorf("Should have returned an error of type FileError"+
			", error was %v",
			err)
	}
	err.Error() // Call it to make gocov happy

}

func TestErrorWhenHeaderComesFromAnotherPageWithSimilarLength(t *testing.T) {
	filename := "impostor.test"
	aPage := genericPage

	impostor := &page{
		isDirty:   true,
		isDeleted: false,
		basepath:  "imdb",
		coll:      "FMJ",
		key:       "Is that you, John Wayne? Is this me?",
		// value has same length but different text
		value: []byte(`HAHAHA yes !!? WHO THE FUCK said that?! Who's the slimy
commode of shit twinkle toed cocksucker just signed his own death warrant?!
Nobody huh? The fairy fucking godmother said it! Out fucking standing! I will
PT you all until you fucking DIE! I will PT you until your assholes are
sucking buttermilk!`),
	}

	impostorHeader := newFileHeader(impostor)
	headerBytes, err := headerToBytes(impostorHeader)
	if err != nil {
		t.Fatalf("Couldn't get fake header, %v", err)
	}
	pageBytes, err := fromPageToBytes(aPage)
	copy(pageBytes, headerBytes)

	if err := ioutil.WriteFile(filename, pageBytes, FILE_PERM); err != nil {
		t.Fatalf("Couldn't write file <%s> : %v", filename, err)
	}
	defer os.Remove(filename)

	_, err = readFromFile(filename)
	if _, isRightType := err.(FileError); !isRightType {
		t.Errorf("Should have returned an error of type FileError"+
			", error was %v",
			err)
	}
	err.Error() // Call it to make gocov happy
}

func TestErrorWhenHeaderComesFromAnotherPageWithDifferentLength(t *testing.T) {
	filename := "impostor.test"
	aPage := genericPage

	pageBytes, err := fromPageToBytes(aPage)

	impostor := genericPage
	impostor.value = []byte("hahahaha yes it's me")

	impostorHeader := newFileHeader(impostor)
	headerBytes, err := headerToBytes(impostorHeader)
	if err != nil {
		t.Fatalf("Couldn't get fake header, %v", err)
	}

	copy(pageBytes, headerBytes)

	if err := ioutil.WriteFile(filename, pageBytes, FILE_PERM); err != nil {
		t.Fatalf("Couldn't write file <%s> : %v", filename, err)
	}
	defer os.Remove(filename)

	result, err := readFromFile(filename)
	if _, isRightType := err.(FileError); !isRightType {
		t.Errorf("Should have returned an error of type FileError"+
			", error was %v, page was : %v",
			err,
			result)
	}
	err.Error() // Call it to make gocov happy
}

func TestErrorWhenChecksumIsWrong(t *testing.T) {
	filename := "wrong_checksum.test"
	aPage := &page{
		isDirty:   true,
		isDeleted: false,
		basepath:  "imdb",
		coll:      "FMJ",
		key:       "Is that you, John Wayne? Is this me?",
		value: []byte(`Who said that? WHO THE FUCK said that?! Who's the slimy
commode of shit twinkle toed cocksucker just signed his own death warrant?!
Nobody huh? The fairy fucking godmother said it! Out fucking standing! I will
PT you all until you fucking DIE! I will PT you until your assholes are
sucking buttermilk!`),
	}

	currentHeader := newFileHeader(aPage)
	currentHeader.Checksum = currentHeader.Checksum + 1
	headerBytes, err := headerToBytes(currentHeader)
	if err != nil {
		t.Fatalf("Couldn't get fake header, %v", err)
	}
	pageBytes, err := fromPageToBytes(aPage)
	copy(pageBytes, headerBytes)

	if err := ioutil.WriteFile(filename, pageBytes, FILE_PERM); err != nil {
		t.Fatalf("Couldn't write file <%s> : %v", filename, err)
	}
	defer os.Remove(filename)

	_, err = readFromFile(filename)
	if _, isRightType := err.(FileError); !isRightType {
		t.Errorf("Should have returned an error of type FileError"+
			", error was %v",
			err)
	}
	err.Error() // Call it to make gocov happy
}
<commit_msg>Add another test that fails about collisions of filenames.<commit_after>package dskvs

import (
	"bytes"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"
)

var genericPage = &page{
	isDirty:   true,
	isDeleted: false,
	basepath:  "imdb",
	coll:      "FMJ",
	key:       "Is that you, John Wayne? Is this me?",
	value: []byte(`Who said that? WHO THE FUCK said that?! Who's the slimy
commode of shit twinkle toed cocksucker just signed his own death warrant?!
Nobody huh? The fairy fucking godmother said it! Out fucking standing! I will
PT you all until you fucking DIE! I will PT you until your assholes are
sucking buttermilk!`),
}

func TestWriteAndReadDirtyPage(t *testing.T) {
	expected := genericPage

	filename := generateFilename(expected)
	os.MkdirAll(filepath.Dir(filename), DIR_PERM)
	defer os.RemoveAll(expected.basepath)

	writeToFile(expected)

	// Make sure to clean up if something goes wrong
	defer os.Remove(filename)
	if expected.isDirty {
		t.Errorf("Should have been cleaned up on write")
	}

	actual, err := readFromFile(filename)

	if err != nil {
		t.Fatalf("Failed reading file. %v", err)
	}

	if actual.key != expected.key {
		t.Errorf("Expected key <%s> but was <%s>",
			expected.key,
			actual.key)
	}

	expectedLen := len(expected.value)
	actualLen := len(actual.value)

	if actualLen != expectedLen {
		t.Errorf("Expected len(value) %d but was %d",
			expectedLen,
			actualLen)
	}

	var minLen int
	if expectedLen < actualLen {
		if expectedLen > 20 {
			minLen = 20
		} else {
			minLen = expectedLen
		}
	} else {
		if actualLen > 20 {
			minLen = 20
		} else {
			minLen = actualLen
		}
	}

	if !bytes.Equal(actual.value, expected.value) {
		t.Errorf("Expected value (truncated) <%v> but was <%v>",
			expected.value[:minLen],
			actual.value[:minLen])
	}
}

func TestPageWithHugeKeys(t *testing.T) {

	expected := genericPage
	expected.key = `artist/this is a very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very long key name`

	filename := generateFilename(expected)
	os.MkdirAll(filepath.Dir(filename), DIR_PERM)
	defer os.RemoveAll(expected.basepath)

	if err := writeToFile(expected); err != nil {
		t.Errorf("Couldn't write page with long key, %v", err)
	}

	actual, err := readFromFile(filename)
	if err != nil {
		t.Errorf("Couldn't read page <%s> back : %v", filename, err)
	}

	if err := deleteFile(filename); err != nil {
		t.Fatalf("Couldn't delete page with long key, %v", err)
	}

	if err = os.Remove(filename); os.IsExist(err) {
		t.Errorf("Didn't delete file <%s> : %v", filename, err)
	}

	if !bytes.Equal(actual.value, expected.value) {
		t.Errorf("Read different value than what was written")
	}

}

func TestPageWithHugeKeysDontCollide(t *testing.T) {

	expected := genericPage
	expected.key = `this is a very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very long key name`

	different := &page{
		isDirty:   true,
		isDeleted: false,
		basepath:  "imdb",
		coll:      "FMJ",
		key: `this is a very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very very very very very very
very very very very very very very very very very long key namr`,
		value: []byte(`My value is different`),
	}

	expectFilename := generateFilename(expected)
	differentFilename := generateFilename(different)
	os.MkdirAll(filepath.Dir(expectFilename), DIR_PERM)
	defer os.RemoveAll(expected.basepath)

	if err := writeToFile(expected); err != nil {
		t.Errorf("Couldn't write page with long key, %v", err)
	}

	if err := writeToFile(different); err != nil {
		t.Errorf("Couldn't write page with long key, %v", err)
	}

	expectedActual, err := readFromFile(expectFilename)
	if err != nil {
		t.Errorf("Couldn't read page <%s> back : %v", expectFilename, err)
	}

	differentActual, err := readFromFile(differentFilename)
	if err != nil {
		t.Errorf("Couldn't read page <%s> back : %v", differentFilename, err)
	}

	if err := deleteFile(expectFilename); err != nil {
		t.Fatalf("Couldn't delete page with long key, %v", err)
	}

	if err := deleteFile(differentFilename); err != nil {
		t.Fatalf("Couldn't delete page with long key, %v", err)
	}

	if err = os.Remove(expectFilename); os.IsExist(err) {
		t.Errorf("Didn't delete file <%s> : %v", expectFilename, err)
	}

	if err = os.Remove(differentFilename); os.IsExist(err) {
		t.Errorf("Didn't delete file <%s> : %v", differentFilename, err)
	}

	if !bytes.Equal(expectedActual.value, expected.value) {
		t.Errorf("Read another value than what was written for expected")
	}

	if !bytes.Equal(differentActual.value, different.value) {
		t.Errorf("Read another value than what was written for different")
	}

}

func TestDeletingPageShouldDeleteFile(t *testing.T) {

	expected := genericPage

	filename := generateFilename(expected)
	os.MkdirAll(filepath.Dir(filename), DIR_PERM)
	defer os.RemoveAll(expected.basepath)

	writeToFile(expected)

	actual, err := readFromFile(filename)
	if err != nil {
		t.Errorf("Couldn't read page <%s> back : %v", filename, err)
	}

	actual.isDirty = true
	actual.isDeleted = true

	writeToFile(actual)

	if err = os.Remove(filename); os.IsExist(err) {
		t.Errorf("Didn't delete file <%s> : %v", filename, err)
	}

}

func TestErrorWhenReadingJunkFile(t *testing.T) {
	filename := "junk_file.test"
	ioutil.WriteFile(filename, []byte{0xDE, 0xAD, 0xBE, 0xEF}, FILE_PERM)
	defer os.Remove(filename)

	_, err := readFromFile(filename)
	if _, isRightType := err.(FileError); !isRightType {
		t.Errorf("Should have returned an error of type FileError"+
			", error was %v",
			err)
	}
	err.Error() // Call it to make gocov happy
}

func TestErrorWhenOpeningDifferentMajorVersion(t *testing.T) {
	filename := "incompatible_version.test"
	aPage := genericPage

	// Get the header as it will be written to disk
	currentHeader := newFileHeader(aPage)
	// Modify it
	currentHeader.Major = MAJOR_VERSION + 1
	headerBytes, err := headerToBytes(currentHeader)
	if err != nil {
		t.Fatalf("Couldn't get fake header, %v", err)
	}

	// Get the data
	pageBytes, err := fromPageToBytes(aPage)
	// Overwrite the header
	copy(pageBytes, headerBytes)

	if err := ioutil.WriteFile(filename, pageBytes, FILE_PERM); err != nil {
		t.Fatalf("Couldn't write file <%s> : %v", filename, err)
	}
	defer os.Remove(filename)

	_, err = readFromFile(filename)
	if _, isRightType := err.(FileError); !isRightType {
		t.Errorf("Should have returned an error of type FileError"+
			", error was %v",
			err)
	}
	err.Error() // Call it to make gocov happy

}

func TestErrorWhenHeaderComesFromAnotherPageWithSimilarLength(t *testing.T) {
	filename := "impostor.test"
	aPage := genericPage

	impostor := &page{
		isDirty:   true,
		isDeleted: false,
		basepath:  "imdb",
		coll:      "FMJ",
		key:       "Is that you, John Wayne? Is this me?",
		// value has same length but different text
		value: []byte(`HAHAHA yes !!? WHO THE FUCK said that?! Who's the slimy
commode of shit twinkle toed cocksucker just signed his own death warrant?!
Nobody huh? The fairy fucking godmother said it! Out fucking standing! I will
PT you all until you fucking DIE! I will PT you until your assholes are
sucking buttermilk!`),
	}

	impostorHeader := newFileHeader(impostor)
	headerBytes, err := headerToBytes(impostorHeader)
	if err != nil {
		t.Fatalf("Couldn't get fake header, %v", err)
	}
	pageBytes, err := fromPageToBytes(aPage)
	copy(pageBytes, headerBytes)

	if err := ioutil.WriteFile(filename, pageBytes, FILE_PERM); err != nil {
		t.Fatalf("Couldn't write file <%s> : %v", filename, err)
	}
	defer os.Remove(filename)

	_, err = readFromFile(filename)
	if _, isRightType := err.(FileError); !isRightType {
		t.Errorf("Should have returned an error of type FileError"+
			", error was %v",
			err)
	}
	err.Error() // Call it to make gocov happy
}

func TestErrorWhenHeaderComesFromAnotherPageWithDifferentLength(t *testing.T) {
	filename := "impostor.test"
	aPage := genericPage

	pageBytes, err := fromPageToBytes(aPage)

	impostor := genericPage
	impostor.value = []byte("hahahaha yes it's me")

	impostorHeader := newFileHeader(impostor)
	headerBytes, err := headerToBytes(impostorHeader)
	if err != nil {
		t.Fatalf("Couldn't get fake header, %v", err)
	}

	copy(pageBytes, headerBytes)

	if err := ioutil.WriteFile(filename, pageBytes, FILE_PERM); err != nil {
		t.Fatalf("Couldn't write file <%s> : %v", filename, err)
	}
	defer os.Remove(filename)

	result, err := readFromFile(filename)
	if _, isRightType := err.(FileError); !isRightType {
		t.Errorf("Should have returned an error of type FileError"+
			", error was %v, page was : %v",
			err,
			result)
	}
	err.Error() // Call it to make gocov happy
}

func TestErrorWhenChecksumIsWrong(t *testing.T) {
	filename := "wrong_checksum.test"
	aPage := &page{
		isDirty:   true,
		isDeleted: false,
		basepath:  "imdb",
		coll:      "FMJ",
		key:       "Is that you, John Wayne? Is this me?",
		value: []byte(`Who said that? WHO THE FUCK said that?! Who's the slimy
commode of shit twinkle toed cocksucker just signed his own death warrant?!
Nobody huh? The fairy fucking godmother said it! Out fucking standing! I will
PT you all until you fucking DIE! I will PT you until your assholes are
sucking buttermilk!`),
	}

	currentHeader := newFileHeader(aPage)
	currentHeader.Checksum = currentHeader.Checksum + 1
	headerBytes, err := headerToBytes(currentHeader)
	if err != nil {
		t.Fatalf("Couldn't get fake header, %v", err)
	}
	pageBytes, err := fromPageToBytes(aPage)
	copy(pageBytes, headerBytes)

	if err := ioutil.WriteFile(filename, pageBytes, FILE_PERM); err != nil {
		t.Fatalf("Couldn't write file <%s> : %v", filename, err)
	}
	defer os.Remove(filename)

	_, err = readFromFile(filename)
	if _, isRightType := err.(FileError); !isRightType {
		t.Errorf("Should have returned an error of type FileError"+
			", error was %v",
			err)
	}
	err.Error() // Call it to make gocov happy
}
<|endoftext|>


<commit_before>package services

import (
	"time"
)

var mtx sync.Mutex
var userMarshaler UserMarshaler = &TeleportUserMarshaler{}

// SetUserMarshaler sets global user marshaler
func SetUserMarshaler(u UserMarshaler) {
	mtx.Lock()
	defer mtx.Unlock()
	userMarshaler = u
}

// GetUserMarshaler returns currently set user marshaler
func GetUserMarshaler() UserMarshaler {
	mtx.Lock()
	defer mtx.Unlock()
	return userMarshaler
}

// UserMarshaler implements marshal/unmarshal of User implementations
// mostly adds support for extended versions
type UserMarshaler interface {
	// UnmarshalUser from binary representation
	UnmarshalUser(bytes []byte) (User, error)
	// MarshalUser to binary representation
	MarshalUser(u User) ([]byte, error)
	// GenerateUser generates new user based on standard teleport user
	// it gives external implementations to add more app-specific
	// data to the user
	GenerateUser(User) (User, error)
}

type TeleportUserMarshaler struct{}

// UnmarshalUser unmarshals user from JSON
func (*TeleportUserMarshaler) UnmarshalUser(bytes []byte) (User, error) {
	var h ResourceHeader
	err := json.Unmarshal(bytes, &h)
	if err != nil {
		return nil, trace.Wrap(err)
	}
	switch h.Version {
	case "":
		var u UserV0
		err := json.Unmarshal(bytes, &u)
		if err != nil {
			return nil, trace.Wrap(err)
		}
		return u.V1(), nil
	}

	return u, nil
}

// GenerateUser generates new user
func (*TeleportUserMarshaler) GenerateUser(in TeleportUser) (User, error) {
	return &in, nil
}

// MarshalUser marshalls user into JSON
func (*TeleportUserMarshaler) MarshalUser(u User) ([]byte, error) {
	return json.Marshal(u)
}

// UserV1 is version1 resource spec of the user
type UserV1 struct {
	// Kind is a resource kind
	Kind string `json:"kind"`
	// Version is version
	Version string `json:"version"`
	// Metadata is User metadata
	Metadata Metadata `json:"metadata"`
	// Spec contains user specification
	Spec UserV1Spec `json:"spec"`
}

// UserV1SchemaTemplate is a template JSON Schema for user
const UserV1SchemaTemplate = `{
  "type": "object",
  "additionalProperties": false,
  "default": {},
  "required": ["kind", "spec", "metadata"],
  "properties": {
    "kind": {"type": "string"},
    "version": {"type": "string", "default": "v1"},
    "metadata": %v,
    "spec": %v
  }
}`

// UserSpecV1 is a specification for V1 user
type UserSpecV1 struct {
	// AllowedLogins represents a list of OS users this teleport
	// user is allowed to login as
	AllowedLogins []string `json:"allowed_logins"`

	// OIDCIdentities lists associated OpenID Connect identities
	// that let user log in using externally verified identity
	OIDCIdentities []OIDCIdentity `json:"oidc_identities"`

	// Roles is a list of roles assigned to user
	Roles []string `json:"roles"`

	// Status is a login status of the user
	Status LoginStatus `json:"status"`

	// Expires if set sets TTL on the user
	Expires time.Time `json:"expires"`

	// CreatedBy holds information about agent or person created this usre
	CreatedBy CreatedBy `json:"created_by"`
}

// UserSpecV1SchemaTemplate is JSON schema for
var UserSpecV1Schema = fmt.Sprintf(`{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "allowed_logins": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "oidc_identities": {
      "type": "array",
      "items": %v
    },
    "roles": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "status": {
      "type": "object",
      "additionalProperties": false,
      "properties": {

      }
    }
  }
}`, OIDCIdentitySchema, LoginStatusSchema, CreatedBySchema)

// SetCreatedBy sets created by information
func (u *UserV1) SetCreatedBy(b CreatedBy) {
	u.CreatedBy = b
}

// GetCreatedBy returns information about who created user
func (u *UserV1) GetCreatedBy() CreatedBy {
	return u.CreatedBy
}

// Equals checks if user equals to another
func (u *UserV1) Equals(other User) bool {
	if u.Name != other.GetName() {
		return false
	}
	otherLogins := other.GetAllowedLogins()
	if len(u.AllowedLogins) != len(otherLogins) {
		return false
	}
	for i := range u.AllowedLogins {
		if u.AllowedLogins[i] != otherLogins[i] {
			return false
		}
	}
	otherIdentities := other.GetIdentities()
	if len(u.OIDCIdentities) != len(otherIdentities) {
		return false
	}
	for i := range u.OIDCIdentities {
		if !u.OIDCIdentities[i].Equals(&otherIdentities[i]) {
			return false
		}
	}
	return true
}

// GetExpiry returns expiry time for temporary users
func (u *UserV1) GetExpiry() time.Time {
	return u.Expires
}

// SetAllowedLogins sets allowed logins for user
func (u *UserV1) SetAllowedLogins(logins []string) {
	u.AllowedLogins = logins
}

// SetRoles sets a list of roles for user
func (u *UserV1) SetRoles(roles []string) {
	u.Roles = utils.Deduplicate(roles)
}

// GetStatus returns login status of the user
func (u *UserV1) GetStatus() LoginStatus {
	return u.Status
}

// WebSessionInfo returns web session information
func (u *UserV1) WebSessionInfo(logins []string) User {
	c := *u
	c.AllowedLogins = logins
	return &c
}

// GetAllowedLogins returns user's allowed linux logins
func (u *UserV1) GetAllowedLogins() []string {
	return u.AllowedLogins
}

// GetIdentities returns a list of connected OIDCIdentities
func (u *UserV1) GetIdentities() []OIDCIdentity {
	return u.OIDCIdentities
}

// GetRoles returns a list of roles assigned to user
func (u *UserV1) GetRoles() []string {
	return u.Roles
}

// AddRole adds a role to user's role list
func (u *UserV1) AddRole(name string) {
	for _, r := range u.Roles {
		if r == name {
			return
		}
	}
	u.Roles = append(u.Roles, name)
}

// GetName returns user name
func (u *UserV1) GetName() string {
	return u.Name
}

func (u *UserV1) String() string {
	return fmt.Sprintf("User(name=%v, allowed_logins=%v, identities=%v)", u.Name, u.AllowedLogins, u.OIDCIdentities)
}

func (u *UserV1) SetLocked(until time.Time, reason string) {
	u.Status.IsLocked = true
	u.Status.LockExpires = until
	u.Status.LockedMessage = reason
}

// Check checks validity of all parameters
func (u *UserV1) Check() error {
	if !cstrings.IsValidUnixUser(u.Name) {
		return trace.BadParameter("'%v' is not a valid user name", u.Name)
	}
	for _, l := range u.AllowedLogins {
		if !cstrings.IsValidUnixUser(l) {
			return trace.BadParameter("'%v is not a valid unix username'", l)
		}
	}
	for _, login := range u.AllowedLogins {
		if !cstrings.IsValidUnixUser(login) {
			return trace.BadParameter("'%v' is not a valid user name", login)
		}
	}
	for _, id := range u.OIDCIdentities {
		if err := id.Check(); err != nil {
			return trace.Wrap(err)
		}
	}
	return nil
}

// UserV0 is V0 version of the user
type UserV0 struct {
	// Name is a user name
	Name string `json:"name"`

	// AllowedLogins represents a list of OS users this teleport
	// user is allowed to login as
	AllowedLogins []string `json:"allowed_logins"`

	// OIDCIdentities lists associated OpenID Connect identities
	// that let user log in using externally verified identity
	OIDCIdentities []OIDCIdentity `json:"oidc_identities"`

	// Roles is a list of roles assigned to user
	Roles []string `json:"roles"`

	// Status is a login status of the user
	Status LoginStatus `json:"status"`

	// Expires if set sets TTL on the user
	Expires time.Time `json:"expires"`

	// CreatedBy holds information about agent or person created this usre
	CreatedBy CreatedBy `json:"created_by"`
}

// UserV1 converts UserV0 to UserV1 format
func (u *UserV0) UserV1() *UserV1 {
	return &UserV1{
		Kind:    KindUser,
		Version: V1,
		Metadata: Metadata{
			Name: u.Name,
		},
		Spec: UserSpecV1{
			AllowedLogins:  u.AllowedLogins,
			OIDCIDentities: u.OIDCIdentitties,
			Roles:          u.Roles,
			Status:         u.Status,
			Expires:        u.Expires,
			CreatedBy:      u.CreatedBy,
		},
	}
}
<commit_msg>more work<commit_after>package services

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

	"github.com/gravitational/teleport/lib/utils"

	"github.com/gravitational/trace"
)

var mtx sync.Mutex
var userMarshaler UserMarshaler = &TeleportUserMarshaler{}

// SetUserMarshaler sets global user marshaler
func SetUserMarshaler(u UserMarshaler) {
	mtx.Lock()
	defer mtx.Unlock()
	userMarshaler = u
}

// GetUserMarshaler returns currently set user marshaler
func GetUserMarshaler() UserMarshaler {
	mtx.Lock()
	defer mtx.Unlock()
	return userMarshaler
}

// UserMarshaler implements marshal/unmarshal of User implementations
// mostly adds support for extended versions
type UserMarshaler interface {
	// UnmarshalUser from binary representation
	UnmarshalUser(bytes []byte) (User, error)
	// MarshalUser to binary representation
	MarshalUser(u User) ([]byte, error)
	// GenerateUser generates new user based on standard teleport user
	// it gives external implementations to add more app-specific
	// data to the user
	GenerateUser(User) (User, error)
}

// GetRoleSchema returns role schema with optionally injected
// schema for extensions
func GetUserSchema(extensionSchema string) string {
	var userSchema string
	if extensionSchema == "" {
		userSchema = fmt.Sprintf(UserSpecV1SchemaTemplate, OIDCIDentitySchema, LoginStatusSchema, CreatedBySchema, `, {"type": "object"}`)
	} else {
		userSchema = fmt.Sprintf(UserSpecV1SchemaTemplate, OIDCIDentitySchema, LoginStatusSchema, CreatedBySchema, ", "+extensionSchema)
	}
	return fmt.Sprintf(UserV1SchemaTemplate, MetadataSchema, userSchema)
}

type TeleportUserMarshaler struct{}

// UnmarshalUser unmarshals user from JSON
func (*TeleportUserMarshaler) UnmarshalUser(bytes []byte) (User, error) {
	var h ResourceHeader
	err := json.Unmarshal(bytes, &h)
	if err != nil {
		return nil, trace.Wrap(err)
	}
	switch h.Version {
	case "":
		var u UserV0
		err := json.Unmarshal(bytes, &u)
		if err != nil {
			return nil, trace.Wrap(err)
		}
		return u.UserV1(), nil
	case V1:
		var u UserV1
		if err := utils.UnmarshalWithSchema(GetRoleSchema(""), &role, data); err != nil {
			return nil, trace.BadParameter(err.Error())
		}
	}

	return u, nil
}

// GenerateUser generates new user
func (*TeleportUserMarshaler) GenerateUser(in User) (User, error) {
	return in, nil
}

// MarshalUser marshalls user into JSON
func (*TeleportUserMarshaler) MarshalUser(u User) ([]byte, error) {
	return json.Marshal(u)
}

// UserV1 is version1 resource spec of the user
type UserV1 struct {
	// Kind is a resource kind
	Kind string `json:"kind"`
	// Version is version
	Version string `json:"version"`
	// Metadata is User metadata
	Metadata Metadata `json:"metadata"`
	// Spec contains user specification
	Spec UserSpecV1 `json:"spec"`
}

// UserV1SchemaTemplate is a template JSON Schema for user
const UserV1SchemaTemplate = `{
  "type": "object",
  "additionalProperties": false,
  "required": ["kind", "spec", "metadata", "version"],
  "properties": {
    "kind": {"type": "string"},
    "version": {"type": "string", "default": "v1"},
    "metadata": %v,
    "spec": %v
  }
}`

// UserSpecV1 is a specification for V1 user
type UserSpecV1 struct {
	// AllowedLogins represents a list of OS users this teleport
	// user is allowed to login as
	AllowedLogins []string `json:"allowed_logins"`

	// OIDCIdentities lists associated OpenID Connect identities
	// that let user log in using externally verified identity
	OIDCIdentities []OIDCIdentity `json:"oidc_identities"`

	// Roles is a list of roles assigned to user
	Roles []string `json:"roles"`

	// Status is a login status of the user
	Status LoginStatus `json:"status"`

	// Expires if set sets TTL on the user
	Expires time.Time `json:"expires"`

	// CreatedBy holds information about agent or person created this usre
	CreatedBy CreatedBy `json:"created_by"`
}

// UserSpecV1SchemaTemplate is JSON schema for
const UserSpecV1SchemaTemplate = `{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "allowed_logins": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "expires": {"type": "string"},
    "roles": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "oidc_identities": {
      "type": "array",
      "items": %v
    },
    "status": %v,
    "created_by": %v%v
  }
}`

// SetCreatedBy sets created by information
func (u *UserV1) SetCreatedBy(b CreatedBy) {
	u.CreatedBy = b
}

// GetCreatedBy returns information about who created user
func (u *UserV1) GetCreatedBy() CreatedBy {
	return u.CreatedBy
}

// Equals checks if user equals to another
func (u *UserV1) Equals(other User) bool {
	if u.Name != other.GetName() {
		return false
	}
	otherLogins := other.GetAllowedLogins()
	if len(u.AllowedLogins) != len(otherLogins) {
		return false
	}
	for i := range u.AllowedLogins {
		if u.AllowedLogins[i] != otherLogins[i] {
			return false
		}
	}
	otherIdentities := other.GetIdentities()
	if len(u.OIDCIdentities) != len(otherIdentities) {
		return false
	}
	for i := range u.OIDCIdentities {
		if !u.OIDCIdentities[i].Equals(&otherIdentities[i]) {
			return false
		}
	}
	return true
}

// GetExpiry returns expiry time for temporary users
func (u *UserV1) GetExpiry() time.Time {
	return u.Expires
}

// SetAllowedLogins sets allowed logins for user
func (u *UserV1) SetAllowedLogins(logins []string) {
	u.AllowedLogins = logins
}

// SetRoles sets a list of roles for user
func (u *UserV1) SetRoles(roles []string) {
	u.Roles = utils.Deduplicate(roles)
}

// GetStatus returns login status of the user
func (u *UserV1) GetStatus() LoginStatus {
	return u.Status
}

// WebSessionInfo returns web session information
func (u *UserV1) WebSessionInfo(logins []string) User {
	c := *u
	c.AllowedLogins = logins
	return &c
}

// GetAllowedLogins returns user's allowed linux logins
func (u *UserV1) GetAllowedLogins() []string {
	return u.AllowedLogins
}

// GetIdentities returns a list of connected OIDCIdentities
func (u *UserV1) GetIdentities() []OIDCIdentity {
	return u.OIDCIdentities
}

// GetRoles returns a list of roles assigned to user
func (u *UserV1) GetRoles() []string {
	return u.Roles
}

// AddRole adds a role to user's role list
func (u *UserV1) AddRole(name string) {
	for _, r := range u.Roles {
		if r == name {
			return
		}
	}
	u.Roles = append(u.Roles, name)
}

// GetName returns user name
func (u *UserV1) GetName() string {
	return u.Name
}

func (u *UserV1) String() string {
	return fmt.Sprintf("User(name=%v, allowed_logins=%v, identities=%v)", u.Name, u.AllowedLogins, u.OIDCIdentities)
}

func (u *UserV1) SetLocked(until time.Time, reason string) {
	u.Status.IsLocked = true
	u.Status.LockExpires = until
	u.Status.LockedMessage = reason
}

// Check checks validity of all parameters
func (u *UserV1) Check() error {
	if !cstrings.IsValidUnixUser(u.Name) {
		return trace.BadParameter("'%v' is not a valid user name", u.Name)
	}
	for _, l := range u.AllowedLogins {
		if !cstrings.IsValidUnixUser(l) {
			return trace.BadParameter("'%v is not a valid unix username'", l)
		}
	}
	for _, login := range u.AllowedLogins {
		if !cstrings.IsValidUnixUser(login) {
			return trace.BadParameter("'%v' is not a valid user name", login)
		}
	}
	for _, id := range u.OIDCIdentities {
		if err := id.Check(); err != nil {
			return trace.Wrap(err)
		}
	}
	return nil
}

// UserV0 is V0 version of the user
type UserV0 struct {
	// Name is a user name
	Name string `json:"name"`

	// AllowedLogins represents a list of OS users this teleport
	// user is allowed to login as
	AllowedLogins []string `json:"allowed_logins"`

	// OIDCIdentities lists associated OpenID Connect identities
	// that let user log in using externally verified identity
	OIDCIdentities []OIDCIdentity `json:"oidc_identities"`

	// Roles is a list of roles assigned to user
	Roles []string `json:"roles"`

	// Status is a login status of the user
	Status LoginStatus `json:"status"`

	// Expires if set sets TTL on the user
	Expires time.Time `json:"expires"`

	// CreatedBy holds information about agent or person created this usre
	CreatedBy CreatedBy `json:"created_by"`
}

// UserV1 converts UserV0 to UserV1 format
func (u *UserV0) UserV1() *UserV1 {
	return &UserV1{
		Kind:    KindUser,
		Version: V1,
		Metadata: Metadata{
			Name: u.Name,
		},
		Spec: UserSpecV1{
			AllowedLogins:  u.AllowedLogins,
			OIDCIDentities: u.OIDCIdentitties,
			Roles:          u.Roles,
			Status:         u.Status,
			Expires:        u.Expires,
			CreatedBy:      u.CreatedBy,
		},
	}
}
<|endoftext|>