<commit_before>package unit

import (
	"fmt"
	"github.com/timeredbull/tsuru/log"
	"os/exec"
	"strconv"
	"strings"
)

type Unit struct {
	Type    string
	Name    string
	Machine int
}

func (u *Unit) Create() error {
	cmd := exec.Command("juju", "deploy", "--repository=/home/charms", "local:"+u.Type, u.Name)
	log.Printf("deploying %s with name %s", u.Type, u.Name)
	return cmd.Start()
}

func (u *Unit) Destroy() error {
	cmd := exec.Command("juju", "destroy-service", u.Name)
	log.Printf("destroying %s with name %s", u.Type, u.Name)
	return cmd.Start()
}

func (u *Unit) AddRelation(su *Unit) error {
	cmd := exec.Command("juju", "add-relation", u.Name, su.Name)
	log.Printf("relating %s with service %s", u.Name, su.Name)
	return cmd.Start()
}

func (u *Unit) RemoveRelation(su *Unit) error {
	cmd := exec.Command("juju", "remove-relation", u.Name, su.Name)
	log.Printf("unrelating %s with service %s", u.Name, su.Name)
	return cmd.Start()
}

func (u *Unit) Command(cmds ...string) ([]byte, error) {
	c := exec.Command("juju", "ssh", "-o", "StrictHostKeyChecking no", strconv.Itoa(u.Machine))
	for _, cmd := range cmds {
		c.Args = append(c.Args, cmd)
	}
	log.Printf("executing %s on %s", strings.Join(cmds, " "), u.Name)
	return c.CombinedOutput()
}

func (u *Unit) SendFile(srcPath, dstPath string) error {
	cmd := exec.Command("juju", "scp", "-r", "-o", "StrictHostKeyChecking no", srcPath, u.Name+"/0:"+dstPath)
	log.Printf("sending %s to %s on %s", srcPath, dstPath, u.Name)
	return cmd.Start()
}

func (u *Unit) ExecuteHook(hook string) error {
	cmd := fmt.Sprintf("/var/lib/tsuru/hooks/%s", hook)
	output, err := u.Command(cmd)
	log.Print(string(output))
	return err
}
<commit_msg>api/unit: refactoring Command function<commit_after>package unit

import (
	"fmt"
	"github.com/timeredbull/tsuru/log"
	"os/exec"
	"strconv"
	"strings"
)

type Unit struct {
	Type    string
	Name    string
	Machine int
}

func (u *Unit) Create() error {
	cmd := exec.Command("juju", "deploy", "--repository=/home/charms", "local:"+u.Type, u.Name)
	log.Printf("deploying %s with name %s", u.Type, u.Name)
	return cmd.Start()
}

func (u *Unit) Destroy() error {
	cmd := exec.Command("juju", "destroy-service", u.Name)
	log.Printf("destroying %s with name %s", u.Type, u.Name)
	return cmd.Start()
}

func (u *Unit) AddRelation(su *Unit) error {
	cmd := exec.Command("juju", "add-relation", u.Name, su.Name)
	log.Printf("relating %s with service %s", u.Name, su.Name)
	return cmd.Start()
}

func (u *Unit) RemoveRelation(su *Unit) error {
	cmd := exec.Command("juju", "remove-relation", u.Name, su.Name)
	log.Printf("unrelating %s with service %s", u.Name, su.Name)
	return cmd.Start()
}

func (u *Unit) Command(cmds ...string) ([]byte, error) {
	c := exec.Command("juju", "ssh", "-o", "StrictHostKeyChecking no", strconv.Itoa(u.Machine))
	c.Args = append(c.Args, cmds...)
	log.Printf("executing %s on %s", strings.Join(cmds, " "), u.Name)
	return c.CombinedOutput()
}

func (u *Unit) SendFile(srcPath, dstPath string) error {
	cmd := exec.Command("juju", "scp", "-r", "-o", "StrictHostKeyChecking no", srcPath, u.Name+"/0:"+dstPath)
	log.Printf("sending %s to %s on %s", srcPath, dstPath, u.Name)
	return cmd.Start()
}

func (u *Unit) ExecuteHook(hook string) error {
	cmd := fmt.Sprintf("/var/lib/tsuru/hooks/%s", hook)
	output, err := u.Command(cmd)
	log.Print(string(output))
	return err
}
<|endoftext|>


<commit_before>// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Worksheet API support - Fetch, Create, Update, Delete, and Search
// See: https://login.circonus.com/resources/api/calls/worksheet

package api

import (
	"encoding/json"
	"fmt"
	"net/url"
	"regexp"

	"github.com/circonus-labs/circonus-gometrics/api/config"
)

// WorksheetGraph defines a worksheet cid to be include in the worksheet
type WorksheetGraph struct {
	GraphCID string `json:"graph"` // string
}

// WorksheetSmartQuery defines a query to include multiple worksheets
type WorksheetSmartQuery struct {
	Name  string   `json:"name"`
	Order []string `json:"order"`
	Query string   `json:"query"`
}

// Worksheet defines a worksheet. See https://login.circonus.com/resources/api/calls/worksheet for more information.
type Worksheet struct {
	CID          string                `json:"_cid,omitempty"` // string
	Description  *string               `json:"description"`    // string or null
	Favorite     bool                  `json:"favorite"`       // boolean
	Graphs       []WorksheetGraph      `json:"graphs"`         // [] len >= 0
	Notes        *string               `json:"notes"`          // string or null
	SmartQueries []WorksheetSmartQuery `json:"smart_queries"`  // [] len >= 0
	Tags         []string              `json:"tags"`           // [] len >= 0
	Title        string                `json:"title"`          // string
}

// NewWorksheet returns a new Worksheet (with defaults, if applicable)
func NewWorksheet() *Worksheet {
	return &Worksheet{}
}

// FetchWorksheet retrieves worksheet with passed cid.
func (a *API) FetchWorksheet(cid CIDType) (*Worksheet, error) {
	if cid == nil || *cid == "" {
		return nil, fmt.Errorf("Invalid worksheet CID [none]")
	}

	worksheetCID := string(*cid)

	matched, err := regexp.MatchString(config.WorksheetCIDRegex, worksheetCID)
	if err != nil {
		return nil, err
	}
	if !matched {
		return nil, fmt.Errorf("Invalid worksheet CID [%s]", worksheetCID)
	}

	result, err := a.Get(string(*cid))
	if err != nil {
		return nil, err
	}

	if a.Debug {
		a.Log.Printf("[DEBUG] fetch worksheet, received JSON: %s", string(result))
	}

	worksheet := new(Worksheet)
	if err := json.Unmarshal(result, worksheet); err != nil {
		return nil, err
	}

	return worksheet, nil
}

// FetchWorksheets retrieves all worksheets available to API Token.
func (a *API) FetchWorksheets() (*[]Worksheet, error) {
	result, err := a.Get(config.WorksheetPrefix)
	if err != nil {
		return nil, err
	}

	var worksheets []Worksheet
	if err := json.Unmarshal(result, &worksheets); err != nil {
		return nil, err
	}

	return &worksheets, nil
}

// UpdateWorksheet updates passed worksheet.
func (a *API) UpdateWorksheet(cfg *Worksheet) (*Worksheet, error) {
	if cfg == nil {
		return nil, fmt.Errorf("Invalid worksheet config [nil]")
	}

	worksheetCID := string(cfg.CID)

	matched, err := regexp.MatchString(config.WorksheetCIDRegex, worksheetCID)
	if err != nil {
		return nil, err
	}
	if !matched {
		return nil, fmt.Errorf("Invalid worksheet CID [%s]", worksheetCID)
	}

	jsonCfg, err := json.Marshal(cfg)
	if err != nil {
		return nil, err
	}

	if a.Debug {
		a.Log.Printf("[DEBUG] update worksheet, sending JSON: %s", string(jsonCfg))
	}

	result, err := a.Put(worksheetCID, jsonCfg)
	if err != nil {
		return nil, err
	}

	worksheet := &Worksheet{}
	if err := json.Unmarshal(result, worksheet); err != nil {
		return nil, err
	}

	return worksheet, nil
}

// CreateWorksheet creates a new worksheet.
func (a *API) CreateWorksheet(cfg *Worksheet) (*Worksheet, error) {
	if cfg == nil {
		return nil, fmt.Errorf("Invalid worksheet config [nil]")
	}

	jsonCfg, err := json.Marshal(cfg)
	if err != nil {
		return nil, err
	}

	if a.Debug {
		a.Log.Printf("[DEBUG] create annotation, sending JSON: %s", string(jsonCfg))
	}

	result, err := a.Post(config.WorksheetPrefix, jsonCfg)
	if err != nil {
		return nil, err
	}

	worksheet := &Worksheet{}
	if err := json.Unmarshal(result, worksheet); err != nil {
		return nil, err
	}

	return worksheet, nil
}

// DeleteWorksheet deletes passed worksheet.
func (a *API) DeleteWorksheet(cfg *Worksheet) (bool, error) {
	if cfg == nil {
		return false, fmt.Errorf("Invalid worksheet config [nil]")
	}
	return a.DeleteWorksheetByCID(CIDType(&cfg.CID))
}

// DeleteWorksheetByCID deletes worksheet with passed cid.
func (a *API) DeleteWorksheetByCID(cid CIDType) (bool, error) {
	if cid == nil || *cid == "" {
		return false, fmt.Errorf("Invalid worksheet CID [none]")
	}

	worksheetCID := string(*cid)

	matched, err := regexp.MatchString(config.WorksheetCIDRegex, worksheetCID)
	if err != nil {
		return false, err
	}
	if !matched {
		return false, fmt.Errorf("Invalid worksheet CID [%s]", worksheetCID)
	}

	_, err = a.Delete(worksheetCID)
	if err != nil {
		return false, err
	}

	return true, nil
}

// SearchWorksheets returns worksheets matching the specified search
// query and/or filter. If nil is passed for both parameters all
// worksheets will be returned.
func (a *API) SearchWorksheets(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Worksheet, error) {
	q := url.Values{}

	if searchCriteria != nil && *searchCriteria != "" {
		q.Set("search", string(*searchCriteria))
	}

	if filterCriteria != nil && len(*filterCriteria) > 0 {
		for filter, criteria := range *filterCriteria {
			for _, val := range criteria {
				q.Add(filter, val)
			}
		}
	}

	if q.Encode() == "" {
		return a.FetchWorksheets()
	}

	reqURL := url.URL{
		Path:     config.WorksheetPrefix,
		RawQuery: q.Encode(),
	}

	result, err := a.Get(reqURL.String())
	if err != nil {
		return nil, fmt.Errorf("[ERROR] API call error %+v", err)
	}

	var worksheets []Worksheet
	if err := json.Unmarshal(result, &worksheets); err != nil {
		return nil, err
	}

	return &worksheets, nil
}
<commit_msg>fix: smart_queries is an optional attribute, graphs is a required attribute<commit_after>// Copyright 2016 Circonus, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Worksheet API support - Fetch, Create, Update, Delete, and Search
// See: https://login.circonus.com/resources/api/calls/worksheet

package api

import (
	"encoding/json"
	"fmt"
	"net/url"
	"regexp"

	"github.com/circonus-labs/circonus-gometrics/api/config"
)

// WorksheetGraph defines a worksheet cid to be include in the worksheet
type WorksheetGraph struct {
	GraphCID string `json:"graph"` // string
}

// WorksheetSmartQuery defines a query to include multiple worksheets
type WorksheetSmartQuery struct {
	Name  string   `json:"name"`
	Order []string `json:"order"`
	Query string   `json:"query"`
}

// Worksheet defines a worksheet. See https://login.circonus.com/resources/api/calls/worksheet for more information.
type Worksheet struct {
	CID          string                `json:"_cid,omitempty"`          // string
	Description  *string               `json:"description"`             // string or null
	Favorite     bool                  `json:"favorite"`                // boolean
	Graphs       []WorksheetGraph      `json:"graphs"`                  // [] len >= 0
	Notes        *string               `json:"notes"`                   // string or null
	SmartQueries []WorksheetSmartQuery `json:"smart_queries,omitempty"` // [] len >= 0
	Tags         []string              `json:"tags"`                    // [] len >= 0
	Title        string                `json:"title"`                   // string
}

