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

package core

import (
	"context"

	"github.com/pingcap/errors"
	"github.com/pingcap/failpoint"
	"github.com/pingcap/parser/ast"
	"github.com/pingcap/parser/model"
	"github.com/pingcap/parser/mysql"
	"github.com/pingcap/tidb/expression"
	"github.com/pingcap/tidb/expression/aggregation"
	"github.com/pingcap/tidb/infoschema"
	"github.com/pingcap/tidb/types"
)

type columnPruner struct {
}

func (s *columnPruner) optimize(ctx context.Context, lp LogicalPlan) (LogicalPlan, error) {
	err := lp.PruneColumns(lp.Schema().Columns)
	return lp, err
}

func getUsedList(usedCols []*expression.Column, schema *expression.Schema) ([]bool, error) {
	failpoint.Inject("enableGetUsedListErr", func(val failpoint.Value) {
		if val.(bool) {
			failpoint.Return(nil, errors.New("getUsedList failed, triggered by gofail enableGetUsedListErr"))
		}
	})

	used := make([]bool, schema.Len())
	for _, col := range usedCols {
		idx := schema.ColumnIndex(col)
		if idx == -1 {
			return nil, errors.Errorf("Can't find column %s from schema %s.", col, schema)
		}
		used[idx] = true
	}
	return used, nil
}

// exprHasSetVar checks if the expression has SetVar function.
func exprHasSetVar(expr expression.Expression) bool {
	scalaFunc, isScalaFunc := expr.(*expression.ScalarFunction)
	if !isScalaFunc {
		return false
	}
	if scalaFunc.FuncName.L == ast.SetVar {
		return true
	}
	for _, arg := range scalaFunc.GetArgs() {
		if exprHasSetVar(arg) {
			return true
		}
	}
	return false
}

// PruneColumns implements LogicalPlan interface.
// If any expression has SetVar functions, we do not prune it.
func (p *LogicalProjection) PruneColumns(parentUsedCols []*expression.Column) error {
	child := p.children[0]
	used, err := getUsedList(parentUsedCols, p.schema)
	if err != nil {
		return err
	}

	for i := len(used) - 1; i >= 0; i-- {
		if !used[i] && !exprHasSetVar(p.Exprs[i]) {
			p.schema.Columns = append(p.schema.Columns[:i], p.schema.Columns[i+1:]...)
			p.Exprs = append(p.Exprs[:i], p.Exprs[i+1:]...)
		}
	}
	selfUsedCols := make([]*expression.Column, 0, len(p.Exprs))
	selfUsedCols = expression.ExtractColumnsFromExpressions(selfUsedCols, p.Exprs, nil)
	return child.PruneColumns(selfUsedCols)
}

// PruneColumns implements LogicalPlan interface.
func (p *LogicalSelection) PruneColumns(parentUsedCols []*expression.Column) error {
	child := p.children[0]
	parentUsedCols = expression.ExtractColumnsFromExpressions(parentUsedCols, p.Conditions, nil)
	return child.PruneColumns(parentUsedCols)
}

// PruneColumns implements LogicalPlan interface.
func (la *LogicalAggregation) PruneColumns(parentUsedCols []*expression.Column) error {
	child := la.children[0]
	used, err := getUsedList(parentUsedCols, la.Schema())
	if err != nil {
		return err
	}

	for i := len(used) - 1; i >= 0; i-- {
		if !used[i] {
			la.schema.Columns = append(la.schema.Columns[:i], la.schema.Columns[i+1:]...)
			la.AggFuncs = append(la.AggFuncs[:i], la.AggFuncs[i+1:]...)
		}
	}
	var selfUsedCols []*expression.Column
	for _, aggrFunc := range la.AggFuncs {
		selfUsedCols = expression.ExtractColumnsFromExpressions(selfUsedCols, aggrFunc.Args, nil)
	}
	if len(la.AggFuncs) == 0 {
		// If all the aggregate functions are pruned, we should add an aggregate function to keep the correctness.
		one, err := aggregation.NewAggFuncDesc(la.ctx, ast.AggFuncFirstRow, []expression.Expression{expression.One}, false)
		if err != nil {
			return err
		}
		la.AggFuncs = []*aggregation.AggFuncDesc{one}
		col := &expression.Column{
			ColName:  model.NewCIStr("dummy_agg"),
			UniqueID: la.ctx.GetSessionVars().AllocPlanColumnID(),
			RetType:  types.NewFieldType(mysql.TypeLonglong),
		}
		la.schema.Columns = []*expression.Column{col}
	}

	if len(la.GroupByItems) > 0 {
		for i := len(la.GroupByItems) - 1; i >= 0; i-- {
			cols := expression.ExtractColumns(la.GroupByItems[i])
			if len(cols) == 0 {
				la.GroupByItems = append(la.GroupByItems[:i], la.GroupByItems[i+1:]...)
			} else {
				selfUsedCols = append(selfUsedCols, cols...)
			}
		}
		// If all the group by items are pruned, we should add a constant 1 to keep the correctness.
		// Because `select count(*) from t` is different from `select count(*) from t group by 1`.
		if len(la.GroupByItems) == 0 {
			la.GroupByItems = []expression.Expression{expression.One}
		}
	}
	return child.PruneColumns(selfUsedCols)
}

// PruneColumns implements LogicalPlan interface.
func (ls *LogicalSort) PruneColumns(parentUsedCols []*expression.Column) error {
	child := ls.children[0]
	for i := len(ls.ByItems) - 1; i >= 0; i-- {
		cols := expression.ExtractColumns(ls.ByItems[i].Expr)
		if len(cols) == 0 {
			if !ls.ByItems[i].Expr.ConstItem() {
				continue
			}
			ls.ByItems = append(ls.ByItems[:i], ls.ByItems[i+1:]...)
		} else if ls.ByItems[i].Expr.GetType().Tp == mysql.TypeNull {
			ls.ByItems = append(ls.ByItems[:i], ls.ByItems[i+1:]...)
		} else {
			parentUsedCols = append(parentUsedCols, cols...)
		}
	}
	return child.PruneColumns(parentUsedCols)
}

// PruneColumns implements LogicalPlan interface.
func (p *LogicalUnionAll) PruneColumns(parentUsedCols []*expression.Column) error {
	used, err := getUsedList(parentUsedCols, p.schema)
	if err != nil {
		return err
	}

	hasBeenUsed := false
	for i := range used {
		hasBeenUsed = hasBeenUsed || used[i]
	}
	if !hasBeenUsed {
		parentUsedCols = make([]*expression.Column, len(p.schema.Columns))
		copy(parentUsedCols, p.schema.Columns)
	} else {
		// Issue 10341: p.schema.Columns might contain table name (AsName), but p.Children()0].Schema().Columns does not.
		for i := len(used) - 1; i >= 0; i-- {
			if !used[i] {
				p.schema.Columns = append(p.schema.Columns[:i], p.schema.Columns[i+1:]...)
			}
		}
	}
	for _, child := range p.Children() {
		err := child.PruneColumns(parentUsedCols)
		if err != nil {
			return err
		}
	}
	return nil
}

// PruneColumns implements LogicalPlan interface.
func (p *LogicalUnionScan) PruneColumns(parentUsedCols []*expression.Column) error {
	parentUsedCols = append(parentUsedCols, p.handleCol)
	return p.children[0].PruneColumns(parentUsedCols)
}

// PruneColumns implements LogicalPlan interface.
func (ds *DataSource) PruneColumns(parentUsedCols []*expression.Column) error {
	used, err := getUsedList(parentUsedCols, ds.schema)
	if err != nil {
		return err
	}

	var (
		handleCol     *expression.Column
		handleColInfo *model.ColumnInfo
	)
	if ds.handleCol != nil {
		handleCol = ds.handleCol
		handleColInfo = ds.Columns[ds.schema.ColumnIndex(handleCol)]
	}
	for i := len(used) - 1; i >= 0; i-- {
		if !used[i] {
			ds.schema.Columns = append(ds.schema.Columns[:i], ds.schema.Columns[i+1:]...)
			ds.Columns = append(ds.Columns[:i], ds.Columns[i+1:]...)
		}
	}
	// For SQL like `select 1 from t`, tikv's response will be empty if no column is in schema.
	// So we'll force to push one if schema doesn't have any column.
	if ds.schema.Len() == 0 && !infoschema.IsMemoryDB(ds.DBName.L) {
		if handleCol == nil {
			handleCol = ds.newExtraHandleSchemaCol()
			handleColInfo = model.NewExtraHandleColInfo()
		}
		ds.Columns = append(ds.Columns, handleColInfo)
		ds.schema.Append(handleCol)
	}
	if ds.handleCol != nil && ds.schema.ColumnIndex(ds.handleCol) == -1 {
		ds.handleCol = nil
	}
	return nil
}

// PruneColumns implements LogicalPlan interface.
func (p *LogicalTableDual) PruneColumns(parentUsedCols []*expression.Column) error {
	used, err := getUsedList(parentUsedCols, p.Schema())
	if err != nil {
		return err
	}

	for i := len(used) - 1; i >= 0; i-- {
		if !used[i] {
			p.schema.Columns = append(p.schema.Columns[:i], p.schema.Columns[i+1:]...)
		}
	}
	return nil
}

func (p *LogicalJoin) extractUsedCols(parentUsedCols []*expression.Column) (leftCols []*expression.Column, rightCols []*expression.Column) {
	for _, eqCond := range p.EqualConditions {
		parentUsedCols = append(parentUsedCols, expression.ExtractColumns(eqCond)...)
	}
	for _, leftCond := range p.LeftConditions {
		parentUsedCols = append(parentUsedCols, expression.ExtractColumns(leftCond)...)
	}
	for _, rightCond := range p.RightConditions {
		parentUsedCols = append(parentUsedCols, expression.ExtractColumns(rightCond)...)
	}
	for _, otherCond := range p.OtherConditions {
		parentUsedCols = append(parentUsedCols, expression.ExtractColumns(otherCond)...)
	}
	lChild := p.children[0]
	rChild := p.children[1]
	for _, col := range parentUsedCols {
		if lChild.Schema().Contains(col) {
			leftCols = append(leftCols, col)
		} else if rChild.Schema().Contains(col) {
			rightCols = append(rightCols, col)
		}
	}
	return leftCols, rightCols
}

func (p *LogicalJoin) mergeSchema() {
	lChild := p.children[0]
	rChild := p.children[1]
	composedSchema := expression.MergeSchema(lChild.Schema(), rChild.Schema())
	if p.JoinType == SemiJoin || p.JoinType == AntiSemiJoin {
		p.schema = lChild.Schema().Clone()
	} else if p.JoinType == LeftOuterSemiJoin || p.JoinType == AntiLeftOuterSemiJoin {
		joinCol := p.schema.Columns[len(p.schema.Columns)-1]
		p.schema = lChild.Schema().Clone()
		p.schema.Append(joinCol)
	} else {
		p.schema = composedSchema
	}
}

// PruneColumns implements LogicalPlan interface.
func (p *LogicalJoin) PruneColumns(parentUsedCols []*expression.Column) error {
	leftCols, rightCols := p.extractUsedCols(parentUsedCols)

	err := p.children[0].PruneColumns(leftCols)
	if err != nil {
		return err
	}

	err = p.children[1].PruneColumns(rightCols)
	if err != nil {
		return err
	}

	p.mergeSchema()
	return nil
}

// PruneColumns implements LogicalPlan interface.
func (la *LogicalApply) PruneColumns(parentUsedCols []*expression.Column) error {
	leftCols, rightCols := la.extractUsedCols(parentUsedCols)

	err := la.children[1].PruneColumns(rightCols)
	if err != nil {
		return err
	}

	la.corCols = extractCorColumnsBySchema(la.children[1], la.children[0].Schema())
	for _, col := range la.corCols {
		leftCols = append(leftCols, &col.Column)
	}

	err = la.children[0].PruneColumns(leftCols)
	if err != nil {
		return err
	}

	la.mergeSchema()
	return nil
}

