<commit_before>/* Copyright 2017 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package service

import (
	"fmt"

	log "github.com/golang/glog"
)

// configEthIntf configures the network interfaces on this device based on the given configuration.
func configEthIntf(ethIntfName string, vlanIDs []int) error {
	log.Infof("Configuring interface %v. VLAN: %v.", ethIntfName, vlanIDs)
	vlanIntfNames := make(map[int]string) // VLAN ID -> VLAN Intf name
	for _, vlanID := range vlanIDs {
		// Add VLAN interface
		vlanIntfName, err := cmdRunner.CreateVLAN(ethIntfName, vlanID)
		if err != nil {
			return err
		}
		vlanIntfNames[vlanID] = vlanIntfName
	}

	// Restart eth interface.
	if err := cmdRunner.RestartIntf(ethIntfName); err != nil {
		return err
	}

	for vlanID, vlanIntfName := range vlanIntfNames {
		// Wipe out IP on VLAN interface.
		if err := cmdRunner.WipeOutIntfIP(vlanIntfName); err != nil {
			return err
		}

		// Add a network bridge
		bridgeName := getBridgeName(vlanID)
		if err := cmdRunner.CreateBridge(bridgeName); err != nil {
			return err
		}

		// Link VLAN intf to bridge
		if err := cmdRunner.AddBridgeIntf(bridgeName, vlanIntfName); err != nil {
			return err
		}
	}

	log.Infof("Configured interface %v.", ethIntfName)
	return nil
}

// cleanupEthIntf cleans up the network interfaces on this device based on the given configuration.
// It goes through all cleanup steps even if some failures are detected, and returns all errors.
func cleanupEthIntf(ethIntfName string, vlanIDs []int) []error {
	log.Infof("Cleaning up interface %s. VLAN: %d.", ethIntfName, vlanIDs)
	var errs []error

	for _, vlanID := range vlanIDs {
		// Delete VLAN interface.
		if err := cmdRunner.DeleteVLAN(ethIntfName, vlanID); err != nil {
			errs = append(errs, err)
		}

		// Turn down network bridge.
		bridgeName := getBridgeName(vlanID)
		if err := cmdRunner.TurnDownIntf(bridgeName); err != nil {
			errs = append(errs, err)
		}

		// Remove network bridge.
		if err := cmdRunner.DeleteBridge(bridgeName); err != nil {
			errs = append(errs, err)
		}
	}

	log.Infof("Cleaned up interface %s. Number of errors = %d.", ethIntfName, len(errs))
	return errs
}

// configWLANIntf configures the network interfaces to make it work with hostapd.
func configWLANIntf(wlanIntfName string) error {
	log.Infof("Configuring WLAN interface %v.", wlanIntfName)

	// Wipe out IP on WLAN interface.
	if err := cmdRunner.WipeOutIntfIP(wlanIntfName); err != nil {
		return err
	}

	if err := cmdRunner.TurnDownIntf(wlanIntfName); err != nil {
		return err
	}

	wlanIntfMAC, err := cmdRunner.IntfMAC(wlanIntfName)
	if err != nil {
		return err
	}

	// Change the MAC address of the wireless interface to avoid conflict with other devices.
	updatedMAC := generateWLANIntfMAC(wlanIntfMAC)
	if err = cmdRunner.UpdateIntfMAC(wlanIntfName, updatedMAC); err != nil {
		return err
	}

	if err = cmdRunner.BringUpIntf(wlanIntfName); err != nil {
		return err
	}

	log.Infof("Configured WLAN interface %v.", wlanIntfName)
	return nil
}

func getBridgeName(vlanID int) string {
	return fmt.Sprintf("br_%d", vlanID)
}

func generateWLANIntfMAC(originalMAC string) string {
	return fmt.Sprintf("02%s0", originalMAC[2:len(originalMAC)-1])
}
<commit_msg>Bring up the bridge after created.<commit_after>/* Copyright 2017 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package service

import (
	"fmt"

	log "github.com/golang/glog"
)

// configEthIntf configures the network interfaces on this device based on the given configuration.
func configEthIntf(ethIntfName string, vlanIDs []int) error {
	log.Infof("Configuring interface %v. VLAN: %v.", ethIntfName, vlanIDs)
	vlanIntfNames := make(map[int]string) // VLAN ID -> VLAN Intf name
	for _, vlanID := range vlanIDs {
		// Add VLAN interface
		vlanIntfName, err := cmdRunner.CreateVLAN(ethIntfName, vlanID)
		if err != nil {
			return err
		}
		vlanIntfNames[vlanID] = vlanIntfName
	}

	// Restart eth interface.
	if err := cmdRunner.RestartIntf(ethIntfName); err != nil {
		return err
	}

	for vlanID, vlanIntfName := range vlanIntfNames {
		// Wipe out IP on VLAN interface.
		if err := cmdRunner.WipeOutIntfIP(vlanIntfName); err != nil {
			return err
		}

		// Add a network bridge
		bridgeName := getBridgeName(vlanID)
		if err := cmdRunner.CreateBridge(bridgeName); err != nil {
			return err
		}

		// Link VLAN intf to bridge
		if err := cmdRunner.AddBridgeIntf(bridgeName, vlanIntfName); err != nil {
			return err
		}

		// Bring up bridge
		if err := cmdRunner.BringUpIntf(bridgeName); err != nil {
			return err
		}
	}

	log.Infof("Configured interface %v.", ethIntfName)
	return nil
}

// cleanupEthIntf cleans up the network interfaces on this device based on the given configuration.
// It goes through all cleanup steps even if some failures are detected, and returns all errors.
func cleanupEthIntf(ethIntfName string, vlanIDs []int) []error {
	log.Infof("Cleaning up interface %s. VLAN: %d.", ethIntfName, vlanIDs)
	var errs []error

	for _, vlanID := range vlanIDs {
		// Delete VLAN interface.
		if err := cmdRunner.DeleteVLAN(ethIntfName, vlanID); err != nil {
			errs = append(errs, err)
		}

		// Turn down network bridge.
		bridgeName := getBridgeName(vlanID)
		if err := cmdRunner.TurnDownIntf(bridgeName); err != nil {
			errs = append(errs, err)
		}

		// Remove network bridge.
		if err := cmdRunner.DeleteBridge(bridgeName); err != nil {
			errs = append(errs, err)
		}
	}

	log.Infof("Cleaned up interface %s. Number of errors = %d.", ethIntfName, len(errs))
	return errs
}

// configWLANIntf configures the network interfaces to make it work with hostapd.
func configWLANIntf(wlanIntfName string) error {
	log.Infof("Configuring WLAN interface %v.", wlanIntfName)

	// Wipe out IP on WLAN interface.
	if err := cmdRunner.WipeOutIntfIP(wlanIntfName); err != nil {
		return err
	}

	if err := cmdRunner.TurnDownIntf(wlanIntfName); err != nil {
		return err
	}

	wlanIntfMAC, err := cmdRunner.IntfMAC(wlanIntfName)
	if err != nil {
		return err
	}

	// Change the MAC address of the wireless interface to avoid conflict with other devices.
	updatedMAC := generateWLANIntfMAC(wlanIntfMAC)
	if err = cmdRunner.UpdateIntfMAC(wlanIntfName, updatedMAC); err != nil {
		return err
	}

	if err = cmdRunner.BringUpIntf(wlanIntfName); err != nil {
		return err
	}

	log.Infof("Configured WLAN interface %v.", wlanIntfName)
	return nil
}

func getBridgeName(vlanID int) string {
	return fmt.Sprintf("br_%d", vlanID)
}

func generateWLANIntfMAC(originalMAC string) string {
	return fmt.Sprintf("02%s0", originalMAC[2:len(originalMAC)-1])
}
<|endoftext|>


<commit_before>package i18n

import (
	"gnd.la/i18n/table"
)

// Tabler is the interface implemented by types
// which instead of returning a language code, can
// store and return a translation table, resulting in
// better performance. All functions in the i18n package
// which accept a Languager will check if the received
// object implements Tabler.
type Tabler interface {
	TranslationTable() *table.Table
}

func getTable(lang Languager) *table.Table {
	if tabler, ok := lang.(Tabler); ok {
		return tabler.TranslationTable()
	}
	return table.Get(lang.Language())
}
<commit_msg>Avoid dereferencing nil pointer<commit_after>package i18n

import (
	"gnd.la/i18n/table"
)

// Tabler is the interface implemented by types
// which instead of returning a language code, can
// store and return a translation table, resulting in
// better performance. All functions in the i18n package
// which accept a Languager will check if the received
// object implements Tabler.
type Tabler interface {
	TranslationTable() *table.Table
}

func getTable(lang Languager) *table.Table {
	if lang == nil {
		return nil
	}
	if tabler, ok := lang.(Tabler); ok {
		return tabler.TranslationTable()
	}
	return table.Get(lang.Language())
}
<|endoftext|>


<commit_before>package mockery

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

type Cleanup func() error

type OutputStreamProvider interface {
	GetWriter(iface *Interface, pkg string) (io.Writer, error, Cleanup)
}

type StdoutStreamProvider struct {
}

func (this *StdoutStreamProvider) GetWriter(iface *Interface, pkg string) (io.Writer, error, Cleanup) {
	return os.Stdout, nil, func() error { return nil }
}

type FileOutputStreamProvider struct {
	BaseDir   string
	InPackage bool
	TestOnly  bool
	Case      string
}

func (this *FileOutputStreamProvider) GetWriter(iface *Interface, pkg string) (io.Writer, error, Cleanup) {
	var path string

	caseName := iface.Name
	if this.Case == "underscore" {
		caseName = this.underscoreCaseName(caseName)
	}

	if this.InPackage {
		path = filepath.Join(filepath.Dir(iface.Path), this.filename(caseName))
	} else {
		path = filepath.Join(this.BaseDir, this.filename(caseName))
		os.MkdirAll(filepath.Dir(path), 0755)
		pkg = filepath.Base(filepath.Dir(path))
	}

	f, err := os.Create(path)
	if err != nil {
		return nil, err, func() error { return nil }
	}

	fmt.Printf("Generating mock for: %s\n", iface.Name)
	return f, nil, func() error {
		return f.Close()
	}
}

func (this *FileOutputStreamProvider) filename(name string) string {
	if this.InPackage && this.TestOnly {
		return "mock_" + name + "_test.go"
	} else if this.InPackage {
		return "mock_" + name + ".go"
	} else if this.TestOnly {
		return name + "_test.go"
	}
	return name + ".go"
}

// shamelessly taken from http://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-camel-caseo
func (this *FileOutputStreamProvider) underscoreCaseName(caseName string) string {
	rxp1 := regexp.MustCompile("(.)([A-Z][a-z]+)")
	s1 := rxp1.ReplaceAllString(caseName, "${1}_${2}")
	rxp2 := regexp.MustCompile("([a-z0-9])([A-Z])")
	return strings.ToLower(rxp2.ReplaceAllString(s1, "${1}_${2}"))
}
<commit_msg>Use underscore outputter for "snake" case<commit_after>package mockery

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

type Cleanup func() error

type OutputStreamProvider interface {
	GetWriter(iface *Interface, pkg string) (io.Writer, error, Cleanup)
}

type StdoutStreamProvider struct {
}

func (this *StdoutStreamProvider) GetWriter(iface *Interface, pkg string) (io.Writer, error, Cleanup) {
	return os.Stdout, nil, func() error { return nil }
}

type FileOutputStreamProvider struct {
	BaseDir   string
	InPackage bool
	TestOnly  bool
	Case      string
}

func (this *FileOutputStreamProvider) GetWriter(iface *Interface, pkg string) (io.Writer, error, Cleanup) {
	var path string

	caseName := iface.Name
	if this.Case == "underscore" || this.Case == "snake" {
		caseName = this.underscoreCaseName(caseName)
	}

	if this.InPackage {
		path = filepath.Join(filepath.Dir(iface.Path), this.filename(caseName))
	} else {
		path = filepath.Join(this.BaseDir, this.filename(caseName))
		os.MkdirAll(filepath.Dir(path), 0755)
		pkg = filepath.Base(filepath.Dir(path))
	}

	f, err := os.Create(path)
	if err != nil {
		return nil, err, func() error { return nil }
	}

	fmt.Printf("Generating mock for: %s\n", iface.Name)
	return f, nil, func() error {
		return f.Close()
	}
}

func (this *FileOutputStreamProvider) filename(name string) string {
	if this.InPackage && this.TestOnly {
		return "mock_" + name + "_test.go"
	} else if this.InPackage {
		return "mock_" + name + ".go"
	} else if this.TestOnly {
		return name + "_test.go"
	}
	return name + ".go"
}

// shamelessly taken from http://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-camel-caseo
func (this *FileOutputStreamProvider) underscoreCaseName(caseName string) string {
	rxp1 := regexp.MustCompile("(.)([A-Z][a-z]+)")
	s1 := rxp1.ReplaceAllString(caseName, "${1}_${2}")
	rxp2 := regexp.MustCompile("([a-z0-9])([A-Z])")
	return strings.ToLower(rxp2.ReplaceAllString(s1, "${1}_${2}"))
}
<|endoftext|>


<commit_before>package ndp

import (
"fmt"
"net"
"encoding/binary"
)

type ICMPOptionType int

const (
    ICMPOptionTypeSourceLinkLayerAddress ICMPOptionType = 1
    ICMPOptionTypeTargetLinkLayerAddress ICMPOptionType = 2
    ICMPOptionTypePrefixInformation      ICMPOptionType = 3
    ICMPOptionTypeRedirectedHeader       ICMPOptionType = 4
    ICMPOptionTypeMTU                    ICMPOptionType = 5
    ICMPOptionTypeRecursiveDNSServer     ICMPOptionType = 25
    ICMPOptionTypeDNSSearchList          ICMPOptionType = 31
)

var icmpOptionTypes = map[ICMPOptionType]string {
    // https://tools.ietf.org/html/rfc4861#section-4.6
    1:  "Source Link Layer Address",
    2:  "Target Link Layer Address",
    3:  "Prefix Information",
    4:  "Redirected Header",
    5:  "MTU",
    // https://tools.ietf.org/html/rfc6106#section-5
    25: "Recursive DNS Server",
    31: "DNS Search List",
}

// https://tools.ietf.org/html/rfc4861#section-4.6
type ICMPOption struct {
    Type   ICMPOptionType
    Length uint8
}

// https://tools.ietf.org/html/rfc4861#section-4.6.1
type ICMPOptionSourceLinkLayerAddress struct {
    ICMPOption
    LinkLayerAddress net.HardwareAddr
}

type ICMPOptionTargetLinkLayerAddress struct {
    ICMPOption
    LinkLayerAddress net.HardwareAddr
}

// https://tools.ietf.org/html/rfc4861#section-4.6.2
type ICMPOptionPrefixInformation struct {
    ICMPOption
    PrefixLength      uint8
    OnLink            bool
    Auto              bool
    ValidLifetime     uint32
    PreferredLifetime uint32
    Prefix            net.IP
}

func (o *ICMPOption) Marshal(proto int) ([]byte, error) {
    b := make([]byte, 8)
    b[0] ^= byte(o.Type)
    b[1] ^= byte(o.Length)
    return b, nil
}

func ParseOptions(b []byte) ([]ICMPOption, error) {
    // empty container
    var icmpOptions = []ICMPOption {}

    for {
        if len(b) < 8 {
            fmt.Printf("options has only %d length left, done parsing\n", len(b))
            break
        }

        optionType := ICMPOptionType(b[0])
        optionLength := uint8(b[1])

        switch optionType {
            case ICMPOptionTypeSourceLinkLayerAddress:
                if optionLength != 1 {
                    fmt.Printf("incorrect length of %d\n", optionLength)
                    goto cleanup
                }

                currentOption := &ICMPOptionSourceLinkLayerAddress{
                    ICMPOption:       ICMPOption{
                        Type:   optionType,
                        Length: optionLength,
                    },
                    LinkLayerAddress: b[2:8],
                }

                fmt.Printf("source address: %s\n", currentOption.LinkLayerAddress.String())

            case ICMPOptionTypeTargetLinkLayerAddress:
                if optionLength != 1 {
                    fmt.Printf("incorrect length of %d\n", optionLength)
                    goto cleanup
                }

                currentOption := &ICMPOptionTargetLinkLayerAddress{
                    ICMPOption:       ICMPOption{
                        Type:   optionType,
                        Length: optionLength,
                    },
                    LinkLayerAddress: b[2:8],
                }

                fmt.Printf("target address: %s\n", currentOption.LinkLayerAddress.String())

            case ICMPOptionTypePrefixInformation:
                if optionLength != 4 {
                    fmt.Printf("incorrect length of %d\n", optionLength)
                    goto cleanup
                }

                currentOption := &ICMPOptionPrefixInformation{
                    ICMPOption:        ICMPOption{
                        Type:   optionType,
                        Length: optionLength,
                    },
                    PrefixLength:      uint8(b[2]),
                    OnLink:            (b[3] & 0x80 > 0),
                    Auto:              (b[3] & 0x40 > 0),
                    ValidLifetime:     binary.BigEndian.Uint32(b[4:8]),
                    PreferredLifetime: binary.BigEndian.Uint32(b[8:12]),
                    Prefix:            net.IP(b[16:32]),
                }

                fmt.Printf("prefix: %s/%d, onlink: %t, auto: %t, valid: %d, preferred: %d\n", currentOption.Prefix.String(), currentOption.PrefixLength, currentOption.OnLink, currentOption.Auto, currentOption.ValidLifetime, currentOption.PreferredLifetime)

            default:
                fmt.Printf("unhandled icmp option %d\n", optionType)
        }

cleanup:
        // chop off bytes for this option
        b = b[(optionLength * 8):]
    }

    return icmpOptions, nil
}
<commit_msg>added parsing of RFC6106 option 25<commit_after>package ndp

import (
"fmt"
"net"
"encoding/binary"
)

type ICMPOptionType int

const (
    ICMPOptionTypeSourceLinkLayerAddress ICMPOptionType = 1
    ICMPOptionTypeTargetLinkLayerAddress ICMPOptionType = 2
    ICMPOptionTypePrefixInformation      ICMPOptionType = 3
    ICMPOptionTypeRedirectedHeader       ICMPOptionType = 4
    ICMPOptionTypeMTU                    ICMPOptionType = 5
    ICMPOptionTypeRecursiveDNSServer     ICMPOptionType = 25
    ICMPOptionTypeDNSSearchList          ICMPOptionType = 31
)

var icmpOptionTypes = map[ICMPOptionType]string {
    // https://tools.ietf.org/html/rfc4861#section-4.6
    1:  "Source Link Layer Address",
    2:  "Target Link Layer Address",
    3:  "Prefix Information",
    4:  "Redirected Header",
    5:  "MTU",
    // https://tools.ietf.org/html/rfc6106#section-5
    25: "Recursive DNS Server",
    31: "DNS Search List",
}

// https://tools.ietf.org/html/rfc4861#section-4.6
type ICMPOption struct {
    Type   ICMPOptionType
    Length uint8
}

// https://tools.ietf.org/html/rfc4861#section-4.6.1
type ICMPOptionSourceLinkLayerAddress struct {
    ICMPOption
    LinkLayerAddress net.HardwareAddr
}

type ICMPOptionTargetLinkLayerAddress struct {
    ICMPOption
    LinkLayerAddress net.HardwareAddr
}

// https://tools.ietf.org/html/rfc4861#section-4.6.2
type ICMPOptionPrefixInformation struct {
    ICMPOption
    PrefixLength      uint8
    OnLink            bool
    Auto              bool
    ValidLifetime     uint32
    PreferredLifetime uint32
    Prefix            net.IP
}

// https://tools.ietf.org/html/rfc6106#section-5
type ICMPOptionRecursiveDNSServer struct {
    ICMPOption
    Lifetime uint32
    Servers  []net.IP
}

func (o *ICMPOption) Marshal(proto int) ([]byte, error) {
    b := make([]byte, 8)
    b[0] ^= byte(o.Type)
    b[1] ^= byte(o.Length)
    return b, nil
}

func ParseOptions(b []byte) ([]ICMPOption, error) {
    // empty container
    var icmpOptions = []ICMPOption {}

    for {
        if len(b) < 8 {
            fmt.Printf("options has only %d length left, done parsing\n", len(b))
            break
        }

        optionType := ICMPOptionType(b[0])
        optionLength := uint8(b[1])

        switch optionType {
            case ICMPOptionTypeSourceLinkLayerAddress:
                if optionLength != 1 {
                    fmt.Printf("incorrect length of %d\n", optionLength)
                    goto cleanup
                }

                currentOption := &ICMPOptionSourceLinkLayerAddress{
                    ICMPOption:       ICMPOption{
                        Type:   optionType,
                        Length: optionLength,
                    },
                    LinkLayerAddress: b[2:8],
                }

                fmt.Printf("source address: %s\n", currentOption.LinkLayerAddress.String())

            case ICMPOptionTypeTargetLinkLayerAddress:
                if optionLength != 1 {
                    fmt.Printf("incorrect length of %d\n", optionLength)
                    goto cleanup
                }

                currentOption := &ICMPOptionTargetLinkLayerAddress{
                    ICMPOption:       ICMPOption{
                        Type:   optionType,
                        Length: optionLength,
                    },
                    LinkLayerAddress: b[2:8],
                }

                fmt.Printf("target address: %s\n", currentOption.LinkLayerAddress.String())

            case ICMPOptionTypePrefixInformation:
                if optionLength != 4 {
                    fmt.Printf("incorrect length of %d\n", optionLength)
                    goto cleanup
                }

                currentOption := &ICMPOptionPrefixInformation{
                    ICMPOption:        ICMPOption{
                        Type:   optionType,
                        Length: optionLength,
                    },
                    PrefixLength:      uint8(b[2]),
                    OnLink:            (b[3] & 0x80 > 0),
                    Auto:              (b[3] & 0x40 > 0),
                    ValidLifetime:     binary.BigEndian.Uint32(b[4:8]),
                    PreferredLifetime: binary.BigEndian.Uint32(b[8:12]),
                    Prefix:            net.IP(b[16:32]),
                }

                fmt.Printf("prefix: %s/%d, onlink: %t, auto: %t, valid: %d, preferred: %d\n", currentOption.Prefix.String(), currentOption.PrefixLength, currentOption.OnLink, currentOption.Auto, currentOption.ValidLifetime, currentOption.PreferredLifetime)

            case ICMPOptionTypeRecursiveDNSServer:
                if optionLength < 3 {
                    fmt.Printf("incorrect length of %d for option 25\n", optionLength)
                    goto cleanup
                }

                currentOption := &ICMPOptionRecursiveDNSServer{
                    ICMPOption: ICMPOption{
                        Type:   optionType,
                        Length: optionLength,
                    },
                    Lifetime:   binary.BigEndian.Uint32(b[4:8]),
                }

                for i := 8; i < (int(optionLength) * 8); i += 16 {
                    currentOption.Servers = append(currentOption.Servers, net.IP(b[i:(i+16)]))
                }

                fmt.Printf("lifetime: %d, servers: %s\n", currentOption.Lifetime, currentOption.Servers)

            default:
                fmt.Printf("unhandled icmp option %d\n", optionType)
        }

cleanup:
        // chop off bytes for this option
        b = b[(optionLength * 8):]
    }

    return icmpOptions, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"regexp"

	"github.com/dustywilson/dir/directory"
	"github.com/dustywilson/dir/file"
)

func main() {
	root := directory.CreateDirectory(nil, "My Root Directory")

	directory.CreateDirectory(root, "Fruit").CreateDirectory("Green").CreateDirectory("Grapes").CreateDirectory("Sour")
	file.CreateFile(directory.CreateDirectory(root, "Meat").CreateDirectory("Cow"), "Ground Chuck")
	cake := directory.CreateDirectory(root, "Dessert").CreateDirectory("Cake")

	file.CreateFile(cake, "Angel Food")

	dirs, err := root.FindDirectories(regexp.MustCompile(`[Rr]`), -1)
	if err != nil {
		panic(err)
	}
	fmt.Printf("\nMatched %d directories.\n", len(dirs))
	for _, d := range dirs {
		fmt.Println(d)
	}

	files, err := root.FindFiles(regexp.MustCompile(`[Oo]`), -1)
	if err != nil {
		panic(err)
	}
	fmt.Printf("\nMatched %d files.\n", len(files))
	for _, f := range files {
		fmt.Println(f)
	}
}
<commit_msg>Updated ex1<commit_after>package main

import (
	"fmt"
	"regexp"

	"github.com/dustywilson/dir/directory"
	"github.com/dustywilson/dir/file"
)

func main() {
	root := directory.CreateDirectory(nil, "My Root Directory")

	directory.CreateDirectory(root, "Fruit").CreateDirectory("Green").CreateDirectory("Grapes").CreateDirectory("Sour")
	file.CreateFile(directory.CreateDirectory(root, "Meat").CreateDirectory("Cow"), "Ground Chuck")
	cake := directory.CreateDirectory(root, "Dessert").CreateDirectory("Cake")

	file.CreateFile(cake, "Angel Food")

	{
		dirs, err := root.FindDirectories(regexp.MustCompile(`[Rr]`), -1)
		if err != nil {
			panic(err)
		}
		fmt.Printf("\nMatched %d directories in 'root'.\n", len(dirs))
		for _, d := range dirs {
			fmt.Println(d)
		}
	}

	{
		files, err := root.FindFiles(regexp.MustCompile(`[Oo]`), -1)
		if err != nil {
			panic(err)
		}
		fmt.Printf("\nMatched %d files in 'root'.\n", len(files))
		for _, f := range files {
			fmt.Println(f)
		}
	}

	{
		files, err := cake.FindFiles(regexp.MustCompile(`[Oo]`), -1)
		if err != nil {
			panic(err)
		}
		fmt.Printf("\nMatched %d files in 'cake'.\n", len(files))
		for _, f := range files {
			fmt.Println(f)
		}
	}
}
<|endoftext|>


<commit_before>package algoliasearch

import (
	"bytes"
	_ "crypto/sha512" // Fix certificates
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math/rand"
	"net"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"
)

const (
	version = "2.0.0"
)

const (
	search = 1 << iota
	write
	read
)

// Transport defines low level functions to trade with Algolia servers.
type Transport struct {
	apiKey        string
	appID         string
	headers       map[string]string
	hosts         []string
	hostsProvided bool
	httpClient    *http.Client
}

// newHTTPClient creates and initializes an http.Client with sane defaults.
func newHTTPClient() *http.Client {
	return &http.Client{
		Timeout: time.Second * 30,
		Transport: &http.Transport{
			Dial: (&net.Dialer{
				Timeout:   2 * time.Second,
				KeepAlive: 180 * time.Second,
			}).Dial,
			DisableKeepAlives:   false,
			MaxIdleConnsPerHost: 2,
			TLSHandshakeTimeout: 2 * time.Second,
		},
	}
}

// NewTransport creates and initializes a new Transport targeting the Algolia
// application `appID` using the API key `apiKey`. The hosts are deduced from
// `appID`.
func NewTransport(appID, apiKey string) *Transport {
	hosts := []string{
		appID + "-1.algolianet.com",
		appID + "-2.algolianet.com",
		appID + "-3.algolianet.com",
	}

	// Randomize the hosts order
	randHosts := make([]string, len(hosts))
	for i, v := range rand.Perm(len(hosts)) {
		randHosts[i] = hosts[v]
	}

	return &Transport{
		apiKey:        apiKey,
		appID:         appID,
		headers:       make(map[string]string),
		hosts:         randHosts,
		hostsProvided: false,
		httpClient:    newHTTPClient(),
	}
}

// NewTransportWithHosts creates and initializes a new Transport targeting the
// Algolia application `appID` using the API key `apiKey` via the specified
// `hosts`.
func NewTransportWithHosts(appID, apiKey string, hosts []string) *Transport {
	return &Transport{
		apiKey:        apiKey,
		appID:         appID,
		headers:       make(map[string]string),
		hosts:         hosts,
		hostsProvided: true,
		httpClient:    newHTTPClient(),
	}
}

// setTimeout changes the timeouts used by the underlying HTTP client.
func (t *Transport) setTimeout(connectTimeout, readTimeout time.Duration) {
	switch transport := t.httpClient.Transport.(type) {
	case *http.Transport:
		transport.TLSHandshakeTimeout = connectTimeout
		transport.ResponseHeaderTimeout = readTimeout
	default:
		fmt.Fprintln(os.Stderr, "Timeouts not set for nonstandard underlying Transport")
	}
}

// setExtraHeader adds a custom header to be used when exchanging with Algolia
// servers.
func (t *Transport) setExtraHeader(key, value string) {
	t.headers[key] = value
}

// request performs a `method` HTTP request at `path` sending `body`.
// `typeCall` represents the operation intended on the Algolia servers and can
// be one of the following constants: `search`, `write` or `read`.
func (t *Transport) request(method, path string, body interface{}, typeCall int) ([]byte, error) {
	var host string
	errorMsg := ""
	if typeCall == write {
		host = t.appID + ".algolia.net"
	} else {
		host = t.appID + "-dsn.algolia.net"
	}

	if !t.hostsProvided {
		req, err := t.buildRequest(method, host, path, body)
		if err != nil {
			return nil, err
		}

		req = t.addHeaders(req)
		resp, err := t.httpClient.Do(req)

		if err != nil {
			if len(errorMsg) > 0 {
				errorMsg = fmt.Sprintf("%s, %s:%s", errorMsg, host, err)
			} else {
				errorMsg = fmt.Sprintf("%s:%s", host, err)
			}
		} else if (resp.StatusCode/100) == 2 || (resp.StatusCode/100) == 4 { // Bad request, not found, forbidden
			return t.handleResponse(resp)
		} else {
			io.Copy(ioutil.Discard, resp.Body)
			resp.Body.Close()
		}
	}

	for _, host := range t.hosts {
		req, err := t.buildRequest(method, host, path, body)
		if err != nil {
			return nil, err
		}

		req = t.addHeaders(req)
		resp, err := t.httpClient.Do(req)

		if err != nil {
			if len(errorMsg) > 0 {
				errorMsg = fmt.Sprintf("%s, %s:%s", errorMsg, host, err)
			} else {
				errorMsg = fmt.Sprintf("%s:%s", host, err)
			}
			continue
		}

		if (resp.StatusCode/100) == 2 || (resp.StatusCode/100) == 4 { // Bad request, not found, forbidden
			return t.handleResponse(resp)
		}
		io.Copy(ioutil.Discard, resp.Body)
		resp.Body.Close()
	}

	return nil, fmt.Errorf("Cannot reach any host. (%s)", errorMsg)
}

// buildRequest builds an http.Request object. The built request uses `method`,
// tries to reach `path` at `host`, sending `body`.
func (t *Transport) buildRequest(method, host, path string, body interface{}) (*http.Request, error) {
	var req *http.Request
	var err error

	if body != nil {
		bodyBytes, err := json.Marshal(body)
		if err != nil {
			return nil, errors.New("Invalid JSON in the query")
		}

		reader := bytes.NewReader(bodyBytes)
		req, err = http.NewRequest(method, "https://"+host+path, reader)
		req.Header.Add("Content-Length", strconv.Itoa(len(string(bodyBytes))))
		req.Header.Add("Content-Type", "application/json; charset=utf-8")
	} else {
		req, err = http.NewRequest(method, "https://"+host+path, nil)
	}

	if strings.Contains(path, "/*/") {
		req.URL = &url.URL{
			Scheme: "https",
			Host:   host,
			Opaque: "//" + host + path, //Remove url encoding
		}
	}

	return req, err
}

