<commit_before>package vault

import (
	"os"
	"reflect"
	"sort"
	"testing"
)

func TestEditLocationNonexisting(t *testing.T) {
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}

	err = v.Edit("testlocation", Credential{"testusername", "testpassword"})
	if err != ErrNoSuchCredential {
		t.Fatal("expected Edit on non-existant location to return ErrNoSuchCredential")
	}
}

func TestEditLocation(t *testing.T) {
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}

	err = v.Add("testlocation", Credential{"testusername", "testpassword"})
	if err != nil {
		t.Fatal(err)
	}

	err = v.Edit("testlocation", Credential{"testusername2", "testpassword2"})
	if err != nil {
		t.Fatal(err)
	}

	cred, err := v.Get("testlocation")
	if err != nil {
		t.Fatal(err)
	}

	if cred.Username != "testusername2" || cred.Password != "testpassword2" {
		t.Fatal("vault.Edit did not change credential data")
	}
}
func TestGetInvalidKey(t *testing.T) {
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	v.secret = [32]byte{}
	if _, err = v.Get("test"); err != ErrCouldNotDecrypt {
		t.Fatal("expected v.Get to return ErrCouldNotDecrypt with invalid secret")
	}
}

func TestAddInvalidKey(t *testing.T) {
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	v.secret = [32]byte{}
	if err = v.Add("testlocation", Credential{"test", "test2"}); err != ErrCouldNotDecrypt {
		t.Fatal("expected v.Add to return ErrCouldNotDecrypt with invalid secret")
	}
}

func TestNonexistentVaultOpen(t *testing.T) {
	_, err := Open("doesntexist.jpg", "nopass")
	if !os.IsNotExist(err) {
		t.Fatal("Open did not return IsNotExist for non-existant filename")
	}
}

func TestGenerate(t *testing.T) {
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	if err = v.Generate("testlocation", "testusername"); err != nil {
		t.Fatal(err)
	}
	cred, err := v.Get("testlocation")
	if err != nil {
		t.Fatal(err)
	}
	if cred.Username != "testusername" {
		t.Fatal("Generate did not set username")
	}
	if cred.Password == "" {
		t.Fatal("generate did not generate a password")
	}
}

func TestGenerateExisting(t *testing.T) {
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	err = v.Add("testlocation", Credential{"testuser", "testpass"})
	if err != nil {
		t.Fatal(err)
	}
	err = v.Generate("testlocation", "testuser")
	if err != ErrCredentialExists {
		t.Fatal("expected credential exists error on generate with existing location")
	}
}

func TestGetLocations(t *testing.T) {
	creds := []Credential{
		{"test1", "testpass1"},
		{"test2", "testpass2"},
		{"test3", "testpass3"},
	}
	locs := []string{"testloc1", "testloc2", "testloc3"}

	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	for i, cred := range creds {
		if err = v.Add(locs[i], cred); err != nil {
			t.Fatal(err)
		}
	}

	vaultLocations, err := v.Locations()
	if err != nil {
		t.Fatal(err)
	}

	sort.Strings(vaultLocations)
	if !reflect.DeepEqual(locs, vaultLocations) {
		t.Fatalf("expected %v to equal %v\n", vaultLocations, locs)
	}
}

func TestGetNonexisting(t *testing.T) {
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	if _, err = v.Get("testlocation"); err != ErrNoSuchCredential {
		t.Fatal("expected vault.Get on nonexisting credential to return ErrNoSuchCredential")
	}
}

func TestAddExisting(t *testing.T) {
	testCredential := Credential{"testuser", "testpass"}
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	err = v.Add("testlocation", testCredential)
	if err != nil {
		t.Fatal(err)
	}
	err = v.Add("testlocation", testCredential)
	if err != ErrCredentialExists {
		t.Fatal("expected add on existing location to return ErrCredentialExists")
	}
}

func TestNewSaveOpen(t *testing.T) {
	testCredential := Credential{"testuser", "testpass"}

	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	err = v.Add("testlocation", testCredential)
	if err != nil {
		t.Fatal(err)
	}
	err = v.Save("pass.db")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove("pass.db")

	vopen, err := Open("pass.db", "testpass")
	if err != nil {
		t.Fatal(err)
	}
	credential, err := vopen.Get("testlocation")
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(&testCredential, credential) {
		t.Fatalf("vault did not store credential correctly. wanted %v got %v", testCredential, credential)
	}

	_, err = Open("pass.db", "wrongpass")
	if err != ErrCouldNotDecrypt {
		t.Fatal("Open decrypted given an incorrect passphrase")
	}
}

func TestNonceRotation(t *testing.T) {
	testCredential := Credential{"testuser", "testpass"}

	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}

	oldnonce := v.nonce
	oldsecret := v.secret

	v.Add("testlocation", testCredential)
	err = v.Save("pass.db")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove("pass.db")

	vopen, err := Open("pass.db", "testpass")
	if err != nil {
		t.Fatal(err)
	}
	if vopen.secret == oldsecret {
		t.Fatal("opened vault had the same secret as the previous vault")
	}
	if vopen.nonce == oldnonce {
		t.Fatal("opened vault had the same nonce as the previous vault")
	}
}
<commit_msg>add benchmarking, TestHeavyVault<commit_after>package vault

import (
	"fmt"
	"os"
	"reflect"
	"sort"
	"testing"
)

func TestEditLocationNonexisting(t *testing.T) {
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}

	err = v.Edit("testlocation", Credential{"testusername", "testpassword"})
	if err != ErrNoSuchCredential {
		t.Fatal("expected Edit on non-existant location to return ErrNoSuchCredential")
	}
}

func TestEditLocation(t *testing.T) {
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}

	err = v.Add("testlocation", Credential{"testusername", "testpassword"})
	if err != nil {
		t.Fatal(err)
	}

	err = v.Edit("testlocation", Credential{"testusername2", "testpassword2"})
	if err != nil {
		t.Fatal(err)
	}

	cred, err := v.Get("testlocation")
	if err != nil {
		t.Fatal(err)
	}

	if cred.Username != "testusername2" || cred.Password != "testpassword2" {
		t.Fatal("vault.Edit did not change credential data")
	}
}

func TestGetInvalidKey(t *testing.T) {
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	v.secret = [32]byte{}
	if _, err = v.Get("test"); err != ErrCouldNotDecrypt {
		t.Fatal("expected v.Get to return ErrCouldNotDecrypt with invalid secret")
	}
}

func TestAddInvalidKey(t *testing.T) {
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	v.secret = [32]byte{}
	if err = v.Add("testlocation", Credential{"test", "test2"}); err != ErrCouldNotDecrypt {
		t.Fatal("expected v.Add to return ErrCouldNotDecrypt with invalid secret")
	}
}

func TestHeavyVault(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}

	size := 10000

	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}

	for i := 0; i < size; i++ {
		err = v.Add(fmt.Sprintf("testlocation%v", i), Credential{"testuser", "testpassword"})
		if err != nil {
			t.Fatal(err)
		}
	}

	err = v.Save("testvault.db")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove("testvault.db")

	vopen, err := Open("testvault.db", "testpass")
	if err != nil {
		t.Fatal(err)
	}

	for i := 0; i < size; i++ {
		cred, err := vopen.Get(fmt.Sprintf("testlocation%v", i))
		if err != nil {
			t.Fatal(err)
		}
		if cred.Username != "testuser" || cred.Password != "testpassword" {
			t.Fatal("huge vault did not contain testuser or testvault")
		}
	}
}

func TestNonexistentVaultOpen(t *testing.T) {
	_, err := Open("doesntexist.jpg", "nopass")
	if !os.IsNotExist(err) {
		t.Fatal("Open did not return IsNotExist for non-existant filename")
	}
}

func TestGenerate(t *testing.T) {
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	if err = v.Generate("testlocation", "testusername"); err != nil {
		t.Fatal(err)
	}
	cred, err := v.Get("testlocation")
	if err != nil {
		t.Fatal(err)
	}
	if cred.Username != "testusername" {
		t.Fatal("Generate did not set username")
	}
	if cred.Password == "" {
		t.Fatal("generate did not generate a password")
	}
}

func TestGenerateExisting(t *testing.T) {
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	err = v.Add("testlocation", Credential{"testuser", "testpass"})
	if err != nil {
		t.Fatal(err)
	}
	err = v.Generate("testlocation", "testuser")
	if err != ErrCredentialExists {
		t.Fatal("expected credential exists error on generate with existing location")
	}
}

func TestGetLocations(t *testing.T) {
	creds := []Credential{
		{"test1", "testpass1"},
		{"test2", "testpass2"},
		{"test3", "testpass3"},
	}
	locs := []string{"testloc1", "testloc2", "testloc3"}

	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	for i, cred := range creds {
		if err = v.Add(locs[i], cred); err != nil {
			t.Fatal(err)
		}
	}

	vaultLocations, err := v.Locations()
	if err != nil {
		t.Fatal(err)
	}

	sort.Strings(vaultLocations)
	if !reflect.DeepEqual(locs, vaultLocations) {
		t.Fatalf("expected %v to equal %v\n", vaultLocations, locs)
	}
}

func TestGetNonexisting(t *testing.T) {
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	if _, err = v.Get("testlocation"); err != ErrNoSuchCredential {
		t.Fatal("expected vault.Get on nonexisting credential to return ErrNoSuchCredential")
	}
}

func TestAddExisting(t *testing.T) {
	testCredential := Credential{"testuser", "testpass"}
	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	err = v.Add("testlocation", testCredential)
	if err != nil {
		t.Fatal(err)
	}
	err = v.Add("testlocation", testCredential)
	if err != ErrCredentialExists {
		t.Fatal("expected add on existing location to return ErrCredentialExists")
	}
}

func TestNewSaveOpen(t *testing.T) {
	testCredential := Credential{"testuser", "testpass"}

	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}
	err = v.Add("testlocation", testCredential)
	if err != nil {
		t.Fatal(err)
	}
	err = v.Save("pass.db")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove("pass.db")

	vopen, err := Open("pass.db", "testpass")
	if err != nil {
		t.Fatal(err)
	}
	credential, err := vopen.Get("testlocation")
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(&testCredential, credential) {
		t.Fatalf("vault did not store credential correctly. wanted %v got %v", testCredential, credential)
	}

	_, err = Open("pass.db", "wrongpass")
	if err != ErrCouldNotDecrypt {
		t.Fatal("Open decrypted given an incorrect passphrase")
	}
}

func TestNonceRotation(t *testing.T) {
	testCredential := Credential{"testuser", "testpass"}

	v, err := New("testpass")
	if err != nil {
		t.Fatal(err)
	}

	oldnonce := v.nonce
	oldsecret := v.secret

	v.Add("testlocation", testCredential)
	err = v.Save("pass.db")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove("pass.db")

	vopen, err := Open("pass.db", "testpass")
	if err != nil {
		t.Fatal(err)
	}
	if vopen.secret == oldsecret {
		t.Fatal("opened vault had the same secret as the previous vault")
	}
	if vopen.nonce == oldnonce {
		t.Fatal("opened vault had the same nonce as the previous vault")
	}
}

func BenchmarkVaultAdd(b *testing.B) {
	v, err := New("testpass")
	if err != nil {
	}
	for i := 0; i < b.N; i++ {
		err = v.Add(fmt.Sprintf("testlocation%v", i), Credential{"testuser", "testpass"})
		if err != nil {
		}
	}
}
<|endoftext|>


<commit_before>package cosmic

import "github.com/MissionCriticalCloud/go-cosmic/v6/cosmic"

// Config is the configuration structure used to instantiate a
// new Cosmic client.
type Config struct {
	APIURL      string
	APIKey      string
	SecretKey   string
	HTTPGETOnly bool
	Timeout     int64
}

// NewClient returns a new Cosmic client.
func (c *Config) NewClient() (*cosmic.CosmicClient, error) {
	cs := cosmic.NewAsyncClient(c.APIURL, c.APIKey, c.SecretKey, nil, 60)
	cs.HTTPGETOnly = c.HTTPGETOnly
	cs.AsyncTimeout(c.Timeout)
	return cs, nil
}
<commit_msg>Bump client timeout<commit_after>package cosmic

import "github.com/MissionCriticalCloud/go-cosmic/v6/cosmic"

// Config is the configuration structure used to instantiate a
// new Cosmic client.
type Config struct {
	APIURL      string
	APIKey      string
	SecretKey   string
	HTTPGETOnly bool
	Timeout     int64
}

