<commit_before>package generator

import (
	"go/ast"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"reflect"
	"testing"

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

type FieldSuite struct {
	suite.Suite
}

func TestField(t *testing.T) {
	suite.Run(t, new(FieldSuite))
}

func (s *FieldSuite) TestInline() {
	cases := []struct {
		typ    string
		tag    string
		inline bool
	}{
		{"", "", false},
		{BaseModel, "", true},
		{"", `kallax:"foo"`, false},
		{"", `kallax:"foo,inline"`, true},
		{"", `kallax:"foo,inline,omitempty"`, true},
		{"", `kallax:",inline,omitempty"`, true},
		{"", `kallax:",inline"`, true},
	}

	for _, c := range cases {
		s.Equal(c.inline, mkField("", c.typ, c.tag).Inline(), "field with tag: %s", c.tag)
	}
}

func (s *FieldSuite) TestIsPrimaryKey() {
	cases := []struct {
		tag string
		ok  bool
	}{
		{"", false},
		{`kallax:"pk"`, false},
		{`kallax:"foo,pk"`, false},
		{`pk:""`, true},
		{`pk:"foo"`, true},
		{`pk:"autoincr"`, true},
	}

	for _, c := range cases {
		s.Equal(c.ok, mkField("", "", c.tag).IsPrimaryKey(), "field with tag: %s", c.tag)
	}
}

func (s *FieldSuite) TestIsAutoIncrement() {
	cases := []struct {
		tag string
		ok  bool
	}{
		{"", false},
		{`pk:""`, false},
		{`pk:"ponies"`, false},
		{`pk:"autoincr"`, true},
	}

	for _, c := range cases {
		s.Equal(c.ok, mkField("", "", c.tag).IsAutoIncrement(), "field with tag: %s", c.tag)
	}
}

func (s *FieldSuite) TestColumnName() {
	cases := []struct {
		tag      string
		name     string
		expected string
	}{
		{"", "Foo", "foo"},
		{"", "FooBar", "foo_bar"},
		{"", "ID", "id"},
		{"", "References", "_references"},
		{`kallax:"foo"`, "Bar", "foo"},
		{`kallax:"References"`, "Bar", "_References"},
		{`kallax:"references"`, "Bar", "_references"},
	}

	for _, c := range cases {
		name := mkField(c.name, "", c.tag).ColumnName()
		s.Equal(c.expected, name, "field with name: %q and tag: %s", c.name, c.tag)
	}
}

func (s *FieldSuite) TestAddress() {
	cases := []struct {
		kind     FieldKind
		isJSON   bool
		isPtr    bool
		name     string
		typeStr  string
		parent   *Field
		expected string
	}{
		{
			Struct, true, false, "Foo", "", nil,
			"types.JSON(&r.Foo)",
		},
		{
			Map, true, false, "Foo", "", nil,
			"types.JSON(&r.Foo)",
		},
		{
			Struct, false, false, "Foo", "", nil,
			"&r.Foo",
		},
		{
			Array, false, false, "Foo", "[5]string", nil,
			`types.Array(&r.Foo, 5)`,
		},
		{
			Slice, false, false, "Foo", "[]string", nil,
			"types.Slice(&r.Foo)",
		},
		{
			Basic, false, true, "Foo", "", nil,
			"types.Nullable(&r.Foo)",
		},
		{
			Interface, false, true, "Foo", "", nil,
			"types.Nullable(r.Foo)",
		},
		{
			Basic, false, true, "Foo", "", withParent(mkField("Bar", "", ""), mkField("Baz", "", "")),
			"types.Nullable(&r.Baz.Bar.Foo)",
		},
	}

	for i, c := range cases {
		f := withKind(withParent(mkField(c.name, c.typeStr, ""), c.parent), c.kind)
		if c.isJSON {
			f = withJSON(f)
		}

		if c.isPtr {
			f = withPtr(f)
		}

		s.Equal(c.expected, f.Address(), "Field %s, i = %d", f.Name, i)
	}
}

func (s *FieldSuite) TestValue() {
	cases := []struct {
		field    *Field
		expected string
	}{
		{
			mkField("Foo", "string", ""),
			"r.Foo, nil",
		},
		{
			withAlias(mkField("Foo", "string", "")),
			"(string)(r.Foo), nil",
		},
		{
			withPtr(withAlias(mkField("Foo", "string", ""))),
			"(*string)(r.Foo), nil",
		},
		{
			withKind(mkField("Foo", "", ""), Slice),
			"types.Slice(r.Foo), nil",
		},
		{
			withKind(mkField("Foo", "[5]string", ""), Array),
			`types.Array(&r.Foo, 5), nil`,
		},
		{
			withJSON(withKind(mkField("Foo", "", ""), Map)),
			"types.JSON(r.Foo), nil",
		},
		{
			withKind(mkField("Foo", "", ""), Struct),
			"r.Foo, nil",
		},
	}

	for i, c := range cases {
		s.Equal(c.expected, c.field.Value(), "Field %s, i=%d", c.field.Name, i)
	}
}

type ModelSuite struct {
	suite.Suite
	model    *Model
	variadic *Model
}

const fixturesSource = `
package fixtures

import (
	"errors"
	"strings"

	kallax "gopkg.in/src-d/go-kallax.v1"
)

type User struct {
	kallax.Model ` + "`table:\"users\" pk:\"id\"`" + `
	ID kallax.ULID
	Username     string
	Email        string
	Password     Password
	Websites     []string
	Emails       []*Email
	Settings     *Settings
}

func newUser(id kallax.ULID, username, email string) (*User, error) {
	if strings.Contains(email, "@spam.org") {
		return nil, errors.New("kallax: is spam!")
	}
	return &User{ID: id, Username: username, Email: email}, nil
}

type Email struct {
	kallax.Model ` + "`table:\"models\"`" + `
	ID int64 ` + "`pk:\"autoincr\"`" + `
	Address      string
	Primary      bool
}

func newProfile(address string, primary bool) *Email {
	return &Email{Address: address, Primary: primary}
}

type Password string

// Kids, don't do this at home
func (p *Password) Set(pwd string) {
	*p = Password("such cypher" + pwd + "much secure")
}

type Settings struct {
	NotificationsActive bool
	NotifyByEmail       bool
}

type Variadic struct {
	kallax.Model
	ID int64 ` + "`pk:\"autoincr\"`" + `
	Foo []string
	Bar string
}

func newVariadic(bar string, foo ...string) *Variadic {
	return &Variadic{Foo: foo, Bar: bar}
}
`

func (s *ModelSuite) SetupSuite() {
	fset := &token.FileSet{}
	astFile, err := parser.ParseFile(fset, "fixture.go", fixturesSource, 0)
	s.Nil(err)

	cfg := &types.Config{
		Importer: importer.For("gc", nil),
	}
	p, err := cfg.Check("foo", fset, []*ast.File{astFile}, nil)
	s.Nil(err)

	prc := NewProcessor("fixture", []string{"foo.go"})
	prc.Package = p
	pkg, err := prc.processPackage()
	s.Nil(err)

	s.Len(pkg.Models, 3, "there should exist 3 models")
	for _, m := range pkg.Models {
		if m.Name == "User" {
			s.model = m
		}

		if m.Name == "Variadic" {
			s.variadic = m
		}
	}
	s.NotNil(s.model, "User struct should be defined")
}

func (s *ModelSuite) TestModel() {
	s.Equal("__user", s.model.Alias())
	s.Equal("users", s.model.Table)
	s.Equal("User", s.model.Name)
	s.Equal("UserStore", s.model.StoreName)
	s.Equal("UserQuery", s.model.QueryName)
	s.Equal("UserResultSet", s.model.ResultSetName)
}

func (s *ModelSuite) TestCtor() {
	s.Equal("id kallax.ULID, username string, email string", s.model.CtorArgs())
	s.Equal("id, username, email", s.model.CtorArgVars())
	s.Equal("(record *User, err error)", s.model.CtorReturns())
	s.Equal("record, err", s.model.CtorRetVars())
}

func (s *ModelSuite) TestCtor_Variadic() {
	s.Equal("bar string, foo ...string", s.variadic.CtorArgs())
	s.Equal("bar, foo...", s.variadic.CtorArgVars())
	s.Equal("(record *Variadic)", s.variadic.CtorReturns())
	s.Equal("record", s.variadic.CtorRetVars())
}

func (s *ModelSuite) TestModelValidate() {
	require := s.Require()

	id := s.model.ID
	m := &Model{Name: "Foo", Table: "foo", ID: id}
	m.Fields = []*Field{
		mkField("ID", "", ""),
		inline(mkField("Nested", "", "", inline(
			mkField("Deep", "", "", mkField("ID", "", "")),
		))),
	}
	require.Error(m.Validate(), "should return error")

	m.Fields = []*Field{
		mkField("ID", "", ""),
		inline(mkField("Nested", "", "", mkField("Foo", "", ""))),
	}
	require.NoError(m.Validate(), "should not return error")

	m.ID = nil
	require.Error(m.Validate(), "should return error")

	m.ID = s.model.Fields[2]
	require.Error(m.Validate(), "should return error")

	m.ID = id
	m.Table = ""
	require.Error(m.Validate(), "should return error")
}

func TestFieldForeignKey(t *testing.T) {
	r := require.New(t)
	m := &Model{Name: "Foo", Table: "bar", Type: "foo.Foo"}

	cases := []struct {
		tag      string
		inverse  bool
		typ      string
		expected string
	}{
		{`fk:""`, false, "", "foo_id"},
		{`fk:"foo_bar_baz"`, false, "", "foo_bar_baz"},
		{`fk:",inverse"`, true, "Bar", "bar_id"},
		{`fk:"foos,inverse"`, true, "Bar", "foos"},
		{``, false, "", "foo_id"},
	}

	for _, c := range cases {
		f := NewField("", "", reflect.StructTag(c.tag))
		f.Kind = Relationship
		f.Model = m
		f.Type = c.typ

		r.Equal(c.expected, f.ForeignKey(), "foreign key with tag: %s", c.tag)
		r.Equal(c.inverse, f.IsInverse(), "is inverse: %s", c.tag)
	}
}

func TestModelSetFields(t *testing.T) {
	r := require.New(t)
	cases := []struct {
		name   string
		fields []*Field
		err    bool
		id     string
	}{
		{
			"only one primary key",
			[]*Field{
				mkField("Foo", "", ""),
				mkField("ID", "", `pk:""`),
			},
			false,
			"ID",
		},
		{
			"multiple primary keys",
			[]*Field{
				mkField("ID", "", `pk:""`),
				mkField("FooID", "", `pk:""`),
			},
			true,
			"",
		},
		{
			"primary key defined in model but empty",
			[]*Field{
				mkField("Model", BaseModel, `pk:""`),
			},
			true,
			"",
		},
		{
			"primary key defined in model and non existent",
			[]*Field{
				mkField("Model", BaseModel, `pk:"foo"`),
				mkField("Bar", "", ""),
			},
			true,
			"",
		},
		{
			"primary key defined in model",
			[]*Field{
				mkField("Model", BaseModel, `pk:"foo"`),
				mkField("Baz", "", ""),
				mkField("Foo", "", ""),
				mkField("Bar", "", ""),
			},
			false,
			"Foo",
		},
	}

	for _, c := range cases {
		m := new(Model)
		err := m.SetFields(c.fields)
		if c.err {
			r.Error(err, c.name)
		} else {
			r.NoError(err, c.name)
			r.Equal(c.id, m.ID.Name)
		}
	}
}

func TestModel(t *testing.T) {
	suite.Run(t, new(ModelSuite))
}

func TestPkProperties(t *testing.T) {
	cases := []struct {
		tag          string
		name         string
		autoincr     bool
		isPrimaryKey bool
	}{
		{`pk:"bar"`, "bar", false, true},
		{`pk:""`, "", false, true},
		{`pk:"autoincr"`, "", true, true},
		{`pk:",autoincr"`, "", true, true},
		{`bar:"baz" pk:"foo"`, "foo", false, true},
		{`pk:"foo,autoincr"`, "foo", true, true},
	}

	require := require.New(t)
	for _, tt := range cases {
		name, autoincr, isPrimaryKey := pkProperties(reflect.StructTag(tt.tag))
		require.Equal(tt.name, name, tt.tag)
		require.Equal(tt.autoincr, autoincr, tt.tag)
		require.Equal(tt.isPrimaryKey, isPrimaryKey, tt.tag)
	}
}

func TestIsUnique(t *testing.T) {
	cases := []struct {
		tag    string
		unique bool
	}{
		{``, false},
		{`fk:"foo"`, false},
		{`unique:""`, false},
		{`unique:"true"`, true},
		{`fk:"foo" unique:"true"`, true},
	}

	for _, tt := range cases {
		t.Run(tt.tag, func(t *testing.T) {
			f := NewField("", "", reflect.StructTag(tt.tag))
			require.Equal(t, tt.unique, f.IsUnique())
		})
	}
}
<commit_msg>Added test case for #229<commit_after>package generator

import (
	"go/ast"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"reflect"
	"testing"

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

type FieldSuite struct {
	suite.Suite
}

func TestField(t *testing.T) {
	suite.Run(t, new(FieldSuite))
}

func (s *FieldSuite) TestInline() {
	cases := []struct {
		typ    string
		tag    string
		inline bool
	}{
		{"", "", false},
		{BaseModel, "", true},
		{"", `kallax:"foo"`, false},
		{"", `kallax:"foo,inline"`, true},
		{"", `kallax:"foo,inline,omitempty"`, true},
		{"", `kallax:",inline,omitempty"`, true},
		{"", `kallax:",inline"`, true},
	}

	for _, c := range cases {
		s.Equal(c.inline, mkField("", c.typ, c.tag).Inline(), "field with tag: %s", c.tag)
	}
}

func (s *FieldSuite) TestIsPrimaryKey() {
	cases := []struct {
		tag string
		ok  bool
	}{
		{"", false},
		{`kallax:"pk"`, false},
		{`kallax:"foo,pk"`, false},
		{`pk:""`, true},
		{`pk:"foo"`, true},
		{`pk:"autoincr"`, true},
	}

	for _, c := range cases {
		s.Equal(c.ok, mkField("", "", c.tag).IsPrimaryKey(), "field with tag: %s", c.tag)
	}
}

func (s *FieldSuite) TestIsAutoIncrement() {
	cases := []struct {
		tag string
		ok  bool
	}{
		{"", false},
		{`pk:""`, false},
		{`pk:"ponies"`, false},
		{`pk:"autoincr"`, true},
	}

	for _, c := range cases {
		s.Equal(c.ok, mkField("", "", c.tag).IsAutoIncrement(), "field with tag: %s", c.tag)
	}
}

func (s *FieldSuite) TestColumnName() {
	cases := []struct {
		tag      string
		name     string
		expected string
	}{
		{"", "Foo", "foo"},
		{"", "FooBar", "foo_bar"},
		{"", "ID", "id"},
		{"", "References", "_references"},
		{`kallax:"foo"`, "Bar", "foo"},
		{`kallax:"References"`, "Bar", "_References"},
		{`kallax:"references"`, "Bar", "_references"},
	}

	for _, c := range cases {
		name := mkField(c.name, "", c.tag).ColumnName()
		s.Equal(c.expected, name, "field with name: %q and tag: %s", c.name, c.tag)
	}
}

func (s *FieldSuite) TestAddress() {
	cases := []struct {
		kind     FieldKind
		isJSON   bool
		isPtr    bool
		name     string
		typeStr  string
		parent   *Field
		expected string
	}{
		{
			Struct, true, false, "Foo", "", nil,
			"types.JSON(&r.Foo)",
		},
		{
			Map, true, false, "Foo", "", nil,
			"types.JSON(&r.Foo)",
		},
		{
			Struct, false, false, "Foo", "", nil,
			"&r.Foo",
		},
		{
			Array, false, false, "Foo", "[5]string", nil,
			`types.Array(&r.Foo, 5)`,
		},
		{
			Slice, false, false, "Foo", "[]string", nil,
			"types.Slice(&r.Foo)",
		},
		{
			Basic, false, true, "Foo", "", nil,
			"types.Nullable(&r.Foo)",
		},
		{
			Interface, false, true, "Foo", "", nil,
			"types.Nullable(r.Foo)",
		},
		{
			Basic, false, true, "Foo", "", withParent(mkField("Bar", "", ""), mkField("Baz", "", "")),
			"types.Nullable(&r.Baz.Bar.Foo)",
		},
	}

	for i, c := range cases {
		f := withKind(withParent(mkField(c.name, c.typeStr, ""), c.parent), c.kind)
		if c.isJSON {
			f = withJSON(f)
		}

		if c.isPtr {
			f = withPtr(f)
		}

		s.Equal(c.expected, f.Address(), "Field %s, i = %d", f.Name, i)
	}
}

func (s *FieldSuite) TestValue() {
	cases := []struct {
		field    *Field
		expected string
	}{
		{
			mkField("Foo", "string", ""),
			"r.Foo, nil",
		},
		{
			withAlias(mkField("Foo", "string", "")),
			"(string)(r.Foo), nil",
		},
		{
			withPtr(withAlias(mkField("Foo", "string", ""))),
			"(*string)(r.Foo), nil",
		},
		{
			withKind(mkField("Foo", "", ""), Slice),
			"types.Slice(r.Foo), nil",
		},
		{
			withKind(mkField("Foo", "[5]string", ""), Array),
			`types.Array(&r.Foo, 5), nil`,
		},
		{
			withJSON(withKind(mkField("Foo", "", ""), Map)),
			"types.JSON(r.Foo), nil",
		},
		{
			withKind(mkField("Foo", "", ""), Struct),
			"r.Foo, nil",
		},
	}

	for i, c := range cases {
		s.Equal(c.expected, c.field.Value(), "Field %s, i=%d", c.field.Name, i)
	}
}

type ModelSuite struct {
	suite.Suite
	model    *Model
	variadic *Model
}

const fixturesSource = `
package fixtures

import (
	"errors"
	"strings"

	kallax "gopkg.in/src-d/go-kallax.v1"
)

type User struct {
	kallax.Model ` + "`table:\"users\" pk:\"id\"`" + `
	ID kallax.ULID
	Username     string
	Email        string
	Password     Password
	Websites     []string
	Emails       []*Email
	Settings     *Settings
}

func newUser(id kallax.ULID, username, email string, websites []string) (*User, error) {
	if strings.Contains(email, "@spam.org") {
		return nil, errors.New("kallax: is spam!")
	}
	return &User{ID: id, Username: username, Email: email, Websites: websites}, nil
}

type Email struct {
	kallax.Model ` + "`table:\"models\"`" + `
	ID int64 ` + "`pk:\"autoincr\"`" + `
	Address      string
	Primary      bool
}

func newProfile(address string, primary bool) *Email {
	return &Email{Address: address, Primary: primary}
}

type Password string

// Kids, don't do this at home
func (p *Password) Set(pwd string) {
	*p = Password("such cypher" + pwd + "much secure")
}

type Settings struct {
	NotificationsActive bool
	NotifyByEmail       bool
}

type Variadic struct {
	kallax.Model
	ID int64 ` + "`pk:\"autoincr\"`" + `
	Foo []string
	Bar string
}

func newVariadic(bar string, foo ...string) *Variadic {
	return &Variadic{Foo: foo, Bar: bar}
}
`

func (s *ModelSuite) SetupSuite() {
	fset := &token.FileSet{}
	astFile, err := parser.ParseFile(fset, "fixture.go", fixturesSource, 0)
	s.Nil(err)

	cfg := &types.Config{
		Importer: importer.For("gc", nil),
	}
	p, err := cfg.Check("foo", fset, []*ast.File{astFile}, nil)
	s.Nil(err)

	prc := NewProcessor("fixture", []string{"foo.go"})
	prc.Package = p
	pkg, err := prc.processPackage()
	s.Nil(err)

	s.Len(pkg.Models, 3, "there should exist 3 models")
	for _, m := range pkg.Models {
		if m.Name == "User" {
			s.model = m
		}

		if m.Name == "Variadic" {
			s.variadic = m
		}
	}
	s.NotNil(s.model, "User struct should be defined")
}

func (s *ModelSuite) TestModel() {
	s.Equal("__user", s.model.Alias())
	s.Equal("users", s.model.Table)
	s.Equal("User", s.model.Name)
	s.Equal("UserStore", s.model.StoreName)
	s.Equal("UserQuery", s.model.QueryName)
	s.Equal("UserResultSet", s.model.ResultSetName)
}

func (s *ModelSuite) TestCtor() {
	s.Equal("id kallax.ULID, username string, email string, websites []string", s.model.CtorArgs())
	s.Equal("id, username, email, websites", s.model.CtorArgVars())
	s.Equal("(record *User, err error)", s.model.CtorReturns())
	s.Equal("record, err", s.model.CtorRetVars())
}

func (s *ModelSuite) TestCtor_Variadic() {
	s.Equal("bar string, foo ...string", s.variadic.CtorArgs())
	s.Equal("bar, foo...", s.variadic.CtorArgVars())
	s.Equal("(record *Variadic)", s.variadic.CtorReturns())
	s.Equal("record", s.variadic.CtorRetVars())
}

func (s *ModelSuite) TestModelValidate() {
	require := s.Require()

	id := s.model.ID
	m := &Model{Name: "Foo", Table: "foo", ID: id}
	m.Fields = []*Field{
		mkField("ID", "", ""),
		inline(mkField("Nested", "", "", inline(
			mkField("Deep", "", "", mkField("ID", "", "")),
		))),
	}
	require.Error(m.Validate(), "should return error")

	m.Fields = []*Field{
		mkField("ID", "", ""),
		inline(mkField("Nested", "", "", mkField("Foo", "", ""))),
	}
	require.NoError(m.Validate(), "should not return error")

	m.ID = nil
	require.Error(m.Validate(), "should return error")

	m.ID = s.model.Fields[2]
	require.Error(m.Validate(), "should return error")

	m.ID = id
	m.Table = ""
	require.Error(m.Validate(), "should return error")
}

func TestFieldForeignKey(t *testing.T) {
	r := require.New(t)
	m := &Model{Name: "Foo", Table: "bar", Type: "foo.Foo"}

	cases := []struct {
		tag      string
		inverse  bool
		typ      string
		expected string
	}{
		{`fk:""`, false, "", "foo_id"},
		{`fk:"foo_bar_baz"`, false, "", "foo_bar_baz"},
		{`fk:",inverse"`, true, "Bar", "bar_id"},
		{`fk:"foos,inverse"`, true, "Bar", "foos"},
		{``, false, "", "foo_id"},
	}

	for _, c := range cases {
		f := NewField("", "", reflect.StructTag(c.tag))
		f.Kind = Relationship
		f.Model = m
		f.Type = c.typ

		r.Equal(c.expected, f.ForeignKey(), "foreign key with tag: %s", c.tag)
		r.Equal(c.inverse, f.IsInverse(), "is inverse: %s", c.tag)
	}
}

func TestModelSetFields(t *testing.T) {
	r := require.New(t)
	cases := []struct {
		name   string
		fields []*Field
		err    bool
		id     string
	}{
		{
			"only one primary key",
			[]*Field{
				mkField("Foo", "", ""),
				mkField("ID", "", `pk:""`),
			},
			false,
			"ID",
		},
		{
			"multiple primary keys",
			[]*Field{
				mkField("ID", "", `pk:""`),
				mkField("FooID", "", `pk:""`),
			},
			true,
			"",
		},
		{
			"primary key defined in model but empty",
			[]*Field{
				mkField("Model", BaseModel, `pk:""`),
			},
			true,
			"",
		},
		{
			"primary key defined in model and non existent",
			[]*Field{
				mkField("Model", BaseModel, `pk:"foo"`),
				mkField("Bar", "", ""),
			},
			true,
			"",
		},
		{
			"primary key defined in model",
			[]*Field{
				mkField("Model", BaseModel, `pk:"foo"`),
				mkField("Baz", "", ""),
				mkField("Foo", "", ""),
				mkField("Bar", "", ""),
			},
			false,
			"Foo",
		},
	}

	for _, c := range cases {
		m := new(Model)
		err := m.SetFields(c.fields)
		if c.err {
			r.Error(err, c.name)
		} else {
			r.NoError(err, c.name)
			r.Equal(c.id, m.ID.Name)
		}
	}
}

func TestModel(t *testing.T) {
	suite.Run(t, new(ModelSuite))
}

func TestPkProperties(t *testing.T) {
	cases := []struct {
		tag          string
		name         string
		autoincr     bool
		isPrimaryKey bool
	}{
		{`pk:"bar"`, "bar", false, true},
		{`pk:""`, "", false, true},
		{`pk:"autoincr"`, "", true, true},
		{`pk:",autoincr"`, "", true, true},
		{`bar:"baz" pk:"foo"`, "foo", false, true},
		{`pk:"foo,autoincr"`, "foo", true, true},
	}

	require := require.New(t)
	for _, tt := range cases {
		name, autoincr, isPrimaryKey := pkProperties(reflect.StructTag(tt.tag))
		require.Equal(tt.name, name, tt.tag)
		require.Equal(tt.autoincr, autoincr, tt.tag)
		require.Equal(tt.isPrimaryKey, isPrimaryKey, tt.tag)
	}
}

func TestIsUnique(t *testing.T) {
	cases := []struct {
		tag    string
		unique bool
	}{
		{``, false},
		{`fk:"foo"`, false},
		{`unique:""`, false},
		{`unique:"true"`, true},
		{`fk:"foo" unique:"true"`, true},
	}

	for _, tt := range cases {
		t.Run(tt.tag, func(t *testing.T) {
			f := NewField("", "", reflect.StructTag(tt.tag))
			require.Equal(t, tt.unique, f.IsUnique())
		})
	}
}
<|endoftext|>


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

// Cgo; see gmp.go for an overview.

// TODO(rsc):
//	Emit correct line number annotations.
//	Make 6g understand the annotations.

package main

import (
	"crypto/md5"
	"flag"
	"fmt"
	"go/ast"
	"go/token"
	"io"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
)

// A Package collects information about the package we're going to write.
type Package struct {
	PackageName string // name of package
	PackagePath string
	PtrSize     int64
	GccOptions  []string
	CgoFlags    map[string]string // #cgo flags (CFLAGS, LDFLAGS)
	Written     map[string]bool
	Name        map[string]*Name    // accumulated Name from Files
	Typedef     map[string]ast.Expr // accumulated Typedef from Files
	ExpFunc     []*ExpFunc          // accumulated ExpFunc from Files
	Decl        []ast.Decl
	GoFiles     []string // list of Go files
	GccFiles    []string // list of gcc output files
}

// A File collects information about a single Go input file.
type File struct {
	AST      *ast.File           // parsed AST
	Comments []*ast.CommentGroup // comments from file
	Package  string              // Package name
	Preamble string              // C preamble (doc comment on import "C")
	Ref      []*Ref              // all references to C.xxx in AST
	ExpFunc  []*ExpFunc          // exported functions for this file
	Name     map[string]*Name    // map from Go name to Name
	Typedef  map[string]ast.Expr // translations of all necessary types from C
}

// A Ref refers to an expression of the form C.xxx in the AST.
type Ref struct {
	Name    *Name
	Expr    *ast.Expr
	Context string // "type", "expr", "call", or "call2"
}

func (r *Ref) Pos() token.Pos {
	return (*r.Expr).Pos()
}

// A Name collects information about C.xxx.
type Name struct {
	Go       string // name used in Go referring to package C
	Mangle   string // name used in generated Go
	C        string // name used in C
	Define   string // #define expansion
	Kind     string // "const", "type", "var", "func", "not-type"
	Type     *Type  // the type of xxx
	FuncType *FuncType
	AddError bool
	Const    string // constant definition
}

// A ExpFunc is an exported function, callable from C.
// Such functions are identified in the Go input file
// by doc comments containing the line //export ExpName
type ExpFunc struct {
	Func    *ast.FuncDecl
	ExpName string // name to use from C
}

// A TypeRepr contains the string representation of a type.
type TypeRepr struct {
	Repr       string
	FormatArgs []interface{}
}

// A Type collects information about a type in both the C and Go worlds.
type Type struct {
	Size       int64
	Align      int64
	C          *TypeRepr
	Go         ast.Expr
	EnumValues map[string]int64
}

// A FuncType collects information about a function type in both the C and Go worlds.
type FuncType struct {
	Params []*Type
	Result *Type
	Go     *ast.FuncType
}

func usage() {
	fmt.Fprint(os.Stderr, "usage: cgo -- [compiler options] file.go ...\n")
	flag.PrintDefaults()
	os.Exit(2)
}

var ptrSizeMap = map[string]int64{
	"386":   4,
	"amd64": 8,
	"arm":   4,
}

var cPrefix string

var fset = token.NewFileSet()

var dynobj = flag.String("dynimport", "", "if non-empty, print dynamic import data for that file")
var dynout = flag.String("dynout", "", "write -dynobj output to this file")

// These flags are for bootstrapping a new Go implementation,
// to generate Go and C headers that match the data layout and
// constant values used in the host's C libraries and system calls.
var godefs = flag.Bool("godefs", false, "for bootstrap: write Go definitions for C file to standard output")
var cdefs = flag.Bool("cdefs", false, "for bootstrap: write C definitions for C file to standard output")
var objDir = flag.String("objdir", "", "object directory")

var gccgo = flag.Bool("gccgo", false, "generate files for use with gccgo")
var importRuntimeCgo = flag.Bool("import_runtime_cgo", true, "import runtime/cgo in generated code")
var goarch, goos string

func main() {
	flag.Usage = usage
	flag.Parse()

	if *dynobj != "" {
		// cgo -dynimport is essentially a separate helper command
		// built into the cgo binary.  It scans a gcc-produced executable
		// and dumps information about the imported symbols and the
		// imported libraries.  The Make.pkg rules for cgo prepare an
		// appropriate executable and then use its import information
		// instead of needing to make the linkers duplicate all the
		// specialized knowledge gcc has about where to look for imported
		// symbols and which ones to use.
		dynimport(*dynobj)
		return
	}

	if *godefs && *cdefs {
		fmt.Fprintf(os.Stderr, "cgo: cannot use -cdefs and -godefs together\n")
		os.Exit(2)
	}

	args := flag.Args()
	if len(args) < 1 {
		usage()
	}

	// Find first arg that looks like a go file and assume everything before
	// that are options to pass to gcc.
	var i int
	for i = len(args); i > 0; i-- {
		if !strings.HasSuffix(args[i-1], ".go") {
			break
		}
	}
	if i == len(args) {
		usage()
	}

	goFiles := args[i:]

	p := newPackage(args[:i])

	// Need a unique prefix for the global C symbols that
	// we use to coordinate between gcc and ourselves.
	// We already put _cgo_ at the beginning, so the main
	// concern is other cgo wrappers for the same functions.
	// Use the beginning of the md5 of the input to disambiguate.
	h := md5.New()
	for _, input := range goFiles {
		f, err := os.Open(input)
		if err != nil {
			fatalf("%s", err)
		}
		io.Copy(h, f)
		f.Close()
	}
	cPrefix = fmt.Sprintf("_%x", h.Sum(nil)[0:6])

	fs := make([]*File, len(goFiles))
	for i, input := range goFiles {
		// Parse flags for all files before translating due to CFLAGS.
		f := new(File)
		f.ReadGo(input)
		p.ParseFlags(f, input)
		fs[i] = f
	}

	if *objDir == "" {
		// make sure that _obj directory exists, so that we can write
		// all the output files there.
		os.Mkdir("_obj", 0777)
		*objDir = "_obj"
	}
	*objDir += string(filepath.Separator)

	for i, input := range goFiles {
		f := fs[i]
		p.Translate(f)
		for _, cref := range f.Ref {
			switch cref.Context {
			case "call", "call2":
				if cref.Name.Kind != "type" {
					break
				}
				*cref.Expr = cref.Name.Type.Go
			}
		}
		if nerrors > 0 {
			os.Exit(2)
		}
		pkg := f.Package
		if dir := os.Getenv("CGOPKGPATH"); dir != "" {
			pkg = filepath.Join(dir, pkg)
		}
		p.PackagePath = pkg
		p.Record(f)
		if *godefs {
			os.Stdout.WriteString(p.godefs(f, input))
		} else if *cdefs {
			os.Stdout.WriteString(p.cdefs(f, input))
		} else {
			p.writeOutput(f, input)
		}
	}

	if !*godefs && !*cdefs {
		p.writeDefs()
	}
	if nerrors > 0 {
		os.Exit(2)
	}
}

// newPackage returns a new Package that will invoke
// gcc with the additional arguments specified in args.
func newPackage(args []string) *Package {
	// Copy the gcc options to a new slice so the list
	// can grow without overwriting the slice that args is in.
	gccOptions := make([]string, len(args))
	copy(gccOptions, args)

	goarch = runtime.GOARCH
	if s := os.Getenv("GOARCH"); s != "" {
		goarch = s
	}
	goos = runtime.GOOS
	if s := os.Getenv("GOOS"); s != "" {
		goos = s
	}
	ptrSize := ptrSizeMap[goarch]
	if ptrSize == 0 {
		fatalf("unknown $GOARCH %q", goarch)
	}

	// Reset locale variables so gcc emits English errors [sic].
	os.Setenv("LANG", "en_US.UTF-8")
	os.Setenv("LC_ALL", "C")

	p := &Package{
		PtrSize:    ptrSize,
		GccOptions: gccOptions,
		CgoFlags:   make(map[string]string),
		Written:    make(map[string]bool),
	}
	return p
}

// Record what needs to be recorded about f.
func (p *Package) Record(f *File) {
	if p.PackageName == "" {
		p.PackageName = f.Package
	} else if p.PackageName != f.Package {
		error_(token.NoPos, "inconsistent package names: %s, %s", p.PackageName, f.Package)
	}

	if p.Name == nil {
		p.Name = f.Name
	} else {
		for k, v := range f.Name {
			if p.Name[k] == nil {
				p.Name[k] = v
			} else if !reflect.DeepEqual(p.Name[k], v) {
				error_(token.NoPos, "inconsistent definitions for C.%s", k)
			}
		}
	}

	p.ExpFunc = append(p.ExpFunc, f.ExpFunc...)
	p.Decl = append(p.Decl, f.AST.Decls...)
}
<commit_msg>cmd/cgo: omit //line in -godefs, -cdefs output<commit_after>// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Cgo; see gmp.go for an overview.

// TODO(rsc):
//	Emit correct line number annotations.
//	Make 6g understand the annotations.

package main

import (
	"crypto/md5"
	"flag"
	"fmt"
	"go/ast"
	"go/printer"
	"go/token"
	"io"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strings"
)

// A Package collects information about the package we're going to write.
type Package struct {
	PackageName string // name of package
	PackagePath string
	PtrSize     int64
	GccOptions  []string
	CgoFlags    map[string]string // #cgo flags (CFLAGS, LDFLAGS)
	Written     map[string]bool
	Name        map[string]*Name    // accumulated Name from Files
	Typedef     map[string]ast.Expr // accumulated Typedef from Files
	ExpFunc     []*ExpFunc          // accumulated ExpFunc from Files
	Decl        []ast.Decl
	GoFiles     []string // list of Go files
	GccFiles    []string // list of gcc output files
}

// A File collects information about a single Go input file.
type File struct {
	AST      *ast.File           // parsed AST
	Comments []*ast.CommentGroup // comments from file
	Package  string              // Package name
	Preamble string              // C preamble (doc comment on import "C")
	Ref      []*Ref              // all references to C.xxx in AST
	ExpFunc  []*ExpFunc          // exported functions for this file
	Name     map[string]*Name    // map from Go name to Name
	Typedef  map[string]ast.Expr // translations of all necessary types from C
}

// A Ref refers to an expression of the form C.xxx in the AST.
type Ref struct {
	Name    *Name
	Expr    *ast.Expr
	Context string // "type", "expr", "call", or "call2"
}

func (r *Ref) Pos() token.Pos {
	return (*r.Expr).Pos()
}

// A Name collects information about C.xxx.
type Name struct {
	Go       string // name used in Go referring to package C
	Mangle   string // name used in generated Go
	C        string // name used in C
	Define   string // #define expansion
	Kind     string // "const", "type", "var", "func", "not-type"
	Type     *Type  // the type of xxx
	FuncType *FuncType
	AddError bool
	Const    string // constant definition
}

// A ExpFunc is an exported function, callable from C.
// Such functions are identified in the Go input file
// by doc comments containing the line //export ExpName
type ExpFunc struct {
	Func    *ast.FuncDecl
	ExpName string // name to use from C
}

// A TypeRepr contains the string representation of a type.
type TypeRepr struct {
	Repr       string
	FormatArgs []interface{}
}

// A Type collects information about a type in both the C and Go worlds.
type Type struct {
	Size       int64
	Align      int64
	C          *TypeRepr
	Go         ast.Expr
	EnumValues map[string]int64
}

// A FuncType collects information about a function type in both the C and Go worlds.
type FuncType struct {
	Params []*Type
	Result *Type
	Go     *ast.FuncType
}

func usage() {
	fmt.Fprint(os.Stderr, "usage: cgo -- [compiler options] file.go ...\n")
	flag.PrintDefaults()
	os.Exit(2)
}

var ptrSizeMap = map[string]int64{
	"386":   4,
	"amd64": 8,
	"arm":   4,
}

var cPrefix string

var fset = token.NewFileSet()

var dynobj = flag.String("dynimport", "", "if non-empty, print dynamic import data for that file")
var dynout = flag.String("dynout", "", "write -dynobj output to this file")

// These flags are for bootstrapping a new Go implementation,
// to generate Go and C headers that match the data layout and
// constant values used in the host's C libraries and system calls.
var godefs = flag.Bool("godefs", false, "for bootstrap: write Go definitions for C file to standard output")
var cdefs = flag.Bool("cdefs", false, "for bootstrap: write C definitions for C file to standard output")
var objDir = flag.String("objdir", "", "object directory")

var gccgo = flag.Bool("gccgo", false, "generate files for use with gccgo")
var importRuntimeCgo = flag.Bool("import_runtime_cgo", true, "import runtime/cgo in generated code")
var goarch, goos string

func main() {
	flag.Usage = usage
	flag.Parse()

	if *dynobj != "" {
		// cgo -dynimport is essentially a separate helper command
		// built into the cgo binary.  It scans a gcc-produced executable
		// and dumps information about the imported symbols and the
		// imported libraries.  The Make.pkg rules for cgo prepare an
		// appropriate executable and then use its import information
		// instead of needing to make the linkers duplicate all the
		// specialized knowledge gcc has about where to look for imported
		// symbols and which ones to use.
		dynimport(*dynobj)
		return
	}

	if *godefs && *cdefs {
		fmt.Fprintf(os.Stderr, "cgo: cannot use -cdefs and -godefs together\n")
		os.Exit(2)
	}

	if *godefs || *cdefs {
		// Generating definitions pulled from header files,
		// to be checked into Go repositories.
		// Line numbers are just noise.
		conf.Mode &^= printer.SourcePos
	}

	args := flag.Args()
	if len(args) < 1 {
		usage()
	}

	// Find first arg that looks like a go file and assume everything before
	// that are options to pass to gcc.
	var i int
	for i = len(args); i > 0; i-- {
		if !strings.HasSuffix(args[i-1], ".go") {
			break
		}
	}
	if i == len(args) {
		usage()
	}

	goFiles := args[i:]

	p := newPackage(args[:i])

	// Need a unique prefix for the global C symbols that
	// we use to coordinate between gcc and ourselves.
	// We already put _cgo_ at the beginning, so the main
	// concern is other cgo wrappers for the same functions.
	// Use the beginning of the md5 of the input to disambiguate.
	h := md5.New()
	for _, input := range goFiles {
		f, err := os.Open(input)
		if err != nil {
			fatalf("%s", err)
		}
		io.Copy(h, f)
		f.Close()
	}
	cPrefix = fmt.Sprintf("_%x", h.Sum(nil)[0:6])

	fs := make([]*File, len(goFiles))
	for i, input := range goFiles {
		// Parse flags for all files before translating due to CFLAGS.
		f := new(File)
		f.ReadGo(input)
		p.ParseFlags(f, input)
		fs[i] = f
	}

	if *objDir == "" {
		// make sure that _obj directory exists, so that we can write
		// all the output files there.
		os.Mkdir("_obj", 0777)
		*objDir = "_obj"
	}
	*objDir += string(filepath.Separator)

	for i, input := range goFiles {
		f := fs[i]
		p.Translate(f)
		for _, cref := range f.Ref {
			switch cref.Context {
			case "call", "call2":
				if cref.Name.Kind != "type" {
					break
				}
				*cref.Expr = cref.Name.Type.Go
			}
		}
		if nerrors > 0 {
			os.Exit(2)
		}
		pkg := f.Package
		if dir := os.Getenv("CGOPKGPATH"); dir != "" {
			pkg = filepath.Join(dir, pkg)
		}
		p.PackagePath = pkg
		p.Record(f)
		if *godefs {
			os.Stdout.WriteString(p.godefs(f, input))
		} else if *cdefs {
			os.Stdout.WriteString(p.cdefs(f, input))
		} else {
			p.writeOutput(f, input)
		}
	}

	if !*godefs && !*cdefs {
		p.writeDefs()
	}
	if nerrors > 0 {
		os.Exit(2)
	}
}

// newPackage returns a new Package that will invoke
// gcc with the additional arguments specified in args.
func newPackage(args []string) *Package {
	// Copy the gcc options to a new slice so the list
	// can grow without overwriting the slice that args is in.
	gccOptions := make([]string, len(args))
	copy(gccOptions, args)

	goarch = runtime.GOARCH
	if s := os.Getenv("GOARCH"); s != "" {
		goarch = s
	}
	goos = runtime.GOOS
	if s := os.Getenv("GOOS"); s != "" {
		goos = s
	}
	ptrSize := ptrSizeMap[goarch]
	if ptrSize == 0 {
		fatalf("unknown $GOARCH %q", goarch)
	}

	// Reset locale variables so gcc emits English errors [sic].
	os.Setenv("LANG", "en_US.UTF-8")
	os.Setenv("LC_ALL", "C")

	p := &Package{
		PtrSize:    ptrSize,
		GccOptions: gccOptions,
		CgoFlags:   make(map[string]string),
		Written:    make(map[string]bool),
	}
	return p
}

// Record what needs to be recorded about f.
func (p *Package) Record(f *File) {
	if p.PackageName == "" {
		p.PackageName = f.Package
	} else if p.PackageName != f.Package {
		error_(token.NoPos, "inconsistent package names: %s, %s", p.PackageName, f.Package)
	}

	if p.Name == nil {
		p.Name = f.Name
	} else {
		for k, v := range f.Name {
			if p.Name[k] == nil {
				p.Name[k] = v
			} else if !reflect.DeepEqual(p.Name[k], v) {
				error_(token.NoPos, "inconsistent definitions for C.%s", k)
			}
		}
	}

	p.ExpFunc = append(p.ExpFunc, f.ExpFunc...)
	p.Decl = append(p.Decl, f.AST.Decls...)
}
<|endoftext|>


<commit_before>package models

import (
	"time"

	envmanModels "github.com/bitrise-io/envman/models"
)

// StepSourceModel ...
type StepSourceModel struct {
	Git    string `json:"git,omitempty" yaml:"git,omitempty"`
	Commit string `json:"commit,omitempty" yaml:"commit,omitempty"`
}

// DependencyModel ...
type DependencyModel struct {
	Manager string `json:"manager,omitempty" yaml:"manager,omitempty"`
	Name    string `json:"name,omitempty" yaml:"name,omitempty"`
}

// StepModel ...0
type StepModel struct {
	Title               *string           `json:"title,omitempty" yaml:"title,omitempty"`
	Description         *string           `json:"description,omitempty" yaml:"description,omitempty"`
	Summary             *string           `json:"summary,omitempty" yaml:"summary,omitempty"`
	Website             *string           `json:"website,omitempty" yaml:"website,omitempty"`
	SourceCodeURL       *string           `json:"source_code_url,omitempty" yaml:"source_code_url,omitempty"`
	SupportURL          *string           `json:"support_url,omitempty" yaml:"support_url,omitempty"`
	PublishedAt         *time.Time        `json:"published_at,omitempty" yaml:"published_at,omitempty"`
	Source              StepSourceModel   `json:"source,omitempty" yaml:"source,omitempty"`
	HostOsTags          []string          `json:"host_os_tags,omitempty" yaml:"host_os_tags,omitempty"`
	ProjectTypeTags     []string          `json:"project_type_tags,omitempty" yaml:"project_type_tags,omitempty"`
	TypeTags            []string          `json:"type_tags,omitempty" yaml:"type_tags,omitempty"`
	Dependencies        []DependencyModel `json:"dependencies,omitempty" yaml:"dependencies,omitempty"`
	IsRequiresAdminUser *bool             `json:"is_requires_admin_user,omitempty" yaml:"is_requires_admin_user,omitempty"`
	// IsAlwaysRun : if true then this step will always run,
	//  even if a previous step fails.
	IsAlwaysRun *bool `json:"is_always_run,omitempty" yaml:"is_always_run,omitempty"`
	// IsSkippable : if true and this step fails the build will still continue.
	//  If false then the build will be marked as failed and only those
	//  steps will run which are marked with IsAlwaysRun.
	IsSkippable *bool `json:"is_skippable,omitempty" yaml:"is_skippable,omitempty"`
	// RunIf : only run the step if the template example evaluates to true
	RunIf   *string                             `json:"run_if,omitempty" yaml:"run_if,omitempty"`
	Inputs  []envmanModels.EnvironmentItemModel `json:"inputs,omitempty" yaml:"inputs,omitempty"`
	Outputs []envmanModels.EnvironmentItemModel `json:"outputs,omitempty" yaml:"outputs,omitempty"`
}

// StepGroupModel ...
type StepGroupModel struct {
	Versions            map[string]StepModel `json:"versions"`
	LatestVersionNumber string               `json:"latest_version_number"`
}

// StepHash ...
type StepHash map[string]StepGroupModel

// DownloadLocationModel ...
type DownloadLocationModel struct {
	Type string `json:"type"`
	Src  string `json:"src"`
}

// StepCollectionModel ...
type StepCollectionModel struct {
	FormatVersion        string                  `json:"format_version" yaml:"format_version"`
	GeneratedAtTimeStamp int64                   `json:"generated_at_timestamp" yaml:"generated_at_timestamp"`
	Steps                StepHash                `json:"steps" yaml:"steps"`
	SteplibSource        string                  `json:"steplib_source" yaml:"steplib_source"`
	DownloadLocations    []DownloadLocationModel `json:"download_locations" yaml:"download_locations"`
}
<commit_msg>StepModel: order change, summary is now before description & a bit of commenting<commit_after>package models

import (
	"time"

	envmanModels "github.com/bitrise-io/envman/models"
)

// StepSourceModel ...
type StepSourceModel struct {
	Git    string `json:"git,omitempty" yaml:"git,omitempty"`
	Commit string `json:"commit,omitempty" yaml:"commit,omitempty"`
}

// DependencyModel ...
type DependencyModel struct {
	Manager string `json:"manager,omitempty" yaml:"manager,omitempty"`
	Name    string `json:"name,omitempty" yaml:"name,omitempty"`
}

// StepModel ...0
type StepModel struct {
	Title       *string `json:"title,omitempty" yaml:"title,omitempty"`
	Summary     *string `json:"summary,omitempty" yaml:"summary,omitempty"`
	Description *string `json:"description,omitempty" yaml:"description,omitempty"`
	//
	Website       *string `json:"website,omitempty" yaml:"website,omitempty"`
	SourceCodeURL *string `json:"source_code_url,omitempty" yaml:"source_code_url,omitempty"`
	SupportURL    *string `json:"support_url,omitempty" yaml:"support_url,omitempty"`
	// auto-generated at share
	PublishedAt *time.Time      `json:"published_at,omitempty" yaml:"published_at,omitempty"`
	Source      StepSourceModel `json:"source,omitempty" yaml:"source,omitempty"`
	//
	HostOsTags          []string          `json:"host_os_tags,omitempty" yaml:"host_os_tags,omitempty"`
	ProjectTypeTags     []string          `json:"project_type_tags,omitempty" yaml:"project_type_tags,omitempty"`
	TypeTags            []string          `json:"type_tags,omitempty" yaml:"type_tags,omitempty"`
	Dependencies        []DependencyModel `json:"dependencies,omitempty" yaml:"dependencies,omitempty"`
	IsRequiresAdminUser *bool             `json:"is_requires_admin_user,omitempty" yaml:"is_requires_admin_user,omitempty"`
	// IsAlwaysRun : if true then this step will always run,
	//  even if a previous step fails.
	IsAlwaysRun *bool `json:"is_always_run,omitempty" yaml:"is_always_run,omitempty"`
	// IsSkippable : if true and this step fails the build will still continue.
	//  If false then the build will be marked as failed and only those
	//  steps will run which are marked with IsAlwaysRun.
	IsSkippable *bool `json:"is_skippable,omitempty" yaml:"is_skippable,omitempty"`
	// RunIf : only run the step if the template example evaluates to true
	RunIf *string `json:"run_if,omitempty" yaml:"run_if,omitempty"`
	//
	Inputs  []envmanModels.EnvironmentItemModel `json:"inputs,omitempty" yaml:"inputs,omitempty"`
	Outputs []envmanModels.EnvironmentItemModel `json:"outputs,omitempty" yaml:"outputs,omitempty"`
}

// StepGroupModel ...
type StepGroupModel struct {
	Versions            map[string]StepModel `json:"versions"`
	LatestVersionNumber string               `json:"latest_version_number"`
}

// StepHash ...
type StepHash map[string]StepGroupModel

// DownloadLocationModel ...
type DownloadLocationModel struct {
	Type string `json:"type"`
	Src  string `json:"src"`
}

// StepCollectionModel ...
type StepCollectionModel struct {
	FormatVersion        string                  `json:"format_version" yaml:"format_version"`
	GeneratedAtTimeStamp int64                   `json:"generated_at_timestamp" yaml:"generated_at_timestamp"`
	Steps                StepHash                `json:"steps" yaml:"steps"`
	SteplibSource        string                  `json:"steplib_source" yaml:"steplib_source"`
	DownloadLocations    []DownloadLocationModel `json:"download_locations" yaml:"download_locations"`
}
<|endoftext|>


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

package models

import (
	"fmt"

	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/util"
	"github.com/go-xorm/xorm"

	"github.com/go-xorm/builder"
)

// ReviewType defines the sort of feedback a review gives
type ReviewType int

// ReviewTypeUnknown unknown review type
const ReviewTypeUnknown ReviewType = -1

const (
	// ReviewTypePending is a review which is not published yet
	ReviewTypePending ReviewType = iota
	// ReviewTypeApprove approves changes
	ReviewTypeApprove
	// ReviewTypeComment gives general feedback
	ReviewTypeComment
	// ReviewTypeReject gives feedback blocking merge
	ReviewTypeReject
)

// Icon returns the corresponding icon for the review type
func (rt ReviewType) Icon() string {
	switch rt {
	case ReviewTypeApprove:
		return "eye"
	case ReviewTypeReject:
		return "x"
	default:
	case ReviewTypeComment:
	case ReviewTypeUnknown:
		return "comment"
	}
	return "comment"
}

// Review represents collection of code comments giving feedback for a PR
type Review struct {
	ID         int64 `xorm:"pk autoincr"`
	Type       ReviewType
	Reviewer   *User  `xorm:"-"`
	ReviewerID int64  `xorm:"index"`
	Issue      *Issue `xorm:"-"`
	IssueID    int64  `xorm:"index"`
	Content    string

	CreatedUnix util.TimeStamp `xorm:"INDEX created"`
	UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`

	// CodeComments are the initial code comments of the review
	CodeComments CodeComments `xorm:"-"`
}

func (r *Review) loadCodeComments(e Engine) (err error) {
	r.CodeComments, err = fetchCodeCommentsByReview(e, r.Issue, nil, r)
	return
}

// LoadCodeComments loads CodeComments
func (r *Review) LoadCodeComments() error {
	return r.loadCodeComments(x)
}

func (r *Review) loadIssue(e Engine) (err error) {
	r.Issue, err = getIssueByID(e, r.IssueID)
	return
}

func (r *Review) loadReviewer(e Engine) (err error) {
	if r.ReviewerID == 0 {
		return nil
	}
	r.Reviewer, err = getUserByID(e, r.ReviewerID)
	return
}

func (r *Review) loadAttributes(e Engine) (err error) {
	if err = r.loadReviewer(e); err != nil {
		return
	}
	if err = r.loadIssue(e); err != nil {
		return
	}
	return
}

// LoadAttributes loads all attributes except CodeComments
func (r *Review) LoadAttributes() error {
	return r.loadAttributes(x)
}

// Publish will send notifications / actions to participants for all code comments; parts are concurrent
func (r *Review) Publish() error {
	return r.publish(x)
}

func (r *Review) publish(e *xorm.Engine) error {
	if r.Type == ReviewTypePending || r.Type == ReviewTypeUnknown {
		return fmt.Errorf("review cannot be published if type is pending or unknown")
	}
	if r.Issue == nil {
		if err := r.loadIssue(e); err != nil {
			return err
		}
	}
	if err := r.Issue.loadRepo(e); err != nil {
		return err
	}
	if len(r.CodeComments) == 0 {
		if err := r.loadCodeComments(e); err != nil {
			return err
		}
	}
	for _, lines := range r.CodeComments {
		for _, comments := range lines {
			for _, comment := range comments {
				go func(en *xorm.Engine, review *Review, comm *Comment) {
					sess := en.NewSession()
					defer sess.Close()
					if err := sendCreateCommentAction(sess, &CreateCommentOptions{
						Doer:    comm.Poster,
						Issue:   review.Issue,
						Repo:    review.Issue.Repo,
						Type:    comm.Type,
						Content: comm.Content,
					}, comm); err != nil {
						log.Warn("sendCreateCommentAction: %v", err)
					}
				}(e, r, comment)
			}
		}
	}
	return nil
}

func getReviewByID(e Engine, id int64) (*Review, error) {
	review := new(Review)
	if has, err := e.ID(id).Get(review); err != nil {
		return nil, err
	} else if !has {
		return nil, ErrReviewNotExist{ID: id}
	} else {
		return review, nil
	}
}

// GetReviewByID returns the review by the given ID
func GetReviewByID(id int64) (*Review, error) {
	return getReviewByID(x, id)
}

// FindReviewOptions represent possible filters to find reviews
type FindReviewOptions struct {
	Type       ReviewType
	IssueID    int64
	ReviewerID int64
}

func (opts *FindReviewOptions) toCond() builder.Cond {
	var cond = builder.NewCond()
	if opts.IssueID > 0 {
		cond = cond.And(builder.Eq{"issue_id": opts.IssueID})
	}
	if opts.ReviewerID > 0 {
		cond = cond.And(builder.Eq{"reviewer_id": opts.ReviewerID})
	}
	if opts.Type != ReviewTypeUnknown {
		cond = cond.And(builder.Eq{"type": opts.Type})
	}
	return cond
}

func findReviews(e Engine, opts FindReviewOptions) ([]*Review, error) {
	reviews := make([]*Review, 0, 10)
	sess := e.Where(opts.toCond())
	return reviews, sess.
		Asc("created_unix").
		Asc("id").
		Find(&reviews)
}

// FindReviews returns reviews passing FindReviewOptions
func FindReviews(opts FindReviewOptions) ([]*Review, error) {
	return findReviews(x, opts)
}

// CreateReviewOptions represent the options to create a review. Type, Issue and Reviewer are required.
type CreateReviewOptions struct {
	Content  string
	Type     ReviewType
	Issue    *Issue
	Reviewer *User
}

func createReview(e Engine, opts CreateReviewOptions) (*Review, error) {
	review := &Review{
		Type:       opts.Type,
		Issue:      opts.Issue,
		IssueID:    opts.Issue.ID,
		Reviewer:   opts.Reviewer,
		ReviewerID: opts.Reviewer.ID,
		Content:    opts.Content,
	}
	if _, err := e.Insert(review); err != nil {
		return nil, err
	}
	return review, nil
}

// CreateReview creates a new review based on opts
func CreateReview(opts CreateReviewOptions) (*Review, error) {
	return createReview(x, opts)
}

func getCurrentReview(e Engine, reviewer *User, issue *Issue) (*Review, error) {
	if reviewer == nil {
		return nil, nil
	}
	reviews, err := findReviews(e, FindReviewOptions{
		Type:       ReviewTypePending,
		IssueID:    issue.ID,
		ReviewerID: reviewer.ID,
	})
	if err != nil {
		return nil, err
	}
	if len(reviews) == 0 {
		return nil, ErrReviewNotExist{}
	}
	reviews[0].Reviewer = reviewer
	reviews[0].Issue = issue
	return reviews[0], nil
}

// GetCurrentReview returns the current pending review of reviewer for given issue
func GetCurrentReview(reviewer *User, issue *Issue) (*Review, error) {
	return getCurrentReview(x, reviewer, issue)
}

// UpdateReview will update all cols of the given review in db
func UpdateReview(r *Review) error {
	if _, err := x.ID(r.ID).AllCols().Update(r); err != nil {
		return err
	}
	return nil
}
<commit_msg>Make switch more clear (#5119)<commit_after>// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package models

import (
	"fmt"

	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/util"
	"github.com/go-xorm/xorm"

	"github.com/go-xorm/builder"
)

// ReviewType defines the sort of feedback a review gives
type ReviewType int

// ReviewTypeUnknown unknown review type
const ReviewTypeUnknown ReviewType = -1

const (
	// ReviewTypePending is a review which is not published yet
	ReviewTypePending ReviewType = iota
	// ReviewTypeApprove approves changes
	ReviewTypeApprove
	// ReviewTypeComment gives general feedback
	ReviewTypeComment
	// ReviewTypeReject gives feedback blocking merge
	ReviewTypeReject
)

// Icon returns the corresponding icon for the review type
func (rt ReviewType) Icon() string {
	switch rt {
	case ReviewTypeApprove:
		return "eye"
	case ReviewTypeReject:
		return "x"
	case ReviewTypeComment, ReviewTypeUnknown:
		return "comment"
	default:
		return "comment"
	}
}

// Review represents collection of code comments giving feedback for a PR
type Review struct {
	ID         int64 `xorm:"pk autoincr"`
	Type       ReviewType
	Reviewer   *User  `xorm:"-"`
	ReviewerID int64  `xorm:"index"`
	Issue      *Issue `xorm:"-"`
	IssueID    int64  `xorm:"index"`
	Content    string

	CreatedUnix util.TimeStamp `xorm:"INDEX created"`
	UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`

	// CodeComments are the initial code comments of the review
	CodeComments CodeComments `xorm:"-"`
}

func (r *Review) loadCodeComments(e Engine) (err error) {
	r.CodeComments, err = fetchCodeCommentsByReview(e, r.Issue, nil, r)
	return
}

// LoadCodeComments loads CodeComments
func (r *Review) LoadCodeComments() error {
	return r.loadCodeComments(x)
}

func (r *Review) loadIssue(e Engine) (err error) {
	r.Issue, err = getIssueByID(e, r.IssueID)
	return
}

func (r *Review) loadReviewer(e Engine) (err error) {
	if r.ReviewerID == 0 {
		return nil
	}
	r.Reviewer, err = getUserByID(e, r.ReviewerID)
	return
}

func (r *Review) loadAttributes(e Engine) (err error) {
	if err = r.loadReviewer(e); err != nil {
		return
	}
	if err = r.loadIssue(e); err != nil {
		return
	}
	return
}

// LoadAttributes loads all attributes except CodeComments
func (r *Review) LoadAttributes() error {
	return r.loadAttributes(x)
}

// Publish will send notifications / actions to participants for all code comments; parts are concurrent
func (r *Review) Publish() error {
	return r.publish(x)
}

func (r *Review) publish(e *xorm.Engine) error {
	if r.Type == ReviewTypePending || r.Type == ReviewTypeUnknown {
		return fmt.Errorf("review cannot be published if type is pending or unknown")
	}
	if r.Issue == nil {
		if err := r.loadIssue(e); err != nil {
			return err
		}
	}
	if err := r.Issue.loadRepo(e); err != nil {
		return err
	}
	if len(r.CodeComments) == 0 {
		if err := r.loadCodeComments(e); err != nil {
			return err
		}
	}
	for _, lines := range r.CodeComments {
		for _, comments := range lines {
			for _, comment := range comments {
				go func(en *xorm.Engine, review *Review, comm *Comment) {
					sess := en.NewSession()
					defer sess.Close()
					if err := sendCreateCommentAction(sess, &CreateCommentOptions{
						Doer:    comm.Poster,
						Issue:   review.Issue,
						Repo:    review.Issue.Repo,
						Type:    comm.Type,
						Content: comm.Content,
					}, comm); err != nil {
						log.Warn("sendCreateCommentAction: %v", err)
					}
				}(e, r, comment)
			}
		}
	}
	return nil
}

func getReviewByID(e Engine, id int64) (*Review, error) {
	review := new(Review)
	if has, err := e.ID(id).Get(review); err != nil {
		return nil, err
	} else if !has {
		return nil, ErrReviewNotExist{ID: id}
	} else {
		return review, nil
	}
}

// GetReviewByID returns the review by the given ID
func GetReviewByID(id int64) (*Review, error) {
	return getReviewByID(x, id)
}

// FindReviewOptions represent possible filters to find reviews
type FindReviewOptions struct {
	Type       ReviewType
	IssueID    int64
	ReviewerID int64
}

func (opts *FindReviewOptions) toCond() builder.Cond {
	var cond = builder.NewCond()
	if opts.IssueID > 0 {
		cond = cond.And(builder.Eq{"issue_id": opts.IssueID})
	}
	if opts.ReviewerID > 0 {
		cond = cond.And(builder.Eq{"reviewer_id": opts.ReviewerID})
	}
	if opts.Type != ReviewTypeUnknown {
		cond = cond.And(builder.Eq{"type": opts.Type})
	}
	return cond
}

func findReviews(e Engine, opts FindReviewOptions) ([]*Review, error) {
	reviews := make([]*Review, 0, 10)
	sess := e.Where(opts.toCond())
	return reviews, sess.
		Asc("created_unix").
		Asc("id").
		Find(&reviews)
}

// FindReviews returns reviews passing FindReviewOptions
func FindReviews(opts FindReviewOptions) ([]*Review, error) {
	return findReviews(x, opts)
}

// CreateReviewOptions represent the options to create a review. Type, Issue and Reviewer are required.
type CreateReviewOptions struct {
	Content  string
	Type     ReviewType
	Issue    *Issue
	Reviewer *User
}

func createReview(e Engine, opts CreateReviewOptions) (*Review, error) {
	review := &Review{
		Type:       opts.Type,
		Issue:      opts.Issue,
		IssueID:    opts.Issue.ID,
		Reviewer:   opts.Reviewer,
		ReviewerID: opts.Reviewer.ID,
		Content:    opts.Content,
	}
	if _, err := e.Insert(review); err != nil {
		return nil, err
	}
	return review, nil
}

// CreateReview creates a new review based on opts
func CreateReview(opts CreateReviewOptions) (*Review, error) {
	return createReview(x, opts)
}

func getCurrentReview(e Engine, reviewer *User, issue *Issue) (*Review, error) {
	if reviewer == nil {
		return nil, nil
	}
	reviews, err := findReviews(e, FindReviewOptions{
		Type:       ReviewTypePending,
		IssueID:    issue.ID,
		ReviewerID: reviewer.ID,
	})
	if err != nil {
		return nil, err
	}
	if len(reviews) == 0 {
		return nil, ErrReviewNotExist{}
	}
	reviews[0].Reviewer = reviewer
	reviews[0].Issue = issue
	return reviews[0], nil
}

// GetCurrentReview returns the current pending review of reviewer for given issue
func GetCurrentReview(reviewer *User, issue *Issue) (*Review, error) {
	return getCurrentReview(x, reviewer, issue)
}

// UpdateReview will update all cols of the given review in db
func UpdateReview(r *Review) error {
	if _, err := x.ID(r.ID).AllCols().Update(r); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package rest

import (
	"net/http"
)

// Insert creates a http handler that will create a document in mongodb.
// It takes a collection name and type struct of whitelisted fields
func (service *Service) Insert(name string, model Model) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		event := name + "_insert_one"
		// Track how long this function take to return
		stop := service.NewTimer(event)
		defer stop()
		// HTTP response status code
		var status int
		// HTTP response body
		var body []byte
		// Validate what came through the wire
		err := model.Validate(r)
		if err == nil {
			body, err := model.Create(r)
			if err == nil {
				// Allow event broker to be optional
				if service.broker != nil {
					err = service.broker.Publish(event, body)
				}
				if err == nil {
					status = http.StatusCreated
					// If a metrics client is defined use it
					if service.metrics != nil {
						service.metrics.Incr(event, 1)
					}
				} else {
					// Something wicked happened while publishing to the event stream
					service.logger.Error(err)
					status, body = InternalServerErrorResponse()
				}
			} else {
				// Something wicked happened while persisting document
				service.logger.Error(err)
				status, body = InternalServerErrorResponse()
			}
		} else {
			service.logger.Error(err)
			status, body = BadRequestResponse()
		}
		w.WriteHeader(status)
		w.Write(body)
	}
}

// Find creates a http handler that will list documents in mongodb
// It takes a collection name and type struct of fields to return
func (service *Service) Find(name string, model Model) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		event := name + ".find.many"
		// HTTP response status code
		var status int
		// HTTP response body
		var body []byte
		// Track how long this function take to return
		stop := service.NewTimer(event)
		defer stop()
		err := model.Validate(r)
		if err == nil {
			body, err = model.Find(r)
			if err == nil {
				// Notify other services, if an event broker exists
				if service.broker != nil {
					err = service.broker.Publish(event, body)
				}
				if err == nil {
					status = http.StatusOK
					if service.metrics != nil {
						service.metrics.Incr(event, 1)
					}
				} else {
					service.logger.Error(err)
					status, body = InternalServerErrorResponse()
				}
			} else {
				// Something wicked happened while fetching document/s
				service.logger.Error(err)
				status, body = InternalServerErrorResponse()
			}
		} else {
			service.logger.Error(err)
			status, body = BadRequestResponse()
		}
		w.WriteHeader(status)
		w.Write(body)
	}
}