// NewWorksheet returns a new Worksheet (with defaults, if applicable)
func NewWorksheet() *Worksheet {
	return &Worksheet{
		Graphs: []WorksheetGraph{}, // graphs is a required attribute and cannot be null
	}
}

// FetchWorksheet retrieves worksheet with passed cid.
func (a *API) FetchWorksheet(cid CIDType) (*Worksheet, error) {
	if cid == nil || *cid == "" {
		return nil, fmt.Errorf("Invalid worksheet CID [none]")
	}

	worksheetCID := string(*cid)

	matched, err := regexp.MatchString(config.WorksheetCIDRegex, worksheetCID)
	if err != nil {
		return nil, err
	}
	if !matched {
		return nil, fmt.Errorf("Invalid worksheet CID [%s]", worksheetCID)
	}

	result, err := a.Get(string(*cid))
	if err != nil {
		return nil, err
	}

	if a.Debug {
		a.Log.Printf("[DEBUG] fetch worksheet, received JSON: %s", string(result))
	}

	worksheet := new(Worksheet)
	if err := json.Unmarshal(result, worksheet); err != nil {
		return nil, err
	}

	return worksheet, nil
}

// FetchWorksheets retrieves all worksheets available to API Token.
func (a *API) FetchWorksheets() (*[]Worksheet, error) {
	result, err := a.Get(config.WorksheetPrefix)
	if err != nil {
		return nil, err
	}

	var worksheets []Worksheet
	if err := json.Unmarshal(result, &worksheets); err != nil {
		return nil, err
	}

	return &worksheets, nil
}

// UpdateWorksheet updates passed worksheet.
func (a *API) UpdateWorksheet(cfg *Worksheet) (*Worksheet, error) {
	if cfg == nil {
		return nil, fmt.Errorf("Invalid worksheet config [nil]")
	}

	worksheetCID := string(cfg.CID)

	matched, err := regexp.MatchString(config.WorksheetCIDRegex, worksheetCID)
	if err != nil {
		return nil, err
	}
	if !matched {
		return nil, fmt.Errorf("Invalid worksheet CID [%s]", worksheetCID)
	}

	jsonCfg, err := json.Marshal(cfg)
	if err != nil {
		return nil, err
	}

	if a.Debug {
		a.Log.Printf("[DEBUG] update worksheet, sending JSON: %s", string(jsonCfg))
	}

	result, err := a.Put(worksheetCID, jsonCfg)
	if err != nil {
		return nil, err
	}

	worksheet := &Worksheet{}
	if err := json.Unmarshal(result, worksheet); err != nil {
		return nil, err
	}

	return worksheet, nil
}

// CreateWorksheet creates a new worksheet.
func (a *API) CreateWorksheet(cfg *Worksheet) (*Worksheet, error) {
	if cfg == nil {
		return nil, fmt.Errorf("Invalid worksheet config [nil]")
	}

	jsonCfg, err := json.Marshal(cfg)
	if err != nil {
		return nil, err
	}

	if a.Debug {
		a.Log.Printf("[DEBUG] create annotation, sending JSON: %s", string(jsonCfg))
	}

	result, err := a.Post(config.WorksheetPrefix, jsonCfg)
	if err != nil {
		return nil, err
	}

	worksheet := &Worksheet{}
	if err := json.Unmarshal(result, worksheet); err != nil {
		return nil, err
	}

	return worksheet, nil
}

// DeleteWorksheet deletes passed worksheet.
func (a *API) DeleteWorksheet(cfg *Worksheet) (bool, error) {
	if cfg == nil {
		return false, fmt.Errorf("Invalid worksheet config [nil]")
	}
	return a.DeleteWorksheetByCID(CIDType(&cfg.CID))
}

// DeleteWorksheetByCID deletes worksheet with passed cid.
func (a *API) DeleteWorksheetByCID(cid CIDType) (bool, error) {
	if cid == nil || *cid == "" {
		return false, fmt.Errorf("Invalid worksheet CID [none]")
	}

	worksheetCID := string(*cid)

	matched, err := regexp.MatchString(config.WorksheetCIDRegex, worksheetCID)
	if err != nil {
		return false, err
	}
	if !matched {
		return false, fmt.Errorf("Invalid worksheet CID [%s]", worksheetCID)
	}

	_, err = a.Delete(worksheetCID)
	if err != nil {
		return false, err
	}

	return true, nil
}

// SearchWorksheets returns worksheets matching the specified search
// query and/or filter. If nil is passed for both parameters all
// worksheets will be returned.
func (a *API) SearchWorksheets(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Worksheet, error) {
	q := url.Values{}

	if searchCriteria != nil && *searchCriteria != "" {
		q.Set("search", string(*searchCriteria))
	}

	if filterCriteria != nil && len(*filterCriteria) > 0 {
		for filter, criteria := range *filterCriteria {
			for _, val := range criteria {
				q.Add(filter, val)
			}
		}
	}

	if q.Encode() == "" {
		return a.FetchWorksheets()
	}

	reqURL := url.URL{
		Path:     config.WorksheetPrefix,
		RawQuery: q.Encode(),
	}

	result, err := a.Get(reqURL.String())
	if err != nil {
		return nil, fmt.Errorf("[ERROR] API call error %+v", err)
	}

	var worksheets []Worksheet
	if err := json.Unmarshal(result, &worksheets); err != nil {
		return nil, err
	}

	return &worksheets, nil
}
<|endoftext|>


<commit_before>package apidef

const Schema = `{
    "type": ["object", "null"],
    "$schema": "http://json-schema.org/draft-04/schema",
    "id": "http://jsonschema.net",
    "additionalProperties": false,
    "properties": {
        "is_site": {
            "type": "boolean"
        },
        "uptime_tests": {
            "type": ["object", "null"]
        },
        "expire_analytics_after": {
            "type": "number"
        },
        "id": {
            "type": "string"
        },
        "org_id": {
            "type": "string"
        },
        "api_id": {
            "type": "string"
        },
        "enable_ip_whitelisting": {
            "type": "boolean"
        },
        "enable_ip_blacklisting": {
            "type": "boolean"
        },
        "enable_context_vars": {
            "type": "boolean"
        },
        "strip_auth_data": {
          "type": "boolean"
        },
        "do_not_track": {
            "type": "boolean"
        },
        "enable_jwt": {
            "type": "boolean"
        },
        "use_openid": {
            "type": "boolean"
        },
        "openid_options": {
            "type": ["object", "null"]
        },
        "use_standard_auth": {
            "type": "boolean"
        },
        "use_go_plugin_auth": {
            "type": "boolean"
        },
        "enable_coprocess_auth": {
            "type": "boolean"
        },
        "jwt_skip_kid": {
            "type": "boolean"
        },
        "base_identity_provided_by": {
            "type": "string"
        },
        "disable_rate_limit": {
            "type": "boolean"
        },
        "disable_quota": {
            "type": "boolean"
        },
        "custom_middleware_bundle": {
            "type": "string"
        },
        "jwt_policy_field_name": {
            "type": "string"
        },
        "jwt_default_policies": {
            "type": ["array", "null"]
        },
        "jwt_signing_method": {
            "type": "string"
        },
        "jwt_source": {
            "type": "string"
        },
        "jwt_identity_base_field": {
            "type": "string"
        },
        "jwt_client_base_field": {
            "type": "string"
        },
        "jwt_disable_issued_at_validation": {
            "type": "boolean"
        },
        "jwt_disable_expires_at_validation": {
            "type": "boolean"
        },
        "jwt_disable_not_before_validation": {
            "type": "boolean"
        },
        "jwt_issued_at_validation_skew": {
            "type": "number"
        },
        "jwt_expires_at_validation_skew": {
            "type": "number"
        },
        "jwt_not_before_validation_skew": {
            "type": "number"
        },
        "jwt_scope_to_policy_mapping": {
            "type": ["object", "null"]
        },
        "jwt_scope_claim_name": {
            "type": "string"
        },
        "use_keyless": {
            "type": "boolean"
        },
        "use_basic_auth": {
            "type": "boolean"
        },
        "use_mutual_tls_auth": {
            "type": "boolean"
        },
        "client_certificates": {
            "type": ["array", "null"]
        },
        "upstream_certificates": {
            "type": ["object", "null"]
        },
        "pinned_public_keys": {
            "type": ["object", "null"]
        },
        "allowed_ips": {
            "type": ["array", "null"]
        },
        "blacklisted_ips": {
            "type": ["array", "null"]
        },
        "enable_batch_request_support": {
            "type": "boolean"
        },
        "event_handlers": {
            "type":["object", "null"]
        },
        "notifications": {
            "type":["object", "null"]
        },
        "use_oauth2": {
            "type": "boolean"
        },
        "oauth_meta": {
            "type":["object", "null"]
        },
        "cache_options": {
            "type":["object", "null"]
        },
        "tags": {
            "type": ["array", "null"]
        },
        "tag_headers": {
            "type": ["array", "null"]
        },
        "basic_auth": {
            "type": ["object", "null"]
        },
        "CORS": {
            "type":["object", "null"]
        },
        "response_processors": {
            "type": ["array", "null"]
        },
        "auth_provider": {
            "type":["object", "null"],
            "properties": {
                "name": {
                    "type": "string",
                    "enum": [""]
                },
                "storage_engine": {
                    "type": "string",
                    "enum": [""]
                }
            }
        },
        "session_provider": {
            "type":["object", "null"],
            "properties": {
                "name": {
                    "type": "string",
                    "enum": [""]
                },
                "storage_engine": {
                    "type": "string",
                    "enum": [""]
                }
            }
        },
        "hmac_allowed_clock_skew": {
            "type": "number"
        },
        "hmac_allowed_algorithms": {
            "type": ["array", "null"]
        },
        "dont_set_quota_on_create": {
            "type": "boolean"
            },
        "custom_middleware": {
            "type":["object", "null"],
            "properties": {
                "pre": {
                    "type": ["array", "null"]
                },
                "post": {
                    "type": ["array", "null"]
                }
            }
        },
        "session_lifetime": {
            "type": "number"
        },
        "enable_detailed_recording": {
            "type": "boolean"
        },
        "enable_signature_checking": {
            "type": "boolean"
        },
        "active": {
            "type": "boolean"
        },
        "internal": {
            "type": "boolean"
        },
        "auth": {
            "type": ["object", "null"],
            "id": "http://jsonschema.net/auth",
            "properties": {
                "auth_header_name": {
                    "type": "string",
                    "id": "http://jsonschema.net/auth/auth_header_name"
                },
                "use_certificate": {
                    "type": "boolean"
                }
            }
        },
		"auth_configs":{
			"type": ["object", "null"]
		},
        "definition": {
            "type": ["object", "null"],
            "id": "http://jsonschema.net/definition",
            "properties": {
                "key": {
                    "type": "string",
                    "id": "http://jsonschema.net/definition/key"
                },
                "location": {
                    "type": "string",
                    "id": "http://jsonschema.net/definition/location"
                },
                "strip_path": {
                    "type": "boolean",
                    "id": "http://jsonschema.net/definition/location"
                }
            },
            "required": [
                "key",
                "location"
            ]
        },
        "name": {
            "type": "string",
            "id": "http://jsonschema.net/name"
        },
        "slug": {
            "type": "string",
            "pattern": "[a-zA-Z0-9]*",
            "id": "http://jsonschema.net/name"
        },
        "domain": {
            "type": "string"
        },
        "listen_port": {
            "type": "number"
        },
        "protocol": {
            "type": "string"
        },
        "enable_proxy_protocol": {
            "type": "boolean"
        },
        "certificates": {
            "type": ["array", "null"]
        },
        "check_host_against_uptime_tests": {
            "type": "boolean"
        },
        "proxy": {
            "type": ["object", "null"],
            "id": "http://jsonschema.net/proxy",
            "properties": {
                "target_url": {
                    "type": "string",
                    "id": "http://jsonschema.net/proxy/target_url"
                },
                "check_host_against_uptime_tests": {
                    "type": "boolean"
                },
                "preserve_host_header": {
                    "type": "boolean"
                },
                "transport": {
                    "type": ["object", "null"],
                    "properties": {
                        "ssl_ciphers": {
                            "type": ["array", "null"]
                        },
                        "ssl_min_version": {
                            "type": "number"
                        },
                        "proxy_url": {
                            "type": "string"
                        },
                        "ssl_force_common_name_check": {
                            "type": "boolean"
                        }
                    }
                }
            },
            "required": [
                "target_url"
            ]
        },
        "hook_references": {
            "type": ["object", "null"]
        },
        "version_data": {
            "type": ["object", "null"],
            "id": "http://jsonschema.net/version_data",
            "properties": {
                "not_versioned": {
                    "type": "boolean",
                    "id": "http://jsonschema.net/version_data/not_versioned"
                },
                "default_version":{
                    "type": "string",
                    "id": "http://jsonschema.net/version_data/default_version"
                },
                "versions": {
                    "type": ["object", "null"],
                    "id": "http://jsonschema.net/version_data/versions",
                    "patternProperties": {
                        "^[a-zA-Z0-9]+$": {
                            "title": "versionInfoProperty",
                            "type": ["object", "null"],
                            "id": "http://jsonschema.net/access_rights/versionInfoProperty",
                            "properties": {
                                "expires": {
                                    "type": "string",
                                    "id": "http://jsonschema.net/version_data/versions/versionInfoProperty/expires"
                                },
                                "name": {
                                    "type": "string",
                                    "id": "http://jsonschema.net/version_data/versions/versionInfoProperty/name"
                                },
                                "paths": {
                                    "type": ["object", "null"],
                                    "id": "http://jsonschema.net/version_data/versions/versionInfoProperty/paths",
                                    "properties": {
                                        "black_list": {
                                            "type": ["array", "null"],
                                            "id": "http://jsonschema.net/version_data/versions/versionInfoProperty/paths/black_list"
                                        },
                                        "ignored": {
                                            "type": ["array", "null"],
                                            "id": "http://jsonschema.net/version_data/versions/versionInfoProperty/paths/ignored"
                                        },
                                        "white_list": {
                                            "type": ["array", "null"],
                                            "id": "http://jsonschema.net/version_data/versions/versionInfoProperty/paths/white_list"
                                        }
                                    }
                                }
                            },
                            "required": [
                                "name"
                            ]
                        }
                    }
                }
            },
            "required": [
                "not_versioned",
                "versions"
            ]
        },
        "config_data": {
            "type": ["object", "null"]
        },
        "global_rate_limit": {
          "type": ["object", "null"],
           "properties": {
                "rate": {
                    "type": "number"
                },
                "per": {
                    "type": "number"
                }
            }
        },
    "request_signing": {
          "type": ["object", "null"],
           "properties": {
                "is_enabled": {
                    "type": "boolean"
                },
                "secret": {
                    "type": "string"
                },
        "key_id": {
                    "type": "string"
                },
        "algorithm": {
                    "type": "string"
                }
            },
        "required": [
            "is_enabled"
        ]
        },
        "graphql": {
            "type": ["object", "null"],
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "api": {
                    "type": "object",
                    "properties": {
                        "endpoint": {
                            "type": "string"
                        },
                        "schema": {
                            "type": "string"
                        },
                        "execution": {
                            "type": "object",
                            "properties": {
                                "mode": {
                                    "type": "string",
                                    "enum": ["proxyOnly"]
                                },
                                "validation": {
                                    "type": "object",
                                    "properties": {
                                        "enabled": {
                                            "type": "boolean"
                                        },
                                        "on_fail": {
                                            "type": "string",
                                            "enum": ["httpStatusCode400", "useErrorObject"]
                                        }
                                    },
                                    "required": [
                                        "enabled",
                                        "on_fail"
                                    ]
                                }
                            },
                            "required": [
                                "mode",
                                "validation"
                            ]
                        }
                    },
                    "required": [
                        "endpoint",
                        "schema",
                        "execution"
                    ]
                },
                "playground": {
                    "type": "object",
                    "properties": {
                        "enabled": {
                            "type": "boolean"
                        },
                        "path": {
                            "type": "string"
                        }
                    },
                    "required": [
                        "enabled"
                    ]
                }
            },
            "required": [
                "enabled"
            ]
        }
    },
    "required": [
        "auth",
        "name",
        "proxy",
        "version_data"
    ]
}`
<commit_msg>Add executionEngine mode to api schema def<commit_after>package apidef