// addHeaders adds the mandatory Algolia headers and the custom headers of `t`
// to `req`.
func (t *Transport) addHeaders(req *http.Request) *http.Request {
	req.Header.Add("X-Algolia-API-Key", t.apiKey)
	req.Header.Add("X-Algolia-Application-Id", t.appID)
	req.Header.Add("Connection", "keep-alive")
	req.Header.Add("User-Agent", "Algolia for Go ("+version+")")

	for k, v := range t.headers {
		req.Header.Add(k, v)
	}

	return req
}

// handleResponse takes care of reading a response as JSON, and returns the
// parsed object. If the status code of the response indicates a failed
// request, or if the body of the response is not a valid JSON object, an error
// is returned.
func (t *Transport) handleResponse(resp *http.Response) ([]byte, error) {
	res, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return nil, err
	}

	var jsonResp interface{}
	if err = json.Unmarshal(res, &jsonResp); err != nil {
		return nil, fmt.Errorf("Invalid JSON response: %s", err)
	}

	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		return res, nil
	} else {
		return nil, errors.New(string(res))
	}
}
<commit_msg>fix: Improve Transport's retry strategy<commit_after>package algoliasearch

import (
	"bytes"
	_ "crypto/sha512" // Fix certificates
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"
)

const (
	version = "2.0.0"
)