// NewClient returns a new Cosmic client.
func (c *Config) NewClient() (*cosmic.CosmicClient, error) {
	cs := cosmic.NewAsyncClient(c.APIURL, c.APIKey, c.SecretKey, nil, 120)
	cs.HTTPGETOnly = c.HTTPGETOnly
	cs.AsyncTimeout(c.Timeout)
	return cs, nil
}
<|endoftext|>


<commit_before>package xormrediscache

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/go-xorm/core"
	"hash/crc32"
	"log"
	"reflect"
	// "strconv"
	"time"
	"unsafe"
)

const (
	DEFAULT_EXPIRATION = time.Duration(0)
	FOREVER_EXPIRATION = time.Duration(-1)
)

// Wraps the Redis client to meet the Cache interface.
type RedisCacher struct {
	pool              *redis.Pool
	defaultExpiration time.Duration
}

// until redigo supports sharding/clustering, only one host will be in hostList
func NewRedisCacher(host string, password string, defaultExpiration time.Duration) *RedisCacher {
	var pool = &redis.Pool{
		MaxIdle:     5,
		IdleTimeout: 240 * time.Second,
		Dial: func() (redis.Conn, error) {
			// the redis protocol should probably be made sett-able
			c, err := redis.Dial("tcp", host)
			if err != nil {
				return nil, err
			}
			if len(password) > 0 {
				if _, err := c.Do("AUTH", password); err != nil {
					c.Close()
					return nil, err
				}
			} else {
				// check with PING
				if _, err := c.Do("PING"); err != nil {
					c.Close()
					return nil, err
				}
			}
			return c, err
		},
		// custom connection test method
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			if _, err := c.Do("PING"); err != nil {
				return err
			}
			return nil
		},
	}
	return &RedisCacher{pool, defaultExpiration}
}

func exists(conn redis.Conn, key string) bool {
	existed, _ := redis.Bool(conn.Do("EXISTS", key))
	return existed
}

func (c *RedisCacher) getBeanKey(tableName string, id string) string {
	return fmt.Sprintf("xorm:bean:%s:%s", tableName, id)
}

func (c *RedisCacher) getSqlKey(tableName string, sql string) string {
	// hash sql to minimize key length
	crc := crc32.ChecksumIEEE([]byte(sql))
	return fmt.Sprintf("xorm:sql:%s:%d", tableName, crc)
}

func (c *RedisCacher) Flush() error {
	// conn := c.pool.Get()
	// defer conn.Close()
	// _, err := conn.Do("FLUSHALL")
	// return err
	return c.delObject("xorm:*")
}

func (c *RedisCacher) getObject(key string) interface{} {
	conn := c.pool.Get()
	defer conn.Close()
	raw, err := conn.Do("GET", key)
	if raw == nil {
		return nil
	}
	item, err := redis.Bytes(raw, err)
	if err != nil {
		log.Fatalf("[xorm/redis_cacher] redis.Bytes failed: %s", err)
		return nil
	}

	value, err := deserialize(item)

	return value
}

func (c *RedisCacher) getObject2(key string, ptr interface{}) error {
	conn := c.pool.Get()
	defer conn.Close()
	raw, err := conn.Do("GET", key)
	if raw == nil {
		return err
	}
	item, err := redis.Bytes(raw, err)
	if err != nil {
		log.Fatalf("[xorm/redis_cacher] redis.Bytes failed: %s", err)
		return err
	}
	err = deserialize2(item, ptr)
	return err
}

func (c *RedisCacher) GetIds(tableName, sql string) interface{} {
	sqlKey := c.getSqlKey(tableName, sql)
	log.Printf("[xorm/redis_cacher] GetIds|tableName:%s|sql:%s|key:%s", tableName, sql, sqlKey)
	return c.getObject(sqlKey)
}

func (c *RedisCacher) GetBean(tableName string, id string) interface{} {
	beanKey := c.getBeanKey(tableName, id)
	log.Printf("[xorm/redis_cacher] GetBean|tableName:%s|id:%s|key:%s", tableName, id, beanKey)
	return c.getObject(beanKey)
}

func (c *RedisCacher) GetBean2(tableName string, id string, ptr interface{}) error {
	beanKey := c.getBeanKey(tableName, id)
	log.Printf("[xorm/redis_cacher] GetBean|tableName:%s|id:%s|key:%s", tableName, id, beanKey)
	return c.getObject2(beanKey, ptr)
}

func (c *RedisCacher) putObject(key string, value interface{}) {
	c.invoke(c.pool.Get().Do, key, value, c.defaultExpiration)
}

func (c *RedisCacher) PutIds(tableName, sql string, ids interface{}) {
	sqlKey := c.getSqlKey(tableName, sql)
	log.Printf("[xorm/redis_cacher] PutIds|tableName:%s|sql:%s|key:%s|obj:%s|type:%v", tableName, sql, sqlKey, ids, reflect.TypeOf(ids))
	c.putObject(sqlKey, ids)
}

func (c *RedisCacher) PutBean(tableName string, id string, obj interface{}) {
	beanKey := c.getBeanKey(tableName, id)
	log.Printf("[xorm/redis_cacher] PutBean|tableName:%s|id:%s|key:%s|type:%v", tableName, id, beanKey, reflect.TypeOf(obj))
	c.putObject(beanKey, obj)
}

func (c *RedisCacher) delObject(key string) error {
	log.Printf("[xorm/redis_cacher] delObject key:[%s]", key)

	conn := c.pool.Get()
	defer conn.Close()
	if !exists(conn, key) {
		log.Printf("[xorm/redis_cacher] delObject: %v", core.ErrCacheMiss)
		return core.ErrCacheMiss
	}
	_, err := conn.Do("DEL", key)
	return err
}

func (c *RedisCacher) delObjects(key string) error {

	log.Printf("[xorm/redis_cacher] delObjects key:[%s]", key)

	conn := c.pool.Get()
	defer conn.Close()

	keys, err := conn.Do("KEYS", key)
	log.Printf("[xorm/redis_cacher] delObjects keys: %v", keys)

	if err == nil {
		for _, key := range keys.([]interface{}) {
			conn.Do("DEL", key)
		}
	}
	return err
}

func (c *RedisCacher) DelIds(tableName, sql string) {
	c.delObject(c.getSqlKey(tableName, sql))
}

func (c *RedisCacher) DelBean(tableName string, id string) {
	c.delObject(c.getBeanKey(tableName, id))
}

func (c *RedisCacher) ClearIds(tableName string) {
	c.delObjects(c.getSqlKey(tableName, "*"))
}

func (c *RedisCacher) ClearBeans(tableName string) {
	c.delObjects(c.getBeanKey(tableName, "*"))
}

func (c *RedisCacher) invoke(f func(string, ...interface{}) (interface{}, error),
	key string, value interface{}, expires time.Duration) error {

	switch expires {
	case DEFAULT_EXPIRATION:
		expires = c.defaultExpiration
	case FOREVER_EXPIRATION:
		expires = time.Duration(0)
	}

	b, err := serialize(value)
	if err != nil {
		return err
	}
	conn := c.pool.Get()
	defer conn.Close()
	if expires > 0 {
		_, err := f("SETEX", key, int32(expires/time.Second), b)
		return err
	} else {
		_, err := f("SET", key, b)
		return err
	}
}

func serialize(value interface{}) ([]byte, error) {

	err := RegisterGobConcreteType(value)
	if err != nil {
		return nil, err
	}

	if reflect.TypeOf(value).Kind() == reflect.Struct {
		return nil, fmt.Errorf("serialize func only take pointer of a struct")
	}

	var b bytes.Buffer
	encoder := gob.NewEncoder(&b)

	log.Printf("[xorm/redis_cacher] serialize type:%v", reflect.TypeOf(value))
	err = encoder.Encode(&value)
	if err != nil {
		log.Fatalf("[xorm/redis_cacher] gob encoding '%s' failed: %s", value, err)
		return nil, err
	}
	return b.Bytes(), nil
}

func deserialize(byt []byte) (ptr interface{}, err error) {
	b := bytes.NewBuffer(byt)
	decoder := gob.NewDecoder(b)

	var p interface{}
	err = decoder.Decode(&p)
	if err != nil {
		log.Fatal("[xorm/redis_cacher] decode:", err)
		return
	}

	v := reflect.ValueOf(p)
	log.Printf("[xorm/redis_cacher] deserialize type:%v", v.Type())
	if v.Kind() == reflect.Struct {

		// !nashtsai! TODO following implementation will new an instance and make a copy,
		// hence performance degration
		var pp interface{} = &p
		datas := reflect.ValueOf(pp).Elem().InterfaceData()

		sp := reflect.NewAt(v.Type(),
			unsafe.Pointer(datas[1])).Interface()
		ptr = sp
		vv := reflect.ValueOf(ptr)
		log.Printf("[xorm/redis_cacher] deserialize convert ptr type:%v | CanAddr:%t", vv.Type(), vv.CanAddr())
		// --
	} else {
		ptr = p
	}
	return
}

func deserialize2(byt []byte, ptr interface{}) (err error) {
	b := bytes.NewBuffer(byt)
	decoder := gob.NewDecoder(b)

	log.Printf("[xorm/redis_cacher] deserialize2 type b4 decode:%v", reflect.TypeOf(ptr))

	err = decoder.Decode(ptr)
	if err != nil {
		return
	}
	log.Printf("[xorm/redis_cacher] deserialize2 type:%v", reflect.TypeOf(ptr))

	return
}

func RegisterGobConcreteType(value interface{}) error {

	t := reflect.TypeOf(value)

	log.Printf("[xorm/redis_cacher] RegisterGobConcreteType:%v", t)

	switch t.Kind() {
	case reflect.Ptr:
		v := reflect.ValueOf(value)
		i := v.Elem().Interface()
		gob.Register(i)
	case reflect.Struct, reflect.Map, reflect.Slice:
		gob.Register(value)
	case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
		// do nothing since already registered known type
	default:
		return fmt.Errorf("unhandled type: %v", t)
	}
	return nil
}
<commit_msg>update logging<commit_after>package xormrediscache

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"github.com/go-xorm/core"
	"hash/crc32"
	"log"
	"reflect"
	// "strconv"
	"time"
	"unsafe"
)

const (
	DEFAULT_EXPIRATION = time.Duration(0)
	FOREVER_EXPIRATION = time.Duration(-1)
)

// Wraps the Redis client to meet the Cache interface.
type RedisCacher struct {
	pool              *redis.Pool
	defaultExpiration time.Duration
}

// until redigo supports sharding/clustering, only one host will be in hostList
func NewRedisCacher(host string, password string, defaultExpiration time.Duration) *RedisCacher {
	var pool = &redis.Pool{
		MaxIdle:     5,
		IdleTimeout: 240 * time.Second,
		Dial: func() (redis.Conn, error) {
			// the redis protocol should probably be made sett-able
			c, err := redis.Dial("tcp", host)
			if err != nil {
				return nil, err
			}
			if len(password) > 0 {
				if _, err := c.Do("AUTH", password); err != nil {
					c.Close()
					return nil, err
				}
			} else {
				// check with PING
				if _, err := c.Do("PING"); err != nil {
					c.Close()
					return nil, err
				}
			}
			return c, err
		},
		// custom connection test method
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			if _, err := c.Do("PING"); err != nil {
				return err
			}
			return nil
		},
	}
	return &RedisCacher{pool, defaultExpiration}
}

func exists(conn redis.Conn, key string) bool {
	existed, _ := redis.Bool(conn.Do("EXISTS", key))
	return existed
}

func (c *RedisCacher) getBeanKey(tableName string, id string) string {
	return fmt.Sprintf("xorm:bean:%s:%s", tableName, id)
}

func (c *RedisCacher) getSqlKey(tableName string, sql string) string {
	// hash sql to minimize key length
	crc := crc32.ChecksumIEEE([]byte(sql))
	return fmt.Sprintf("xorm:sql:%s:%d", tableName, crc)
}

func (c *RedisCacher) Flush() error {
	// conn := c.pool.Get()
	// defer conn.Close()
	// _, err := conn.Do("FLUSHALL")
	// return err
	return c.delObject("xorm:*")
}