const Schema = `{
    "type": ["object", "null"],
    "$schema": "http://json-schema.org/draft-04/schema",
    "id": "http://jsonschema.net",
    "additionalProperties": false,
    "properties": {
        "is_site": {
            "type": "boolean"
        },
        "uptime_tests": {
            "type": ["object", "null"]
        },
        "expire_analytics_after": {
            "type": "number"
        },
        "id": {
            "type": "string"
        },
        "org_id": {
            "type": "string"
        },
        "api_id": {
            "type": "string"
        },
        "enable_ip_whitelisting": {
            "type": "boolean"
        },
        "enable_ip_blacklisting": {
            "type": "boolean"
        },
        "enable_context_vars": {
            "type": "boolean"
        },
        "strip_auth_data": {
          "type": "boolean"
        },
        "do_not_track": {
            "type": "boolean"
        },
        "enable_jwt": {
            "type": "boolean"
        },
        "use_openid": {
            "type": "boolean"
        },
        "openid_options": {
            "type": ["object", "null"]
        },
        "use_standard_auth": {
            "type": "boolean"
        },
        "use_go_plugin_auth": {
            "type": "boolean"
        },
        "enable_coprocess_auth": {
            "type": "boolean"
        },
        "jwt_skip_kid": {
            "type": "boolean"
        },
        "base_identity_provided_by": {
            "type": "string"
        },
        "disable_rate_limit": {
            "type": "boolean"
        },
        "disable_quota": {
            "type": "boolean"
        },
        "custom_middleware_bundle": {
            "type": "string"
        },
        "jwt_policy_field_name": {
            "type": "string"
        },
        "jwt_default_policies": {
            "type": ["array", "null"]
        },
        "jwt_signing_method": {
            "type": "string"
        },
        "jwt_source": {
            "type": "string"
        },
        "jwt_identity_base_field": {
            "type": "string"
        },
        "jwt_client_base_field": {
            "type": "string"
        },
        "jwt_disable_issued_at_validation": {
            "type": "boolean"
        },
        "jwt_disable_expires_at_validation": {
            "type": "boolean"
        },
        "jwt_disable_not_before_validation": {
            "type": "boolean"
        },
        "jwt_issued_at_validation_skew": {
            "type": "number"
        },
        "jwt_expires_at_validation_skew": {
            "type": "number"
        },
        "jwt_not_before_validation_skew": {
            "type": "number"
        },
        "jwt_scope_to_policy_mapping": {
            "type": ["object", "null"]
        },
        "jwt_scope_claim_name": {
            "type": "string"
        },
        "use_keyless": {
            "type": "boolean"
        },
        "use_basic_auth": {
            "type": "boolean"
        },
        "use_mutual_tls_auth": {
            "type": "boolean"
        },
        "client_certificates": {
            "type": ["array", "null"]
        },
        "upstream_certificates": {
            "type": ["object", "null"]
        },
        "pinned_public_keys": {
            "type": ["object", "null"]
        },
        "allowed_ips": {
            "type": ["array", "null"]
        },
        "blacklisted_ips": {
            "type": ["array", "null"]
        },
        "enable_batch_request_support": {
            "type": "boolean"
        },
        "event_handlers": {
            "type":["object", "null"]
        },
        "notifications": {
            "type":["object", "null"]
        },
        "use_oauth2": {
            "type": "boolean"
        },
        "oauth_meta": {
            "type":["object", "null"]
        },
        "cache_options": {
            "type":["object", "null"]
        },
        "tags": {
            "type": ["array", "null"]
        },
        "tag_headers": {
            "type": ["array", "null"]
        },
        "basic_auth": {
            "type": ["object", "null"]
        },
        "CORS": {
            "type":["object", "null"]
        },
        "response_processors": {
            "type": ["array", "null"]
        },
        "auth_provider": {
            "type":["object", "null"],
            "properties": {
                "name": {
                    "type": "string",
                    "enum": [""]
                },
                "storage_engine": {
                    "type": "string",
                    "enum": [""]
                }
            }
        },
        "session_provider": {
            "type":["object", "null"],
            "properties": {
                "name": {
                    "type": "string",
                    "enum": [""]
                },
                "storage_engine": {
                    "type": "string",
                    "enum": [""]
                }
            }
        },
        "hmac_allowed_clock_skew": {
            "type": "number"
        },
        "hmac_allowed_algorithms": {
            "type": ["array", "null"]
        },
        "dont_set_quota_on_create": {
            "type": "boolean"
            },
        "custom_middleware": {
            "type":["object", "null"],
            "properties": {
                "pre": {
                    "type": ["array", "null"]
                },
                "post": {
                    "type": ["array", "null"]
                }
            }
        },
        "session_lifetime": {
            "type": "number"
        },
        "enable_detailed_recording": {
            "type": "boolean"
        },
        "enable_signature_checking": {
            "type": "boolean"
        },
        "active": {
            "type": "boolean"
        },
        "internal": {
            "type": "boolean"
        },
        "auth": {
            "type": ["object", "null"],
            "id": "http://jsonschema.net/auth",
            "properties": {
                "auth_header_name": {
                    "type": "string",
                    "id": "http://jsonschema.net/auth/auth_header_name"
                },
                "use_certificate": {
                    "type": "boolean"
                }
            }
        },
		"auth_configs":{
			"type": ["object", "null"]
		},
        "definition": {
            "type": ["object", "null"],
            "id": "http://jsonschema.net/definition",
            "properties": {
                "key": {
                    "type": "string",
                    "id": "http://jsonschema.net/definition/key"
                },
                "location": {
                    "type": "string",
                    "id": "http://jsonschema.net/definition/location"
                },
                "strip_path": {
                    "type": "boolean",
                    "id": "http://jsonschema.net/definition/location"
                }
            },
            "required": [
                "key",
                "location"
            ]
        },
        "name": {
            "type": "string",
            "id": "http://jsonschema.net/name"
        },
        "slug": {
            "type": "string",
            "pattern": "[a-zA-Z0-9]*",
            "id": "http://jsonschema.net/name"
        },
        "domain": {
            "type": "string"
        },
        "listen_port": {
            "type": "number"
        },
        "protocol": {
            "type": "string"
        },
        "enable_proxy_protocol": {
            "type": "boolean"
        },
        "certificates": {
            "type": ["array", "null"]
        },
        "check_host_against_uptime_tests": {
            "type": "boolean"
        },
        "proxy": {
            "type": ["object", "null"],
            "id": "http://jsonschema.net/proxy",
            "properties": {
                "target_url": {
                    "type": "string",
                    "id": "http://jsonschema.net/proxy/target_url"
                },
                "check_host_against_uptime_tests": {
                    "type": "boolean"
                },
                "preserve_host_header": {
                    "type": "boolean"
                },
                "transport": {
                    "type": ["object", "null"],
                    "properties": {
                        "ssl_ciphers": {
                            "type": ["array", "null"]
                        },
                        "ssl_min_version": {
                            "type": "number"
                        },
                        "proxy_url": {
                            "type": "string"
                        },
                        "ssl_force_common_name_check": {
                            "type": "boolean"
                        }
                    }
                }
            },
            "required": [
                "target_url"
            ]
        },
        "hook_references": {
            "type": ["object", "null"]
        },
        "version_data": {
            "type": ["object", "null"],
            "id": "http://jsonschema.net/version_data",
            "properties": {
                "not_versioned": {
                    "type": "boolean",
                    "id": "http://jsonschema.net/version_data/not_versioned"
                },
                "default_version":{
                    "type": "string",
                    "id": "http://jsonschema.net/version_data/default_version"
                },
                "versions": {
                    "type": ["object", "null"],
                    "id": "http://jsonschema.net/version_data/versions",
                    "patternProperties": {
                        "^[a-zA-Z0-9]+$": {
                            "title": "versionInfoProperty",
                            "type": ["object", "null"],
                            "id": "http://jsonschema.net/access_rights/versionInfoProperty",
                            "properties": {
                                "expires": {
                                    "type": "string",
                                    "id": "http://jsonschema.net/version_data/versions/versionInfoProperty/expires"
                                },
                                "name": {
                                    "type": "string",
                                    "id": "http://jsonschema.net/version_data/versions/versionInfoProperty/name"
                                },
                                "paths": {
                                    "type": ["object", "null"],
                                    "id": "http://jsonschema.net/version_data/versions/versionInfoProperty/paths",
                                    "properties": {
                                        "black_list": {
                                            "type": ["array", "null"],
                                            "id": "http://jsonschema.net/version_data/versions/versionInfoProperty/paths/black_list"
                                        },
                                        "ignored": {
                                            "type": ["array", "null"],
                                            "id": "http://jsonschema.net/version_data/versions/versionInfoProperty/paths/ignored"
                                        },
                                        "white_list": {
                                            "type": ["array", "null"],
                                            "id": "http://jsonschema.net/version_data/versions/versionInfoProperty/paths/white_list"
                                        }
                                    }
                                }
                            },
                            "required": [
                                "name"
                            ]
                        }
                    }
                }
            },
            "required": [
                "not_versioned",
                "versions"
            ]
        },
        "config_data": {
            "type": ["object", "null"]
        },
        "global_rate_limit": {
          "type": ["object", "null"],
           "properties": {
                "rate": {
                    "type": "number"
                },
                "per": {
                    "type": "number"
                }
            }
        },
    "request_signing": {
          "type": ["object", "null"],
           "properties": {
                "is_enabled": {
                    "type": "boolean"
                },
                "secret": {
                    "type": "string"
                },
        "key_id": {
                    "type": "string"
                },
        "algorithm": {
                    "type": "string"
                }
            },
        "required": [
            "is_enabled"
        ]
        },
        "graphql": {
            "type": ["object", "null"],
            "properties": {
                "enabled": {
                    "type": "boolean"
                },
                "api": {
                    "type": "object",
                    "properties": {
                        "endpoint": {
                            "type": "string"
                        },
                        "schema": {
                            "type": "string"
                        },
                        "execution": {
                            "type": "object",
                            "properties": {
                                "mode": {
                                    "type": "string",
                                    "enum": ["proxyOnly", "executionEngine"]
                                },
                                "validation": {
                                    "type": "object",
                                    "properties": {
                                        "enabled": {
                                            "type": "boolean"
                                        },
                                        "on_fail": {
                                            "type": "string",
                                            "enum": ["httpStatusCode400", "useErrorObject"]
                                        }
                                    },
                                    "required": [
                                        "enabled",
                                        "on_fail"
                                    ]
                                }
                            },
                            "required": [
                                "mode",
                                "validation"
                            ]
                        }
                    },
                    "required": [
                        "endpoint",
                        "schema",
                        "execution"
                    ]
                },
                "playground": {
                    "type": "object",
                    "properties": {
                        "enabled": {
                            "type": "boolean"
                        },
                        "path": {
                            "type": "string"
                        }
                    },
                    "required": [
                        "enabled"
                    ]
                }
            },
            "required": [
                "enabled"
            ]
        }
    },
    "required": [
        "auth",
        "name",
        "proxy",
        "version_data"
    ]
}`
<|endoftext|>