// Delete creates a http handler that will delete a document by id in mongodb
// It takes a collection name
func (service *Service) Delete(name string, model Model) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		event := name + "_delete_one"
		// Track how long this function take to return
		stop := service.NewTimer(event)
		defer stop()
		// HTTP response status code
		var status int
		// HTTP response body
		var body []byte
		err := model.Validate(r)
		if err == nil {
			err := model.Delete(r)
			if err == nil {
				// Notify other services, if an event broker exists
				if service.broker != nil {
					err = service.broker.Publish(event, body)
				}
				if err == nil {
					status, body = NoContentResponse()
					// If a metrics client is defined use it
					if service.metrics != nil {
						service.metrics.Incr(event, 1)
					}
				} else {
					status, body = InternalServerErrorResponse()
					service.logger.Error(err)
				}
			} else {
				status, body = InternalServerErrorResponse()
				service.logger.Error(err)
			}
		} else {
			status, body = BadRequestResponse()
			service.logger.Error(err)
		}
		w.WriteHeader(status)
		w.Write(body)
	}
}

// Update creates a http handler that will updates a document by id in mongodb
// It takes a collection name and type struct of whitelisted fields
func (service *Service) Update(name string, model Model) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		event := name + "_update_one"
		// Track how long this function take to return
		stop := service.NewTimer(event)
		defer stop()
		// HTTP response status code
		var status int
		// HTTP response body
		var body []byte
		err := model.Validate(r)
		if err == nil {
			body, err := model.Update(r)
			if err == nil {
				// Notify other services, if an event broker exists
				if service.broker != nil {
					err = service.broker.Publish(event, body)
				}
				if err == nil {
					status, body = NoContentResponse()
					// If a metrics client is defined use it
					if service.metrics != nil {
						service.metrics.Incr(event, 1)
					}
				} else {
					//Something wicked happened while trying to publish to the event strea,
					status, body = InternalServerErrorResponse()
					service.logger.Error(err)
				}
			} else {
				//Something wicked happened while persisting the update
				status, body = InternalServerErrorResponse()
				service.logger.Error(err)
			}

		} else {
			//The request failed validation rules in the model
			status, body = BadRequestResponse()
			service.logger.Error(err)
		}
		w.WriteHeader(status)
		w.Write(body)
	}
}