// PruneColumns implements LogicalPlan interface.
func (p *LogicalLock) PruneColumns(parentUsedCols []*expression.Column) error {
	if p.Lock != ast.SelectLockForUpdate {
		return p.baseLogicalPlan.PruneColumns(parentUsedCols)
	}

	for _, cols := range p.tblID2Handle {
		parentUsedCols = append(parentUsedCols, cols...)
	}
	return p.children[0].PruneColumns(parentUsedCols)
}

// PruneColumns implements LogicalPlan interface.
func (p *LogicalWindow) PruneColumns(parentUsedCols []*expression.Column) error {
	windowColumns := p.GetWindowResultColumns()
	len := 0
	for _, col := range parentUsedCols {
		used := false
		for _, windowColumn := range windowColumns {
			if windowColumn.Equal(nil, col) {
				used = true
				break
			}
		}
		if !used {
			parentUsedCols[len] = col
			len++
		}
	}
	parentUsedCols = parentUsedCols[:len]
	parentUsedCols = p.extractUsedCols(parentUsedCols)
	err := p.children[0].PruneColumns(parentUsedCols)
	if err != nil {
		return err
	}

	p.SetSchema(p.children[0].Schema().Clone())
	p.Schema().Append(windowColumns...)
	return nil
}

func (p *LogicalWindow) extractUsedCols(parentUsedCols []*expression.Column) []*expression.Column {
	for _, desc := range p.WindowFuncDescs {
		for _, arg := range desc.Args {
			parentUsedCols = append(parentUsedCols, expression.ExtractColumns(arg)...)
		}
	}
	for _, by := range p.PartitionBy {
		parentUsedCols = append(parentUsedCols, by.Col)
	}
	for _, by := range p.OrderBy {
		parentUsedCols = append(parentUsedCols, by.Col)
	}
	return parentUsedCols
}