<commit_before>package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sqs"
	"github.com/mercari/go-circuitbreaker"
	"github.com/nabeken/aws-go-sqs/v3/multiqueue"
	"github.com/nabeken/aws-go-sqs/v3/queue"
	"github.com/nabeken/aws-go-sqs/v3/queue/option"
)

func main() {
	var queueName1 = flag.String("queue1", "", "specify SQS queue name 1")
	var queueName2 = flag.String("queue2", "", "specify SQS queue name 2")
	var drain = flag.Bool("drain", false, "drain")
	var concurrency = flag.Int("concurrency", 1, "specify concurrency")
	var count = flag.Int("count", 10000, "number of messages")

	flag.Parse()

	rand.Seed(time.Now().UnixNano())

	if *queueName1 == "" || *queueName2 == "" {
		log.Fatal("Please specify queue name")
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)

	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		sig := <-c
		log.Println("got signal:", sig)
		cancel()
	}()

	// Create SQS instance
	s := sqs.New(session.Must(session.NewSession()))

	// Create Queue instance
	q1 := queue.MustNew(s, *queueName1)
	q2 := queue.MustNew(s, *queueName2)

	// if we do not set OpenTimeout nor OpenBackOff, the default value of OpenBackOff will be used.
	cbOpts := &circuitbreaker.Options{
		Interval:   1 * time.Minute,
		ShouldTrip: circuitbreaker.NewTripFuncFailureRate(100, 0.7),
	}

	d := multiqueue.New(cbOpts, q1, q2).
		WithOnStateChange(func(q *queue.Queue, oldState, newState circuitbreaker.State) {
			log.Printf("%s: state has been changed from %s to %s", *q.URL, oldState, newState)
		})

	fss := &failureScenarioServer{
		q: q1,
	}
	go func() {
		log.Print("starting failure injection HTTP server...")
		http.ListenAndServe("127.0.0.1:9003", fss)
	}()

	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		defer wg.Done()
		d.StartStateMonitor(ctx)
	}()

	cntCh := make(chan int64, *concurrency)
	for i := 0; i < *concurrency; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			cntCh <- recv(ctx, d)
		}()
	}

	if !*drain {
		wg.Add(1)
		go func() {
			defer wg.Done()
			send(ctx, *count, *concurrency, d, fss)
		}()
	}

	wg.Wait()

	var total int64
	for i := 0; i < *concurrency; i++ {
		total += <-cntCh
	}

	log.Printf("done! total: %d", total)
}

func send(ctx context.Context, count, concurrency int, d *multiqueue.Dispatcher, fss *failureScenarioServer) {
	// MessageAttributes
	attrs := map[string]interface{}{
		"ATTR1": "STRING!!",
		"ATTR2": 12345,
	}

	sem := make(chan struct{}, concurrency)

LOOP:
	for i := 0; i < count; i++ {
		sem <- struct{}{}
		cnt := i + 1
		go func() {
			defer func() { <-sem }()
			for {
				exec := d.Dispatch()
				_, err := exec.Do(ctx, func() (interface{}, error) {
					if err := fss.failureScenario(exec.Queue); err != nil {
						return nil, err
					}

					return exec.SendMessage(fmt.Sprintf("MESSAGE BODY FROM MULTI-QUEUE %d", cnt), option.MessageAttributes(attrs))
				})

				if err != nil {
					log.Printf("%s: unable to send the message. will retry: %s", *exec.Queue.URL, err)
				} else {
					log.Printf("%s: the message has been sent (%d)", *exec.Queue.URL, cnt)
					break
				}
			}
		}()

		select {
		case <-ctx.Done():
			break LOOP
		default:
		}
	}

	log.Print("sent!")
}

func recv(ctx context.Context, d *multiqueue.Dispatcher) int64 {
	log.Print("starting receiver...")

	var cnt int64
	for {
		select {
		case <-ctx.Done():
			log.Printf("shutting down receiver... count:%d", cnt)
			return cnt
		default:
		}

		exec := d.DispatchByRR()
		resp, err := exec.ReceiveMessage(
			option.MaxNumberOfMessages(10),
			option.WaitTimeSeconds(0),
		)
		if err != nil {
			log.Printf("unable to receive message: %s", err)
		}

		for _, m := range resp {
			if err := exec.DeleteMessage(m.ReceiptHandle); err != nil {
				log.Printf("unable to delete message: %s", err)
			}

			cnt++
		}
	}
}

type failureScenarioServer struct {
	// queue going to be failed
	q *queue.Queue

	mu      sync.Mutex
	until   time.Time
	errRate float64
}

func (s *failureScenarioServer) failureScenario(q *queue.Queue) error {
	// q is not the queue we want to be failed
	if q != s.q {
		return nil
	}

	if time.Now().Before(s.until) {
		if rand.Float64() > s.errRate {
			return nil
		}
		return fmt.Errorf("this is a failure scenario until %s", s.until.Format(time.RFC3339))
	}

	return nil
}

func (s *failureScenarioServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if err := req.ParseForm(); err != nil {
		http.Error(rw, err.Error(), http.StatusBadRequest)
		return
	}

	dur, err := time.ParseDuration(req.Form.Get("duration"))
	if err != nil {
		http.Error(rw, err.Error(), http.StatusBadRequest)
		return
	}

	errRate, err := strconv.ParseFloat(req.Form.Get("error_rate"), 64)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusBadRequest)
		return
	}

	s.mu.Lock()
	defer s.mu.Unlock()
	s.errRate = errRate
	s.until = time.Now().Add(dur)
}
<commit_msg>test-multiqueue: support more complex test scenario<commit_after>package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sqs"
	"github.com/mercari/go-circuitbreaker"
	"github.com/nabeken/aws-go-sqs/v3/multiqueue"
	"github.com/nabeken/aws-go-sqs/v3/queue"
	"github.com/nabeken/aws-go-sqs/v3/queue/option"
)

func main() {
	var queueName1 = flag.String("queue1", "", "specify SQS queue name 1")
	var region1 = flag.String("region1", "ap-northeast-1", "specify a region for queue1")
	var queueName2 = flag.String("queue2", "", "specify SQS queue name 2")
	var region2 = flag.String("region2", "ap-southeast-1", "specify a region for queue2")
	var drain = flag.Bool("drain", false, "drain")
	var concurrency = flag.Int("concurrency", 1, "specify concurrency")
	var count = flag.Int("count", 10000, "number of messages")

	flag.Parse()

	rand.Seed(time.Now().UnixNano())

	if *queueName1 == "" || *queueName2 == "" {
		log.Fatal("Please specify queue name")
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)

	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		sig := <-c
		log.Println("got signal:", sig)
		cancel()
	}()

	// Create SQS instance for region1
	s1 := sqs.New(session.Must(session.NewSession(&aws.Config{
		Region: region1,
	})))
	s2 := sqs.New(session.Must(session.NewSession(&aws.Config{
		Region: region2,
	})))

	// Create Queue instance
	q1 := queue.MustNew(s1, *queueName1)
	q2 := queue.MustNew(s2, *queueName2)

	// if we do not set OpenTimeout nor OpenBackOff, the default value of OpenBackOff will be used.
	cbOpts := &circuitbreaker.Options{
		Interval:   1 * time.Minute,
		ShouldTrip: circuitbreaker.NewTripFuncFailureRate(100, 0.7),
	}

	d := multiqueue.New(cbOpts, q1, q2).
		WithOnStateChange(func(q *queue.Queue, oldState, newState circuitbreaker.State) {
			log.Printf("%s: state has been changed from %s to %s", *q.URL, oldState, newState)
		})

	fss := &failureScenarioServer{
		scenario: []failureScenario{
			{URL: *q1.URL},
			{URL: *q2.URL},
		},
	}
	go func() {
		log.Print("starting failure injection HTTP server...")
		http.ListenAndServe("127.0.0.1:9003", fss)
	}()

	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		defer wg.Done()
		d.StartStateMonitor(ctx)
	}()

	cntCh := make(chan int64, *concurrency)
	for i := 0; i < *concurrency; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			cntCh <- recv(ctx, d)
		}()
	}

	if !*drain {
		wg.Add(1)
		go func() {
			defer wg.Done()
			send(ctx, *count, *concurrency, d, fss)
		}()
	}

	wg.Wait()

	var total int64
	for i := 0; i < *concurrency; i++ {
		total += <-cntCh
	}

	log.Printf("done! total: %d", total)
}

func send(ctx context.Context, count, concurrency int, d *multiqueue.Dispatcher, fss *failureScenarioServer) {
	// MessageAttributes
	attrs := map[string]interface{}{
		"ATTR1": "STRING!!",
		"ATTR2": 12345,
	}

	sem := make(chan struct{}, concurrency)

LOOP:
	for i := 0; i < count; i++ {
		sem <- struct{}{}
		cnt := i + 1
		go func() {
			defer func() { <-sem }()
			for {
				exec := d.Dispatch()
				_, err := exec.Do(ctx, func() (interface{}, error) {
					if err := fss.failureScenario(exec.Queue); err != nil {
						time.Sleep(100 * time.Millisecond)
						return nil, err
					}

					return exec.SendMessage(fmt.Sprintf("MESSAGE BODY FROM MULTI-QUEUE %d", cnt), option.MessageAttributes(attrs))
				})

				if err != nil {
					log.Printf("%s: unable to send the message. will retry: %s", *exec.Queue.URL, err)
					if err == circuitbreaker.ErrOpen {
						time.Sleep(time.Second)
					}
				} else {
					log.Printf("%s: the message has been sent (%d)", *exec.Queue.URL, cnt)
					break
				}
			}
		}()

		select {
		case <-ctx.Done():
			break LOOP
		default:
		}
	}

	log.Print("sent!")
}