// Define the constants used to specify the type of request.
const (
	search = 1 << iota
	write
	read
)

// Seed the RNG used to shuffle the hosts slice (see `defaultHosts` function).
func init() {
	rand.Seed(int64(time.Now().Nanosecond()))
}

// Transport is responsible for the connection and the retry strategy to
// Algolia servers.
type Transport struct {
	activeHost        string
	activeSince       time.Time
	apiKey            string
	appId             string
	dialTimeout       time.Duration
	headers           map[string]string
	hosts             []string
	httpClient        *http.Client
	keepAliveDuration time.Duration
}

// NewTransport instantiates a new Transport with the default Algolia hosts to
// connect to.
func NewTransport(appId, apiKey string) *Transport {
	return &Transport{
		activeHost:        "",
		apiKey:            apiKey,
		appId:             appId,
		dialTimeout:       1 * time.Second,
		headers:           defaultHeaders(appId, apiKey),
		hosts:             defaultHosts(appId),
		httpClient:        defaultHttpClient(),
		keepAliveDuration: 5 * 60 * time.Second,
	}
}

// NewTransport instantiates a new Transport with the specificed hosts as main
// servers to connect to.
func NewTransportWithHosts(appId, apiKey string, hosts []string) *Transport {
	return &Transport{
		activeHost:        "",
		apiKey:            apiKey,
		appId:             appId,
		dialTimeout:       1 * time.Second,
		headers:           defaultHeaders(appId, apiKey),
		hosts:             hosts,
		httpClient:        defaultHttpClient(),
		keepAliveDuration: 5 * 60 * time.Second,
	}
}

// defaultHeaders is used to set the default HTTP headers to use with each
// requests.
func defaultHeaders(appId, apiKey string) map[string]string {
	return map[string]string{
		"Connection":               "keep-alive",
		"User-Agent":               "Algolia for Go (" + version + ")",
		"X-Algolia-API-Key":        apiKey,
		"X-Algolia-Application-Id": appId,
	}
}

// defaultHosts returns the list of the default Algolia hosts to use. The
// entries are shuffled.
func defaultHosts(appId string) []string {
	hosts := []string{
		appId + "-1.algolianet.com",
		appId + "-2.algolianet.com",
		appId + "-3.algolianet.com",
	}

	shuffled := make([]string, len(hosts))
	for i, v := range rand.Perm(len(hosts)) {
		shuffled[i] = hosts[v]
	}

	return shuffled
}

// defaultHttpClient returns the `*http.Client` which will perform all the
// requests. All the timeout settings are explicitely defined here.
func defaultHttpClient() *http.Client {
	return &http.Client{
		Timeout:   time.Second * 30,
		Transport: defaultTransport(1 * time.Second),
	}
}

// defaultTransport returns the `*http.Transport` which starts and maintain the
// connection with the server. The `dialTimeout` is used to specify the timeout
// beyond which the connection is considered as failed (used to control DNS
// lookup timeouts).
func defaultTransport(dialTimeout time.Duration) *http.Transport {
	return &http.Transport{
		Dial: (&net.Dialer{
			KeepAlive: 180 * time.Second,
			Timeout:   dialTimeout,
		}).Dial,
		DisableKeepAlives:   false,
		MaxIdleConnsPerHost: 2,
		TLSHandshakeTimeout: 2 * time.Second,
	}
}

// addHeaders add the key/value pairs from `headers` to the header list of the
// `req` request.
func addHeaders(req *http.Request, headers map[string]string) {
	for k, v := range headers {
		req.Header.Add(k, v)
	}
}

// setExtraHeader lets the user (through the exported `Client.SetExtraHeader`)
// add custom headers to the requests.
func (t *Transport) setExtraHeader(key, value string) {
	t.headers[key] = value
}

// setTimeout lets the user (through the exported `Client.SetTimeout`) replace
// the default values of `TLSHandshakeTimeout` (via `connectTimeout`) and
// `ResponseHeaderTimeout` (via `readTimeout`).
func (t *Transport) setTimeout(connectTimeout, readTimeout time.Duration) {
	switch transport := t.httpClient.Transport.(type) {
	case *http.Transport:
		transport.TLSHandshakeTimeout = connectTimeout
		transport.ResponseHeaderTimeout = readTimeout
	default:
		fmt.Fprintln(os.Stderr, "Timeouts not set for nonstandard underlying Transport")
	}
}

// request is the method used by the `Client` to perform the request against
// the Algolia servers (or to the list of specified hosts).
func (t *Transport) request(method, path string, body interface{}, typeCall int) ([]byte, error) {
	var res []byte
	var err error

	for _, host := range t.hostsToTry(typeCall) {
		res, err = t.tryRequest(method, host, path, body)
		if err == nil {
			t.resetDialTimeout()
			t.activeSince = time.Now()
			t.activeHost = host
			return res, nil
		}
		t.increaseDialTimeout()
	}

	t.activeHost = ""
	return nil, err
}