func (*columnPruner) name() string {
	return "column_prune"
}
<commit_msg>planner: early break loop if the condition is satisfied (#11766)<commit_after>// Copyright 2016 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package core

import (
	"context"

	"github.com/pingcap/errors"
	"github.com/pingcap/failpoint"
	"github.com/pingcap/parser/ast"
	"github.com/pingcap/parser/model"
	"github.com/pingcap/parser/mysql"
	"github.com/pingcap/tidb/expression"
	"github.com/pingcap/tidb/expression/aggregation"
	"github.com/pingcap/tidb/infoschema"
	"github.com/pingcap/tidb/types"
)

type columnPruner struct {
}

func (s *columnPruner) optimize(ctx context.Context, lp LogicalPlan) (LogicalPlan, error) {
	err := lp.PruneColumns(lp.Schema().Columns)
	return lp, err
}

func getUsedList(usedCols []*expression.Column, schema *expression.Schema) ([]bool, error) {
	failpoint.Inject("enableGetUsedListErr", func(val failpoint.Value) {
		if val.(bool) {
			failpoint.Return(nil, errors.New("getUsedList failed, triggered by gofail enableGetUsedListErr"))
		}
	})

	used := make([]bool, schema.Len())
	for _, col := range usedCols {
		idx := schema.ColumnIndex(col)
		if idx == -1 {
			return nil, errors.Errorf("Can't find column %s from schema %s.", col, schema)
		}
		used[idx] = true
	}
	return used, nil
}

// exprHasSetVar checks if the expression has SetVar function.
func exprHasSetVar(expr expression.Expression) bool {
	scalaFunc, isScalaFunc := expr.(*expression.ScalarFunction)
	if !isScalaFunc {
		return false
	}
	if scalaFunc.FuncName.L == ast.SetVar {
		return true
	}
	for _, arg := range scalaFunc.GetArgs() {
		if exprHasSetVar(arg) {
			return true
		}
	}
	return false
}

// PruneColumns implements LogicalPlan interface.
// If any expression has SetVar functions, we do not prune it.
func (p *LogicalProjection) PruneColumns(parentUsedCols []*expression.Column) error {
	child := p.children[0]
	used, err := getUsedList(parentUsedCols, p.schema)
	if err != nil {
		return err
	}

	for i := len(used) - 1; i >= 0; i-- {
		if !used[i] && !exprHasSetVar(p.Exprs[i]) {
			p.schema.Columns = append(p.schema.Columns[:i], p.schema.Columns[i+1:]...)
			p.Exprs = append(p.Exprs[:i], p.Exprs[i+1:]...)
		}
	}
	selfUsedCols := make([]*expression.Column, 0, len(p.Exprs))
	selfUsedCols = expression.ExtractColumnsFromExpressions(selfUsedCols, p.Exprs, nil)
	return child.PruneColumns(selfUsedCols)
}

// PruneColumns implements LogicalPlan interface.
func (p *LogicalSelection) PruneColumns(parentUsedCols []*expression.Column) error {
	child := p.children[0]
	parentUsedCols = expression.ExtractColumnsFromExpressions(parentUsedCols, p.Conditions, nil)
	return child.PruneColumns(parentUsedCols)
}

// PruneColumns implements LogicalPlan interface.
func (la *LogicalAggregation) PruneColumns(parentUsedCols []*expression.Column) error {
	child := la.children[0]
	used, err := getUsedList(parentUsedCols, la.Schema())
	if err != nil {
		return err
	}

	for i := len(used) - 1; i >= 0; i-- {
		if !used[i] {
			la.schema.Columns = append(la.schema.Columns[:i], la.schema.Columns[i+1:]...)
			la.AggFuncs = append(la.AggFuncs[:i], la.AggFuncs[i+1:]...)
		}
	}
	var selfUsedCols []*expression.Column
	for _, aggrFunc := range la.AggFuncs {
		selfUsedCols = expression.ExtractColumnsFromExpressions(selfUsedCols, aggrFunc.Args, nil)
	}
	if len(la.AggFuncs) == 0 {
		// If all the aggregate functions are pruned, we should add an aggregate function to keep the correctness.
		one, err := aggregation.NewAggFuncDesc(la.ctx, ast.AggFuncFirstRow, []expression.Expression{expression.One}, false)
		if err != nil {
			return err
		}
		la.AggFuncs = []*aggregation.AggFuncDesc{one}
		col := &expression.Column{
			ColName:  model.NewCIStr("dummy_agg"),
			UniqueID: la.ctx.GetSessionVars().AllocPlanColumnID(),
			RetType:  types.NewFieldType(mysql.TypeLonglong),
		}
		la.schema.Columns = []*expression.Column{col}
	}

	if len(la.GroupByItems) > 0 {
		for i := len(la.GroupByItems) - 1; i >= 0; i-- {
			cols := expression.ExtractColumns(la.GroupByItems[i])
			if len(cols) == 0 {
				la.GroupByItems = append(la.GroupByItems[:i], la.GroupByItems[i+1:]...)
			} else {
				selfUsedCols = append(selfUsedCols, cols...)
			}
		}
		// If all the group by items are pruned, we should add a constant 1 to keep the correctness.
		// Because `select count(*) from t` is different from `select count(*) from t group by 1`.
		if len(la.GroupByItems) == 0 {
			la.GroupByItems = []expression.Expression{expression.One}
		}
	}
	return child.PruneColumns(selfUsedCols)
}

// PruneColumns implements LogicalPlan interface.
func (ls *LogicalSort) PruneColumns(parentUsedCols []*expression.Column) error {
	child := ls.children[0]
	for i := len(ls.ByItems) - 1; i >= 0; i-- {
		cols := expression.ExtractColumns(ls.ByItems[i].Expr)
		if len(cols) == 0 {
			if !ls.ByItems[i].Expr.ConstItem() {
				continue
			}
			ls.ByItems = append(ls.ByItems[:i], ls.ByItems[i+1:]...)
		} else if ls.ByItems[i].Expr.GetType().Tp == mysql.TypeNull {
			ls.ByItems = append(ls.ByItems[:i], ls.ByItems[i+1:]...)
		} else {
			parentUsedCols = append(parentUsedCols, cols...)
		}
	}
	return child.PruneColumns(parentUsedCols)
}

// PruneColumns implements LogicalPlan interface.
func (p *LogicalUnionAll) PruneColumns(parentUsedCols []*expression.Column) error {
	used, err := getUsedList(parentUsedCols, p.schema)
	if err != nil {
		return err
	}

	hasBeenUsed := false
	for i := range used {
		hasBeenUsed = hasBeenUsed || used[i]
		if hasBeenUsed {
			break
		}
	}
	if !hasBeenUsed {
		parentUsedCols = make([]*expression.Column, len(p.schema.Columns))
		copy(parentUsedCols, p.schema.Columns)
	} else {
		// Issue 10341: p.schema.Columns might contain table name (AsName), but p.Children()0].Schema().Columns does not.
		for i := len(used) - 1; i >= 0; i-- {
			if !used[i] {
				p.schema.Columns = append(p.schema.Columns[:i], p.schema.Columns[i+1:]...)
			}
		}
	}
	for _, child := range p.Children() {
		err := child.PruneColumns(parentUsedCols)
		if err != nil {
			return err
		}
	}
	return nil
}

// PruneColumns implements LogicalPlan interface.
func (p *LogicalUnionScan) PruneColumns(parentUsedCols []*expression.Column) error {
	parentUsedCols = append(parentUsedCols, p.handleCol)
	return p.children[0].PruneColumns(parentUsedCols)
}

// PruneColumns implements LogicalPlan interface.
func (ds *DataSource) PruneColumns(parentUsedCols []*expression.Column) error {
	used, err := getUsedList(parentUsedCols, ds.schema)
	if err != nil {
		return err
	}

	var (
		handleCol     *expression.Column
		handleColInfo *model.ColumnInfo
	)
	if ds.handleCol != nil {
		handleCol = ds.handleCol
		handleColInfo = ds.Columns[ds.schema.ColumnIndex(handleCol)]
	}
	for i := len(used) - 1; i >= 0; i-- {
		if !used[i] {
			ds.schema.Columns = append(ds.schema.Columns[:i], ds.schema.Columns[i+1:]...)
			ds.Columns = append(ds.Columns[:i], ds.Columns[i+1:]...)
		}
	}
	// For SQL like `select 1 from t`, tikv's response will be empty if no column is in schema.
	// So we'll force to push one if schema doesn't have any column.
	if ds.schema.Len() == 0 && !infoschema.IsMemoryDB(ds.DBName.L) {
		if handleCol == nil {
			handleCol = ds.newExtraHandleSchemaCol()
			handleColInfo = model.NewExtraHandleColInfo()
		}
		ds.Columns = append(ds.Columns, handleColInfo)
		ds.schema.Append(handleCol)
	}
	if ds.handleCol != nil && ds.schema.ColumnIndex(ds.handleCol) == -1 {
		ds.handleCol = nil
	}
	return nil
}

// PruneColumns implements LogicalPlan interface.
func (p *LogicalTableDual) PruneColumns(parentUsedCols []*expression.Column) error {
	used, err := getUsedList(parentUsedCols, p.Schema())
	if err != nil {
		return err
	}

	for i := len(used) - 1; i >= 0; i-- {
		if !used[i] {
			p.schema.Columns = append(p.schema.Columns[:i], p.schema.Columns[i+1:]...)
		}
	}
	return nil
}

func (p *LogicalJoin) extractUsedCols(parentUsedCols []*expression.Column) (leftCols []*expression.Column, rightCols []*expression.Column) {
	for _, eqCond := range p.EqualConditions {
		parentUsedCols = append(parentUsedCols, expression.ExtractColumns(eqCond)...)
	}
	for _, leftCond := range p.LeftConditions {
		parentUsedCols = append(parentUsedCols, expression.ExtractColumns(leftCond)...)
	}
	for _, rightCond := range p.RightConditions {
		parentUsedCols = append(parentUsedCols, expression.ExtractColumns(rightCond)...)
	}
	for _, otherCond := range p.OtherConditions {
		parentUsedCols = append(parentUsedCols, expression.ExtractColumns(otherCond)...)
	}
	lChild := p.children[0]
	rChild := p.children[1]
	for _, col := range parentUsedCols {
		if lChild.Schema().Contains(col) {
			leftCols = append(leftCols, col)
		} else if rChild.Schema().Contains(col) {
			rightCols = append(rightCols, col)
		}
	}
	return leftCols, rightCols
}

func (p *LogicalJoin) mergeSchema() {
	lChild := p.children[0]
	rChild := p.children[1]
	composedSchema := expression.MergeSchema(lChild.Schema(), rChild.Schema())
	if p.JoinType == SemiJoin || p.JoinType == AntiSemiJoin {
		p.schema = lChild.Schema().Clone()
	} else if p.JoinType == LeftOuterSemiJoin || p.JoinType == AntiLeftOuterSemiJoin {
		joinCol := p.schema.Columns[len(p.schema.Columns)-1]
		p.schema = lChild.Schema().Clone()
		p.schema.Append(joinCol)
	} else {
		p.schema = composedSchema
	}
}

// PruneColumns implements LogicalPlan interface.
func (p *LogicalJoin) PruneColumns(parentUsedCols []*expression.Column) error {
	leftCols, rightCols := p.extractUsedCols(parentUsedCols)

	err := p.children[0].PruneColumns(leftCols)
	if err != nil {
		return err
	}

	err = p.children[1].PruneColumns(rightCols)
	if err != nil {
		return err
	}

	p.mergeSchema()
	return nil
}

// PruneColumns implements LogicalPlan interface.
func (la *LogicalApply) PruneColumns(parentUsedCols []*expression.Column) error {
	leftCols, rightCols := la.extractUsedCols(parentUsedCols)

	err := la.children[1].PruneColumns(rightCols)
	if err != nil {
		return err
	}

	la.corCols = extractCorColumnsBySchema(la.children[1], la.children[0].Schema())
	for _, col := range la.corCols {
		leftCols = append(leftCols, &col.Column)
	}

	err = la.children[0].PruneColumns(leftCols)
	if err != nil {
		return err
	}

	la.mergeSchema()
	return nil
}

// PruneColumns implements LogicalPlan interface.
func (p *LogicalLock) PruneColumns(parentUsedCols []*expression.Column) error {
	if p.Lock != ast.SelectLockForUpdate {
		return p.baseLogicalPlan.PruneColumns(parentUsedCols)
	}

	for _, cols := range p.tblID2Handle {
		parentUsedCols = append(parentUsedCols, cols...)
	}
	return p.children[0].PruneColumns(parentUsedCols)
}

// PruneColumns implements LogicalPlan interface.
func (p *LogicalWindow) PruneColumns(parentUsedCols []*expression.Column) error {
	windowColumns := p.GetWindowResultColumns()
	len := 0
	for _, col := range parentUsedCols {
		used := false
		for _, windowColumn := range windowColumns {
			if windowColumn.Equal(nil, col) {
				used = true
				break
			}
		}
		if !used {
			parentUsedCols[len] = col
			len++
		}
	}
	parentUsedCols = parentUsedCols[:len]
	parentUsedCols = p.extractUsedCols(parentUsedCols)
	err := p.children[0].PruneColumns(parentUsedCols)
	if err != nil {
		return err
	}

	p.SetSchema(p.children[0].Schema().Clone())
	p.Schema().Append(windowColumns...)
	return nil
}

func (p *LogicalWindow) extractUsedCols(parentUsedCols []*expression.Column) []*expression.Column {
	for _, desc := range p.WindowFuncDescs {
		for _, arg := range desc.Args {
			parentUsedCols = append(parentUsedCols, expression.ExtractColumns(arg)...)
		}
	}
	for _, by := range p.PartitionBy {
		parentUsedCols = append(parentUsedCols, by.Col)
	}
	for _, by := range p.OrderBy {
		parentUsedCols = append(parentUsedCols, by.Col)
	}
	return parentUsedCols
}

func (*columnPruner) name() string {
	return "column_prune"
}
<|endoftext|>


<commit_before>package template

// Module is the go.mod template used for new projects.
var Module = `module {{.Vendor}}{{.Service}}{{if .Client}}-client{{end}}

go 1.16

require (
	github.com/asim/go-micro/v3 v3.5.2
)

// This can be removed once etcd becomes go gettable, version 3.4 and 3.5 is not,
// see https://github.com/etcd-io/etcd/issues/11154 and https://github.com/etcd-io/etcd/issues/11931.
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0{{if .Vendor}}{{if not .Skaffold}}

replace {{.Vendor}}{{lower .Service}} => ../{{lower .Service}}{{end}}{{end}}
`
<commit_msg>update model template (#2307)<commit_after>package template

// Module is the go.mod template used for new projects.
var Module = `module {{.Vendor}}{{.Service}}{{if .Client}}-client{{end}}

go 1.16

require (
	go-micro.dev/v4 v4.1.0
)

// This can be removed once etcd becomes go gettable, version 3.4 and 3.5 is not,
// see https://github.com/etcd-io/etcd/issues/11154 and https://github.com/etcd-io/etcd/issues/11931.
replace google.golang.org/grpc => google.golang.org/grpc v1.26.0{{if .Vendor}}{{if not .Skaffold}}

replace {{.Vendor}}{{lower .Service}} => ../{{lower .Service}}{{end}}{{end}}
`
<|endoftext|>


<commit_before>package qemubuild

import (
	"errors"
	"io/ioutil"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strconv"

	"github.com/taskcluster/taskcluster-worker/commands/qemu-run"
	"github.com/taskcluster/taskcluster-worker/engines/qemu/image"
	"github.com/taskcluster/taskcluster-worker/engines/qemu/network"
	"github.com/taskcluster/taskcluster-worker/engines/qemu/vm"
	"github.com/taskcluster/taskcluster-worker/runtime"
)

type cmd struct{}

func (cmd) Summary() string {
	return "Build an image for the QEMU engine"
}

func (cmd) Usage() string {
	return `
taskcluster-worker qemu-build takes

run a given command inside an image to test it,
and give you an VNC viewer to get you into the virtual machine.

usage:
	taskcluster-worker qemu-build [options] from-new <machine.json> <result.tar.lz4>
	taskcluster-worker qemu-build [options] from-image <image.tar.lz4> <result.tar.lz4>

options:
     --no-vnc       	Do not open a VNC display.
     --size <size>  	Size of the image in GiB [default: 10].
     --boot <file>  	File to use as cd-rom 1 and boot medium.
     --cdrom <file>	 	File to use as cd-rom 2 (drivers etc).
  -h --help         	Show this screen.
`
}

func (cmd) Execute(arguments map[string]interface{}) {
	// Setup logging
	logger, _ := runtime.CreateLogger("info")
	log := logger.WithField("component", "qemu-build")

	// Parse arguments
	inputImageFile, _ := arguments["<image.tar.lz4>"].(string)
	machineFile, _ := arguments["<machine.json>"].(string)
	outputFile := arguments["<result.tar.lz4>"].(string)
	fromNew := arguments["from-new"].(bool)
	fromImage := arguments["from-image"].(bool)
	novnc := arguments["--no-vnc"].(bool)
	boot, _ := arguments["--boot"].(string)
	cdrom, _ := arguments["--cdrom"].(string)
	size, err := strconv.ParseInt(arguments["--size"].(string), 10, 32)
	if err != nil {
		log.Fatal("Couldn't parse --size, error: ", err)
	}
	if size > 80 {
		log.Fatal("Images have a sanity limit of 80 GiB!")
	}

	// Find absolute outputFile
	outputFile, err = filepath.Abs(outputFile)
	if err != nil {
		log.Fatal("Failed to resolve output file, error: ", err)
	}

	// Create temp folder for the image
	tempFolder, err := ioutil.TempDir("", "taskcluster-worker-build-image-")
	if err != nil {
		log.Fatal("Failed to create temporary folder, error: ", err)
	}
	defer os.RemoveAll(tempFolder)

	var img *image.MutableImage
	if fromNew {
		// Read machine definition
		machine, err := vm.LoadMachine(machineFile)
		if err != nil {
			log.Fatal("Failed to load machine file from ", machineFile, " error: ", err)
		}

		// Construct MutableImage
		log.Info("Creating MutableImage")
		img, err = image.NewMutableImage(tempFolder, int(size), machine)
		if err != nil {
			log.Fatal("Failed to create image, error: ", err)
		}
	}
	if fromImage {
		img, err = image.NewMutableImageFromFile(inputImageFile, tempFolder)
		if err != nil {
			log.Fatal("Failed to load image, error: ", err)
		}
	}

	// Create temp folder for sockets
	socketFolder, err := ioutil.TempDir("", "taskcluster-worker-sockets-")
	if err != nil {
		log.Fatal("Failed to create temporary folder, error: ", err)
	}
	defer os.RemoveAll(socketFolder)

	// Setup a user-space network
	log.Info("Creating user-space network")
	net, err := network.NewUserNetwork(tempFolder)
	if err != nil {
		log.Fatal("Failed to create user-space network, error: ", err)
	}

	// Create virtual machine
	log.Info("Creating virtual machine")
	vm := vm.NewVirtualMachine(img, net, socketFolder, boot, cdrom)

	// Start the virtual machine
	log.Info("Starting virtual machine")
	vm.Start()

	// Open VNC display
	if !novnc {
		go qemurun.StartVNCViewer(vm.VNCSocket(), vm.Done)
	}

	// Wait for interrupt to gracefully kill everything
	interrupted := make(chan os.Signal, 1)
	signal.Notify(interrupted, os.Interrupt)

	// Wait for virtual machine to be done, or we get interrupted
	select {
	case <-interrupted:
		vm.Kill()
		err = errors.New("SIGINT recieved, aborting virtual machine")
	case <-vm.Done:
		err = vm.Error
	}
	<-vm.Done
	signal.Stop(interrupted)
	defer img.Dispose()

	if err != nil {
		if e, ok := err.(*exec.ExitError); ok {
			log.Fatal("QEMU error: ", string(e.Stderr))
		}
		log.Info("Error running virtual machine: ", err)
		return
	}

	// Package up the finished image
	log.Info("Package virtual machine image")
	err = img.Package(outputFile)
	if err != nil {
		log.Fatal("Failed to package finished image, error: ", err)
	}
}
<commit_msg>better usage docs<commit_after>package qemubuild

import (
	"errors"
	"io/ioutil"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strconv"

	"github.com/taskcluster/taskcluster-worker/commands/qemu-run"
	"github.com/taskcluster/taskcluster-worker/engines/qemu/image"
	"github.com/taskcluster/taskcluster-worker/engines/qemu/network"
	"github.com/taskcluster/taskcluster-worker/engines/qemu/vm"
	"github.com/taskcluster/taskcluster-worker/runtime"
)

type cmd struct{}

func (cmd) Summary() string {
	return "Build an image for the QEMU engine"
}

func (cmd) Usage() string {
	return `
taskcluster-worker qemu-build takes a machine definition as JSON or an existing
image and two ISO files to mounted as CDs and creates a virtual machine that
will be saved to disk when terminated.

usage:
	taskcluster-worker qemu-build [options] from-new <machine.json> <result.tar.lz4>
	taskcluster-worker qemu-build [options] from-image <image.tar.lz4> <result.tar.lz4>

options:
     --no-vnc       	Do not open a VNC display.
     --size <size>  	Size of the image in GiB [default: 10].
     --boot <file>  	File to use as cd-rom 1 and boot medium.
     --cdrom <file>	 	File to use as cd-rom 2 (drivers etc).
  -h --help         	Show this screen.
`
}

func (cmd) Execute(arguments map[string]interface{}) {
	// Setup logging
	logger, _ := runtime.CreateLogger("info")
	log := logger.WithField("component", "qemu-build")

	// Parse arguments
	inputImageFile, _ := arguments["<image.tar.lz4>"].(string)
	machineFile, _ := arguments["<machine.json>"].(string)
	outputFile := arguments["<result.tar.lz4>"].(string)
	fromNew := arguments["from-new"].(bool)
	fromImage := arguments["from-image"].(bool)
	novnc := arguments["--no-vnc"].(bool)
	boot, _ := arguments["--boot"].(string)
	cdrom, _ := arguments["--cdrom"].(string)
	size, err := strconv.ParseInt(arguments["--size"].(string), 10, 32)
	if err != nil {
		log.Fatal("Couldn't parse --size, error: ", err)
	}
	if size > 80 {
		log.Fatal("Images have a sanity limit of 80 GiB!")
	}

	// Find absolute outputFile
	outputFile, err = filepath.Abs(outputFile)
	if err != nil {
		log.Fatal("Failed to resolve output file, error: ", err)
	}

	// Create temp folder for the image
	tempFolder, err := ioutil.TempDir("", "taskcluster-worker-build-image-")
	if err != nil {
		log.Fatal("Failed to create temporary folder, error: ", err)
	}
	defer os.RemoveAll(tempFolder)

	var img *image.MutableImage
	if fromNew {
		// Read machine definition
		machine, err := vm.LoadMachine(machineFile)
		if err != nil {
			log.Fatal("Failed to load machine file from ", machineFile, " error: ", err)
		}

		// Construct MutableImage
		log.Info("Creating MutableImage")
		img, err = image.NewMutableImage(tempFolder, int(size), machine)
		if err != nil {
			log.Fatal("Failed to create image, error: ", err)
		}
	}
	if fromImage {
		img, err = image.NewMutableImageFromFile(inputImageFile, tempFolder)
		if err != nil {
			log.Fatal("Failed to load image, error: ", err)
		}
	}

	// Create temp folder for sockets
	socketFolder, err := ioutil.TempDir("", "taskcluster-worker-sockets-")
	if err != nil {
		log.Fatal("Failed to create temporary folder, error: ", err)
	}
	defer os.RemoveAll(socketFolder)

	// Setup a user-space network
	log.Info("Creating user-space network")
	net, err := network.NewUserNetwork(tempFolder)
	if err != nil {
		log.Fatal("Failed to create user-space network, error: ", err)
	}

	// Create virtual machine
	log.Info("Creating virtual machine")
	vm := vm.NewVirtualMachine(img, net, socketFolder, boot, cdrom)

	// Start the virtual machine
	log.Info("Starting virtual machine")
	vm.Start()

	// Open VNC display
	if !novnc {
		go qemurun.StartVNCViewer(vm.VNCSocket(), vm.Done)
	}

	// Wait for interrupt to gracefully kill everything
	interrupted := make(chan os.Signal, 1)
	signal.Notify(interrupted, os.Interrupt)

	// Wait for virtual machine to be done, or we get interrupted
	select {
	case <-interrupted:
		vm.Kill()
		err = errors.New("SIGINT recieved, aborting virtual machine")
	case <-vm.Done:
		err = vm.Error
	}
	<-vm.Done
	signal.Stop(interrupted)
	defer img.Dispose()

	if err != nil {
		if e, ok := err.(*exec.ExitError); ok {
			log.Fatal("QEMU error: ", string(e.Stderr))
		}
		log.Info("Error running virtual machine: ", err)
		return
	}

	// Package up the finished image
	log.Info("Package virtual machine image")
	err = img.Package(outputFile)
	if err != nil {
		log.Fatal("Failed to package finished image, error: ", err)
	}
}
<|endoftext|>


<commit_before>package metrics

import (
	"strconv"
	"sync/atomic"
)

type Counter int64

func NewCounter() Counter {
	return 0
}

func (c *Counter) Inc(delta int64) {
	atomic.AddInt64((*int64)(c), delta)
}

func (c *Counter) Dec(delta int64) {
	atomic.AddInt64((*int64)(c), -delta)
}

func (c *Counter) Set(value int64) {
	atomic.StoreInt64((*int64)(c), value)
}

func (c *Counter) Count() int64 {
	return atomic.LoadInt64((*int64)(c))
}

func (c *Counter) String() string {
	return strconv.FormatInt(c.Count(), 10)
}
<commit_msg>Fix Counter for expvar<commit_after>package metrics

import (
	"strconv"
	"sync/atomic"
)

type Counter int64

func NewCounter() Counter {
	return 0
}

func (c *Counter) Inc(delta int64) {
	atomic.AddInt64((*int64)(c), delta)
}

func (c *Counter) Dec(delta int64) {
	atomic.AddInt64((*int64)(c), -delta)
}

func (c *Counter) Set(value int64) {
	atomic.StoreInt64((*int64)(c), value)
}

func (c *Counter) Count() int64 {
	return atomic.LoadInt64((*int64)(c))
}

func (c Counter) String() string {
	return strconv.FormatInt(c.Count(), 10)
}
<|endoftext|>


<commit_before>package gitlab

import (
	"fmt"
	"net/http"
	"testing"

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

func TestMilestonesService_ListMilestones(t *testing.T) {
	mux, server, client := setup(t)
	defer teardown(server)

	mux.HandleFunc("/api/v4/projects/5/milestones", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprintf(w, `
			[
			  {
				"id": 12,
				"iid": 3,
				"project_id": 16,
				"title": "10.0",
				"description": "Version",
				"state": "active",
				"expired": false
			  }
			]
		`)
	})

	want := []*Milestone{{
		ID:          12,
		IID:         3,
		ProjectID:   16,
		Title:       "10.0",
		Description: "Version",
		State:       "active",
		WebURL:      "",
		Expired:     Bool(false),
	}}

	ms, resp, err := client.Milestones.ListMilestones(5, nil)
	require.NoError(t, err)
	require.NotNil(t, resp)
	require.Equal(t, want, ms)

	ms, resp, err = client.Milestones.ListMilestones(5.01, nil)
	require.EqualError(t, err, "invalid ID type 5.01, the ID must be an int or a string")
	require.Nil(t, resp)
	require.Nil(t, ms)

	ms, resp, err = client.Milestones.ListMilestones(5, nil, errorOption)
	require.EqualError(t, err, "RequestOptionFunc returns an error")
	require.Nil(t, resp)
	require.Nil(t, ms)

	ms, resp, err = client.Milestones.ListMilestones(3, nil)
	require.Error(t, err)
	require.Nil(t, ms)
	require.Equal(t, http.StatusNotFound, resp.StatusCode)
}

func TestMilestonesService_GetMilestone(t *testing.T) {
	mux, server, client := setup(t)
	defer teardown(server)

	mux.HandleFunc("/api/v4/projects/5/milestones/12", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprintf(w, `
		  {
			"id": 12,
			"iid": 3,
			"project_id": 16,
			"title": "10.0",
			"description": "Version",
			"state": "active",
			"expired": false
		  }
		`)
	})

	want := &Milestone{
		ID:          12,
		IID:         3,
		ProjectID:   16,
		Title:       "10.0",
		Description: "Version",
		State:       "active",
		WebURL:      "",
		Expired:     Bool(false),
	}

	ms, resp, err := client.Milestones.GetMilestone(5, 12, nil)
	require.NoError(t, err)
	require.NotNil(t, resp)
	require.Equal(t, want, ms)

	ms, resp, err = client.Milestones.GetMilestone(5.01, 12, nil)
	require.EqualError(t, err, "invalid ID type 5.01, the ID must be an int or a string")
	require.Nil(t, resp)
	require.Nil(t, ms)

	ms, resp, err = client.Milestones.GetMilestone(5, 12, nil, errorOption)
	require.EqualError(t, err, "RequestOptionFunc returns an error")
	require.Nil(t, resp)
	require.Nil(t, ms)

	ms, resp, err = client.Milestones.GetMilestone(3, 12, nil)
	require.Error(t, err)
	require.Nil(t, ms)
	require.Equal(t, http.StatusNotFound, resp.StatusCode)
}
<commit_msg>add tests for create, update, delete in milestones<commit_after>package gitlab

import (
	"fmt"
	"net/http"
	"testing"

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

func TestMilestonesService_ListMilestones(t *testing.T) {
	mux, server, client := setup(t)
	defer teardown(server)

	mux.HandleFunc("/api/v4/projects/5/milestones", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprintf(w, `
			[
			  {
				"id": 12,
				"iid": 3,
				"project_id": 16,
				"title": "10.0",
				"description": "Version",
				"state": "active",
				"expired": false
			  }
			]
		`)
	})

	want := []*Milestone{{
		ID:          12,
		IID:         3,
		ProjectID:   16,
		Title:       "10.0",
		Description: "Version",
		State:       "active",
		WebURL:      "",
		Expired:     Bool(false),
	}}

	ms, resp, err := client.Milestones.ListMilestones(5, nil)
	require.NoError(t, err)
	require.NotNil(t, resp)
	require.Equal(t, want, ms)

	ms, resp, err = client.Milestones.ListMilestones(5.01, nil)
	require.EqualError(t, err, "invalid ID type 5.01, the ID must be an int or a string")
	require.Nil(t, resp)
	require.Nil(t, ms)

	ms, resp, err = client.Milestones.ListMilestones(5, nil, errorOption)
	require.EqualError(t, err, "RequestOptionFunc returns an error")
	require.Nil(t, resp)
	require.Nil(t, ms)

	ms, resp, err = client.Milestones.ListMilestones(3, nil)
	require.Error(t, err)
	require.Nil(t, ms)
	require.Equal(t, http.StatusNotFound, resp.StatusCode)
}

func TestMilestonesService_GetMilestone(t *testing.T) {
	mux, server, client := setup(t)
	defer teardown(server)

	mux.HandleFunc("/api/v4/projects/5/milestones/12", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodGet)
		fmt.Fprintf(w, `
		  {
			"id": 12,
			"iid": 3,
			"project_id": 16,
			"title": "10.0",
			"description": "Version",
			"state": "active",
			"expired": false
		  }
		`)
	})

	want := &Milestone{
		ID:          12,
		IID:         3,
		ProjectID:   16,
		Title:       "10.0",
		Description: "Version",
		State:       "active",
		WebURL:      "",
		Expired:     Bool(false),
	}

	m, resp, err := client.Milestones.GetMilestone(5, 12, nil)
	require.NoError(t, err)
	require.NotNil(t, resp)
	require.Equal(t, want, m)

	m, resp, err = client.Milestones.GetMilestone(5.01, 12, nil)
	require.EqualError(t, err, "invalid ID type 5.01, the ID must be an int or a string")
	require.Nil(t, resp)
	require.Nil(t, m)

	m, resp, err = client.Milestones.GetMilestone(5, 12, nil, errorOption)
	require.EqualError(t, err, "RequestOptionFunc returns an error")
	require.Nil(t, resp)
	require.Nil(t, m)

	m, resp, err = client.Milestones.GetMilestone(3, 12, nil)
	require.Error(t, err)
	require.Nil(t, m)
	require.Equal(t, http.StatusNotFound, resp.StatusCode)
}