func recv(ctx context.Context, d *multiqueue.Dispatcher) int64 {
	log.Print("starting receiver...")

	var cnt int64
	for {
		select {
		case <-ctx.Done():
			log.Printf("shutting down receiver... count:%d", cnt)
			return cnt
		default:
		}

		exec := d.DispatchByRR()
		resp, err := exec.ReceiveMessage(
			option.MaxNumberOfMessages(10),
			option.WaitTimeSeconds(0),
		)
		if err != nil {
			log.Printf("unable to receive message: %s", err)
		}

		for _, m := range resp {
			if err := exec.DeleteMessage(m.ReceiptHandle); err != nil {
				log.Printf("unable to delete message: %s", err)
			}

			cnt++
		}
	}
}

type failureScenario struct {
	URL     string
	Until   time.Time
	ErrRate float64
}

type failureScenarioServer struct {
	mu       sync.Mutex
	scenario []failureScenario
}

func (s *failureScenarioServer) findScenario(q *queue.Queue) (failureScenario, bool) {
	for _, sc := range s.scenario {
		if sc.URL == *q.URL {
			return sc, true
		}
	}
	return failureScenario{}, false
}

func (s *failureScenarioServer) failureScenario(q *queue.Queue) error {
	sc, found := s.findScenario(q)
	if !found {
		return nil
	}

	if time.Now().Before(sc.Until) {
		if rand.Float64() > sc.ErrRate {
			return nil
		}
		return fmt.Errorf("this is a failure scenario until %s", sc.Until.Format(time.RFC3339))
	}

	return nil
}

func (s *failureScenarioServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	if err := req.ParseForm(); err != nil {
		http.Error(rw, err.Error(), http.StatusBadRequest)
		return
	}

	index, err := strconv.ParseInt(req.Form.Get("index"), 10, 64)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusBadRequest)
		return
	}

	dur, err := time.ParseDuration(req.Form.Get("duration"))
	if err != nil {
		http.Error(rw, err.Error(), http.StatusBadRequest)
		return
	}

	errRate, err := strconv.ParseFloat(req.Form.Get("error_rate"), 64)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusBadRequest)
		return
	}

	s.mu.Lock()
	defer s.mu.Unlock()
	if index > int64(len(s.scenario))-1 {
		http.Error(rw, err.Error(), http.StatusBadRequest)
		return
	}
	s.scenario[index].Until = time.Now().Add(dur)
	s.scenario[index].ErrRate = errRate

	json.NewEncoder(rw).Encode(s.scenario)
}
<|endoftext|>


<commit_before><commit_msg>init logger<commit_after>package logger
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"flag"
	"fmt"
	"log"
	"net/http"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"github.com/gorilla/mux"
)

var (
	db             = flag.String("db", "", "the db")
	host	       = flag.String("host", "", "the db host")
	user           = flag.String("user", "", "the db user")
	pwd            = flag.String("pwd", "", "the db password")
	port	       = flag.String("port", "http", "the port to listen on")

	dbConn *sql.DB = nil
	selectStmt *sql.Stmt = nil
	updateStmt *sql.Stmt = nil
)

func RootHandler(w http.ResponseWriter, r *http.Request) {
	rows, err := dbConn.Query("SELECT `long`,`short`,`access_count` FROM `url` ORDER BY access_count DESC")
	if err != nil {
		http.Error(w, fmt.Sprintf("%v", err), http.StatusInternalServerError)
		return
	}
	// TODO: template
	for rows.Next() {
	        var shorturl string
		var longurl string
		var access_count int
		if err := rows.Scan(&shorturl, longurl, access_count); err != nil {
			http.Error(w, fmt.Sprintf("%v", err), http.StatusInternalServerError)
			return
		}
		w.Write([]byte(fmt.Sprintf("%s -> %s: %d\n", shorturl, longurl, access_count)))
	}
	if err := rows.Err(); err != nil {
		http.Error(w, fmt.Sprintf("%v", err), http.StatusInternalServerError)
		return
	}

	// TODO: form for creating new one
}

func ShortURLHandler(w http.ResponseWriter, r *http.Request) {
	shorturl := mux.Vars(r)["shorturl"]

	// lookup shorturl
	var longurl string
	var access_count int
	err := selectStmt.QueryRow(shorturl).Scan(&longurl, &access_count)

	switch {
	case err == sql.ErrNoRows:
		log.Printf("%q not found", shorturl)
		http.NotFound(w, r)

	case err != nil:
		log.Fatal(err)

	default:
		log.Printf("%q -> %q", shorturl, longurl)
		access_count++
		access_time := time.Now().UTC()
		_, err := updateStmt.Exec(access_count, access_time, shorturl)
		if err != nil {
			log.Printf("Failed to update access information: %v\n", err)
		}
		http.Redirect(w, r, longurl, http.StatusFound)
	}
}

func main() {
	flag.Parse()

	c, err := sql.Open("mysql", *user+":"+*pwd+"@("+*host+")/"+*db)
	if err != nil {
		log.Fatal("Failed to open sql dbConnection")
	}
	dbConn = c
	defer dbConn.Close()

	selectLongURL, err := dbConn.Prepare("SELECT `long`,`access_count` FROM `url` WHERE short=?")
	if err != nil {
		log.Fatal("Failed to prepare selectStmt: ", err)
	}
	selectStmt = selectLongURL

	updateAccess, err := dbConn.Prepare("UPDATE `url` SET `access_count`=?,`accessed`=? WHERE short=?")
	if err != nil {
		log.Fatal("Failed to prepare updateStmt: ", err)
	}
	updateStmt = updateAccess

	r := mux.NewRouter()
	r.HandleFunc("/", RootHandler).Methods("GET")
	r.HandleFunc("/{shorturl}", ShortURLHandler).Methods("GET")
	http.Handle("/", r)

	log.Fatal(http.ListenAndServe(":"+*port, nil))
}
<commit_msg>Fix references in Scan for RootHandler<commit_after>package main

import (
	"database/sql"
	"flag"
	"fmt"
	"log"
	"net/http"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"github.com/gorilla/mux"
)

var (
	db             = flag.String("db", "", "the db")
	host	       = flag.String("host", "", "the db host")
	user           = flag.String("user", "", "the db user")
	pwd            = flag.String("pwd", "", "the db password")
	port	       = flag.String("port", "http", "the port to listen on")

	dbConn *sql.DB = nil
	selectStmt *sql.Stmt = nil
	updateStmt *sql.Stmt = nil
)

func RootHandler(w http.ResponseWriter, r *http.Request) {
	rows, err := dbConn.Query("SELECT `long`,`short`,`access_count` FROM `url` ORDER BY access_count DESC")
	if err != nil {
		http.Error(w, fmt.Sprintf("%v", err), http.StatusInternalServerError)
		return
	}
	// TODO: template
	for rows.Next() {
	        var shorturl string
		var longurl string
		var access_count int
		if err := rows.Scan(&longurl, &shorturl, &access_count); err != nil {
			http.Error(w, fmt.Sprintf("%v", err), http.StatusInternalServerError)
			return
		}
		w.Write([]byte(fmt.Sprintf("%s -> %s: %d\n", shorturl, longurl, access_count)))
	}
	if err := rows.Err(); err != nil {
		http.Error(w, fmt.Sprintf("%v", err), http.StatusInternalServerError)
		return
	}

	// TODO: form for creating new one
}

func ShortURLHandler(w http.ResponseWriter, r *http.Request) {
	shorturl := mux.Vars(r)["shorturl"]

	// lookup shorturl
	var longurl string
	var access_count int
	err := selectStmt.QueryRow(shorturl).Scan(&longurl, &access_count)

	switch {
	case err == sql.ErrNoRows:
		log.Printf("%q not found", shorturl)
		http.NotFound(w, r)

	case err != nil:
		log.Fatal(err)

	default:
		log.Printf("%q -> %q", shorturl, longurl)
		access_count++
		access_time := time.Now().UTC()
		_, err := updateStmt.Exec(access_count, access_time, shorturl)
		if err != nil {
			log.Printf("Failed to update access information: %v\n", err)
		}
		http.Redirect(w, r, longurl, http.StatusFound)
	}
}

func main() {
	flag.Parse()

	c, err := sql.Open("mysql", *user+":"+*pwd+"@("+*host+")/"+*db)
	if err != nil {
		log.Fatal("Failed to open sql dbConnection")
	}
	dbConn = c
	defer dbConn.Close()

	selectLongURL, err := dbConn.Prepare("SELECT `long`,`access_count` FROM `url` WHERE short=?")
	if err != nil {
		log.Fatal("Failed to prepare selectStmt: ", err)
	}
	selectStmt = selectLongURL

	updateAccess, err := dbConn.Prepare("UPDATE `url` SET `access_count`=?,`accessed`=? WHERE short=?")
	if err != nil {
		log.Fatal("Failed to prepare updateStmt: ", err)
	}
	updateStmt = updateAccess

	r := mux.NewRouter()
	r.HandleFunc("/", RootHandler).Methods("GET")
	r.HandleFunc("/{shorturl}", ShortURLHandler).Methods("GET")
	http.Handle("/", r)

	log.Fatal(http.ListenAndServe(":"+*port, nil))
}
<|endoftext|>


<commit_before>package bits

import (
	"encoding/binary"
	"io"
)

// Writer
type Writer struct {
	n int  // current number of bits
	v uint // current accumulated value

	wr io.Writer
}

// NewWriter returns a new Writer.
func NewWriter(w io.Writer) *Writer {
	return &Writer{
		wr: w,
	}
}

// Write writes bits with give size n.
func (w *Writer) Write(bits uint, n int) error {
	w.v <<= uint(n)
	w.v |= bits & mask(n)
	w.n += n
	for w.n >= 8 {
		b := (w.v >> (uint(w.n) - 8)) & mask(8)
		if err := binary.Write(w.wr, binary.BigEndian, uint8(b)); err != nil {
			return err
		}
		w.n -= 8
	}
	w.v &= mask(8)

	return nil
}

// Flush writes any remaining bits to the underlying io.Writer.
// bits will be left-shifted.
func (w *Writer) Flush() error {
	if w.n != 0 {
		b := (w.v << (8 - uint(w.n))) & mask(8)
		if err := binary.Write(w.wr, binary.BigEndian, uint8(b)); err != nil {
			return err
		}
	}
	return nil
}

type Reader struct {
	n int  // current number of bits
	v uint // current accumulated value

	rd io.Reader
}

func NewReader(rd io.Reader) *Reader {
	return &Reader{
		rd: rd,
	}
}

func (r *Reader) Read(n int) (uint, error) {
	var err error

	for r.n <= n {
		r.v <<= 8
		var b uint8
		err = binary.Read(r.rd, binary.BigEndian, &b)
		if err != nil && err != io.EOF {
			return 0, err
		}
		r.v |= uint(b)

		r.n += 8
	}
	v := r.v >> uint(r.n-n)

	r.n -= n
	r.v &= mask(r.n)

	return v, err
}

func mask(n int) uint {
	return (1 << uint(n)) - 1
}
<commit_msg>golint<commit_after>package bits

import (
	"encoding/binary"
	"io"
)

// Writer writes bits into underlying io.Writer
type Writer struct {
	n int  // current number of bits
	v uint // current accumulated value

	wr io.Writer
}

// NewWriter returns a new Writer.
func NewWriter(w io.Writer) *Writer {
	return &Writer{
		wr: w,
	}
}

// Write writes bits with give size n.
func (w *Writer) Write(bits uint, n int) error {
	w.v <<= uint(n)
	w.v |= bits & mask(n)
	w.n += n
	for w.n >= 8 {
		b := (w.v >> (uint(w.n) - 8)) & mask(8)
		if err := binary.Write(w.wr, binary.BigEndian, uint8(b)); err != nil {
			return err
		}
		w.n -= 8
	}
	w.v &= mask(8)

	return nil
}

// Flush writes any remaining bits to the underlying io.Writer.
// bits will be left-shifted.
func (w *Writer) Flush() error {
	if w.n != 0 {
		b := (w.v << (8 - uint(w.n))) & mask(8)
		if err := binary.Write(w.wr, binary.BigEndian, uint8(b)); err != nil {
			return err
		}
	}
	return nil
}

// Reader reads bits from the given io.Reader.
type Reader struct {
	n int  // current number of bits
	v uint // current accumulated value

	rd io.Reader
}

