<commit_before>// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package placement

import (
	"encoding/json"
	"testing"

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

func TestSnapshot(t *testing.T) {
	h1 := NewEmptyHostShards("r1h1", "r1")
	h1.AddShard(1)
	h1.AddShard(2)
	h1.AddShard(3)

	h2 := NewEmptyHostShards("r2h2", "r2")
	h2.AddShard(4)
	h2.AddShard(5)
	h2.AddShard(6)

	h3 := NewEmptyHostShards("r3h3", "r3")
	h3.AddShard(1)
	h3.AddShard(3)
	h3.AddShard(5)

	h4 := NewEmptyHostShards("r4h4", "r4")
	h4.AddShard(2)
	h4.AddShard(4)
	h4.AddShard(6)

	h5 := NewEmptyHostShards("r5h5", "r5")
	h5.AddShard(5)
	h5.AddShard(6)
	h5.AddShard(1)

	h6 := NewEmptyHostShards("r6h6", "r6")
	h6.AddShard(2)
	h6.AddShard(3)
	h6.AddShard(4)

	hss := []HostShards{h1, h2, h3, h4, h5, h6}

	ids := []uint32{1, 2, 3, 4, 5, 6}
	s := NewPlacementSnapshot(hss, ids, 3)
	assert.NoError(t, s.Validate())
	testSnapshotJSONRoundTrip(t, s)

	hs := s.HostShard("r6h6")
	assert.Equal(t, h6, hs)
	hs = s.HostShard("h100")
	assert.Nil(t, hs)

	assert.Equal(t, 6, s.HostsLen())
	assert.Equal(t, 3, s.Replicas())
	assert.Equal(t, ids, s.Shards())
	assert.Equal(t, hss, s.HostShards())

	s = NewEmptyPlacementSnapshot([]Host{NewHost("h1", "r1"), NewHost("h2", "r2")}, ids)
	assert.Equal(t, 0, s.Replicas())
	assert.Equal(t, ids, s.Shards())
	assert.NoError(t, s.Validate())
}

func TestValidate(t *testing.T) {
	ids := []uint32{1, 2, 3, 4, 5, 6}

	h1 := NewEmptyHostShards("r1h1", "r1")
	h1.AddShard(1)
	h1.AddShard(2)
	h1.AddShard(3)

	h2 := NewEmptyHostShards("r2h2", "r2")
	h2.AddShard(4)
	h2.AddShard(5)
	h2.AddShard(6)

	hss := []HostShards{h1, h2}
	s := NewPlacementSnapshot(hss, ids, 1)
	assert.NoError(t, s.Validate())

	// mismatch shards
	s = NewPlacementSnapshot(hss, append(ids, 7), 1)
	assert.Error(t, s.Validate())

	// host missing a shard
	h1 = NewEmptyHostShards("r1h1", "r1")
	h1.AddShard(1)
	h1.AddShard(2)
	h1.AddShard(3)
	h1.AddShard(4)
	h1.AddShard(5)
	h1.AddShard(6)

	h2 = NewEmptyHostShards("r2h2", "r2")
	h2.AddShard(2)
	h2.AddShard(3)
	h2.AddShard(4)
	h2.AddShard(5)
	h2.AddShard(6)

	hss = []HostShards{h1, h2}
	s = NewPlacementSnapshot(hss, ids, 2)
	assert.Error(t, s.Validate())

	// host contains shard that's not supposed to be in snapshot
	h1 = NewEmptyHostShards("r1h1", "r1")
	h1.AddShard(1)
	h1.AddShard(2)
	h1.AddShard(3)
	h1.AddShard(4)
	h1.AddShard(5)
	h1.AddShard(6)
	h1.AddShard(7)

	h2 = NewEmptyHostShards("r2h2", "r2")
	h2.AddShard(2)
	h2.AddShard(3)
	h2.AddShard(4)
	h2.AddShard(5)
	h2.AddShard(6)

	hss = []HostShards{h1, h2}
	s = NewPlacementSnapshot(hss, ids, 2)
	assert.Error(t, s.Validate())

	// duplicated shards
	h1 = NewEmptyHostShards("r1h1", "r1")
	h1.AddShard(2)
	h1.AddShard(3)
	h1.AddShard(4)

	h2 = NewEmptyHostShards("r2h2", "r2")
	h2.AddShard(4)
	h2.AddShard(5)
	h2.AddShard(6)

	hss = []HostShards{h1, h2}
	s = NewPlacementSnapshot(hss, []uint32{2, 3, 4, 4, 5, 6}, 1)
	assert.Error(t, s.Validate())
}

func TestSnapshotMarshalling(t *testing.T) {
	invalidJSON := `{
		:{"ID":123,"Rack":"r1","Shards":[0,7,11]}
	}`
	data := []byte(invalidJSON)
	ps, err := NewPlacementFromJSON(data)
	assert.Nil(t, ps)
	assert.Error(t, err)

	validJSON := `{
		"r2h4": {"ID":"r2h4","Rack":"r2","Shards":[6,13,15]},
		"r3h5": {"ID":"r3h5","Rack":"r3","Shards":[2,8,19]},
		"r4h6": {"ID":"r4h6","Rack":"r4","Shards":[3,9,18]},
		"r1h1": {"ID":"r1h1","Rack":"r1","Shards":[0,7,11]},
		"r2h3": {"ID":"r2h3","Rack":"r2","Shards":[1,4,12]},
		"r5h7": {"ID":"r5h7","Rack":"r5","Shards":[10,14]},
		"r6h9": {"ID":"r6h9","Rack":"r6","Shards":[5,16,17]}
	}`
	data = []byte(validJSON)
	ps, err = NewPlacementFromJSON(data)
	assert.NoError(t, err)
	assert.Equal(t, 7, ps.HostsLen())
	assert.Equal(t, 1, ps.Replicas())
	assert.Equal(t, 20, ps.ShardsLen())

	testSnapshotJSONRoundTrip(t, ps)

	// an extra replica for shard 1
	invalidPlacementJSON := `{
		"r1h1": {"ID":"r1h1","Rack":"r1","Shards":[0,1,7,11]},
		"r2h3": {"ID":"r2h3","Rack":"r2","Shards":[1,4,12]},
		"r2h4": {"ID":"r2h4","Rack":"r2","Shards":[6,13,15]},
		"r3h5": {"ID":"r3h5","Rack":"r3","Shards":[2,8,19]},
		"r4h6": {"ID":"r4h6","Rack":"r4","Shards":[3,9,18]},
		"r5h7": {"ID":"r5h7","Rack":"r5","Shards":[10,14]},
		"r6h9": {"ID":"r6h9","Rack":"r6","Shards":[5,16,17]}
	}`
	data = []byte(invalidPlacementJSON)
	ps, err = NewPlacementFromJSON(data)
	assert.Equal(t, err, errShardsWithDifferentReplicas)
	assert.Nil(t, ps)

	// an extra replica for shard 0 on r1h1
	invalidPlacementJSON = `{
		"r1h1": {"ID":"r1h1","Rack":"r1","Shards":[0,0,7,11]},
		"r2h3": {"ID":"r2h3","Rack":"r2","Shards":[1,4,12]},
		"r2h4": {"ID":"r2h4","Rack":"r2","Shards":[6,13,15]},
		"r3h5": {"ID":"r3h5","Rack":"r3","Shards":[2,8,19]},
		"r4h6": {"ID":"r4h6","Rack":"r4","Shards":[3,9,18]},
		"r5h7": {"ID":"r5h7","Rack":"r5","Shards":[10,14]},
		"r6h9": {"ID":"r6h9","Rack":"r6","Shards":[5,16,17]}
	}`
	data = []byte(invalidPlacementJSON)
	ps, err = NewPlacementFromJSON(data)
	assert.Equal(t, err, errInvalidHostShards)
	assert.Nil(t, ps)
}

func TestHostShards(t *testing.T) {
	h1 := NewEmptyHostShards("r1h1", "r1")
	h1.AddShard(1)
	h1.AddShard(2)
	h1.AddShard(3)

	assert.True(t, h1.ContainsShard(1))
	assert.False(t, h1.ContainsShard(100))
	assert.Equal(t, 3, h1.ShardsLen())
	assert.Equal(t, "r1h1", h1.Host().ID())
	assert.Equal(t, "r1", h1.Host().Rack())

	h1.RemoveShard(1)
	assert.False(t, h1.ContainsShard(1))
	assert.False(t, h1.ContainsShard(100))
	assert.Equal(t, 2, h1.ShardsLen())
	assert.Equal(t, "r1h1", h1.Host().ID())
	assert.Equal(t, "r1", h1.Host().Rack())
}

func testSnapshotJSONRoundTrip(t *testing.T, s Snapshot) {
	json1, err := json.Marshal(s)
	assert.NoError(t, err)

	var snapShotFromJSON snapshot
	err = json.Unmarshal(json1, &snapShotFromJSON)
	assert.NoError(t, err)

	json2, err := json.Marshal(s)
	assert.NoError(t, err)
	assert.Equal(t, string(json1), string(json2))
}
<commit_msg>make tests deterministic<commit_after>// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package placement

import (
	"encoding/json"
	"testing"

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

func TestSnapshot(t *testing.T) {
	h1 := NewEmptyHostShards("r1h1", "r1")
	h1.AddShard(1)
	h1.AddShard(2)
	h1.AddShard(3)

	h2 := NewEmptyHostShards("r2h2", "r2")
	h2.AddShard(4)
	h2.AddShard(5)
	h2.AddShard(6)

	h3 := NewEmptyHostShards("r3h3", "r3")
	h3.AddShard(1)
	h3.AddShard(3)
	h3.AddShard(5)

	h4 := NewEmptyHostShards("r4h4", "r4")
	h4.AddShard(2)
	h4.AddShard(4)
	h4.AddShard(6)

	h5 := NewEmptyHostShards("r5h5", "r5")
	h5.AddShard(5)
	h5.AddShard(6)
	h5.AddShard(1)

	h6 := NewEmptyHostShards("r6h6", "r6")
	h6.AddShard(2)
	h6.AddShard(3)
	h6.AddShard(4)

	hss := []HostShards{h1, h2, h3, h4, h5, h6}

	ids := []uint32{1, 2, 3, 4, 5, 6}
	s := NewPlacementSnapshot(hss, ids, 3)
	assert.NoError(t, s.Validate())
	testSnapshotJSONRoundTrip(t, s)

	hs := s.HostShard("r6h6")
	assert.Equal(t, h6, hs)
	hs = s.HostShard("h100")
	assert.Nil(t, hs)

	assert.Equal(t, 6, s.HostsLen())
	assert.Equal(t, 3, s.Replicas())
	assert.Equal(t, ids, s.Shards())
	assert.Equal(t, hss, s.HostShards())

	s = NewEmptyPlacementSnapshot([]Host{NewHost("h1", "r1"), NewHost("h2", "r2")}, ids)
	assert.Equal(t, 0, s.Replicas())
	assert.Equal(t, ids, s.Shards())
	assert.NoError(t, s.Validate())
}

func TestValidate(t *testing.T) {
	ids := []uint32{1, 2, 3, 4, 5, 6}

	h1 := NewEmptyHostShards("r1h1", "r1")
	h1.AddShard(1)
	h1.AddShard(2)
	h1.AddShard(3)

	h2 := NewEmptyHostShards("r2h2", "r2")
	h2.AddShard(4)
	h2.AddShard(5)
	h2.AddShard(6)

	hss := []HostShards{h1, h2}
	s := NewPlacementSnapshot(hss, ids, 1)
	assert.NoError(t, s.Validate())

	// mismatch shards
	s = NewPlacementSnapshot(hss, append(ids, 7), 1)
	assert.Error(t, s.Validate())

	// host missing a shard
	h1 = NewEmptyHostShards("r1h1", "r1")
	h1.AddShard(1)
	h1.AddShard(2)
	h1.AddShard(3)
	h1.AddShard(4)
	h1.AddShard(5)
	h1.AddShard(6)

	h2 = NewEmptyHostShards("r2h2", "r2")
	h2.AddShard(2)
	h2.AddShard(3)
	h2.AddShard(4)
	h2.AddShard(5)
	h2.AddShard(6)

	hss = []HostShards{h1, h2}
	s = NewPlacementSnapshot(hss, ids, 2)
	assert.Error(t, s.Validate())

	// host contains shard that's not supposed to be in snapshot
	h1 = NewEmptyHostShards("r1h1", "r1")
	h1.AddShard(1)
	h1.AddShard(2)
	h1.AddShard(3)
	h1.AddShard(4)
	h1.AddShard(5)
	h1.AddShard(6)
	h1.AddShard(7)

	h2 = NewEmptyHostShards("r2h2", "r2")
	h2.AddShard(2)
	h2.AddShard(3)
	h2.AddShard(4)
	h2.AddShard(5)
	h2.AddShard(6)

	hss = []HostShards{h1, h2}
	s = NewPlacementSnapshot(hss, ids, 2)
	assert.Error(t, s.Validate())

	// duplicated shards
	h1 = NewEmptyHostShards("r1h1", "r1")
	h1.AddShard(2)
	h1.AddShard(3)
	h1.AddShard(4)

	h2 = NewEmptyHostShards("r2h2", "r2")
	h2.AddShard(4)
	h2.AddShard(5)
	h2.AddShard(6)

	hss = []HostShards{h1, h2}
	s = NewPlacementSnapshot(hss, []uint32{2, 3, 4, 4, 5, 6}, 1)
	assert.Error(t, s.Validate())
}

func TestSnapshotMarshalling(t *testing.T) {
	invalidJSON := `{
		:{"ID":123,"Rack":"r1","Shards":[0,7,11]}
	}`
	data := []byte(invalidJSON)
	ps, err := NewPlacementFromJSON(data)
	assert.Nil(t, ps)
	assert.Error(t, err)

	validJSON := `{
		"r2h4": {"ID":"r2h4","Rack":"r2","Shards":[6,13,15]},
		"r3h5": {"ID":"r3h5","Rack":"r3","Shards":[2,8,19]},
		"r4h6": {"ID":"r4h6","Rack":"r4","Shards":[3,9,18]},
		"r1h1": {"ID":"r1h1","Rack":"r1","Shards":[0,7,11]},
		"r2h3": {"ID":"r2h3","Rack":"r2","Shards":[1,4,12]},
		"r5h7": {"ID":"r5h7","Rack":"r5","Shards":[10,14]},
		"r6h9": {"ID":"r6h9","Rack":"r6","Shards":[5,16,17]}
	}`
	data = []byte(validJSON)
	ps, err = NewPlacementFromJSON(data)
	assert.NoError(t, err)
	assert.Equal(t, 7, ps.HostsLen())
	assert.Equal(t, 1, ps.Replicas())
	assert.Equal(t, 20, ps.ShardsLen())

	testSnapshotJSONRoundTrip(t, ps)

	// an extra replica for shard 1
	invalidPlacementJSON := `{
		"r1h1": {"ID":"r1h1","Rack":"r1","Shards":[0,1,7,11]},
		"r2h3": {"ID":"r2h3","Rack":"r2","Shards":[1,4,12]},
		"r2h4": {"ID":"r2h4","Rack":"r2","Shards":[6,13,15]},
		"r3h5": {"ID":"r3h5","Rack":"r3","Shards":[2,8,19]},
		"r4h6": {"ID":"r4h6","Rack":"r4","Shards":[3,9,18]},
		"r5h7": {"ID":"r5h7","Rack":"r5","Shards":[10,14]},
		"r6h9": {"ID":"r6h9","Rack":"r6","Shards":[5,16,17]}
	}`
	data = []byte(invalidPlacementJSON)
	ps, err = NewPlacementFromJSON(data)
	assert.Equal(t, err, errShardsWithDifferentReplicas)
	assert.Nil(t, ps)

	// an extra replica for shard 0 on r1h1
	invalidPlacementJSON = `{
		"r1h1": {"ID":"r1h1","Rack":"r1","Shards":[0,0,7,11]},
		"r2h3": {"ID":"r2h3","Rack":"r2","Shards":[1,4,12]},
		"r2h4": {"ID":"r2h4","Rack":"r2","Shards":[6,13,15]},
		"r3h5": {"ID":"r3h5","Rack":"r3","Shards":[2,8,19]},
		"r4h6": {"ID":"r4h6","Rack":"r4","Shards":[3,9,18]},
		"r5h7": {"ID":"r5h7","Rack":"r5","Shards":[10,14]},
		"r6h9": {"ID":"r6h9","Rack":"r6","Shards":[5,16,17]}
	}`
	data = []byte(invalidPlacementJSON)
	ps, err = NewPlacementFromJSON(data)
	assert.Equal(t, err, errInvalidHostShards)
	assert.Nil(t, ps)
}

func TestHostShards(t *testing.T) {
	h1 := NewEmptyHostShards("r1h1", "r1")
	h1.AddShard(1)
	h1.AddShard(2)
	h1.AddShard(3)

	assert.True(t, h1.ContainsShard(1))
	assert.False(t, h1.ContainsShard(100))
	assert.Equal(t, 3, h1.ShardsLen())
	assert.Equal(t, "r1h1", h1.Host().ID())
	assert.Equal(t, "r1", h1.Host().Rack())

	h1.RemoveShard(1)
	assert.False(t, h1.ContainsShard(1))
	assert.False(t, h1.ContainsShard(100))
	assert.Equal(t, 2, h1.ShardsLen())
	assert.Equal(t, "r1h1", h1.Host().ID())
	assert.Equal(t, "r1", h1.Host().Rack())
}

func testSnapshotJSONRoundTrip(t *testing.T, s Snapshot) {
	json1, err := json.Marshal(s)
	assert.NoError(t, err)

	var unmarshalled snapshot
	err = json.Unmarshal(json1, &unmarshalled)
	assert.NoError(t, err)

	assert.Equal(t, s.(snapshot).placementSnapshotToJSON(), unmarshalled.placementSnapshotToJSON())

}
<|endoftext|>


<commit_before>package tempCrit

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"testing"
)
import (
	"../plots"
	"../tempAll"
	"../tempPair"
)

var production = flag.Bool("production", false, "Production mode: make plots that shouldn't change.")
var testPlot = flag.Bool("testPlot", false, "Run tests involving plots")
var longPlot = flag.Bool("longPlot", false, "Run long version of plot tests")
var tinyX = flag.Bool("tinyX", false, "Plot very small values of X")

// kz^2 values
//var defaultEnvSolution = []float64{0.006316132112386478, -0.5799328990719926, 3.727109277361983}
// cos(kz) values
var defaultEnvSolution = []float64{0.005166791967836855, -0.5859596040494773, 3.9111016056424477}

// Solve a critical-temperature system for the appropriate values of
// (D1,Mu_h,Beta)
func TestSolveCritTempSystem(t *testing.T) {
	flag.Parse()
	if *production {
		return
	}
	vars := []string{"D1", "Mu_h", "Beta"}
	eps := 1e-6
	env, err := ctDefaultEnv()
	if err != nil {
		t.Fatal(err)
	}
	err = tempAll.VerifySolution(env, CritTempSolve, CritTempFullSystem, vars, eps, eps, defaultEnvSolution)
	if err != nil {
		fmt.Printf("T_c = %f\n", 1.0/env.Beta)
		t.Fatal(err)
	}
}

func ctDefaultEnv() (*tempAll.Environment, error) {
	data, err := ioutil.ReadFile("system_test_env.json")
	if err != nil {
		return nil, err
	}
	env, err := CritTempEnvironment(string(data))
	if err != nil {
		return nil, err
	}
	return env, nil
}

func tpDefaultEnv() (*tempAll.Environment, error) {
	data, err := ioutil.ReadFile("../tempPair/system_test_env.json")
	if err != nil {
		return nil, err
	}
	env, err := tempPair.PairTempEnvironment(string(data))
	if err != nil {
		return nil, err
	}
	return env, nil
}

// Production-ready plots:
//   D1, Mu_h, and Tc vs x.
//   Extra set of plots to zoom in on x=0.
//   Vary tz and thp independently in each set (fix one at 0.1 and the other is in {0.05, 0.1, 0.15}).
func TestProductionPlots(t *testing.T) {
	// setup
	flag.Parse()
	if !*production {
		return
	}
	defaultEnv, err := ctDefaultEnv()
	if err != nil {
		t.Fatal(err)
	}
	tpEnv, err := tpDefaultEnv()
	if err != nil {
		t.Fatal(err)
	}
	// number of X values to use
	Nx := 30
	// vary thp
	envs := defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{Nx, 1, 3}, []float64{0.01, 0.1, 0.05}, []float64{0.15, 0.1, 0.15})
	fileLabelTp := "plot_data_THP.tp_x"
	fileLabelMu := "plot_data_THP.mu_x"
	fileLabelD1 := "plot_data_THP.D1_x"
	eps := 1e-6
	err = solveAndPlot(envs, eps, eps, fileLabelTp, fileLabelMu, fileLabelD1)
	// vary thp (small x)
	envs = defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{Nx, 1, 3}, []float64{0.001, 0.1, 0.05}, []float64{0.01, 0.1, 0.15})
	fileLabelTp = "plot_data_THP_LOWX.tp_x"
	fileLabelMu = "plot_data_THP_LOWX.mu_x"
	fileLabelD1 = "plot_data_THP_LOWX.D1_x"
	err = solveAndPlot(envs, eps, eps, fileLabelTp, fileLabelMu, fileLabelD1)
	// vary tz
	envs = defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{Nx, 3, 1}, []float64{0.01, 0.05, 0.1}, []float64{0.15, 0.15, 0.1})
	fileLabelTp = "plot_data_TZ.tp_x"
	fileLabelMu = "plot_data_TZ.mu_x"
	fileLabelD1 = "plot_data_TZ.D1_x"
	err = solveAndPlot(envs, eps, eps, fileLabelTp, fileLabelMu, fileLabelD1)
	// vary tz (small x)
	envs = defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{Nx, 3, 1}, []float64{0.001, 0.05, 0.1}, []float64{0.01, 0.15, 0.1})
	fileLabelTp = "plot_data_TZ_LOWX.tp_x"
	fileLabelMu = "plot_data_TZ_LOWX.mu_x"
	fileLabelD1 = "plot_data_TZ_LOWX.D1_x"
	err = solveAndPlot(envs, eps, eps, fileLabelTp, fileLabelMu, fileLabelD1)
	// Tc and Tp plotted together, tz = thp = 0.1
	envsTc := defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{Nx, 1, 1}, []float64{0.001, 0.1, 0.1}, []float64{0.1, 0.1, 0.1})
	envsTp := tpEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{Nx, 1, 1}, []float64{0.001, 0.1, 0.1}, []float64{0.1, 0.1, 0.1})
	// TODO shouldn't throw away errors here
	_, _ = tempAll.MultiSolve(envsTc, eps, eps, CritTempSolve)
	_, _ = tempAll.MultiSolve(envsTp, eps, eps, tempPair.PairTempSolve)
	fileLabelTcTp := "plot_data_TCTP_Tz_0.1_Thp_0.1_"
	plotTcTp(envsTc, envsTp, fileLabelTcTp)
}