// NewTimer creates a stop timer to track the performance of a function
func (service *Service) NewTimer(stat string) func() {
	// Allow metrics to be optional
	if service.metrics == nil {
		return func() {}
	}
	return service.metrics.NewTimer(stat)
}
<commit_msg>Fixed bug, ineffectual assignment<commit_after>package rest

import (
	"net/http"
)

// Insert creates a http handler that will create a document in mongodb.
// It takes a collection name and type struct of whitelisted fields
func (service *Service) Insert(name string, model Model) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		event := name + "_insert_one"
		// Track how long this function take to return
		stop := service.NewTimer(event)
		defer stop()
		// HTTP response status code
		var status int
		// HTTP response body
		var body []byte
		// Validate what came through the wire
		err := model.Validate(r)
		if err == nil {
			body, err = model.Create(r)
			if err == nil {
				// Allow event broker to be optional
				if service.broker != nil {
					err = service.broker.Publish(event, body)
				}
				if err == nil {
					status = http.StatusCreated
					// If a metrics client is defined use it
					if service.metrics != nil {
						service.metrics.Incr(event, 1)
					}
				} else {
					// Something wicked happened while publishing to the event stream
					service.logger.Error(err)
					status, body = InternalServerErrorResponse()
				}
			} else {
				// Something wicked happened while persisting document
				service.logger.Error(err)
				status, body = InternalServerErrorResponse()
			}
		} else {
			service.logger.Error(err)
			status, body = BadRequestResponse()
		}
		w.WriteHeader(status)
		w.Write(body)
	}
}