// hostsToTry returns the list of hosts to try ordered by priority according to
// the type of request (write vs. read/search) and if a previous host was
// marked as active.
func (t *Transport) hostsToTry(typeCall int) []string {
	// If it's a `write` request, we first try `APPID.algolia.net` and then the
	// `APPID-{1,2,3}.algolianet.com` endpoints (shuffled).
	if typeCall == write {
		return append([]string{t.appId + ".algolia.net"}, t.hosts...)
	}

	var hosts []string

	// If a previous host was active since less than k.keepAliveDuration
	// seconds and was not the `APPID-dsn.algolia.net` endpoint, then it is
	// used first in the list of endpoints to try.
	if t.activeHost != "" &&
		!strings.Contains(t.activeHost, "-dsn.algolia.net") &&
		time.Now().Sub(t.activeSince) <= t.keepAliveDuration {
		hosts = []string{t.activeHost}
	}

	// Whether or not an active host is first in the list of hosts to try, the
	// following entries in the list is the DSN endpoint followed by the
	// `APPID-{1,2,3}.algolianet.com` endpoints (shuffled).
	hosts = append(hosts, t.appId+"-dsn.algolia.net")
	hosts = append(hosts, t.hosts...)
	return hosts

}

// tryRequest is the underlying method which actually performs the request. It
// returns the response as a byte slice or a non-nil error if anything went
// wrong.
func (t *Transport) tryRequest(method, host, path string, body interface{}) ([]byte, error) {
	// Build the request
	req, err := t.buildRequest(method, host, path, body)
	if err != nil {
		return nil, err
	}

	// Perform the request
	res, err := t.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("Cannot perform request [%s] %s (%s): %s", method, path, host, err)
	}

	// Read response's body
	bodyRes, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return nil, fmt.Errorf("Cannot read response body: %s", err)
	}
	res.Body.Close()

	// Return the body as an error if the status code is not 2XX
	code := res.StatusCode
	if code < 200 || 300 <= code {
		return nil, errors.New(string(bodyRes))
	}

	return bodyRes, nil
}

// buildRequest returns a valid `http.Request` with the headers and body (if
// any) correctly set. The return error is non-nil if the request is invalid or
// if the body, if non-nil, is not a valid JSON.
func (t *Transport) buildRequest(method, host, path string, body interface{}) (*http.Request, error) {
	var req *http.Request
	var err error
	urlStr := "https://" + host + path

	if body == nil {
		// As the body is nil, an empty body request is instantiated
		req, err = http.NewRequest(method, urlStr, nil)
		if err != nil {
			return nil, fmt.Errorf("Cannot instantiate request: [%s] %s", method, urlStr)
		}
	} else {
		// As the body is non-nil, the content is read
		data, err := json.Marshal(body)
		if err != nil {
			return nil, errors.New("Invalid JSON in the query")
		}
		reader := bytes.NewReader(data)

		// The request is then instantiated with the body content
		req, err = http.NewRequest(method, urlStr, reader)
		if err != nil {
			return nil, fmt.Errorf("Cannot instantiate request: [%s] %s", method, urlStr)
		}

		// Add content specific headers
		req.Header.Add("Content-Length", strconv.Itoa(len(string(data))))
		req.Header.Add("Content-Type", "application/json; charset=utf-8")
	}

	// Add default and Algolia specific headers
	addHeaders(req, t.headers)

	if strings.Contains(path, "/*/") {
		req.URL = &url.URL{
			Scheme: "https",
			Host:   host,
			Opaque: "//" + host + path, //Remove url encoding
		}
	}

	return req, nil
}

// resetDialTimeout increases the `Timeout` value of the underlying dialer by 1
// second.
func (t *Transport) increaseDialTimeout() {
	t.dialTimeout = t.dialTimeout + time.Second
	t.httpClient.Transport = defaultTransport(t.dialTimeout)
}

// resetDialTimeout resets the `Timeout` value of the underlying dialer to 1
// second.
func (t *Transport) resetDialTimeout() {
	t.dialTimeout = 1 * time.Second
	t.httpClient.Transport = defaultTransport(t.dialTimeout)
}
<|endoftext|>


<commit_before>package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}
<commit_msg>Update go hello world with tab instead of spaces indentation.<commit_after>package main

import "fmt"

func main() {
	fmt.Println("Hello, World!")
}
<|endoftext|>


<commit_before>package models

import (
	"database/sql"

	e "github.com/techjanitor/pram-post/errors"
	u "github.com/techjanitor/pram-post/utils"
)

type DeletePostModel struct {
	Thread  uint
	Id      uint
	Ib      uint
	Name    string
	Deleted bool
}

// Status will return info
func (i *DeletePostModel) Status() (err error) {

	// Get Database handle
	db, err := u.GetDb()
	if err != nil {
		return
	}

	// get thread ib and title
	err = db.QueryRow(`SELECT ib_id, thread_title, post_deleted FROM threads
	INNER JOIN posts on threads.thread_id = posts.thread_id
	WHERE threads.thread_id = ? LIMIT 1`, i.Thread).Scan(&i.Ib, &i.Name, &i.Deleted)
	if err == sql.ErrNoRows {
		return e.ErrNotFound
	} else if err != nil {
		return
	}

	return

}

// Delete will remove the entry
func (i *DeletePostModel) Delete() (err error) {

	// Get transaction handle
	tx, err := u.GetTransaction()
	if err != nil {
		return
	}
	defer tx.Rollback()

	var lasttime string

	// get last post time
	err = tx.QueryRow("SELECT post_time FROM posts
	WHERE thread_id = ? AND post_deleted != 1
	ORDER BY post_id DESC LIMIT 1", i.Thread).Scan(&lasttime)
	if err != nil {
		return
	}

	// set post to deleted
	ps1, err := tx.Prepare(`UPDATE posts SET post_deleted = ?
	WHERE posts.thread_id = ? AND posts.post_num = ? LIMIT 1`)
	if err != nil {
		return
	}
	defer ps1.Close()

	// update last post time in thread
	ps2, err := tx.Prepare("UPDATE threads SET thread_last_post= ? WHERE thread_id= ?")
	if err != nil {
		return
	}
	defer ps2.Close()

	_, err = ps1.Exec(!i.Deleted, i.Thread, i.Id)
	if err != nil {
		return
	}

	_, err = ps2.Exec(lasttime, i.Thread)
	if err != nil {
		return
	}

	// Commit transaction
	err = tx.Commit()
	if err != nil {
		return
	}

	return

}
<commit_msg>update last thread post on post deletion<commit_after>package models

import (
	"database/sql"

	e "github.com/techjanitor/pram-post/errors"
	u "github.com/techjanitor/pram-post/utils"
)

type DeletePostModel struct {
	Thread  uint
	Id      uint
	Ib      uint
	Name    string
	Deleted bool
}

// Status will return info
func (i *DeletePostModel) Status() (err error) {

	// Get Database handle
	db, err := u.GetDb()
	if err != nil {
		return
	}

	// get thread ib and title
	err = db.QueryRow(`SELECT ib_id, thread_title, post_deleted FROM threads
	INNER JOIN posts on threads.thread_id = posts.thread_id
	WHERE threads.thread_id = ? LIMIT 1`, i.Thread).Scan(&i.Ib, &i.Name, &i.Deleted)
	if err == sql.ErrNoRows {
		return e.ErrNotFound
	} else if err != nil {
		return
	}

	return

}

// Delete will remove the entry
func (i *DeletePostModel) Delete() (err error) {

	// Get transaction handle
	tx, err := u.GetTransaction()
	if err != nil {
		return
	}
	defer tx.Rollback()

	var lasttime string

	// get last post time
	err = tx.QueryRow(`SELECT post_time FROM posts
	WHERE thread_id = ? AND post_deleted != 1
	ORDER BY post_id DESC LIMIT 1`, i.Thread).Scan(&lasttime)
	if err != nil {
		return
	}

	// set post to deleted
	ps1, err := tx.Prepare(`UPDATE posts SET post_deleted = ?
	WHERE posts.thread_id = ? AND posts.post_num = ? LIMIT 1`)
	if err != nil {
		return
	}
	defer ps1.Close()

	// update last post time in thread
	ps2, err := tx.Prepare("UPDATE threads SET thread_last_post= ? WHERE thread_id= ?")
	if err != nil {
		return
	}
	defer ps2.Close()

	_, err = ps1.Exec(!i.Deleted, i.Thread, i.Id)
	if err != nil {
		return
	}

	_, err = ps2.Exec(lasttime, i.Thread)
	if err != nil {
		return
	}

	// Commit transaction
	err = tx.Commit()
	if err != nil {
		return
	}

	return

}
<|endoftext|>


<commit_before>package types

import (
	"database/sql/driver"
	"github.com/ethereum/go-ethereum/rlp"
	"github.com/jinzhu/gorm"
	"errors"
	"log"
)
type NetworkAddressMap map[uint]*Address

type netAddPair struct {
	Net uint
	Address Address
}

func (nmap NetworkAddressMap) Value() (driver.Value, error) {
	items := []netAddPair{}
	for k, v := range nmap {
		items = append(items, netAddPair{k, *v})
	}
	bytes, err := rlp.EncodeToBytes(items)
	log.Printf("Saving nmap: %v, %#x, %v", items, bytes[:], err)
	bytes, err = rlp.EncodeToBytes(&items[0])
	log.Printf("nmap[0]: %v, %#x, %v", items[0], bytes[:], err)

	return rlp.EncodeToBytes(items)
}

func (nmap NetworkAddressMap) Scan(src interface{}) error {
	switch v := src.(type) {
	case []byte:
		items := []netAddPair{}
		rlp.DecodeBytes(v, items)
		for _, item := range items {
			nmap[item.Net] = &item.Address
		}
		return nil
	default:
		return errors.New("NetworkAddressMap scanner src should be []byte")
	}
}

// GormDataType tells gorm what data type to use for the column.
func (nmap NetworkAddressMap) GormDataType(dialect gorm.Dialect) string {
	if dialect.GetName() == "postgres" {
		return "bytea"
	}
	return "blob"
}
<commit_msg>Removing debugging statements that only work if len(items) >= 1<commit_after>package types

import (
	"database/sql/driver"
	"github.com/ethereum/go-ethereum/rlp"
	"github.com/jinzhu/gorm"
	"errors"
)
type NetworkAddressMap map[uint]*Address

type netAddPair struct {
	Net uint
	Address Address
}

func (nmap NetworkAddressMap) Value() (driver.Value, error) {
	items := []netAddPair{}
	for k, v := range nmap {
		items = append(items, netAddPair{k, *v})
	}
	return rlp.EncodeToBytes(items)
}

func (nmap NetworkAddressMap) Scan(src interface{}) error {
	switch v := src.(type) {
	case []byte:
		items := []netAddPair{}
		rlp.DecodeBytes(v, items)
		for _, item := range items {
			nmap[item.Net] = &item.Address
		}
		return nil
	default:
		return errors.New("NetworkAddressMap scanner src should be []byte")
	}
}

// GormDataType tells gorm what data type to use for the column.
func (nmap NetworkAddressMap) GormDataType(dialect gorm.Dialect) string {
	if dialect.GetName() == "postgres" {
		return "bytea"
	}
	return "blob"
}
<|endoftext|>


<commit_before>package samlsp

import (
	"encoding/base64"
	"encoding/pem"
	"encoding/xml"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/dgrijalva/jwt-go"

	"github.com/crewjam/saml"
)

// Middleware implements middleware than allows a web application
// to support SAML.
//
// It implements http.Handler so that it can provide the metadata and ACS endpoints,
// typically /saml/metadata and /saml/acs, respectively.
//
// It also provides middleware, RequireAccount which redirects users to
// the auth process if they do not have session credentials.
//
// When redirecting the user through the SAML auth flow, the middlware assigns
// a temporary cookie with a random name beginning with "saml_". The value of
// the cookie is a signed JSON Web Token containing the original URL requested
// and the SAML request ID. The random part of the name corresponds to the
// RelayState parameter passed through the SAML flow.
//
// When validating the SAML response, the RelayState is used to look up the
// correct cookie, validate that the SAML request ID, and redirect the user
// back to their original URL.
//
// Sessions are established by issuing a JSON Web Token (JWT) as a session
// cookie once the SAML flow has succeeded. The JWT token contains the
// authenticated attributes from the SAML assertion.
//
// When the middlware receives a request with a valid session JWT it extracts
// the SAML attributes and modifies the http.Request object adding headers
// corresponding to the specified attributes. For example, if the attribute
// "cn" were present in the initial assertion with a value of "Alice Smith",
// then a corresponding header "X-Saml-Cn" will be added to the request with
// a value of "Alice Smith". For safety, the middleware strips out any existing
// headers that begin with "X-Saml-".
//
// When issuing JSON Web Tokens, a signing key is required. Because the
// SAML service provider already has a private key, we borrow that key
// to sign the JWTs as well.
type Middleware struct {
	ServiceProvider   saml.ServiceProvider
	AllowIDPInitiated bool
}

const cookieMaxAge = time.Hour // TODO(ross): must be configurable
const cookieName = "token"

func randomBytes(n int) []byte {
	rv := make([]byte, n)
	if _, err := saml.RandReader.Read(rv); err != nil {
		panic(err)
	}
	return rv
}

// ServeHTTP implements http.Handler and serves the SAML-specific HTTP endpoints
// on the URIs specified by m.ServiceProvider.MetadataURL and
// m.ServiceProvider.AcsURL.
func (m *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	metadataURL, _ := url.Parse(m.ServiceProvider.MetadataURL)
	if r.URL.Path == metadataURL.Path {
		buf, _ := xml.MarshalIndent(saml.EntitiesDescriptor{
			EntityDescriptor: []*saml.Metadata{m.ServiceProvider.Metadata()},
		}, "", "  ")
		w.Write(buf)
		return
	}

	acsURL, _ := url.Parse(m.ServiceProvider.AcsURL)
	if r.URL.Path == acsURL.Path {
		r.ParseForm()
		assertion, err := m.ServiceProvider.ParseResponse(r, m.getPossibleRequestIDs(r))
		if err != nil {
			if parseErr, ok := err.(*saml.InvalidResponseError); ok {
				log.Printf("RESPONSE: ===\n%s\n===\nNOW: %s\nERROR: %s",
					parseErr.Response, parseErr.Now, parseErr.PrivateErr)
			}
			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
			return
		}

		m.Authorize(w, r, assertion)
		return
	}

	http.NotFoundHandler().ServeHTTP(w, r)
}

// RequireAccount is HTTP middleware that requires that each request be
// associated with a valid session. If the request is not associated with a valid
// session, then rather than serve the request, the middlware redirects the user
// to start the SAML auth flow.
func (m *Middleware) RequireAccount(handler http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		if m.IsAuthorized(r) {
			handler.ServeHTTP(w, r)
			return
		}

		// If we try to redirect when the original request is the ACS URL we'll
		// end up in a loop. This is a programming error, so we panic here. In
		// general this means a 500 to the user, which is preferable to a
		// redirect loop.
		acsURL, _ := url.Parse(m.ServiceProvider.AcsURL)
		if r.URL.Path == acsURL.Path {
			panic("don't wrap Middleware with RequireAccount")
		}

		req, err := m.ServiceProvider.MakeAuthenticationRequest(
			m.ServiceProvider.GetSSOBindingLocation(saml.HTTPRedirectBinding))
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		// relayState is limited to 80 bytes but also must be integrety protected.
		// this means that we cannot use a JWT because it is way to long. Instead
		// we set a cookie that corresponds to the state
		relayState := base64.StdEncoding.EncodeToString(randomBytes(42))

		secretBlock, _ := pem.Decode([]byte(m.ServiceProvider.Key))
		state := jwt.New(jwt.GetSigningMethod("HS256"))
		state.Claims["id"] = req.ID
		state.Claims["uri"] = r.URL.String()
		signedState, err := state.SignedString(secretBlock.Bytes)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		http.SetCookie(w, &http.Cookie{
			Name:     fmt.Sprintf("saml_%s", relayState),
			Value:    signedState,
			MaxAge:   int(saml.MaxIssueDelay.Seconds()),
			HttpOnly: false,
			Path:     acsURL.Path,
		})
		redirectURL := req.Redirect(relayState)

		w.Header().Add("Location", redirectURL.String())
		w.WriteHeader(http.StatusFound)
		return
	}
	return http.HandlerFunc(fn)
}