// Plot evolution of Tc vs X.
func TestPlotTcVsX(t *testing.T) {
	flag.Parse()
	if !*testPlot && !*longPlot {
		return
	}
	defaultEnv, err := ctDefaultEnv()
	if err != nil {
		t.Fatal(err)
	}
	envs := defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{4, 1, 1}, []float64{0.025, 0.05, 0.05}, []float64{0.10, 0.1, 0.1})
	if *longPlot {
		if !*tinyX {
			envs = defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{10, 2, 2}, []float64{0.0001, 0.05, 0.05}, []float64{0.15, 0.1, 0.10})
		} else {
			envs = defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{10, 2, 2}, []float64{0.001, 0.05, 0.05}, []float64{0.01, 0.1, 0.1})
		}
	}
	vars := plots.GraphVars{"X", "", []string{"Tz", "Thp"}, []string{"t_z", "t_h^{\\prime}"}, nil, tempAll.GetTemp}
	eps := 1e-6
	// solve the full system
	plotEnvs, errs := tempAll.MultiSolve(envs, eps, eps, CritTempSolve)

	// Tc vs x plots
	wd, _ := os.Getwd()
	grapherPath := wd + "/../plots/grapher.py"
	var fileLabel string
	if !*tinyX {
		fileLabel = "plot_data.tc_x"
	} else {
		fileLabel = "plot_data.tinyX_tc_x"
	}
	graphParams := map[string]string{plots.FILE_KEY: wd + "/" + fileLabel, plots.XLABEL_KEY: "$x$", plots.YLABEL_KEY: "$T_c$"}
	err = plots.MultiPlot(plotEnvs, errs, vars, graphParams, grapherPath)
	if err != nil {
		t.Fatalf("error making Tc plot: %v", err)
	}
	// Mu_h vs x plots
	if !*tinyX {
		fileLabel = "plot_data.mu_x_data"
	} else {
		fileLabel = "plot_data.tinyX_mu_x_data"
	}
	graphParams[plots.FILE_KEY] = wd + "/" + fileLabel
	graphParams[plots.YLABEL_KEY] = "$\\mu_h$"
	vars.Y = "Mu_h"
	vars.YFunc = nil
	err = plots.MultiPlot(plotEnvs, errs, vars, graphParams, grapherPath)
	if err != nil {
		t.Fatalf("error making Mu_h plot: %v", err)
	}
	// D_1 vs x plots
	if !*tinyX {
		fileLabel = "plot_data.D1_x_data"
	} else {
		fileLabel = "plot_data.tinyX_D1_x_data"
	}
	graphParams[plots.FILE_KEY] = wd + "/" + fileLabel
	graphParams[plots.YLABEL_KEY] = "$D_1$"
	vars.Y = "D1"
	vars.YFunc = nil
	err = plots.MultiPlot(plotEnvs, errs, vars, graphParams, grapherPath)
	if err != nil {
		t.Fatalf("error making D1 plot: %v", err)
	}

}

func solveAndPlot(envs []*tempAll.Environment, epsabs, epsrel float64, fileLabelTc, fileLabelMu, fileLabelD1 string) error {
	// solve
	plotEnvs, errs := tempAll.MultiSolve(envs, epsabs, epsrel, CritTempSolve)
	// Tc vs x plot
	vars := plots.GraphVars{"X", "", []string{"Tz", "Thp"}, []string{"t_z", "t_h^{\\prime}"}, nil, tempAll.GetTemp}
	wd, _ := os.Getwd()
	grapherPath := wd + "/../plots/grapher.py"
	graphParams := map[string]string{plots.FILE_KEY: wd + "/" + fileLabelTc, plots.XLABEL_KEY: "$x$", plots.YLABEL_KEY: "$T_c/t_0$"}
	err := plots.MultiPlotStyle(plotEnvs, errs, vars, graphParams, grapherPath, false)
	if err != nil {
		return err
	}
	graphParams[plots.FILE_KEY] = wd + "/" + fileLabelTc + "_BW_"
	err = plots.MultiPlotStyle(plotEnvs, errs, vars, graphParams, grapherPath, true)
	if err != nil {
		return err
	}
	// Mu_h vs x plots
	graphParams[plots.FILE_KEY] = wd + "/" + fileLabelMu
	graphParams[plots.YLABEL_KEY] = "$\\mu_h/t_0$"
	vars.Y = "Mu_h"
	vars.YFunc = nil
	err = plots.MultiPlot(plotEnvs, errs, vars, graphParams, grapherPath)
	if err != nil {
		return err
	}
	graphParams[plots.FILE_KEY] = wd + "/" + fileLabelMu + "_BW_"
	err = plots.MultiPlotStyle(plotEnvs, errs, vars, graphParams, grapherPath, true)
	if err != nil {
		return err
	}
	// D_1 vs x plots
	graphParams[plots.FILE_KEY] = wd + "/" + fileLabelD1
	graphParams[plots.YLABEL_KEY] = "$D_1$"
	vars.Y = "D1"
	vars.YFunc = nil
	err = plots.MultiPlot(plotEnvs, errs, vars, graphParams, grapherPath)
	if err != nil {
		return err
	}
	graphParams[plots.FILE_KEY] = wd + "/" + fileLabelD1 + "_BW_"
	err = plots.MultiPlotStyle(plotEnvs, errs, vars, graphParams, grapherPath, true)
	if err != nil {
		return err
	}
	return nil
}

