<commit_before>package tail

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

const (
	RotateMarker   = "__ROTATE__\n"
	TruncateMarker = "__TRUNCATE__\n"
	EOFMarker      = "__EOF__\n"
)

var Logs = []string{
	"single line\n",
	"multi line 1\nmulti line 2\nmulti line 3\n",
	"continuous line 1", "continuous line 2", "continuous line 3\n",
	RotateMarker,
	"foo\n",
	"bar\n",
	"baz\n",
	TruncateMarker,
	"FOOOO\n",
	"BAAAR\n",
	"BAZZZZZZZ\n",
	EOFMarker,
}

func TestTailFile(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "go-tail.")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	go writeFile(t, tmpdir)
	tail, err := NewTailFile(filepath.Join(tmpdir, "test.log"))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer tail.Close()

	expected := strings.Join(Logs, "")
	actual, err := receive(t, tail)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if actual != expected {
		t.Errorf("got %s\nwant %s", actual, expected)
	}
}

func TestTailReader(t *testing.T) {
	reader, writer := io.Pipe()

	go writeWriter(t, writer)
	tail, err := NewTailReader(reader)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	expected := strings.Join(Logs, "")
	actual, err := receive(t, tail)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if actual != expected {
		t.Errorf("got %s\nwant %s", actual, expected)
	}

	reader.Close()
	writer.Close()
	select {
	case _, ok := <-tail.Lines:
		if ok {
			t.Error("want closed, but not")
		}
	case <-time.After(time.Second):
		t.Error("want closed, but not")
	}
	select {
	case _, ok := <-tail.Errors:
		if ok {
			t.Error("want closed, but not")
		}
	case <-time.After(time.Second):
		t.Error("want closed, but not")
	}
	tail.Close()
}

func writeFile(t *testing.T, tmpdir string) error {
	filename := filepath.Join(tmpdir, "test.log")
	file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}

	// wait for starting to tail...
	time.Sleep(2 * time.Second)

	for _, line := range Logs {
		_, err := file.WriteString(line)
		if err != nil {
			return err
		}
		t.Logf("write: %s", line)
		switch line {
		case RotateMarker:
			file.Close()
			os.Rename(filename, filename+".old")
			file, _ = os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
		case TruncateMarker:
			time.Sleep(1 * time.Second)
			os.Truncate(filename, 0)
			file.Seek(int64(0), os.SEEK_SET)
		}
		time.Sleep(10 * time.Millisecond)
	}

	file.Close()
	return nil
}

func writeWriter(t *testing.T, writer io.Writer) error {
	w := bufio.NewWriter(writer)
	for _, line := range Logs {
		_, err := w.WriteString(line)
		if err != nil {
			return err
		}
		w.Flush()
		t.Logf("write: %s", line)
		time.Sleep(10 * time.Millisecond)
	}
	return nil
}

func receive(t *testing.T, tail *Tail) (string, error) {
	actual := ""
	for {
		select {
		case line := <-tail.Lines:
			t.Logf("received: %s", line.Text)
			actual += line.Text
			if line.Text == EOFMarker {
				return actual, nil
			}
		case err := <-tail.Errors:
			return "", err
		case <-time.After(5 * time.Second):
			return "", errors.New("timeout")
		}
	}
}

func TestTailFile_Rotate(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "go-tail.")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	go func() {
		filename := filepath.Join(tmpdir, "test.log")
		for i := 0; i < 10; i++ {
			file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
			if err != nil {
				t.Error(err)
				return
			}
			if i == 0 {
				// wait for starting to tail...
				time.Sleep(2 * time.Second)
			}

			// start to write logs
			go writeFileAndClose(t, file, fmt.Sprintf("file: %d\n", i))
			time.Sleep(time.Second)

			// Rotate log file, and start writing logs into a new file.
			// While, some logs are still written into the old file.
			os.Rename(filename, fmt.Sprintf("%s.%d", filename, i))
		}
	}()

	tail, err := NewTailFile(filepath.Join(tmpdir, "test.log"))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	go func() {
		time.Sleep(25 * time.Second)
		tail.Close()
	}()

	var cnt int
	for range tail.Lines {
		cnt++
	}
	if cnt != 1000 {
		t.Errorf("want 1000, got %d", cnt)
	}
}

func writeFileAndClose(t *testing.T, file *os.File, line string) {
	for i := 0; i < 100; i++ {
		_, err := file.WriteString(line)
		if err != nil {
			t.Error(err)
			return
		}
		time.Sleep(90 * time.Millisecond)
	}

	if err := file.Close(); err != nil {
		t.Error(err)
	}
}
<commit_msg>log errors<commit_after>package tail

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

const (
	RotateMarker   = "__ROTATE__\n"
	TruncateMarker = "__TRUNCATE__\n"
	EOFMarker      = "__EOF__\n"
)

var Logs = []string{
	"single line\n",
	"multi line 1\nmulti line 2\nmulti line 3\n",
	"continuous line 1", "continuous line 2", "continuous line 3\n",
	RotateMarker,
	"foo\n",
	"bar\n",
	"baz\n",
	TruncateMarker,
	"FOOOO\n",
	"BAAAR\n",
	"BAZZZZZZZ\n",
	EOFMarker,
}

func TestTailFile(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "go-tail.")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	go writeFile(t, tmpdir)
	tail, err := NewTailFile(filepath.Join(tmpdir, "test.log"))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer tail.Close()

	expected := strings.Join(Logs, "")
	actual, err := receive(t, tail)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if actual != expected {
		t.Errorf("got %s\nwant %s", actual, expected)
	}
}

func TestTailReader(t *testing.T) {
	reader, writer := io.Pipe()

	go writeWriter(t, writer)
	tail, err := NewTailReader(reader)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	expected := strings.Join(Logs, "")
	actual, err := receive(t, tail)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if actual != expected {
		t.Errorf("got %s\nwant %s", actual, expected)
	}

	reader.Close()
	writer.Close()
	select {
	case _, ok := <-tail.Lines:
		if ok {
			t.Error("want closed, but not")
		}
	case <-time.After(time.Second):
		t.Error("want closed, but not")
	}
	select {
	case _, ok := <-tail.Errors:
		if ok {
			t.Error("want closed, but not")
		}
	case <-time.After(time.Second):
		t.Error("want closed, but not")
	}
	tail.Close()
}

func writeFile(t *testing.T, tmpdir string) error {
	filename := filepath.Join(tmpdir, "test.log")
	file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}

	// wait for starting to tail...
	time.Sleep(2 * time.Second)

	for _, line := range Logs {
		_, err := file.WriteString(line)
		if err != nil {
			return err
		}
		t.Logf("write: %s", line)
		switch line {
		case RotateMarker:
			file.Close()
			os.Rename(filename, filename+".old")
			file, _ = os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
		case TruncateMarker:
			time.Sleep(1 * time.Second)
			os.Truncate(filename, 0)
			file.Seek(int64(0), os.SEEK_SET)
		}
		time.Sleep(10 * time.Millisecond)
	}

	file.Close()
	return nil
}

func writeWriter(t *testing.T, writer io.Writer) error {
	w := bufio.NewWriter(writer)
	for _, line := range Logs {
		_, err := w.WriteString(line)
		if err != nil {
			return err
		}
		w.Flush()
		t.Logf("write: %s", line)
		time.Sleep(10 * time.Millisecond)
	}
	return nil
}

func receive(t *testing.T, tail *Tail) (string, error) {
	actual := ""
	for {
		select {
		case line := <-tail.Lines:
			t.Logf("received: %s", line.Text)
			actual += line.Text
			if line.Text == EOFMarker {
				return actual, nil
			}
		case err := <-tail.Errors:
			return "", err
		case <-time.After(5 * time.Second):
			return "", errors.New("timeout")
		}
	}
}

func TestTailFile_Rotate(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "go-tail.")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	go func() {
		filename := filepath.Join(tmpdir, "test.log")
		for i := 0; i < 10; i++ {
			file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
			if err != nil {
				t.Error(err)
				return
			}
			if i == 0 {
				// wait for starting to tail...
				time.Sleep(2 * time.Second)
			}

			// start to write logs
			go writeFileAndClose(t, file, fmt.Sprintf("file: %d\n", i))
			time.Sleep(time.Second)

			// Rotate log file, and start writing logs into a new file.
			// While, some logs are still written into the old file.
			os.Rename(filename, fmt.Sprintf("%s.%d", filename, i))
		}
	}()

	tail, err := NewTailFile(filepath.Join(tmpdir, "test.log"))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	go func() {
		time.Sleep(25 * time.Second)
		tail.Close()
	}()
	go func() {
		for err := range tail.Errors {
			t.Log("error: ", err)
		}
	}()

	var cnt int
	for range tail.Lines {
		cnt++
	}
	if cnt != 1000 {
		t.Errorf("want 1000, got %d", cnt)
	}
}

func writeFileAndClose(t *testing.T, file *os.File, line string) {
	for i := 0; i < 100; i++ {
		_, err := file.WriteString(line)
		if err != nil {
			t.Error(err)
			return
		}
		time.Sleep(90 * time.Millisecond)
	}

	if err := file.Close(); err != nil {
		t.Error(err)
	}
}
<|endoftext|>


<commit_before>package configgtm

import (
	"fmt"
	"github.com/akamai/AkamaiOPEN-edgegrid-golang/client-v1"
	"net/http"
	"strings"
)

//
// Support gtm domains thru Edgegrid
// Based on 1.3 Schema
//

// The Domain data structure represents a GTM domain
type Domain struct {
	Name                         string          `json:"name"`
	Type                         string          `json:"type"`
	AsMaps                       []*AsMap        `json:"asMaps,omitempty"`
	Resources                    []*Resource     `json:"resources,omitempty"`
	DefaultUnreachableThreshold  float32         `json:"defaultUnreachableThreshold,omitempty"`
	EmailNotificationList        []string        `json:"emailNotificationList,omitempty"`
	MinPingableRegionFraction    float32         `json:"minPingableRegionFraction,omitempty"`
	DefaultTimeoutPenalty        int             `json:"defaultTimeoutPenalty,omitempty"`
	Datacenters                  []*Datacenter   `json:"datacenters"`
	ServermonitorLivenessCount   int             `json:"servermonitorLivenessCount,omitempty"`
	RoundRobinPrefix             string          `json:"roundRobinPrefix,omitempty"`
	ServermonitorLoadCount       int             `json:"servermonitorLoadCount,omitempty"`
	PingInterval                 int             `json:"pingInterval,omitempty"`
	MaxTTL                       int64           `json:"maxTTL,omitempty"`
	LoadImbalancePercentage      float64         `json:"loadImbalancePercentage,omitempty"`
	DefaultHealthMax             int             `json:"defaultHealthMax,omitempty"`
	LastModified                 string          `json:"lastModified,omitempty"`
	Status                       *ResponseStatus `json:"status,omitempty"`
	MapUpdateInterval            int             `json:"mapUpdateInterval,omitempty"`
	MaxProperties                int             `json:"maxProperties,omitempty"`
	MaxResources                 int             `json:"maxResources,omitempty"`
	DefaultSslClientPrivateKey   string          `json:"defaultSslClientPrivateKey,omitempty"`
	DefaultErrorPenalty          int             `json:"defaultErrorPenalty,omitempty"`
	Links                        []*Link         `json:"links,omitempty"`
	Properties                   []*Property     `json:"properties,omitempty"`
	MaxTestTimeout               float64         `json:"maxTestTimeout,omitempty"`
	CnameCoalescingEnabled       bool            `json:"cnameCoalescingEnabled,omitempty"`
	DefaultHealthMultiplier      int             `json:"defaultHealthMultiplier,omitempty"`
	ServermonitorPool            string          `json:"servermonitorPool,omitempty"`
	LoadFeedback                 bool            `json:"loadFeedback,omitempty"`
	MinTTL                       int64           `json:"minTTL,omitempty"`
	GeographicMaps               []*GeoMap       `json:"geographicMaps,omitempty"`
	CidrMaps                     []*CidrMap      `json:"cidrMaps,omitempty"`
	DefaultMaxUnreachablePenalty int             `json:"defaultMaxUnreachablePenalty,omitempty"`
	DefaultHealthThreshold       int             `json:"defaultHealthThreshold,omitempty"`
	LastModifiedBy               string          `json:"lastModifiedBy,omitempty"`
	ModificationComments         string          `json:"modificationComments,omitempty"`
	MinTestInterval              int             `json:"minTestInterval,omitempty"`
	PingPacketSize               int             `json:"pingPacketSize,omitempty"`
	DefaultSslClientCertificate  string          `json:"defaultSslClientCertificate,omitempty"`
}