// Find creates a http handler that will list documents in mongodb
// It takes a collection name and type struct of fields to return
func (service *Service) Find(name string, model Model) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		event := name + ".find.many"
		// HTTP response status code
		var status int
		// HTTP response body
		var body []byte
		// Track how long this function take to return
		stop := service.NewTimer(event)
		defer stop()
		err := model.Validate(r)
		if err == nil {
			body, err = model.Find(r)
			if err == nil {
				// Notify other services, if an event broker exists
				if service.broker != nil {
					err = service.broker.Publish(event, body)
				}
				if err == nil {
					status = http.StatusOK
					if service.metrics != nil {
						service.metrics.Incr(event, 1)
					}
				} else {
					service.logger.Error(err)
					status, body = InternalServerErrorResponse()
				}
			} else {
				// Something wicked happened while fetching document/s
				service.logger.Error(err)
				status, body = InternalServerErrorResponse()
			}
		} else {
			service.logger.Error(err)
			status, body = BadRequestResponse()
		}
		w.WriteHeader(status)
		w.Write(body)
	}
}

// Delete creates a http handler that will delete a document by id in mongodb
// It takes a collection name
func (service *Service) Delete(name string, model Model) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		event := name + "_delete_one"
		// Track how long this function take to return
		stop := service.NewTimer(event)
		defer stop()
		// HTTP response status code
		var status int
		// HTTP response body
		var body []byte
		err := model.Validate(r)
		if err == nil {
			err := model.Delete(r)
			if err == nil {
				// Notify other services, if an event broker exists
				if service.broker != nil {
					err = service.broker.Publish(event, body)
				}
				if err == nil {
					status, body = NoContentResponse()
					// If a metrics client is defined use it
					if service.metrics != nil {
						service.metrics.Incr(event, 1)
					}
				} else {
					status, body = InternalServerErrorResponse()
					service.logger.Error(err)
				}
			} else {
				status, body = InternalServerErrorResponse()
				service.logger.Error(err)
			}
		} else {
			status, body = BadRequestResponse()
			service.logger.Error(err)
		}
		w.WriteHeader(status)
		w.Write(body)
	}
}