// NewReader returns new a new Reader.
func NewReader(rd io.Reader) *Reader {
	return &Reader{
		rd: rd,
	}
}

func (r *Reader) Read(n int) (uint, error) {
	var err error

	for r.n <= n {
		r.v <<= 8
		var b uint8
		err = binary.Read(r.rd, binary.BigEndian, &b)
		if err != nil && err != io.EOF {
			return 0, err
		}
		r.v |= uint(b)

		r.n += 8
	}
	v := r.v >> uint(r.n-n)

	r.n -= n
	r.v &= mask(r.n)

	return v, err
}

func mask(n int) uint {
	return (1 << uint(n)) - 1
}
<|endoftext|>


<commit_before>package exec

import (
	"bytes"
	"context"
	"encoding/json"
	"reflect"
	"sync"

	"github.com/neelance/graphql-go/errors"
	"github.com/neelance/graphql-go/internal/common"
	"github.com/neelance/graphql-go/internal/exec/resolvable"
	"github.com/neelance/graphql-go/internal/exec/selected"
	"github.com/neelance/graphql-go/internal/query"
	"github.com/neelance/graphql-go/internal/schema"
	"github.com/neelance/graphql-go/log"
	"github.com/neelance/graphql-go/trace"
)

type Request struct {
	selected.Request
	Limiter chan struct{}
	Tracer  trace.Tracer
	Logger  log.Logger
}

type fieldResult struct {
	name  string
	value []byte
}

func (r *Request) handlePanic(ctx context.Context) {
	if value := recover(); value != nil {
		r.Logger.LogPanic(ctx, value)
		r.AddError(makePanicError(value))
	}
}

func makePanicError(value interface{}) *errors.QueryError {
	return errors.Errorf("graphql: panic occurred: %v", value)
}

func (r *Request) Execute(ctx context.Context, s *resolvable.Schema, op *query.Operation) ([]byte, []*errors.QueryError) {
	var out bytes.Buffer
	func() {
		defer r.handlePanic(ctx)
		sels := selected.ApplyOperation(&r.Request, s, op)
		r.execSelections(ctx, sels, s.Resolver, &out, op.Type == query.Mutation)
	}()

	if err := ctx.Err(); err != nil {
		return nil, []*errors.QueryError{errors.Errorf("%s", err)}
	}

	return out.Bytes(), r.Errs
}

type fieldToExec struct {
	field    *selected.SchemaField
	sels     []selected.Selection
	resolver reflect.Value
	out      *bytes.Buffer
}

func (r *Request) execSelections(ctx context.Context, sels []selected.Selection, resolver reflect.Value, out *bytes.Buffer, serially bool) {
	async := !serially && selected.HasAsyncSel(sels)

	var fields []*fieldToExec
	collectFieldsToResolve(sels, resolver, &fields, make(map[string]*fieldToExec))

	if async {
		var wg sync.WaitGroup
		wg.Add(len(fields))
		for _, f := range fields {
			go func(f *fieldToExec) {
				defer wg.Done()
				defer r.handlePanic(ctx)
				f.out = new(bytes.Buffer)
				execFieldSelection(ctx, r, f, false)
			}(f)
		}
		wg.Wait()
	}

	out.WriteByte('{')
	for i, f := range fields {
		if i > 0 {
			out.WriteByte(',')
		}
		out.WriteByte('"')
		out.WriteString(f.field.Alias)
		out.WriteByte('"')
		out.WriteByte(':')
		if async {
			out.Write(f.out.Bytes())
			continue
		}
		f.out = out
		execFieldSelection(ctx, r, f, false)
	}
	out.WriteByte('}')
}

func collectFieldsToResolve(sels []selected.Selection, resolver reflect.Value, fields *[]*fieldToExec, fieldByAlias map[string]*fieldToExec) {
	for _, sel := range sels {
		switch sel := sel.(type) {
		case *selected.SchemaField:
			field, ok := fieldByAlias[sel.Alias]
			if !ok { // validation already checked for conflict (TODO)
				field = &fieldToExec{field: sel, resolver: resolver}
				fieldByAlias[sel.Alias] = field
				*fields = append(*fields, field)
			}
			field.sels = append(field.sels, sel.Sels...)

		case *selected.TypenameField:
			sf := &selected.SchemaField{
				Field:       resolvable.MetaFieldTypename,
				Alias:       sel.Alias,
				FixedResult: reflect.ValueOf(typeOf(sel, resolver)),
			}
			*fields = append(*fields, &fieldToExec{field: sf, resolver: resolver})

		case *selected.TypeAssertion:
			out := resolver.Method(sel.MethodIndex).Call(nil)
			if !out[1].Bool() {
				continue
			}
			collectFieldsToResolve(sel.Sels, out[0], fields, fieldByAlias)

		default:
			panic("unreachable")
		}
	}
}

func typeOf(tf *selected.TypenameField, resolver reflect.Value) string {
	if len(tf.TypeAssertions) == 0 {
		return tf.Name
	}
	for name, a := range tf.TypeAssertions {
		out := resolver.Method(a.MethodIndex).Call(nil)
		if out[1].Bool() {
			return name
		}
	}
	return ""
}

func execFieldSelection(ctx context.Context, r *Request, f *fieldToExec, applyLimiter bool) {
	if applyLimiter {
		r.Limiter <- struct{}{}
	}

	var result reflect.Value
	var err *errors.QueryError

	traceCtx, finish := r.Tracer.TraceField(ctx, f.field.TraceLabel, f.field.TypeName, f.field.Name, !f.field.Async, f.field.Args)
	defer func() {
		finish(err)
	}()

	err = func() (err *errors.QueryError) {
		defer func() {
			if panicValue := recover(); panicValue != nil {
				r.Logger.LogPanic(ctx, panicValue)
				err = makePanicError(panicValue)
			}
		}()

		if f.field.FixedResult.IsValid() {
			result = f.field.FixedResult
			return nil
		}

		if err := traceCtx.Err(); err != nil {
			return errors.Errorf("%s", err) // don't execute any more resolvers if context got cancelled
		}

		var in []reflect.Value
		if f.field.HasContext {
			in = append(in, reflect.ValueOf(traceCtx))
		}
		if f.field.ArgsPacker != nil {
			in = append(in, f.field.PackedArgs)
		}
		callOut := f.resolver.Method(f.field.MethodIndex).Call(in)
		result = callOut[0]
		if f.field.HasError && !callOut[1].IsNil() {
			resolverErr := callOut[1].Interface().(error)
			err := errors.Errorf("%s", resolverErr)
			err.ResolverError = resolverErr
			return err
		}
		return nil
	}()

	if applyLimiter {
		<-r.Limiter
	}

	if err != nil {
		r.AddError(err)
		f.out.WriteString("null") // TODO handle non-nil
		return
	}

	r.execSelectionSet(traceCtx, f.sels, f.field.Type, result, f.out)
}

func (r *Request) execSelectionSet(ctx context.Context, sels []selected.Selection, typ common.Type, resolver reflect.Value, out *bytes.Buffer) {
	t, nonNull := unwrapNonNull(typ)
	switch t := t.(type) {
	case *schema.Object, *schema.Interface, *schema.Union:
		if resolver.Kind() == reflect.Ptr && resolver.IsNil() {
			if nonNull {
				panic(errors.Errorf("got nil for non-null %q", t))
			}
			out.WriteString("null")
			return
		}

		r.execSelections(ctx, sels, resolver, out, false)
		return
	}

	if !nonNull {
		if resolver.IsNil() {
			out.WriteString("null")
			return
		}
		resolver = resolver.Elem()
	}

	switch t := t.(type) {
	case *common.List:
		l := resolver.Len()

		if selected.HasAsyncSel(sels) {
			var wg sync.WaitGroup
			wg.Add(l)
			entryouts := make([]bytes.Buffer, l)
			for i := 0; i < l; i++ {
				go func(i int) {
					defer wg.Done()
					defer r.handlePanic(ctx)
					r.execSelectionSet(ctx, sels, t.OfType, resolver.Index(i), &entryouts[i])
				}(i)
			}
			wg.Wait()

			out.WriteByte('[')
			for i, entryout := range entryouts {
				if i > 0 {
					out.WriteByte(',')
				}
				out.Write(entryout.Bytes())
			}
			out.WriteByte(']')
			return
		}

		out.WriteByte('[')
		for i := 0; i < l; i++ {
			if i > 0 {
				out.WriteByte(',')
			}
			r.execSelectionSet(ctx, sels, t.OfType, resolver.Index(i), out)
		}
		out.WriteByte(']')

	case *schema.Scalar:
		v := resolver.Interface()
		data, err := json.Marshal(v)
		if err != nil {
			panic(errors.Errorf("could not marshal %v", v))
		}
		out.Write(data)

	case *schema.Enum:
		out.WriteByte('"')
		out.WriteString(resolver.String())
		out.WriteByte('"')

	default:
		panic("unreachable")
	}
}

func unwrapNonNull(t common.Type) (common.Type, bool) {
	if nn, ok := t.(*common.NonNull); ok {
		return nn.OfType, true
	}
	return t, false
}

type marshaler interface {
	MarshalJSON() ([]byte, error)
}
<commit_msg>fix limiter<commit_after>package exec

import (
	"bytes"
	"context"
	"encoding/json"
	"reflect"
	"sync"

	"github.com/neelance/graphql-go/errors"
	"github.com/neelance/graphql-go/internal/common"
	"github.com/neelance/graphql-go/internal/exec/resolvable"
	"github.com/neelance/graphql-go/internal/exec/selected"
	"github.com/neelance/graphql-go/internal/query"
	"github.com/neelance/graphql-go/internal/schema"
	"github.com/neelance/graphql-go/log"
	"github.com/neelance/graphql-go/trace"
)

type Request struct {
	selected.Request
	Limiter chan struct{}
	Tracer  trace.Tracer
	Logger  log.Logger
}

type fieldResult struct {
	name  string
	value []byte
}

func (r *Request) handlePanic(ctx context.Context) {
	if value := recover(); value != nil {
		r.Logger.LogPanic(ctx, value)
		r.AddError(makePanicError(value))
	}
}

func makePanicError(value interface{}) *errors.QueryError {
	return errors.Errorf("graphql: panic occurred: %v", value)
}

func (r *Request) Execute(ctx context.Context, s *resolvable.Schema, op *query.Operation) ([]byte, []*errors.QueryError) {
	var out bytes.Buffer
	func() {
		defer r.handlePanic(ctx)
		sels := selected.ApplyOperation(&r.Request, s, op)
		r.execSelections(ctx, sels, s.Resolver, &out, op.Type == query.Mutation)
	}()

	if err := ctx.Err(); err != nil {
		return nil, []*errors.QueryError{errors.Errorf("%s", err)}
	}

	return out.Bytes(), r.Errs
}

type fieldToExec struct {
	field    *selected.SchemaField
	sels     []selected.Selection
	resolver reflect.Value
	out      *bytes.Buffer
}

func (r *Request) execSelections(ctx context.Context, sels []selected.Selection, resolver reflect.Value, out *bytes.Buffer, serially bool) {
	async := !serially && selected.HasAsyncSel(sels)

	var fields []*fieldToExec
	collectFieldsToResolve(sels, resolver, &fields, make(map[string]*fieldToExec))

	if async {
		var wg sync.WaitGroup
		wg.Add(len(fields))
		for _, f := range fields {
			go func(f *fieldToExec) {
				defer wg.Done()
				defer r.handlePanic(ctx)
				f.out = new(bytes.Buffer)
				execFieldSelection(ctx, r, f, true)
			}(f)
		}
		wg.Wait()
	}

	out.WriteByte('{')
	for i, f := range fields {
		if i > 0 {
			out.WriteByte(',')
		}
		out.WriteByte('"')
		out.WriteString(f.field.Alias)
		out.WriteByte('"')
		out.WriteByte(':')
		if async {
			out.Write(f.out.Bytes())
			continue
		}
		f.out = out
		execFieldSelection(ctx, r, f, false)
	}
	out.WriteByte('}')
}