func (c *RedisCacher) getObject(key string) interface{} {
	conn := c.pool.Get()
	defer conn.Close()
	raw, err := conn.Do("GET", key)
	if raw == nil {
		return nil
	}
	item, err := redis.Bytes(raw, err)
	if err != nil {
		log.Fatalf("[xorm/redis_cacher] redis.Bytes failed: %s", err)
		return nil
	}

	value, err := deserialize(item)

	return value
}

func (c *RedisCacher) getObject2(key string, ptr interface{}) error {
	conn := c.pool.Get()
	defer conn.Close()
	raw, err := conn.Do("GET", key)
	if raw == nil {
		return err
	}
	item, err := redis.Bytes(raw, err)
	if err != nil {
		log.Fatalf("[xorm/redis_cacher] redis.Bytes failed: %s", err)
		return err
	}
	err = deserialize2(item, ptr)
	return err
}

func (c *RedisCacher) GetIds(tableName, sql string) interface{} {
	sqlKey := c.getSqlKey(tableName, sql)
	log.Printf("[xorm/redis_cacher] GetIds|tableName:%s|sql:%s|key:%s", tableName, sql, sqlKey)
	return c.getObject(sqlKey)
}

func (c *RedisCacher) GetBean(tableName string, id string) interface{} {
	beanKey := c.getBeanKey(tableName, id)
	log.Printf("[xorm/redis_cacher] GetBean|tableName:%s|id:%s|key:%s", tableName, id, beanKey)
	return c.getObject(beanKey)
}

func (c *RedisCacher) GetBean2(tableName string, id string, ptr interface{}) error {
	beanKey := c.getBeanKey(tableName, id)
	log.Printf("[xorm/redis_cacher] GetBean|tableName:%s|id:%s|key:%s", tableName, id, beanKey)
	return c.getObject2(beanKey, ptr)
}

func (c *RedisCacher) putObject(key string, value interface{}) {
	c.invoke(c.pool.Get().Do, key, value, c.defaultExpiration)
}

func (c *RedisCacher) PutIds(tableName, sql string, ids interface{}) {
	sqlKey := c.getSqlKey(tableName, sql)
	log.Printf("[xorm/redis_cacher] PutIds|tableName:%s|sql:%s|key:%s|obj:%s|type:%v", tableName, sql, sqlKey, ids, reflect.TypeOf(ids))
	c.putObject(sqlKey, ids)
}

func (c *RedisCacher) PutBean(tableName string, id string, obj interface{}) {
	beanKey := c.getBeanKey(tableName, id)
	log.Printf("[xorm/redis_cacher] PutBean|tableName:%s|id:%s|key:%s|type:%v", tableName, id, beanKey, reflect.TypeOf(obj))
	c.putObject(beanKey, obj)
}

func (c *RedisCacher) delObject(key string) error {
	log.Printf("[xorm/redis_cacher] delObject key:[%s]", key)

	conn := c.pool.Get()
	defer conn.Close()
	if !exists(conn, key) {
		log.Printf("[xorm/redis_cacher] delObject: %v", core.ErrCacheMiss)
		return core.ErrCacheMiss
	}
	_, err := conn.Do("DEL", key)
	return err
}

func (c *RedisCacher) delObjects(key string) error {

	log.Printf("[xorm/redis_cacher] delObjects key:[%s]", key)

	conn := c.pool.Get()
	defer conn.Close()

	keys, err := conn.Do("KEYS", key)
	log.Printf("[xorm/redis_cacher] delObjects keys: %v", keys)

	if err == nil {
		for _, key := range keys.([]interface{}) {
			conn.Do("DEL", key)
		}
	}
	return err
}

func (c *RedisCacher) DelIds(tableName, sql string) {
	c.delObject(c.getSqlKey(tableName, sql))
}

func (c *RedisCacher) DelBean(tableName string, id string) {
	c.delObject(c.getBeanKey(tableName, id))
}

func (c *RedisCacher) ClearIds(tableName string) {
	c.delObjects(c.getSqlKey(tableName, "*"))
}

func (c *RedisCacher) ClearBeans(tableName string) {
	c.delObjects(c.getBeanKey(tableName, "*"))
}

func (c *RedisCacher) invoke(f func(string, ...interface{}) (interface{}, error),
	key string, value interface{}, expires time.Duration) error {

	switch expires {
	case DEFAULT_EXPIRATION:
		expires = c.defaultExpiration
	case FOREVER_EXPIRATION:
		expires = time.Duration(0)
	}

	b, err := serialize(value)
	if err != nil {
		return err
	}
	conn := c.pool.Get()
	defer conn.Close()
	if expires > 0 {
		_, err := f("SETEX", key, int32(expires/time.Second), b)
		return err
	} else {
		_, err := f("SET", key, b)
		return err
	}
}

func serialize(value interface{}) ([]byte, error) {

	err := RegisterGobConcreteType(value)
	if err != nil {
		return nil, err
	}

	if reflect.TypeOf(value).Kind() == reflect.Struct {
		return nil, fmt.Errorf("serialize func only take pointer of a struct")
	}

	var b bytes.Buffer
	encoder := gob.NewEncoder(&b)

	log.Printf("[xorm/redis_cacher] serialize type:%v", reflect.TypeOf(value))
	err = encoder.Encode(&value)
	if err != nil {
		log.Fatalf("[xorm/redis_cacher] gob encoding '%s' failed: %s|value:%v", value, err, value)
		return nil, err
	}
	return b.Bytes(), nil
}

func deserialize(byt []byte) (ptr interface{}, err error) {
	b := bytes.NewBuffer(byt)
	decoder := gob.NewDecoder(b)

	var p interface{}
	err = decoder.Decode(&p)
	if err != nil {
		log.Fatal("[xorm/redis_cacher] decode:", err)
		return
	}

	v := reflect.ValueOf(p)
	log.Printf("[xorm/redis_cacher] deserialize type:%v", v.Type())
	if v.Kind() == reflect.Struct {

		// !nashtsai! TODO following implementation will new an instance and make a copy,
		// hence performance degration
		var pp interface{} = &p
		datas := reflect.ValueOf(pp).Elem().InterfaceData()

		sp := reflect.NewAt(v.Type(),
			unsafe.Pointer(datas[1])).Interface()
		ptr = sp
		vv := reflect.ValueOf(ptr)
		log.Printf("[xorm/redis_cacher] deserialize convert ptr type:%v | CanAddr:%t", vv.Type(), vv.CanAddr())
		// --
	} else {
		ptr = p
	}
	return
}

func deserialize2(byt []byte, ptr interface{}) (err error) {
	b := bytes.NewBuffer(byt)
	decoder := gob.NewDecoder(b)

	log.Printf("[xorm/redis_cacher] deserialize2 type b4 decode:%v", reflect.TypeOf(ptr))

	err = decoder.Decode(ptr)
	if err != nil {
		return
	}
	log.Printf("[xorm/redis_cacher] deserialize2 type:%v", reflect.TypeOf(ptr))

	return
}

func RegisterGobConcreteType(value interface{}) error {

	t := reflect.TypeOf(value)

	log.Printf("[xorm/redis_cacher] RegisterGobConcreteType:%v", t)

	switch t.Kind() {
	case reflect.Ptr:
		v := reflect.ValueOf(value)
		i := v.Elem().Interface()
		gob.Register(i)
	case reflect.Struct, reflect.Map, reflect.Slice:
		gob.Register(value)
	case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Bool, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
		// do nothing since already registered known type
	default:
		return fmt.Errorf("unhandled type: %v", t)
	}
	return nil
}
<|endoftext|>


<commit_before>package db

import (
	"database/sql"
	"fmt"
	"strconv"
	"sync"
	"time"

	"github.com/OpenBazaar/openbazaar-go/repo"
)

type ChatDB struct {
	modelStore
}

func NewChatStore(db *sql.DB, lock *sync.Mutex) repo.ChatStore {
	return &ChatDB{modelStore{db, lock}}
}

func (c *ChatDB) Put(messageId string, peerId string, subject string, message string, timestamp time.Time, read bool, outgoing bool) error {
	c.lock.Lock()
	defer c.lock.Unlock()

	// timestamp.UnixNano() is undefined when time has a zero value
	if timestamp.IsZero() {
		log.Warningf("putting chat message (%s): recieved zero timestamp, using current time", messageId)
		timestamp = time.Now()
	}

	tx, err := c.db.Begin()
	if err != nil {
		return err
	}
	stm := `insert into chat(messageID, peerID, subject, message, read, timestamp, outgoing) values(?,?,?,?,?,?,?)`
	stmt, err := tx.Prepare(stm)
	if err != nil {
		return err
	}
	readInt := 0
	if read {
		readInt = 1
	}

	outgoingInt := 0
	if outgoing {
		outgoingInt = 1
	}

	defer stmt.Close()
	_, err = stmt.Exec(
		messageId,
		peerId,
		subject,
		message,
		readInt,
		int(timestamp.UnixNano()),
		outgoingInt,
	)
	if err != nil {
		tx.Rollback()
		return err
	}
	tx.Commit()
	return nil
}

func (c *ChatDB) GetConversations() []repo.ChatConversation {
	c.lock.Lock()
	defer c.lock.Unlock()
	var ret []repo.ChatConversation

	stm := "select distinct peerID from chat where subject='' order by timestamp desc;"
	rows, err := c.db.Query(stm)
	if err != nil {
		return ret
	}
	var ids []string
	for rows.Next() {
		var peerId string
		if err := rows.Scan(&peerId); err != nil {
			continue
		}
		ids = append(ids, peerId)

	}
	defer rows.Close()
	for _, peerId := range ids {
		var (
			count  int
			m      string
			ts     int64
			outInt int
			stm    = "select Count(*) from chat where peerID='" + peerId + "' and read=0 and subject='' and outgoing=0;"
			row    = c.db.QueryRow(stm)
		)
		row.Scan(&count)
		stm = "select max(timestamp), message, outgoing from chat where peerID='" + peerId + "' and subject=''"
		row = c.db.QueryRow(stm)
		row.Scan(&ts, &m, &outInt)
		outgoing := false
		if outInt > 0 {
			outgoing = true
		}
		timestamp := repo.NewAPITime(time.Unix(0, ts))
		convo := repo.ChatConversation{
			PeerId:    peerId,
			Unread:    count,
			Last:      m,
			Timestamp: timestamp,
			Outgoing:  outgoing,
		}
		ret = append(ret, convo)
	}
	return ret
}

func (c *ChatDB) GetMessages(peerID string, subject string, offsetId string, limit int) []repo.ChatMessage {
	c.lock.Lock()
	defer c.lock.Unlock()
	var ret []repo.ChatMessage

	var peerStm string
	if peerID != "" {
		peerStm = " and peerID='" + peerID + "'"
	}

	var stm string
	if offsetId != "" {
		stm = "select messageID, peerID, message, read, timestamp, outgoing from chat where subject='" + subject + "'" + peerStm + " and timestamp<(select timestamp from chat where messageID='" + offsetId + "') order by timestamp desc limit " + strconv.Itoa(limit) + " ;"
	} else {
		stm = "select messageID, peerID, message, read, timestamp, outgoing from chat where subject='" + subject + "'" + peerStm + " order by timestamp desc limit " + strconv.Itoa(limit) + ";"
	}
	rows, err := c.db.Query(stm)
	if err != nil {
		log.Error(err)
		return ret
	}
	for rows.Next() {
		var (
			msgID        string
			pid          string
			message      string
			readInt      int
			timestampInt int64
			outgoingInt  int
		)
		if err := rows.Scan(&msgID, &pid, &message, &readInt, &timestampInt, &outgoingInt); err != nil {
			continue
		}
		var read bool
		if readInt == 1 {
			read = true
		}
		var outgoing bool
		if outgoingInt == 1 {
			outgoing = true
		}
		timestamp := repo.NewAPITime(time.Unix(0, timestampInt))
		chatMessage := repo.ChatMessage{
			PeerId:    pid,
			MessageId: msgID,
			Subject:   subject,
			Message:   message,
			Read:      read,
			Timestamp: timestamp,
			Outgoing:  outgoing,
		}
		ret = append(ret, chatMessage)
	}
	return ret
}