// Update creates a http handler that will updates a document by id in mongodb
// It takes a collection name and type struct of whitelisted fields
func (service *Service) Update(name string, model Model) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		event := name + "_update_one"
		// Track how long this function take to return
		stop := service.NewTimer(event)
		defer stop()
		// HTTP response status code
		var status int
		// HTTP response body
		var body []byte
		err := model.Validate(r)
		if err == nil {
			body, err = model.Update(r)
			if err == nil {
				// Notify other services, if an event broker exists
				if service.broker != nil {
					err = service.broker.Publish(event, body)
				}
				if err == nil {
					status, body = NoContentResponse()
					// If a metrics client is defined use it
					if service.metrics != nil {
						service.metrics.Incr(event, 1)
					}
				} else {
					//Something wicked happened while trying to publish to the event strea,
					status, body = InternalServerErrorResponse()
					service.logger.Error(err)
				}
			} else {
				//Something wicked happened while persisting the update
				status, body = InternalServerErrorResponse()
				service.logger.Error(err)
			}

		} else {
			//The request failed validation rules in the model
			status, body = BadRequestResponse()
			service.logger.Error(err)
		}
		w.WriteHeader(status)
		w.Write(body)
	}
}

// NewTimer creates a stop timer to track the performance of a function
func (service *Service) NewTimer(stat string) func() {
	// Allow metrics to be optional
	if service.metrics == nil {
		return func() {}
	}
	return service.metrics.NewTimer(stat)
}
<|endoftext|>