func (m *Middleware) getPossibleRequestIDs(r *http.Request) []string {
	rv := []string{}
	for _, cookie := range r.Cookies() {
		if !strings.HasPrefix(cookie.Name, "saml_") {
			continue
		}
		log.Printf("getPossibleRequestIDs: cookie: %s", cookie.String())
		token, err := jwt.Parse(cookie.Value, func(t *jwt.Token) (interface{}, error) {
			secretBlock, _ := pem.Decode([]byte(m.ServiceProvider.Key))
			return secretBlock.Bytes, nil
		})
		if err != nil || !token.Valid {
			log.Printf("... invalid token %s", err)
			continue
		}
		rv = append(rv, token.Claims["id"].(string))
	}

	// If IDP initiated requests are allowed, then we can expect an empty response ID.
	if m.AllowIDPInitiated {
		rv = append(rv, "")
	}

	return rv
}

// Authorize is invoked by ServeHTTP when we have a new, valid SAML assertion.
// It sets a cookie that contains a signed JWT containing the assertion attributes.
// It then redirects the user's browser to the original URL contained in RelayState.
func (m *Middleware) Authorize(w http.ResponseWriter, r *http.Request, assertion *saml.Assertion) {
	secretBlock, _ := pem.Decode([]byte(m.ServiceProvider.Key))

	redirectURI := "/"
	if r.Form.Get("RelayState") != "" {
		stateCookie, err := r.Cookie(fmt.Sprintf("saml_%s", r.Form.Get("RelayState")))
		if err != nil {
			log.Printf("cannot find corresponding cookie: %s", fmt.Sprintf("saml_%s", r.Form.Get("RelayState")))
			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
			return
		}

		state, err := jwt.Parse(stateCookie.Value, func(t *jwt.Token) (interface{}, error) {
			return secretBlock.Bytes, nil
		})
		if err != nil || !state.Valid {
			log.Printf("Cannot decode state JWT: %s (%s)", err, stateCookie.Value)
			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
			return
		}
		redirectURI = state.Claims["uri"].(string)

		// delete the cookie
		stateCookie.Value = ""
		stateCookie.Expires = time.Time{}
		http.SetCookie(w, stateCookie)
	}

	token := jwt.New(jwt.GetSigningMethod("HS256"))
	for _, attr := range assertion.AttributeStatement.Attributes {
		valueStrings := []string{}
		for _, v := range attr.Values {
			valueStrings = append(valueStrings, v.Value)
		}
		claimName := attr.FriendlyName
		if claimName == "" {
			claimName = attr.Name
		}
		token.Claims[claimName] = valueStrings
	}
	token.Claims["exp"] = saml.TimeNow().Add(cookieMaxAge).Unix()
	signedToken, err := token.SignedString(secretBlock.Bytes)
	if err != nil {
		panic(err)
	}

	http.SetCookie(w, &http.Cookie{
		Name:     cookieName,
		Value:    signedToken,
		MaxAge:   int(cookieMaxAge.Seconds()),
		HttpOnly: false,
		Path:     "/",
	})

	http.Redirect(w, r, redirectURI, http.StatusFound)
}

// IsAuthorized is invoked by RequireAccount to determine if the request
// is already authorized or if the user's browser should be redirected to the
// SAML login flow. If the request is authorized, then the request headers
// starting with X-Saml- for each SAML assertion attribute are set. For example,
// if an attribute "uid" has the value "alice@example.com", then the following
// header would be added to the request:
//
//     X-Saml-Uid: alice@example.com
//
// It is an error for this function to be invoked with a request containing
// any headers starting with X-Saml. This function will panic if you do.
func (m *Middleware) IsAuthorized(r *http.Request) bool {
	cookie, err := r.Cookie(cookieName)
	if err != nil {
		return false
	}
	token, err := jwt.Parse(cookie.Value, func(t *jwt.Token) (interface{}, error) {
		secretBlock, _ := pem.Decode([]byte(m.ServiceProvider.Key))
		return secretBlock.Bytes, nil
	})
	if err != nil || !token.Valid {
		return false
	}

	// It is an error for the request to include any X-SAML* headers,
	// because those might be confused with ours. If we encounter any
	// such headers, we abort the request, so there is no confustion.
	for headerName := range r.Header {
		if strings.HasPrefix(headerName, "X-Saml") {
			panic("X-Saml-* headers should not exist when this function is called")
		}
	}

	for claimName, claimValue := range token.Claims {
		if claimName == "exp" {
			continue
		}
		for _, claimValueStr := range claimValue.([]interface{}) {
			r.Header.Add(fmt.Sprintf("X-Saml-%s", claimName), claimValueStr.(string))
		}
	}
	return true
}

// RequireAttribute returns a middleware function that requires that the
// SAML attribute `name` be set to `value`. This can be used to require
// that a remote user be a member of a group. It relies on the X-Saml-* headers
// that RequireAccount adds to the request.
//
// For example:
//
//     goji.Use(m.RequireAccount)
//     goji.Use(RequireAttributeMiddleware("eduPersonAffiliation", "Staff"))
//
func RequireAttribute(name, value string) func(http.Handler) http.Handler {
	return func(handler http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, r *http.Request) {
			if values, ok := r.Header[http.CanonicalHeaderKey(fmt.Sprintf("X-Saml-%s", name))]; ok {
				for _, actualValue := range values {
					if actualValue == value {
						handler.ServeHTTP(w, r)
						return
					}
				}
			}
			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
		}
		return http.HandlerFunc(fn)
	}
}
<commit_msg>middleware: don’t try to set a cookie with an invalid name<commit_after>package samlsp

import (
	"encoding/base64"
	"encoding/pem"
	"encoding/xml"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/dgrijalva/jwt-go"

	"github.com/crewjam/saml"
)

// Middleware implements middleware than allows a web application
// to support SAML.
//
// It implements http.Handler so that it can provide the metadata and ACS endpoints,
// typically /saml/metadata and /saml/acs, respectively.
//
// It also provides middleware, RequireAccount which redirects users to
// the auth process if they do not have session credentials.
//
// When redirecting the user through the SAML auth flow, the middlware assigns
// a temporary cookie with a random name beginning with "saml_". The value of
// the cookie is a signed JSON Web Token containing the original URL requested
// and the SAML request ID. The random part of the name corresponds to the
// RelayState parameter passed through the SAML flow.
//
// When validating the SAML response, the RelayState is used to look up the
// correct cookie, validate that the SAML request ID, and redirect the user
// back to their original URL.
//
// Sessions are established by issuing a JSON Web Token (JWT) as a session
// cookie once the SAML flow has succeeded. The JWT token contains the
// authenticated attributes from the SAML assertion.
//
// When the middlware receives a request with a valid session JWT it extracts
// the SAML attributes and modifies the http.Request object adding headers
// corresponding to the specified attributes. For example, if the attribute
// "cn" were present in the initial assertion with a value of "Alice Smith",
// then a corresponding header "X-Saml-Cn" will be added to the request with
// a value of "Alice Smith". For safety, the middleware strips out any existing
// headers that begin with "X-Saml-".
//
// When issuing JSON Web Tokens, a signing key is required. Because the
// SAML service provider already has a private key, we borrow that key
// to sign the JWTs as well.
type Middleware struct {
	ServiceProvider   saml.ServiceProvider
	AllowIDPInitiated bool
}

const cookieMaxAge = time.Hour // TODO(ross): must be configurable
const cookieName = "token"

func randomBytes(n int) []byte {
	rv := make([]byte, n)
	if _, err := saml.RandReader.Read(rv); err != nil {
		panic(err)
	}
	return rv
}

// ServeHTTP implements http.Handler and serves the SAML-specific HTTP endpoints
// on the URIs specified by m.ServiceProvider.MetadataURL and
// m.ServiceProvider.AcsURL.
func (m *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	metadataURL, _ := url.Parse(m.ServiceProvider.MetadataURL)
	if r.URL.Path == metadataURL.Path {
		buf, _ := xml.MarshalIndent(saml.EntitiesDescriptor{
			EntityDescriptor: []*saml.Metadata{m.ServiceProvider.Metadata()},
		}, "", "  ")
		w.Write(buf)
		return
	}

	acsURL, _ := url.Parse(m.ServiceProvider.AcsURL)
	if r.URL.Path == acsURL.Path {
		r.ParseForm()
		assertion, err := m.ServiceProvider.ParseResponse(r, m.getPossibleRequestIDs(r))
		if err != nil {
			if parseErr, ok := err.(*saml.InvalidResponseError); ok {
				log.Printf("RESPONSE: ===\n%s\n===\nNOW: %s\nERROR: %s",
					parseErr.Response, parseErr.Now, parseErr.PrivateErr)
			}
			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
			return
		}

		m.Authorize(w, r, assertion)
		return
	}

	http.NotFoundHandler().ServeHTTP(w, r)
}