func TestMilestonesService_CreateMilestone(t *testing.T) {
	mux, server, client := setup(t)
	defer teardown(server)

	mux.HandleFunc("/api/v4/projects/5/milestones", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodPost)
		fmt.Fprintf(w, `
		  {
			"id": 12,
			"iid": 3,
			"project_id": 16,
			"title": "10.0",
			"description": "Version",
			"state": "active",
			"expired": false
		  }
		`)
	})

	want := &Milestone{
		ID:          12,
		IID:         3,
		ProjectID:   16,
		Title:       "10.0",
		Description: "Version",
		State:       "active",
		WebURL:      "",
		Expired:     Bool(false),
	}

	m, resp, err := client.Milestones.CreateMilestone(5, nil)
	require.NoError(t, err)
	require.NotNil(t, resp)
	require.Equal(t, want, m)

	m, resp, err = client.Milestones.CreateMilestone(5.01, nil)
	require.EqualError(t, err, "invalid ID type 5.01, the ID must be an int or a string")
	require.Nil(t, resp)
	require.Nil(t, m)

	m, resp, err = client.Milestones.CreateMilestone(5, nil, errorOption)
	require.EqualError(t, err, "RequestOptionFunc returns an error")
	require.Nil(t, resp)
	require.Nil(t, m)

	m, resp, err = client.Milestones.CreateMilestone(3, nil)
	require.Error(t, err)
	require.Nil(t, m)
	require.Equal(t, http.StatusNotFound, resp.StatusCode)
}