type DomainsList struct {
	DomainItems []*DomainItem `json:"items"`
}

// DomainItem is a DomainsList item
type DomainItem struct {
	AcgId        string  `json:"acgId"`
	LastModified string  `json:"lastModified"`
	Links        []*Link `json:"links"`
	Name         string  `json:"name"`
	Status       string  `json:"status"`
}

// NewDomain is a utility function that creates a new Domain object.
func NewDomain(domainName, domainType string) *Domain {
	domain := &Domain{}
	domain.Name = domainName
	domain.Type = domainType
	return domain
}

// GetStatus retrieves current status for the given domainname.
func GetDomainStatus(domainName string) (*ResponseStatus, error) {
	stat := &ResponseStatus{}
	req, err := client.NewRequest(
		Config,
		"GET",
		fmt.Sprintf("/config-gtm/v1/domains/%s/status/current", domainName),
		nil,
	)
	if err != nil {
		return nil, err
	}

	setVersionHeader(req, schemaVersion)

	printHttpRequest(req, true)

	res, err := client.Do(Config, req)
	if err != nil {
		return nil, err
	}

	printHttpResponse(res, true)

	if client.IsError(res) && res.StatusCode != 404 {
		return nil, client.NewAPIError(res)
	} else if res.StatusCode == 404 {
		return nil, CommonError{entityName: "Domain", name: domainName}
	} else {
		err = client.BodyJSON(res, stat)
		if err != nil {
			return nil, err
		}

		return stat, nil
	}
}

// ListDomains retrieves all Domains.
func ListDomains() ([]*DomainItem, error) {
	domains := &DomainsList{}
	req, err := client.NewRequest(
		Config,
		"GET",
		"/config-gtm/v1/domains/",
		nil,
	)
	if err != nil {
		return nil, err
	}

	setVersionHeader(req, schemaVersion)

	printHttpRequest(req, true)

	res, err := client.Do(Config, req)
	if err != nil {
		return nil, err
	}

	printHttpResponse(res, true)

	if client.IsError(res) && res.StatusCode != 404 {
		return nil, client.NewAPIError(res)
	} else if res.StatusCode == 404 {
		return nil, CommonError{entityName: "Domain"}
	} else {
		err = client.BodyJSON(res, domains)
		if err != nil {
			return nil, err
		}

		return domains.DomainItems, nil
	}
}

// GetDomain retrieves a Domain with the given domainname.
func GetDomain(domainName string) (*Domain, error) {
	domain := NewDomain(domainName, "basic")
	req, err := client.NewRequest(
		Config,
		"GET",
		fmt.Sprintf("/config-gtm/v1/domains/%s", domainName),
		nil,
	)
	if err != nil {
		return nil, err
	}

	setVersionHeader(req, schemaVersion)

	printHttpRequest(req, true)

	res, err := client.Do(Config, req)
	if err != nil {
		return nil, err
	}

	printHttpResponse(res, true)

	if client.IsError(res) && res.StatusCode != 404 {
		return nil, client.NewAPIError(res)
	} else if res.StatusCode == 404 {
		return nil, CommonError{entityName: "Domain", name: domainName}
	} else {
		err = client.BodyJSON(res, domain)
		if err != nil {
			return nil, err
		}

		return domain, nil
	}
}

// Save method; Create or Update
func (domain *Domain) save(queryArgs map[string]string, req *http.Request) (*DomainResponse, error) {

	// set schema version
	setVersionHeader(req, schemaVersion)

	// Look for optional args
	if len(queryArgs) > 0 {
		q := req.URL.Query()
		if val, ok := queryArgs["contractId"]; ok {
        		q.Add("contractId", strings.TrimPrefix(val, "ctr_"))
		}
		if val, ok := queryArgs["gid"]; ok {
			q.Add("gid", strings.TrimPrefix(val, "grp_"))
		}
		req.URL.RawQuery = q.Encode()
	}

	printHttpRequest(req, true)

	res, err := client.Do(Config, req)

	printHttpResponse(res, true)

	// Network error
	if err != nil {
		return nil, CommonError{
			entityName:       "Domain",
			name:             domain.Name,
			httpErrorMessage: err.Error(),
			err:              err,
		}
	}

	// API error
	if client.IsError(res) {
		err := client.NewAPIError(res)
		return nil, CommonError{entityName: "Domain", name: domain.Name, apiErrorMessage: err.Detail, err: err}
	}

	// TODO: What validation can we do? E.g. if not equivalent there was a concurrent change...
	responseBody := &DomainResponse{}
	// Unmarshall whole response body in case want status
	err = client.BodyJSON(res, responseBody)
	if err != nil {
		return nil, err
	}

	return responseBody, nil

}

// Create is a method applied to a domain object resulting in creation.
func (domain *Domain) Create(queryArgs map[string]string) (*DomainResponse, error) {

	req, err := client.NewJSONRequest(
		Config,
		"POST",
		fmt.Sprintf("/config-gtm/v1/domains/"),
		domain,
	)
	if err != nil {
		return nil, err
	}

	return domain.save(queryArgs, req)

}

// Update is a method applied to a domain object resulting in an update.
func (domain *Domain) Update(queryArgs map[string]string) (*ResponseStatus, error) {

	// Any validation to do?
	req, err := client.NewJSONRequest(
		Config,
		"PUT",
		fmt.Sprintf("/config-gtm/v1/domains/%s", domain.Name),
		domain,
	)
	if err != nil {
		return nil, err
	}

	stat, err := domain.save(queryArgs, req)
	if err != nil {
		return nil, err
	}
	return stat.Status, err
}

// Delete is a method applied to a domain object resulting in removal.
func (domain *Domain) Delete() (*ResponseStatus, error) {

	req, err := client.NewRequest(
		Config,
		"DELETE",
		fmt.Sprintf("/config-gtm/v1/domains/%s", domain.Name),
		nil,
	)
	if err != nil {
		return nil, err
	}

	setVersionHeader(req, schemaVersion)

	printHttpRequest(req, true)

	res, err := client.Do(Config, req)
	if err != nil {
		return nil, err
	}

	printHttpResponse(res, true)

	// Network error
	if err != nil {
		return nil, CommonError{
			entityName:       "Domain",
			name:             domain.Name,
			httpErrorMessage: err.Error(),
			err:              err,
		}
	}

	// API error
	if client.IsError(res) {
		err := client.NewAPIError(res)
		return nil, CommonError{entityName: "Domain", name: domain.Name, apiErrorMessage: err.Detail, err: err}
	}

	responseBody := &ResponseBody{}
	// Unmarshall whole response body in case want status
	err = client.BodyJSON(res, responseBody)
	if err != nil {
		return nil, err
	}

	return responseBody.Status, nil

}
<commit_msg>formatting<commit_after>package configgtm

import (
	"fmt"
	"github.com/akamai/AkamaiOPEN-edgegrid-golang/client-v1"
	"net/http"
	"strings"
)

//
// Support gtm domains thru Edgegrid
// Based on 1.3 Schema
//

// The Domain data structure represents a GTM domain
type Domain struct {
	Name                         string          `json:"name"`
	Type                         string          `json:"type"`
	AsMaps                       []*AsMap        `json:"asMaps,omitempty"`
	Resources                    []*Resource     `json:"resources,omitempty"`
	DefaultUnreachableThreshold  float32         `json:"defaultUnreachableThreshold,omitempty"`
	EmailNotificationList        []string        `json:"emailNotificationList,omitempty"`
	MinPingableRegionFraction    float32         `json:"minPingableRegionFraction,omitempty"`
	DefaultTimeoutPenalty        int             `json:"defaultTimeoutPenalty,omitempty"`
	Datacenters                  []*Datacenter   `json:"datacenters"`
	ServermonitorLivenessCount   int             `json:"servermonitorLivenessCount,omitempty"`
	RoundRobinPrefix             string          `json:"roundRobinPrefix,omitempty"`
	ServermonitorLoadCount       int             `json:"servermonitorLoadCount,omitempty"`
	PingInterval                 int             `json:"pingInterval,omitempty"`
	MaxTTL                       int64           `json:"maxTTL,omitempty"`
	LoadImbalancePercentage      float64         `json:"loadImbalancePercentage,omitempty"`
	DefaultHealthMax             int             `json:"defaultHealthMax,omitempty"`
	LastModified                 string          `json:"lastModified,omitempty"`
	Status                       *ResponseStatus `json:"status,omitempty"`
	MapUpdateInterval            int             `json:"mapUpdateInterval,omitempty"`
	MaxProperties                int             `json:"maxProperties,omitempty"`
	MaxResources                 int             `json:"maxResources,omitempty"`
	DefaultSslClientPrivateKey   string          `json:"defaultSslClientPrivateKey,omitempty"`
	DefaultErrorPenalty          int             `json:"defaultErrorPenalty,omitempty"`
	Links                        []*Link         `json:"links,omitempty"`
	Properties                   []*Property     `json:"properties,omitempty"`
	MaxTestTimeout               float64         `json:"maxTestTimeout,omitempty"`
	CnameCoalescingEnabled       bool            `json:"cnameCoalescingEnabled,omitempty"`
	DefaultHealthMultiplier      int             `json:"defaultHealthMultiplier,omitempty"`
	ServermonitorPool            string          `json:"servermonitorPool,omitempty"`
	LoadFeedback                 bool            `json:"loadFeedback,omitempty"`
	MinTTL                       int64           `json:"minTTL,omitempty"`
	GeographicMaps               []*GeoMap       `json:"geographicMaps,omitempty"`
	CidrMaps                     []*CidrMap      `json:"cidrMaps,omitempty"`
	DefaultMaxUnreachablePenalty int             `json:"defaultMaxUnreachablePenalty,omitempty"`
	DefaultHealthThreshold       int             `json:"defaultHealthThreshold,omitempty"`
	LastModifiedBy               string          `json:"lastModifiedBy,omitempty"`
	ModificationComments         string          `json:"modificationComments,omitempty"`
	MinTestInterval              int             `json:"minTestInterval,omitempty"`
	PingPacketSize               int             `json:"pingPacketSize,omitempty"`
	DefaultSslClientCertificate  string          `json:"defaultSslClientCertificate,omitempty"`
}

type DomainsList struct {
	DomainItems []*DomainItem `json:"items"`
}

// DomainItem is a DomainsList item
type DomainItem struct {
	AcgId        string  `json:"acgId"`
	LastModified string  `json:"lastModified"`
	Links        []*Link `json:"links"`
	Name         string  `json:"name"`
	Status       string  `json:"status"`
}

// NewDomain is a utility function that creates a new Domain object.
func NewDomain(domainName, domainType string) *Domain {
	domain := &Domain{}
	domain.Name = domainName
	domain.Type = domainType
	return domain
}