func (c *ChatDB) MarkAsRead(peerID string, subject string, outgoing bool, messageId string) (string, bool, error) {
	c.lock.Lock()
	defer c.lock.Unlock()
	updated := false
	outgoingInt := 0
	if outgoing {
		outgoingInt = 1
	}
	var err error
	if messageId != "" {
		stm := "select messageID from chat where peerID=? and subject=? and outgoing=? and read=0 and timestamp<=(select timestamp from chat where messageID=?) limit 1"
		rows, err := c.db.Query(stm, peerID, subject, outgoingInt, messageId)
		if err != nil {
			return "", updated, err
		}
		if rows.Next() {
			updated = true
		}
		rows.Close()
		var tx *sql.Tx
		tx, err = c.db.Begin()
		if err != nil {
			if rErr := tx.Rollback(); rErr != nil {
				return "", updated, fmt.Errorf("create tx: %s (w rollback error: %s)", err, rErr)
			}
			return "", updated, fmt.Errorf("create tx: %s", err)
		}
		var stmt *sql.Stmt
		stmt, err = tx.Prepare("update chat set read=1 where peerID=? and subject=? and outgoing=? and timestamp<=(select timestamp from chat where messageID=?)")
		if err != nil {
			if rErr := tx.Rollback(); rErr != nil {
				return "", updated, fmt.Errorf("prepare chat update: %s (w rollback error: %s)", err, rErr)
			}
			return "", updated, fmt.Errorf("prepare chat update: %s", err)
		}
		_, err = stmt.Exec(peerID, subject, outgoingInt, messageId)
		if err != nil {
			if rErr := tx.Rollback(); rErr != nil {
				return "", updated, fmt.Errorf("update chat record: %s (w rollback error: %s)", err, rErr)
			}
			return "", updated, fmt.Errorf("update chat record: %s", err)
		}
		if err := tx.Commit(); err != nil {
			return "", updated, fmt.Errorf("commit tx: %s", err)
		}
	} else {
		var peerStm string
		if peerID != "" {
			peerStm = " and peerID=?"
		}

		stm := "select messageID from chat where subject=?" + peerStm + " and outgoing=? and read=0 limit 1"
		var rows *sql.Rows
		var err error
		if peerID != "" {
			rows, err = c.db.Query(stm, subject, peerID, outgoingInt)
		} else {
			rows, err = c.db.Query(stm, subject, outgoingInt)
		}
		if err != nil {
			return "", updated, err
		}
		if rows.Next() {
			updated = true
		}
		rows.Close()
		var tx *sql.Tx
		tx, err = c.db.Begin()
		if err != nil {
			if rErr := tx.Rollback(); rErr != nil {
				return "", updated, fmt.Errorf("create tx: %s (w rollback error: %s)", err, rErr)
			}
			return "", updated, fmt.Errorf("create tx: %s", err)
		}
		var stmt *sql.Stmt
		stmt, err = tx.Prepare("update chat set read=1 where subject=?" + peerStm + " and outgoing=?")
		if err != nil {
			if rErr := tx.Rollback(); rErr != nil {
				return "", updated, fmt.Errorf("prepare chat update: %s (w rollback error: %s)", err, rErr)
			}
			return "", updated, fmt.Errorf("prepare chat update: %s", err)
		}
		if peerID != "" {
			_, err = stmt.Exec(subject, peerID, outgoingInt)
		} else {
			_, err = stmt.Exec(subject, outgoingInt)
		}
		if err != nil {
			if rErr := tx.Rollback(); rErr != nil {
				return "", updated, fmt.Errorf("update chat record: %s (w rollback error: %s)", err, rErr)
			}
			return "", updated, fmt.Errorf("update chat record: %s", err)
		}
		if err := tx.Commit(); err != nil {
			return "", updated, fmt.Errorf("commit tx: %s", err)
		}
	}
	var peerStm string

	if peerID != "" {
		peerStm = " and peerID=?"
	}
	stmt2, err := c.db.Prepare("select max(timestamp), messageID from chat where subject=?" + peerStm + " and outgoing=?")
	if err != nil {
		return "", updated, err
	}
	defer stmt2.Close()
	var (
		timestamp sql.NullInt64
		msgId     sql.NullString
	)
	if peerID != "" {
		err = stmt2.QueryRow(subject, peerID, outgoingInt).Scan(&timestamp, &msgId)
	} else {
		err = stmt2.QueryRow(subject, outgoingInt).Scan(&timestamp, &msgId)
	}
	if err != nil {
		return "", updated, err
	}
	return msgId.String, updated, nil
}

func (c *ChatDB) GetUnreadCount(subject string) (int, error) {
	stm := "select Count(*) from chat where read=0 and subject=? and outgoing=0;"
	row := c.db.QueryRow(stm, subject)
	var count int
	err := row.Scan(&count)
	if err != nil {
		return 0, err
	}
	return count, nil
}

func (c *ChatDB) DeleteMessage(msgID string) error {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.db.Exec("delete from chat where messageID=?", msgID)
	return nil
}