// RequireAccount is HTTP middleware that requires that each request be
// associated with a valid session. If the request is not associated with a valid
// session, then rather than serve the request, the middlware redirects the user
// to start the SAML auth flow.
func (m *Middleware) RequireAccount(handler http.Handler) http.Handler {
	fn := func(w http.ResponseWriter, r *http.Request) {
		if m.IsAuthorized(r) {
			handler.ServeHTTP(w, r)
			return
		}

		// If we try to redirect when the original request is the ACS URL we'll
		// end up in a loop. This is a programming error, so we panic here. In
		// general this means a 500 to the user, which is preferable to a
		// redirect loop.
		acsURL, _ := url.Parse(m.ServiceProvider.AcsURL)
		if r.URL.Path == acsURL.Path {
			panic("don't wrap Middleware with RequireAccount")
		}

		req, err := m.ServiceProvider.MakeAuthenticationRequest(
			m.ServiceProvider.GetSSOBindingLocation(saml.HTTPRedirectBinding))
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		// relayState is limited to 80 bytes but also must be integrety protected.
		// this means that we cannot use a JWT because it is way to long. Instead
		// we set a cookie that corresponds to the state
		relayState := base64.URLEncoding.EncodeToString(randomBytes(42))

		secretBlock, _ := pem.Decode([]byte(m.ServiceProvider.Key))
		state := jwt.New(jwt.GetSigningMethod("HS256"))
		state.Claims["id"] = req.ID
		state.Claims["uri"] = r.URL.String()
		signedState, err := state.SignedString(secretBlock.Bytes)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		http.SetCookie(w, &http.Cookie{
			Name:     fmt.Sprintf("saml_%s", relayState),
			Value:    signedState,
			MaxAge:   int(saml.MaxIssueDelay.Seconds()),
			HttpOnly: false,
			Path:     acsURL.Path,
		})
		redirectURL := req.Redirect(relayState)

		w.Header().Add("Location", redirectURL.String())
		w.WriteHeader(http.StatusFound)
		return
	}
	return http.HandlerFunc(fn)
}

func (m *Middleware) getPossibleRequestIDs(r *http.Request) []string {
	rv := []string{}
	for _, cookie := range r.Cookies() {
		if !strings.HasPrefix(cookie.Name, "saml_") {
			continue
		}
		log.Printf("getPossibleRequestIDs: cookie: %s", cookie.String())
		token, err := jwt.Parse(cookie.Value, func(t *jwt.Token) (interface{}, error) {
			secretBlock, _ := pem.Decode([]byte(m.ServiceProvider.Key))
			return secretBlock.Bytes, nil
		})
		if err != nil || !token.Valid {
			log.Printf("... invalid token %s", err)
			continue
		}
		rv = append(rv, token.Claims["id"].(string))
	}

	// If IDP initiated requests are allowed, then we can expect an empty response ID.
	if m.AllowIDPInitiated {
		rv = append(rv, "")
	}

	return rv
}

// Authorize is invoked by ServeHTTP when we have a new, valid SAML assertion.
// It sets a cookie that contains a signed JWT containing the assertion attributes.
// It then redirects the user's browser to the original URL contained in RelayState.
func (m *Middleware) Authorize(w http.ResponseWriter, r *http.Request, assertion *saml.Assertion) {
	secretBlock, _ := pem.Decode([]byte(m.ServiceProvider.Key))

	redirectURI := "/"
	if r.Form.Get("RelayState") != "" {
		stateCookie, err := r.Cookie(fmt.Sprintf("saml_%s", r.Form.Get("RelayState")))
		if err != nil {
			log.Printf("cannot find corresponding cookie: %s", fmt.Sprintf("saml_%s", r.Form.Get("RelayState")))
			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
			return
		}

		state, err := jwt.Parse(stateCookie.Value, func(t *jwt.Token) (interface{}, error) {
			return secretBlock.Bytes, nil
		})
		if err != nil || !state.Valid {
			log.Printf("Cannot decode state JWT: %s (%s)", err, stateCookie.Value)
			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
			return
		}
		redirectURI = state.Claims["uri"].(string)

		// delete the cookie
		stateCookie.Value = ""
		stateCookie.Expires = time.Time{}
		http.SetCookie(w, stateCookie)
	}

	token := jwt.New(jwt.GetSigningMethod("HS256"))
	for _, attr := range assertion.AttributeStatement.Attributes {
		valueStrings := []string{}
		for _, v := range attr.Values {
			valueStrings = append(valueStrings, v.Value)
		}
		claimName := attr.FriendlyName
		if claimName == "" {
			claimName = attr.Name
		}
		token.Claims[claimName] = valueStrings
	}
	token.Claims["exp"] = saml.TimeNow().Add(cookieMaxAge).Unix()
	signedToken, err := token.SignedString(secretBlock.Bytes)
	if err != nil {
		panic(err)
	}

	http.SetCookie(w, &http.Cookie{
		Name:     cookieName,
		Value:    signedToken,
		MaxAge:   int(cookieMaxAge.Seconds()),
		HttpOnly: false,
		Path:     "/",
	})

	http.Redirect(w, r, redirectURI, http.StatusFound)
}

// IsAuthorized is invoked by RequireAccount to determine if the request
// is already authorized or if the user's browser should be redirected to the
// SAML login flow. If the request is authorized, then the request headers
// starting with X-Saml- for each SAML assertion attribute are set. For example,
// if an attribute "uid" has the value "alice@example.com", then the following
// header would be added to the request:
//
//     X-Saml-Uid: alice@example.com
//
// It is an error for this function to be invoked with a request containing
// any headers starting with X-Saml. This function will panic if you do.
func (m *Middleware) IsAuthorized(r *http.Request) bool {
	cookie, err := r.Cookie(cookieName)
	if err != nil {
		return false
	}
	token, err := jwt.Parse(cookie.Value, func(t *jwt.Token) (interface{}, error) {
		secretBlock, _ := pem.Decode([]byte(m.ServiceProvider.Key))
		return secretBlock.Bytes, nil
	})
	if err != nil || !token.Valid {
		return false
	}

	// It is an error for the request to include any X-SAML* headers,
	// because those might be confused with ours. If we encounter any
	// such headers, we abort the request, so there is no confustion.
	for headerName := range r.Header {
		if strings.HasPrefix(headerName, "X-Saml") {
			panic("X-Saml-* headers should not exist when this function is called")
		}
	}

	for claimName, claimValue := range token.Claims {
		if claimName == "exp" {
			continue
		}
		for _, claimValueStr := range claimValue.([]interface{}) {
			r.Header.Add(fmt.Sprintf("X-Saml-%s", claimName), claimValueStr.(string))
		}
	}
	return true
}

// RequireAttribute returns a middleware function that requires that the
// SAML attribute `name` be set to `value`. This can be used to require
// that a remote user be a member of a group. It relies on the X-Saml-* headers
// that RequireAccount adds to the request.
//
// For example:
//
//     goji.Use(m.RequireAccount)
//     goji.Use(RequireAttributeMiddleware("eduPersonAffiliation", "Staff"))
//
func RequireAttribute(name, value string) func(http.Handler) http.Handler {
	return func(handler http.Handler) http.Handler {
		fn := func(w http.ResponseWriter, r *http.Request) {
			if values, ok := r.Header[http.CanonicalHeaderKey(fmt.Sprintf("X-Saml-%s", name))]; ok {
				for _, actualValue := range values {
					if actualValue == value {
						handler.ServeHTTP(w, r)
						return
					}
				}
			}
			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
		}
		return http.HandlerFunc(fn)
	}
}
<|endoftext|>


<commit_before>package butlerd

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

	"github.com/itchio/wharf/werrors"

	"github.com/itchio/httpkit/neterr"
	"github.com/itchio/httpkit/progress"

	"crawshaw.io/sqlite"
	"github.com/itchio/butler/database/models"
	itchio "github.com/itchio/go-itchio"
	"github.com/itchio/wharf/state"
	"github.com/pkg/errors"
	"github.com/sourcegraph/jsonrpc2"
)

type RequestHandler func(rc *RequestContext) (interface{}, error)
type NotificationHandler func(rc *RequestContext)

type GetClientFunc func(key string) *itchio.Client

type Router struct {
	Handlers             map[string]RequestHandler
	NotificationHandlers map[string]NotificationHandler
	CancelFuncs          *CancelFuncs
	dbPool               *sqlite.Pool
	getClient            GetClientFunc

	ButlerVersion       string
	ButlerVersionString string
}

func NewRouter(dbPool *sqlite.Pool, getClient GetClientFunc) *Router {
	return &Router{
		Handlers:             make(map[string]RequestHandler),
		NotificationHandlers: make(map[string]NotificationHandler),
		CancelFuncs: &CancelFuncs{
			Funcs: make(map[string]context.CancelFunc),
		},
		dbPool:    dbPool,
		getClient: getClient,
	}
}

func (r *Router) Register(method string, rh RequestHandler) {
	if _, ok := r.Handlers[method]; ok {
		panic(fmt.Sprintf("Can't register handler twice for %s", method))
	}
	r.Handlers[method] = rh
}

func (r *Router) RegisterNotification(method string, nh NotificationHandler) {
	if _, ok := r.NotificationHandlers[method]; ok {
		panic(fmt.Sprintf("Can't register handler twice for %s", method))
	}
	r.NotificationHandlers[method] = nh
}

func (r *Router) Dispatch(ctx context.Context, origConn *jsonrpc2.Conn, req *jsonrpc2.Request) {
	method := req.Method
	var res interface{}

	conn := &JsonRPC2Conn{origConn}
	consumer, cErr := NewStateConsumer(&NewStateConsumerParams{
		Ctx:  ctx,
		Conn: conn,
	})
	if cErr != nil {
		return
	}

	err := func() (err error) {
		defer func() {
			if r := recover(); r != nil {
				if rErr, ok := r.(error); ok {
					err = errors.WithStack(rErr)
				} else {
					err = errors.Errorf("panic: %v", r)
				}
			}
		}()

		rc := &RequestContext{
			Ctx:         ctx,
			Consumer:    consumer,
			Params:      req.Params,
			Conn:        conn,
			CancelFuncs: r.CancelFuncs,
			dbPool:      r.dbPool,
			Client:      r.getClient,

			ButlerVersion:       r.ButlerVersion,
			ButlerVersionString: r.ButlerVersionString,

			origConn: origConn,
			method:   method,
		}

		if req.Notif {
			if nh, ok := r.NotificationHandlers[req.Method]; ok {
				nh(rc)
			}
		} else {
			if h, ok := r.Handlers[method]; ok {
				rc.Consumer.OnProgress = func(alpha float64) {
					if rc.tracker == nil {
						// skip
						return
					}

					rc.tracker.SetProgress(alpha)
					notif := ProgressNotification{
						Progress: alpha,
						ETA:      rc.tracker.ETA().Seconds(),
						BPS:      rc.tracker.BPS(),
					}
					// cannot use autogenerated wrappers to avoid import cycles
					rc.Notify("Progress", notif)
				}
				rc.Consumer.OnProgressLabel = func(label string) {
					// muffin
				}
				rc.Consumer.OnPauseProgress = func() {
					if rc.tracker != nil {
						rc.tracker.Pause()
					}
				}
				rc.Consumer.OnResumeProgress = func() {
					if rc.tracker != nil {
						rc.tracker.Resume()
					}
				}

				res, err = h(rc)
			} else {
				err = &RpcError{
					Code:    jsonrpc2.CodeMethodNotFound,
					Message: fmt.Sprintf("Method '%s' not found", req.Method),
				}
			}
		}
		return
	}()

	if req.Notif {
		return
	}

	if err == nil {
		err = origConn.Reply(ctx, req.ID, res)
		if err != nil {
			consumer.Errorf("Error while replying: %s", err.Error())
		}
		return
	}

	var code int64
	var message string
	var data map[string]interface{}

	if ee, ok := AsButlerdError(err); ok {
		code = ee.RpcErrorCode()
		message = ee.RpcErrorMessage()
		data = ee.RpcErrorData()
	} else {
		if neterr.IsNetworkError(err) {
			code = int64(CodeNetworkDisconnected)
			message = CodeNetworkDisconnected.Error()
		} else if errors.Cause(err) == werrors.ErrCancelled {
			code = int64(CodeOperationCancelled)
			message = CodeOperationCancelled.Error()
		} else {
			code = jsonrpc2.CodeInternalError
			message = err.Error()
		}
	}

	var rawData *json.RawMessage
	if data == nil {
		data = make(map[string]interface{})
	}
	data["stack"] = fmt.Sprintf("%+v", err)
	data["butlerVersion"] = r.ButlerVersionString

	marshalledData, marshalErr := json.Marshal(data)
	if marshalErr == nil {
		rawMessage := json.RawMessage(marshalledData)
		rawData = &rawMessage
	}

	origConn.ReplyWithError(ctx, req.ID, &jsonrpc2.Error{
		Code:    code,
		Message: message,
		Data:    rawData,
	})
}