func TestMilestonesService_UpdateMilestone(t *testing.T) {
	mux, server, client := setup(t)
	defer teardown(server)

	mux.HandleFunc("/api/v4/projects/5/milestones/12", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodPut)
		fmt.Fprintf(w, `
		  {
			"id": 12,
			"iid": 3,
			"project_id": 16,
			"title": "10.0",
			"description": "Version",
			"state": "active",
			"expired": false
		  }
		`)
	})

	want := &Milestone{
		ID:          12,
		IID:         3,
		ProjectID:   16,
		Title:       "10.0",
		Description: "Version",
		State:       "active",
		WebURL:      "",
		Expired:     Bool(false),
	}

	m, resp, err := client.Milestones.UpdateMilestone(5, 12, nil)
	require.NoError(t, err)
	require.NotNil(t, resp)
	require.Equal(t, want, m)

	m, resp, err = client.Milestones.UpdateMilestone(5.01, 12, nil)
	require.EqualError(t, err, "invalid ID type 5.01, the ID must be an int or a string")
	require.Nil(t, resp)
	require.Nil(t, m)

	m, resp, err = client.Milestones.UpdateMilestone(5, 12, nil, errorOption)
	require.EqualError(t, err, "RequestOptionFunc returns an error")
	require.Nil(t, resp)
	require.Nil(t, m)

	m, resp, err = client.Milestones.UpdateMilestone(3, 12, nil)
	require.Error(t, err)
	require.Nil(t, m)
	require.Equal(t, http.StatusNotFound, resp.StatusCode)
}

func TestMilestonesService_DeleteMilestone(t *testing.T) {
	mux, server, client := setup(t)
	defer teardown(server)

	mux.HandleFunc("/api/v4/projects/5/milestones/12", func(w http.ResponseWriter, r *http.Request) {
		testMethod(t, r, http.MethodDelete)
	})

	resp, err := client.Milestones.DeleteMilestone(5, 12, nil)
	require.NoError(t, err)
	require.NotNil(t, resp)

	resp, err = client.Milestones.DeleteMilestone(5.01, 12, nil)
	require.EqualError(t, err, "invalid ID type 5.01, the ID must be an int or a string")
	require.Nil(t, resp)

	resp, err = client.Milestones.DeleteMilestone(5, 12, nil, errorOption)
	require.EqualError(t, err, "RequestOptionFunc returns an error")
	require.Nil(t, resp)

	resp, err = client.Milestones.DeleteMilestone(3, 12, nil)
	require.Error(t, err)
	require.Equal(t, http.StatusNotFound, resp.StatusCode)
}
<|endoftext|>


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

package git

import (
	"net/http"

	"go.chromium.org/luci/common/api/gitiles"
	"go.chromium.org/luci/common/data/stringset"
	"go.chromium.org/luci/common/errors"
	gitilespb "go.chromium.org/luci/common/proto/gitiles"
	"go.chromium.org/luci/server/auth"
	"golang.org/x/net/context"
)

// ClientFactory creates a Gitiles client.
type ClientFactory func(ctx context.Context, host string) (gitilespb.GitilesClient, error)

var factoryKey = "gitiles client factory key"

// UseFactory installs f into c.
func UseFactory(c context.Context, f ClientFactory) context.Context {
	return context.WithValue(c, &factoryKey, f)
}

// TODO(tandrii): remove the following per https://crbug.com/796317.
// Until Milo properly supports ACLs for blamelists, we have a hack; if the git
// repo being log'd is in this list, use `auth.AsSelf`. Otherwise use
// `auth.Anonymous`.
//
// The reason to do this is that we currently do blamelist calculation in the
// backend, so we can't accurately determine if the requesting user has access
// to these repos or not. For now, we use this whitelist to indicate domains
// that we know have full public read-access so that we can use milo's
// credentials (instead of anonymous) in order to avoid hitting gitiles'
// anonymous quota limits.
var whitelistPublicDomains = stringset.NewFromSlice(
	"chromium.googlesource.com",
)

// AuthenticatedProdClient returns a production Gitiles client,
// authenticated as self. Implements ClientFactory.
func AuthenticatedProdClient(c context.Context, host string) (gitilespb.GitilesClient, error) {
	asWho := auth.NoAuth
	if whitelistPublicDomains.Has(host) {
		asWho = auth.AsSelf
	}
	t, err := auth.GetRPCTransport(c, asWho, auth.WithScopes(gitiles.OAuthScope))
	if err != nil {
		return nil, errors.Annotate(err, "getting RPC Transport").Err()
	}
	return gitiles.NewRESTClient(&http.Client{Transport: t}, host, true)
}

// Client creates a new Gitiles client using the ClientFactory installed in c.
// See also UseFactory.
func Client(c context.Context, host string) (gitilespb.GitilesClient, error) {
	f, ok := c.Value(&factoryKey).(ClientFactory)
	if !ok {
		return nil, errors.New("gitiles client factory is not installed in context")
	}
	return f(c, host)
}
<commit_msg>[milo] fix incorrect oauth scopes use when doing anonymous rpc.<commit_after>// Copyright 2018 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package git

import (
	"net/http"

	"go.chromium.org/luci/common/api/gitiles"
	"go.chromium.org/luci/common/data/stringset"
	"go.chromium.org/luci/common/errors"
	gitilespb "go.chromium.org/luci/common/proto/gitiles"
	"go.chromium.org/luci/server/auth"
	"golang.org/x/net/context"
)

// ClientFactory creates a Gitiles client.
type ClientFactory func(ctx context.Context, host string) (gitilespb.GitilesClient, error)

var factoryKey = "gitiles client factory key"

// UseFactory installs f into c.
func UseFactory(c context.Context, f ClientFactory) context.Context {
	return context.WithValue(c, &factoryKey, f)
}

// TODO(tandrii): remove the following per https://crbug.com/796317.
// Until Milo properly supports ACLs for blamelists, we have a hack; if the git
// repo being log'd is in this list, use `auth.AsSelf`. Otherwise use
// `auth.Anonymous`.
//
// The reason to do this is that we currently do blamelist calculation in the
// backend, so we can't accurately determine if the requesting user has access
// to these repos or not. For now, we use this whitelist to indicate domains
// that we know have full public read-access so that we can use milo's
// credentials (instead of anonymous) in order to avoid hitting gitiles'
// anonymous quota limits.
var whitelistPublicDomains = stringset.NewFromSlice(
	"chromium.googlesource.com",
)

// AuthenticatedProdClient returns a production Gitiles client.
//
// Currently, it is authenticated as self only for a whitelistPublicDomains.
// For all other repos, the client will not use authentication to avoid
// information leaks.
// TODO(tandrii): fix this per https://crbug.com/796317.
//
// Implements ClientFactory.
func AuthenticatedProdClient(c context.Context, host string) (gitilespb.GitilesClient, error) {
	var t http.RoundTripper
	var err error
	if whitelistPublicDomains.Has(host) {
		t, err = auth.GetRPCTransport(c, auth.AsSelf, auth.WithScopes(gitiles.OAuthScope))
	} else {
		// No scopes if we aren't authenticating.
		t, err = auth.GetRPCTransport(c, auth.NoAuth)
	}
	if err != nil {
		return nil, errors.Annotate(err, "getting RPC Transport").Err()
	}
	return gitiles.NewRESTClient(&http.Client{Transport: t}, host, true)
}

// Client creates a new Gitiles client using the ClientFactory installed in c.
// See also UseFactory.
func Client(c context.Context, host string) (gitilespb.GitilesClient, error) {
	f, ok := c.Value(&factoryKey).(ClientFactory)
	if !ok {
		return nil, errors.New("gitiles client factory is not installed in context")
	}
	return f(c, host)
}
<|endoftext|>


<commit_before>package unsplash

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/ayoisaiah/stellar-photos-server/config"
	"github.com/ayoisaiah/stellar-photos-server/utils"
)

// UnsplashAPILocation represents the base URL for requests to Unsplash's API
const UnsplashAPILocation = "https://api.unsplash.com"

// download represents the result from triggering a download on a photo
type download struct {
	URL string `json:"url,omitempty"`
}

// searchResult represents the result for a search for photos.
type searchResult struct {
	Total      int           `json:"total,omitempty"`
	TotalPages int           `json:"total_pages,omitempty"`
	Results    []interface{} `json:"results,omitempty"`
}

// collection respresents a single Unsplash collection ID
type collection struct {
	ID string `json:"id,omitempty"`
}

// randomPhoto represents the result from fetching a random photo from Unsplash
type randomPhoto struct {
	ID             string `json:"id"`
	CreatedAt      string `json:"created_at"`
	UpdatedAt      string `json:"updated_at"`
	PromotedAt     string `json:"promoted_at"`
	Width          int    `json:"width"`
	Height         int    `json:"height"`
	Color          string `json:"color"`
	BlurHash       string `json:"blur_hash"`
	Description    string `json:"description"`
	AltDescription string `json:"alt_description"`
	Urls           struct {
		Raw     string `json:"raw"`
		Full    string `json:"full"`
		Regular string `json:"regular"`
		Small   string `json:"small"`
		Thumb   string `json:"thumb"`
		Custom  string `json:"custom"`
	} `json:"urls"`
	Links struct {
		Self             string `json:"self"`
		HTML             string `json:"html"`
		Download         string `json:"download"`
		DownloadLocation string `json:"download_location"`
	} `json:"links"`
	Categories             []interface{} `json:"categories"`
	Likes                  int           `json:"likes"`
	LikedByUser            bool          `json:"liked_by_user"`
	CurrentUserCollections []interface{} `json:"current_user_collections"`
	User                   struct {
		ID              string      `json:"id"`
		UpdatedAt       string      `json:"updated_at"`
		Username        string      `json:"username"`
		Name            string      `json:"name"`
		FirstName       string      `json:"first_name"`
		LastName        string      `json:"last_name"`
		TwitterUsername interface{} `json:"twitter_username"`
		PortfolioURL    string      `json:"portfolio_url"`
		Bio             string      `json:"bio"`
		Location        interface{} `json:"location"`
		Links           struct {
			Self      string `json:"self"`
			HTML      string `json:"html"`
			Photos    string `json:"photos"`
			Likes     string `json:"likes"`
			Portfolio string `json:"portfolio"`
			Following string `json:"following"`
			Followers string `json:"followers"`
		} `json:"links"`
		ProfileImage struct {
			Small  string `json:"small"`
			Medium string `json:"medium"`
			Large  string `json:"large"`
		} `json:"profile_image"`
		InstagramUsername string `json:"instagram_username"`
		TotalCollections  int    `json:"total_collections"`
		TotalLikes        int    `json:"total_likes"`
		TotalPhotos       int    `json:"total_photos"`
		AcceptedTos       bool   `json:"accepted_tos"`
	} `json:"user"`
	Exif struct {
		Make         string `json:"make"`
		Model        string `json:"model"`
		ExposureTime string `json:"exposure_time"`
		Aperture     string `json:"aperture"`
		FocalLength  string `json:"focal_length"`
		Iso          int    `json:"iso"`
	} `json:"exif"`
	Location struct {
		Title    string `json:"title"`
		Name     string `json:"name"`
		City     string `json:"city"`
		Country  string `json:"country"`
		Position struct {
			Latitude  float64 `json:"latitude"`
			Longitude float64 `json:"longitude"`
		} `json:"position"`
	} `json:"location"`
	Views     int `json:"views"`
	Downloads int `json:"downloads"`
}

// randomPhotoWithBase64 respresents the base64 encoding of randomPhoto
type randomPhotoBase64 struct {
	*randomPhoto
	Base64 string `json:"base64,omitempty"`
}

// DownloadPhoto is triggered each time a download is attempted
func DownloadPhoto(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	id := values.Get("id")
	if id == "" {
		return utils.NewHTTPError(
			nil,
			http.StatusBadRequest,
			"Photo ID must not be empty",
		)
	}

	_, err = TrackPhotoDownload(id)
	if err != nil {
		return err
	}

	w.WriteHeader(http.StatusOK)
	return nil
}

// TrackPhotoDownload is used to increment the number of downloads
// for the specified photo
func TrackPhotoDownload(id string) ([]byte, error) {
	unsplashAccessKey := config.Conf.Unsplash.AccessKey
	url := fmt.Sprintf(
		"%s/photos/%s/download?client_id=%s",
		UnsplashAPILocation,
		id,
		unsplashAccessKey,
	)

	return utils.SendGETRequest(url, &download{})
}