func (c *ChatDB) DeleteConversation(peerId string) error {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.db.Exec("delete from chat where peerId=? and subject=''", peerId)
	return nil
}
<commit_msg>[#1169] Remove unused variable; minor cleanup<commit_after>package db

import (
	"database/sql"
	"fmt"
	"strconv"
	"sync"
	"time"

	"github.com/OpenBazaar/openbazaar-go/repo"
)

type ChatDB struct {
	modelStore
}

func NewChatStore(db *sql.DB, lock *sync.Mutex) repo.ChatStore {
	return &ChatDB{modelStore{db, lock}}
}

func (c *ChatDB) Put(messageId string, peerId string, subject string, message string, timestamp time.Time, read bool, outgoing bool) error {
	c.lock.Lock()
	defer c.lock.Unlock()

	// timestamp.UnixNano() is undefined when time has a zero value
	if timestamp.IsZero() {
		log.Warningf("putting chat message (%s): recieved zero timestamp, using current time", messageId)
		timestamp = time.Now()
	}

	tx, err := c.db.Begin()
	if err != nil {
		return err
	}
	stm := `insert into chat(messageID, peerID, subject, message, read, timestamp, outgoing) values(?,?,?,?,?,?,?)`
	stmt, err := tx.Prepare(stm)
	if err != nil {
		return err
	}
	readInt := 0
	if read {
		readInt = 1
	}

	outgoingInt := 0
	if outgoing {
		outgoingInt = 1
	}

	defer stmt.Close()
	_, err = stmt.Exec(
		messageId,
		peerId,
		subject,
		message,
		readInt,
		int(timestamp.UnixNano()),
		outgoingInt,
	)
	if err != nil {
		tx.Rollback()
		return err
	}
	tx.Commit()
	return nil
}

func (c *ChatDB) GetConversations() []repo.ChatConversation {
	c.lock.Lock()
	defer c.lock.Unlock()
	var ret []repo.ChatConversation

	stm := "select distinct peerID from chat where subject='' order by timestamp desc;"
	rows, err := c.db.Query(stm)
	if err != nil {
		return ret
	}
	var ids []string
	for rows.Next() {
		var peerId string
		if err := rows.Scan(&peerId); err != nil {
			continue
		}
		ids = append(ids, peerId)

	}
	defer rows.Close()
	for _, peerId := range ids {
		var (
			count  int
			m      string
			ts     int64
			outInt int
			stm    = "select Count(*) from chat where peerID='" + peerId + "' and read=0 and subject='' and outgoing=0;"
			row    = c.db.QueryRow(stm)
		)
		row.Scan(&count)
		stm = "select max(timestamp), message, outgoing from chat where peerID='" + peerId + "' and subject=''"
		row = c.db.QueryRow(stm)
		row.Scan(&ts, &m, &outInt)
		outgoing := false
		if outInt > 0 {
			outgoing = true
		}
		timestamp := repo.NewAPITime(time.Unix(0, ts))
		convo := repo.ChatConversation{
			PeerId:    peerId,
			Unread:    count,
			Last:      m,
			Timestamp: timestamp,
			Outgoing:  outgoing,
		}
		ret = append(ret, convo)
	}
	return ret
}

func (c *ChatDB) GetMessages(peerID string, subject string, offsetId string, limit int) []repo.ChatMessage {
	c.lock.Lock()
	defer c.lock.Unlock()
	var ret []repo.ChatMessage

	var peerStm string
	if peerID != "" {
		peerStm = " and peerID='" + peerID + "'"
	}

	var stm string
	if offsetId != "" {
		stm = "select messageID, peerID, message, read, timestamp, outgoing from chat where subject='" + subject + "'" + peerStm + " and timestamp<(select timestamp from chat where messageID='" + offsetId + "') order by timestamp desc limit " + strconv.Itoa(limit) + " ;"
	} else {
		stm = "select messageID, peerID, message, read, timestamp, outgoing from chat where subject='" + subject + "'" + peerStm + " order by timestamp desc limit " + strconv.Itoa(limit) + ";"
	}
	rows, err := c.db.Query(stm)
	if err != nil {
		log.Error(err)
		return ret
	}
	for rows.Next() {
		var (
			msgID        string
			pid          string
			message      string
			readInt      int
			timestampInt int64
			outgoingInt  int
		)
		if err := rows.Scan(&msgID, &pid, &message, &readInt, &timestampInt, &outgoingInt); err != nil {
			continue
		}
		var read bool
		if readInt == 1 {
			read = true
		}
		var outgoing bool
		if outgoingInt == 1 {
			outgoing = true
		}
		timestamp := repo.NewAPITime(time.Unix(0, timestampInt))
		chatMessage := repo.ChatMessage{
			PeerId:    pid,
			MessageId: msgID,
			Subject:   subject,
			Message:   message,
			Read:      read,
			Timestamp: timestamp,
			Outgoing:  outgoing,
		}
		ret = append(ret, chatMessage)
	}
	return ret
}

func (c *ChatDB) MarkAsRead(peerID string, subject string, outgoing bool, messageId string) (string, bool, error) {
	c.lock.Lock()
	defer c.lock.Unlock()
	updated := false
	outgoingInt := 0
	if outgoing {
		outgoingInt = 1
	}
	if messageId != "" {
		stm := "select messageID from chat where peerID=? and subject=? and outgoing=? and read=0 and timestamp<=(select timestamp from chat where messageID=?) limit 1"
		rows, err := c.db.Query(stm, peerID, subject, outgoingInt, messageId)
		if err != nil {
			return "", updated, err
		}
		if rows.Next() {
			updated = true
		}
		rows.Close()
		var tx *sql.Tx
		tx, err = c.db.Begin()
		if err != nil {
			if rErr := tx.Rollback(); rErr != nil {
				return "", updated, fmt.Errorf("create tx: %s (w rollback error: %s)", err, rErr)
			}
			return "", updated, fmt.Errorf("create tx: %s", err)
		}
		var stmt *sql.Stmt
		stmt, err = tx.Prepare("update chat set read=1 where peerID=? and subject=? and outgoing=? and timestamp<=(select timestamp from chat where messageID=?)")
		if err != nil {
			if rErr := tx.Rollback(); rErr != nil {
				return "", updated, fmt.Errorf("prepare chat update: %s (w rollback error: %s)", err, rErr)
			}
			return "", updated, fmt.Errorf("prepare chat update: %s", err)
		}
		_, err = stmt.Exec(peerID, subject, outgoingInt, messageId)
		if err != nil {
			if rErr := tx.Rollback(); rErr != nil {
				return "", updated, fmt.Errorf("update chat record: %s (w rollback error: %s)", err, rErr)
			}
			return "", updated, fmt.Errorf("update chat record: %s", err)
		}
		if err := tx.Commit(); err != nil {
			return "", updated, fmt.Errorf("commit tx: %s", err)
		}
	} else {
		var (
			peerStm string
			stm     = "select messageID from chat where subject=?" + peerStm + " and outgoing=? and read=0 limit 1"
		)
		if peerID != "" {
			peerStm = " and peerID=?"
		}

		var rows *sql.Rows
		var err error
		if peerID != "" {
			rows, err = c.db.Query(stm, subject, peerID, outgoingInt)
		} else {
			rows, err = c.db.Query(stm, subject, outgoingInt)
		}
		if err != nil {
			return "", updated, err
		}
		if rows.Next() {
			updated = true
		}
		rows.Close()
		var tx *sql.Tx
		tx, err = c.db.Begin()
		if err != nil {
			if rErr := tx.Rollback(); rErr != nil {
				return "", updated, fmt.Errorf("create tx: %s (w rollback error: %s)", err, rErr)
			}
			return "", updated, fmt.Errorf("create tx: %s", err)
		}
		var stmt *sql.Stmt
		stmt, err = tx.Prepare("update chat set read=1 where subject=?" + peerStm + " and outgoing=?")
		if err != nil {
			if rErr := tx.Rollback(); rErr != nil {
				return "", updated, fmt.Errorf("prepare chat update: %s (w rollback error: %s)", err, rErr)
			}
			return "", updated, fmt.Errorf("prepare chat update: %s", err)
		}
		if peerID != "" {
			_, err = stmt.Exec(subject, peerID, outgoingInt)
		} else {
			_, err = stmt.Exec(subject, outgoingInt)
		}
		if err != nil {
			if rErr := tx.Rollback(); rErr != nil {
				return "", updated, fmt.Errorf("update chat record: %s (w rollback error: %s)", err, rErr)
			}
			return "", updated, fmt.Errorf("update chat record: %s", err)
		}
		if err := tx.Commit(); err != nil {
			return "", updated, fmt.Errorf("commit tx: %s", err)
		}
	}
	var peerStm string

	if peerID != "" {
		peerStm = " and peerID=?"
	}
	stmt2, err := c.db.Prepare("select max(timestamp), messageID from chat where subject=?" + peerStm + " and outgoing=?")
	if err != nil {
		return "", updated, err
	}
	defer stmt2.Close()
	var (
		timestamp sql.NullInt64
		msgId     sql.NullString
	)
	if peerID != "" {
		err = stmt2.QueryRow(subject, peerID, outgoingInt).Scan(&timestamp, &msgId)
	} else {
		err = stmt2.QueryRow(subject, outgoingInt).Scan(&timestamp, &msgId)
	}
	if err != nil {
		return "", updated, err
	}
	return msgId.String, updated, nil
}

func (c *ChatDB) GetUnreadCount(subject string) (int, error) {
	stm := "select Count(*) from chat where read=0 and subject=? and outgoing=0;"
	row := c.db.QueryRow(stm, subject)
	var count int
	err := row.Scan(&count)
	if err != nil {
		return 0, err
	}
	return count, nil
}

func (c *ChatDB) DeleteMessage(msgID string) error {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.db.Exec("delete from chat where messageID=?", msgID)
	return nil
}

func (c *ChatDB) DeleteConversation(peerId string) error {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.db.Exec("delete from chat where peerId=? and subject=''", peerId)
	return nil
}
<|endoftext|>


<commit_before>package search

import (
	"encoding/json"
	"sort"
	"strings"
	"sync"

	"github.com/StackExchange/tcollector/opentsdb"
)

/* Questions we want to ask:
1) what metrics are available for a tag set?
2) what tag keys are available for a metric?
3) what tag values are available for a metric+tag key query?
*/

type MetricTagSet struct {
	Metric string          `json:"metric"`
	Tags   opentsdb.TagSet `json:"tags"`
}

func (mts *MetricTagSet) key() string {
	s := make([]string, len(mts.Tags))
	i := 0
	for k, v := range mts.Tags {
		s[i] = strings.Join([]string{k, v}, "")
		i++
	}
	sort.Strings(s)
	s = append(s, mts.Metric)
	return strings.Join(s, "")
}

type QMap map[Query]Present
type SMap map[string]Present
type MTSMap map[string]MetricTagSet

var (
	// tagk + tagv -> metrics
	Metric = make(QMap)
	// metric -> tag keys
	Tagk = make(SMap)
	// metric + tagk -> tag values
	Tagv = make(QMap)
	// Each Record
	MetricTags = make(MTSMap)

	lock = sync.RWMutex{}
)

type Present map[string]interface{}

type Query struct {
	A, B string
}

var (
	dc = make(chan *opentsdb.DataPoint)
)

func init() {
	go Process(dc)
}

func HTTPExtract(body []byte) {
	var dp opentsdb.DataPoint
	var mdp opentsdb.MultiDataPoint
	var err error
	if err = json.Unmarshal(body, &dp); err == nil {
		mdp = append(mdp, &dp)
	} else if err := json.Unmarshal(body, &mdp); err != nil {
		return
	}
	for _, d := range mdp {
		dc <- d
	}
}

func Process(c chan *opentsdb.DataPoint) {
	for dp := range c {
		go func(dp *opentsdb.DataPoint) {
			lock.Lock()
			defer lock.Unlock()
			var mts MetricTagSet
			mts.Metric = dp.Metric
			mts.Tags = dp.Tags
			MetricTags[mts.key()] = mts
			var q Query
			for k, v := range dp.Tags {
				q.A, q.B = k, v
				if _, ok := Metric[q]; !ok {
					Metric[q] = make(Present)
				}
				Metric[q][dp.Metric] = nil

				if _, ok := Tagk[dp.Metric]; !ok {
					Tagk[dp.Metric] = make(Present)
				}
				Tagk[dp.Metric][k] = nil

				q.A, q.B = dp.Metric, k
				if _, ok := Tagv[q]; !ok {
					Tagv[q] = make(Present)
				}
				Tagv[q][v] = nil
			}
		}(dp)
	}
}

func UniqueMetrics() []string {
	lock.RLock()
	defer lock.RUnlock()
	metrics := make([]string, len(Tagk))
	i := 0
	for k, _ := range Tagk {
		metrics[i] = k
		i++
	}
	sort.Strings(metrics)
	return metrics
}

func TagValuesByTagKey(tagk string) []string {
	lock.RLock()
	defer lock.RUnlock()
	tagvset := make(map[string]bool)
	for _, metric := range UniqueMetrics() {
		for _, tagv := range TagValuesByMetricTagKey(metric, tagk) {
			tagvset[tagv] = true
		}
	}
	tagvs := make([]string, len(tagvset))
	i := 0
	for k, _ := range tagvset {
		tagvs[i] = k
		i++
	}
	sort.Strings(tagvs)
	return tagvs

}

func MetricsByTagPair(tagk, tagv string) []string {
	lock.RLock()
	defer lock.RUnlock()
	var r []string
	for k := range Metric[Query{tagk, tagv}] {
		r = append(r, k)
	}
	return r
}

func TagKeysByMetric(metric string) []string {
	lock.RLock()
	defer lock.RUnlock()
	var r []string
	for k := range Tagk[metric] {
		r = append(r, k)
	}
	return r
}

func TagValuesByMetricTagKey(metric, tagk string) []string {
	lock.RLock()
	defer lock.RUnlock()
	var r []string
	for k := range Tagv[Query{metric, tagk}] {
		r = append(r, k)
	}
	return r
}

func FilteredTagValuesByMetricTagKey(metric, tagk string, tsf map[string]string) []string {
	lock.RLock()
	defer lock.RUnlock()
	tagvset := make(map[string]bool)
	for _, mts := range MetricTags {
		if metric == mts.Metric {
			match := true
			if tagv, ok := mts.Tags[tagk]; ok {
				for tpk, tpv := range tsf {
					if v, ok := mts.Tags[tpk]; ok {
						if !(v == tpv) {
							match = false
						}
					} else {
						match = false
					}
				}
				if match {
					tagvset[tagv] = true
				}
			}
		}
	}
	tagvs := make([]string, len(tagvset))
	i := 0
	for k, _ := range tagvset {
		tagvs[i] = k
		i++
	}
	sort.Strings(tagvs)
	return tagvs
}
<commit_msg>Safer serialization method for MetricTagSet.key()<commit_after>package search

import (
	"encoding/json"
	"fmt"
	"sort"
	"strings"
	"sync"

	"github.com/StackExchange/tcollector/opentsdb"
)

/* Questions we want to ask:
1) what metrics are available for a tag set?
2) what tag keys are available for a metric?
3) what tag values are available for a metric+tag key query?
*/

type MetricTagSet struct {
	Metric string          `json:"metric"`
	Tags   opentsdb.TagSet `json:"tags"`
}

func (mts *MetricTagSet) key() string {
	s := make([]string, len(mts.Tags))
	i := 0
	for k, v := range mts.Tags {
		s[i] = fmt.Sprintf("%v=%v,", k, v)
		i++
	}
	sort.Strings(s)
	s = append(s, fmt.Sprintf("metric=%v", mts.Metric))
	return strings.Join(s, "")
}

type QMap map[Query]Present
type SMap map[string]Present
type MTSMap map[string]MetricTagSet

var (
	// tagk + tagv -> metrics
	Metric = make(QMap)
	// metric -> tag keys
	Tagk = make(SMap)
	// metric + tagk -> tag values
	Tagv = make(QMap)
	// Each Record
	MetricTags = make(MTSMap)

	lock = sync.RWMutex{}
)

type Present map[string]interface{}

type Query struct {
	A, B string
}

var (
	dc = make(chan *opentsdb.DataPoint)
)

func init() {
	go Process(dc)
}

func HTTPExtract(body []byte) {
	var dp opentsdb.DataPoint
	var mdp opentsdb.MultiDataPoint
	var err error
	if err = json.Unmarshal(body, &dp); err == nil {
		mdp = append(mdp, &dp)
	} else if err := json.Unmarshal(body, &mdp); err != nil {
		return
	}
	for _, d := range mdp {
		dc <- d
	}
}

func Process(c chan *opentsdb.DataPoint) {
	for dp := range c {
		go func(dp *opentsdb.DataPoint) {
			lock.Lock()
			defer lock.Unlock()
			var mts MetricTagSet
			mts.Metric = dp.Metric
			mts.Tags = dp.Tags
			MetricTags[mts.key()] = mts
			var q Query
			for k, v := range dp.Tags {
				q.A, q.B = k, v
				if _, ok := Metric[q]; !ok {
					Metric[q] = make(Present)
				}
				Metric[q][dp.Metric] = nil

				if _, ok := Tagk[dp.Metric]; !ok {
					Tagk[dp.Metric] = make(Present)
				}
				Tagk[dp.Metric][k] = nil

				q.A, q.B = dp.Metric, k
				if _, ok := Tagv[q]; !ok {
					Tagv[q] = make(Present)
				}
				Tagv[q][v] = nil
			}
		}(dp)
	}
}

func UniqueMetrics() []string {
	lock.RLock()
	defer lock.RUnlock()
	metrics := make([]string, len(Tagk))
	i := 0
	for k, _ := range Tagk {
		metrics[i] = k
		i++
	}
	sort.Strings(metrics)
	return metrics
}

func TagValuesByTagKey(tagk string) []string {
	lock.RLock()
	defer lock.RUnlock()
	tagvset := make(map[string]bool)
	for _, metric := range UniqueMetrics() {
		for _, tagv := range TagValuesByMetricTagKey(metric, tagk) {
			tagvset[tagv] = true
		}
	}
	tagvs := make([]string, len(tagvset))
	i := 0
	for k, _ := range tagvset {
		tagvs[i] = k
		i++
	}
	sort.Strings(tagvs)
	return tagvs

}

func MetricsByTagPair(tagk, tagv string) []string {
	lock.RLock()
	defer lock.RUnlock()
	var r []string
	for k := range Metric[Query{tagk, tagv}] {
		r = append(r, k)
	}
	return r
}

func TagKeysByMetric(metric string) []string {
	lock.RLock()
	defer lock.RUnlock()
	var r []string
	for k := range Tagk[metric] {
		r = append(r, k)
	}
	return r
}

func TagValuesByMetricTagKey(metric, tagk string) []string {
	lock.RLock()
	defer lock.RUnlock()
	var r []string
	for k := range Tagv[Query{metric, tagk}] {
		r = append(r, k)
	}
	return r
}

func FilteredTagValuesByMetricTagKey(metric, tagk string, tsf map[string]string) []string {
	lock.RLock()
	defer lock.RUnlock()
	tagvset := make(map[string]bool)
	for _, mts := range MetricTags {
		if metric == mts.Metric {
			match := true
			if tagv, ok := mts.Tags[tagk]; ok {
				for tpk, tpv := range tsf {
					if v, ok := mts.Tags[tpk]; ok {
						if !(v == tpv) {
							match = false
						}
					} else {
						match = false
					}
				}
				if match {
					tagvset[tagv] = true
				}
			}
		}
	}
	tagvs := make([]string, len(tagvset))
	i := 0
	for k, _ := range tagvset {
		tagvs[i] = k
		i++
	}
	sort.Strings(tagvs)
	return tagvs
}
<|endoftext|>


<commit_before>package search

import (
	"fmt"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/StackExchange/bosun/_third_party/github.com/StackExchange/scollector/collect"
	"github.com/StackExchange/bosun/_third_party/github.com/StackExchange/scollector/opentsdb"
)

/* Questions we want to ask:
1) what metrics are available for a tag set?
2) what tag keys are available for a metric?
3) what tag values are available for a metric+tag key query?
*/

type MetricTagSet struct {
	Metric string          `json:"metric"`
	Tags   opentsdb.TagSet `json:"tags"`
}

func (mts *MetricTagSet) key() string {
	s := make([]string, len(mts.Tags))
	i := 0
	for k, v := range mts.Tags {
		s[i] = fmt.Sprintf("%v=%v,", k, v)
		i++
	}
	sort.Strings(s)
	s = append(s, fmt.Sprintf("metric=%v", mts.Metric))
	return strings.Join(s, "")
}

type QMap map[Query]Present
type SMap map[string]Present
type MTSMap map[string]MetricTagSet

var (
	// tagk + tagv -> metrics
	Metric = make(QMap)
	// metric -> tag keys
	Tagk = make(SMap)
	// metric + tagk -> tag values
	Tagv = make(QMap)
	// Each Record
	MetricTags = make(MTSMap)

	Lock = sync.RWMutex{}
)

type Present map[string]interface{}

type Query struct {
	A, B string
}

var (
	dc = make(chan opentsdb.MultiDataPoint)
)

func init() {
	go Process()
}

// HTTPExtract populates the search indexes with OpenTSDB tags and metrics from
// body. body is a JSON string of an OpenTSDB v2 /api/put request. body may be
// gzipped.
func HTTPExtract(mdp opentsdb.MultiDataPoint) {
	collect.Add("search.puts_relayed", nil, 1)
	collect.Add("search.datapoints_relayed", nil, float64(len(mdp)))
	select {
	case dc <- mdp:
	case <-time.After(time.Millisecond * 100):
		collect.Add("search.timeout_drop", nil, 1)
	}
}

func Process() {
	for mdp := range dc {
		Lock.Lock()
		for _, dp := range mdp {
			var mts MetricTagSet
			mts.Metric = dp.Metric
			mts.Tags = dp.Tags
			MetricTags[mts.key()] = mts
			var q Query
			for k, v := range dp.Tags {
				q.A, q.B = k, v
				if _, ok := Metric[q]; !ok {
					Metric[q] = make(Present)
				}
				Metric[q][dp.Metric] = nil

				if _, ok := Tagk[dp.Metric]; !ok {
					Tagk[dp.Metric] = make(Present)
				}
				Tagk[dp.Metric][k] = nil

				q.A, q.B = dp.Metric, k
				if _, ok := Tagv[q]; !ok {
					Tagv[q] = make(Present)
				}
				Tagv[q][v] = nil
			}
		}
		Lock.Unlock()
	}
}

func UniqueMetrics() []string {
	Lock.RLock()
	defer Lock.RUnlock()
	metrics := make([]string, len(Tagk))
	i := 0
	for k := range Tagk {
		metrics[i] = k
		i++
	}
	sort.Strings(metrics)
	return metrics
}

func TagValuesByTagKey(tagk string) []string {
	Lock.RLock()
	defer Lock.RUnlock()
	tagvset := make(map[string]bool)
	for _, metric := range UniqueMetrics() {
		for _, tagv := range TagValuesByMetricTagKey(metric, tagk) {
			tagvset[tagv] = true
		}
	}
	tagvs := make([]string, len(tagvset))
	i := 0
	for k := range tagvset {
		tagvs[i] = k
		i++
	}
	sort.Strings(tagvs)
	return tagvs

}

func MetricsByTagPair(tagk, tagv string) []string {
	Lock.RLock()
	defer Lock.RUnlock()
	r := make([]string, 0)
	for k := range Metric[Query{tagk, tagv}] {
		r = append(r, k)
	}
	sort.Strings(r)
	return r
}

func TagKeysByMetric(metric string) []string {
	Lock.RLock()
	defer Lock.RUnlock()
	var r []string
	for k := range Tagk[metric] {
		r = append(r, k)
	}
	sort.Strings(r)
	return r
}

func TagValuesByMetricTagKey(metric, tagk string) []string {
	Lock.RLock()
	defer Lock.RUnlock()
	var r []string
	for k := range Tagv[Query{metric, tagk}] {
		r = append(r, k)
	}
	sort.Strings(r)
	return r
}

func FilteredTagValuesByMetricTagKey(metric, tagk string, tsf map[string]string) []string {
	Lock.RLock()
	defer Lock.RUnlock()
	tagvset := make(map[string]bool)
	for _, mts := range MetricTags {
		if metric == mts.Metric {
			match := true
			if tagv, ok := mts.Tags[tagk]; ok {
				for tpk, tpv := range tsf {
					if v, ok := mts.Tags[tpk]; ok {
						if !(v == tpv) {
							match = false
						}
					} else {
						match = false
					}
				}
				if match {
					tagvset[tagv] = true
				}
			}
		}
	}
	tagvs := make([]string, len(tagvset))
	i := 0
	for k := range tagvset {
		tagvs[i] = k
		i++
	}
	sort.Strings(tagvs)
	return tagvs
}
<commit_msg>Don't double lock<commit_after>package search

import (
	"fmt"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/StackExchange/bosun/_third_party/github.com/StackExchange/scollector/collect"
	"github.com/StackExchange/bosun/_third_party/github.com/StackExchange/scollector/opentsdb"
)

/* Questions we want to ask:
1) what metrics are available for a tag set?
2) what tag keys are available for a metric?
3) what tag values are available for a metric+tag key query?
*/

type MetricTagSet struct {
	Metric string          `json:"metric"`
	Tags   opentsdb.TagSet `json:"tags"`
}

func (mts *MetricTagSet) key() string {
	s := make([]string, len(mts.Tags))
	i := 0
	for k, v := range mts.Tags {
		s[i] = fmt.Sprintf("%v=%v,", k, v)
		i++
	}
	sort.Strings(s)
	s = append(s, fmt.Sprintf("metric=%v", mts.Metric))
	return strings.Join(s, "")
}

type QMap map[Query]Present
type SMap map[string]Present
type MTSMap map[string]MetricTagSet

var (
	// tagk + tagv -> metrics
	Metric = make(QMap)
	// metric -> tag keys
	Tagk = make(SMap)
	// metric + tagk -> tag values
	Tagv = make(QMap)
	// Each Record
	MetricTags = make(MTSMap)

	Lock = sync.RWMutex{}
)

type Present map[string]interface{}

type Query struct {
	A, B string
}

var (
	dc = make(chan opentsdb.MultiDataPoint)
)

func init() {
	go Process()
}

// HTTPExtract populates the search indexes with OpenTSDB tags and metrics from
// body. body is a JSON string of an OpenTSDB v2 /api/put request. body may be
// gzipped.
func HTTPExtract(mdp opentsdb.MultiDataPoint) {
	collect.Add("search.puts_relayed", nil, 1)
	collect.Add("search.datapoints_relayed", nil, float64(len(mdp)))
	select {
	case dc <- mdp:
	case <-time.After(time.Millisecond * 100):
		collect.Add("search.timeout_drop", nil, 1)
	}
}

func Process() {
	for mdp := range dc {
		Lock.Lock()
		for _, dp := range mdp {
			var mts MetricTagSet
			mts.Metric = dp.Metric
			mts.Tags = dp.Tags
			MetricTags[mts.key()] = mts
			var q Query
			for k, v := range dp.Tags {
				q.A, q.B = k, v
				if _, ok := Metric[q]; !ok {
					Metric[q] = make(Present)
				}
				Metric[q][dp.Metric] = nil

				if _, ok := Tagk[dp.Metric]; !ok {
					Tagk[dp.Metric] = make(Present)
				}
				Tagk[dp.Metric][k] = nil

				q.A, q.B = dp.Metric, k
				if _, ok := Tagv[q]; !ok {
					Tagv[q] = make(Present)
				}
				Tagv[q][v] = nil
			}
		}
		Lock.Unlock()
	}
}

func UniqueMetrics() []string {
	Lock.RLock()
	defer Lock.RUnlock()
	metrics := make([]string, len(Tagk))
	i := 0
	for k := range Tagk {
		metrics[i] = k
		i++
	}
	sort.Strings(metrics)
	return metrics
}

func TagValuesByTagKey(tagk string) []string {
	Lock.RLock()
	defer Lock.RUnlock()
	tagvset := make(map[string]bool)
	for _, metric := range UniqueMetrics() {
		for _, tagv := range tagValuesByMetricTagKey(metric, tagk) {
			tagvset[tagv] = true
		}
	}
	tagvs := make([]string, len(tagvset))
	i := 0
	for k := range tagvset {
		tagvs[i] = k
		i++
	}
	sort.Strings(tagvs)
	return tagvs
}

func MetricsByTagPair(tagk, tagv string) []string {
	Lock.RLock()
	defer Lock.RUnlock()
	r := make([]string, 0)
	for k := range Metric[Query{tagk, tagv}] {
		r = append(r, k)
	}
	sort.Strings(r)
	return r
}

func TagKeysByMetric(metric string) []string {
	Lock.RLock()
	defer Lock.RUnlock()
	var r []string
	for k := range Tagk[metric] {
		r = append(r, k)
	}
	sort.Strings(r)
	return r
}

func tagValuesByMetricTagKey(metric, tagk string) []string {
	var r []string
	for k := range Tagv[Query{metric, tagk}] {
		r = append(r, k)
	}
	sort.Strings(r)
	return r
}

func TagValuesByMetricTagKey(metric, tagk string) []string {
	Lock.RLock()
	defer Lock.RUnlock()
	return tagValuesByMetricTagKey(metric, tagk)
}

func FilteredTagValuesByMetricTagKey(metric, tagk string, tsf map[string]string) []string {
	Lock.RLock()
	defer Lock.RUnlock()
	tagvset := make(map[string]bool)
	for _, mts := range MetricTags {
		if metric == mts.Metric {
			match := true
			if tagv, ok := mts.Tags[tagk]; ok {
				for tpk, tpv := range tsf {
					if v, ok := mts.Tags[tpk]; ok {
						if !(v == tpv) {
							match = false
						}
					} else {
						match = false
					}
				}
				if match {
					tagvset[tagv] = true
				}
			}
		}
	}
	tagvs := make([]string, len(tagvset))
	i := 0
	for k := range tagvset {
		tagvs[i] = k
		i++
	}
	sort.Strings(tagvs)
	return tagvs
}
<|endoftext|>


<commit_before>package search

import (
	"errors"
	"strings"
)

import "fmt"

const (
	lines = `line 1
	lien 2
	line3
line 4
line 5
Nope
Still nope
Really, nope`
)

//Result describes the results of a search
type Result struct {
	Hits    int
	Lines   []int
	Pattern string
	index   int //the current index i
}

//NewSearch searchs in a multiline string thos lines that match the given pattern,
//it returns:
//* the number of hits (lines)
//* the line index
func NewSearch(text [][]rune, pattern string) (*Result, error) {
	if text != nil {
		sr := &Result{Pattern: pattern, index: -1}
		for i, l := range text {
			line := string(l)
			if strings.Contains(line, pattern) {
				sr.Hits++
				sr.Lines = append(sr.Lines, i)
			}
		}
		return sr, nil
	}
	return nil, errors.New("Cannot search in an empty text.")

	/*reader := strings.NewReader(s)
	scanner := bufio.NewScanner(reader)
	line := 0

	for scanner.Scan() {
		if strings.Contains(scanner.Text(), pattern) {
			sr.Hits++
			sr.Lines = append(sr.Lines, line)
		}
		line++
	}
	if scanner.Err() != nil {
		return nil, scanner.Err()
	}
	return sr, nil*/
}

func (result *Result) String() string {
	if result.Hits > 0 {
		return fmt.Sprintf("Pattern found %d times", result.Hits)
	}
	return "Pattern not found"
}

//InitialLine sets the position for iterating the search results on the first line
//that is either has the same value or is the closest from 0 to the given line number.
//So, for a result that found that lines (1, 3, 5) were a hit:
// * InitialLine(-1) will set the internal iteration index at 0 (the default starting index)
// * InitialLine(3) will set the internal iteration index at 1.
// * InitialLine(4) will set the internal iteration index at 1.
// * InitialLine(10) will set the internal iteration index at 2.
func (result *Result) InitialLine(lineNumber int) error {
	if result.Lines == nil {
		return nohitsError()
	}
	candidate := 0
	for i, line := range result.Lines {
		if line < lineNumber {
			candidate = i
		} else {
			break
		}
	}
	result.index = candidate
	return nil
}

//NextLine returns the previous line while iterating the search results.
//So, for a result that found that lines (1, 3, 5) were a hit:
// *NextLine() should give 1
// *NextLine() should give 3
// *NextLine() should give 5
// *NextLine() should give 1
func (result *Result) NextLine() (int, error) {
	if result.Lines == nil {
		return -1, nohitsError()
	}
	result.index++
	if result.index >= len(result.Lines) {
		result.index = 0
	}
	return result.Lines[result.index], nil
}

//PreviousLine returns the previous line while iterating the search results.
//So, for a result that found that lines (1, 3, 5) were a hit:
// * PreviousLine() should give 5
// * PreviousLine() should give 3
// * PreviousLine() should give 1
// * PreviousLine() should give 5
func (result *Result) PreviousLine() (int, error) {
	if result.Lines == nil {
		return -1, nohitsError()
	}
	result.index--
	if result.index < 0 {
		result.index = len(result.Lines) - 1
	}
	return result.Lines[result.index], nil
}

func nohitsError() error {
	return errors.New("Trying to iterate through the search result when there are no hits")
}
<commit_msg>Minor<commit_after>package search

import (
	"errors"
	"strings"
)

import "fmt"

const (
	lines = `line 1
	lien 2
	line3
line 4
line 5
Nope
Still nope
Really, nope`
)

//Result describes the results of a search
type Result struct {
	Hits    int
	Lines   []int
	Pattern string
	index   int //the current index i to iterate Lines
}

//NewSearch searchs in a multiline string for thoseslines that match the given pattern,
//it returns:
//* the number of hits (lines)
//* the line index
func NewSearch(text [][]rune, pattern string) (*Result, error) {
	if text != nil {
		sr := &Result{Pattern: pattern, index: -1}
		for i, l := range text {
			line := string(l)
			if strings.Contains(line, pattern) {
				sr.Hits++
				sr.Lines = append(sr.Lines, i)
			}
		}
		return sr, nil
	}
	return nil, errors.New("Nothing to search in an empty text.")
}

func (result *Result) String() string {
	if result.Hits > 0 {
		return fmt.Sprintf("Pattern found %d times", result.Hits)
	}
	return "Pattern not found"
}

//InitialLine sets the position for iterating the search results on the first line
//that is either has the same value or is the closest from 0 to the given line number.
//So, for a result that found that lines (1, 3, 5) were a hit:
// * InitialLine(-1) will set the internal iteration index at 0 (the default starting index)
// * InitialLine(3) will set the internal iteration index at 1.
// * InitialLine(4) will set the internal iteration index at 1.
// * InitialLine(10) will set the internal iteration index at 2.
func (result *Result) InitialLine(lineNumber int) error {
	if result.Lines == nil {
		return nohitsError()
	}
	candidate := 0
	for i, line := range result.Lines {
		if line < lineNumber {
			candidate = i
		} else {
			break
		}
	}
	result.index = candidate
	return nil
}

//NextLine returns the previous line while iterating the search results.
//So, for a result that found that lines (1, 3, 5) were a hit:
// *NextLine() should give 1
// *NextLine() should give 3
// *NextLine() should give 5
// *NextLine() should give 1
func (result *Result) NextLine() (int, error) {
	if result.Lines == nil {
		return -1, nohitsError()
	}
	result.index++
	if result.index >= len(result.Lines) {
		result.index = 0
	}
	return result.Lines[result.index], nil
}

//PreviousLine returns the previous line while iterating the search results.
//So, for a result that found that lines (1, 3, 5) were a hit:
// * PreviousLine() should give 5
// * PreviousLine() should give 3
// * PreviousLine() should give 1
// * PreviousLine() should give 5
func (result *Result) PreviousLine() (int, error) {
	if result.Lines == nil {
		return -1, nohitsError()
	}
	result.index--
	if result.index < 0 {
		result.index = len(result.Lines) - 1
	}
	return result.Lines[result.index], nil
}

func nohitsError() error {
	return errors.New("Trying to iterate through the search result when there are no hits")
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/realglobe-Inc/edo/util"
	"net/http"
	"net/url"
)

type authRequest struct {
	// リクエスト元 TA の ID。
	Ta string `json:"client_id"`
	// リクエスト元 TA の名前。
	TaName string `json:"client_name"`
	// 結果通知リダイレクト先。
	RawRediUri string `json:"redirect_uri"`
	rediUri    *url.URL
	// 結果の形式。
	RespType util.StringSet `json:"response_type"`

	Stat   string                   `json:"state,omitempty"`
	Nonc   string                   `json:"nonce,omitempty"`
	Prmpts util.StringSet           `json:"prompt,omitempty"`
	Scops  util.StringSet           `json:"scope,omitempty"`
	Clms   map[string]*claimRequest `json:"claims,omitempty"`
}

type claimRequest struct {
	Ess  bool          `json:"essential,omitempty"`
	Val  interface{}   `json:"value,omitempty"`
	Vals []interface{} `json:"values,omitempty"`

	Loc string `json:"locale,omitempty"`
}

// エラーは HttpStatusError の予定。
func newAuthRequest(r *http.Request) (*authRequest, error) {
	// TODO request, request_uri のサポート。

	return &authRequest{
		Ta:         r.FormValue(formTaId),
		RawRediUri: r.FormValue(formRediUri),
		RespType:   formValueSet(r, formRespType),
		Stat:       r.FormValue(formStat),
		Nonc:       r.FormValue(formNonc),
		Prmpts:     formValueSet(r, formPrmpt),
		Scops:      formValueSet(r, formScop),
		Clms:       map[string]*claimRequest{},
	}, nil
}

// リクエスト元 TA の ID を返す。
func (this *authRequest) ta() string {
	return this.Ta
}

// リクエスト元 TA 名を返す。
func (this *authRequest) taName() string {
	return this.TaName
}

// リクエスト元 TA 名を設定する。
func (this *authRequest) setTaName(taName string) {
	this.TaName = taName
}

// 結果を通知するリダイレクト先を返す。
func (this *authRequest) rawRedirectUri() string {
	return this.RawRediUri
}
func (this *authRequest) redirectUri() *url.URL {
	if this.rediUri == nil {
		this.rediUri, _ = url.Parse(this.RawRediUri)
	}
	return this.rediUri
}

// 結果を通知するリダイレクト先を設定する。
func (this *authRequest) setRedirectUri(rediUri *url.URL) {
	this.rediUri = rediUri
}

// 結果の形式を返す。
func (this *authRequest) responseType() map[string]bool {
	return this.RespType
}

// state 値を返す。
func (this *authRequest) state() string {
	return this.Stat
}

// nonce 値を返す。
func (this *authRequest) nonce() string {
	return this.Nonc
}

// 要求されている prompt を返す。
func (this *authRequest) prompts() map[string]bool {
	return this.Prmpts
}

// 要求されている scope を返す。
func (this *authRequest) scopes() map[string]bool {
	return this.Scops
}

// 要求されているクレームを返す。
func (this *authRequest) claims() map[string]*claimRequest {
	return this.Clms
}

// 要求されているクレームを名前だけ返す。
func (this *authRequest) claimNames() map[string]bool {
	m := map[string]bool{}
	for clm := range this.Clms {
		m[clm] = true
	}
	return m
}
<commit_msg>コメントの変更<commit_after>package main

import (
	"github.com/realglobe-Inc/edo/util"
	"net/http"
	"net/url"
)

type authRequest struct {
	// リクエスト元 TA の ID。
	Ta string `json:"client_id"`
	// リクエスト元 TA の名前。
	TaName string `json:"client_name"`
	// 結果通知リダイレクト先。
	RawRediUri string `json:"redirect_uri"`
	rediUri    *url.URL
	// 結果の形式。
	RespType util.StringSet `json:"response_type"`

	Stat   string                   `json:"state,omitempty"`
	Nonc   string                   `json:"nonce,omitempty"`
	Prmpts util.StringSet           `json:"prompt,omitempty"`
	Scops  util.StringSet           `json:"scope,omitempty"`
	Clms   map[string]*claimRequest `json:"claims,omitempty"`
}

type claimRequest struct {
	Ess  bool          `json:"essential,omitempty"`
	Val  interface{}   `json:"value,omitempty"`
	Vals []interface{} `json:"values,omitempty"`

	Loc string `json:"locale,omitempty"`
}

// エラーは HttpStatusError の予定。
func newAuthRequest(r *http.Request) (*authRequest, error) {
	// TODO claims, request, request_uri パラメータのサポート。

	return &authRequest{
		Ta:         r.FormValue(formTaId),
		RawRediUri: r.FormValue(formRediUri),
		RespType:   formValueSet(r, formRespType),
		Stat:       r.FormValue(formStat),
		Nonc:       r.FormValue(formNonc),
		Prmpts:     formValueSet(r, formPrmpt),
		Scops:      formValueSet(r, formScop),
		Clms:       map[string]*claimRequest{},
	}, nil
}

// リクエスト元 TA の ID を返す。
func (this *authRequest) ta() string {
	return this.Ta
}

// リクエスト元 TA 名を返す。
func (this *authRequest) taName() string {
	return this.TaName
}

// リクエスト元 TA 名を設定する。
func (this *authRequest) setTaName(taName string) {
	this.TaName = taName
}

// 結果を通知するリダイレクト先を返す。
func (this *authRequest) rawRedirectUri() string {
	return this.RawRediUri
}
func (this *authRequest) redirectUri() *url.URL {
	if this.rediUri == nil {
		this.rediUri, _ = url.Parse(this.RawRediUri)
	}
	return this.rediUri
}

// 結果を通知するリダイレクト先を設定する。
func (this *authRequest) setRedirectUri(rediUri *url.URL) {
	this.rediUri = rediUri
}

// 結果の形式を返す。
func (this *authRequest) responseType() map[string]bool {
	return this.RespType
}

// state 値を返す。
func (this *authRequest) state() string {
	return this.Stat
}

// nonce 値を返す。
func (this *authRequest) nonce() string {
	return this.Nonc
}

// 要求されている prompt を返す。
func (this *authRequest) prompts() map[string]bool {
	return this.Prmpts
}

// 要求されている scope を返す。
func (this *authRequest) scopes() map[string]bool {
	return this.Scops
}

// 要求されているクレームを返す。
func (this *authRequest) claims() map[string]*claimRequest {
	return this.Clms
}

// 要求されているクレームを名前だけ返す。
func (this *authRequest) claimNames() map[string]bool {
	m := map[string]bool{}
	for clm := range this.Clms {
		m[clm] = true
	}
	return m
}
<|endoftext|>


<commit_before>package jsonapi

import (
	"testing"

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

func TestParseRequestError(t *testing.T) {
	invalidAccept := newTestRequester("GET", "", nil)
	invalidAccept.header["Accept"] = "foo"

	invalidContentType := newTestRequester("GET", "", nil)
	invalidContentType.header["Content-Type"] = "foo"

	missingContentType := newTestRequester("POST", "foo", nil)

	list := []Requester{
		invalidAccept,
		invalidContentType,
		missingContentType,
		newTestRequester("PUT", "", nil),
		newTestRequester("GET", "", nil),
		newTestRequester("POST", "", nil),
		newTestRequester("GET", "/", nil),
		newTestRequester("GET", "foo/bar/baz/qux", nil),
		newTestRequester("GET", "foo/bar/baz/qux/quux", nil),
		newTestRequester("GET", "foo", map[string][]string{
			"page[number]": []string{"bar"},
		}),
		newTestRequester("GET", "foo", map[string][]string{
			"page[size]": []string{"bar"},
		}),
		newTestRequester("GET", "foo", map[string][]string{
			"page[number]": []string{"1"},
		}),
		newTestRequester("GET", "foo", map[string][]string{
			"page[size]": []string{"1"},
		}),
		newTestRequester("GET", "foo", map[string][]string{
			"page[number]": []string{"bar", "baz"},
		}),
		newTestRequester("GET", "foo", map[string][]string{
			"page[size]": []string{"bar", "baz"},
		}),
		newTestRequester("PATCH", "foo", nil),
	}

	for _, r := range list {
		req, err := ParseRequest(r, "")
		assert.Error(t, err)
		assert.Nil(t, req)
	}
}

func TestParseRequestPrefix(t *testing.T) {
	list := map[string]string{
		"bar":           "",
		"/bar":          "",
		"foo/bar":       "foo",
		"/foo/bar":      "foo",
		"foo/bar/":      "/foo",
		"/foo/bar/":     "foo/",
		"baz/foo/bar/":  "/baz/foo",
		"/baz/foo/bar/": "baz/foo/",
	}

	for url, prefix := range list {
		r := newTestRequester("GET", url, nil)

		req, err := ParseRequest(r, prefix)
		assert.NoError(t, err)
		assert.Equal(t, "bar", req.ResourceType)
	}
}

func TestParseRequestResource(t *testing.T) {
	r := newTestRequester("GET", "foo", nil)

	req, err := ParseRequest(r, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
	}, req)
}

func TestParseRequestResourceID(t *testing.T) {
	r := newTestRequester("GET", "foo/1", nil)

	req, err := ParseRequest(r, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       FindResource,
		ResourceType: "foo",
		ResourceID:   "1",
	}, req)
}