func plotTcTp(tcEnvs, tpEnvs []*tempAll.Environment, fileLabelTcTp string) error {
	wd, _ := os.Getwd()
	grapherPath := wd + "/../plots/grapher.py"
	graphParams := map[string]string{plots.FILE_KEY: wd + "/" + fileLabelTcTp, plots.XLABEL_KEY: "$x$", plots.YLABEL_KEY: "$T/t_0$"}
	seriesParams := make([]map[string]string, 2)
	seriesParams[0] = map[string]string{"label": "$T_c/t_0$", "style": "b-"}
	seriesParams[1] = map[string]string{"label": "$T_p/t_0$", "style": "r--"}
	xsTc, xsTp, Tcs, Tps := []float64{}, []float64{}, []float64{}, []float64{}
	for _, tcEnv := range tcEnvs {
		xsTc = append(xsTc, tcEnv.X)
		Tcs = append(Tcs, 1.0/tcEnv.Beta)
	}
	for _, tpEnv := range tpEnvs {
		xsTp = append(xsTp, tpEnv.X)
		Tps = append(Tps, 1.0/tpEnv.Beta)
	}
	series := make([]plots.Series, 2)
	series[0] = plots.MakeSeries(xsTc, Tcs)
	series[1] = plots.MakeSeries(xsTp, Tps)
	err := plots.PlotMPL(series, graphParams, seriesParams, grapherPath)
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>make Tc plots have y-axis start at 0<commit_after>package tempCrit

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"testing"
)
import (
	"../plots"
	"../tempAll"
	"../tempPair"
)

var production = flag.Bool("production", false, "Production mode: make plots that shouldn't change.")
var testPlot = flag.Bool("testPlot", false, "Run tests involving plots")
var longPlot = flag.Bool("longPlot", false, "Run long version of plot tests")
var tinyX = flag.Bool("tinyX", false, "Plot very small values of X")

// kz^2 values
//var defaultEnvSolution = []float64{0.006316132112386478, -0.5799328990719926, 3.727109277361983}
// cos(kz) values
var defaultEnvSolution = []float64{0.005166791967836855, -0.5859596040494773, 3.9111016056424477}

// Solve a critical-temperature system for the appropriate values of
// (D1,Mu_h,Beta)
func TestSolveCritTempSystem(t *testing.T) {
	flag.Parse()
	if *production {
		return
	}
	vars := []string{"D1", "Mu_h", "Beta"}
	eps := 1e-6
	env, err := ctDefaultEnv()
	if err != nil {
		t.Fatal(err)
	}
	err = tempAll.VerifySolution(env, CritTempSolve, CritTempFullSystem, vars, eps, eps, defaultEnvSolution)
	if err != nil {
		fmt.Printf("T_c = %f\n", 1.0/env.Beta)
		t.Fatal(err)
	}
}

func ctDefaultEnv() (*tempAll.Environment, error) {
	data, err := ioutil.ReadFile("system_test_env.json")
	if err != nil {
		return nil, err
	}
	env, err := CritTempEnvironment(string(data))
	if err != nil {
		return nil, err
	}
	return env, nil
}

func tpDefaultEnv() (*tempAll.Environment, error) {
	data, err := ioutil.ReadFile("../tempPair/system_test_env.json")
	if err != nil {
		return nil, err
	}
	env, err := tempPair.PairTempEnvironment(string(data))
	if err != nil {
		return nil, err
	}
	return env, nil
}

// Production-ready plots:
//   D1, Mu_h, and Tc vs x.
//   Extra set of plots to zoom in on x=0.
//   Vary tz and thp independently in each set (fix one at 0.1 and the other is in {0.05, 0.1, 0.15}).
func TestProductionPlots(t *testing.T) {
	// setup
	flag.Parse()
	if !*production {
		return
	}
	defaultEnv, err := ctDefaultEnv()
	if err != nil {
		t.Fatal(err)
	}
	tpEnv, err := tpDefaultEnv()
	if err != nil {
		t.Fatal(err)
	}
	// number of X values to use
	Nx := 30
	// vary thp
	envs := defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{Nx, 1, 3}, []float64{0.001, 0.1, 0.05}, []float64{0.15, 0.1, 0.15})
	fileLabelTp := "plot_data_THP.tp_x"
	fileLabelMu := "plot_data_THP.mu_x"
	fileLabelD1 := "plot_data_THP.D1_x"
	eps := 1e-6
	err = solveAndPlot(envs, eps, eps, fileLabelTp, fileLabelMu, fileLabelD1)
	// vary thp (small x)
	envs = defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{Nx, 1, 3}, []float64{0.001, 0.1, 0.05}, []float64{0.01, 0.1, 0.15})
	fileLabelTp = "plot_data_THP_LOWX.tp_x"
	fileLabelMu = "plot_data_THP_LOWX.mu_x"
	fileLabelD1 = "plot_data_THP_LOWX.D1_x"
	err = solveAndPlot(envs, eps, eps, fileLabelTp, fileLabelMu, fileLabelD1)
	// vary tz
	envs = defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{Nx, 3, 1}, []float64{0.001, 0.05, 0.1}, []float64{0.15, 0.15, 0.1})
	fileLabelTp = "plot_data_TZ.tp_x"
	fileLabelMu = "plot_data_TZ.mu_x"
	fileLabelD1 = "plot_data_TZ.D1_x"
	err = solveAndPlot(envs, eps, eps, fileLabelTp, fileLabelMu, fileLabelD1)
	// vary tz (small x)
	envs = defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{Nx, 3, 1}, []float64{0.001, 0.05, 0.1}, []float64{0.01, 0.15, 0.1})
	fileLabelTp = "plot_data_TZ_LOWX.tp_x"
	fileLabelMu = "plot_data_TZ_LOWX.mu_x"
	fileLabelD1 = "plot_data_TZ_LOWX.D1_x"
	err = solveAndPlot(envs, eps, eps, fileLabelTp, fileLabelMu, fileLabelD1)
	// Tc and Tp plotted together, tz = thp = 0.1
	envsTc := defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{Nx, 1, 1}, []float64{0.001, 0.1, 0.1}, []float64{0.1, 0.1, 0.1})
	envsTp := tpEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{Nx, 1, 1}, []float64{0.001, 0.1, 0.1}, []float64{0.1, 0.1, 0.1})
	// TODO shouldn't throw away errors here
	_, _ = tempAll.MultiSolve(envsTc, eps, eps, CritTempSolve)
	_, _ = tempAll.MultiSolve(envsTp, eps, eps, tempPair.PairTempSolve)
	fileLabelTcTp := "plot_data_TCTP_Tz_0.1_Thp_0.1_"
	plotTcTp(envsTc, envsTp, fileLabelTcTp)
}

// Plot evolution of Tc vs X.
func TestPlotTcVsX(t *testing.T) {
	flag.Parse()
	if !*testPlot && !*longPlot {
		return
	}
	defaultEnv, err := ctDefaultEnv()
	if err != nil {
		t.Fatal(err)
	}
	envs := defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{4, 1, 1}, []float64{0.025, 0.05, 0.05}, []float64{0.10, 0.1, 0.1})
	if *longPlot {
		if !*tinyX {
			envs = defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{10, 2, 2}, []float64{0.0001, 0.05, 0.05}, []float64{0.15, 0.1, 0.10})
		} else {
			envs = defaultEnv.MultiSplit([]string{"X", "Tz", "Thp"}, []int{10, 2, 2}, []float64{0.001, 0.05, 0.05}, []float64{0.01, 0.1, 0.1})
		}
	}
	vars := plots.GraphVars{"X", "", []string{"Tz", "Thp"}, []string{"t_z", "t_h^{\\prime}"}, nil, tempAll.GetTemp}
	eps := 1e-6
	// solve the full system
	plotEnvs, errs := tempAll.MultiSolve(envs, eps, eps, CritTempSolve)

	// Tc vs x plots
	wd, _ := os.Getwd()
	grapherPath := wd + "/../plots/grapher.py"
	var fileLabel string
	if !*tinyX {
		fileLabel = "plot_data.tc_x"
	} else {
		fileLabel = "plot_data.tinyX_tc_x"
	}
	graphParams := map[string]string{plots.FILE_KEY: wd + "/" + fileLabel, plots.XLABEL_KEY: "$x$", plots.YLABEL_KEY: "$T_c$"}
	err = plots.MultiPlot(plotEnvs, errs, vars, graphParams, grapherPath)
	if err != nil {
		t.Fatalf("error making Tc plot: %v", err)
	}
	// Mu_h vs x plots
	if !*tinyX {
		fileLabel = "plot_data.mu_x_data"
	} else {
		fileLabel = "plot_data.tinyX_mu_x_data"
	}
	graphParams[plots.FILE_KEY] = wd + "/" + fileLabel
	graphParams[plots.YLABEL_KEY] = "$\\mu_h$"
	vars.Y = "Mu_h"
	vars.YFunc = nil
	err = plots.MultiPlot(plotEnvs, errs, vars, graphParams, grapherPath)
	if err != nil {
		t.Fatalf("error making Mu_h plot: %v", err)
	}
	// D_1 vs x plots
	if !*tinyX {
		fileLabel = "plot_data.D1_x_data"
	} else {
		fileLabel = "plot_data.tinyX_D1_x_data"
	}
	graphParams[plots.FILE_KEY] = wd + "/" + fileLabel
	graphParams[plots.YLABEL_KEY] = "$D_1$"
	vars.Y = "D1"
	vars.YFunc = nil
	err = plots.MultiPlot(plotEnvs, errs, vars, graphParams, grapherPath)
	if err != nil {
		t.Fatalf("error making D1 plot: %v", err)
	}

}

func solveAndPlot(envs []*tempAll.Environment, epsabs, epsrel float64, fileLabelTc, fileLabelMu, fileLabelD1 string) error {
	// solve
	plotEnvs, errs := tempAll.MultiSolve(envs, epsabs, epsrel, CritTempSolve)
	// Tc vs x plot
	vars := plots.GraphVars{"X", "", []string{"Tz", "Thp"}, []string{"t_z", "t_h^{\\prime}"}, nil, tempAll.GetTemp}
	wd, _ := os.Getwd()
	grapherPath := wd + "/../plots/grapher.py"
	graphParams := map[string]string{plots.FILE_KEY: wd + "/" + fileLabelTc, plots.XLABEL_KEY: "$x$", plots.YLABEL_KEY: "$T_c/t_0$", plots.YMIN_KEY: "0.0"}
	err := plots.MultiPlotStyle(plotEnvs, errs, vars, graphParams, grapherPath, false)
	if err != nil {
		return err
	}
	graphParams[plots.FILE_KEY] = wd + "/" + fileLabelTc + "_BW_"
	err = plots.MultiPlotStyle(plotEnvs, errs, vars, graphParams, grapherPath, true)
	if err != nil {
		return err
	}
	// Mu_h vs x plots
	graphParams[plots.FILE_KEY] = wd + "/" + fileLabelMu
	graphParams[plots.YLABEL_KEY] = "$\\mu_h/t_0$"
	graphParams[plots.YMIN_KEY] = ""
	vars.Y = "Mu_h"
	vars.YFunc = nil
	err = plots.MultiPlot(plotEnvs, errs, vars, graphParams, grapherPath)
	if err != nil {
		return err
	}
	graphParams[plots.FILE_KEY] = wd + "/" + fileLabelMu + "_BW_"
	err = plots.MultiPlotStyle(plotEnvs, errs, vars, graphParams, grapherPath, true)
	if err != nil {
		return err
	}
	// D_1 vs x plots
	graphParams[plots.FILE_KEY] = wd + "/" + fileLabelD1
	graphParams[plots.YLABEL_KEY] = "$D_1$"
	vars.Y = "D1"
	vars.YFunc = nil
	err = plots.MultiPlot(plotEnvs, errs, vars, graphParams, grapherPath)
	if err != nil {
		return err
	}
	graphParams[plots.FILE_KEY] = wd + "/" + fileLabelD1 + "_BW_"
	err = plots.MultiPlotStyle(plotEnvs, errs, vars, graphParams, grapherPath, true)
	if err != nil {
		return err
	}
	return nil
}