// SearchUnsplash triggers a photo search and sends a single page of photo
// results for a query.
func SearchUnsplash(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	key := values.Get("key")
	page := values.Get("page")

	unsplashAccessKey := config.Conf.Unsplash.AccessKey
	url := fmt.Sprintf(
		"%s/search/photos?page=%s&query=%s&per_page=%s&client_id=%s",
		UnsplashAPILocation,
		page,
		key,
		"28",
		unsplashAccessKey,
	)

	s := &searchResult{}

	bs, err := utils.SendGETRequest(url, s)
	if err != nil {
		return err
	}

	return utils.JSONResponse(w, bs)
}

// GetRandomPhoto retrives a single random photo using the provided collection
// IDs to narrow the pool of photos from which a random one will be chosen.
// If no collection IDs are present, it defaults to 998309 which is the ID of
// the official Stellar Photos collection
func GetRandomPhoto(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	collections := values.Get("collections")
	if collections == "" {
		collections = "998309"
	}

	unsplashAccessKey := config.Conf.Unsplash.AccessKey
	url := fmt.Sprintf(
		"%s/photos/random?collections=%s&client_id=%s",
		UnsplashAPILocation,
		collections,
		unsplashAccessKey,
	)

	res := &randomPhoto{}

	_, err = utils.SendGETRequest(url, res)
	if err != nil {
		return err
	}

	imageURL := res.Urls.Raw + "&w=2000"

	base64, err := utils.ImageURLToBase64(imageURL)
	if err != nil {
		return err
	}

	data := randomPhotoBase64{
		res,
		base64,
	}

	bytes, err := json.Marshal(data)
	if err != nil {
		return err
	}

	return utils.JSONResponse(w, bytes)
}

// ValidateCollections ensures that all the custom collection IDs that are added
// to the extension are valid
func ValidateCollections(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	collections := strings.Split(values.Get("collections"), ",")

	unsplashAccessKey := config.Conf.Unsplash.AccessKey

	for _, value := range collections {
		valueToNum, err := strconv.Atoi(value)
		if err != nil {
			return utils.NewHTTPError(
				err,
				http.StatusBadRequest,
				"Collection ID must be a number",
			)
		}

		url := fmt.Sprintf(
			"%s/collections/%d/?client_id=%s",
			UnsplashAPILocation,
			valueToNum,
			unsplashAccessKey,
		)
		_, err = utils.SendGETRequest(url, &collection{})
		if err != nil {
			return err
		}
	}

	w.WriteHeader(http.StatusOK)
	return nil
}
<commit_msg>Support changing the image resolution<commit_after>package unsplash

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/ayoisaiah/stellar-photos-server/config"
	"github.com/ayoisaiah/stellar-photos-server/utils"
)

// UnsplashAPILocation represents the base URL for requests to Unsplash's API
const UnsplashAPILocation = "https://api.unsplash.com"

// download represents the result from triggering a download on a photo
type download struct {
	URL string `json:"url,omitempty"`
}

// searchResult represents the result for a search for photos.
type searchResult struct {
	Total      int           `json:"total,omitempty"`
	TotalPages int           `json:"total_pages,omitempty"`
	Results    []interface{} `json:"results,omitempty"`
}

// collection respresents a single Unsplash collection ID
type collection struct {
	ID string `json:"id,omitempty"`
}

// randomPhoto represents the result from fetching a random photo from Unsplash
type randomPhoto struct {
	ID             string `json:"id"`
	CreatedAt      string `json:"created_at"`
	UpdatedAt      string `json:"updated_at"`
	PromotedAt     string `json:"promoted_at"`
	Width          int    `json:"width"`
	Height         int    `json:"height"`
	Color          string `json:"color"`
	BlurHash       string `json:"blur_hash"`
	Description    string `json:"description"`
	AltDescription string `json:"alt_description"`
	Urls           struct {
		Raw     string `json:"raw"`
		Full    string `json:"full"`
		Regular string `json:"regular"`
		Small   string `json:"small"`
		Thumb   string `json:"thumb"`
		Custom  string `json:"custom"`
	} `json:"urls"`
	Links struct {
		Self             string `json:"self"`
		HTML             string `json:"html"`
		Download         string `json:"download"`
		DownloadLocation string `json:"download_location"`
	} `json:"links"`
	Categories             []interface{} `json:"categories"`
	Likes                  int           `json:"likes"`
	LikedByUser            bool          `json:"liked_by_user"`
	CurrentUserCollections []interface{} `json:"current_user_collections"`
	User                   struct {
		ID              string      `json:"id"`
		UpdatedAt       string      `json:"updated_at"`
		Username        string      `json:"username"`
		Name            string      `json:"name"`
		FirstName       string      `json:"first_name"`
		LastName        string      `json:"last_name"`
		TwitterUsername interface{} `json:"twitter_username"`
		PortfolioURL    string      `json:"portfolio_url"`
		Bio             string      `json:"bio"`
		Location        interface{} `json:"location"`
		Links           struct {
			Self      string `json:"self"`
			HTML      string `json:"html"`
			Photos    string `json:"photos"`
			Likes     string `json:"likes"`
			Portfolio string `json:"portfolio"`
			Following string `json:"following"`
			Followers string `json:"followers"`
		} `json:"links"`
		ProfileImage struct {
			Small  string `json:"small"`
			Medium string `json:"medium"`
			Large  string `json:"large"`
		} `json:"profile_image"`
		InstagramUsername string `json:"instagram_username"`
		TotalCollections  int    `json:"total_collections"`
		TotalLikes        int    `json:"total_likes"`
		TotalPhotos       int    `json:"total_photos"`
		AcceptedTos       bool   `json:"accepted_tos"`
	} `json:"user"`
	Exif struct {
		Make         string `json:"make"`
		Model        string `json:"model"`
		ExposureTime string `json:"exposure_time"`
		Aperture     string `json:"aperture"`
		FocalLength  string `json:"focal_length"`
		Iso          int    `json:"iso"`
	} `json:"exif"`
	Location struct {
		Title    string `json:"title"`
		Name     string `json:"name"`
		City     string `json:"city"`
		Country  string `json:"country"`
		Position struct {
			Latitude  float64 `json:"latitude"`
			Longitude float64 `json:"longitude"`
		} `json:"position"`
	} `json:"location"`
	Views     int `json:"views"`
	Downloads int `json:"downloads"`
}

// randomPhotoWithBase64 respresents the base64 encoding of randomPhoto
type randomPhotoBase64 struct {
	*randomPhoto
	Base64 string `json:"base64,omitempty"`
}

// DownloadPhoto is triggered each time a download is attempted
func DownloadPhoto(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	id := values.Get("id")
	if id == "" {
		return utils.NewHTTPError(
			nil,
			http.StatusBadRequest,
			"Photo ID must not be empty",
		)
	}

	_, err = TrackPhotoDownload(id)
	if err != nil {
		return err
	}

	w.WriteHeader(http.StatusOK)
	return nil
}

// TrackPhotoDownload is used to increment the number of downloads
// for the specified photo
func TrackPhotoDownload(id string) ([]byte, error) {
	unsplashAccessKey := config.Conf.Unsplash.AccessKey
	url := fmt.Sprintf(
		"%s/photos/%s/download?client_id=%s",
		UnsplashAPILocation,
		id,
		unsplashAccessKey,
	)

	return utils.SendGETRequest(url, &download{})
}

// SearchUnsplash triggers a photo search and sends a single page of photo
// results for a query.
func SearchUnsplash(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	key := values.Get("key")
	page := values.Get("page")

	unsplashAccessKey := config.Conf.Unsplash.AccessKey
	url := fmt.Sprintf(
		"%s/search/photos?page=%s&query=%s&per_page=%s&client_id=%s",
		UnsplashAPILocation,
		page,
		key,
		"28",
		unsplashAccessKey,
	)

	s := &searchResult{}

	bs, err := utils.SendGETRequest(url, s)
	if err != nil {
		return err
	}

	return utils.JSONResponse(w, bs)
}

// GetRandomPhoto retrives a single random photo using the provided collection
// IDs to narrow the pool of photos from which a random one will be chosen.
// If no collection IDs are present, it defaults to 998309 which is the ID of
// the official Stellar Photos collection
func GetRandomPhoto(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	collections := values.Get("collections")
	if collections == "" {
		collections = "998309"
	}

	resolution := values.Get("resolution")

	unsplashAccessKey := config.Conf.Unsplash.AccessKey
	url := fmt.Sprintf(
		"%s/photos/random?collections=%s&client_id=%s",
		UnsplashAPILocation,
		collections,
		unsplashAccessKey,
	)

	res := &randomPhoto{}

	_, err = utils.SendGETRequest(url, res)
	if err != nil {
		return err
	}

	var imageWidth = "2000"
	switch resolution {
	case "high":
		imageWidth = "4000"
	case "max":
		imageWidth = strconv.Itoa(res.Width)
	}

	imageURL := res.Urls.Raw + "&w=" + imageWidth

	base64, err := utils.ImageURLToBase64(imageURL)
	if err != nil {
		return err
	}

	data := randomPhotoBase64{
		res,
		base64,
	}

	bytes, err := json.Marshal(data)
	if err != nil {
		return err
	}

	return utils.JSONResponse(w, bytes)
}

// ValidateCollections ensures that all the custom collection IDs that are added
// to the extension are valid
func ValidateCollections(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	collections := strings.Split(values.Get("collections"), ",")

	unsplashAccessKey := config.Conf.Unsplash.AccessKey

	for _, value := range collections {
		valueToNum, err := strconv.Atoi(value)
		if err != nil {
			return utils.NewHTTPError(
				err,
				http.StatusBadRequest,
				"Collection ID must be a number",
			)
		}

		url := fmt.Sprintf(
			"%s/collections/%d/?client_id=%s",
			UnsplashAPILocation,
			valueToNum,
			unsplashAccessKey,
		)
		_, err = utils.SendGETRequest(url, &collection{})
		if err != nil {
			return err
		}
	}

	w.WriteHeader(http.StatusOK)
	return nil
}
<|endoftext|>


<commit_before>package validator

import (
	"bytes"
	"fmt"
	"html/template"
	"reflect"
	"regexp"
	"strings"

	"github.com/go-playground/validator"
	"github.com/pkg/errors"
)

type Validate struct {
	gpValidate           *validator.Validate
	customTemplateMap    TemplateMap
	inclusionValidations map[string][]string
}

type Rule struct {
	// Field mean field name of struct, it can be nested.
	// For example "Address.City".
	Field string
	// This tag contains tag and param, use "," to separate multiple tags.
	// For example "required,lte=20".
	Tag     string
	Message string
}

type Error struct {
	Field   string
	Tag     string
	Param   string
	Message string
}

type Errors []Error

type MapError map[string][]string

type TemplateMap map[string]string

var defaultTemplateMap = TemplateMap{
	"required":     "can not be blank",
	"lte":          "is too long, maximum length is {{.Param}}",
	"gte":          "is too short, minimum length is {{.Param}}",
	"max":          "is too large, maximum is {{.Param}}",
	"min":          "is too small, minimum is {{.Param}}",
	"zipcode_jp":   "invalid zipcode format, format is 123-1234",
	"inclusion":    "invalid {{.Param}} value",
	"simple_email": "invalid email format",
	"default":      "validation failed with {{ if eq .Param \"\" }}{{.Tag}}{{ else }}{{.Tag}}={{.Param}}{{ end }}",
}

func New() *Validate {
	gpValidate := validator.New()

	inclusionValidations := map[string][]string{}
	if err := gpValidate.RegisterValidation("inclusion", validateInclusion(inclusionValidations)); err != nil {
		panic(errors.Wrap(err, "register validation inclusion failed"))
	}

	validate := Validate{gpValidate: gpValidate, inclusionValidations: inclusionValidations}

	if err := validate.RegisterRegexpValidation("zipcode_jp", `^\d{3}-\d{4}$`); err != nil {
		panic(errors.Wrap(err, "register regexp validation zipcode_jp failed"))
	}

	if err := validate.RegisterRegexpValidation("simple_email", `^[^\s@]+@[^\s@]+$`); err != nil {
		panic(errors.Wrap(err, "register regexp validation simple_email failed"))
	}

	return &validate
}