func TestParseRequestRelatedResource(t *testing.T) {
	r := newTestRequester("GET", "foo/1/bar", nil)

	req, err := ParseRequest(r, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:          GetRelatedResources,
		ResourceType:    "foo",
		ResourceID:      "1",
		RelatedResource: "bar",
	}, req)
}

func TestParseRequestRelationship(t *testing.T) {
	r := newTestRequester("GET", "foo/1/relationships/bar", nil)

	req, err := ParseRequest(r, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       GetRelationship,
		ResourceType: "foo",
		ResourceID:   "1",
		Relationship: "bar",
	}, req)
}

func TestParseRequestIntent(t *testing.T) {
	list := []struct {
		method string
		url    string
		intent Intent
		doc    bool
	}{
		{"GET", "/posts", ListResources, false},
		{"GET", "/posts/1", FindResource, false},
		{"POST", "/posts", CreateResource, true},
		{"PATCH", "/posts/1", UpdateResource, true},
		{"DELETE", "/posts/1", DeleteResource, false},
		{"GET", "/posts/1/author", GetRelatedResources, false},
		{"GET", "/posts/1/relationships/author", GetRelationship, false},
		{"PATCH", "/posts/1/relationships/author", SetRelationship, true},
		{"POST", "/posts/1/relationships/comments", AppendToRelationship, true},
		{"DELETE", "/posts/1/relationships/comments", RemoveFromRelationship, true},
	}

	for _, entry := range list {
		r := newTestRequester(entry.method, entry.url, nil)
		r.header["Content-Type"] = MediaType

		req, err := ParseRequest(r, "")
		assert.NoError(t, err)
		assert.Equal(t, entry.intent, req.Intent)
		assert.Equal(t, entry.doc, req.Intent.DocumentExpected())
		assert.Equal(t, entry.url, req.Self())
		assert.Equal(t, entry.method, req.Intent.RequestMethod())
	}
}