type RequestContext struct {
	Ctx         context.Context
	Consumer    *state.Consumer
	Params      *json.RawMessage
	Conn        Conn
	CancelFuncs *CancelFuncs
	dbPool      *sqlite.Pool
	Client      GetClientFunc

	ButlerVersion       string
	ButlerVersionString string

	notificationInterceptors map[string]NotificationInterceptor
	tracker                  *progress.Tracker

	method   string
	origConn *jsonrpc2.Conn
}

type WithParamsFunc func() (interface{}, error)

type NotificationInterceptor func(method string, params interface{}) error

func (rc *RequestContext) Call(method string, params interface{}, res interface{}) error {
	return rc.Conn.Call(rc.Ctx, method, params, res)
}

func (rc *RequestContext) InterceptNotification(method string, interceptor NotificationInterceptor) {
	if rc.notificationInterceptors == nil {
		rc.notificationInterceptors = make(map[string]NotificationInterceptor)
	}
	rc.notificationInterceptors[method] = interceptor
}

func (rc *RequestContext) StopInterceptingNotification(method string) {
	if rc.notificationInterceptors == nil {
		return
	}
	delete(rc.notificationInterceptors, method)
}

func (rc *RequestContext) Notify(method string, params interface{}) error {
	if rc.notificationInterceptors != nil {
		if ni, ok := rc.notificationInterceptors[method]; ok {
			return ni(method, params)
		}
	}
	return rc.Conn.Notify(rc.Ctx, method, params)
}

func (rc *RequestContext) RootClient() *itchio.Client {
	return rc.Client("<keyless>")
}

func (rc *RequestContext) ProfileClient(profileID int64) (*models.Profile, *itchio.Client) {
	if profileID == 0 {
		panic(errors.New("profileId must be non-zero"))
	}

	conn := rc.GetConn()
	defer rc.PutConn(conn)

	profile := models.ProfileByID(conn, profileID)
	if profile == nil {
		panic(errors.Errorf("Could not find profile %d", profileID))
	}

	if profile.APIKey == "" {
		panic(errors.Errorf("Profile %d lacks API key", profileID))
	}

	return profile, rc.Client(profile.APIKey)
}

func (rc *RequestContext) StartProgress() {
	rc.StartProgressWithTotalBytes(0)
}

func (rc *RequestContext) StartProgressWithTotalBytes(totalBytes int64) {
	rc.StartProgressWithInitialAndTotal(0.0, totalBytes)
}

func (rc *RequestContext) StartProgressWithInitialAndTotal(initialProgress float64, totalBytes int64) {
	if rc.tracker != nil {
		rc.Consumer.Warnf("Asked to start progress but already tracking progress!")
		return
	}

	rc.tracker = progress.NewTracker()
	rc.tracker.SetSilent(true)
	rc.tracker.SetProgress(initialProgress)
	rc.tracker.SetTotalBytes(totalBytes)
	rc.tracker.Start()
}

func (rc *RequestContext) EndProgress() {
	if rc.tracker != nil {
		rc.tracker.Finish()
		rc.tracker = nil
	} else {
		rc.Consumer.Warnf("Asked to stop progress but wasn't tracking progress!")
	}
}

func (rc *RequestContext) GetConn() *sqlite.Conn {
	getCtx, cancel := context.WithTimeout(rc.Ctx, 3*time.Second)
	defer cancel()
	conn := rc.dbPool.Get(getCtx.Done())
	if conn != nil {
		// this works around an issue with sqlite3_interrupt()
		// I (amos) have spent a lot of time trying to figure out
		// what it is, and I wasn't able to. If you want to mess
		// with it, knock yourself out:
		//
		// => https://github.com/fasterthanlime/chaoe
		//
		conn.SetInterrupt(context.Background().Done())
		return conn
	}

	panic(errors.WithStack(CodeDatabaseBusy))
}

func (rc *RequestContext) PutConn(conn *sqlite.Conn) {
	rc.dbPool.Put(conn)
}

func (rc *RequestContext) WithConn(f func(conn *sqlite.Conn)) {
	conn := rc.GetConn()
	defer rc.PutConn(conn)
	f(conn)
}

func (rc *RequestContext) WithConnBool(f func(conn *sqlite.Conn) bool) bool {
	conn := rc.GetConn()
	defer rc.PutConn(conn)
	return f(conn)
}

type CancelFuncs struct {
	Funcs map[string]context.CancelFunc
}

func (cf *CancelFuncs) Add(id string, f context.CancelFunc) {
	cf.Funcs[id] = f
}

func (cf *CancelFuncs) Remove(id string) {
	delete(cf.Funcs, id)
}

func (cf *CancelFuncs) Call(id string) bool {
	if f, ok := cf.Funcs[id]; ok {
		f()
		delete(cf.Funcs, id)
		return true
	}

	return false
}
<commit_msg>early exit<commit_after>package butlerd

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

	"github.com/itchio/wharf/werrors"

	"github.com/itchio/httpkit/neterr"
	"github.com/itchio/httpkit/progress"

	"crawshaw.io/sqlite"
	"github.com/itchio/butler/database/models"
	itchio "github.com/itchio/go-itchio"
	"github.com/itchio/wharf/state"
	"github.com/pkg/errors"
	"github.com/sourcegraph/jsonrpc2"
)

type RequestHandler func(rc *RequestContext) (interface{}, error)
type NotificationHandler func(rc *RequestContext)

type GetClientFunc func(key string) *itchio.Client

type Router struct {
	Handlers             map[string]RequestHandler
	NotificationHandlers map[string]NotificationHandler
	CancelFuncs          *CancelFuncs
	dbPool               *sqlite.Pool
	getClient            GetClientFunc

	ButlerVersion       string
	ButlerVersionString string
}

func NewRouter(dbPool *sqlite.Pool, getClient GetClientFunc) *Router {
	return &Router{
		Handlers:             make(map[string]RequestHandler),
		NotificationHandlers: make(map[string]NotificationHandler),
		CancelFuncs: &CancelFuncs{
			Funcs: make(map[string]context.CancelFunc),
		},
		dbPool:    dbPool,
		getClient: getClient,
	}
}

func (r *Router) Register(method string, rh RequestHandler) {
	if _, ok := r.Handlers[method]; ok {
		panic(fmt.Sprintf("Can't register handler twice for %s", method))
	}
	r.Handlers[method] = rh
}

func (r *Router) RegisterNotification(method string, nh NotificationHandler) {
	if _, ok := r.NotificationHandlers[method]; ok {
		panic(fmt.Sprintf("Can't register handler twice for %s", method))
	}
	r.NotificationHandlers[method] = nh
}

func (r *Router) Dispatch(ctx context.Context, origConn *jsonrpc2.Conn, req *jsonrpc2.Request) {
	method := req.Method
	var res interface{}

	conn := &JsonRPC2Conn{origConn}
	consumer, cErr := NewStateConsumer(&NewStateConsumerParams{
		Ctx:  ctx,
		Conn: conn,
	})
	if cErr != nil {
		return
	}

	err := func() (err error) {
		defer func() {
			if r := recover(); r != nil {
				if rErr, ok := r.(error); ok {
					err = errors.WithStack(rErr)
				} else {
					err = errors.Errorf("panic: %v", r)
				}
			}
		}()

		rc := &RequestContext{
			Ctx:         ctx,
			Consumer:    consumer,
			Params:      req.Params,
			Conn:        conn,
			CancelFuncs: r.CancelFuncs,
			dbPool:      r.dbPool,
			Client:      r.getClient,

			ButlerVersion:       r.ButlerVersion,
			ButlerVersionString: r.ButlerVersionString,

			origConn: origConn,
			method:   method,
		}

		if req.Notif {
			if nh, ok := r.NotificationHandlers[req.Method]; ok {
				nh(rc)
			}
		} else {
			if h, ok := r.Handlers[method]; ok {
				rc.Consumer.OnProgress = func(alpha float64) {
					if rc.tracker == nil {
						// skip
						return
					}

					rc.tracker.SetProgress(alpha)
					notif := ProgressNotification{
						Progress: alpha,
						ETA:      rc.tracker.ETA().Seconds(),
						BPS:      rc.tracker.BPS(),
					}
					// cannot use autogenerated wrappers to avoid import cycles
					rc.Notify("Progress", notif)
				}
				rc.Consumer.OnProgressLabel = func(label string) {
					// muffin
				}
				rc.Consumer.OnPauseProgress = func() {
					if rc.tracker != nil {
						rc.tracker.Pause()
					}
				}
				rc.Consumer.OnResumeProgress = func() {
					if rc.tracker != nil {
						rc.tracker.Resume()
					}
				}

				res, err = h(rc)
			} else {
				err = &RpcError{
					Code:    jsonrpc2.CodeMethodNotFound,
					Message: fmt.Sprintf("Method '%s' not found", req.Method),
				}
			}
		}
		return
	}()

	if req.Notif {
		return
	}

	if err == nil {
		err = origConn.Reply(ctx, req.ID, res)
		if err != nil {
			consumer.Errorf("Error while replying: %s", err.Error())
		}
		return
	}

	var code int64
	var message string
	var data map[string]interface{}

	if ee, ok := AsButlerdError(err); ok {
		code = ee.RpcErrorCode()
		message = ee.RpcErrorMessage()
		data = ee.RpcErrorData()
	} else {
		if neterr.IsNetworkError(err) {
			code = int64(CodeNetworkDisconnected)
			message = CodeNetworkDisconnected.Error()
		} else if errors.Cause(err) == werrors.ErrCancelled {
			code = int64(CodeOperationCancelled)
			message = CodeOperationCancelled.Error()
		} else {
			code = jsonrpc2.CodeInternalError
			message = err.Error()
		}
	}

	var rawData *json.RawMessage
	if data == nil {
		data = make(map[string]interface{})
	}
	data["stack"] = fmt.Sprintf("%+v", err)
	data["butlerVersion"] = r.ButlerVersionString

	marshalledData, marshalErr := json.Marshal(data)
	if marshalErr == nil {
		rawMessage := json.RawMessage(marshalledData)
		rawData = &rawMessage
	}

	origConn.ReplyWithError(ctx, req.ID, &jsonrpc2.Error{
		Code:    code,
		Message: message,
		Data:    rawData,
	})
}

type RequestContext struct {
	Ctx         context.Context
	Consumer    *state.Consumer
	Params      *json.RawMessage
	Conn        Conn
	CancelFuncs *CancelFuncs
	dbPool      *sqlite.Pool
	Client      GetClientFunc

	ButlerVersion       string
	ButlerVersionString string

	notificationInterceptors map[string]NotificationInterceptor
	tracker                  *progress.Tracker

	method   string
	origConn *jsonrpc2.Conn
}

type WithParamsFunc func() (interface{}, error)

type NotificationInterceptor func(method string, params interface{}) error

func (rc *RequestContext) Call(method string, params interface{}, res interface{}) error {
	return rc.Conn.Call(rc.Ctx, method, params, res)
}

func (rc *RequestContext) InterceptNotification(method string, interceptor NotificationInterceptor) {
	if rc.notificationInterceptors == nil {
		rc.notificationInterceptors = make(map[string]NotificationInterceptor)
	}
	rc.notificationInterceptors[method] = interceptor
}

func (rc *RequestContext) StopInterceptingNotification(method string) {
	if rc.notificationInterceptors == nil {
		return
	}
	delete(rc.notificationInterceptors, method)
}

func (rc *RequestContext) Notify(method string, params interface{}) error {
	if rc.notificationInterceptors != nil {
		if ni, ok := rc.notificationInterceptors[method]; ok {
			return ni(method, params)
		}
	}
	return rc.Conn.Notify(rc.Ctx, method, params)
}

func (rc *RequestContext) RootClient() *itchio.Client {
	return rc.Client("<keyless>")
}

func (rc *RequestContext) ProfileClient(profileID int64) (*models.Profile, *itchio.Client) {
	if profileID == 0 {
		panic(errors.New("profileId must be non-zero"))
	}

	conn := rc.GetConn()
	defer rc.PutConn(conn)

	profile := models.ProfileByID(conn, profileID)
	if profile == nil {
		panic(errors.Errorf("Could not find profile %d", profileID))
	}

	if profile.APIKey == "" {
		panic(errors.Errorf("Profile %d lacks API key", profileID))
	}

	return profile, rc.Client(profile.APIKey)
}

func (rc *RequestContext) StartProgress() {
	rc.StartProgressWithTotalBytes(0)
}

func (rc *RequestContext) StartProgressWithTotalBytes(totalBytes int64) {
	rc.StartProgressWithInitialAndTotal(0.0, totalBytes)
}

func (rc *RequestContext) StartProgressWithInitialAndTotal(initialProgress float64, totalBytes int64) {
	if rc.tracker != nil {
		rc.Consumer.Warnf("Asked to start progress but already tracking progress!")
		return
	}

	rc.tracker = progress.NewTracker()
	rc.tracker.SetSilent(true)
	rc.tracker.SetProgress(initialProgress)
	rc.tracker.SetTotalBytes(totalBytes)
	rc.tracker.Start()
}