func plotTcTp(tcEnvs, tpEnvs []*tempAll.Environment, fileLabelTcTp string) error {
	wd, _ := os.Getwd()
	grapherPath := wd + "/../plots/grapher.py"
	graphParams := map[string]string{plots.FILE_KEY: wd + "/" + fileLabelTcTp, plots.XLABEL_KEY: "$x$", plots.YLABEL_KEY: "$T/t_0$", plots.YMIN_KEY: "0.0"}
	seriesParams := make([]map[string]string, 2)
	seriesParams[0] = map[string]string{"label": "$T_c/t_0$", "style": "b-"}
	seriesParams[1] = map[string]string{"label": "$T_p/t_0$", "style": "r--"}
	xsTc, xsTp, Tcs, Tps := []float64{}, []float64{}, []float64{}, []float64{}
	for _, tcEnv := range tcEnvs {
		xsTc = append(xsTc, tcEnv.X)
		Tcs = append(Tcs, 1.0/tcEnv.Beta)
	}
	for _, tpEnv := range tpEnvs {
		xsTp = append(xsTp, tpEnv.X)
		Tps = append(Tps, 1.0/tpEnv.Beta)
	}
	series := make([]plots.Series, 2)
	series[0] = plots.MakeSeries(xsTc, Tcs)
	series[1] = plots.MakeSeries(xsTp, Tps)
	err := plots.PlotMPL(series, graphParams, seriesParams, grapherPath)
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package slack

import "encoding/json"

// AttachmentField contains information for an attachment field
// An Attachment can contain multiple of these
type AttachmentField struct {
	Title string `json:"title"`
	Value string `json:"value"`
	Short bool   `json:"short"`
}

// AttachmentAction is a button or menu to be included in the attachment. Required when
// using message buttons or menus and otherwise not useful. A maximum of 5 actions may be
// provided per attachment.
type AttachmentAction struct {
	Name            string                        `json:"name"`                       // Required.
	Text            string                        `json:"text"`                       // Required.
	Style           string                        `json:"style,omitempty"`            // Optional. Allowed values: "default", "primary", "danger".
	Type            string                        `json:"type"`                       // Required. Must be set to "button" or "select".
	Value           string                        `json:"value,omitempty"`            // Optional.
	DataSource      string                        `json:"data_source,omitempty"`      // Optional.
	MinQueryLength  int                           `json:"min_query_length,omitempty"` // Optional. Default value is 1.
	Options         []AttachmentActionOption      `json:"options,omitempty"`          // Optional. Maximum of 100 options can be provided in each menu.
	SelectedOptions []AttachmentActionOption      `json:"selected_options,omitempty"` // Optional. The first element of this array will be set as the pre-selected option for this menu.
	OptionGroups    []AttachmentActionOptionGroup `json:"option_groups,omitempty"`    // Optional.
	Confirm         *ConfirmationField            `json:"confirm,omitempty"`          // Optional.
	URL             string                        `json:"url,omitempty"`              // Optional.
}

// AttachmentActionOption the individual option to appear in action menu.
type AttachmentActionOption struct {
	Text        string `json:"text"`                  // Required.
	Value       string `json:"value"`                 // Required.
	Description string `json:"description,omitempty"` // Optional. Up to 30 characters.
}

// AttachmentActionOptionGroup is a semi-hierarchal way to list available options to appear in action menu.
type AttachmentActionOptionGroup struct {
	Text    string                   `json:"text"`    // Required.
	Options []AttachmentActionOption `json:"options"` // Required.
}

// AttachmentActionCallback is sent from Slack when a user clicks a button in an interactive message (aka AttachmentAction)
type AttachmentActionCallback struct {
	Actions    []AttachmentAction `json:"actions"`
	CallbackID string             `json:"callback_id"`
	Team       Team               `json:"team"`
	Channel    Channel            `json:"channel"`
	User       User               `json:"user"`

	Name  string `json:"name"`
	Value string `json:"value"`

	OriginalMessage Message `json:"original_message"`

	ActionTs     string `json:"action_ts"`
	MessageTs    string `json:"message_ts"`
	AttachmentID string `json:"attachment_id"`
	Token        string `json:"token"`
	ResponseURL  string `json:"response_url"`
}

// ConfirmationField are used to ask users to confirm actions
type ConfirmationField struct {
	Title       string `json:"title,omitempty"`        // Optional.
	Text        string `json:"text"`                   // Required.
	OkText      string `json:"ok_text,omitempty"`      // Optional. Defaults to "Okay"
	DismissText string `json:"dismiss_text,omitempty"` // Optional. Defaults to "Cancel"
}

// Attachment contains all the information for an attachment
type Attachment struct {
	Color    string `json:"color,omitempty"`
	Fallback string `json:"fallback"`

	CallbackID string `json:"callback_id,omitempty"`
	ID         int    `json:"id,omitempty"`

	AuthorName    string `json:"author_name,omitempty"`
	AuthorSubname string `json:"author_subname,omitempty"`
	AuthorLink    string `json:"author_link,omitempty"`
	AuthorIcon    string `json:"author_icon,omitempty"`

	Title     string `json:"title,omitempty"`
	TitleLink string `json:"title_link,omitempty"`
	Pretext   string `json:"pretext,omitempty"`
	Text      string `json:"text"`

	ImageURL string `json:"image_url,omitempty"`
	ThumbURL string `json:"thumb_url,omitempty"`

	Fields     []AttachmentField  `json:"fields,omitempty"`
	Actions    []AttachmentAction `json:"actions,omitempty"`
	MarkdownIn []string           `json:"mrkdwn_in,omitempty"`

	Footer     string `json:"footer,omitempty"`
	FooterIcon string `json:"footer_icon,omitempty"`

	Ts json.Number `json:"ts,omitempty"`
}
<commit_msg>add trigger_id in AttachmentActionCallback (#198)<commit_after>package slack

import "encoding/json"

// AttachmentField contains information for an attachment field
// An Attachment can contain multiple of these
type AttachmentField struct {
	Title string `json:"title"`
	Value string `json:"value"`
	Short bool   `json:"short"`
}

// AttachmentAction is a button or menu to be included in the attachment. Required when
// using message buttons or menus and otherwise not useful. A maximum of 5 actions may be
// provided per attachment.
type AttachmentAction struct {
	Name            string                        `json:"name"`                       // Required.
	Text            string                        `json:"text"`                       // Required.
	Style           string                        `json:"style,omitempty"`            // Optional. Allowed values: "default", "primary", "danger".
	Type            string                        `json:"type"`                       // Required. Must be set to "button" or "select".
	Value           string                        `json:"value,omitempty"`            // Optional.
	DataSource      string                        `json:"data_source,omitempty"`      // Optional.
	MinQueryLength  int                           `json:"min_query_length,omitempty"` // Optional. Default value is 1.
	Options         []AttachmentActionOption      `json:"options,omitempty"`          // Optional. Maximum of 100 options can be provided in each menu.
	SelectedOptions []AttachmentActionOption      `json:"selected_options,omitempty"` // Optional. The first element of this array will be set as the pre-selected option for this menu.
	OptionGroups    []AttachmentActionOptionGroup `json:"option_groups,omitempty"`    // Optional.
	Confirm         *ConfirmationField            `json:"confirm,omitempty"`          // Optional.
	URL             string                        `json:"url,omitempty"`              // Optional.
}

// AttachmentActionOption the individual option to appear in action menu.
type AttachmentActionOption struct {
	Text        string `json:"text"`                  // Required.
	Value       string `json:"value"`                 // Required.
	Description string `json:"description,omitempty"` // Optional. Up to 30 characters.
}

// AttachmentActionOptionGroup is a semi-hierarchal way to list available options to appear in action menu.
type AttachmentActionOptionGroup struct {
	Text    string                   `json:"text"`    // Required.
	Options []AttachmentActionOption `json:"options"` // Required.
}

// AttachmentActionCallback is sent from Slack when a user clicks a button in an interactive message (aka AttachmentAction)
type AttachmentActionCallback struct {
	Actions    []AttachmentAction `json:"actions"`
	CallbackID string             `json:"callback_id"`
	Team       Team               `json:"team"`
	Channel    Channel            `json:"channel"`
	User       User               `json:"user"`

	Name  string `json:"name"`
	Value string `json:"value"`

	OriginalMessage Message `json:"original_message"`

	ActionTs     string `json:"action_ts"`
	MessageTs    string `json:"message_ts"`
	AttachmentID string `json:"attachment_id"`
	Token        string `json:"token"`
	ResponseURL  string `json:"response_url"`
	TriggerID    string `json:"trigger_id"`
}

// ConfirmationField are used to ask users to confirm actions
type ConfirmationField struct {
	Title       string `json:"title,omitempty"`        // Optional.
	Text        string `json:"text"`                   // Required.
	OkText      string `json:"ok_text,omitempty"`      // Optional. Defaults to "Okay"
	DismissText string `json:"dismiss_text,omitempty"` // Optional. Defaults to "Cancel"
}

// Attachment contains all the information for an attachment
type Attachment struct {
	Color    string `json:"color,omitempty"`
	Fallback string `json:"fallback"`

	CallbackID string `json:"callback_id,omitempty"`
	ID         int    `json:"id,omitempty"`

	AuthorName    string `json:"author_name,omitempty"`
	AuthorSubname string `json:"author_subname,omitempty"`
	AuthorLink    string `json:"author_link,omitempty"`
	AuthorIcon    string `json:"author_icon,omitempty"`

	Title     string `json:"title,omitempty"`
	TitleLink string `json:"title_link,omitempty"`
	Pretext   string `json:"pretext,omitempty"`
	Text      string `json:"text"`

	ImageURL string `json:"image_url,omitempty"`
	ThumbURL string `json:"thumb_url,omitempty"`

	Fields     []AttachmentField  `json:"fields,omitempty"`
	Actions    []AttachmentAction `json:"actions,omitempty"`
	MarkdownIn []string           `json:"mrkdwn_in,omitempty"`

	Footer     string `json:"footer,omitempty"`
	FooterIcon string `json:"footer_icon,omitempty"`

	Ts json.Number `json:"ts,omitempty"`
}
<|endoftext|>


<commit_before>// SNS client

package sns

import (
	"encoding/json"
	"errors"
	"fmt"
	"sync"
	"time"
	"unicode/utf8"

	SDK "github.com/aws/aws-sdk-go/service/sns"

	"github.com/evalphobia/aws-sdk-go-wrapper/auth"
	"github.com/evalphobia/aws-sdk-go-wrapper/config"
	"github.com/evalphobia/aws-sdk-go-wrapper/log"
)

const (
	snsConfigSectionName = "sns"
	defaultRegion        = "us-west-1"
	defaultEndpoint      = "http://localhost:9292"
	defaultPrefix        = "dev_"

	AppTypeAPNS        = "apns"
	AppTypeAPNSSandbox = "apns_sandbox"
	AppTypeGCM         = "gcm"

	topicMaxDeviceNumber = 10000
	MessageBodyLimit     = 2000
)

var isProduction bool

type AmazonSNS struct {
	apps   map[string]*SNSApp
	topics map[string]*SNSTopic
	Client *SDK.SNS
}

// Create new AmazonSQS struct
func NewClient() *AmazonSNS {
	svc := &AmazonSNS{}
	svc.apps = make(map[string]*SNSApp)
	svc.topics = make(map[string]*SNSTopic)
	region := config.GetConfigValue(snsConfigSectionName, "region", auth.EnvRegion())
	awsConf := auth.NewConfig(region)
	endpoint := config.GetConfigValue(snsConfigSectionName, "endpoint", "")
	switch {
	case endpoint != "":
		awsConf.Endpoint = endpoint
	case region == "":
		awsConf.Region = defaultRegion
		awsConf.Endpoint = defaultEndpoint
	}
	svc.Client = SDK.New(awsConf)
	if config.GetConfigValue(snsConfigSectionName, "app.production", "false") != "false" {
		isProduction = true
	} else {
		isProduction = false
	}
	return svc
}

// Get SNSApp struct
func (svc *AmazonSNS) GetApp(typ string) (*SNSApp, error) {
	// get the app from cache
	app, ok := svc.apps[typ]
	if ok {
		return app, nil
	}
	arn := config.GetConfigValue(snsConfigSectionName, "app."+typ, "")
	if arn == "" {
		errMsg := "[SNS] error, cannot find ARN setting"
		log.Error(errMsg, typ)
		return nil, errors.New(errMsg)
	}
	app = NewApp(arn, typ, svc)
	svc.apps[typ] = app
	return app, nil
}

// Get SNSApp struct of Apple Push Notification Service for iOS
func (svc *AmazonSNS) GetAppAPNS() (*SNSApp, error) {
	if isProduction {
		return svc.GetApp(AppTypeAPNS)
	} else {
		return svc.GetApp(AppTypeAPNSSandbox)
	}
}

// Get SNSApp struct for Google Cloud Messaging for Android
func (svc *AmazonSNS) GetAppGCM() (*SNSApp, error) {
	return svc.GetApp(AppTypeGCM)
}

// Create SNS Topic and return `TopicARN`
func (svc *AmazonSNS) createTopic(name string) (string, error) {
	prefix := config.GetConfigValue(snsConfigSectionName, "prefix", defaultPrefix)
	in := &SDK.CreateTopicInput{
		Name: String(prefix + name),
	}
	resp, err := svc.Client.CreateTopic(in)
	if err != nil {
		log.Error("[SNS] error on `CreateTopic` operation, name="+name, err.Error())
		return "", err
	}
	return *resp.TopicARN, nil
}

// Create SNS Topic and return `TopicARN`
func (svc *AmazonSNS) CreateTopic(name string) (*SNSTopic, error) {
	arn, err := svc.createTopic(name)
	if err != nil {
		return nil, err
	}
	topic := NewTopic(arn, name, svc)
	return topic, nil
}

// Publish notification for arn(topic or endpoint)
func (svc *AmazonSNS) Publish(arn string, msg string, opt map[string]interface{}) error {
	msg = truncateMessage(msg)
	m := make(map[string]string)
	m["default"] = msg
	m["GCM"] = composeMessageGCM(msg)
	m["APNS"] = composeMessageAPNS(msg, opt)
	m["APNS_SANDBOX"] = m["APNS"]
	jsonString, _ := json.Marshal(m)
	resp, err := svc.Client.Publish(&SDK.PublishInput{
		TargetARN:        String(arn),
		Message:          String(string(jsonString)),
		MessageStructure: String("json"),
	})
	if err != nil {
		log.Error("[SNS] error on `Publish` operation, arn="+arn, err.Error())
		return err
	}
	log.Info("[SNS] publish message", *resp.MessageID)
	return nil
}

// Limit message size to the allowed payload size
func truncateMessage(msg string) string {
	if len(msg) <= MessageBodyLimit {
		return msg
	}
	runes := []rune(msg[:MessageBodyLimit])
	valid := len(runes)
	// traverse runes from last string and detect invalid string
	for i := valid; ; {
		i--
		if runes[i] != utf8.RuneError {
			break
		}
		valid = i
	}
	return string(runes[0:valid])
}

// Register endpoint(device) to application
func (svc *AmazonSNS) RegisterEndpoint(device, token string) (*SNSEndpoint, error) {
	var app *SNSApp
	var err error
	switch device {
	case "ios", "apns":
		app, err = svc.GetAppAPNS()
	case "android", "gcm":
		app, err = svc.GetAppGCM()
	default:
		errMsg := "[SNS] Unsupported device, device=" + device
		log.Error(errMsg, token)
		return nil, errors.New(errMsg)
	}
	if err != nil {
		return nil, err
	}
	return app.CreateEndpoint(token)
}

// Publish notification for many endpoints
// (supports single device only)
func (svc *AmazonSNS) BulkPublishByDevice(device string, tokens []string, msg string) error {
	name := fmt.Sprintf("%d", time.Now().UnixNano()) + "_" + device
	topic, err := svc.CreateTopic(name)
	if err != nil {
		return err
	}
	var wg sync.WaitGroup
	for _, token := range tokens {
		wg.Add(1)
		go func(t string) {
			e, err := svc.RegisterEndpoint(device, t)
			if err != nil {
				wg.Done()
				return
			}
			topic.Subscribe(e)
			wg.Done()
		}(token)
	}
	wg.Wait()
	topic.Publish(msg)
	topic.Delete()
	return nil
}

// Publish notification for many endpoints
// tokens is map of string slices, each key stands for device, like "android"/"ios"
// ex) tokens := map[string][]string{ "android": []string{"token1", "token2"}, "ios": []string{"token3", "token4"}}
func (svc *AmazonSNS) BulkPublish(tokens map[string][]string, msg string) error {
	for device, t := range tokens {
		l := len(t)
		max := (l-1)/topicMaxDeviceNumber + 1
		for i := 0; i < max; i++ {
			from := i * topicMaxDeviceNumber
			to := (i + 1) * topicMaxDeviceNumber
			if l < to {
				to = l
			}
			svc.BulkPublishByDevice(device, t[from:to], msg)
		}
	}
	return nil
}
<commit_msg>[SNS] Added publish directly by token<commit_after>// SNS client

package sns

import (
	"encoding/json"
	"errors"
	"fmt"
	"sync"
	"time"
	"unicode/utf8"

	SDK "github.com/aws/aws-sdk-go/service/sns"

	"github.com/evalphobia/aws-sdk-go-wrapper/auth"
	"github.com/evalphobia/aws-sdk-go-wrapper/config"
	"github.com/evalphobia/aws-sdk-go-wrapper/log"
)

const (
	snsConfigSectionName = "sns"
	defaultRegion        = "us-west-1"
	defaultEndpoint      = "http://localhost:9292"
	defaultPrefix        = "dev_"

	AppTypeAPNS        = "apns"
	AppTypeAPNSSandbox = "apns_sandbox"
	AppTypeGCM         = "gcm"

	topicMaxDeviceNumber = 10000
	MessageBodyLimit     = 2000
)

var isProduction bool

type AmazonSNS struct {
	apps   map[string]*SNSApp
	topics map[string]*SNSTopic
	Client *SDK.SNS
}

// Create new AmazonSQS struct
func NewClient() *AmazonSNS {
	svc := &AmazonSNS{}
	svc.apps = make(map[string]*SNSApp)
	svc.topics = make(map[string]*SNSTopic)
	region := config.GetConfigValue(snsConfigSectionName, "region", auth.EnvRegion())
	awsConf := auth.NewConfig(region)
	endpoint := config.GetConfigValue(snsConfigSectionName, "endpoint", "")
	switch {
	case endpoint != "":
		awsConf.Endpoint = endpoint
	case region == "":
		awsConf.Region = defaultRegion
		awsConf.Endpoint = defaultEndpoint
	}
	svc.Client = SDK.New(awsConf)
	if config.GetConfigValue(snsConfigSectionName, "app.production", "false") != "false" {
		isProduction = true
	} else {
		isProduction = false
	}
	return svc
}

// Get SNSApp struct
func (svc *AmazonSNS) GetApp(typ string) (*SNSApp, error) {
	// get the app from cache
	app, ok := svc.apps[typ]
	if ok {
		return app, nil
	}
	arn := config.GetConfigValue(snsConfigSectionName, "app."+typ, "")
	if arn == "" {
		errMsg := "[SNS] error, cannot find ARN setting"
		log.Error(errMsg, typ)
		return nil, errors.New(errMsg)
	}
	app = NewApp(arn, typ, svc)
	svc.apps[typ] = app
	return app, nil
}

// Get SNSApp struct of Apple Push Notification Service for iOS
func (svc *AmazonSNS) GetAppAPNS() (*SNSApp, error) {
	if isProduction {
		return svc.GetApp(AppTypeAPNS)
	} else {
		return svc.GetApp(AppTypeAPNSSandbox)
	}
}

// Get SNSApp struct for Google Cloud Messaging for Android
func (svc *AmazonSNS) GetAppGCM() (*SNSApp, error) {
	return svc.GetApp(AppTypeGCM)
}

// Create SNS Topic and return `TopicARN`
func (svc *AmazonSNS) createTopic(name string) (string, error) {
	prefix := config.GetConfigValue(snsConfigSectionName, "prefix", defaultPrefix)
	in := &SDK.CreateTopicInput{
		Name: String(prefix + name),
	}
	resp, err := svc.Client.CreateTopic(in)
	if err != nil {
		log.Error("[SNS] error on `CreateTopic` operation, name="+name, err.Error())
		return "", err
	}
	return *resp.TopicARN, nil
}

// Create SNS Topic and return `TopicARN`
func (svc *AmazonSNS) CreateTopic(name string) (*SNSTopic, error) {
	arn, err := svc.createTopic(name)
	if err != nil {
		return nil, err
	}
	topic := NewTopic(arn, name, svc)
	return topic, nil
}

// Publish notification for arn(topic or endpoint)
func (svc *AmazonSNS) Publish(arn string, msg string, opt map[string]interface{}) error {
	msg = truncateMessage(msg)
	m := make(map[string]string)
	m["default"] = msg
	m["GCM"] = composeMessageGCM(msg)
	m["APNS"] = composeMessageAPNS(msg, opt)
	m["APNS_SANDBOX"] = m["APNS"]
	jsonString, _ := json.Marshal(m)
	resp, err := svc.Client.Publish(&SDK.PublishInput{
		TargetARN:        String(arn),
		Message:          String(string(jsonString)),
		MessageStructure: String("json"),
	})
	if err != nil {
		log.Error("[SNS] error on `Publish` operation, arn="+arn, err.Error())
		return err
	}
	log.Info("[SNS] publish message", *resp.MessageID)
	return nil
}

// Limit message size to the allowed payload size
func truncateMessage(msg string) string {
	if len(msg) <= MessageBodyLimit {
		return msg
	}
	runes := []rune(msg[:MessageBodyLimit])
	valid := len(runes)
	// traverse runes from last string and detect invalid string
	for i := valid; ; {
		i--
		if runes[i] != utf8.RuneError {
			break
		}
		valid = i
	}
	return string(runes[0:valid])
}

// Register endpoint(device) to application
func (svc *AmazonSNS) RegisterEndpoint(device, token string) (*SNSEndpoint, error) {
	var app *SNSApp
	var err error
	switch device {
	case "ios", "apns":
		app, err = svc.GetAppAPNS()
	case "android", "gcm":
		app, err = svc.GetAppGCM()
	default:
		errMsg := "[SNS] Unsupported device, device=" + device
		log.Error(errMsg, token)
		return nil, errors.New(errMsg)
	}
	if err != nil {
		return nil, err
	}
	return app.CreateEndpoint(token)
}

// PublishAPNSByToken sends push message for iOS device by device token
func (svc *AmazonSNS) PublishAPNSByToken(token string, msg string, badge int) error {
	return svc.PublishByToken(AppTypeAPNS, token, msg, badge)
}

// PublishGCMByToken sends push message for Android device by device token
func (svc *AmazonSNS) PublishGCMByToken(token string, msg string, badge int) error {
	return svc.PublishByToken(AppTypeGCM, token, msg, badge)
}

// PublishByToken sends push message by device token
func (svc *AmazonSNS) PublishByToken(device, token string, msg string, badge int) error {
	ep, err := svc.RegisterEndpoint(device, token)
	if err != nil {
		return err
	}
	return ep.Publish(msg, badge)
}

// Publish notification for many endpoints
// (supports single device only)
func (svc *AmazonSNS) BulkPublishByDevice(device string, tokens []string, msg string) error {
	name := fmt.Sprintf("%d", time.Now().UnixNano()) + "_" + device
	topic, err := svc.CreateTopic(name)
	if err != nil {
		return err
	}
	var wg sync.WaitGroup
	for _, token := range tokens {
		wg.Add(1)
		go func(t string) {
			e, err := svc.RegisterEndpoint(device, t)
			if err != nil {
				wg.Done()
				return
			}
			topic.Subscribe(e)
			wg.Done()
		}(token)
	}
	wg.Wait()
	topic.Publish(msg)
	topic.Delete()
	return nil
}

// Publish notification for many endpoints
// tokens is map of string slices, each key stands for device, like "android"/"ios"
// ex) tokens := map[string][]string{ "android": []string{"token1", "token2"}, "ios": []string{"token3", "token4"}}
func (svc *AmazonSNS) BulkPublish(tokens map[string][]string, msg string) error {
	for device, t := range tokens {
		l := len(t)
		max := (l-1)/topicMaxDeviceNumber + 1
		for i := 0; i < max; i++ {
			from := i * topicMaxDeviceNumber
			to := (i + 1) * topicMaxDeviceNumber
			if l < to {
				to = l
			}
			svc.BulkPublishByDevice(device, t[from:to], msg)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package soap

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"time"
	"unicode/utf8"
)

func MarshalFixed14_4(v float64) (string, error) {
	if v >= 1e14 || v <= -1e14 {
		return "", fmt.Errorf("soap fixed14.4: value %v out of bounds", v)
	}
	return strconv.FormatFloat(v, 'f', 4, 64), nil
}

func UnmarshalFixed14_4(s string) (float64, error) {
	v, err := strconv.ParseFloat(s, 64)
	if err != nil {
		return 0, err
	}
	if v >= 1e14 || v <= -1e14 {
		return 0, fmt.Errorf("soap fixed14.4: value %q out of bounds", s)
	}
	return v, nil
}

func MarshalChar(v rune) (string, error) {
	if v == 0 {
		return "", errors.New("soap char: rune 0 is not allowed")
	}
	return string(v), nil
}

func UnmarshalChar(s string) (rune, error) {
	if len(s) == 0 {
		return 0, errors.New("soap char: got empty string")
	}
	r, n := utf8.DecodeRune([]byte(s))
	if n != len(s) {
		return 0, fmt.Errorf("soap char: value %q is not a single rune", s)
	}
	return r, nil
}

// MarshalDate marshals time.Time to SOAP "date" type. Note that this converts
// to local time, and discards the time-of-day components.
func MarshalDate(v time.Time) (string, error) {
	return v.Local().Format("2006-01-02"), nil
}

var dateFmts = []string{"2006-01-02", "20060102"}

// UnmarshalDate unmarshals time.Time from SOAP "date" type. This outputs the
// date as midnight in the local time zone.
func UnmarshalDate(s string) (time.Time, error) {
	for _, f := range dateFmts {
		if t, err := time.ParseInLocation(f, s, time.Local); err == nil {
			return t, nil
		}
	}
	return time.Time{}, fmt.Errorf("soap date: value %q is not in a recognized date format", s)
}

// TimeOfDay is used in cases where SOAP "time" or "time.tz" is used.
type TimeOfDay struct {
	// Duration of time since midnight.
	FromMidnight time.Duration

	// Set to true if Offset is specified. If false, then the timezone is
	// unspecified (and by ISO8601 - implies some "local" time).
	HasOffset bool

	// Offset is non-zero only if time.tz is used. It is otherwise ignored. If
	// non-zero, then it is regarded as a UTC offset in seconds. Note that the
	// sub-minutes is ignored by the marshal function.
	Offset int16
}

// MarshalTimeOfDay marshals TimeOfDay to the "time" type.
func MarshalTimeOfDay(v TimeOfDay) (string, error) {
	d := int64(v.FromMidnight / time.Second)
	hour := d / 3600
	d = d % 3600
	minute := d / 60
	second := d % 60

	return fmt.Sprintf("%02d:%02d:%02d", hour, minute, second), nil
}

// UnmarshalTimeOfDay unmarshals TimeOfDay from the "time" type.
func UnmarshalTimeOfDay(s string) (TimeOfDay, error) {
	t, err := UnmarshalTimeOfDayTz(s)
	if err != nil {
		return TimeOfDay{}, err
	} else if t.HasOffset {
		return TimeOfDay{}, fmt.Errorf("soap time: value %q contains unexpected timezone")
	}
	return t, nil
}

// MarshalTimeOfDayTz marshals TimeOfDay to the "time.tz" type.
func MarshalTimeOfDayTz(v TimeOfDay) (string, error) {
	d := int64(v.FromMidnight / time.Second)
	hour := d / 3600
	d = d % 3600
	minute := d / 60
	second := d % 60

	tz := ""
	if v.HasOffset {
		if v.Offset == 0 {
			tz = "Z"
		} else {
			offsetMins := v.Offset / 60
			sign := '+'
			if offsetMins < 1 {
				offsetMins = -offsetMins
				sign = '-'
			}
			tz = fmt.Sprintf("%c%02d:%02d", sign, offsetMins/60, offsetMins%60)
		}
	}

	return fmt.Sprintf("%02d:%02d:%02d%s", hour, minute, second, tz), nil
}

var timeRegexp = regexp.MustCompile(
	`^(\d\d)(?::?(\d\d)(?::?(\d\d))?)?` + // hh[:mm[:ss]]
		`(?:(Z)|([+-])(\d\d)(?::?(\d\d))?)?$`) // Z | ±hh[:mm]

// UnmarshalTimeOfDayTz unmarshals TimeOfDay from the "time.tz" type.
func UnmarshalTimeOfDayTz(s string) (TimeOfDay, error) {
	parts := timeRegexp.FindStringSubmatch(s)
	if parts == nil {
		return TimeOfDay{}, fmt.Errorf("soap time.tz: value %q is not in ISO8601 time format", s)
	}

	// HH:MM:SS parsing.
	parts = parts[1:]
	var iParts [3]int64
	for i, pStr := range parts[:3] {
		iParts[i], _ = strconv.ParseInt(pStr, 10, 64)
	}
	hour, minute, second := iParts[0], iParts[1], iParts[2]

	fromMidnight := time.Duration(hour*3600+minute*60+second) * time.Second

	// ISO8601 special case - values up to 24:00:00 are allowed, so using
	// strictly greater-than for the maximum value.
	if fromMidnight > 24*time.Hour || minute >= 60 || second >= 60 {
		return TimeOfDay{}, fmt.Errorf("soap time.tz: value %q has value(s) out of range", s)
	}

	// Timezone offset parsing.
	hasOffset := false
	var offset int64
	if parts[3] == "Z" {
		hasOffset = true
		offset = 0
	} else if parts[4] != "" {
		hasOffset = true
		hours, _ := strconv.ParseInt(parts[5], 10, 64)
		var mins int64
		if parts[6] != "" {
			mins, _ = strconv.ParseInt(parts[6], 10, 64)
		}
		offset = hours*3600 + mins*60
		if parts[4] == "-" {
			offset = -offset
		}
	}
	return TimeOfDay{
		FromMidnight: time.Duration(hour*3600+minute*60+second) * time.Second,
		HasOffset:    hasOffset,
		Offset:       int16(offset),
	}, nil
}
<commit_msg>Document the other implemented marshallers.<commit_after>package soap

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"time"
	"unicode/utf8"
)

// MarshalFixed14_4 marshals float64 to SOAP "fixed.14.4" type.
func MarshalFixed14_4(v float64) (string, error) {
	if v >= 1e14 || v <= -1e14 {
		return "", fmt.Errorf("soap fixed14.4: value %v out of bounds", v)
	}
	return strconv.FormatFloat(v, 'f', 4, 64), nil
}

// UnmarshalFixed14_4 unmarshals float64 from SOAP "fixed.14.4" type.
func UnmarshalFixed14_4(s string) (float64, error) {
	v, err := strconv.ParseFloat(s, 64)
	if err != nil {
		return 0, err
	}
	if v >= 1e14 || v <= -1e14 {
		return 0, fmt.Errorf("soap fixed14.4: value %q out of bounds", s)
	}
	return v, nil
}

// MarshalChar marshals rune to SOAP "char" type.
func MarshalChar(v rune) (string, error) {
	if v == 0 {
		return "", errors.New("soap char: rune 0 is not allowed")
	}
	return string(v), nil
}

// UnmarshalChar unmarshals rune from SOAP "char" type.
func UnmarshalChar(s string) (rune, error) {
	if len(s) == 0 {
		return 0, errors.New("soap char: got empty string")
	}
	r, n := utf8.DecodeRune([]byte(s))
	if n != len(s) {
		return 0, fmt.Errorf("soap char: value %q is not a single rune", s)
	}
	return r, nil
}

// MarshalDate marshals time.Time to SOAP "date" type. Note that this converts
// to local time, and discards the time-of-day components.
func MarshalDate(v time.Time) (string, error) {
	return v.Local().Format("2006-01-02"), nil
}

var dateFmts = []string{"2006-01-02", "20060102"}

// UnmarshalDate unmarshals time.Time from SOAP "date" type. This outputs the
// date as midnight in the local time zone.
func UnmarshalDate(s string) (time.Time, error) {
	for _, f := range dateFmts {
		if t, err := time.ParseInLocation(f, s, time.Local); err == nil {
			return t, nil
		}
	}
	return time.Time{}, fmt.Errorf("soap date: value %q is not in a recognized date format", s)
}

// TimeOfDay is used in cases where SOAP "time" or "time.tz" is used.
type TimeOfDay struct {
	// Duration of time since midnight.
	FromMidnight time.Duration

	// Set to true if Offset is specified. If false, then the timezone is
	// unspecified (and by ISO8601 - implies some "local" time).
	HasOffset bool

	// Offset is non-zero only if time.tz is used. It is otherwise ignored. If
	// non-zero, then it is regarded as a UTC offset in seconds. Note that the
	// sub-minutes is ignored by the marshal function.
	Offset int16
}

// MarshalTimeOfDay marshals TimeOfDay to the "time" type.
func MarshalTimeOfDay(v TimeOfDay) (string, error) {
	d := int64(v.FromMidnight / time.Second)
	hour := d / 3600
	d = d % 3600
	minute := d / 60
	second := d % 60

	return fmt.Sprintf("%02d:%02d:%02d", hour, minute, second), nil
}

// UnmarshalTimeOfDay unmarshals TimeOfDay from the "time" type.
func UnmarshalTimeOfDay(s string) (TimeOfDay, error) {
	t, err := UnmarshalTimeOfDayTz(s)
	if err != nil {
		return TimeOfDay{}, err
	} else if t.HasOffset {
		return TimeOfDay{}, fmt.Errorf("soap time: value %q contains unexpected timezone")
	}
	return t, nil
}

// MarshalTimeOfDayTz marshals TimeOfDay to the "time.tz" type.
func MarshalTimeOfDayTz(v TimeOfDay) (string, error) {
	d := int64(v.FromMidnight / time.Second)
	hour := d / 3600
	d = d % 3600
	minute := d / 60
	second := d % 60

	tz := ""
	if v.HasOffset {
		if v.Offset == 0 {
			tz = "Z"
		} else {
			offsetMins := v.Offset / 60
			sign := '+'
			if offsetMins < 1 {
				offsetMins = -offsetMins
				sign = '-'
			}
			tz = fmt.Sprintf("%c%02d:%02d", sign, offsetMins/60, offsetMins%60)
		}
	}

	return fmt.Sprintf("%02d:%02d:%02d%s", hour, minute, second, tz), nil
}

var timeRegexp = regexp.MustCompile(
	`^(\d\d)(?::?(\d\d)(?::?(\d\d))?)?` + // hh[:mm[:ss]]
		`(?:(Z)|([+-])(\d\d)(?::?(\d\d))?)?$`) // Z | ±hh[:mm]

// UnmarshalTimeOfDayTz unmarshals TimeOfDay from the "time.tz" type.
func UnmarshalTimeOfDayTz(s string) (TimeOfDay, error) {
	parts := timeRegexp.FindStringSubmatch(s)
	if parts == nil {
		return TimeOfDay{}, fmt.Errorf("soap time.tz: value %q is not in ISO8601 time format", s)
	}

	// HH:MM:SS parsing.
	parts = parts[1:]
	var iParts [3]int64
	for i, pStr := range parts[:3] {
		iParts[i], _ = strconv.ParseInt(pStr, 10, 64)
	}
	hour, minute, second := iParts[0], iParts[1], iParts[2]

	fromMidnight := time.Duration(hour*3600+minute*60+second) * time.Second

	// ISO8601 special case - values up to 24:00:00 are allowed, so using
	// strictly greater-than for the maximum value.
	if fromMidnight > 24*time.Hour || minute >= 60 || second >= 60 {
		return TimeOfDay{}, fmt.Errorf("soap time.tz: value %q has value(s) out of range", s)
	}

	// Timezone offset parsing.
	hasOffset := false
	var offset int64
	if parts[3] == "Z" {
		hasOffset = true
		offset = 0
	} else if parts[4] != "" {
		hasOffset = true
		hours, _ := strconv.ParseInt(parts[5], 10, 64)
		var mins int64
		if parts[6] != "" {
			mins, _ = strconv.ParseInt(parts[6], 10, 64)
		}
		offset = hours*3600 + mins*60
		if parts[4] == "-" {
			offset = -offset
		}
	}
	return TimeOfDay{
		FromMidnight: time.Duration(hour*3600+minute*60+second) * time.Second,
		HasOffset:    hasOffset,
		Offset:       int16(offset),
	}, nil
}
<|endoftext|>


<commit_before>package auth

import (
	"github.com/LUSHDigital/microservice-core-golang/response"
	"net/http"
)

const errorMissingRequiredGrants = "missing required grants"

// Grant is a type alised string to represent permissions grants.
type Grant string

// HandlerGrants is an HTTP handler to check that the consumer in the request context has the required grants.
func HandlerGrants(grants []Grant, next http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		consumer := ConsumerFromContext(r.Context())
		if consumer.HasGrants(grants...) {
			next.ServeHTTP(w, r)
			return
		}

		response.New(http.StatusUnauthorized, errorMissingRequiredGrants, nil).WriteTo(w)
	})
}
<commit_msg>Fix broken function call<commit_after>package auth

import (
	"github.com/LUSHDigital/microservice-core-golang/response"
	"net/http"
)

const errorMissingRequiredGrants = "missing required grants"

// Grant is a type alised string to represent permissions grants.
type Grant string

// HandlerGrants is an HTTP handler to check that the consumer in the request context has the required grants.
func HandlerGrants(grants []Grant, next http.HandlerFunc) http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		consumer := ConsumerFromContext(r.Context())
		if consumer.HasAnyGrant(grants...) {
			next.ServeHTTP(w, r)
			return
		}

		response.New(http.StatusUnauthorized, errorMissingRequiredGrants, nil).WriteTo(w)
	})
}
<|endoftext|>