func TestParseRequestInclude(t *testing.T) {
	r1 := newTestRequester("GET", "foo", map[string][]string{
		"include": []string{"bar,baz"},
	})

	req, err := ParseRequest(r1, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
		Include:      []string{"bar", "baz"},
	}, req)

	r2 := newTestRequester("GET", "foo", map[string][]string{
		"include": []string{"bar", "baz,qux"},
	})

	req, err = ParseRequest(r2, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
		Include:      []string{"bar", "baz", "qux"},
	}, req)
}

func TestParseRequestSorting(t *testing.T) {
	r1 := newTestRequester("GET", "foo", map[string][]string{
		"sort": []string{"bar,baz"},
	})

	req, err := ParseRequest(r1, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
		Sorting:      []string{"bar", "baz"},
	}, req)

	r2 := newTestRequester("GET", "foo", map[string][]string{
		"sort": []string{"bar", "baz,qux"},
	})

	req, err = ParseRequest(r2, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
		Sorting:      []string{"bar", "baz", "qux"},
	}, req)
}

func TestParseRequestPage(t *testing.T) {
	r := newTestRequester("GET", "foo", map[string][]string{
		"page[number]": []string{"1"},
		"page[size]":   []string{"2"},
	})

	req, err := ParseRequest(r, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
		PageNumber:   1,
		PageSize:     2,
	}, req)
}