// RegisterInclusionValidationParam register a param for inclusion validation.
// validList are all valid values for param of the inclusion validation.
//
// For example, if you register a "gender" param,
// then you can use `validate:"inclusion=gender"` validation tag for the struct.
//
// If you use a unregistered inclusion param,
// then this field of the struct validation always failed.
//
// If you register the same param multiple times, the front will be covered.
// If param is empty, it will return error.
func (v *Validate) RegisterInclusionValidationParam(param string, validList []string) error {
	if param == "" {
		return errors.New("param can not be empty")
	}

	v.inclusionValidations[param] = validList

	return nil
}

// val should be a struct value.
// If found the tagName of val, then use the tag value replace name.
//
// It return invalid value if has some errors.
// It return nil pointer value if try to get value from nil.
func fieldByNameNested(val reflect.Value, name string, tagName string) (reflect.Value, string) {
	names := []string{}

	for _, n := range strings.Split(name, ".") {
		if val.Kind() == reflect.Invalid {
			return val, ""
		}
		if val.Kind() == reflect.Ptr {
			if val.IsNil() {
				return val, ""
			}
			val = val.Elem()
		}

		tag := getTagValue(val, n, tagName)
		if tag == "" {
			names = append(names, n)
		} else {
			names = append(names, tag)
		}

		val = val.FieldByName(n)
	}

	return val, strings.Join(names, ".")
}

func (ves Errors) Error() string {
	if len(ves) == 0 {
		return ""
	}

	errStrs := []string{}
	for _, ve := range ves {
		if ve.Param == "" {
			if ve.Message == "" {
				errStrs = append(errStrs, fmt.Sprintf("%v of %v", ve.Tag, ve.Field))
			} else {
				errStrs = append(errStrs, fmt.Sprintf("%v of %v: %v", ve.Tag, ve.Field, ve.Message))
			}
		} else {
			if ve.Message == "" {
				errStrs = append(errStrs, fmt.Sprintf("%v=%v of %v", ve.Tag, ve.Param, ve.Field))
			} else {
				errStrs = append(errStrs, fmt.Sprintf("%v=%v of %v: %v", ve.Tag, ve.Param, ve.Field, ve.Message))
			}
		}
	}

	return "validation failed: " + strings.Join(errStrs, "; ")
}

// fmt []string{"1", "2", "3"} to `["1", "2", "3"]`
func fmtStringArray(strs []string) string {
	if len(strs) == 0 {
		return ""
	}

	return `["` + strings.Join(strs, `", "`) + `"]`
}

func (vem MapError) Error() string {
	errStr := ""

	for field, messages := range vem {
		errStr = errStr + field + ":" + fmtStringArray(messages) + " "
	}

	if errStr != "" {
		// Remove last " " char.
		errStr = errStr[:len(errStr)-1]
	}

	return errStr
}

// data should be a struct or a pointer to struct.
//
// if return (nil, nil), it mean no validation error.
//
// If it return (nil, error), you must to solve it. Possible errors:
// * Invalid Rule.Tag
// * Invalid Rule.Field
// * data is not a struct or a pointer to struct
//
// Some custom tags:
// * zipcode_jp
// * simple_email
func (v *Validate) DoRules(data interface{}, rules []Rule) (Errors, error) {
	return v.DoRulesWithTagName(data, rules, "")
}

func (v *Validate) DoRulesWithTagName(data interface{}, rules []Rule, tagName string) (verrs Errors, err error) {
	defer func() {
		if r := recover(); r != nil {
			verrs = nil
			err = errors.New(fmt.Sprint(r))
		}
	}()

	val := reflect.ValueOf(data)
	if val.Kind() == reflect.Ptr && !val.IsNil() {
		val = val.Elem()
	}
	if val.Kind() != reflect.Struct {
		return nil, errors.New("data should be a struct or a pointer to struct")
	}

	verrs = Errors{}

	for _, rule := range rules {
		field, fieldName := fieldByNameNested(val, rule.Field, tagName)
		if (field.Kind() == reflect.Invalid) || (field.Kind() == reflect.Ptr && field.IsNil()) {
			return nil, errors.New(fmt.Sprintf("get value from %v field failed", rule.Field))
		}
		fieldVal := field.Interface()

		varTags := []string{}
		for _, tag := range splitTag(rule.Tag) {
			switch getTagBefore(tag) {
			case "eqfield":
				otherField, _ := fieldByNameNested(val, getTagAfter(tag), "")
				if (otherField.Kind() == reflect.Invalid) || (otherField.Kind() == reflect.Ptr && otherField.IsNil()) {
					return nil, errors.New(fmt.Sprintf("get value from %v field failed", rule.Field))
				}
				otherFieldVal := otherField.Interface()

				verrs, err = appendErrors(v.gpValidate.VarWithValue(fieldVal, otherFieldVal, "eqfield"), verrs, fieldName, rule.Message)
				if err != nil {
					return nil, err
				}
			default:
				varTags = append(varTags, tag)
			}
		}

		if len(varTags) > 0 {
			verrs, err = appendErrors(v.gpValidate.Var(fieldVal, strings.Join(varTags, tagSeparator)), verrs, fieldName, rule.Message)
			if err != nil {
				return nil, err
			}
		}
	}

	if len(verrs) == 0 {
		return nil, nil
	}

	return verrs, nil
}

func appendErrors(err error, verrs Errors, fieldName string, message string) (Errors, error) {
	if _, ok := err.(*validator.InvalidValidationError); ok {
		return nil, err
	}

	if validationErrors, ok := err.(validator.ValidationErrors); ok {
		for _, validationErr := range validationErrors {
			verrs = append(verrs, Error{
				Field:   fieldName,
				Tag:     validationErr.Tag(),
				Param:   validationErr.Param(),
				Message: message,
			})
		}
	}

	return verrs, nil
}

// It's a proxy function for validate.Var from github.com/go-playground/validator.
// But it return bool type.
//
// If has some invalid input, it will return false.
func (v *Validate) IsVar(field interface{}, tag string) bool {
	err := v.gpValidate.Var(field, tag)

	if _, ok := err.(*validator.InvalidValidationError); ok {
		return false
	}

	if _, ok := err.(validator.ValidationErrors); ok {
		return false
	}

	return true
}

type templateValues struct {
	Param string
	Tag   string
}

func getTemplate(tag string, customTemplateMap TemplateMap) string {
	if message := customTemplateMap[tag]; message != "" {
		return message
	}

	if message := defaultTemplateMap[tag]; message != "" {
		return message
	}

	return defaultTemplateMap["default"]
}

func parseTemplate(tplValues templateValues, templateStr string) (string, error) {
	tl, err := template.New("validate").Parse(templateStr)
	if err != nil {
		return "", errors.Wrap(err, "template parse failed")
	}

	message := bytes.Buffer{}
	if err := tl.Execute(&message, tplValues); err != nil {
		return "", errors.Wrap(err, "template execute failed")
	}

	return message.String(), nil
}

// templateMap is a map that mean [tag]template,
// templateMap will be parsed by go template,
// you can use ".Tag" and ".Param" variable in the template.
//
// For example, validation tag is "max=100", then tag is "max", param is "100",
// if template is "is too large, maximum is {{.Param}}",
// then it will be parsed to "is too large, maximum is 100".
//
// If templateMap is nil, it will use defaultTemplateMap to parse.
// If not found the tag in the templateMap, it will use defaultTemplateMap to parse for this tag,
// it mean templateMap will merge to defaultTemplateMap,
// so you don't worry about missing some tags of the templateMap.
//
// If parse template failed, it will return error.
func VErrorsToMap(verrs Errors, templateMap TemplateMap) (MapError, error) {
	verrMap := MapError{}
	for _, verr := range verrs {
		vMessage, err := parseTemplate(templateValues{Param: verr.Param, Tag: verr.Tag}, getTemplate(verr.Tag, templateMap))
		if err != nil {
			return nil, errors.Wrap(err, "parseTemplate failed")
		}
		verrMap[verr.Field] = append(verrMap[verr.Field], vMessage)
	}
	return verrMap, nil
}

func (v *Validate) RegisterTemplateMap(templateMap TemplateMap) error {
	if err := checkTemplateMap(templateMap); err != nil {
		return err
	}

	v.customTemplateMap = templateMap

	return nil
}

func checkTemplateMap(templateMap TemplateMap) error {
	tplValues := templateValues{
		Param: "check param",
		Tag:   "check tag",
	}

	for tag, tpl := range templateMap {
		if tag == "" {
			return errors.New("tag of the templateMap can not be empty")
		}

		_, err := parseTemplate(tplValues, tpl)
		if err != nil {
			return errors.Wrap(err, "tpl of the templateMap invalid")
		}
	}

	return nil
}

// Same as DoRules, run DoRules and VErrorsToMap with custom template.
// You can use RegisterTemplateMap func to register custom template.
func (v *Validate) DoRulesAndToMapError(data interface{}, rules []Rule) (MapError, error) {
	verrs, err := v.DoRules(data, rules)
	if err != nil {
		return nil, err
	}

	return VErrorsToMap(verrs, v.customTemplateMap)
}

func (v *Validate) DoRulesAndToMapErrorWithTagName(data interface{}, rules []Rule, tagName string) (MapError, error) {
	verrs, err := v.DoRulesWithTagName(data, rules, tagName)
	if err != nil {
		return nil, err
	}

	return VErrorsToMap(verrs, v.customTemplateMap)
}

// RegisterValidation adds a validation with the given tag.
//
// NOTES:
// - if the key already exists, the previous validation function will be replaced.
// - this method is not thread-safe it is intended that these all be registered prior to any validation
//
// TODO if we use this function, we must import github.com/go-playground/validator,
// this is not good, because we hope only import this package.
// To find a better way.
func (v *Validate) RegisterValidation(tag string, fn func(validator.FieldLevel) bool) error {
	return v.gpValidate.RegisterValidation(tag, fn)
}

// RegisterRegexpValidation adds a regexp validation with the given tag and regexpString
//
// NOTES:
// - if the key already exists, the previous validation function will be replaced.
// - this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterRegexpValidation(tag string, regexpString string) error {
	return v.gpValidate.RegisterValidation(tag, generateRegexpValidation(regexpString))
}

func generateRegexpValidation(regexpString string) func(fl validator.FieldLevel) bool {
	return func(fl validator.FieldLevel) bool {
		val := fl.Field().String()

		if !regexp.MustCompile(regexpString).MatchString(val) {
			return false
		}

		return true
	}
}

func validateInclusion(inclusionValidations map[string][]string) validator.Func {
	return func(fl validator.FieldLevel) bool {
		return isInStringArray(fl.Field().String(), inclusionValidations[fl.Param()])
	}
}

func isInStringArray(check string, array []string) bool {
	for _, v := range array {
		if v == check {
			return true
		}
	}

	return false
}
<commit_msg>expose Validate.GPValidate<commit_after>package validator

import (
	"bytes"
	"fmt"
	"html/template"
	"reflect"
	"regexp"
	"strings"

	"github.com/go-playground/validator"
	"github.com/pkg/errors"
)

type Validate struct {
	GPValidate           *validator.Validate
	customTemplateMap    TemplateMap
	inclusionValidations map[string][]string
}

type Rule struct {
	// Field mean field name of struct, it can be nested.
	// For example "Address.City".
	Field string
	// This tag contains tag and param, use "," to separate multiple tags.
	// For example "required,lte=20".
	Tag     string
	Message string
}

type Error struct {
	Field   string
	Tag     string
	Param   string
	Message string
}

type Errors []Error

type MapError map[string][]string

type TemplateMap map[string]string