// GetStatus retrieves current status for the given domainname.
func GetDomainStatus(domainName string) (*ResponseStatus, error) {
	stat := &ResponseStatus{}
	req, err := client.NewRequest(
		Config,
		"GET",
		fmt.Sprintf("/config-gtm/v1/domains/%s/status/current", domainName),
		nil,
	)
	if err != nil {
		return nil, err
	}

	setVersionHeader(req, schemaVersion)

	printHttpRequest(req, true)

	res, err := client.Do(Config, req)
	if err != nil {
		return nil, err
	}

	printHttpResponse(res, true)

	if client.IsError(res) && res.StatusCode != 404 {
		return nil, client.NewAPIError(res)
	} else if res.StatusCode == 404 {
		return nil, CommonError{entityName: "Domain", name: domainName}
	} else {
		err = client.BodyJSON(res, stat)
		if err != nil {
			return nil, err
		}

		return stat, nil
	}
}

// ListDomains retrieves all Domains.
func ListDomains() ([]*DomainItem, error) {
	domains := &DomainsList{}
	req, err := client.NewRequest(
		Config,
		"GET",
		"/config-gtm/v1/domains/",
		nil,
	)
	if err != nil {
		return nil, err
	}

	setVersionHeader(req, schemaVersion)

	printHttpRequest(req, true)

	res, err := client.Do(Config, req)
	if err != nil {
		return nil, err
	}

	printHttpResponse(res, true)

	if client.IsError(res) && res.StatusCode != 404 {
		return nil, client.NewAPIError(res)
	} else if res.StatusCode == 404 {
		return nil, CommonError{entityName: "Domain"}
	} else {
		err = client.BodyJSON(res, domains)
		if err != nil {
			return nil, err
		}

		return domains.DomainItems, nil
	}
}

// GetDomain retrieves a Domain with the given domainname.
func GetDomain(domainName string) (*Domain, error) {
	domain := NewDomain(domainName, "basic")
	req, err := client.NewRequest(
		Config,
		"GET",
		fmt.Sprintf("/config-gtm/v1/domains/%s", domainName),
		nil,
	)
	if err != nil {
		return nil, err
	}

	setVersionHeader(req, schemaVersion)

	printHttpRequest(req, true)

	res, err := client.Do(Config, req)
	if err != nil {
		return nil, err
	}

	printHttpResponse(res, true)

	if client.IsError(res) && res.StatusCode != 404 {
		return nil, client.NewAPIError(res)
	} else if res.StatusCode == 404 {
		return nil, CommonError{entityName: "Domain", name: domainName}
	} else {
		err = client.BodyJSON(res, domain)
		if err != nil {
			return nil, err
		}

		return domain, nil
	}
}

// Save method; Create or Update
func (domain *Domain) save(queryArgs map[string]string, req *http.Request) (*DomainResponse, error) {

	// set schema version
	setVersionHeader(req, schemaVersion)

	// Look for optional args
	if len(queryArgs) > 0 {
		q := req.URL.Query()
		if val, ok := queryArgs["contractId"]; ok {
			q.Add("contractId", strings.TrimPrefix(val, "ctr_"))
		}
		if val, ok := queryArgs["gid"]; ok {
			q.Add("gid", strings.TrimPrefix(val, "grp_"))
		}
		req.URL.RawQuery = q.Encode()
	}

	printHttpRequest(req, true)

	res, err := client.Do(Config, req)

	printHttpResponse(res, true)

	// Network error
	if err != nil {
		return nil, CommonError{
			entityName:       "Domain",
			name:             domain.Name,
			httpErrorMessage: err.Error(),
			err:              err,
		}
	}

	// API error
	if client.IsError(res) {
		err := client.NewAPIError(res)
		return nil, CommonError{entityName: "Domain", name: domain.Name, apiErrorMessage: err.Detail, err: err}
	}

	// TODO: What validation can we do? E.g. if not equivalent there was a concurrent change...
	responseBody := &DomainResponse{}
	// Unmarshall whole response body in case want status
	err = client.BodyJSON(res, responseBody)
	if err != nil {
		return nil, err
	}

	return responseBody, nil

}

// Create is a method applied to a domain object resulting in creation.
func (domain *Domain) Create(queryArgs map[string]string) (*DomainResponse, error) {

	req, err := client.NewJSONRequest(
		Config,
		"POST",
		fmt.Sprintf("/config-gtm/v1/domains/"),
		domain,
	)
	if err != nil {
		return nil, err
	}

	return domain.save(queryArgs, req)

}

// Update is a method applied to a domain object resulting in an update.
func (domain *Domain) Update(queryArgs map[string]string) (*ResponseStatus, error) {

	// Any validation to do?
	req, err := client.NewJSONRequest(
		Config,
		"PUT",
		fmt.Sprintf("/config-gtm/v1/domains/%s", domain.Name),
		domain,
	)
	if err != nil {
		return nil, err
	}

	stat, err := domain.save(queryArgs, req)
	if err != nil {
		return nil, err
	}
	return stat.Status, err
}

// Delete is a method applied to a domain object resulting in removal.
func (domain *Domain) Delete() (*ResponseStatus, error) {

	req, err := client.NewRequest(
		Config,
		"DELETE",
		fmt.Sprintf("/config-gtm/v1/domains/%s", domain.Name),
		nil,
	)
	if err != nil {
		return nil, err
	}

	setVersionHeader(req, schemaVersion)

	printHttpRequest(req, true)

	res, err := client.Do(Config, req)
	if err != nil {
		return nil, err
	}

	printHttpResponse(res, true)

	// Network error
	if err != nil {
		return nil, CommonError{
			entityName:       "Domain",
			name:             domain.Name,
			httpErrorMessage: err.Error(),
			err:              err,
		}
	}

	// API error
	if client.IsError(res) {
		err := client.NewAPIError(res)
		return nil, CommonError{entityName: "Domain", name: domain.Name, apiErrorMessage: err.Detail, err: err}
	}

	responseBody := &ResponseBody{}
	// Unmarshall whole response body in case want status
	err = client.BodyJSON(res, responseBody)
	if err != nil {
		return nil, err
	}

	return responseBody.Status, nil

}
<|endoftext|>


<commit_before>package connection

import (
	"context"
	"net"
	"strings"
	"time"

	"github.com/golang/glog"
	"github.com/kubernetes-csi/csi-lib-utils/protosanitizer"
	"google.golang.org/grpc"
)

const (
	// Interval of logging connection errors
	connectionLoggingInterval = 10 * time.Second
)

// Connect opens insecure gRPC connection to a CSI driver. Address must be either absolute path to a socket file
// or have format '<protocol>://', following gRPC name resolution mechanism at https://github.com/grpc/grpc/blob/master/doc/naming.md.
// The function tries to connect indefinitely every second until it connects. The function automatically adds
// interceptor for gRPC message logging.
func Connect(address string, dialOptions ...grpc.DialOption) (*grpc.ClientConn, error) {
	dialOptions = append(dialOptions,
		grpc.WithInsecure(),                   // Don't use TLS, it's usually local Unix domain socket in a container.
		grpc.WithBackoffMaxDelay(time.Second), // Retry every second after failure.
		grpc.WithBlock(),                      // Block until connection succeeds.
		grpc.WithUnaryInterceptor(LogGRPC),    // Log all messages.
	)
	if strings.HasPrefix(address, "/") {
		// It looks like filesystem path.
		dialOptions = append(dialOptions, grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
			return net.DialTimeout("unix", addr, timeout)
		}))
	}
	glog.Infof("Connecting to %s", address)

	// Connect in background.
	var conn *grpc.ClientConn
	var err error
	ready := make(chan bool)
	go func() {
		conn, err = grpc.Dial(address, dialOptions...)
		close(ready)
	}()

	// Log error every connectionLoggingInterval
	ticker := time.NewTicker(connectionLoggingInterval)
	defer ticker.Stop()

	// Wait until Dial() succeeds.
	for {
		select {
		case <-ticker.C:
			glog.Warningf("Still connecting to %s", address)

		case <-ready:
			return conn, err
		}
	}
}

// LogGRPC is gPRC unary interceptor for logging of CSI messages at level 5. It removes any secrets from the message.
func LogGRPC(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	glog.V(5).Infof("GRPC call: %s", method)
	glog.V(5).Infof("GRPC request: %s", protosanitizer.StripSecrets(req))
	err := invoker(ctx, method, req, reply, cc, opts...)
	glog.V(5).Infof("GRPC response: %s", protosanitizer.StripSecrets(reply))
	glog.V(5).Infof("GRPC error: %v", err)
	return err
}
<commit_msg>Review updates<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package connection

import (
	"context"
	"strings"
	"time"

	"github.com/golang/glog"
	"github.com/kubernetes-csi/csi-lib-utils/protosanitizer"
	"google.golang.org/grpc"
)

const (
	// Interval of logging connection errors
	connectionLoggingInterval = 10 * time.Second
)

// Connect opens insecure gRPC connection to a CSI driver. Address must be either absolute path to UNIX domain socket
// file or have format '<protocol>://', following gRPC name resolution mechanism at
// https://github.com/grpc/grpc/blob/master/doc/naming.md.
// The function tries to connect indefinitely every second until it connects. The function automatically disables TLS
// and adds interceptor for logging of all gRPC messages at level 5.
func Connect(address string) (*grpc.ClientConn, error) {
	dialOptions := []grpc.DialOption{
		grpc.WithInsecure(),                   // Don't use TLS, it's usually local Unix domain socket in a container.
		grpc.WithBackoffMaxDelay(time.Second), // Retry every second after failure.
		grpc.WithBlock(),                      // Block until connection succeeds.
		grpc.WithUnaryInterceptor(LogGRPC),    // Log all messages.
	}
	if strings.HasPrefix(address, "/") {
		// It looks like filesystem path.
		address = "unix://" + address
	}
	glog.Infof("Connecting to %s", address)

	// Connect in background.
	var conn *grpc.ClientConn
	var err error
	ready := make(chan bool)
	go func() {
		conn, err = grpc.Dial(address, dialOptions...)
		close(ready)
	}()

	// Log error every connectionLoggingInterval
	ticker := time.NewTicker(connectionLoggingInterval)
	defer ticker.Stop()

	// Wait until Dial() succeeds.
	for {
		select {
		case <-ticker.C:
			glog.Warningf("Still connecting to %s", address)

		case <-ready:
			return conn, err
		}
	}
}

// LogGRPC is gPRC unary interceptor for logging of CSI messages at level 5. It removes any secrets from the message.
func LogGRPC(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	glog.V(5).Infof("GRPC call: %s", method)
	glog.V(5).Infof("GRPC request: %s", protosanitizer.StripSecrets(req))
	err := invoker(ctx, method, req, reply, cc, opts...)
	glog.V(5).Infof("GRPC response: %s", protosanitizer.StripSecrets(reply))
	glog.V(5).Infof("GRPC error: %v", err)
	return err
}
<|endoftext|>


<commit_before>// +build !windows

package termbox

import "unicode/utf8"
import "bytes"
import "syscall"
import "unsafe"
import "strings"
import "strconv"
import "os"
import "io"

// private API

const (
	t_enter_ca = iota
	t_exit_ca
	t_show_cursor
	t_hide_cursor
	t_clear_screen
	t_sgr0
	t_underline
	t_bold
	t_blink
	t_reverse
	t_enter_keypad
	t_exit_keypad
	t_enter_mouse
	t_exit_mouse
	t_max_funcs
)

const (
	coord_invalid = -2
	attr_invalid  = Attribute(0xFFFF)
)

type input_event struct {
	data []byte
	err  error
}