<commit_before>package twitch

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
)

var (
	authSignRegEx = regexp.MustCompile("/twitch/signin/([\\w]+)/*")
)

type authToken struct {
	AuthToken struct {
		CreatedAtString string   `json:"created_at"` // 2013-06-03T19:12:02Z
		UpdatedAtStr    string   `json:"updated_at"` // 2016-12-14T01:01:44Z
		ScopeList       []string `json:"scopes"`
	} `json:"authorization"`
	ClientID string `json:"client_id"` // "uo6dggojyb8d6soh92zknwmi5ej1q2"
	UserID   string `json:"user_id"`   // "44322889"
	Username string `json:"user_name"` // "dallas"
	IsValid  bool   `json:"valid"`     // true
}

// UserAuth - Used to manage OAuth for Logins
type UserAuth struct {
	TwitchID string
	User     *User

	authcode   string
	oauthState string
	token      *authToken

	scopes map[string]bool

	client *Client
}

func makeUserAuth(id string, twitchClient *Client, reqScopes []string) *UserAuth {
	newScope := make(map[string]bool)
	for _, ov := range ValidScopes {
		newScope[ov] = false
		for _, v := range reqScopes {
			if v == ov {
				newScope[ov] = true
			}
		}
	}

	au := UserAuth{
		TwitchID: id,
		User:     nil,
		token:    nil,

		oauthState: GenerateRandomString(16),
		scopes:     newScope,
		client:     twitchClient,
	}

	return &au
}