func (rc *RequestContext) EndProgress() {
	if rc.tracker != nil {
		rc.tracker.Finish()
		rc.tracker = nil
	} else {
		rc.Consumer.Warnf("Asked to stop progress but wasn't tracking progress!")
	}
}

func (rc *RequestContext) GetConn() *sqlite.Conn {
	getCtx, cancel := context.WithTimeout(rc.Ctx, 3*time.Second)
	defer cancel()
	conn := rc.dbPool.Get(getCtx.Done())
	if conn != nil {
		panic(errors.WithStack(CodeDatabaseBusy))
	}

	// this works around an issue with sqlite3_interrupt()
	// I (amos) have spent a lot of time trying to figure out
	// what it is, and I wasn't able to. If you want to mess
	// with it, knock yourself out:
	//
	// => https://github.com/fasterthanlime/chaoe
	//
	conn.SetInterrupt(context.Background().Done())
	return conn
}

func (rc *RequestContext) PutConn(conn *sqlite.Conn) {
	rc.dbPool.Put(conn)
}

func (rc *RequestContext) WithConn(f func(conn *sqlite.Conn)) {
	conn := rc.GetConn()
	defer rc.PutConn(conn)
	f(conn)
}

func (rc *RequestContext) WithConnBool(f func(conn *sqlite.Conn) bool) bool {
	conn := rc.GetConn()
	defer rc.PutConn(conn)
	return f(conn)
}

type CancelFuncs struct {
	Funcs map[string]context.CancelFunc
}

func (cf *CancelFuncs) Add(id string, f context.CancelFunc) {
	cf.Funcs[id] = f
}

func (cf *CancelFuncs) Remove(id string) {
	delete(cf.Funcs, id)
}

func (cf *CancelFuncs) Call(id string) bool {
	if f, ok := cf.Funcs[id]; ok {
		f()
		delete(cf.Funcs, id)
		return true
	}

	return false
}
<|endoftext|>


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

package sdb

import (
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"testing"
)

func TestPut(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// PutAttributes
////////////////////////////////////////////////////////////////////////

type PutTest struct {
	domainTest

	item ItemName
	updates []PutUpdate
	preconditions []Precondition

	err error
}

func init() { RegisterTestSuite(&PutTest{}) }

func (t *PutTest) SetUp(i *TestInfo) {
	// Call common setup code.
	t.domainTest.SetUp(i)

	// Make the request legal by default.
	t.item = "foo"
	t.updates = []PutUpdate{PutUpdate{"bar", "baz", false}}
}

func (t *PutTest) callDomain() {
	t.err = t.domain.PutAttributes(t.item, t.updates, t.preconditions)
}

func (t *PutTest) EmptyItemName() {
	t.item = ""

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("Invalid")))
	ExpectThat(t.err, Error(HasSubstr("item name")))
}

func (t *PutTest) InvalidItemName() {
	t.item = "taco\x80\x81\x82"

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("Invalid")))
	ExpectThat(t.err, Error(HasSubstr("item name")))
	ExpectThat(t.err, Error(HasSubstr(string(t.item))))
}

func (t *PutTest) ZeroUpdates() {
	t.updates = []PutUpdate{}

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("number")))
	ExpectThat(t.err, Error(HasSubstr("updates")))
	ExpectThat(t.err, Error(HasSubstr("0")))
}

func (t *PutTest) TooManyUpdates() {
	t.updates = make([]PutUpdate, 257)

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("number")))
	ExpectThat(t.err, Error(HasSubstr("updates")))
	ExpectThat(t.err, Error(HasSubstr("256")))
}

func (t *PutTest) OneAttributeNameEmpty() {
	t.updates = []PutUpdate{
		PutUpdate{Name: "foo"},
		PutUpdate{Name: "", Value: "taco"},
		PutUpdate{Name: "bar"},
	}

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("Invalid")))
	ExpectThat(t.err, Error(HasSubstr("attribute")))
	ExpectThat(t.err, Error(HasSubstr("name")))
	ExpectThat(t.err, Error(HasSubstr("taco")))
}

func (t *PutTest) OneAttributeNameInvalid() {
	t.updates = []PutUpdate{
		PutUpdate{Name: "foo"},
		PutUpdate{Name: "taco\x80\x81\x82"},
		PutUpdate{Name: "bar"},
	}

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("Invalid")))
	ExpectThat(t.err, Error(HasSubstr("attribute")))
	ExpectThat(t.err, Error(HasSubstr("name")))
	ExpectThat(t.err, Error(HasSubstr(t.updates[1].Name)))
}

func (t *PutTest) OneAttributeValueInvalid() {
	t.updates = []PutUpdate{
		PutUpdate{Name: "foo"},
		PutUpdate{Name: "bar", Value: "taco\x80\x81\x82"},
		PutUpdate{Name: "baz"},
	}

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("Invalid")))
	ExpectThat(t.err, Error(HasSubstr("attribute")))
	ExpectThat(t.err, Error(HasSubstr("value")))
	ExpectThat(t.err, Error(HasSubstr(t.updates[1].Value)))
}

func (t *PutTest) OnePreconditionNameEmpty() {
	t.preconditions = []Precondition{
		Precondition{Name: "foo", Exists: new(bool)},
		Precondition{Name: "", Exists: new(bool)},
		Precondition{Name: "baz", Exists: new(bool)},
	}

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("Invalid")))
	ExpectThat(t.err, Error(HasSubstr("attribute")))
	ExpectThat(t.err, Error(HasSubstr("name")))
}

func (t *PutTest) OnePreconditionNameInvalid() {
	t.preconditions = []Precondition{
		Precondition{Name: "foo", Exists: new(bool)},
		Precondition{Name: "taco\x80\x81\x82", Exists: new(bool)},
		Precondition{Name: "baz", Exists: new(bool)},
	}

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("Invalid")))
	ExpectThat(t.err, Error(HasSubstr("attribute")))
	ExpectThat(t.err, Error(HasSubstr("name")))
	ExpectThat(t.err, Error(HasSubstr(t.preconditions[1].Name)))
}

func (t *PutTest) OnePreconditionValueInvalid() {
	ExpectEq("TODO", "")
}

func (t *PutTest) NoPreconditions() {
	ExpectEq("TODO", "")
}

func (t *PutTest) SomePreconditions() {
	ExpectEq("TODO", "")
}

func (t *PutTest) ConnReturnsError() {
	ExpectEq("TODO", "")
}

func (t *PutTest) ConnSaysOkay() {
	ExpectEq("TODO", "")
}

////////////////////////////////////////////////////////////////////////
// BatchPutAttributes
////////////////////////////////////////////////////////////////////////

type BatchPutTest struct {
	domainTest
}

func init() { RegisterTestSuite(&BatchPutTest{}) }

func (t *BatchPutTest) DoesFoo() {
	ExpectEq("TODO", "")
}
<commit_msg>PutTest.OnePreconditionValueInvalid<commit_after>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sdb

import (
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"testing"
)

func TestPut(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// PutAttributes
////////////////////////////////////////////////////////////////////////

type PutTest struct {
	domainTest

	item ItemName
	updates []PutUpdate
	preconditions []Precondition

	err error
}

func init() { RegisterTestSuite(&PutTest{}) }

func (t *PutTest) SetUp(i *TestInfo) {
	// Call common setup code.
	t.domainTest.SetUp(i)

	// Make the request legal by default.
	t.item = "foo"
	t.updates = []PutUpdate{PutUpdate{"bar", "baz", false}}
}

func (t *PutTest) callDomain() {
	t.err = t.domain.PutAttributes(t.item, t.updates, t.preconditions)
}

func (t *PutTest) EmptyItemName() {
	t.item = ""

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("Invalid")))
	ExpectThat(t.err, Error(HasSubstr("item name")))
}

func (t *PutTest) InvalidItemName() {
	t.item = "taco\x80\x81\x82"

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("Invalid")))
	ExpectThat(t.err, Error(HasSubstr("item name")))
	ExpectThat(t.err, Error(HasSubstr(string(t.item))))
}

func (t *PutTest) ZeroUpdates() {
	t.updates = []PutUpdate{}

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("number")))
	ExpectThat(t.err, Error(HasSubstr("updates")))
	ExpectThat(t.err, Error(HasSubstr("0")))
}

func (t *PutTest) TooManyUpdates() {
	t.updates = make([]PutUpdate, 257)

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("number")))
	ExpectThat(t.err, Error(HasSubstr("updates")))
	ExpectThat(t.err, Error(HasSubstr("256")))
}

func (t *PutTest) OneAttributeNameEmpty() {
	t.updates = []PutUpdate{
		PutUpdate{Name: "foo"},
		PutUpdate{Name: "", Value: "taco"},
		PutUpdate{Name: "bar"},
	}

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("Invalid")))
	ExpectThat(t.err, Error(HasSubstr("attribute")))
	ExpectThat(t.err, Error(HasSubstr("name")))
	ExpectThat(t.err, Error(HasSubstr("taco")))
}

func (t *PutTest) OneAttributeNameInvalid() {
	t.updates = []PutUpdate{
		PutUpdate{Name: "foo"},
		PutUpdate{Name: "taco\x80\x81\x82"},
		PutUpdate{Name: "bar"},
	}

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("Invalid")))
	ExpectThat(t.err, Error(HasSubstr("attribute")))
	ExpectThat(t.err, Error(HasSubstr("name")))
	ExpectThat(t.err, Error(HasSubstr(t.updates[1].Name)))
}

func (t *PutTest) OneAttributeValueInvalid() {
	t.updates = []PutUpdate{
		PutUpdate{Name: "foo"},
		PutUpdate{Name: "bar", Value: "taco\x80\x81\x82"},
		PutUpdate{Name: "baz"},
	}

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("Invalid")))
	ExpectThat(t.err, Error(HasSubstr("attribute")))
	ExpectThat(t.err, Error(HasSubstr("value")))
	ExpectThat(t.err, Error(HasSubstr(t.updates[1].Value)))
}

func (t *PutTest) OnePreconditionNameEmpty() {
	t.preconditions = []Precondition{
		Precondition{Name: "foo", Exists: new(bool)},
		Precondition{Name: "", Exists: new(bool)},
		Precondition{Name: "baz", Exists: new(bool)},
	}

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("Invalid")))
	ExpectThat(t.err, Error(HasSubstr("attribute")))
	ExpectThat(t.err, Error(HasSubstr("name")))
}

func (t *PutTest) OnePreconditionNameInvalid() {
	t.preconditions = []Precondition{
		Precondition{Name: "foo", Exists: new(bool)},
		Precondition{Name: "taco\x80\x81\x82", Exists: new(bool)},
		Precondition{Name: "baz", Exists: new(bool)},
	}

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("Invalid")))
	ExpectThat(t.err, Error(HasSubstr("attribute")))
	ExpectThat(t.err, Error(HasSubstr("name")))
	ExpectThat(t.err, Error(HasSubstr(t.preconditions[1].Name)))
}

func (t *PutTest) OnePreconditionValueInvalid() {
	t.preconditions = []Precondition{
		Precondition{Name: "foo", Value: new(string)},
		Precondition{Name: "bar", Value: new(string)},
		Precondition{Name: "baz", Value: new(string)},
	}

	*t.preconditions[0].Value = ""
	*t.preconditions[1].Value = "taco\x80\x81\x82"
	*t.preconditions[2].Value = "qux"

	// Call
	t.callDomain()

	ExpectThat(t.err, Error(HasSubstr("Invalid")))
	ExpectThat(t.err, Error(HasSubstr("attribute")))
	ExpectThat(t.err, Error(HasSubstr("value")))
	ExpectThat(t.err, Error(HasSubstr(*t.preconditions[1].Value)))
}

func (t *PutTest) OnePreconditionMissingOperand() {
	ExpectEq("TODO", "")
}

func (t *PutTest) OnePreconditionHasTwoOperands() {
	ExpectEq("TODO", "")
}

func (t *PutTest) NoPreconditions() {
	ExpectEq("TODO", "")
}

func (t *PutTest) SomePreconditions() {
	ExpectEq("TODO", "")
}

func (t *PutTest) ConnReturnsError() {
	ExpectEq("TODO", "")
}

func (t *PutTest) ConnSaysOkay() {
	ExpectEq("TODO", "")
}

////////////////////////////////////////////////////////////////////////
// BatchPutAttributes
////////////////////////////////////////////////////////////////////////

type BatchPutTest struct {
	domainTest
}

func init() { RegisterTestSuite(&BatchPutTest{}) }

func (t *BatchPutTest) DoesFoo() {
	ExpectEq("TODO", "")
}
<|endoftext|>