var (
	// term specific sequences
	keys  []string
	funcs []string

	// termbox inner state
	orig_tios    syscall_Termios
	back_buffer  cellbuf
	front_buffer cellbuf
	termw        int
	termh        int
	input_mode   = InputEsc
	output_mode  = OutputNormal
	out          *os.File
	in           int
	lastfg       = attr_invalid
	lastbg       = attr_invalid
	lastx        = coord_invalid
	lasty        = coord_invalid
	cursor_x     = cursor_hidden
	cursor_y     = cursor_hidden
	foreground   = ColorDefault
	background   = ColorDefault
	inbuf        = make([]byte, 0, 64)
	outbuf       bytes.Buffer
	sigwinch     = make(chan os.Signal, 1)
	sigio        = make(chan os.Signal, 1)
	quit         = make(chan int)
	input_comm   = make(chan input_event)
	intbuf       = make([]byte, 0, 16)
)

func write_cursor(x, y int) {
	outbuf.WriteString("\033[")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(y+1), 10))
	outbuf.WriteString(";")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(x+1), 10))
	outbuf.WriteString("H")
}

func write_sgr_fg(a Attribute) {
	outbuf.WriteString("\033[3")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
	outbuf.WriteString("m")
}

func write_sgr_bg(a Attribute) {
	outbuf.WriteString("\033[4")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
	outbuf.WriteString("m")
}

func write_sgr(fg, bg Attribute) {
	switch output_mode {
	case Output256, Output216, OutputGrayscale:
		outbuf.WriteString("\033[38;5;")
		outbuf.Write(strconv.AppendUint(intbuf, uint64(fg), 10))
		outbuf.WriteString("m")
		outbuf.WriteString("\033[48;5;")
		outbuf.Write(strconv.AppendUint(intbuf, uint64(bg), 10))
		outbuf.WriteString("m")
	default:
		outbuf.WriteString("\033[3")
		outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10))
		outbuf.WriteString(";4")
		outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10))
		outbuf.WriteString("m")
	}
}

type winsize struct {
	rows    uint16
	cols    uint16
	xpixels uint16
	ypixels uint16
}

func get_term_size(fd uintptr) (int, int) {
	var sz winsize
	_, _, _ = syscall.Syscall(syscall.SYS_IOCTL,
		fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz)))
	return int(sz.cols), int(sz.rows)
}

func send_attr(fg, bg Attribute) {
	if fg == lastfg && bg == lastbg {
		return
	}

	outbuf.WriteString(funcs[t_sgr0])

	var fgcol, bgcol Attribute

	switch output_mode {
	case Output256:
		fgcol = fg & 0xFF
		bgcol = bg & 0xFF
		write_sgr(fgcol, bgcol)
	case Output216:
		fgcol = fg & 0xFF; if fgcol > 215 { fgcol = 7 }
		bgcol = bg & 0xFF; if bgcol > 215 { bgcol = 0 }
		fgcol += 0x10;
		bgcol += 0x10;
		write_sgr(fgcol, bgcol)
	case OutputGrayscale:
		fgcol = fg & 0xFF; if fgcol > 23 { fg = 23 }
		bgcol = bg & 0xFF; if bgcol > 23 { bg = 0 }
		fgcol += 0xe8;
		bgcol += 0xe8;
		write_sgr(fgcol, bgcol)
	default:
		fgcol = fg & 0x0F
		bgcol = bg & 0x0F
		if fgcol != ColorDefault {
			if bgcol != ColorDefault {
				write_sgr(fgcol, bgcol)
			} else {
				write_sgr_fg(fgcol)
			}
		} else if bgcol != ColorDefault {
			write_sgr_bg(bgcol)
		}
	}

	if fg&AttrBold != 0 {
		outbuf.WriteString(funcs[t_bold])
	}
	if bg&AttrBold != 0 {
		outbuf.WriteString(funcs[t_blink])
	}
	if fg&AttrUnderline != 0 {
		outbuf.WriteString(funcs[t_underline])
	}
	if fg&AttrReverse|bg&AttrReverse != 0 {
		outbuf.WriteString(funcs[t_reverse])
	}

	lastfg, lastbg = fg, bg
}

func send_char(x, y int, ch rune) {
	var buf [8]byte
	n := utf8.EncodeRune(buf[:], ch)
	if x-1 != lastx || y != lasty {
		write_cursor(x, y)
	}
	lastx, lasty = x, y
	outbuf.Write(buf[:n])
}

func flush() error {
	_, err := io.Copy(out, &outbuf)
	outbuf.Reset()
	if err != nil {
		return err
	}
	return nil
}

func send_clear() error {
	send_attr(foreground, background)
	outbuf.WriteString(funcs[t_clear_screen])
	if !is_cursor_hidden(cursor_x, cursor_y) {
		write_cursor(cursor_x, cursor_y)
	}

	// we need to invalidate cursor position too and these two vars are
	// used only for simple cursor positioning optimization, cursor
	// actually may be in the correct place, but we simply discard
	// optimization once and it gives us simple solution for the case when
	// cursor moved
	lastx = coord_invalid
	lasty = coord_invalid

	return flush()
}

func update_size_maybe() error {
	w, h := get_term_size(out.Fd())
	if w != termw || h != termh {
		termw, termh = w, h
		back_buffer.resize(termw, termh)
		front_buffer.resize(termw, termh)
		front_buffer.clear()
		return send_clear()
	}
	return nil
}

func tcsetattr(fd uintptr, termios *syscall_Termios) error {
	r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
		fd, uintptr(syscall_TCSETS), uintptr(unsafe.Pointer(termios)))
	if r != 0 {
		return os.NewSyscallError("SYS_IOCTL", e)
	}
	return nil
}

func tcgetattr(fd uintptr, termios *syscall_Termios) error {
	r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
		fd, uintptr(syscall_TCGETS), uintptr(unsafe.Pointer(termios)))
	if r != 0 {
		return os.NewSyscallError("SYS_IOCTL", e)
	}
	return nil
}

func parse_escape_sequence(event *Event, buf []byte) (int, bool) {
	bufstr := string(buf)
	// mouse
	if len(bufstr) >= 6 && strings.HasPrefix(bufstr, "\033[M") {
		switch buf[3] & 3 {
		case 0:
			event.Key = MouseLeft
		case 1:
			event.Key = MouseMiddle
		case 2:
			event.Key = MouseRight
		case 3:
			return 6, false
		}
		event.Type = EventMouse // KeyEvent by default
		// wheel up outputs MouseLeft
		if buf[3] == 0x60 || buf[3] == 0x70 {
			event.Key = MouseMiddle
		}
		// the coord is 1,1 for upper left
		event.MouseX = int(buf[4]) - 1 - 32
		event.MouseY = int(buf[5]) - 1 - 32
		return 6, true
	}

	for i, key := range keys {
		if strings.HasPrefix(bufstr, key) {
			event.Ch = 0
			event.Key = Key(0xFFFF - i)
			return len(key), true
		}
	}
	return 0, true
}

func extract_event(event *Event) bool {
	if len(inbuf) == 0 {
		return false
	}

	if inbuf[0] == '\033' {
		// possible escape sequence
		n, ok := parse_escape_sequence(event, inbuf)
		if n != 0 {
			copy(inbuf, inbuf[n:])
			inbuf = inbuf[:len(inbuf)-n]
			return ok
		}

		// it's not escape sequence, then it's Alt or Esc, check input_mode
		switch {
		case input_mode&InputEsc != 0:
			// if we're in escape mode, fill Esc event, pop buffer, return success
			event.Ch = 0
			event.Key = KeyEsc
			event.Mod = 0
			copy(inbuf, inbuf[1:])
			inbuf = inbuf[:len(inbuf)-1]
			return true
		case input_mode&InputAlt != 0:
			// if we're in alt mode, set Alt modifier to event and redo parsing
			event.Mod = ModAlt
			copy(inbuf, inbuf[1:])
			inbuf = inbuf[:len(inbuf)-1]
			return extract_event(event)
		default:
			panic("unreachable")
		}
	}

	// if we're here, this is not an escape sequence and not an alt sequence
	// so, it's a FUNCTIONAL KEY or a UNICODE character

	// first of all check if it's a functional key
	if Key(inbuf[0]) <= KeySpace || Key(inbuf[0]) == KeyBackspace2 {
		// fill event, pop buffer, return success
		event.Ch = 0
		event.Key = Key(inbuf[0])
		copy(inbuf, inbuf[1:])
		inbuf = inbuf[:len(inbuf)-1]
		return true
	}

	// the only possible option is utf8 rune
	if r, n := utf8.DecodeRune(inbuf); r != utf8.RuneError {
		event.Ch = r
		event.Key = 0
		copy(inbuf, inbuf[n:])
		inbuf = inbuf[:len(inbuf)-n]
		return true
	}

	return false
}

func fcntl(fd int, cmd int, arg int) (val int, err error) {
	r, _, e := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), uintptr(cmd),
		uintptr(arg))
	val = int(r)
	if e != 0 {
		err = e
	}
	return
}
<commit_msg>Fix typo in send_attr.<commit_after>// +build !windows

package termbox

import "unicode/utf8"
import "bytes"
import "syscall"
import "unsafe"
import "strings"
import "strconv"
import "os"
import "io"

// private API

const (
	t_enter_ca = iota
	t_exit_ca
	t_show_cursor
	t_hide_cursor
	t_clear_screen
	t_sgr0
	t_underline
	t_bold
	t_blink
	t_reverse
	t_enter_keypad
	t_exit_keypad
	t_enter_mouse
	t_exit_mouse
	t_max_funcs
)

const (
	coord_invalid = -2
	attr_invalid  = Attribute(0xFFFF)
)

type input_event struct {
	data []byte
	err  error
}

var (
	// term specific sequences
	keys  []string
	funcs []string

	// termbox inner state
	orig_tios    syscall_Termios
	back_buffer  cellbuf
	front_buffer cellbuf
	termw        int
	termh        int
	input_mode   = InputEsc
	output_mode  = OutputNormal
	out          *os.File
	in           int
	lastfg       = attr_invalid
	lastbg       = attr_invalid
	lastx        = coord_invalid
	lasty        = coord_invalid
	cursor_x     = cursor_hidden
	cursor_y     = cursor_hidden
	foreground   = ColorDefault
	background   = ColorDefault
	inbuf        = make([]byte, 0, 64)
	outbuf       bytes.Buffer
	sigwinch     = make(chan os.Signal, 1)
	sigio        = make(chan os.Signal, 1)
	quit         = make(chan int)
	input_comm   = make(chan input_event)
	intbuf       = make([]byte, 0, 16)
)

func write_cursor(x, y int) {
	outbuf.WriteString("\033[")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(y+1), 10))
	outbuf.WriteString(";")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(x+1), 10))
	outbuf.WriteString("H")
}

func write_sgr_fg(a Attribute) {
	outbuf.WriteString("\033[3")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
	outbuf.WriteString("m")
}

func write_sgr_bg(a Attribute) {
	outbuf.WriteString("\033[4")
	outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
	outbuf.WriteString("m")
}

func write_sgr(fg, bg Attribute) {
	switch output_mode {
	case Output256, Output216, OutputGrayscale:
		outbuf.WriteString("\033[38;5;")
		outbuf.Write(strconv.AppendUint(intbuf, uint64(fg), 10))
		outbuf.WriteString("m")
		outbuf.WriteString("\033[48;5;")
		outbuf.Write(strconv.AppendUint(intbuf, uint64(bg), 10))
		outbuf.WriteString("m")
	default:
		outbuf.WriteString("\033[3")
		outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10))
		outbuf.WriteString(";4")
		outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10))
		outbuf.WriteString("m")
	}
}

type winsize struct {
	rows    uint16
	cols    uint16
	xpixels uint16
	ypixels uint16
}

func get_term_size(fd uintptr) (int, int) {
	var sz winsize
	_, _, _ = syscall.Syscall(syscall.SYS_IOCTL,
		fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz)))
	return int(sz.cols), int(sz.rows)
}