func (ua *UserAuth) getRootToken() error {

	ua.token = &authToken{}

	tokenContain := &struct {
		Token *authToken `json:"token"`
	}{ua.token}

	_, err := ua.client.Get(ua, "", tokenContain)
	if err != nil {
		ua.token = nil
		return err
	}

	if ua.token.IsValid == false {
		err = fmt.Errorf("Root Response is Invalid: %v ", ua.token)
		ua.token = nil
		return err
	}

	if clientID != ua.token.ClientID {
		ua.token = nil
		return fmt.Errorf("Client ID doesn't match [%s:%s]", clientID, ua.token.ClientID)
	}

	ua.TwitchID = ua.token.UserID

	return nil
}

// GetAuth - checks if auth and if auth returns auth code
func (ua *UserAuth) GetAuth() (bool, string) {
	if ua.token == nil {
		return false, ""
	}
	return true, ua.authcode
}

// GetIrcAuth - returns the stuff needed for IRC
func (ua *UserAuth) GetIrcAuth() (hasauth bool, name string, pass string, addr string) {
	isAuth, c := ua.GetAuth()
	if !isAuth {
		return false, "", "", ircServerAddr
	}

	return true, ua.token.Username, "oauth:" + c, ircServerAddr
}

func (ua *UserAuth) getScopeString() string {
	if ua.scopes == nil {
		return ""
	}

	s := ""
	for k, v := range ua.scopes {
		if v {
			s += k + "+"
		}
	}

	s = strings.TrimRight(s, "+")

	return s
}

func (ua *UserAuth) updateScope(scopeList []string) {
	for k := range ua.scopes {
		ua.scopes[k] = false
	}
	for _, k := range scopeList {
		ua.scopes[k] = true
	}
}

func (ua *UserAuth) checkScope(reqScopes ...string) error {
	for _, v := range reqScopes {
		if ua.scopes[v] == false {
			return fmt.Errorf("Scope Required: %s", v)
		}
	}

	return nil
}

func (ua *UserAuth) handleOAuthStart(w http.ResponseWriter, req *http.Request) {
	if ua.token != nil {
		http.Error(w, "Already logged in admin", http.StatusConflict)
		return
	}

	fullRedirStr := fmt.Sprintf(baseURL, rootURL, clientID, redirURL, ua.getScopeString(), ua.oauthState)
	http.Redirect(w, req, fullRedirStr, http.StatusSeeOther)
}

func (ah *Client) findUserByState(state string) *UserAuth {
	for _, v := range ah.AuthUsers {
		if v.oauthState == state {
			return v
		}
	}

	return nil
}

func (ah *Client) handlePublicOAuthResult(w http.ResponseWriter, req *http.Request) {
	qList := req.URL.Query()

	c, ok := qList["code"]
	if !ok {
		s := fmt.Sprintf("Hello your auth was cancelled")

		for k, v := range qList {
			s += fmt.Sprintf("%s:%s\n", k, v)
		}
		http.Error(w, s, 400)
		return
	}

	stateList, ok := qList["state"]
	if !ok {
		http.Error(w, "Invalid State", 400)
		return
	}

	var authU *UserAuth
	isAdmin := false
	stateVal := stateList[0]
	// Check if Admin Login
	if (ah.AdminAuth.token == nil) && stateVal == ah.AdminAuth.oauthState {
		authU = ah.AdminAuth
		isAdmin = true
	} else { // Normal user do logic
		authU = ah.findUserByState(stateVal)
	}

	if authU == nil {
		http.Error(w, "Invalid Auth State", 400)
		return
	}

	scopeList, ok := qList["scope"]
	if !ok {
		http.Error(w, "Invalid Scope", 400)
		return
	}
	scopeList = strings.Split(scopeList[0], " ")

	// Save State
	authU.token = nil
	authU.updateScope(scopeList)
	authU.authcode = c[0]

	err := ah.handleOAuthResult(authU)
	if err != nil {
		log.Println(err, authU)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if isAdmin {
		if ah.AdminAuth.token != nil {
			ah.AdminChannel <- 1
		} else {
			http.Error(w, "Admin Auth has no token", 400)
		}
	}

	fmt.Fprintf(w, "Logged in %s #%s", authU.token.Username, authU.TwitchID)

	authU.User, err = ah.User.Get(authU.TwitchID)

	if err != nil {
		log.Println("Failed to Get User Data", err)
	}

}

func (ah *Client) handleOAuthResult(authU *UserAuth) error {

	// Setup Payload
	data := url.Values{}
	data.Set("client_id", clientID)
	data.Set("client_secret", clientSecret)
	data.Set("grant_type", "authorization_code")
	data.Set("redirect_uri", redirURL)
	data.Set("code", authU.authcode)
	data.Set("state", authU.oauthState)
	payload := strings.NewReader(data.Encode())

	// Server get Auth Code
	req, err := http.NewRequest("POST", "https://api.twitch.tv/kraken/oauth2/token", payload)
	if err != nil {
		log.Println("Failed to Build Request")
		return err
	}

	req.Header.Add("accept", "application/vnd.twitchtv.v5+json")
	req.Header.Add("client-id", clientID)
	req.Header.Add("content-type", "application/x-www-form-urlencoded")
	req.Header.Add("content-length", strconv.Itoa(len(data.Encode())))
	req.Header.Add("cache-control", "no-cache")

	resp, err := ah.httpClient.Do(req)
	if err != nil || resp.StatusCode >= 400 {
		log.Printf("---\n %#v \n---\n %#v \n---\n %#v \n---\n", req, resp, payload)
		if err != nil {
			return err
		}
		return fmt.Errorf("Failed to auth follow through %d - %s", resp.StatusCode, resp.Body)
	}

	// Decode JSON
	defer resp.Body.Close()
	tokenStruct := struct {
		Token   string `json:"access_token"`
		Refresh string `json:"refresh_token"`
	}{}
	err = json.NewDecoder(resp.Body).Decode(&tokenStruct)
	if err != nil {
		return err
	}
	authU.authcode = tokenStruct.Token

	err = authU.getRootToken()
	if err != nil {
		return err
	}

	// Output Result
	return nil
}
<commit_msg>Working on Dashboard<commit_after>package twitch

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
)