func collectFieldsToResolve(sels []selected.Selection, resolver reflect.Value, fields *[]*fieldToExec, fieldByAlias map[string]*fieldToExec) {
	for _, sel := range sels {
		switch sel := sel.(type) {
		case *selected.SchemaField:
			field, ok := fieldByAlias[sel.Alias]
			if !ok { // validation already checked for conflict (TODO)
				field = &fieldToExec{field: sel, resolver: resolver}
				fieldByAlias[sel.Alias] = field
				*fields = append(*fields, field)
			}
			field.sels = append(field.sels, sel.Sels...)

		case *selected.TypenameField:
			sf := &selected.SchemaField{
				Field:       resolvable.MetaFieldTypename,
				Alias:       sel.Alias,
				FixedResult: reflect.ValueOf(typeOf(sel, resolver)),
			}
			*fields = append(*fields, &fieldToExec{field: sf, resolver: resolver})

		case *selected.TypeAssertion:
			out := resolver.Method(sel.MethodIndex).Call(nil)
			if !out[1].Bool() {
				continue
			}
			collectFieldsToResolve(sel.Sels, out[0], fields, fieldByAlias)

		default:
			panic("unreachable")
		}
	}
}

func typeOf(tf *selected.TypenameField, resolver reflect.Value) string {
	if len(tf.TypeAssertions) == 0 {
		return tf.Name
	}
	for name, a := range tf.TypeAssertions {
		out := resolver.Method(a.MethodIndex).Call(nil)
		if out[1].Bool() {
			return name
		}
	}
	return ""
}

func execFieldSelection(ctx context.Context, r *Request, f *fieldToExec, applyLimiter bool) {
	if applyLimiter {
		r.Limiter <- struct{}{}
	}

	var result reflect.Value
	var err *errors.QueryError

	traceCtx, finish := r.Tracer.TraceField(ctx, f.field.TraceLabel, f.field.TypeName, f.field.Name, !f.field.Async, f.field.Args)
	defer func() {
		finish(err)
	}()

	err = func() (err *errors.QueryError) {
		defer func() {
			if panicValue := recover(); panicValue != nil {
				r.Logger.LogPanic(ctx, panicValue)
				err = makePanicError(panicValue)
			}
		}()

		if f.field.FixedResult.IsValid() {
			result = f.field.FixedResult
			return nil
		}

		if err := traceCtx.Err(); err != nil {
			return errors.Errorf("%s", err) // don't execute any more resolvers if context got cancelled
		}

		var in []reflect.Value
		if f.field.HasContext {
			in = append(in, reflect.ValueOf(traceCtx))
		}
		if f.field.ArgsPacker != nil {
			in = append(in, f.field.PackedArgs)
		}
		callOut := f.resolver.Method(f.field.MethodIndex).Call(in)
		result = callOut[0]
		if f.field.HasError && !callOut[1].IsNil() {
			resolverErr := callOut[1].Interface().(error)
			err := errors.Errorf("%s", resolverErr)
			err.ResolverError = resolverErr
			return err
		}
		return nil
	}()

	if applyLimiter {
		<-r.Limiter
	}

	if err != nil {
		r.AddError(err)
		f.out.WriteString("null") // TODO handle non-nil
		return
	}

	r.execSelectionSet(traceCtx, f.sels, f.field.Type, result, f.out)
}

func (r *Request) execSelectionSet(ctx context.Context, sels []selected.Selection, typ common.Type, resolver reflect.Value, out *bytes.Buffer) {
	t, nonNull := unwrapNonNull(typ)
	switch t := t.(type) {
	case *schema.Object, *schema.Interface, *schema.Union:
		if resolver.Kind() == reflect.Ptr && resolver.IsNil() {
			if nonNull {
				panic(errors.Errorf("got nil for non-null %q", t))
			}
			out.WriteString("null")
			return
		}

		r.execSelections(ctx, sels, resolver, out, false)
		return
	}

	if !nonNull {
		if resolver.IsNil() {
			out.WriteString("null")
			return
		}
		resolver = resolver.Elem()
	}

	switch t := t.(type) {
	case *common.List:
		l := resolver.Len()

		if selected.HasAsyncSel(sels) {
			var wg sync.WaitGroup
			wg.Add(l)
			entryouts := make([]bytes.Buffer, l)
			for i := 0; i < l; i++ {
				go func(i int) {
					defer wg.Done()
					defer r.handlePanic(ctx)
					r.execSelectionSet(ctx, sels, t.OfType, resolver.Index(i), &entryouts[i])
				}(i)
			}
			wg.Wait()

			out.WriteByte('[')
			for i, entryout := range entryouts {
				if i > 0 {
					out.WriteByte(',')
				}
				out.Write(entryout.Bytes())
			}
			out.WriteByte(']')
			return
		}

		out.WriteByte('[')
		for i := 0; i < l; i++ {
			if i > 0 {
				out.WriteByte(',')
			}
			r.execSelectionSet(ctx, sels, t.OfType, resolver.Index(i), out)
		}
		out.WriteByte(']')

	case *schema.Scalar:
		v := resolver.Interface()
		data, err := json.Marshal(v)
		if err != nil {
			panic(errors.Errorf("could not marshal %v", v))
		}
		out.Write(data)

	case *schema.Enum:
		out.WriteByte('"')
		out.WriteString(resolver.String())
		out.WriteByte('"')

	default:
		panic("unreachable")
	}
}

func unwrapNonNull(t common.Type) (common.Type, bool) {
	if nn, ok := t.(*common.NonNull); ok {
		return nn.OfType, true
	}
	return t, false
}

type marshaler interface {
	MarshalJSON() ([]byte, error)
}
<|endoftext|>


<commit_before>package ipfs

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	gohttp "net/http"
	"time"

	ipfs "github.com/ipfs/go-ipfs-api"
	"github.com/johnny-morrice/godless/api"
	"github.com/johnny-morrice/godless/crdt"
	"github.com/johnny-morrice/godless/internal/http"
	"github.com/johnny-morrice/godless/log"
	"github.com/pkg/errors"
)

type IPFSRecord struct {
	Namespace crdt.Namespace
}

func makeIpfsRecord(namespace crdt.Namespace) *IPFSRecord {
	return &IPFSRecord{
		Namespace: namespace,
	}
}

func (record *IPFSRecord) encode(w io.Writer) error {
	invalid, err := crdt.EncodeNamespace(record.Namespace, w)

	record.logInvalid(invalid)

	return err
}

func (record *IPFSRecord) decode(r io.Reader) error {
	ns, invalid, err := crdt.DecodeNamespace(r)

	record.logInvalid(invalid)

	if err != nil {
		return err
	}

	record.Namespace = ns
	return nil
}

func (record *IPFSRecord) logInvalid(invalid []crdt.InvalidNamespaceEntry) {
	invalidCount := len(invalid)

	if invalidCount > 0 {
		log.Error("IPFSRecord: %v invalid entries", invalidCount)
	}
}

type encoder interface {
	encode(io.Writer) error
}

type decoder interface {
	decode(io.Reader) error
}

type IPFSIndex struct {
	Index crdt.Index
}

func makeIpfsIndex(index crdt.Index) *IPFSIndex {
	return &IPFSIndex{
		Index: index,
	}
}

func (index *IPFSIndex) encode(w io.Writer) error {
	invalid, err := crdt.EncodeIndex(index.Index, w)

	index.logInvalid(invalid)

	return err
}

func (index *IPFSIndex) decode(r io.Reader) error {
	dx, invalid, err := crdt.DecodeIndex(r)

	index.logInvalid(invalid)

	if err != nil {
		return err
	}

	// TODO should cache the invalid details.
	if len(invalid) > 0 {
		log.Warn("IPFSIndex Decoded invalid index entries")
	}

	index.Index = dx
	return nil
}

func (index *IPFSIndex) logInvalid(invalid []crdt.InvalidIndexEntry) {
	invalidCount := len(invalid)

	if invalidCount > 0 {
		log.Error("IPFSRecord: %v invalid entries", invalidCount)
	}
}

// TODO Don't use Shell directly - invent an interface.  This would enable mocking.
type IPFSPeer struct {
	Url         string
	Client      *gohttp.Client
	Shell       *ipfs.Shell
	PingTimeout time.Duration
	pinger      *ipfs.Shell
}

func (peer *IPFSPeer) Connect() error {
	if peer.PingTimeout == 0 {
		peer.PingTimeout = __DEFAULT_PING_TIMEOUT
	}

	if peer.Client == nil {
		log.Info("Using default HTTP client")
		peer.Client = http.DefaultBackendClient()
	}

	log.Info("Connecting to IPFS API...")
	pingClient := http.DefaultBackendClient()
	pingClient.Timeout = peer.PingTimeout
	peer.Shell = ipfs.NewShellWithClient(peer.Url, peer.Client)
	peer.pinger = ipfs.NewShellWithClient(peer.Url, pingClient)
	err := peer.validateConnection()

	if err == nil {
		log.Info("IPFS API Connection OK")
	}

	return err
}

func (peer *IPFSPeer) Disconnect() error {
	// Nothing to do.
	return nil
}

func (peer *IPFSPeer) validateShell() error {
	if peer.Shell == nil {
		return peer.Connect()
	}

	return peer.validateConnection()
}

func (peer *IPFSPeer) validateConnection() error {
	if !peer.pinger.IsUp() {
		return fmt.Errorf("IPFSPeer is not up at '%v'", peer.Url)
	}

	return nil
}

func (peer *IPFSPeer) PublishAddr(addr crdt.Link, topics []api.PubSubTopic) error {
	const failMsg = "IPFSPeer.PublishAddr failed"

	if verr := peer.validateShell(); verr != nil {
		return verr
	}

	publishValue, printErr := crdt.PrintLink(addr)

	if printErr != nil {
		return errors.Wrap(printErr, failMsg)
	}

	for _, t := range topics {
		topicText := string(t)
		log.Info("Publishing to topic: %v", t)
		pubsubErr := peer.Shell.PubSubPublish(topicText, string(publishValue))

		if pubsubErr != nil {
			log.Warn("Pubsub failed (topic %v): %v", t, pubsubErr.Error())
			continue
		}

		log.Info("Published to topic: %v", t)
	}

	return nil
}

func (peer *IPFSPeer) SubscribeAddrStream(topic api.PubSubTopic) (<-chan crdt.Link, <-chan error) {
	stream := make(chan crdt.Link)
	errch := make(chan error)

	tidy := func() {
		close(stream)
		close(errch)
	}

	if verr := peer.validateShell(); verr != nil {
		go func() {
			errch <- verr
			defer tidy()
		}()

		return stream, errch
	}

	go func() {
		defer tidy()

		topicText := string(topic)

		var subscription *ipfs.PubSubSubscription

	RESTART:
		for {
			var launchErr error
			log.Info("(Re)starting subscription on %v", topic)
			subscription, launchErr = peer.Shell.PubSubSubscribe(topicText)

			if launchErr != nil {
				log.Error("Subcription launch failed, retrying: %v", launchErr.Error())
				continue
			}

			for {
				log.Info("Fetching next subscription message on %v...", topic)
				record, recordErr := subscription.Next()

				if recordErr != nil {
					log.Error("Subscription read failed (topic %v), continuing: %v", topic, recordErr.Error())
					continue RESTART
				}

				pubsubPeer := record.From()
				bs := record.Data()
				addr, err := crdt.ParseLink(crdt.LinkText(bs))

				if err != nil {
					log.Warn("Bad link from peer (topic %v): %v", topic, pubsubPeer)
					continue
				}

				stream <- addr
				log.Info("Subscription update: '%v' from '%v'", addr, pubsubPeer)
			}
		}

	}()

	return stream, errch
}

func (peer *IPFSPeer) AddIndex(index crdt.Index) (crdt.IPFSPath, error) {
	const failMsg = "IPFSPeer.AddIndex failed"

	log.Info("Adding index to IPFS...")

	if verr := peer.validateShell(); verr != nil {
		return crdt.NIL_PATH, verr
	}

	chunk := makeIpfsIndex(index)

	path, addErr := peer.add(chunk)

	if addErr != nil {
		return crdt.NIL_PATH, errors.Wrap(addErr, failMsg)
	}

	log.Info("Added index")

	return path, nil
}

func (peer *IPFSPeer) CatIndex(addr crdt.IPFSPath) (crdt.Index, error) {
	log.Info("Catting index from IPFS...")

	if verr := peer.validateShell(); verr != nil {
		return crdt.EmptyIndex(), verr
	}

	chunk := &IPFSIndex{}
	caterr := peer.cat(addr, chunk)

	if caterr != nil {
		return crdt.EmptyIndex(), errors.Wrap(caterr, "IPFSPeer.CatNamespace failed")
	}

	log.Info("Catted index")

	return chunk.Index, nil
}