func send_attr(fg, bg Attribute) {
	if fg == lastfg && bg == lastbg {
		return
	}

	outbuf.WriteString(funcs[t_sgr0])

	var fgcol, bgcol Attribute

	switch output_mode {
	case Output256:
		fgcol = fg & 0xFF
		bgcol = bg & 0xFF
		write_sgr(fgcol, bgcol)
	case Output216:
		fgcol = fg & 0xFF; if fgcol > 215 { fgcol = 7 }
		bgcol = bg & 0xFF; if bgcol > 215 { bgcol = 0 }
		fgcol += 0x10;
		bgcol += 0x10;
		write_sgr(fgcol, bgcol)
	case OutputGrayscale:
		fgcol = fg & 0xFF; if fgcol > 23 { fgcol = 23 }
		bgcol = bg & 0xFF; if bgcol > 23 { bgcol = 0 }
		fgcol += 0xe8;
		bgcol += 0xe8;
		write_sgr(fgcol, bgcol)
	default:
		fgcol = fg & 0x0F
		bgcol = bg & 0x0F
		if fgcol != ColorDefault {
			if bgcol != ColorDefault {
				write_sgr(fgcol, bgcol)
			} else {
				write_sgr_fg(fgcol)
			}
		} else if bgcol != ColorDefault {
			write_sgr_bg(bgcol)
		}
	}

	if fg&AttrBold != 0 {
		outbuf.WriteString(funcs[t_bold])
	}
	if bg&AttrBold != 0 {
		outbuf.WriteString(funcs[t_blink])
	}
	if fg&AttrUnderline != 0 {
		outbuf.WriteString(funcs[t_underline])
	}
	if fg&AttrReverse|bg&AttrReverse != 0 {
		outbuf.WriteString(funcs[t_reverse])
	}

	lastfg, lastbg = fg, bg
}

func send_char(x, y int, ch rune) {
	var buf [8]byte
	n := utf8.EncodeRune(buf[:], ch)
	if x-1 != lastx || y != lasty {
		write_cursor(x, y)
	}
	lastx, lasty = x, y
	outbuf.Write(buf[:n])
}

func flush() error {
	_, err := io.Copy(out, &outbuf)
	outbuf.Reset()
	if err != nil {
		return err
	}
	return nil
}

func send_clear() error {
	send_attr(foreground, background)
	outbuf.WriteString(funcs[t_clear_screen])
	if !is_cursor_hidden(cursor_x, cursor_y) {
		write_cursor(cursor_x, cursor_y)
	}

	// we need to invalidate cursor position too and these two vars are
	// used only for simple cursor positioning optimization, cursor
	// actually may be in the correct place, but we simply discard
	// optimization once and it gives us simple solution for the case when
	// cursor moved
	lastx = coord_invalid
	lasty = coord_invalid

	return flush()
}

func update_size_maybe() error {
	w, h := get_term_size(out.Fd())
	if w != termw || h != termh {
		termw, termh = w, h
		back_buffer.resize(termw, termh)
		front_buffer.resize(termw, termh)
		front_buffer.clear()
		return send_clear()
	}
	return nil
}

func tcsetattr(fd uintptr, termios *syscall_Termios) error {
	r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
		fd, uintptr(syscall_TCSETS), uintptr(unsafe.Pointer(termios)))
	if r != 0 {
		return os.NewSyscallError("SYS_IOCTL", e)
	}
	return nil
}

func tcgetattr(fd uintptr, termios *syscall_Termios) error {
	r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
		fd, uintptr(syscall_TCGETS), uintptr(unsafe.Pointer(termios)))
	if r != 0 {
		return os.NewSyscallError("SYS_IOCTL", e)
	}
	return nil
}

func parse_escape_sequence(event *Event, buf []byte) (int, bool) {
	bufstr := string(buf)
	// mouse
	if len(bufstr) >= 6 && strings.HasPrefix(bufstr, "\033[M") {
		switch buf[3] & 3 {
		case 0:
			event.Key = MouseLeft
		case 1:
			event.Key = MouseMiddle
		case 2:
			event.Key = MouseRight
		case 3:
			return 6, false
		}
		event.Type = EventMouse // KeyEvent by default
		// wheel up outputs MouseLeft
		if buf[3] == 0x60 || buf[3] == 0x70 {
			event.Key = MouseMiddle
		}
		// the coord is 1,1 for upper left
		event.MouseX = int(buf[4]) - 1 - 32
		event.MouseY = int(buf[5]) - 1 - 32
		return 6, true
	}

	for i, key := range keys {
		if strings.HasPrefix(bufstr, key) {
			event.Ch = 0
			event.Key = Key(0xFFFF - i)
			return len(key), true
		}
	}
	return 0, true
}

func extract_event(event *Event) bool {
	if len(inbuf) == 0 {
		return false
	}

	if inbuf[0] == '\033' {
		// possible escape sequence
		n, ok := parse_escape_sequence(event, inbuf)
		if n != 0 {
			copy(inbuf, inbuf[n:])
			inbuf = inbuf[:len(inbuf)-n]
			return ok
		}

		// it's not escape sequence, then it's Alt or Esc, check input_mode
		switch {
		case input_mode&InputEsc != 0:
			// if we're in escape mode, fill Esc event, pop buffer, return success
			event.Ch = 0
			event.Key = KeyEsc
			event.Mod = 0
			copy(inbuf, inbuf[1:])
			inbuf = inbuf[:len(inbuf)-1]
			return true
		case input_mode&InputAlt != 0:
			// if we're in alt mode, set Alt modifier to event and redo parsing
			event.Mod = ModAlt
			copy(inbuf, inbuf[1:])
			inbuf = inbuf[:len(inbuf)-1]
			return extract_event(event)
		default:
			panic("unreachable")
		}
	}

	// if we're here, this is not an escape sequence and not an alt sequence
	// so, it's a FUNCTIONAL KEY or a UNICODE character

	// first of all check if it's a functional key
	if Key(inbuf[0]) <= KeySpace || Key(inbuf[0]) == KeyBackspace2 {
		// fill event, pop buffer, return success
		event.Ch = 0
		event.Key = Key(inbuf[0])
		copy(inbuf, inbuf[1:])
		inbuf = inbuf[:len(inbuf)-1]
		return true
	}

	// the only possible option is utf8 rune
	if r, n := utf8.DecodeRune(inbuf); r != utf8.RuneError {
		event.Ch = r
		event.Key = 0
		copy(inbuf, inbuf[n:])
		inbuf = inbuf[:len(inbuf)-n]
		return true
	}

	return false
}

func fcntl(fd int, cmd int, arg int) (val int, err error) {
	r, _, e := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), uintptr(cmd),
		uintptr(arg))
	val = int(r)
	if e != 0 {
		err = e
	}
	return
}
<|endoftext|>


<commit_before>package simra

import (
	"context"

	"github.com/pankona/gomo-simra/simra/fps"
	"github.com/pankona/gomo-simra/simra/peer"
)

// Spriter represents an interface of Sprite
type Spriter interface {
	// ReplaceTexture replaces sprite's texture with specified image resource.
	ReplaceTexture(texture *Texture)
	// AddTouchListener registers a listener for touch event.
	// Touch event will be notified when "sprite" is touched.
	AddTouchListener(listener peer.TouchListener)
	// RemoveAllTouchListener removes all listeners already registered.
	RemoveAllTouchListener()
	// AddAnimationSet adds a specified AnimationSet to sprite
	AddAnimationSet(animationName string, set *AnimationSet)
	// StartAnimation starts animation by specified animation name
	StartAnimation(animationName string, shouldLoop bool, animationEndCallback func())
	// StopAnimation stops animation
	StopAnimation()
	// ProgressAnimation progresses registered animation
	ProgressAnimation()
	// SetPosition sets sprite's position
	SetPosition(x, y int)
	// SetPositionX sets sprite's position X
	SetPositionX(x int)
	// SetPositionY sets sprite's position Y
	SetPositionY(y int)
	// SetScale sets sprite's size
	SetScale(w, h int)
	// SetScaleW sets sprite's size W
	SetScaleW(w int)
	// SetScaleH sets sprite's size H
	SetScaleH(h int)
	// GetPosition gets sprites position
	GetPosition() Position
	// GetScale gets sprites size
	GetScale() Scale
	// SetRotate sets sprite's rotation
	SetRotate(r float32)
	// getRotate gets sprite's rotation
	GetRotate() float32
}

// Position represents position of sprite
type Position struct {
	X, Y int
}

// Scale represents size of sprite
type Scale struct {
	W, H int
}

// Sprite represents a sprite object.
type sprite struct {
	peer.Sprite
	animationSets   map[string]*AnimationSet
	animationCancel func()
	texture         *Texture
}

// NewSprite returns an instance of Sprite
func NewSprite() Spriter {
	return &sprite{animationSets: map[string]*AnimationSet{}}
}

// ReplaceTexture replaces sprite's texture with specified image resource.
func (sprite *sprite) ReplaceTexture(texture *Texture) {
	LogDebug("IN")
	// retain reference for texture to avoid to be discarded by GC
	sprite.texture = texture
	peer.GetSpriteContainer().ReplaceTexture(&sprite.Sprite, texture.texture)
	LogDebug("OUT")
}

// AddTouchListener registers a listener for touch event.
// Touch event will be notified when "sprite" is touched.
func (sprite *sprite) AddTouchListener(listener peer.TouchListener) {
	LogDebug("IN")
	sprite.Sprite.AddTouchListener(listener)
	LogDebug("OUT")
}

// RemoveAllTouchListener removes all listeners already registered.
func (sprite *sprite) RemoveAllTouchListener() {
	LogDebug("IN")
	sprite.Sprite.RemoveAllTouchListener()
	LogDebug("OUT")
}

// AddAnimationSet adds a specified AnimationSet to sprite
func (sprite *sprite) AddAnimationSet(animationName string, set *AnimationSet) {
	LogDebug("IN")
	sprite.animationSets[animationName] = set
	LogDebug("OUT")
}

// StartAnimation starts animation by specified animation name
func (sprite *sprite) StartAnimation(animationName string, shouldLoop bool, animationEndCallback func()) {
	LogDebug("IN")
	if sprite.animationCancel != nil {
		// animation is already in progress. don't start.
		// TODO: should exlude control
		return
	}
	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)
	sprite.animationCancel = cancel
	go sprite.startAnimation(ctx, animationName, shouldLoop, animationEndCallback)
	LogDebug("OUT")
}

func (sprite *sprite) startAnimation(ctx context.Context, animationName string, shouldLoop bool, animationEndCallback func()) {
	LogDebug("IN")
	animationSet := sprite.animationSets[animationName]
	if animationSet == nil {
		panic("specified animation is not set. animation name = " + animationName)
	}

	loopCount := 0
animation:
	for {
		select {
		case <-ctx.Done():
			break animation
		case <-fps.After(animationSet.interval):
			sprite.ReplaceTexture(animationSet.textures[loopCount])
			loopCount = (loopCount + 1) % len(animationSet.textures)
			if !shouldLoop && loopCount == 0 {
				break animation
			}
		}
	}
	sprite.animationCancel = nil
	animationEndCallback()
	LogDebug("OUT")
}

// StopAnimation stops animation
func (sprite *sprite) StopAnimation() {
	LogDebug("IN")
	if sprite == nil {
		return
	}

	if sprite.animationCancel != nil {
		sprite.animationCancel()
	}
	LogDebug("OUT")
}

// ProgressAnimation progresses registered animation
func (sprite *sprite) ProgressAnimation() {
	fps.Progress()
}

func (sprite *sprite) SetPosition(x, y int) {
	sprite.X, sprite.Y = (float32)(x), (float32)(y)
}