var (
	authSignRegEx = regexp.MustCompile("/twitch/signin/([\\w]+)/*")
)

type authToken struct {
	AuthToken struct {
		CreatedAtString string   `json:"created_at"` // 2013-06-03T19:12:02Z
		UpdatedAtStr    string   `json:"updated_at"` // 2016-12-14T01:01:44Z
		ScopeList       []string `json:"scopes"`
	} `json:"authorization"`
	ClientID string `json:"client_id"` // "uo6dggojyb8d6soh92zknwmi5ej1q2"
	UserID   string `json:"user_id"`   // "44322889"
	Username string `json:"user_name"` // "dallas"
	IsValid  bool   `json:"valid"`     // true
}

// UserAuth - Used to manage OAuth for Logins
type UserAuth struct {
	TwitchID string
	User     *User

	authcode   string
	oauthState string
	token      *authToken

	scopes map[string]bool

	client *Client
}

func makeUserAuth(id string, twitchClient *Client, reqScopes []string) *UserAuth {
	newScope := make(map[string]bool)
	for _, ov := range ValidScopes {
		newScope[ov] = false
		for _, v := range reqScopes {
			if v == ov {
				newScope[ov] = true
			}
		}
	}

	au := UserAuth{
		TwitchID: id,
		User:     nil,
		token:    nil,

		oauthState: GenerateRandomString(16),
		scopes:     newScope,
		client:     twitchClient,
	}

	return &au
}

func (ua *UserAuth) GetNick() string {
	if ua.User != nil {
		return ua.User.Name
	}

	if ua.token != nil {
		return ua.token.Username
	}

	return ua.TwitchID
}

func (ua *UserAuth) getRootToken() error {

	ua.token = &authToken{}

	tokenContain := &struct {
		Token *authToken `json:"token"`
	}{ua.token}

	_, err := ua.client.Get(ua, "", tokenContain)
	if err != nil {
		ua.token = nil
		return err
	}

	if ua.token.IsValid == false {
		err = fmt.Errorf("Root Response is Invalid: %v ", ua.token)
		ua.token = nil
		return err
	}

	if clientID != ua.token.ClientID {
		ua.token = nil
		return fmt.Errorf("Client ID doesn't match [%s:%s]", clientID, ua.token.ClientID)
	}

	ua.TwitchID = ua.token.UserID

	return nil
}

// GetAuth - checks if auth and if auth returns auth code
func (ua *UserAuth) GetAuth() (bool, string) {
	if ua.token == nil {
		return false, ""
	}
	return true, ua.authcode
}

// GetIrcAuth - returns the stuff needed for IRC
func (ua *UserAuth) GetIrcAuth() (hasauth bool, name string, pass string, addr string) {
	isAuth, c := ua.GetAuth()
	if !isAuth {
		return false, "", "", ircServerAddr
	}

	return true, ua.token.Username, "oauth:" + c, ircServerAddr
}

func (ua *UserAuth) getScopeString() string {
	if ua.scopes == nil {
		return ""
	}

	s := ""
	for k, v := range ua.scopes {
		if v {
			s += k + "+"
		}
	}

	s = strings.TrimRight(s, "+")

	return s
}

func (ua *UserAuth) updateScope(scopeList []string) {
	for k := range ua.scopes {
		ua.scopes[k] = false
	}
	for _, k := range scopeList {
		ua.scopes[k] = true
	}
}

func (ua *UserAuth) checkScope(reqScopes ...string) error {
	for _, v := range reqScopes {
		if ua.scopes[v] == false {
			return fmt.Errorf("Scope Required: %s", v)
		}
	}

	return nil
}

func (ua *UserAuth) handleOAuthStart(w http.ResponseWriter, req *http.Request) {
	if ua.token != nil {
		http.Error(w, "Already logged in admin", http.StatusConflict)
		return
	}

	fullRedirStr := fmt.Sprintf(baseURL, rootURL, clientID, redirURL, ua.getScopeString(), ua.oauthState)
	http.Redirect(w, req, fullRedirStr, http.StatusSeeOther)
}

func (ah *Client) findUserByState(state string) *UserAuth {
	for _, v := range ah.AuthUsers {
		if v.oauthState == state {
			return v
		}
	}

	return nil
}