func (peer *IPFSPeer) AddNamespace(namespace crdt.Namespace) (crdt.IPFSPath, error) {
	log.Info("Adding Namespace to IPFS...")

	if verr := peer.validateShell(); verr != nil {
		return crdt.NIL_PATH, verr
	}

	chunk := makeIpfsRecord(namespace)

	path, err := peer.add(chunk)

	if err != nil {
		return crdt.NIL_PATH, errors.Wrap(err, "IPFSPeer.AddNamespace failed")
	}

	log.Info("Added namespace")

	return path, nil
}

func (peer *IPFSPeer) CatNamespace(addr crdt.IPFSPath) (crdt.Namespace, error) {
	log.Info("Catting namespace from IPFS...")

	if verr := peer.validateShell(); verr != nil {
		return crdt.EmptyNamespace(), verr
	}

	chunk := &IPFSRecord{}
	caterr := peer.cat(addr, chunk)

	if caterr != nil {
		return crdt.EmptyNamespace(), errors.Wrap(caterr, "IPFSPeer.CatNamespace failed")
	}

	log.Info("Catted namespace")

	return chunk.Namespace, nil
}

func (peer *IPFSPeer) add(chunk encoder) (crdt.IPFSPath, error) {
	const failMsg = "IPFSPeer.add failed"
	buff := &bytes.Buffer{}
	err := chunk.encode(buff)

	if err != nil {
		return crdt.NIL_PATH, errors.Wrap(err, failMsg)
	}

	path, sherr := peer.Shell.Add(buff)

	if sherr != nil {
		return crdt.NIL_PATH, errors.Wrap(err, failMsg)
	}

	return crdt.IPFSPath(path), nil
}

func (peer *IPFSPeer) cat(path crdt.IPFSPath, out decoder) error {
	const failMsg = "IPFSPeer.cat failed"
	reader, err := peer.Shell.Cat(string(path))

	if err != nil {
		return errors.Wrap(err, failMsg)
	}

	defer reader.Close()

	err = out.decode(reader)

	if err != nil {
		return errors.Wrap(err, failMsg)
	}

	// According to IPFS binding docs we must drain the reader.
	remainder, drainerr := ioutil.ReadAll(reader)

	if drainerr != nil {
		log.Warn("error draining reader: %v", drainerr)
	}

	if len(remainder) != 0 {
		log.Warn("remaining bits after gob: %v", remainder)
	}

	return nil
}

const __DEFAULT_PING_TIMEOUT = time.Second * 5
<commit_msg>Log cat addrs<commit_after>package ipfs

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	gohttp "net/http"
	"time"

	ipfs "github.com/ipfs/go-ipfs-api"
	"github.com/johnny-morrice/godless/api"
	"github.com/johnny-morrice/godless/crdt"
	"github.com/johnny-morrice/godless/internal/http"
	"github.com/johnny-morrice/godless/log"
	"github.com/pkg/errors"
)

type IPFSRecord struct {
	Namespace crdt.Namespace
}

func makeIpfsRecord(namespace crdt.Namespace) *IPFSRecord {
	return &IPFSRecord{
		Namespace: namespace,
	}
}

func (record *IPFSRecord) encode(w io.Writer) error {
	invalid, err := crdt.EncodeNamespace(record.Namespace, w)

	record.logInvalid(invalid)

	return err
}

func (record *IPFSRecord) decode(r io.Reader) error {
	ns, invalid, err := crdt.DecodeNamespace(r)

	record.logInvalid(invalid)

	if err != nil {
		return err
	}

	record.Namespace = ns
	return nil
}

func (record *IPFSRecord) logInvalid(invalid []crdt.InvalidNamespaceEntry) {
	invalidCount := len(invalid)

	if invalidCount > 0 {
		log.Error("IPFSRecord: %v invalid entries", invalidCount)
	}
}

type encoder interface {
	encode(io.Writer) error
}

type decoder interface {
	decode(io.Reader) error
}

type IPFSIndex struct {
	Index crdt.Index
}

func makeIpfsIndex(index crdt.Index) *IPFSIndex {
	return &IPFSIndex{
		Index: index,
	}
}

func (index *IPFSIndex) encode(w io.Writer) error {
	invalid, err := crdt.EncodeIndex(index.Index, w)

	index.logInvalid(invalid)

	return err
}

func (index *IPFSIndex) decode(r io.Reader) error {
	dx, invalid, err := crdt.DecodeIndex(r)

	index.logInvalid(invalid)

	if err != nil {
		return err
	}

	// TODO should cache the invalid details.
	if len(invalid) > 0 {
		log.Warn("IPFSIndex Decoded invalid index entries")
	}

	index.Index = dx
	return nil
}

func (index *IPFSIndex) logInvalid(invalid []crdt.InvalidIndexEntry) {
	invalidCount := len(invalid)

	if invalidCount > 0 {
		log.Error("IPFSRecord: %v invalid entries", invalidCount)
	}
}

// TODO Don't use Shell directly - invent an interface.  This would enable mocking.
type IPFSPeer struct {
	Url         string
	Client      *gohttp.Client
	Shell       *ipfs.Shell
	PingTimeout time.Duration
	pinger      *ipfs.Shell
}

func (peer *IPFSPeer) Connect() error {
	if peer.PingTimeout == 0 {
		peer.PingTimeout = __DEFAULT_PING_TIMEOUT
	}

	if peer.Client == nil {
		log.Info("Using default HTTP client")
		peer.Client = http.DefaultBackendClient()
	}

	log.Info("Connecting to IPFS API...")
	pingClient := http.DefaultBackendClient()
	pingClient.Timeout = peer.PingTimeout
	peer.Shell = ipfs.NewShellWithClient(peer.Url, peer.Client)
	peer.pinger = ipfs.NewShellWithClient(peer.Url, pingClient)
	err := peer.validateConnection()

	if err == nil {
		log.Info("IPFS API Connection OK")
	}

	return err
}

func (peer *IPFSPeer) Disconnect() error {
	// Nothing to do.
	return nil
}

func (peer *IPFSPeer) validateShell() error {
	if peer.Shell == nil {
		return peer.Connect()
	}

	return peer.validateConnection()
}

func (peer *IPFSPeer) validateConnection() error {
	if !peer.pinger.IsUp() {
		return fmt.Errorf("IPFSPeer is not up at '%v'", peer.Url)
	}

	return nil
}

func (peer *IPFSPeer) PublishAddr(addr crdt.Link, topics []api.PubSubTopic) error {
	const failMsg = "IPFSPeer.PublishAddr failed"

	if verr := peer.validateShell(); verr != nil {
		return verr
	}

	publishValue, printErr := crdt.PrintLink(addr)

	if printErr != nil {
		return errors.Wrap(printErr, failMsg)
	}

	for _, t := range topics {
		topicText := string(t)
		log.Info("Publishing to topic: %v", t)
		pubsubErr := peer.Shell.PubSubPublish(topicText, string(publishValue))

		if pubsubErr != nil {
			log.Warn("Pubsub failed (topic %v): %v", t, pubsubErr.Error())
			continue
		}

		log.Info("Published to topic: %v", t)
	}

	return nil
}

func (peer *IPFSPeer) SubscribeAddrStream(topic api.PubSubTopic) (<-chan crdt.Link, <-chan error) {
	stream := make(chan crdt.Link)
	errch := make(chan error)

	tidy := func() {
		close(stream)
		close(errch)
	}

	if verr := peer.validateShell(); verr != nil {
		go func() {
			errch <- verr
			defer tidy()
		}()

		return stream, errch
	}

	go func() {
		defer tidy()

		topicText := string(topic)

		var subscription *ipfs.PubSubSubscription

	RESTART:
		for {
			var launchErr error
			log.Info("(Re)starting subscription on %v", topic)
			subscription, launchErr = peer.Shell.PubSubSubscribe(topicText)

			if launchErr != nil {
				log.Error("Subcription launch failed, retrying: %v", launchErr.Error())
				continue
			}

			for {
				log.Info("Fetching next subscription message on %v...", topic)
				record, recordErr := subscription.Next()

				if recordErr != nil {
					log.Error("Subscription read failed (topic %v), continuing: %v", topic, recordErr.Error())
					continue RESTART
				}

				pubsubPeer := record.From()
				bs := record.Data()
				addr, err := crdt.ParseLink(crdt.LinkText(bs))

				if err != nil {
					log.Warn("Bad link from peer (topic %v): %v", topic, pubsubPeer)
					continue
				}

				stream <- addr
				log.Info("Subscription update: '%v' from '%v'", addr, pubsubPeer)
			}
		}

	}()

	return stream, errch
}

func (peer *IPFSPeer) AddIndex(index crdt.Index) (crdt.IPFSPath, error) {
	const failMsg = "IPFSPeer.AddIndex failed"

	log.Info("Adding index to IPFS...")

	if verr := peer.validateShell(); verr != nil {
		return crdt.NIL_PATH, verr
	}

	chunk := makeIpfsIndex(index)

	path, addErr := peer.add(chunk)

	if addErr != nil {
		return crdt.NIL_PATH, errors.Wrap(addErr, failMsg)
	}

	log.Info("Added index")

	return path, nil
}

func (peer *IPFSPeer) CatIndex(addr crdt.IPFSPath) (crdt.Index, error) {
	log.Info("Catting index from IPFS at: %v ...", addr)

	if verr := peer.validateShell(); verr != nil {
		return crdt.EmptyIndex(), verr
	}

	chunk := &IPFSIndex{}
	caterr := peer.cat(addr, chunk)

	if caterr != nil {
		return crdt.EmptyIndex(), errors.Wrap(caterr, "IPFSPeer.CatNamespace failed")
	}

	log.Info("Catted index")

	return chunk.Index, nil
}

func (peer *IPFSPeer) AddNamespace(namespace crdt.Namespace) (crdt.IPFSPath, error) {
	log.Info("Adding Namespace to IPFS...")

	if verr := peer.validateShell(); verr != nil {
		return crdt.NIL_PATH, verr
	}

	chunk := makeIpfsRecord(namespace)

	path, err := peer.add(chunk)

	if err != nil {
		return crdt.NIL_PATH, errors.Wrap(err, "IPFSPeer.AddNamespace failed")
	}

	log.Info("Added namespace")

	return path, nil
}

func (peer *IPFSPeer) CatNamespace(addr crdt.IPFSPath) (crdt.Namespace, error) {
	log.Info("Catting namespace from IPFS at: %v ...", addr)

	if verr := peer.validateShell(); verr != nil {
		return crdt.EmptyNamespace(), verr
	}

	chunk := &IPFSRecord{}
	caterr := peer.cat(addr, chunk)

	if caterr != nil {
		return crdt.EmptyNamespace(), errors.Wrap(caterr, "IPFSPeer.CatNamespace failed")
	}

	log.Info("Catted namespace")

	return chunk.Namespace, nil
}

func (peer *IPFSPeer) add(chunk encoder) (crdt.IPFSPath, error) {
	const failMsg = "IPFSPeer.add failed"
	buff := &bytes.Buffer{}
	err := chunk.encode(buff)

	if err != nil {
		return crdt.NIL_PATH, errors.Wrap(err, failMsg)
	}

	path, sherr := peer.Shell.Add(buff)

	if sherr != nil {
		return crdt.NIL_PATH, errors.Wrap(err, failMsg)
	}

	return crdt.IPFSPath(path), nil
}

func (peer *IPFSPeer) cat(path crdt.IPFSPath, out decoder) error {
	const failMsg = "IPFSPeer.cat failed"
	reader, err := peer.Shell.Cat(string(path))

	if err != nil {
		return errors.Wrap(err, failMsg)
	}

	defer reader.Close()

	err = out.decode(reader)

	if err != nil {
		return errors.Wrap(err, failMsg)
	}

	// According to IPFS binding docs we must drain the reader.
	remainder, drainerr := ioutil.ReadAll(reader)

	if drainerr != nil {
		log.Warn("error draining reader: %v", drainerr)
	}

	if len(remainder) != 0 {
		log.Warn("remaining bits after gob: %v", remainder)
	}

	return nil
}

const __DEFAULT_PING_TIMEOUT = time.Second * 5
<|endoftext|>