func (sprite *sprite) SetPositionX(x int) {
	sprite.X = (float32)(x)
}

func (sprite *sprite) SetPositionY(y int) {
	sprite.Y = (float32)(y)
}

func (sprite *sprite) SetScale(w, h int) {
	sprite.W, sprite.H = (float32)(w), (float32)(h)
}

func (sprite *sprite) SetScaleW(w int) {
	sprite.W = (float32)(w)
}

func (sprite *sprite) SetScaleH(h int) {
	sprite.H = (float32)(h)
}

func (sprite *sprite) GetPosition() Position {
	return Position{(int)(sprite.X), (int)(sprite.Y)}
}

func (sprite *sprite) GetScale() Scale {
	return Scale{(int)(sprite.W), (int)(sprite.H)}
}

func (sprite *sprite) SetRotate(r float32) {
	sprite.R = r
}

func (sprite *sprite) GetRotate() float32 {
	return sprite.R
}
<commit_msg>don't cast sprite's value to float32. not necessary.<commit_after>package simra

import (
	"context"

	"github.com/pankona/gomo-simra/simra/fps"
	"github.com/pankona/gomo-simra/simra/peer"
)

// Spriter represents an interface of Sprite
type Spriter interface {
	// ReplaceTexture replaces sprite's texture with specified image resource.
	ReplaceTexture(texture *Texture)
	// AddTouchListener registers a listener for touch event.
	// Touch event will be notified when "sprite" is touched.
	AddTouchListener(listener peer.TouchListener)
	// RemoveAllTouchListener removes all listeners already registered.
	RemoveAllTouchListener()
	// AddAnimationSet adds a specified AnimationSet to sprite
	AddAnimationSet(animationName string, set *AnimationSet)
	// StartAnimation starts animation by specified animation name
	StartAnimation(animationName string, shouldLoop bool, animationEndCallback func())
	// StopAnimation stops animation
	StopAnimation()
	// ProgressAnimation progresses registered animation
	ProgressAnimation()
	// SetPosition sets sprite's position
	SetPosition(x, y int)
	// SetPositionX sets sprite's position X
	SetPositionX(x int)
	// SetPositionY sets sprite's position Y
	SetPositionY(y int)
	// SetScale sets sprite's size
	SetScale(w, h int)
	// SetScaleW sets sprite's size W
	SetScaleW(w int)
	// SetScaleH sets sprite's size H
	SetScaleH(h int)
	// GetPosition gets sprites position
	GetPosition() Position
	// GetScale gets sprites size
	GetScale() Scale
	// SetRotate sets sprite's rotation
	SetRotate(r float32)
	// getRotate gets sprite's rotation
	GetRotate() float32
}

// Position represents position of sprite
type Position struct {
	X, Y int
}

// Scale represents size of sprite
type Scale struct {
	W, H int
}

// Sprite represents a sprite object.
type sprite struct {
	peer.Sprite
	animationSets   map[string]*AnimationSet
	animationCancel func()
	texture         *Texture
}

// NewSprite returns an instance of Sprite
func NewSprite() Spriter {
	return &sprite{animationSets: map[string]*AnimationSet{}}
}

// ReplaceTexture replaces sprite's texture with specified image resource.
func (sprite *sprite) ReplaceTexture(texture *Texture) {
	LogDebug("IN")
	// retain reference for texture to avoid to be discarded by GC
	sprite.texture = texture
	peer.GetSpriteContainer().ReplaceTexture(&sprite.Sprite, texture.texture)
	LogDebug("OUT")
}

// AddTouchListener registers a listener for touch event.
// Touch event will be notified when "sprite" is touched.
func (sprite *sprite) AddTouchListener(listener peer.TouchListener) {
	LogDebug("IN")
	sprite.Sprite.AddTouchListener(listener)
	LogDebug("OUT")
}

// RemoveAllTouchListener removes all listeners already registered.
func (sprite *sprite) RemoveAllTouchListener() {
	LogDebug("IN")
	sprite.Sprite.RemoveAllTouchListener()
	LogDebug("OUT")
}

// AddAnimationSet adds a specified AnimationSet to sprite
func (sprite *sprite) AddAnimationSet(animationName string, set *AnimationSet) {
	LogDebug("IN")
	sprite.animationSets[animationName] = set
	LogDebug("OUT")
}

// StartAnimation starts animation by specified animation name
func (sprite *sprite) StartAnimation(animationName string, shouldLoop bool, animationEndCallback func()) {
	LogDebug("IN")
	if sprite.animationCancel != nil {
		// animation is already in progress. don't start.
		// TODO: should exlude control
		return
	}
	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)
	sprite.animationCancel = cancel
	go sprite.startAnimation(ctx, animationName, shouldLoop, animationEndCallback)
	LogDebug("OUT")
}

func (sprite *sprite) startAnimation(ctx context.Context, animationName string, shouldLoop bool, animationEndCallback func()) {
	LogDebug("IN")
	animationSet := sprite.animationSets[animationName]
	if animationSet == nil {
		panic("specified animation is not set. animation name = " + animationName)
	}

	loopCount := 0
animation:
	for {
		select {
		case <-ctx.Done():
			break animation
		case <-fps.After(animationSet.interval):
			sprite.ReplaceTexture(animationSet.textures[loopCount])
			loopCount = (loopCount + 1) % len(animationSet.textures)
			if !shouldLoop && loopCount == 0 {
				break animation
			}
		}
	}
	sprite.animationCancel = nil
	animationEndCallback()
	LogDebug("OUT")
}

// StopAnimation stops animation
func (sprite *sprite) StopAnimation() {
	LogDebug("IN")
	if sprite == nil {
		return
	}

	if sprite.animationCancel != nil {
		sprite.animationCancel()
	}
	LogDebug("OUT")
}

// ProgressAnimation progresses registered animation
func (sprite *sprite) ProgressAnimation() {
	fps.Progress()
}

func (sprite *sprite) SetPosition(x, y int) {
	sprite.X, sprite.Y = x, y
}

func (sprite *sprite) SetPositionX(x int) {
	sprite.X = x
}

func (sprite *sprite) SetPositionY(y int) {
	sprite.Y = y
}

func (sprite *sprite) SetScale(w, h int) {
	sprite.W, sprite.H = w, h
}

func (sprite *sprite) SetScaleW(w int) {
	sprite.W = w
}

func (sprite *sprite) SetScaleH(h int) {
	sprite.H = h
}

func (sprite *sprite) GetPosition() Position {
	return Position{sprite.X, sprite.Y}
}

func (sprite *sprite) GetScale() Scale {
	return Scale{sprite.W, sprite.H}
}

func (sprite *sprite) SetRotate(r float32) {
	sprite.R = r
}

func (sprite *sprite) GetRotate() float32 {
	return sprite.R
}
<|endoftext|>


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

package zanzibar

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/buger/jsonparser"
	"github.com/pkg/errors"
	"github.com/uber-go/tally"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// ServerHTTPResponse struct manages server http response
type ServerHTTPResponse struct {
	Request    *ServerHTTPRequest
	StatusCode int

	responseWriter    http.ResponseWriter
	flushed           bool
	finished          bool
	finishTime        time.Time
	pendingBodyBytes  []byte
	pendingBodyObj    interface{}
	pendingStatusCode int
	logger            Logger
	scope             tally.Scope
	err               error
}

// NewServerHTTPResponse is helper function to alloc ServerHTTPResponse
func NewServerHTTPResponse(
	w http.ResponseWriter,
	req *ServerHTTPRequest,
) *ServerHTTPResponse {
	return &ServerHTTPResponse{
		Request:        req,
		StatusCode:     200,
		responseWriter: w,
		logger:         req.logger,
		scope:          req.scope,
	}
}

// finish will handle final logic, like metrics
func (res *ServerHTTPResponse) finish() {
	if !res.Request.started {
		/* coverage ignore next line */
		res.logger.Error(
			"Forgot to start server response",
			zap.String("path", res.Request.URL.Path),
		)
		/* coverage ignore next line */
		return
	}
	if res.finished {
		/* coverage ignore next line */
		res.logger.Error(
			"Finished an server response twice",
			zap.String("path", res.Request.URL.Path),
		)
		/* coverage ignore next line */
		return
	}
	res.finished = true
	res.finishTime = time.Now()

	_, known := knownStatusCodes[res.StatusCode]
	// no need to put this tag on the context because this is the end of response life cycle
	statusTag := map[string]string{scopeTagStatus: fmt.Sprintf("%d", res.StatusCode)}
	tagged := res.scope.Tagged(statusTag)
	tagged.Timer(endpointLatency).Record(res.finishTime.Sub(res.Request.startTime))
	if !known {
		res.logger.Error(
			"Could not emit statusCode metric",
			zap.Int("UnknownStatusCode", res.StatusCode),
		)
	} else {
		tagged.Counter(endpointStatus).Inc(1)
	}
	if !known || res.StatusCode >= 400 && res.StatusCode < 600 {
		tagged.Counter(endpointAppErrors).Inc(1)

	}

	span := res.Request.GetSpan()
	if span != nil {
		span.Finish()
	}

	res.logger.Info(
		"Finished an incoming server HTTP request",
		serverHTTPLogFields(res.Request, res)...,
	)
}

func serverHTTPLogFields(req *ServerHTTPRequest, res *ServerHTTPResponse) []zapcore.Field {
	fields := []zapcore.Field{
		zap.String("method", req.httpRequest.Method),
		zap.String("remoteAddr", req.httpRequest.RemoteAddr),
		zap.String("pathname", req.httpRequest.URL.RequestURI()),
		zap.String("host", req.httpRequest.Host),
		zap.Time("timestamp-started", req.startTime),
		zap.Time("timestamp-finished", res.finishTime),
		zap.Int("statusCode", res.StatusCode),
	}

	if res.err != nil {
		fields = append(fields, zap.Error(res.err))

		cause := errors.Cause(res.err)
		if cause != nil && cause != res.err {
			fields = append(fields, zap.NamedError("errorCause", cause))
		}
	}

	for k, v := range req.httpRequest.Header {
		if len(v) > 0 {
			fields = append(fields, zap.String("Request-Header-"+k, v[0]))
		}
	}
	for k, v := range req.res.responseWriter.Header() {
		if len(v) > 0 {
			fields = append(fields, zap.String("Response-Header-"+k, v[0]))
		}
	}

	return fields
}

// SendErrorString helper to send an error string
func (res *ServerHTTPResponse) SendErrorString(
	statusCode int, errMsg string,
) {
	res.WriteJSONBytes(statusCode, nil,
		[]byte(`{"error":"`+errMsg+`"}`),
	)
}

// SendError helper to send an server error message, propagates underlying cause to logs etc.
func (res *ServerHTTPResponse) SendError(
	statusCode int, errMsg string, errCause error,
) {
	res.err = errCause
	res.WriteJSONBytes(statusCode, nil,
		[]byte(`{"error":"`+errMsg+`"}`),
	)
}

// WriteJSONBytes writes a byte[] slice that is valid json to Response
func (res *ServerHTTPResponse) WriteJSONBytes(
	statusCode int, headers Header, bytes []byte,
) {
	if headers != nil {
		for _, k := range headers.Keys() {
			v, ok := headers.Get(k)
			if ok {
				res.responseWriter.Header().Set(k, v)
			}
		}
	}

	res.responseWriter.Header().Set("content-type", "application/json")

	res.pendingStatusCode = statusCode
	res.pendingBodyBytes = bytes
}