<commit_before>package main;

import (
	"github.com/labstack/echo"
)

// Interface for REST handlers
type Handlers interface {
	Browse(c echo.Context) (err error)
	Read(c echo.Context) (err error)
	Edit(c echo.Context) (err error)
	Add(c echo.Context) (err error)
	Destroy(c echo.Context) (err error)
	Wipe(c echo.Context) (err error)
}

// Registers handler methods to Echo instance
func Register(e *echo.Echo, h Handlers, path string) {
  e.GET   (path         , h.Browse )
  e.GET   (path + "/:pk", h.Read   )
  e.PATCH (path + "/:pk", h.Edit   )
  e.POST  (path         , h.Add    )
  e.DELETE(path + "/:pk", h.Destroy)
  e.DELETE(path         , h.Wipe   )
  e.PUT   (path + "/:pk", h.Edit   )
}
<commit_msg>Fix documentation strings<commit_after>package main;

import (
	"github.com/labstack/echo"
)

// Handlers is an interface for REST handlers
type Handlers interface {
	Browse(c echo.Context) (err error)
	Read(c echo.Context) (err error)
	Edit(c echo.Context) (err error)
	Add(c echo.Context) (err error)
	Destroy(c echo.Context) (err error)
	Wipe(c echo.Context) (err error)
}

// Register registers Handlers methods to an Echo instance
func Register(e *echo.Echo, h Handlers, path string) {
  e.GET   (path         , h.Browse )
  e.GET   (path + "/:pk", h.Read   )
  e.PATCH (path + "/:pk", h.Edit   )
  e.POST  (path         , h.Add    )
  e.DELETE(path + "/:pk", h.Destroy)
  e.DELETE(path         , h.Wipe   )
  e.PUT   (path + "/:pk", h.Edit   )
}
<|endoftext|>


<commit_before>package zip

import (
	. "jvmgo/any"
	"jvmgo/jvm/rtda"
	rtc "jvmgo/jvm/rtda/class"
)

func init() {
	_zf(initIDs, "initIDs", "()V")
	_zf(open, "open", "(Ljava/lang/String;IJZ)J")
	_zf(getTotal, "getTotal", "(J)I")
	_zf(startsWithLOC, "startsWithLOC", "(J)Z")
}

func _zf(method Any, name, desc string) {
	rtc.RegisterNativeMethod("java/util/zip/ZipFile", name, desc, method)
}

// private static native void initIDs();
// ()V
func initIDs(frame *rtda.Frame) {
	// todo
}

// private static native long open(String name, int mode, long lastModified,
//                                 boolean usemmap) throws IOException;
// (Ljava/lang/String;IJZ)J
func open(frame *rtda.Frame) {
	vars := frame.LocalVars()
	nameObj := vars.GetRef(0)

	name := rtda.GoString(nameObj)
	jzfile, err := openZip(name)
	if err != nil {
		// todo
		panic("IOException")
	}

	stack := frame.OperandStack()
	stack.PushLong(jzfile)
}

// private static native int getTotal(long jzfile);
// (J)I
func getTotal(frame *rtda.Frame) {
	vars := frame.LocalVars()
	jzfile := vars.GetLong(0)

	total := getEntryCount(jzfile)

	stack := frame.OperandStack()
	stack.PushInt(total)
}

// private static native boolean startsWithLOC(long jzfile);
// (J)Z
func startsWithLOC(frame *rtda.Frame) {
	// todo
	stack := frame.OperandStack()
	stack.PushBoolean(true)
}
<commit_msg>reorder code<commit_after>package zip

import (
	. "jvmgo/any"
	"jvmgo/jvm/rtda"
	rtc "jvmgo/jvm/rtda/class"
)

func init() {
	_zf(initIDs, "initIDs", "()V")
	_zf(getTotal, "getTotal", "(J)I")
	_zf(open, "open", "(Ljava/lang/String;IJZ)J")
	_zf(startsWithLOC, "startsWithLOC", "(J)Z")
}

func _zf(method Any, name, desc string) {
	rtc.RegisterNativeMethod("java/util/zip/ZipFile", name, desc, method)
}

// private static native void initIDs();
// ()V
func initIDs(frame *rtda.Frame) {
	// todo
}

// private static native long open(String name, int mode, long lastModified,
//                                 boolean usemmap) throws IOException;
// (Ljava/lang/String;IJZ)J
func open(frame *rtda.Frame) {
	vars := frame.LocalVars()
	nameObj := vars.GetRef(0)

	name := rtda.GoString(nameObj)
	jzfile, err := openZip(name)
	if err != nil {
		// todo
		panic("IOException")
	}

	stack := frame.OperandStack()
	stack.PushLong(jzfile)
}

// private static native boolean startsWithLOC(long jzfile);
// (J)Z
func startsWithLOC(frame *rtda.Frame) {
	// todo
	stack := frame.OperandStack()
	stack.PushBoolean(true)
}

// private static native int getTotal(long jzfile);
// (J)I
func getTotal(frame *rtda.Frame) {
	vars := frame.LocalVars()
	jzfile := vars.GetLong(0)

	total := getEntryCount(jzfile)

	stack := frame.OperandStack()
	stack.PushInt(total)
}
<|endoftext|>