var defaultTemplateMap = TemplateMap{
	"required":     "can not be blank",
	"lte":          "is too long, maximum length is {{.Param}}",
	"gte":          "is too short, minimum length is {{.Param}}",
	"max":          "is too large, maximum is {{.Param}}",
	"min":          "is too small, minimum is {{.Param}}",
	"zipcode_jp":   "invalid zipcode format, format is 123-1234",
	"inclusion":    "invalid {{.Param}} value",
	"simple_email": "invalid email format",
	"default":      "validation failed with {{ if eq .Param \"\" }}{{.Tag}}{{ else }}{{.Tag}}={{.Param}}{{ end }}",
}

func New() *Validate {
	gpValidate := validator.New()

	inclusionValidations := map[string][]string{}
	if err := gpValidate.RegisterValidation("inclusion", validateInclusion(inclusionValidations)); err != nil {
		panic(errors.Wrap(err, "register validation inclusion failed"))
	}

	validate := Validate{GPValidate: gpValidate, inclusionValidations: inclusionValidations}

	if err := validate.RegisterRegexpValidation("zipcode_jp", `^\d{3}-\d{4}$`); err != nil {
		panic(errors.Wrap(err, "register regexp validation zipcode_jp failed"))
	}

	if err := validate.RegisterRegexpValidation("simple_email", `^[^\s@]+@[^\s@]+$`); err != nil {
		panic(errors.Wrap(err, "register regexp validation simple_email failed"))
	}

	return &validate
}

// RegisterInclusionValidationParam register a param for inclusion validation.
// validList are all valid values for param of the inclusion validation.
//
// For example, if you register a "gender" param,
// then you can use `validate:"inclusion=gender"` validation tag for the struct.
//
// If you use a unregistered inclusion param,
// then this field of the struct validation always failed.
//
// If you register the same param multiple times, the front will be covered.
// If param is empty, it will return error.
func (v *Validate) RegisterInclusionValidationParam(param string, validList []string) error {
	if param == "" {
		return errors.New("param can not be empty")
	}

	v.inclusionValidations[param] = validList

	return nil
}

// val should be a struct value.
// If found the tagName of val, then use the tag value replace name.
//
// It return invalid value if has some errors.
// It return nil pointer value if try to get value from nil.
func fieldByNameNested(val reflect.Value, name string, tagName string) (reflect.Value, string) {
	names := []string{}

	for _, n := range strings.Split(name, ".") {
		if val.Kind() == reflect.Invalid {
			return val, ""
		}
		if val.Kind() == reflect.Ptr {
			if val.IsNil() {
				return val, ""
			}
			val = val.Elem()
		}

		tag := getTagValue(val, n, tagName)
		if tag == "" {
			names = append(names, n)
		} else {
			names = append(names, tag)
		}

		val = val.FieldByName(n)
	}

	return val, strings.Join(names, ".")
}

func (ves Errors) Error() string {
	if len(ves) == 0 {
		return ""
	}

	errStrs := []string{}
	for _, ve := range ves {
		if ve.Param == "" {
			if ve.Message == "" {
				errStrs = append(errStrs, fmt.Sprintf("%v of %v", ve.Tag, ve.Field))
			} else {
				errStrs = append(errStrs, fmt.Sprintf("%v of %v: %v", ve.Tag, ve.Field, ve.Message))
			}
		} else {
			if ve.Message == "" {
				errStrs = append(errStrs, fmt.Sprintf("%v=%v of %v", ve.Tag, ve.Param, ve.Field))
			} else {
				errStrs = append(errStrs, fmt.Sprintf("%v=%v of %v: %v", ve.Tag, ve.Param, ve.Field, ve.Message))
			}
		}
	}

	return "validation failed: " + strings.Join(errStrs, "; ")
}

// fmt []string{"1", "2", "3"} to `["1", "2", "3"]`
func fmtStringArray(strs []string) string {
	if len(strs) == 0 {
		return ""
	}

	return `["` + strings.Join(strs, `", "`) + `"]`
}

func (vem MapError) Error() string {
	errStr := ""

	for field, messages := range vem {
		errStr = errStr + field + ":" + fmtStringArray(messages) + " "
	}

	if errStr != "" {
		// Remove last " " char.
		errStr = errStr[:len(errStr)-1]
	}

	return errStr
}

// data should be a struct or a pointer to struct.
//
// if return (nil, nil), it mean no validation error.
//
// If it return (nil, error), you must to solve it. Possible errors:
// * Invalid Rule.Tag
// * Invalid Rule.Field
// * data is not a struct or a pointer to struct
//
// Some custom tags:
// * zipcode_jp
// * simple_email
func (v *Validate) DoRules(data interface{}, rules []Rule) (Errors, error) {
	return v.DoRulesWithTagName(data, rules, "")
}

func (v *Validate) DoRulesWithTagName(data interface{}, rules []Rule, tagName string) (verrs Errors, err error) {
	defer func() {
		if r := recover(); r != nil {
			verrs = nil
			err = errors.New(fmt.Sprint(r))
		}
	}()

	val := reflect.ValueOf(data)
	if val.Kind() == reflect.Ptr && !val.IsNil() {
		val = val.Elem()
	}
	if val.Kind() != reflect.Struct {
		return nil, errors.New("data should be a struct or a pointer to struct")
	}

	verrs = Errors{}

	for _, rule := range rules {
		field, fieldName := fieldByNameNested(val, rule.Field, tagName)
		if (field.Kind() == reflect.Invalid) || (field.Kind() == reflect.Ptr && field.IsNil()) {
			return nil, errors.New(fmt.Sprintf("get value from %v field failed", rule.Field))
		}
		fieldVal := field.Interface()

		varTags := []string{}
		for _, tag := range splitTag(rule.Tag) {
			switch getTagBefore(tag) {
			case "eqfield":
				otherField, _ := fieldByNameNested(val, getTagAfter(tag), "")
				if (otherField.Kind() == reflect.Invalid) || (otherField.Kind() == reflect.Ptr && otherField.IsNil()) {
					return nil, errors.New(fmt.Sprintf("get value from %v field failed", rule.Field))
				}
				otherFieldVal := otherField.Interface()

				verrs, err = appendErrors(v.GPValidate.VarWithValue(fieldVal, otherFieldVal, "eqfield"), verrs, fieldName, rule.Message)
				if err != nil {
					return nil, err
				}
			default:
				varTags = append(varTags, tag)
			}
		}

		if len(varTags) > 0 {
			verrs, err = appendErrors(v.GPValidate.Var(fieldVal, strings.Join(varTags, tagSeparator)), verrs, fieldName, rule.Message)
			if err != nil {
				return nil, err
			}
		}
	}

	if len(verrs) == 0 {
		return nil, nil
	}

	return verrs, nil
}

func appendErrors(err error, verrs Errors, fieldName string, message string) (Errors, error) {
	if _, ok := err.(*validator.InvalidValidationError); ok {
		return nil, err
	}

	if validationErrors, ok := err.(validator.ValidationErrors); ok {
		for _, validationErr := range validationErrors {
			verrs = append(verrs, Error{
				Field:   fieldName,
				Tag:     validationErr.Tag(),
				Param:   validationErr.Param(),
				Message: message,
			})
		}
	}

	return verrs, nil
}

// It's a proxy function for validate.Var from github.com/go-playground/validator.
// But it return bool type.
//
// If has some invalid input, it will return false.
func (v *Validate) IsVar(field interface{}, tag string) bool {
	err := v.GPValidate.Var(field, tag)

	if _, ok := err.(*validator.InvalidValidationError); ok {
		return false
	}

	if _, ok := err.(validator.ValidationErrors); ok {
		return false
	}

	return true
}

type templateValues struct {
	Param string
	Tag   string
}

func getTemplate(tag string, customTemplateMap TemplateMap) string {
	if message := customTemplateMap[tag]; message != "" {
		return message
	}

	if message := defaultTemplateMap[tag]; message != "" {
		return message
	}

	return defaultTemplateMap["default"]
}

func parseTemplate(tplValues templateValues, templateStr string) (string, error) {
	tl, err := template.New("validate").Parse(templateStr)
	if err != nil {
		return "", errors.Wrap(err, "template parse failed")
	}

	message := bytes.Buffer{}
	if err := tl.Execute(&message, tplValues); err != nil {
		return "", errors.Wrap(err, "template execute failed")
	}

	return message.String(), nil
}

// templateMap is a map that mean [tag]template,
// templateMap will be parsed by go template,
// you can use ".Tag" and ".Param" variable in the template.
//
// For example, validation tag is "max=100", then tag is "max", param is "100",
// if template is "is too large, maximum is {{.Param}}",
// then it will be parsed to "is too large, maximum is 100".
//
// If templateMap is nil, it will use defaultTemplateMap to parse.
// If not found the tag in the templateMap, it will use defaultTemplateMap to parse for this tag,
// it mean templateMap will merge to defaultTemplateMap,
// so you don't worry about missing some tags of the templateMap.
//
// If parse template failed, it will return error.
func VErrorsToMap(verrs Errors, templateMap TemplateMap) (MapError, error) {
	verrMap := MapError{}
	for _, verr := range verrs {
		vMessage, err := parseTemplate(templateValues{Param: verr.Param, Tag: verr.Tag}, getTemplate(verr.Tag, templateMap))
		if err != nil {
			return nil, errors.Wrap(err, "parseTemplate failed")
		}
		verrMap[verr.Field] = append(verrMap[verr.Field], vMessage)
	}
	return verrMap, nil
}

func (v *Validate) RegisterTemplateMap(templateMap TemplateMap) error {
	if err := checkTemplateMap(templateMap); err != nil {
		return err
	}

	v.customTemplateMap = templateMap

	return nil
}

func checkTemplateMap(templateMap TemplateMap) error {
	tplValues := templateValues{
		Param: "check param",
		Tag:   "check tag",
	}

	for tag, tpl := range templateMap {
		if tag == "" {
			return errors.New("tag of the templateMap can not be empty")
		}

		_, err := parseTemplate(tplValues, tpl)
		if err != nil {
			return errors.Wrap(err, "tpl of the templateMap invalid")
		}
	}

	return nil
}

// Same as DoRules, run DoRules and VErrorsToMap with custom template.
// You can use RegisterTemplateMap func to register custom template.
func (v *Validate) DoRulesAndToMapError(data interface{}, rules []Rule) (MapError, error) {
	verrs, err := v.DoRules(data, rules)
	if err != nil {
		return nil, err
	}

	return VErrorsToMap(verrs, v.customTemplateMap)
}

func (v *Validate) DoRulesAndToMapErrorWithTagName(data interface{}, rules []Rule, tagName string) (MapError, error) {
	verrs, err := v.DoRulesWithTagName(data, rules, tagName)
	if err != nil {
		return nil, err
	}

	return VErrorsToMap(verrs, v.customTemplateMap)
}

// RegisterValidation adds a validation with the given tag.
//
// NOTES:
// - if the key already exists, the previous validation function will be replaced.
// - this method is not thread-safe it is intended that these all be registered prior to any validation
//
// TODO if we use this function, we must import github.com/go-playground/validator,
// this is not good, because we hope only import this package.
// To find a better way.
func (v *Validate) RegisterValidation(tag string, fn func(validator.FieldLevel) bool) error {
	return v.GPValidate.RegisterValidation(tag, fn)
}

// RegisterRegexpValidation adds a regexp validation with the given tag and regexpString
//
// NOTES:
// - if the key already exists, the previous validation function will be replaced.
// - this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterRegexpValidation(tag string, regexpString string) error {
	return v.GPValidate.RegisterValidation(tag, generateRegexpValidation(regexpString))
}

func generateRegexpValidation(regexpString string) func(fl validator.FieldLevel) bool {
	return func(fl validator.FieldLevel) bool {
		val := fl.Field().String()

		if !regexp.MustCompile(regexpString).MatchString(val) {
			return false
		}

		return true
	}
}

func validateInclusion(inclusionValidations map[string][]string) validator.Func {
	return func(fl validator.FieldLevel) bool {
		return isInStringArray(fl.Field().String(), inclusionValidations[fl.Param()])
	}
}

func isInStringArray(check string, array []string) bool {
	for _, v := range array {
		if v == check {
			return true
		}
	}

	return false
}
<|endoftext|>