// WriteJSON writes a json serializable struct to Response
func (res *ServerHTTPResponse) WriteJSON(
	statusCode int, headers Header, body json.Marshaler,
) {
	if body == nil {
		res.SendError(500, "Could not serialize json response", errors.New("No Body JSON"))
		res.logger.Error("Could not serialize nil pointer body")
		return
	}

	bytes, err := body.MarshalJSON()
	if err != nil {
		res.SendError(500, "Could not serialize json response", err)
		res.logger.Error("Could not serialize json response", zap.Error(err))
		return
	}

	if headers != nil {
		for _, k := range headers.Keys() {
			v, ok := headers.Get(k)
			if ok {
				res.responseWriter.Header().Set(k, v)
			}
		}
	}

	res.responseWriter.Header().
		Set("content-type", "application/json")

	res.pendingStatusCode = statusCode
	res.pendingBodyBytes = bytes
	res.pendingBodyObj = body
}

// PeekBody allows for inspecting a key path inside the body
// that is not flushed yet. This is useful for response middlewares
// that want to inspect the response body.
func (res *ServerHTTPResponse) PeekBody(
	keys ...string,
) ([]byte, jsonparser.ValueType, error) {
	value, valueType, _, err := jsonparser.Get(
		res.pendingBodyBytes, keys...,
	)

	if err != nil {
		return nil, -1, err
	}

	return value, valueType, nil
}

// Flush will write the body to the response. Before flush is called
// the body is pending. A pending body allows a response middleware to
// write a different body.
func (res *ServerHTTPResponse) flush() {
	if res.flushed {
		/* coverage ignore next line */
		res.logger.Error(
			"Flushed a server response twice",
			zap.String("path", res.Request.URL.Path),
		)
		/* coverage ignore next line */
		return
	}

	res.flushed = true
	res.writeHeader(res.pendingStatusCode)
	res.writeBytes(res.pendingBodyBytes)
	res.finish()
}

func (res *ServerHTTPResponse) writeHeader(statusCode int) {
	res.StatusCode = statusCode
	res.responseWriter.WriteHeader(statusCode)
}

// WriteBytes writes raw bytes to output
func (res *ServerHTTPResponse) writeBytes(bytes []byte) {
	_, err := res.responseWriter.Write(bytes)
	if err != nil {
		/* coverage ignore next line */
		res.logger.Error(
			"Could not write string to resp body",
			zap.Error(err),
		)
	}
}

// GetPendingResponse lets you read the pending body bytes, obj and status code
// which isn't sent back yet.
func (res *ServerHTTPResponse) GetPendingResponse() ([]byte, int) {
	return res.pendingBodyBytes, res.pendingStatusCode
}

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

package zanzibar

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/buger/jsonparser"
	"github.com/pkg/errors"
	"github.com/uber-go/tally"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// ServerHTTPResponse struct manages server http response
type ServerHTTPResponse struct {
	Request    *ServerHTTPRequest
	StatusCode int

	responseWriter    http.ResponseWriter
	flushed           bool
	finished          bool
	finishTime        time.Time
	pendingBodyBytes  []byte
	pendingBodyObj    interface{}
	pendingStatusCode int
	logger            Logger
	scope             tally.Scope
	err               error
}

// NewServerHTTPResponse is helper function to alloc ServerHTTPResponse
func NewServerHTTPResponse(
	w http.ResponseWriter,
	req *ServerHTTPRequest,
) *ServerHTTPResponse {
	return &ServerHTTPResponse{
		Request:        req,
		StatusCode:     200,
		responseWriter: w,
		logger:         req.logger,
		scope:          req.scope,
	}
}

// finish will handle final logic, like metrics
func (res *ServerHTTPResponse) finish() {
	if !res.Request.started {
		/* coverage ignore next line */
		res.logger.Error(
			"Forgot to start server response",
			zap.String("path", res.Request.URL.Path),
		)
		/* coverage ignore next line */
		return
	}
	if res.finished {
		/* coverage ignore next line */
		res.logger.Error(
			"Finished an server response twice",
			zap.String("path", res.Request.URL.Path),
		)
		/* coverage ignore next line */
		return
	}
	res.finished = true
	res.finishTime = time.Now()

	_, known := knownStatusCodes[res.StatusCode]
	// no need to put this tag on the context because this is the end of response life cycle
	statusTag := map[string]string{scopeTagStatus: fmt.Sprintf("%d", res.StatusCode)}
	tagged := res.scope.Tagged(statusTag)
	tagged.Timer(endpointLatency).Record(res.finishTime.Sub(res.Request.startTime))
	if !known {
		res.logger.Error(
			"Could not emit statusCode metric",
			zap.Int("UnknownStatusCode", res.StatusCode),
		)
	} else {
		tagged.Counter(endpointStatus).Inc(1)
	}
	if !known || res.StatusCode >= 400 && res.StatusCode < 600 {
		tagged.Counter(endpointAppErrors).Inc(1)

	}

	span := res.Request.GetSpan()
	if span != nil {
		span.Finish()
	}

	res.logger.Info(
		"Finished an incoming server HTTP request",
		serverHTTPLogFields(res.Request, res)...,
	)
}

func serverHTTPLogFields(req *ServerHTTPRequest, res *ServerHTTPResponse) []zapcore.Field {
	fields := []zapcore.Field{
		zap.String("method", req.httpRequest.Method),
		zap.String("remoteAddr", req.httpRequest.RemoteAddr),
		zap.String("pathname", req.httpRequest.URL.RequestURI()),
		zap.String("host", req.httpRequest.Host),
		zap.Time("timestamp-started", req.startTime),
		zap.Time("timestamp-finished", res.finishTime),
		zap.Int("statusCode", res.StatusCode),
	}

	if res.err != nil {
		fields = append(fields, zap.Error(res.err))

		cause := errors.Cause(res.err)
		if cause != nil && cause != res.err {
			fields = append(fields, zap.NamedError("errorCause", cause))
		}
	}

	for k, v := range req.httpRequest.Header {
		if len(v) > 0 {
			fields = append(fields, zap.String("Request-Header-"+k, v[0]))
		}
	}
	for k, v := range req.res.responseWriter.Header() {
		if len(v) > 0 {
			fields = append(fields, zap.String("Response-Header-"+k, v[0]))
		}
	}

	return fields
}

// SendErrorString helper to send an error string
func (res *ServerHTTPResponse) SendErrorString(
	statusCode int, errMsg string,
) {
	res.WriteJSONBytes(statusCode, nil,
		[]byte(`{"error":"`+errMsg+`"}`),
	)
}

// SendError helper to send an server error message, propagates underlying cause to logs etc.
func (res *ServerHTTPResponse) SendError(
	statusCode int, errMsg string, errCause error,
) {
	res.err = errCause
	res.WriteJSONBytes(statusCode, nil,
		[]byte(`{"error":"`+errMsg+`"}`),
	)
}

// WriteBytes writes a byte[] slice that is valid Response
func (res *ServerHTTPResponse) WriteBytes(
	statusCode int, headers Header, bytes []byte,
) {
	if headers != nil {
		for _, k := range headers.Keys() {
			v, ok := headers.Get(k)
			if ok {
				res.responseWriter.Header().Set(k, v)
			}
		}
	}

	res.pendingStatusCode = statusCode
	res.pendingBodyBytes = bytes
}

// WriteJSONBytes writes a byte[] slice that is valid json to Response
func (res *ServerHTTPResponse) WriteJSONBytes(
	statusCode int, headers Header, bytes []byte,
) {
	if headers == nil {
		headers = ServerHTTPHeader{}
	}

	headers.Add("content-type", "application/json")
	res.WriteBytes(statusCode, headers, bytes)
}

// WriteJSON writes a json serializable struct to Response
func (res *ServerHTTPResponse) WriteJSON(
	statusCode int, headers Header, body json.Marshaler,
) {
	if body == nil {
		res.SendError(500, "Could not serialize json response", errors.New("No Body JSON"))
		res.logger.Error("Could not serialize nil pointer body")
		return
	}

	bytes, err := body.MarshalJSON()
	if err != nil {
		res.SendError(500, "Could not serialize json response", err)
		res.logger.Error("Could not serialize json response", zap.Error(err))
		return
	}

	if headers != nil {
		for _, k := range headers.Keys() {
			v, ok := headers.Get(k)
			if ok {
				res.responseWriter.Header().Set(k, v)
			}
		}
	}

	res.responseWriter.Header().
		Set("content-type", "application/json")

	res.pendingStatusCode = statusCode
	res.pendingBodyBytes = bytes
	res.pendingBodyObj = body
}

// PeekBody allows for inspecting a key path inside the body
// that is not flushed yet. This is useful for response middlewares
// that want to inspect the response body.
func (res *ServerHTTPResponse) PeekBody(
	keys ...string,
) ([]byte, jsonparser.ValueType, error) {
	value, valueType, _, err := jsonparser.Get(
		res.pendingBodyBytes, keys...,
	)

	if err != nil {
		return nil, -1, err
	}

	return value, valueType, nil
}

// Flush will write the body to the response. Before flush is called
// the body is pending. A pending body allows a response middleware to
// write a different body.
func (res *ServerHTTPResponse) flush() {
	if res.flushed {
		/* coverage ignore next line */
		res.logger.Error(
			"Flushed a server response twice",
			zap.String("path", res.Request.URL.Path),
		)
		/* coverage ignore next line */
		return
	}

	res.flushed = true
	res.writeHeader(res.pendingStatusCode)
	res.writeBytes(res.pendingBodyBytes)
	res.finish()
}

func (res *ServerHTTPResponse) writeHeader(statusCode int) {
	res.StatusCode = statusCode
	res.responseWriter.WriteHeader(statusCode)
}

// WriteBytes writes raw bytes to output
func (res *ServerHTTPResponse) writeBytes(bytes []byte) {
	_, err := res.responseWriter.Write(bytes)
	if err != nil {
		/* coverage ignore next line */
		res.logger.Error(
			"Could not write string to resp body",
			zap.Error(err),
		)
	}
}

// GetPendingResponse lets you read the pending body bytes, obj and status code
// which isn't sent back yet.
func (res *ServerHTTPResponse) GetPendingResponse() ([]byte, int) {
	return res.pendingBodyBytes, res.pendingStatusCode
}

// Headers returns the underlying http response's headers
func (res *ServerHTTPResponse) Headers() http.Header {
	return res.responseWriter.Header()
}
<|endoftext|>


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

package nosql

import (
	"context"
	"fmt"

	"github.com/hidal-go/hidalgo/legacy/nosql"

	"github.com/cayleygraph/cayley/clog"
	"github.com/cayleygraph/cayley/graph"
	"github.com/cayleygraph/cayley/quad"
)

var _ graph.Iterator = (*Iterator)(nil)

type Linkage struct {
	Dir quad.Direction
	Val NodeHash
}

type Iterator struct {
	qs         *QuadStore
	collection string
	limit      int64
	constraint []nosql.FieldFilter
	links      []Linkage // used in Contains

	iter   nosql.DocIterator
	result graph.Ref
	size   int64
	err    error
}

func linkageToFilters(links []Linkage) []nosql.FieldFilter {
	filters := make([]nosql.FieldFilter, 0, len(links))
	for _, l := range links {
		filters = append(filters, nosql.FieldFilter{
			Path:   []string{l.Dir.String()},
			Filter: nosql.Equal,
			Value:  nosql.String(l.Val),
		})
	}
	return filters
}

func NewLinksToIterator(qs *QuadStore, collection string, links []Linkage) *Iterator {
	filters := linkageToFilters(links)
	it := NewIterator(qs, collection, filters...)
	it.links = links
	return it
}

func (it *Iterator) makeIterator() nosql.DocIterator {
	q := it.qs.db.Query(it.collection)
	if len(it.constraint) != 0 {
		q = q.WithFields(it.constraint...)
	}
	if it.limit > 0 {
		q = q.Limit(int(it.limit))
	}
	return q.Iterate()
}