<commit_before>/*
Package hashauth provides a means of creating a cookie- and url-friendly token
containing arbitrary encoded information, with an embedded authentication code
that ensures that it was created by you (not forged) and is in its original
form  (not tampered with).

Primary use-cases are login sessions, password reset tokens, and the like. Any
situation where you need to provide to the user a token they can present back
to you which contains a small amount of data and authentication guarantees.

The package provides methods for Encoding, Validating, and Decoding tokens,
and also a higher-level API for interacting with HTTP request and response
cookies for sessions.

Login session example:

	var Signer = hashauth.New([]byte("secret key"), nil)
	const loginCtx = "login"

	type LoginSession struct {
		UserID     int
		Expiration time.Time
	}

	// implementing this method causes hashauth to set the Expires cookie attr
	func (sess *LoginSession) Expires() time.Time {
		return sess.Expiration
	}

	func AuthRequired(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			sess := new(LoginSession)
			err := Signer.Authenticate(loginCtx, r, sess)
			if err != nil {
				http.Error(w, "Login Required", http.StatusForbidden)
			} else if time.Now().UTC().Before(sess.Expiration) {
				// check the expiration, cookie attributes can be tampered with
				http.Error(w, "Login Expired", http.StatusForbidden)
			} else {
				h.ServeHTTP(w, r)
			}
		})
	}

	func LogUserIn(w http.ResponseWriter, uid int) error {
		return Signer.SetCookie(loginCtx, w, &LoginSession{
			UserID:     uid,
			Expiration: time.Now().UTC().Add(7*24*time.Hour),
		})
	}

	func LogUserOut(w http.ResponseWriter) {
		Signer.ClearCookie(w)
	}
*/
package hashauth

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"errors"
	"fmt"
	"hash"
	"io"
	"net/http"
	"reflect"
	"sort"
	"strings"
	"time"

	"github.com/ugorji/go/codec"
)

const padChar = '.'

// ErrInvalid is returned when an authentication check fails
var ErrInvalid = errors.New("token validation failed")

var (
	defaultHasher     = sha1.New
	defaultCookieName = "_ha"
	mh                codec.MsgpackHandle
)

// Options is a simple container for various HashAuth options.
type Options struct {
	Hash           func() hash.Hash
	CookieName     string
	CookiePath     string
	CookieDomain   string
	CookieSecure   bool
	CookieHTTPOnly bool
}

// HashAuth is an Encoder and Decoder of tokens.
type HashAuth struct {
	key            []byte
	hasher         func() hash.Hash
	cookieName     string
	cookiePath     string
	cookieDomain   string
	cookieSecure   bool
	cookieHTTPOnly bool
}

// Expirer can be implemented by session data types, in which case their
// expiration time will be set as the cookie expiration time in
// HashAuth.SetCookie.
type Expirer interface {
	Expires() time.Time
}

// MaxAger can be implemented by session data types, in which case the
// HashAuth.SetCookie method will set a max-age attribute (unless it
// also implements Expirer, which takes precedence).
type MaxAger interface {
	MaxAge() time.Duration
}

// New creates a new HashAuth En/Decoder.
// key should be a carefully guarded secret, with it anyone could forge a token.
// opts can be nil, in which case a sha1 hasher, a default cookie name, and no
// cookie attributes will be used.
func New(key []byte, opts *Options) *HashAuth {
	if opts == nil {
		opts = &Options{}
	}

	hasher := opts.Hash
	if hasher == nil {
		hasher = defaultHasher
	}

	cname := opts.CookieName
	if cname == "" {
		cname = defaultCookieName
	}

	return &HashAuth{
		key:            key,
		hasher:         hasher,
		cookieName:     cname,
		cookiePath:     opts.CookiePath,
		cookieDomain:   opts.CookieDomain,
		cookieSecure:   opts.CookieSecure,
		cookieHTTPOnly: opts.CookieHTTPOnly,
	}
}

// Encode produces a signed token with session data.
func (ha *HashAuth) Encode(context string, session interface{}) ([]byte, error) {
	buf := &bytes.Buffer{}

	err := encodeKeyless(buf, session)
	if err != nil {
		return nil, err
	}

	hasher := hmac.New(ha.hasher, append(ha.key, []byte(context)...))
	hasher.Write(buf.Bytes())
	mac := hasher.Sum(nil)

	_, err = buf.Write(mac)
	if err != nil {
		return nil, err
	}

	return b64encode(buf.Bytes()), nil
}

// Validate tests a token's signature and returns whether it it valid.
func (ha *HashAuth) Validate(context string, token []byte) bool {
	token, err := b64decode(token)
	if err != nil {
		return false
	}
	return ha.validate(context, token)
}

func (ha *HashAuth) validate(context string, token []byte) bool {
	hashSize := ha.hasher().Size()

	length := len(token)
	if length < hashSize+1 {
		return false
	}

	hasher := hmac.New(ha.hasher, append(ha.key, []byte(context)...))
	hasher.Write(token[:length-hasher.Size()])

	mac1 := token[length-hasher.Size():]
	mac2 := hasher.Sum(nil)

	if !hmac.Equal(mac1, mac2) {
		return false
	}

	return true
}

// Decode checks a token's validity and extracts the data encoded in it.
// May return ErrInvalid if the validity check fails.
func (ha *HashAuth) Decode(context string, token []byte, container interface{}) error {
	token, err := b64decode(token)
	if err != nil {
		return err
	}

	if !ha.validate(context, token) {
		return ErrInvalid
	}

	buf := bytes.NewBuffer(token)
	return decodeKeyless(container, buf)
}

// Authenticate finds and decodes the auth token from a request, populating
// the container with the session data.
// It will return nil on success, or:
//  - http.ErrNoCookie if there is no auth header at all
//  - a decoding error if it is malformed
//  - ErrInvalid if there is a properly formatted token that is invalid
func (ha *HashAuth) Authenticate(context string, r *http.Request, container interface{}) error {
	cookie, err := r.Cookie(ha.cookieName)
	if err != nil {
		return err
	}
	return ha.Decode(context, []byte(cookie.Value), container)
}

// SetCookie adds an encoded token as a cookie on an HTTP response.
// If the provided session data object implements the Expirer or MaxAger
// interfaces, then the corresponding cookie attribute will also be set.
// Other cookie attributes will be set according to the *Options with
// which the HashAuth was created.
func (ha *HashAuth) SetCookie(context string, w http.ResponseWriter, session interface{}) error {
	token, err := ha.Encode(context, session)
	if err != nil {
		return err
	}

	cookie := &http.Cookie{
		Name:     ha.cookieName,
		Value:    string(token),
		Path:     ha.cookiePath,
		Domain:   ha.cookieDomain,
		Secure:   ha.cookieSecure,
		HttpOnly: ha.cookieHTTPOnly,
	}
	augmentCookie(cookie, session)
	w.Header().Add("Set-Cookie", fmtCookie(cookie))
	return nil
}

// ClearCookie adds a Set-Cookie header to a response that will remove
// the auth cookie.
func (ha *HashAuth) ClearCookie(w http.ResponseWriter) {
	w.Header().Add("Set-Cookie", fmtCookie(&http.Cookie{
		Name:     ha.cookieName,
		Value:    "",
		Path:     ha.cookiePath,
		Domain:   ha.cookieDomain,
		Secure:   ha.cookieSecure,
		HttpOnly: ha.cookieHTTPOnly,
		MaxAge:   -1,
	}))
}

func augmentCookie(cookie *http.Cookie, session interface{}) {
	var (
		expire time.Time
		maxage time.Duration
	)

	if sess, ok := session.(Expirer); ok {
		expire = sess.Expires().UTC()
	}
	if !expire.IsZero() {
		//TODO: find out exactly: for which browsers is this necessary?
		s := expire.Format(time.RFC1123)
		if strings.HasSuffix(s, "UTC") {
			s = s[:len(s)-3] + "GMT"
		}
		cookie.RawExpires = s
		return
	}

	if sess, ok := session.(MaxAger); ok {
		maxage = sess.MaxAge()
	}
	if maxage != 0 {
		cookie.MaxAge = int(maxage / time.Second)
	}
}

func fmtCookie(cookie *http.Cookie) string {
	cookie.Expires = time.Time{}
	if len(cookie.RawExpires) > 0 {
		return fmt.Sprintf("%s; Expires=%s", cookie.String(), cookie.RawExpires)
	}
	return cookie.String()
}

func b64encode(plain []byte) []byte {
	enc := make([]byte, base64.URLEncoding.EncodedLen(len(plain)))
	base64.URLEncoding.Encode(enc, plain)
	return bytes.Replace(enc, []byte{'='}, []byte{padChar}, -1)
}

func b64decode(enc []byte) ([]byte, error) {
	plain := make([]byte, base64.URLEncoding.DecodedLen(len(enc)))

	enc = bytes.Replace(enc, []byte{padChar}, []byte{'='}, -1)

	n, err := base64.URLEncoding.Decode(plain, enc)
	if err != nil {
		return nil, err
	}

	return plain[:n], nil
}

func deref(val reflect.Value) reflect.Value {
	kind := val.Kind()
	for kind == reflect.Ptr || kind == reflect.Interface {
		val = val.Elem()
		kind = val.Kind()
	}
	return val
}

func getKeys(t reflect.Type) []string {
	if t.Kind() != reflect.Struct {
		return nil
	}

	num := t.NumField()
	keys := make([]string, num)
	j := 0
	for i := 0; i < num; i++ {
		key := t.Field(i).Name
		if key[0:1] != strings.ToUpper(key[0:1]) {
			continue
		}
		keys[j] = key
		j++
	}
	keys = keys[:j]

	sort.Strings(keys)
	return keys
}

func encodeKeyless(w io.Writer, obj interface{}) error {
	enc := codec.NewEncoder(w, &mh)
	val := deref(reflect.ValueOf(obj))

	keys := getKeys(val.Type())
	if keys == nil {
		return enc.Encode(obj)
	}

	for _, key := range keys {
		err := enc.Encode(val.FieldByName(key).Interface())
		if err != nil {
			return err
		}
	}

	return nil
}