func (ah *Client) handlePublicOAuthResult(w http.ResponseWriter, req *http.Request) {
	qList := req.URL.Query()

	c, ok := qList["code"]
	if !ok {
		s := fmt.Sprintf("Hello your auth was cancelled")

		for k, v := range qList {
			s += fmt.Sprintf("%s:%s\n", k, v)
		}
		http.Error(w, s, 400)
		return
	}

	stateList, ok := qList["state"]
	if !ok {
		http.Error(w, "Invalid State", 400)
		return
	}

	var authU *UserAuth
	isAdmin := false
	stateVal := stateList[0]
	// Check if Admin Login
	if (ah.AdminAuth.token == nil) && stateVal == ah.AdminAuth.oauthState {
		authU = ah.AdminAuth
		isAdmin = true
	} else { // Normal user do logic
		authU = ah.findUserByState(stateVal)
	}

	if authU == nil {
		http.Error(w, "Invalid Auth State", 400)
		return
	}

	scopeList, ok := qList["scope"]
	if !ok {
		http.Error(w, "Invalid Scope", 400)
		return
	}
	scopeList = strings.Split(scopeList[0], " ")

	// Save State
	authU.token = nil
	authU.updateScope(scopeList)
	authU.authcode = c[0]

	err := ah.handleOAuthResult(authU)
	if err != nil {
		log.Println(err, authU)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	if isAdmin {
		if ah.AdminAuth.token != nil {
			ah.AdminChannel <- 1
		} else {
			http.Error(w, "Admin Auth has no token", 400)
		}
	}

	fmt.Fprintf(w, "Logged in %s #%s", authU.token.Username, authU.TwitchID)

	authU.User, err = ah.User.Get(authU.TwitchID)

	if err != nil {
		log.Println("Failed to Get User Data", err)
	}

}

func (ah *Client) handleOAuthResult(authU *UserAuth) error {

	// Setup Payload
	data := url.Values{}
	data.Set("client_id", clientID)
	data.Set("client_secret", clientSecret)
	data.Set("grant_type", "authorization_code")
	data.Set("redirect_uri", redirURL)
	data.Set("code", authU.authcode)
	data.Set("state", authU.oauthState)
	payload := strings.NewReader(data.Encode())

	// Server get Auth Code
	req, err := http.NewRequest("POST", "https://api.twitch.tv/kraken/oauth2/token", payload)
	if err != nil {
		log.Println("Failed to Build Request")
		return err
	}

	req.Header.Add("accept", "application/vnd.twitchtv.v5+json")
	req.Header.Add("client-id", clientID)
	req.Header.Add("content-type", "application/x-www-form-urlencoded")
	req.Header.Add("content-length", strconv.Itoa(len(data.Encode())))
	req.Header.Add("cache-control", "no-cache")

	resp, err := ah.httpClient.Do(req)
	if err != nil || resp.StatusCode >= 400 {
		log.Printf("---\n %#v \n---\n %#v \n---\n %#v \n---\n", req, resp, payload)
		if err != nil {
			return err
		}
		return fmt.Errorf("Failed to auth follow through %d - %s", resp.StatusCode, resp.Body)
	}

	// Decode JSON
	defer resp.Body.Close()
	tokenStruct := struct {
		Token   string `json:"access_token"`
		Refresh string `json:"refresh_token"`
	}{}
	err = json.NewDecoder(resp.Body).Decode(&tokenStruct)
	if err != nil {
		return err
	}
	authU.authcode = tokenStruct.Token

	err = authU.getRootToken()
	if err != nil {
		return err
	}

	// Output Result
	return nil
}
<|endoftext|>


<commit_before>package sqlsess

import (
	"database/sql"
	"net/http"
	"time"

	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"

	"github.com/BurntSushi/locker"
)

var (
	SessionLastUpdated = "__sess_last_updated"
	CookieIdName       = "sess_sessionid"
	SqlTableName       = "sess_session"
	SqlCreateSession   = `
	CREATE TABLE ` + SqlTableName + ` IF NOT EXISTS (
		id BYTEA NOT NULL,
		name VARCHAR (255) NOT NULL,
		key TEXT NOT NULL,
		value TEXT NOT NULL,
		PRIMARY KEY (id, name, key)
	)
	`
)

type Store struct {
	*sql.DB
	hashKey, blockKey []byte
}

func Open(db *sql.DB) (*Store, error) {
	if _, err := db.Exec(SqlCreateSession); err != nil {
		return nil, err
	}

	s := &Store{
		DB:       db,
		hashKey:  securecookie.GenerateRandomKey(64),
		blockKey: securecookie.GenerateRandomKey(32),
	}
	return s, nil
}

func (s *Store) Clean(inactive time.Duration) error {
	locker.Lock("clean")
	defer locker.Unlock("clean")

	rows, err := s.Query(`
		SELECT id, value
		FROM ` + SqlTableName + `
		WHERE key = $1
	`, SessionLastUpdated)
	if err != nil {
		return err
	}
	defer rows.Close()

	cutoff := time.Now().UTC().Add(-inactive)
	for rows.Next() {
		var id []byte
		var last string
		if err := rows.Scan(&id, &last); err != nil {
			return err
		}
		lastUp, err := time.Parse(time.RFC3339Nano, last)
		if err != nil {
			return err
		}
		if lastUp.Before(cutoff) {
			_, err = s.Exec(
				"DELETE FROM " + SqlTableName + " WHERE id = $1", id)
			if err != nil {
				return err
			}
		}
	}
	return rows.Err()
}

func (s *Store) Get(r *http.Request, name string) (*sessions.Session, error) {
	return sessions.GetRegistry(r).Get(s, name)
}

func (s *Store) New(r *http.Request, name string) (*sessions.Session, error) {
	sess := sessions.NewSession(s, name)
	sess.ID = s.id(r)

	RLock(sess)
	defer RUnlock(sess)

	rows, err := s.Query(`
		SELECT key, value
		FROM ` + SqlTableName + `
		WHERE id = $1 AND name = $2
	`, []byte(sess.ID), name)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	for rows.Next() {
		var k, v string
		if err := rows.Scan(&k, &v); err != nil {
			return nil, err
		}
		sess.Values[k] = v
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return sess, nil
}

func (s *Store) Save(
	r *http.Request,
	w http.ResponseWriter,
	sess *sessions.Session,
) error {
	Lock(sess)
	defer Unlock(sess)

	s.writeCookie(r, w, CookieIdName, sess.ID)

	id := []byte(sess.ID)
	sess.Values[SessionLastUpdated] = time.Now().UTC()
	tx, err := s.Begin()
	if err != nil {
		return err
	}

	_, err = tx.Exec("DELETE FROM " + SqlTableName + " WHERE id = $1", id)
	if err != nil {
		tx.Rollback()
		return err
	}

	prep, err := tx.Prepare(`
		INSERT INTO ` + SqlTableName + `
			(id, name, key, value) VALUES ($1, $2, $3, $4)
	`)
	if err != nil {
		tx.Rollback()
		return err
	}
	defer prep.Close()

	for k, v := range sess.Values {
		if _, err := prep.Exec(id, sess.Name(), k, v); err != nil {
			tx.Rollback()
			return err
		}
	}
	return tx.Commit()
}

// id returns either the session id from a user's cookie or generates
// a fresh one if the cookie is inaccessible or missing.
func (s *Store) id(r *http.Request) string {
	id := s.readCookie(r, CookieIdName)
	if len(id) == 0 {
		id = string(securecookie.GenerateRandomKey(64))
	}
	return id
}

// SetKeys sets the hash and block keys used to read and write the session
// cookie. A hash key is required and is used to to authenticate a cookie
// value using HMAC. It's recommend to be 32 or 64 bytes.
//
// A block key is optional and is used to encrypt the cookie value. If it's
// set to nil, then encryption will not be used. This package uses AES, so
// the block key must have length 16, 24 or 32 bytes corresponding to
// AES-128, AES-192 or AES-256. If the block key violates these constraints,
// SetKeys will panic.
//
// This method is exposed so that multiple instantiations of session stores
// can share the same cookie. This particularly useful if you want to be able
// to restart your web server without invalidating existing user sessions.
//
// If this method is not called, then a fresh set of keys is created
// automatically, but will invalidate all existing user sessions.
func (s *Store) SetKeys(hash, block []byte) {
	validLen := len(block) == 16 || len(block) == 24 || len(block) == 32
	if block != nil && !validLen {
		panic("invalid block key")
	}
	s.hashKey, s.blockKey = hash, block
}

func (s *Store) cookrw() *securecookie.SecureCookie {
	return securecookie.New(s.hashKey, s.blockKey)
}

// Returns an empty string if the cookie doesn't exist or if there was
// a problem decoding it.
func (s *Store) readCookie(r *http.Request, cname string) string {
	if cook, err := r.Cookie(cname); err == nil {
		var v string
		if err = s.cookrw().Decode(cname, cook.Value, &v); err == nil {
			return v
		}
	}
	return ""
}

// Writes the value to the named cookie with encryption.
func (s *Store) writeCookie(
	r *http.Request,
	w http.ResponseWriter,
	cname, cvalue string,
) {
	if encoded, err := s.cookrw().Encode(cname, cvalue); err == nil {
		cook := &http.Cookie{
			Name:     cname,
			Value:    encoded,
			Path:     "/",
			HttpOnly: true,
		}
		http.SetCookie(w, cook)
	}
}

func Lock(sess *sessions.Session)    { locker.Lock(sess.ID) }
func Unlock(sess *sessions.Session)  { locker.Unlock(sess.ID) }
func RLock(sess *sessions.Session)   { locker.RLock(sess.ID) }
func RUnlock(sess *sessions.Session) { locker.RUnlock(sess.ID) }
<commit_msg>Support session removal.<commit_after>package sqlsess

import (
	"database/sql"
	"net/http"
	"time"

	"github.com/gorilla/securecookie"
	"github.com/gorilla/sessions"

	"github.com/BurntSushi/locker"
)

var (
	SessionLastUpdated = "__sess_last_updated"
	CookieIdName       = "sess_sessionid"
	SqlTableName       = "sess_session"
	SqlCreateSession   = `
	CREATE TABLE ` + SqlTableName + ` IF NOT EXISTS (
		id BYTEA NOT NULL,
		name VARCHAR (255) NOT NULL,
		key TEXT NOT NULL,
		value TEXT NOT NULL,
		PRIMARY KEY (id, name, key)
	)
	`
)

type Store struct {
	*sql.DB
	hashKey, blockKey []byte
}

func Open(db *sql.DB) (*Store, error) {
	if _, err := db.Exec(SqlCreateSession); err != nil {
		return nil, err
	}

	s := &Store{
		DB:       db,
		hashKey:  securecookie.GenerateRandomKey(64),
		blockKey: securecookie.GenerateRandomKey(32),
	}
	return s, nil
}

func (s *Store) Clean(inactive time.Duration) error {
	locker.Lock("clean")
	defer locker.Unlock("clean")

	rows, err := s.Query(`
		SELECT id, value
		FROM ` + SqlTableName + `
		WHERE key = $1
	`, SessionLastUpdated)
	if err != nil {
		return err
	}
	defer rows.Close()

	cutoff := time.Now().UTC().Add(-inactive)
	for rows.Next() {
		var id []byte
		var last string
		if err := rows.Scan(&id, &last); err != nil {
			return err
		}
		lastUp, err := time.Parse(time.RFC3339Nano, last)
		if err != nil {
			return err
		}
		if lastUp.Before(cutoff) {
			_, err = s.Exec(
				"DELETE FROM " + SqlTableName + " WHERE id = $1", id)
			if err != nil {
				return err
			}
		}
	}
	return rows.Err()
}

func (s *Store) Delete(sess *sessions.Session) error {
	id := []byte(sess.ID)
	_, err := s.Exec("DELETE FROM " + SqlTableName + " WHERE id = $1", id)
	return err
}

func (s *Store) Get(r *http.Request, name string) (*sessions.Session, error) {
	return sessions.GetRegistry(r).Get(s, name)
}

func (s *Store) New(r *http.Request, name string) (*sessions.Session, error) {
	sess := sessions.NewSession(s, name)
	sess.ID = s.id(r)

	RLock(sess)
	defer RUnlock(sess)

	rows, err := s.Query(`
		SELECT key, value
		FROM ` + SqlTableName + `
		WHERE id = $1 AND name = $2
	`, []byte(sess.ID), name)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	for rows.Next() {
		var k, v string
		if err := rows.Scan(&k, &v); err != nil {
			return nil, err
		}
		sess.Values[k] = v
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return sess, nil
}

func (s *Store) Save(
	r *http.Request,
	w http.ResponseWriter,
	sess *sessions.Session,
) error {
	Lock(sess)
	defer Unlock(sess)

	s.writeCookie(r, w, CookieIdName, sess.ID)

	id := []byte(sess.ID)
	sess.Values[SessionLastUpdated] = time.Now().UTC()
	tx, err := s.Begin()
	if err != nil {
		return err
	}

	_, err = tx.Exec("DELETE FROM " + SqlTableName + " WHERE id = $1", id)
	if err != nil {
		tx.Rollback()
		return err
	}

	prep, err := tx.Prepare(`
		INSERT INTO ` + SqlTableName + `
			(id, name, key, value) VALUES ($1, $2, $3, $4)
	`)
	if err != nil {
		tx.Rollback()
		return err
	}
	defer prep.Close()

	for k, v := range sess.Values {
		if _, err := prep.Exec(id, sess.Name(), k, v); err != nil {
			tx.Rollback()
			return err
		}
	}
	return tx.Commit()
}

// id returns either the session id from a user's cookie or generates
// a fresh one if the cookie is inaccessible or missing.
func (s *Store) id(r *http.Request) string {
	id := s.readCookie(r, CookieIdName)
	if len(id) == 0 {
		id = string(securecookie.GenerateRandomKey(64))
	}
	return id
}

// SetKeys sets the hash and block keys used to read and write the session
// cookie. A hash key is required and is used to to authenticate a cookie
// value using HMAC. It's recommend to be 32 or 64 bytes.
//
// A block key is optional and is used to encrypt the cookie value. If it's
// set to nil, then encryption will not be used. This package uses AES, so
// the block key must have length 16, 24 or 32 bytes corresponding to
// AES-128, AES-192 or AES-256. If the block key violates these constraints,
// SetKeys will panic.
//
// This method is exposed so that multiple instantiations of session stores
// can share the same cookie. This particularly useful if you want to be able
// to restart your web server without invalidating existing user sessions.
//
// If this method is not called, then a fresh set of keys is created
// automatically, but will invalidate all existing user sessions.
func (s *Store) SetKeys(hash, block []byte) {
	validLen := len(block) == 16 || len(block) == 24 || len(block) == 32
	if block != nil && !validLen {
		panic("invalid block key")
	}
	s.hashKey, s.blockKey = hash, block
}

func (s *Store) cookrw() *securecookie.SecureCookie {
	return securecookie.New(s.hashKey, s.blockKey)
}

// Returns an empty string if the cookie doesn't exist or if there was
// a problem decoding it.
func (s *Store) readCookie(r *http.Request, cname string) string {
	if cook, err := r.Cookie(cname); err == nil {
		var v string
		if err = s.cookrw().Decode(cname, cook.Value, &v); err == nil {
			return v
		}
	}
	return ""
}

// Writes the value to the named cookie with encryption.
func (s *Store) writeCookie(
	r *http.Request,
	w http.ResponseWriter,
	cname, cvalue string,
) {
	if encoded, err := s.cookrw().Encode(cname, cvalue); err == nil {
		cook := &http.Cookie{
			Name:     cname,
			Value:    encoded,
			Path:     "/",
			HttpOnly: true,
		}
		http.SetCookie(w, cook)
	}
}

func Lock(sess *sessions.Session)    { locker.Lock(sess.ID) }
func Unlock(sess *sessions.Session)  { locker.Unlock(sess.ID) }
func RLock(sess *sessions.Session)   { locker.RLock(sess.ID) }
func RUnlock(sess *sessions.Session) { locker.RUnlock(sess.ID) }
<|endoftext|>


<commit_before>// Package aws provides core functionality for making requests to AWS services.
package aws

// SDKName is the name of this AWS SDK
const SDKName = "aws-sdk-go"

// SDKVersion is the version of this SDK
const SDKVersion = "0.6.1"
<commit_msg>Tag release v0.6.2<commit_after>// Package aws provides core functionality for making requests to AWS services.
package aws

// SDKName is the name of this AWS SDK
const SDKName = "aws-sdk-go"

// SDKVersion is the version of this SDK
const SDKVersion = "0.6.2"
<|endoftext|>


<commit_before>//File contains all the db related functions of couch
package couchdb

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"

	log "github.com/Sirupsen/logrus"
	"github.com/parnurzeal/gorequest"
)

// Database defintes a database client
// It can be used to get Documents and Views
type Database struct {
	Name    string
	BaseURL string
	Client  *Client
	Req     Request
}

// NewDB creates an instance of a Database
func NewDB(name string, c *Client) Database {
	protocol := "http"
	if c.Secure {
		protocol += "s"
	}

	port := ""
	if c.Port == 0 {
		port = "80"
	} else {
		port = strconv.Itoa(c.Port)
	}

	url := protocol + "://" + c.Host + ":" + port + "/" + name
	req := Request{gorequest.New(), url}
	if c.Timeout != 0 {
		req.Req.Timeout(c.GetTimeoutDuration())
	}
	if c.Username != "" && c.Password != "" {
		req.Req.SetBasicAuth(c.Username, c.Password)
	}
	return Database{
		Name:    name,
		Client:  c,
		BaseURL: url,
		Req:     req,
	}
}

// Exists check to see if database exists
func (db *Database) Exists() (bool, error) {
	type response struct {
		Error  string `json:"error"`
		DBName string `json:"db_name"`
	}
	_, body, _ := db.Req.Get("").End()
	result := response{}
	pErr := json.Unmarshal([]byte(body), &result)
	if pErr != nil {
		return false, pErr
	}
	if result.DBName != db.Name || result.Error != "" {
		return false, nil
	}

	return true, nil
}

// Create creates a new database
func (db *Database) Create() error {
	type response struct {
		Error  string `json:"error"`
		Reason string `json:"reason"`
		Ok     bool   `json:"ok"`
	}
	_, body, _ := db.Req.Put("").End()
	result := response{}
	pErr := json.Unmarshal([]byte(body), &result)
	log.Info("couch : Create : Result, JsonResp", result, body)
	if pErr != nil {
		return pErr
	}
	if result.Error != "" {
		return errors.New(result.Error + " " + result.Reason)
	}
	if !result.Ok {
		return errors.New("Couch returned failure when creating [" + db.Name + "]")
	}

	return nil
}

func (db *Database) GetView(docName string, viewName string, key string) ([]byte, error) {

	log.Print("couch : GetView key " + key + " in viewName " + viewName + " of desDoc " + docName)
	type ViewResponse struct {
		Error  string `json:"error"`
		Reason string `json:"reason"`
	}

	var body string
	var errs []error
	if key == "" {
		prefix := docName + "/_view/" + viewName
		log.Info("Getting view name " + prefix)
		_, body, errs = db.Req.Get(prefix).End()
	} else {
		key = "\"" + key + "\""
		prefix := docName + "/_view/" + viewName
		_, body, errs = db.Req.Get(prefix).Query("key=" + key).End()
	}

	if len(errs) > 0 {
		return nil, errors.New("Database : " + fmt.Sprint("%v", errs))
	}
	viewResp := &ViewResponse{}
	err := json.Unmarshal([]byte(body), viewResp)

	if err != nil {
		log.Error(body)
		return nil, err
	}

	if viewResp.Error != "" {
		err = errors.New(viewResp.Error + " " + viewResp.Reason)
		return nil, err
	}

	return []byte(body), nil
}

// Delete deletes database
func (db *Database) Delete() error {
	type response struct {
		Error string `json:"error"`
		Ok    bool   `json:"ok"`
	}
	_, body, _ := db.Req.Delete("").End()
	result := response{}
	pErr := json.Unmarshal([]byte(body), &result)
	log.Info(result)
	if pErr != nil {
		return pErr
	}
	if result.Error != "" {
		return errors.New(result.Error)
	}
	if !result.Ok {
		return errors.New("Couch returned failure when creating [" + db.Name + "]")
	}

	return nil
}
<commit_msg>Adding some format related review comments<commit_after>//File contains all the db related functions of couch
package couchdb

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"

	log "github.com/Sirupsen/logrus"
	"github.com/parnurzeal/gorequest"
)

// Database defintes a database client
// It can be used to get Documents and Views
type Database struct {
	Name    string
	BaseURL string
	Client  *Client
	Req     Request
}

// NewDB creates an instance of a Database
func NewDB(name string, c *Client) Database {
	protocol := "http"
	if c.Secure {
		protocol += "s"
	}

	port := ""
	if c.Port == 0 {
		port = "80"
	} else {
		port = strconv.Itoa(c.Port)
	}

	url := protocol + "://" + c.Host + ":" + port + "/" + name
	req := Request{gorequest.New(), url}
	if c.Timeout != 0 {
		req.Req.Timeout(c.GetTimeoutDuration())
	}
	if c.Username != "" && c.Password != "" {
		req.Req.SetBasicAuth(c.Username, c.Password)
	}
	return Database{
		Name:    name,
		Client:  c,
		BaseURL: url,
		Req:     req,
	}
}

// Exists check to see if database exists
func (db *Database) Exists() (bool, error) {
	type response struct {
		Error  string `json:"error"`
		DBName string `json:"db_name"`
	}
	_, body, _ := db.Req.Get("").End()
	result := response{}
	pErr := json.Unmarshal([]byte(body), &result)
	if pErr != nil {
		return false, pErr
	}
	if result.DBName != db.Name || result.Error != "" {
		return false, nil
	}

	return true, nil
}

// Create creates a new database
func (db *Database) Create() error {
	type response struct {
		Error  string `json:"error"`
		Reason string `json:"reason"`
		Ok     bool   `json:"ok"`
	}
	_, body, _ := db.Req.Put("").End()
	result := response{}
	pErr := json.Unmarshal([]byte(body), &result)
	log.Info("couch : Create : Result, JsonResp", result, body)
	if pErr != nil {
		return pErr
	}
	if result.Error != "" {
		return errors.New(result.Error + " " + result.Reason)
	}
	if !result.Ok {
		return errors.New("Couch returned failure when creating [" + db.Name + "]")
	}

	return nil
}

func (db *Database) GetView(docName string, viewName string, key string) ([]byte, error) {

	log.Printf("couch : GetView key %s in viewName %s of desDoc %s", key, viewName, docName)
	type ViewResponse struct {
		Error  string `json:"error"`
		Reason string `json:"reason"`
	}

	var body string
	var errs []error
	if key == "" {
		prefix := docName + "/_view/" + viewName
		log.Info("Getting view name " + prefix)
		_, body, errs = db.Req.Get(prefix).End()
	} else {
		key = `"` + key + `"`
		prefix := docName + "/_view/" + viewName
		_, body, errs = db.Req.Get(prefix).Query("key=" + key).End()
	}

	if len(errs) > 0 {
		return nil, errors.New("Database : Error making request " + fmt.Sprint("%v", errs))
	}
	viewResp := &ViewResponse{}
	err := json.Unmarshal([]byte(body), viewResp)

	if err != nil {
		log.Error(body)
		return nil, err
	}

	if viewResp.Error != "" {
		err = errors.New(viewResp.Error + " " + viewResp.Reason)
		return nil, err
	}

	return []byte(body), nil
}

// Delete deletes database
func (db *Database) Delete() error {
	type response struct {
		Error string `json:"error"`
		Ok    bool   `json:"ok"`
	}
	_, body, _ := db.Req.Delete("").End()
	result := response{}
	pErr := json.Unmarshal([]byte(body), &result)
	log.Info(result)
	if pErr != nil {
		return pErr
	}
	if result.Error != "" {
		return errors.New(result.Error)
	}
	if !result.Ok {
		return errors.New("Couch returned failure when creating [" + db.Name + "]")
	}

	return nil
}
<|endoftext|>