func NewIterator(qs *QuadStore, collection string, constraints ...nosql.FieldFilter) *Iterator {
	return &Iterator{
		qs:         qs,
		constraint: constraints,
		collection: collection,
		size:       -1,
	}
}

func (it *Iterator) Reset() {
	it.Close()
	it.iter = it.makeIterator()
}

func (it *Iterator) Close() error {
	if it.iter != nil {
		return it.iter.Close()
	}
	return nil
}

func (it *Iterator) TagResults(dst map[string]graph.Ref) {}

func (it *Iterator) Next(ctx context.Context) bool {
	if it.iter == nil {
		it.iter = it.makeIterator()
	}
	var doc nosql.Document
	for {
		if !it.iter.Next(ctx) {
			if err := it.iter.Err(); err != nil {
				it.err = err
				clog.Errorf("error nexting iterator: %v", err)
			}
			return false
		}
		doc = it.iter.Doc()
		if it.collection == colQuads && !checkQuadValid(doc) {
			continue
		}
		break
	}
	if it.collection == colQuads {
		sh, _ := doc[fldSubject].(nosql.String)
		ph, _ := doc[fldPredicate].(nosql.String)
		oh, _ := doc[fldObject].(nosql.String)
		lh, _ := doc[fldLabel].(nosql.String)
		it.result = QuadHash{
			string(sh), string(ph), string(oh), string(lh),
		}
	} else {
		id, _ := doc[fldHash].(nosql.String)
		it.result = NodeHash(id)
	}
	return true
}

func (it *Iterator) Err() error {
	return it.err
}

func (it *Iterator) Result() graph.Ref {
	return it.result
}

func (it *Iterator) NextPath(ctx context.Context) bool {
	return false
}

func (it *Iterator) SubIterators() []graph.Iterator {
	return nil
}

func (it *Iterator) Contains(ctx context.Context, v graph.Ref) bool {
	if len(it.links) != 0 {
		qh := v.(QuadHash)
		for _, l := range it.links {
			if l.Val != NodeHash(qh.Get(l.Dir)) {
				return false
			}
		}
		it.result = v
		return true
	}
	if len(it.constraint) == 0 {
		it.result = v
		return true
	}
	qv := it.qs.NameOf(v)
	if qv == nil {
		return false
	}
	d := toDocumentValue(&it.qs.opt, qv)
	for _, f := range it.constraint {
		if !f.Matches(d) {
			return false
		}
	}
	it.result = v
	return true
}

func (it *Iterator) Size() (int64, bool) {
	if it.size == -1 {
		var err error
		it.size, err = it.qs.getSize(it.collection, it.constraint)
		if err != nil {
			it.err = err
		}
	}
	if it.limit > 0 && it.size > it.limit {
		it.size = it.limit
	}
	if it.size < 0 {
		return it.qs.Size(), false
	}
	return it.size, true
}

func (it *Iterator) Sorted() bool                     { return true }
func (it *Iterator) Optimize() (graph.Iterator, bool) { return it, false }

func (it *Iterator) String() string {
	return fmt.Sprintf("NoSQL(%v)", it.collection)
}

func (it *Iterator) Stats() graph.IteratorStats {
	size, exact := it.Size()
	return graph.IteratorStats{
		ContainsCost: 1,
		NextCost:     5,
		Size:         size,
		ExactSize:    exact,
	}
}
<commit_msg>nosql: rewrite iterators for the new interfaces<commit_after>// Copyright 2014 The Cayley Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package nosql

import (
	"context"
	"fmt"

	"github.com/hidal-go/hidalgo/legacy/nosql"

	"github.com/cayleygraph/cayley/clog"
	"github.com/cayleygraph/cayley/graph"
	"github.com/cayleygraph/cayley/quad"
)

type Linkage struct {
	Dir quad.Direction
	Val NodeHash
}

func linkageToFilters(links []Linkage) []nosql.FieldFilter {
	filters := make([]nosql.FieldFilter, 0, len(links))
	for _, l := range links {
		filters = append(filters, nosql.FieldFilter{
			Path:   []string{l.Dir.String()},
			Filter: nosql.Equal,
			Value:  nosql.String(l.Val),
		})
	}
	return filters
}

var _ graph.IteratorFuture = (*Iterator)(nil)

func NewLinksToIterator(qs *QuadStore, collection string, links []Linkage) *Iterator {
	it := &Iterator{
		it: newLinksToIterator(qs, collection, links),
	}
	it.Iterator = graph.NewLegacy(it.it, it)
	return it
}

func NewIterator(qs *QuadStore, collection string, constraints ...nosql.FieldFilter) *Iterator {
	it := &Iterator{
		it: newIterator(qs, collection, constraints...),
	}
	it.Iterator = graph.NewLegacy(it.it, it)
	return it
}

type Iterator struct {
	it *iterator2
	graph.Iterator
}

func (it *Iterator) AsShape() graph.Shape {
	it.Close()
	return it.it
}

func (it *Iterator) Sorted() bool { return true }

var _ graph.ShapeCompat = (*iterator2)(nil)

type iterator2 struct {
	qs         *QuadStore
	collection string
	limit      int64
	constraint []nosql.FieldFilter
	links      []Linkage // used in Contains

	size graph.Size
	err  error
}

func newLinksToIterator(qs *QuadStore, collection string, links []Linkage) *iterator2 {
	filters := linkageToFilters(links)
	it := newIterator(qs, collection, filters...)
	it.links = links
	return it
}

func newIterator(qs *QuadStore, collection string, constraints ...nosql.FieldFilter) *iterator2 {
	return &iterator2{
		qs:         qs,
		constraint: constraints,
		collection: collection,
		size:       graph.Size{Size: -1},
	}
}

func (it *iterator2) Iterate() graph.Scanner {
	return newIteratorNext(it.qs, it.collection, it.constraint, it.limit)
}

func (it *iterator2) Lookup() graph.Index {
	return newIteratorContains(it.qs, it.collection, it.constraint, it.links, it.limit)
}

func (it *iterator2) AsLegacy() graph.Iterator {
	it2 := &Iterator{it: it}
	it2.Iterator = graph.NewLegacy(it, it2)
	return it2
}

func (it *iterator2) SubIterators() []graph.Shape {
	return nil
}

func (it *iterator2) getSize(ctx context.Context) (graph.Size, error) {
	if it.size.Size == -1 {
		size, err := it.qs.getSize(it.collection, it.constraint)
		if err != nil {
			it.err = err
		}
		it.size = graph.Size{
			Size:  size,
			Exact: true,
		}
	}
	if it.limit > 0 && it.size.Size > it.limit {
		it.size.Size = it.limit
	}
	if it.size.Size < 0 {
		return graph.Size{
			Size:  it.qs.Size(),
			Exact: false,
		}, it.err
	}
	return it.size, nil
}

func (it *iterator2) Sorted() bool                                     { return true }
func (it *iterator2) Optimize(ctx context.Context) (graph.Shape, bool) { return it, false }

func (it *iterator2) String() string {
	return fmt.Sprintf("NoSQL(%v)", it.collection)
}

func (it *iterator2) Stats(ctx context.Context) (graph.IteratorCosts, error) {
	size, err := it.getSize(ctx)
	return graph.IteratorCosts{
		ContainsCost: 1,
		NextCost:     5,
		Size:         size,
	}, err
}

type iteratorNext struct {
	qs         *QuadStore
	collection string
	limit      int64
	constraint []nosql.FieldFilter

	iter   nosql.DocIterator
	result graph.Ref
	err    error
}

func newIteratorNext(qs *QuadStore, collection string, constraints []nosql.FieldFilter, limit int64) *iteratorNext {
	return &iteratorNext{
		qs:         qs,
		constraint: constraints,
		collection: collection,
		limit:      limit,
	}
}

func (it *iteratorNext) makeIterator() nosql.DocIterator {
	q := it.qs.db.Query(it.collection)
	if len(it.constraint) != 0 {
		q = q.WithFields(it.constraint...)
	}
	if it.limit > 0 {
		q = q.Limit(int(it.limit))
	}
	return q.Iterate()
}

func (it *iteratorNext) Close() error {
	if it.iter != nil {
		return it.iter.Close()
	}
	return nil
}

func (it *iteratorNext) TagResults(dst map[string]graph.Ref) {}

func (it *iteratorNext) Next(ctx context.Context) bool {
	if it.iter == nil {
		it.iter = it.makeIterator()
	}
	var doc nosql.Document
	for {
		if !it.iter.Next(ctx) {
			if err := it.iter.Err(); err != nil {
				it.err = err
				clog.Errorf("error nexting iterator: %v", err)
			}
			return false
		}
		doc = it.iter.Doc()
		if it.collection == colQuads && !checkQuadValid(doc) {
			continue
		}
		break
	}
	if it.collection == colQuads {
		sh, _ := doc[fldSubject].(nosql.String)
		ph, _ := doc[fldPredicate].(nosql.String)
		oh, _ := doc[fldObject].(nosql.String)
		lh, _ := doc[fldLabel].(nosql.String)
		it.result = QuadHash{
			string(sh), string(ph), string(oh), string(lh),
		}
	} else {
		id, _ := doc[fldHash].(nosql.String)
		it.result = NodeHash(id)
	}
	return true
}

func (it *iteratorNext) Err() error {
	return it.err
}

func (it *iteratorNext) Result() graph.Ref {
	return it.result
}

func (it *iteratorNext) NextPath(ctx context.Context) bool {
	return false
}

func (it *iteratorNext) Sorted() bool { return true }

func (it *iteratorNext) String() string {
	return fmt.Sprintf("NoSQLNext(%v)", it.collection)
}

type iteratorContains struct {
	qs         *QuadStore
	collection string
	limit      int64 // FIXME(dennwc): doesn't work right now
	constraint []nosql.FieldFilter
	links      []Linkage

	iter   nosql.DocIterator
	result graph.Ref
	err    error
}

func newIteratorContains(qs *QuadStore, collection string, constraints []nosql.FieldFilter, links []Linkage, limit int64) *iteratorContains {
	return &iteratorContains{
		qs:         qs,
		collection: collection,
		constraint: constraints,
		links:      links,
		limit:      limit,
	}
}

func (it *iteratorContains) makeIterator() nosql.DocIterator {
	q := it.qs.db.Query(it.collection)
	if len(it.constraint) != 0 {
		q = q.WithFields(it.constraint...)
	}
	if it.limit > 0 {
		q = q.Limit(int(it.limit))
	}
	return q.Iterate()
}

func (it *iteratorContains) Close() error {
	if it.iter != nil {
		return it.iter.Close()
	}
	return nil
}

func (it *iteratorContains) TagResults(dst map[string]graph.Ref) {}

func (it *iteratorContains) Err() error {
	return it.err
}

func (it *iteratorContains) Result() graph.Ref {
	return it.result
}

func (it *iteratorContains) NextPath(ctx context.Context) bool {
	return false
}

func (it *iteratorContains) Contains(ctx context.Context, v graph.Ref) bool {
	if len(it.links) != 0 {
		qh := v.(QuadHash)
		for _, l := range it.links {
			if l.Val != NodeHash(qh.Get(l.Dir)) {
				return false
			}
		}
		it.result = v
		return true
	}
	if len(it.constraint) == 0 {
		it.result = v
		return true
	}
	qv := it.qs.NameOf(v)
	if qv == nil {
		return false
	}
	d := toDocumentValue(&it.qs.opt, qv)
	for _, f := range it.constraint {
		if !f.Matches(d) {
			return false
		}
	}
	it.result = v
	return true
}

func (it *iteratorContains) Sorted() bool { return true }

func (it *iteratorContains) String() string {
	return fmt.Sprintf("NoSQLContains(%v)", it.collection)
}
<|endoftext|>