func decodeKeyless(dest interface{}, r io.Reader) error {
	dec := codec.NewDecoder(r, &mh)
	val := deref(reflect.ValueOf(dest))

	keys := getKeys(val.Type())
	if keys == nil {
		return dec.Decode(dest)
	}

	for _, key := range keys {
		dest = val.FieldByName(key).Addr().Interface()
		err := dec.Decode(&dest)
		if err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>switches to sha256 by default<commit_after>/*
Package hashauth provides a means of creating a cookie- and url-friendly token
containing arbitrary encoded information, with an embedded authentication code
that ensures that it was created by you (not forged) and is in its original
form  (not tampered with).

Primary use-cases are login sessions, password reset tokens, and the like. Any
situation where you need to provide to the user a token they can present back
to you which contains a small amount of data and authentication guarantees.

The package provides methods for Encoding, Validating, and Decoding tokens,
and also a higher-level API for interacting with HTTP request and response
cookies for sessions.

Login session example:

	var Signer = hashauth.New([]byte("secret key"), nil)
	const loginCtx = "login"

	type LoginSession struct {
		UserID     int
		Expiration time.Time
	}

	// implementing this method causes hashauth to set the Expires cookie attr
	func (sess *LoginSession) Expires() time.Time {
		return sess.Expiration
	}

	func AuthRequired(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			sess := new(LoginSession)
			err := Signer.Authenticate(loginCtx, r, sess)
			if err != nil {
				http.Error(w, "Login Required", http.StatusForbidden)
			} else if time.Now().UTC().Before(sess.Expiration) {
				// check the expiration, cookie attributes can be tampered with
				http.Error(w, "Login Expired", http.StatusForbidden)
			} else {
				h.ServeHTTP(w, r)
			}
		})
	}

	func LogUserIn(w http.ResponseWriter, uid int) error {
		return Signer.SetCookie(loginCtx, w, &LoginSession{
			UserID:     uid,
			Expiration: time.Now().UTC().Add(7*24*time.Hour),
		})
	}

	func LogUserOut(w http.ResponseWriter) {
		Signer.ClearCookie(w)
	}
*/
package hashauth

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"fmt"
	"hash"
	"io"
	"net/http"
	"reflect"
	"sort"
	"strings"
	"time"

	"github.com/ugorji/go/codec"
)

const padChar = '.'

// ErrInvalid is returned when an authentication check fails
var ErrInvalid = errors.New("token validation failed")

var (
	defaultHasher     = sha256.New
	defaultCookieName = "_ha"
	mh                codec.MsgpackHandle
)

// Options is a simple container for various HashAuth options.
type Options struct {
	Hash           func() hash.Hash
	CookieName     string
	CookiePath     string
	CookieDomain   string
	CookieSecure   bool
	CookieHTTPOnly bool
}

// HashAuth is an Encoder and Decoder of tokens.
type HashAuth struct {
	key            []byte
	hasher         func() hash.Hash
	cookieName     string
	cookiePath     string
	cookieDomain   string
	cookieSecure   bool
	cookieHTTPOnly bool
}

// Expirer can be implemented by session data types, in which case their
// expiration time will be set as the cookie expiration time in
// HashAuth.SetCookie.
type Expirer interface {
	Expires() time.Time
}

// MaxAger can be implemented by session data types, in which case the
// HashAuth.SetCookie method will set a max-age attribute (unless it
// also implements Expirer, which takes precedence).
type MaxAger interface {
	MaxAge() time.Duration
}

// New creates a new HashAuth En/Decoder.
// key should be a carefully guarded secret, with it anyone could forge a token.
// opts can be nil, in which case a sha256 hasher, a default cookie name, and
// no cookie attributes will be used.
func New(key []byte, opts *Options) *HashAuth {
	if opts == nil {
		opts = &Options{}
	}

	hasher := opts.Hash
	if hasher == nil {
		hasher = defaultHasher
	}

	cname := opts.CookieName
	if cname == "" {
		cname = defaultCookieName
	}

	return &HashAuth{
		key:            key,
		hasher:         hasher,
		cookieName:     cname,
		cookiePath:     opts.CookiePath,
		cookieDomain:   opts.CookieDomain,
		cookieSecure:   opts.CookieSecure,
		cookieHTTPOnly: opts.CookieHTTPOnly,
	}
}

// Encode produces a signed token with session data.
func (ha *HashAuth) Encode(context string, session interface{}) ([]byte, error) {
	buf := &bytes.Buffer{}

	err := encodeKeyless(buf, session)
	if err != nil {
		return nil, err
	}

	hasher := hmac.New(ha.hasher, append(ha.key, []byte(context)...))
	hasher.Write(buf.Bytes())
	mac := hasher.Sum(nil)

	_, err = buf.Write(mac)
	if err != nil {
		return nil, err
	}

	return b64encode(buf.Bytes()), nil
}

// Validate tests a token's signature and returns whether it it valid.
func (ha *HashAuth) Validate(context string, token []byte) bool {
	token, err := b64decode(token)
	if err != nil {
		return false
	}
	return ha.validate(context, token)
}

func (ha *HashAuth) validate(context string, token []byte) bool {
	hashSize := ha.hasher().Size()

	length := len(token)
	if length < hashSize+1 {
		return false
	}

	hasher := hmac.New(ha.hasher, append(ha.key, []byte(context)...))
	hasher.Write(token[:length-hasher.Size()])

	mac1 := token[length-hasher.Size():]
	mac2 := hasher.Sum(nil)

	if !hmac.Equal(mac1, mac2) {
		return false
	}

	return true
}

// Decode checks a token's validity and extracts the data encoded in it.
// May return ErrInvalid if the validity check fails.
func (ha *HashAuth) Decode(context string, token []byte, container interface{}) error {
	token, err := b64decode(token)
	if err != nil {
		return err
	}

	if !ha.validate(context, token) {
		return ErrInvalid
	}

	buf := bytes.NewBuffer(token)
	return decodeKeyless(container, buf)
}

// Authenticate finds and decodes the auth token from a request, populating
// the container with the session data.
// It will return nil on success, or:
//  - http.ErrNoCookie if there is no auth header at all
//  - a decoding error if it is malformed
//  - ErrInvalid if there is a properly formatted token that is invalid
func (ha *HashAuth) Authenticate(context string, r *http.Request, container interface{}) error {
	cookie, err := r.Cookie(ha.cookieName)
	if err != nil {
		return err
	}
	return ha.Decode(context, []byte(cookie.Value), container)
}

// SetCookie adds an encoded token as a cookie on an HTTP response.
// If the provided session data object implements the Expirer or MaxAger
// interfaces, then the corresponding cookie attribute will also be set.
// Other cookie attributes will be set according to the *Options with
// which the HashAuth was created.
func (ha *HashAuth) SetCookie(context string, w http.ResponseWriter, session interface{}) error {
	token, err := ha.Encode(context, session)
	if err != nil {
		return err
	}

	cookie := &http.Cookie{
		Name:     ha.cookieName,
		Value:    string(token),
		Path:     ha.cookiePath,
		Domain:   ha.cookieDomain,
		Secure:   ha.cookieSecure,
		HttpOnly: ha.cookieHTTPOnly,
	}
	augmentCookie(cookie, session)
	w.Header().Add("Set-Cookie", fmtCookie(cookie))
	return nil
}

// ClearCookie adds a Set-Cookie header to a response that will remove
// the auth cookie.
func (ha *HashAuth) ClearCookie(w http.ResponseWriter) {
	w.Header().Add("Set-Cookie", fmtCookie(&http.Cookie{
		Name:     ha.cookieName,
		Value:    "",
		Path:     ha.cookiePath,
		Domain:   ha.cookieDomain,
		Secure:   ha.cookieSecure,
		HttpOnly: ha.cookieHTTPOnly,
		MaxAge:   -1,
	}))
}

func augmentCookie(cookie *http.Cookie, session interface{}) {
	var (
		expire time.Time
		maxage time.Duration
	)

	if sess, ok := session.(Expirer); ok {
		expire = sess.Expires().UTC()
	}
	if !expire.IsZero() {
		//TODO: find out exactly: for which browsers is this necessary?
		s := expire.Format(time.RFC1123)
		if strings.HasSuffix(s, "UTC") {
			s = s[:len(s)-3] + "GMT"
		}
		cookie.RawExpires = s
		return
	}

	if sess, ok := session.(MaxAger); ok {
		maxage = sess.MaxAge()
	}
	if maxage != 0 {
		cookie.MaxAge = int(maxage / time.Second)
	}
}

func fmtCookie(cookie *http.Cookie) string {
	cookie.Expires = time.Time{}
	if len(cookie.RawExpires) > 0 {
		return fmt.Sprintf("%s; Expires=%s", cookie.String(), cookie.RawExpires)
	}
	return cookie.String()
}

func b64encode(plain []byte) []byte {
	enc := make([]byte, base64.URLEncoding.EncodedLen(len(plain)))
	base64.URLEncoding.Encode(enc, plain)
	return bytes.Replace(enc, []byte{'='}, []byte{padChar}, -1)
}

func b64decode(enc []byte) ([]byte, error) {
	plain := make([]byte, base64.URLEncoding.DecodedLen(len(enc)))

	enc = bytes.Replace(enc, []byte{padChar}, []byte{'='}, -1)

	n, err := base64.URLEncoding.Decode(plain, enc)
	if err != nil {
		return nil, err
	}

	return plain[:n], nil
}

func deref(val reflect.Value) reflect.Value {
	kind := val.Kind()
	for kind == reflect.Ptr || kind == reflect.Interface {
		val = val.Elem()
		kind = val.Kind()
	}
	return val
}

func getKeys(t reflect.Type) []string {
	if t.Kind() != reflect.Struct {
		return nil
	}

	num := t.NumField()
	keys := make([]string, num)
	j := 0
	for i := 0; i < num; i++ {
		key := t.Field(i).Name
		if key[0:1] != strings.ToUpper(key[0:1]) {
			continue
		}
		keys[j] = key
		j++
	}
	keys = keys[:j]

	sort.Strings(keys)
	return keys
}

func encodeKeyless(w io.Writer, obj interface{}) error {
	enc := codec.NewEncoder(w, &mh)
	val := deref(reflect.ValueOf(obj))

	keys := getKeys(val.Type())
	if keys == nil {
		return enc.Encode(obj)
	}

	for _, key := range keys {
		err := enc.Encode(val.FieldByName(key).Interface())
		if err != nil {
			return err
		}
	}

	return nil
}

func decodeKeyless(dest interface{}, r io.Reader) error {
	dec := codec.NewDecoder(r, &mh)
	val := deref(reflect.ValueOf(dest))

	keys := getKeys(val.Type())
	if keys == nil {
		return dec.Decode(dest)
	}

	for _, key := range keys {
		dest = val.FieldByName(key).Addr().Interface()
		err := dec.Decode(&dest)
		if err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package goinsta

import (
	"encoding/json"
	"fmt"
)

type Hashtag struct {
	inst *Instagram
	err  error

	Name string `json:"name"`

	Sections []struct {
		LayoutType    string `json:"layout_type"`
		LayoutContent struct {
			// F*ck you instagram.
			// Why you do this f*cking horribly structure?!?
			// Media []Media IS EASY. CHECK IT!
			Medias []struct {
				Item Item `json:"media"`
			} `json:"medias"`
		} `json:"layout_content"`
		FeedType        string `json:"feed_type"`
		ExploreItemInfo struct {
			NumColumns      int  `json:"num_columns"`
			TotalNumColumns int  `json:"total_num_columns"`
			AspectRatio     int  `json:"aspect_ratio"`
			Autoplay        bool `json:"autoplay"`
		} `json:"explore_item_info"`
	} `json:"sections"`
	MediaCount          int     `json:"media_count"`
	ID                  int64   `json:"id"`
	MoreAvailable       bool    `json:"more_available"`
	NextID              string  `json:"next_max_id"`
	NextPage            int     `json:"next_page"`
	NextMediaIds        []int64 `json:"next_media_ids"`
	AutoLoadMoreEnabled bool    `json:"auto_load_more_enabled"`
	Status              string  `json:"status"`
}

// NewHashtag returns initialised hashtag structure
func (inst *Instagram) NewHashtag() *Hashtag {
	return &Hashtag{
		inst: inst,
	}
}

// Sync updates Hashtag information preparing it to Next call.
func (h *Hashtag) Sync() error {
	insta := h.inst

	body, err := insta.sendSimpleRequest(urlTagSync, h.Name)
	if err == nil {
		var resp struct {
			Name       string `json:"name"`
			ID         int64  `json:"id"`
			MediaCount int    `json:"media_count"`
		}
		err = json.Unmarshal(body, &resp)
		if err == nil {
			h.Name = resp.Name
			h.ID = resp.ID
			h.MediaCount = resp.MediaCount
		}
	}
	return err
}

// Next paginates over hashtag pages (xd).
func (h *Hashtag) Next() bool {
	if h.err != nil {
		return false
	}
	insta := h.inst
	name := h.Name
	body, err := insta.sendRequest(
		&reqOptions{
			Query: map[string]string{
				"max_id":     h.NextID,
				"rank_token": insta.rankToken,
				"page":       fmt.Sprintf("%d", h.NextPage),
			},
			Endpoint: fmt.Sprintf(urlTagContent, name),
			IsPost:   false,
		},
	)
	if err == nil {
		ht := Hashtag{}
		err = json.Unmarshal(body, &ht)
		if err == nil {
			*h = ht
			h.inst = insta
			h.Name = name
			if !h.MoreAvailable {
				h.err = ErrNoMore
			}
			return true
		}
	}
	h.err = err
	return false
}

// TODO: func (h *Hashtag) Stories()
<commit_msg>Added Error function to hashtag structure<commit_after>package goinsta

import (
	"encoding/json"
	"fmt"
)

type Hashtag struct {
	inst *Instagram
	err  error

	Name string `json:"name"`

	Sections []struct {
		LayoutType    string `json:"layout_type"`
		LayoutContent struct {
			// F*ck you instagram.
			// Why you do this f*cking horribly structure?!?
			// Media []Media IS EASY. CHECK IT!
			Medias []struct {
				Item Item `json:"media"`
			} `json:"medias"`
		} `json:"layout_content"`
		FeedType        string `json:"feed_type"`
		ExploreItemInfo struct {
			NumColumns      int  `json:"num_columns"`
			TotalNumColumns int  `json:"total_num_columns"`
			AspectRatio     int  `json:"aspect_ratio"`
			Autoplay        bool `json:"autoplay"`
		} `json:"explore_item_info"`
	} `json:"sections"`
	MediaCount          int     `json:"media_count"`
	ID                  int64   `json:"id"`
	MoreAvailable       bool    `json:"more_available"`
	NextID              string  `json:"next_max_id"`
	NextPage            int     `json:"next_page"`
	NextMediaIds        []int64 `json:"next_media_ids"`
	AutoLoadMoreEnabled bool    `json:"auto_load_more_enabled"`
	Status              string  `json:"status"`
}

// NewHashtag returns initialised hashtag structure
func (inst *Instagram) NewHashtag() *Hashtag {
	return &Hashtag{
		inst: inst,
	}
}

// Sync updates Hashtag information preparing it to Next call.
func (h *Hashtag) Sync() error {
	insta := h.inst

	body, err := insta.sendSimpleRequest(urlTagSync, h.Name)
	if err == nil {
		var resp struct {
			Name       string `json:"name"`
			ID         int64  `json:"id"`
			MediaCount int    `json:"media_count"`
		}
		err = json.Unmarshal(body, &resp)
		if err == nil {
			h.Name = resp.Name
			h.ID = resp.ID
			h.MediaCount = resp.MediaCount
		}
	}
	return err
}

// Next paginates over hashtag pages (xd).
func (h *Hashtag) Next() bool {
	if h.err != nil {
		return false
	}
	insta := h.inst
	name := h.Name
	body, err := insta.sendRequest(
		&reqOptions{
			Query: map[string]string{
				"max_id":     h.NextID,
				"rank_token": insta.rankToken,
				"page":       fmt.Sprintf("%d", h.NextPage),
			},
			Endpoint: fmt.Sprintf(urlTagContent, name),
			IsPost:   false,
		},
	)
	if err == nil {
		ht := Hashtag{}
		err = json.Unmarshal(body, &ht)
		if err == nil {
			*h = ht
			h.inst = insta
			h.Name = name
			if !h.MoreAvailable {
				h.err = ErrNoMore
			}
			return true
		}
	}
	h.err = err
	return false
}

func (h *Hashtag) Error() error {
	return h.err
}

// TODO: func (h *Hashtag) Stories()
<|endoftext|>