func TestParseRequestFields(t *testing.T) {
	r := newTestRequester("GET", "foo", map[string][]string{
		"fields[foo]": []string{"bar,baz"},
	})

	req, err := ParseRequest(r, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
		Fields: map[string][]string{
			"foo": {"bar", "baz"},
		},
	}, req)
}

func TestParseRequestFilters(t *testing.T) {
	r := newTestRequester("GET", "foo", map[string][]string{
		"filter[foo]": []string{"bar,baz"},
	})

	req, err := ParseRequest(r, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
		Filters: map[string][]string{
			"foo": {"bar", "baz"},
		},
	}, req)
}

func TestZeroIntentRequestMethod(t *testing.T) {
	assert.Empty(t, Intent(0).RequestMethod())
}

func BenchmarkParseRequest(b *testing.B) {
	r := newTestRequester("GET", "foo/1", nil)

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		ParseRequest(r, "")
	}
}

func BenchmarkParseRequestFilterAndSort(b *testing.B) {
	r := newTestRequester("GET", "foo/1?sort=bar&filter[baz]=qux", nil)

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		ParseRequest(r, "")
	}
}
<commit_msg>simplified<commit_after>package jsonapi

import (
	"testing"

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

func TestParseRequestError(t *testing.T) {
	invalidAccept := newTestRequester("GET", "", nil)
	invalidAccept.header["Accept"] = "foo"

	invalidContentType := newTestRequester("GET", "", nil)
	invalidContentType.header["Content-Type"] = "foo"

	missingContentType := newTestRequester("POST", "foo", nil)

	list := []Requester{
		invalidAccept,
		invalidContentType,
		missingContentType,
		newTestRequester("PUT", "", nil),
		newTestRequester("GET", "", nil),
		newTestRequester("POST", "", nil),
		newTestRequester("GET", "/", nil),
		newTestRequester("GET", "foo/bar/baz/qux", nil),
		newTestRequester("GET", "foo/bar/baz/qux/quux", nil),
		newTestRequester("GET", "foo", map[string][]string{
			"page[number]": {"bar"},
		}),
		newTestRequester("GET", "foo", map[string][]string{
			"page[size]": {"bar"},
		}),
		newTestRequester("GET", "foo", map[string][]string{
			"page[number]": {"1"},
		}),
		newTestRequester("GET", "foo", map[string][]string{
			"page[size]": {"1"},
		}),
		newTestRequester("GET", "foo", map[string][]string{
			"page[number]": {"bar", "baz"},
		}),
		newTestRequester("GET", "foo", map[string][]string{
			"page[size]": {"bar", "baz"},
		}),
		newTestRequester("PATCH", "foo", nil),
	}

	for _, r := range list {
		req, err := ParseRequest(r, "")
		assert.Error(t, err)
		assert.Nil(t, req)
	}
}

func TestParseRequestPrefix(t *testing.T) {
	list := map[string]string{
		"bar":           "",
		"/bar":          "",
		"foo/bar":       "foo",
		"/foo/bar":      "foo",
		"foo/bar/":      "/foo",
		"/foo/bar/":     "foo/",
		"baz/foo/bar/":  "/baz/foo",
		"/baz/foo/bar/": "baz/foo/",
	}

	for url, prefix := range list {
		r := newTestRequester("GET", url, nil)

		req, err := ParseRequest(r, prefix)
		assert.NoError(t, err)
		assert.Equal(t, "bar", req.ResourceType)
	}
}

func TestParseRequestResource(t *testing.T) {
	r := newTestRequester("GET", "foo", nil)

	req, err := ParseRequest(r, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
	}, req)
}

func TestParseRequestResourceID(t *testing.T) {
	r := newTestRequester("GET", "foo/1", nil)

	req, err := ParseRequest(r, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       FindResource,
		ResourceType: "foo",
		ResourceID:   "1",
	}, req)
}

func TestParseRequestRelatedResource(t *testing.T) {
	r := newTestRequester("GET", "foo/1/bar", nil)

	req, err := ParseRequest(r, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:          GetRelatedResources,
		ResourceType:    "foo",
		ResourceID:      "1",
		RelatedResource: "bar",
	}, req)
}

func TestParseRequestRelationship(t *testing.T) {
	r := newTestRequester("GET", "foo/1/relationships/bar", nil)

	req, err := ParseRequest(r, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       GetRelationship,
		ResourceType: "foo",
		ResourceID:   "1",
		Relationship: "bar",
	}, req)
}

func TestParseRequestIntent(t *testing.T) {
	list := []struct {
		method string
		url    string
		intent Intent
		doc    bool
	}{
		{"GET", "/posts", ListResources, false},
		{"GET", "/posts/1", FindResource, false},
		{"POST", "/posts", CreateResource, true},
		{"PATCH", "/posts/1", UpdateResource, true},
		{"DELETE", "/posts/1", DeleteResource, false},
		{"GET", "/posts/1/author", GetRelatedResources, false},
		{"GET", "/posts/1/relationships/author", GetRelationship, false},
		{"PATCH", "/posts/1/relationships/author", SetRelationship, true},
		{"POST", "/posts/1/relationships/comments", AppendToRelationship, true},
		{"DELETE", "/posts/1/relationships/comments", RemoveFromRelationship, true},
	}

	for _, entry := range list {
		r := newTestRequester(entry.method, entry.url, nil)
		r.header["Content-Type"] = MediaType

		req, err := ParseRequest(r, "")
		assert.NoError(t, err)
		assert.Equal(t, entry.intent, req.Intent)
		assert.Equal(t, entry.doc, req.Intent.DocumentExpected())
		assert.Equal(t, entry.url, req.Self())
		assert.Equal(t, entry.method, req.Intent.RequestMethod())
	}
}

func TestParseRequestInclude(t *testing.T) {
	r1 := newTestRequester("GET", "foo", map[string][]string{
		"include": {"bar,baz"},
	})

	req, err := ParseRequest(r1, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
		Include:      []string{"bar", "baz"},
	}, req)

	r2 := newTestRequester("GET", "foo", map[string][]string{
		"include": {"bar", "baz,qux"},
	})

	req, err = ParseRequest(r2, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
		Include:      []string{"bar", "baz", "qux"},
	}, req)
}

func TestParseRequestSorting(t *testing.T) {
	r1 := newTestRequester("GET", "foo", map[string][]string{
		"sort": {"bar,baz"},
	})

	req, err := ParseRequest(r1, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
		Sorting:      []string{"bar", "baz"},
	}, req)

	r2 := newTestRequester("GET", "foo", map[string][]string{
		"sort": {"bar", "baz,qux"},
	})

	req, err = ParseRequest(r2, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
		Sorting:      []string{"bar", "baz", "qux"},
	}, req)
}

func TestParseRequestPage(t *testing.T) {
	r := newTestRequester("GET", "foo", map[string][]string{
		"page[number]": {"1"},
		"page[size]":   {"2"},
	})

	req, err := ParseRequest(r, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
		PageNumber:   1,
		PageSize:     2,
	}, req)
}

func TestParseRequestFields(t *testing.T) {
	r := newTestRequester("GET", "foo", map[string][]string{
		"fields[foo]": {"bar,baz"},
	})

	req, err := ParseRequest(r, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
		Fields: map[string][]string{
			"foo": {"bar", "baz"},
		},
	}, req)
}

func TestParseRequestFilters(t *testing.T) {
	r := newTestRequester("GET", "foo", map[string][]string{
		"filter[foo]": {"bar,baz"},
	})

	req, err := ParseRequest(r, "")
	assert.NoError(t, err)
	assert.Equal(t, &Request{
		Intent:       ListResources,
		ResourceType: "foo",
		Filters: map[string][]string{
			"foo": {"bar", "baz"},
		},
	}, req)
}

func TestZeroIntentRequestMethod(t *testing.T) {
	assert.Empty(t, Intent(0).RequestMethod())
}

func BenchmarkParseRequest(b *testing.B) {
	r := newTestRequester("GET", "foo/1", nil)

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		ParseRequest(r, "")
	}
}

func BenchmarkParseRequestFilterAndSort(b *testing.B) {
	r := newTestRequester("GET", "foo/1?sort=bar&filter[baz]=qux", nil)

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		ParseRequest(r, "")
	}
}
<|endoftext|>