<commit_before>package connmgr

import (
	"context"
	"sort"
	"sync"
	"time"

	logging "github.com/ipfs/go-log"
	ifconnmgr "github.com/libp2p/go-libp2p-interface-connmgr"
	inet "github.com/libp2p/go-libp2p-net"
	peer "github.com/libp2p/go-libp2p-peer"
	ma "github.com/multiformats/go-multiaddr"
)

var log = logging.Logger("connmgr")

type connManager struct {
	highWater int
	lowWater  int

	gracePeriod time.Duration

	peers     map[peer.ID]*peerInfo
	connCount int

	lk sync.Mutex

	lastTrim time.Time
}

var _ ifconnmgr.ConnManager = (*connManager)(nil)

func NewConnManager(low, hi int, grace time.Duration) ifconnmgr.ConnManager {
	return &connManager{
		highWater:   hi,
		lowWater:    low,
		gracePeriod: grace,
		peers:       make(map[peer.ID]*peerInfo),
	}
}

type peerInfo struct {
	tags  map[string]int
	value int

	conns map[inet.Conn]time.Time

	firstSeen time.Time
}

func (cm *connManager) TrimOpenConns(ctx context.Context) {
	defer log.EventBegin(ctx, "connCleanup").Done()
	for _, c := range cm.getConnsToClose(ctx) {
		log.Info("closing conn: ", c.RemotePeer())
		log.Event(ctx, "closeConn", c.RemotePeer())
		c.Close()
	}
}

func (cm *connManager) getConnsToClose(ctx context.Context) []inet.Conn {
	cm.lk.Lock()
	defer cm.lk.Unlock()
	if cm.lowWater == 0 || cm.highWater == 0 {
		// disabled
		return nil
	}
	now := time.Now()
	cm.lastTrim = now

	if len(cm.peers) < cm.lowWater {
		log.Info("open connection count below limit")
		return nil
	}

	var infos []*peerInfo

	for _, inf := range cm.peers {
		infos = append(infos, inf)
	}

	sort.Slice(infos, func(i, j int) bool {
		return infos[i].value < infos[j].value
	})

	closeCount := len(infos) - cm.lowWater
	toclose := infos[:closeCount]

	// 2x number of peers we're disconnecting from because we may have more
	// than one connection per peer. Slightly over allocating isn't an issue
	// as this is a very short-lived array.
	closed := make([]inet.Conn, 0, len(toclose)*2)

	for _, inf := range toclose {
		// TODO: should we be using firstSeen or the time associated with the connection itself?
		if inf.firstSeen.Add(cm.gracePeriod).After(now) {
			continue
		}

		// TODO: if a peer has more than one connection, maybe only close one?
		for c := range inf.conns {
			// TODO: probably don't want to always do this in a goroutine
			closed = append(closed, c)
		}
	}

	return closed
}

func (cm *connManager) GetTagInfo(p peer.ID) *ifconnmgr.TagInfo {
	cm.lk.Lock()
	defer cm.lk.Unlock()

	pi, ok := cm.peers[p]
	if !ok {
		return nil
	}

	out := &ifconnmgr.TagInfo{
		FirstSeen: pi.firstSeen,
		Value:     pi.value,
		Tags:      make(map[string]int),
		Conns:     make(map[string]time.Time),
	}

	for t, v := range pi.tags {
		out.Tags[t] = v
	}
	for c, t := range pi.conns {
		out.Conns[c.RemoteMultiaddr().String()] = t
	}

	return out
}

func (cm *connManager) TagPeer(p peer.ID, tag string, val int) {
	cm.lk.Lock()
	defer cm.lk.Unlock()

	pi, ok := cm.peers[p]
	if !ok {
		log.Error("tried to tag conn from untracked peer: ", p)
		return
	}

	pi.value += (val - pi.tags[tag])
	pi.tags[tag] = val
}

func (cm *connManager) UntagPeer(p peer.ID, tag string) {
	cm.lk.Lock()
	defer cm.lk.Unlock()

	pi, ok := cm.peers[p]
	if !ok {
		log.Error("tried to remove tag from untracked peer: ", p)
		return
	}

	pi.value -= pi.tags[tag]
	delete(pi.tags, tag)
}

func (cm *connManager) Notifee() inet.Notifiee {
	return (*cmNotifee)(cm)
}

type cmNotifee connManager

func (nn *cmNotifee) cm() *connManager {
	return (*connManager)(nn)
}

func (nn *cmNotifee) Connected(n inet.Network, c inet.Conn) {
	cm := nn.cm()

	cm.lk.Lock()
	defer cm.lk.Unlock()

	pinfo, ok := cm.peers[c.RemotePeer()]
	if !ok {
		pinfo = &peerInfo{
			firstSeen: time.Now(),
			tags:      make(map[string]int),
			conns:     make(map[inet.Conn]time.Time),
		}
		cm.peers[c.RemotePeer()] = pinfo
	}

	_, ok = pinfo.conns[c]
	if ok {
		log.Error("received connected notification for conn we are already tracking: ", c.RemotePeer())
		return
	}

	pinfo.conns[c] = time.Now()
	cm.connCount++

	if cm.connCount > nn.highWater {
		if cm.lastTrim.IsZero() || time.Since(cm.lastTrim) > time.Second*10 {
			go cm.TrimOpenConns(context.Background())
		}
	}
}

func (nn *cmNotifee) Disconnected(n inet.Network, c inet.Conn) {
	cm := nn.cm()

	cm.lk.Lock()
	defer cm.lk.Unlock()

	cinf, ok := cm.peers[c.RemotePeer()]
	if !ok {
		log.Error("received disconnected notification for peer we are not tracking: ", c.RemotePeer())
		return
	}

	_, ok = cinf.conns[c]
	if !ok {
		log.Error("received disconnected notification for conn we are not tracking: ", c.RemotePeer())
		return
	}

	delete(cinf.conns, c)
	cm.connCount--
	if len(cinf.conns) == 0 {
		delete(cm.peers, c.RemotePeer())
	}
}

func (nn *cmNotifee) Listen(n inet.Network, addr ma.Multiaddr)      {}
func (nn *cmNotifee) ListenClose(n inet.Network, addr ma.Multiaddr) {}
func (nn *cmNotifee) OpenedStream(inet.Network, inet.Stream)        {}
func (nn *cmNotifee) ClosedStream(inet.Network, inet.Stream)        {}
<commit_msg>add a way to expose info from the conn manager<commit_after>package connmgr

import (
	"context"
	"sort"
	"sync"
	"time"

	logging "github.com/ipfs/go-log"
	ifconnmgr "github.com/libp2p/go-libp2p-interface-connmgr"
	inet "github.com/libp2p/go-libp2p-net"
	peer "github.com/libp2p/go-libp2p-peer"
	ma "github.com/multiformats/go-multiaddr"
)

var log = logging.Logger("connmgr")

type BasicConnMgr struct {
	highWater int
	lowWater  int

	gracePeriod time.Duration

	peers     map[peer.ID]*peerInfo
	connCount int

	lk sync.Mutex

	lastTrim time.Time
}

var _ ifconnmgr.ConnManager = (*BasicConnMgr)(nil)

func NewConnManager(low, hi int, grace time.Duration) *BasicConnMgr {
	return &BasicConnMgr{
		highWater:   hi,
		lowWater:    low,
		gracePeriod: grace,
		peers:       make(map[peer.ID]*peerInfo),
	}
}

type peerInfo struct {
	tags  map[string]int
	value int

	conns map[inet.Conn]time.Time

	firstSeen time.Time
}

func (cm *BasicConnMgr) TrimOpenConns(ctx context.Context) {
	defer log.EventBegin(ctx, "connCleanup").Done()
	for _, c := range cm.getConnsToClose(ctx) {
		log.Info("closing conn: ", c.RemotePeer())
		log.Event(ctx, "closeConn", c.RemotePeer())
		c.Close()
	}
}

func (cm *BasicConnMgr) getConnsToClose(ctx context.Context) []inet.Conn {
	cm.lk.Lock()
	defer cm.lk.Unlock()
	if cm.lowWater == 0 || cm.highWater == 0 {
		// disabled
		return nil
	}
	now := time.Now()
	cm.lastTrim = now

	if len(cm.peers) < cm.lowWater {
		log.Info("open connection count below limit")
		return nil
	}

	var infos []*peerInfo

	for _, inf := range cm.peers {
		infos = append(infos, inf)
	}

	sort.Slice(infos, func(i, j int) bool {
		return infos[i].value < infos[j].value
	})

	closeCount := len(infos) - cm.lowWater
	toclose := infos[:closeCount]

	// 2x number of peers we're disconnecting from because we may have more
	// than one connection per peer. Slightly over allocating isn't an issue
	// as this is a very short-lived array.
	closed := make([]inet.Conn, 0, len(toclose)*2)

	for _, inf := range toclose {
		// TODO: should we be using firstSeen or the time associated with the connection itself?
		if inf.firstSeen.Add(cm.gracePeriod).After(now) {
			continue
		}

		// TODO: if a peer has more than one connection, maybe only close one?
		for c := range inf.conns {
			// TODO: probably don't want to always do this in a goroutine
			closed = append(closed, c)
		}
	}

	return closed
}

func (cm *BasicConnMgr) GetTagInfo(p peer.ID) *ifconnmgr.TagInfo {
	cm.lk.Lock()
	defer cm.lk.Unlock()

	pi, ok := cm.peers[p]
	if !ok {
		return nil
	}

	out := &ifconnmgr.TagInfo{
		FirstSeen: pi.firstSeen,
		Value:     pi.value,
		Tags:      make(map[string]int),
		Conns:     make(map[string]time.Time),
	}

	for t, v := range pi.tags {
		out.Tags[t] = v
	}
	for c, t := range pi.conns {
		out.Conns[c.RemoteMultiaddr().String()] = t
	}

	return out
}

func (cm *BasicConnMgr) TagPeer(p peer.ID, tag string, val int) {
	cm.lk.Lock()
	defer cm.lk.Unlock()

	pi, ok := cm.peers[p]
	if !ok {
		log.Error("tried to tag conn from untracked peer: ", p)
		return
	}

	pi.value += (val - pi.tags[tag])
	pi.tags[tag] = val
}

func (cm *BasicConnMgr) UntagPeer(p peer.ID, tag string) {
	cm.lk.Lock()
	defer cm.lk.Unlock()

	pi, ok := cm.peers[p]
	if !ok {
		log.Error("tried to remove tag from untracked peer: ", p)
		return
	}

	pi.value -= pi.tags[tag]
	delete(pi.tags, tag)
}

type CMInfo struct {
	LowWater    int
	HighWater   int
	LastTrim    time.Time
	GracePeriod time.Duration
	ConnCount   int
}

func (cm *BasicConnMgr) GetInfo() CMInfo {
	cm.lk.Lock()
	defer cm.lk.Unlock()

	return CMInfo{
		HighWater:   cm.highWater,
		LowWater:    cm.lowWater,
		LastTrim:    cm.lastTrim,
		GracePeriod: cm.gracePeriod,
		ConnCount:   cm.connCount,
	}
}

func (cm *BasicConnMgr) Notifee() inet.Notifiee {
	return (*cmNotifee)(cm)
}

type cmNotifee BasicConnMgr

func (nn *cmNotifee) cm() *BasicConnMgr {
	return (*BasicConnMgr)(nn)
}

func (nn *cmNotifee) Connected(n inet.Network, c inet.Conn) {
	cm := nn.cm()

	cm.lk.Lock()
	defer cm.lk.Unlock()

	pinfo, ok := cm.peers[c.RemotePeer()]
	if !ok {
		pinfo = &peerInfo{
			firstSeen: time.Now(),
			tags:      make(map[string]int),
			conns:     make(map[inet.Conn]time.Time),
		}
		cm.peers[c.RemotePeer()] = pinfo
	}

	_, ok = pinfo.conns[c]
	if ok {
		log.Error("received connected notification for conn we are already tracking: ", c.RemotePeer())
		return
	}

	pinfo.conns[c] = time.Now()
	cm.connCount++

	if cm.connCount > nn.highWater {
		if cm.lastTrim.IsZero() || time.Since(cm.lastTrim) > time.Second*10 {
			go cm.TrimOpenConns(context.Background())
		}
	}
}

func (nn *cmNotifee) Disconnected(n inet.Network, c inet.Conn) {
	cm := nn.cm()

	cm.lk.Lock()
	defer cm.lk.Unlock()

	cinf, ok := cm.peers[c.RemotePeer()]
	if !ok {
		log.Error("received disconnected notification for peer we are not tracking: ", c.RemotePeer())
		return
	}

	_, ok = cinf.conns[c]
	if !ok {
		log.Error("received disconnected notification for conn we are not tracking: ", c.RemotePeer())
		return
	}

	delete(cinf.conns, c)
	cm.connCount--
	if len(cinf.conns) == 0 {
		delete(cm.peers, c.RemotePeer())
	}
}

func (nn *cmNotifee) Listen(n inet.Network, addr ma.Multiaddr)      {}
func (nn *cmNotifee) ListenClose(n inet.Network, addr ma.Multiaddr) {}
func (nn *cmNotifee) OpenedStream(inet.Network, inet.Stream)        {}
func (nn *cmNotifee) ClosedStream(inet.Network, inet.Stream)        {}
<|endoftext|>


<commit_before>package moxxiConf

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"github.com/dchest/uniuri"
	"net"
	"net/http"
	"os"
	"strconv"
	"strings"
)

func inArr(a []string, t string) bool {
	for _, s := range a {
		if t == s {
			return true
		}
	}
	return false
}

func validHost(s string) string {
	s = strings.Trim(s, ".")
	parts := strings.Split(s, DomainSep)
	if len(parts) < 2 {
		return ""
	}
	for i := 0; i < len(parts)-1; {
		switch {
		case len(parts[i]) < 1:
			parts = append(parts[:i], parts[i+1:]...)
		case isNotAlphaNum.MatchString(parts[i]):
			return ""
		default:
			i++
		}
	}
	return strings.Join(parts, DomainSep)
}

func confCheck(proxy siteParams, config HandlerConfig) (siteParams, Err) {
	var conf siteParams
	if conf.IntHost = validHost(proxy.IntHost); conf.IntHost == "" {
		return siteParams{}, &NewErr{Code: ErrBadHost, value: proxy.IntHost}
	}

	tempIP := net.ParseIP(proxy.IntIP)
	if tempIP == nil {
		return siteParams{}, &NewErr{Code: ErrBadIP, value: proxy.IntIP}
	}
	if len(config.ipList) > 0 && !ipListContains(tempIP, config.ipList) {
		return siteParams{}, &NewErr{Code: ErrBlockedIP, value: tempIP.String()}
	}

	conf.IntPort = 80
	if proxy.IntPort > 0 && proxy.IntPort < MaxAllowedPort {
		conf.IntPort = proxy.IntPort
	}

	conf.IntIP = tempIP.String()
	conf.Encrypted = proxy.Encrypted
	conf.StripHeaders = proxy.StripHeaders

	if config.redirectTracing {
		newIntHost, newIntPort, newEncrypted, err := redirectTrace(conf.IntHost, conf.IntPort, conf.Encrypted)
		if err == nil {
			conf.IntHost = newIntHost
			conf.IntPort = newIntPort
			conf.Encrypted = newEncrypted
		}
	}

	return conf, nil
}

func confWrite(config HandlerConfig) func(siteParams) (siteParams, Err) {

	return func(siteConfig siteParams) (siteParams, Err) {

		err := os.ErrExist
		var randPart, fileName string
		var f *os.File

		for os.IsExist(err) {
			randPart = uniuri.NewLenChars(config.subdomainLen, SubdomainChars)
			// pick again if you got something reserved
			if inArr(config.exclude, randPart) {
				continue
			}
			if inArr(config.exclude, randPart+DomainSep+config.baseURL) {
				continue
			}
			fileName = strings.Join([]string{
				strings.TrimRight(config.confPath, PathSep),
				PathSep,
				randPart,
				DomainSep,
				config.baseURL,
				DomainSep,
				strings.TrimLeft(config.confExt, DomainSep)}, "")
			f, err = os.Create(fileName)
		}

		siteConfig.ExtHost = strings.Join([]string{
			randPart,
			DomainSep,
			config.baseURL}, "")

		if err == os.ErrPermission {
			return siteParams{ExtHost: randPart}, &NewErr{Code: ErrFilePerm, value: fileName, deepErr: err}
		} else if err != nil {
			return siteParams{ExtHost: randPart}, &NewErr{Code: ErrFileUnexpect, value: fileName, deepErr: err}
		}

		tErr := config.confTempl.Execute(f, siteConfig)

		if err = f.Close(); err != nil {
			return siteParams{}, &NewErr{Code: ErrCloseFile, value: fileName, deepErr: err}
		}

		if tErr != nil {
			if err = os.Remove(fileName); err != nil {
				return siteParams{}, &NewErr{Code: ErrRemoveFile, value: fileName, deepErr: err}
			}
		}

		return siteConfig, nil
	}
}

func parseCheckbox(in string) bool {
	checkedValues := []string{
		"true",
		"checked",
		"on",
		"yes",
		"y",
		"1",
	}

	for _, each := range checkedValues {
		if each == in {
			return true
		}
	}
	return false
}

func parseIPList(ipFile string) ([]*net.IPNet, Err) {
	file, err := os.Open(ipFile)
	if err != nil {
		return []*net.IPNet{}, NewErr{
			Code:    ErrConfigBadIPFile,
			value:   ipFile,
			deepErr: err,
		}
	}

	var out []*net.IPNet

	s := bufio.NewScanner(file)

	for s.Scan() {
		t := strings.TrimSpace(s.Text())
		switch {
		case strings.HasPrefix(t, "//"):
		case strings.HasPrefix(t, "#"):
		case strings.HasPrefix(t, ";"):
		default:
			_, ipNet, err := net.ParseCIDR(s.Text())
			if err == nil {
				out = append(out, ipNet)
			}
		}
	}
	return out, nil
}

func ipListContains(address net.IP, list []*net.IPNet) bool {
	for _, each := range list {
		if each.Contains(address) {
			return true
		}
	}
	return false
}

func redirectTrace(initHost string, initPort int, initTLS bool) (string, int, bool, Err) {

	var initURL string
	switch {
	case initTLS && initPort == 443:
		initURL = (fmt.Sprintf("https://%s/", initHost))
	case initTLS && initPort != 443:
		initURL = (fmt.Sprintf("https://%s:%d/", initHost, initPort))
	case !initTLS && initPort == 80:
		initURL = (fmt.Sprintf("http://%s/", initHost))
	case !initTLS && initPort != 80:
		initURL = (fmt.Sprintf("http://%s:%d/", initHost, initPort))
	}

	c := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
		},
	}

	resp, err := c.Head(initURL)
	if err != nil {
		return "", 0, false, NewErr{
			Code:    ErrBadHostnameTrace,
			value:   initURL,
			deepErr: err,
		}
	}

	var respHost string
	var respPort int
	var respTLS bool

	if resp.Request == nil {
		return "", 0, false, NewErr{
			Code:    ErrBadHostnameTrace,
			value:   initURL,
			deepErr: fmt.Errorf("did not get a request back from %s", initURL),
		}
	}

	var hostname string
	if resp.Request.Host != "" {
		hostname = resp.Request.Host
	} else if resp.Request.URL != nil {
		hostname = resp.Request.URL.Host
	} else {
		return "", 0, false, NewErr{
			Code:    ErrBadHostnameTrace,
			value:   initURL,
			deepErr: fmt.Errorf("cound not find the URL"),
		}
	}

	if strings.Contains(hostname, ":") {
		parts := strings.Split(hostname, ":")
		respPort, err = strconv.Atoi(parts[len(parts)-1])
		if err != nil {
			respHost = resp.Request.Host
		} else {
			respHost = strings.Join(parts[:len(parts)-1], ":")
		}
	} else {
		respHost = hostname
		if resp.TLS == nil {
			respPort = 80
		} else {
			respPort = 443
		}
	}
	if resp.TLS != nil {
		respTLS = true
	}

	return respHost, respPort, respTLS, nil
}
<commit_msg>added a standard timeout to the redirectTrace method<commit_after>package moxxiConf

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"github.com/dchest/uniuri"
	"net"
	"net/http"
	"os"
	"strconv"
	"strings"
)

func inArr(a []string, t string) bool {
	for _, s := range a {
		if t == s {
			return true
		}
	}
	return false
}

func validHost(s string) string {
	s = strings.Trim(s, ".")
	parts := strings.Split(s, DomainSep)
	if len(parts) < 2 {
		return ""
	}
	for i := 0; i < len(parts)-1; {
		switch {
		case len(parts[i]) < 1:
			parts = append(parts[:i], parts[i+1:]...)
		case isNotAlphaNum.MatchString(parts[i]):
			return ""
		default:
			i++
		}
	}
	return strings.Join(parts, DomainSep)
}

func confCheck(proxy siteParams, config HandlerConfig) (siteParams, Err) {
	var conf siteParams
	if conf.IntHost = validHost(proxy.IntHost); conf.IntHost == "" {
		return siteParams{}, &NewErr{Code: ErrBadHost, value: proxy.IntHost}
	}

	tempIP := net.ParseIP(proxy.IntIP)
	if tempIP == nil {
		return siteParams{}, &NewErr{Code: ErrBadIP, value: proxy.IntIP}
	}
	if len(config.ipList) > 0 && !ipListContains(tempIP, config.ipList) {
		return siteParams{}, &NewErr{Code: ErrBlockedIP, value: tempIP.String()}
	}

	conf.IntPort = 80
	if proxy.IntPort > 0 && proxy.IntPort < MaxAllowedPort {
		conf.IntPort = proxy.IntPort
	}

	conf.IntIP = tempIP.String()
	conf.Encrypted = proxy.Encrypted
	conf.StripHeaders = proxy.StripHeaders

	if config.redirectTracing {
		newIntHost, newIntPort, newEncrypted, err := redirectTrace(conf.IntHost, conf.IntPort, conf.Encrypted)
		if err == nil {
			conf.IntHost = newIntHost
			conf.IntPort = newIntPort
			conf.Encrypted = newEncrypted
		}
	}

	return conf, nil
}

func confWrite(config HandlerConfig) func(siteParams) (siteParams, Err) {

	return func(siteConfig siteParams) (siteParams, Err) {

		err := os.ErrExist
		var randPart, fileName string
		var f *os.File

		for os.IsExist(err) {
			randPart = uniuri.NewLenChars(config.subdomainLen, SubdomainChars)
			// pick again if you got something reserved
			if inArr(config.exclude, randPart) {
				continue
			}
			if inArr(config.exclude, randPart+DomainSep+config.baseURL) {
				continue
			}
			fileName = strings.Join([]string{
				strings.TrimRight(config.confPath, PathSep),
				PathSep,
				randPart,
				DomainSep,
				config.baseURL,
				DomainSep,
				strings.TrimLeft(config.confExt, DomainSep)}, "")
			f, err = os.Create(fileName)
		}

		siteConfig.ExtHost = strings.Join([]string{
			randPart,
			DomainSep,
			config.baseURL}, "")

		if err == os.ErrPermission {
			return siteParams{ExtHost: randPart}, &NewErr{Code: ErrFilePerm, value: fileName, deepErr: err}
		} else if err != nil {
			return siteParams{ExtHost: randPart}, &NewErr{Code: ErrFileUnexpect, value: fileName, deepErr: err}
		}

		tErr := config.confTempl.Execute(f, siteConfig)

		if err = f.Close(); err != nil {
			return siteParams{}, &NewErr{Code: ErrCloseFile, value: fileName, deepErr: err}
		}

		if tErr != nil {
			if err = os.Remove(fileName); err != nil {
				return siteParams{}, &NewErr{Code: ErrRemoveFile, value: fileName, deepErr: err}
			}
		}

		return siteConfig, nil
	}
}

func parseCheckbox(in string) bool {
	checkedValues := []string{
		"true",
		"checked",
		"on",
		"yes",
		"y",
		"1",
	}

	for _, each := range checkedValues {
		if each == in {
			return true
		}
	}
	return false
}

func parseIPList(ipFile string) ([]*net.IPNet, Err) {
	file, err := os.Open(ipFile)
	if err != nil {
		return []*net.IPNet{}, NewErr{
			Code:    ErrConfigBadIPFile,
			value:   ipFile,
			deepErr: err,
		}
	}

	var out []*net.IPNet

	s := bufio.NewScanner(file)

	for s.Scan() {
		t := strings.TrimSpace(s.Text())
		switch {
		case strings.HasPrefix(t, "//"):
		case strings.HasPrefix(t, "#"):
		case strings.HasPrefix(t, ";"):
		default:
			_, ipNet, err := net.ParseCIDR(s.Text())
			if err == nil {
				out = append(out, ipNet)
			}
		}
	}
	return out, nil
}

func ipListContains(address net.IP, list []*net.IPNet) bool {
	for _, each := range list {
		if each.Contains(address) {
			return true
		}
	}
	return false
}

func redirectTrace(initHost string, initPort int, initTLS bool) (string, int, bool, Err) {

	var initURL string
	switch {
	case initTLS && initPort == 443:
		initURL = (fmt.Sprintf("https://%s/", initHost))
	case initTLS && initPort != 443:
		initURL = (fmt.Sprintf("https://%s:%d/", initHost, initPort))
	case !initTLS && initPort == 80:
		initURL = (fmt.Sprintf("http://%s/", initHost))
	case !initTLS && initPort != 80:
		initURL = (fmt.Sprintf("http://%s:%d/", initHost, initPort))
	}

	c := &http.Client{
		Timeout: time.Second * 10,
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
		},
	}

	resp, err := c.Head(initURL)
	if err != nil {
		return "", 0, false, NewErr{
			Code:    ErrBadHostnameTrace,
			value:   initURL,
			deepErr: err,
		}
	}

	var respHost string
	var respPort int
	var respTLS bool

	if resp.Request == nil {
		return "", 0, false, NewErr{
			Code:    ErrBadHostnameTrace,
			value:   initURL,
			deepErr: fmt.Errorf("did not get a request back from %s", initURL),
		}
	}

	var hostname string
	if resp.Request.Host != "" {
		hostname = resp.Request.Host
	} else if resp.Request.URL != nil {
		hostname = resp.Request.URL.Host
	} else {
		return "", 0, false, NewErr{
			Code:    ErrBadHostnameTrace,
			value:   initURL,
			deepErr: fmt.Errorf("cound not find the URL"),
		}
	}

	if strings.Contains(hostname, ":") {
		parts := strings.Split(hostname, ":")
		respPort, err = strconv.Atoi(parts[len(parts)-1])
		if err != nil {
			respHost = resp.Request.Host
		} else {
			respHost = strings.Join(parts[:len(parts)-1], ":")
		}
	} else {
		respHost = hostname
		if resp.TLS == nil {
			respPort = 80
		} else {
			respPort = 443
		}
	}
	if resp.TLS != nil {
		respTLS = true
	}

	return respHost, respPort, respTLS, nil
}
<|endoftext|>


<commit_before>package raft_test

import (
	"reflect"
	"testing"
	"time"

	"github.com/relab/raft"
	pb "github.com/relab/raft/raftpb"
)

type mockStorage struct {
	term     uint64
	votedFor uint64
	log      []*pb.Entry
}

func (s *mockStorage) SaveState(uint64, uint64) error {
	return nil
}

func (s *mockStorage) SaveEntries([]*pb.Entry) error {
	return nil
}

func (s *mockStorage) Load() raft.Persistent {
	commands := make(map[raft.UniqueCommand]*pb.ClientCommandRequest)

	for _, entry := range s.log {
		commands[raft.UniqueCommand{
			ClientID:       entry.Data.ClientID,
			SequenceNumber: entry.Data.SequenceNumber,
		}] = entry.Data
	}

	return raft.Persistent{
		CurrentTerm: s.term,
		VotedFor:    s.votedFor,
		Log:         s.log,
		Commands:    commands,
	}
}

var term5 = &raft.Config{
	ElectionTimeout: time.Second,
	Storage:         &mockStorage{term: 5},
}

var term5log2 = &raft.Config{
	ElectionTimeout: time.Second,
	Storage: &mockStorage{
		term: 5,
		log: []*pb.Entry{
			&pb.Entry{
				Term: 4,
				Data: &pb.ClientCommandRequest{
					ClientID:       123,
					SequenceNumber: 456,
					Command:        "first",
				},
			},
			&pb.Entry{
				Term: 5,
				Data: &pb.ClientCommandRequest{
					ClientID:       123,
					SequenceNumber: 457,
					Command:        "second",
				},
			},
		},
	},
}

var handleRequestVoteRequestTests = []struct {
	name string
	r    *raft.Replica
	req  []*pb.RequestVoteRequest
	res  []*pb.RequestVoteResponse
}{
	{
		"lower term",
		raft.NewReplica(term5),
		[]*pb.RequestVoteRequest{&pb.RequestVoteRequest{Term: 1}},
		[]*pb.RequestVoteResponse{&pb.RequestVoteResponse{Term: 5}},
	},
	{
		"high then low term",
		raft.NewReplica(term5),
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{Term: 7},
			&pb.RequestVoteRequest{Term: 1},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 7, VoteGranted: true},
			&pb.RequestVoteResponse{Term: 7},
		},
	},
	{
		"increasing terms",
		raft.NewReplica(term5),
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{Term: 4},
			&pb.RequestVoteRequest{Term: 5},
			&pb.RequestVoteRequest{Term: 6},
			&pb.RequestVoteRequest{Term: 7},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5},
			&pb.RequestVoteResponse{Term: 5, VoteGranted: true},
			&pb.RequestVoteResponse{Term: 6, VoteGranted: true},
			&pb.RequestVoteResponse{Term: 7, VoteGranted: true},
		},
	},
	{
		"already voted",
		raft.NewReplica(term5),
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{CandidateID: 1, Term: 5},
			&pb.RequestVoteRequest{CandidateID: 2, Term: 5},
			&pb.RequestVoteRequest{CandidateID: 1, Term: 5},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5, VoteGranted: true},
			&pb.RequestVoteResponse{Term: 5, VoteGranted: false},
			&pb.RequestVoteResponse{Term: 5, VoteGranted: true},
		},
	},
	{
		"pre-vote lower term",
		raft.NewReplica(term5),
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{Term: 4, PreVote: true},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5},
		},
	},
	{
		"pre-vote same term",
		raft.NewReplica(term5),
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{Term: 5, PreVote: true},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5},
		},
	},
	{
		"pre-vote higher term",
		raft.NewReplica(term5),
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{CandidateID: 1, Term: 6, PreVote: true},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 6, VoteGranted: true},
		},
	},
	{
		// A pre-election is actually an election for the next term, so
		// a vote granted in an earlier term should not interfere.
		"pre-vote already voted",
		raft.NewReplica(term5),
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{CandidateID: 1, Term: 5},
			&pb.RequestVoteRequest{CandidateID: 2, Term: 6, PreVote: true},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5, VoteGranted: true},
			&pb.RequestVoteResponse{Term: 6, VoteGranted: true},
		},
	},
	{
		"log not up-to-date",
		raft.NewReplica(term5log2),
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{
				Term:         5,
				LastLogIndex: 0,
				LastLogTerm:  0,
			},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5},
		},
	},
	{
		"log not up-to-date shorter log",
		raft.NewReplica(term5log2),
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{
				Term:         5,
				LastLogIndex: 0,
				LastLogTerm:  5,
			},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5},
		},
	},
	{
		"log not up-to-date lower term",
		raft.NewReplica(term5log2),
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{
				Term:         5,
				LastLogIndex: 10,
				LastLogTerm:  4,
			},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5},
		},
	},
	{
		"log up-to-date",
		raft.NewReplica(term5log2),
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{
				Term:         5,
				LastLogIndex: 2,
				LastLogTerm:  5,
			},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5, VoteGranted: true},
		},
	},
	{
		"log up-to-date already voted",
		raft.NewReplica(term5log2),
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{
				CandidateID:  1,
				Term:         5,
				LastLogIndex: 2,
				LastLogTerm:  5,
			},
			&pb.RequestVoteRequest{
				CandidateID:  2,
				Term:         5,
				LastLogIndex: 15,
				LastLogTerm:  5,
			},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5, VoteGranted: true},
			&pb.RequestVoteResponse{Term: 5},
		},
	},
	{
		"log up-to-date already voted higher term",
		raft.NewReplica(term5log2),
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{
				CandidateID:  1,
				Term:         5,
				LastLogIndex: 2,
				LastLogTerm:  5,
			},
			&pb.RequestVoteRequest{
				CandidateID:  2,
				Term:         6,
				LastLogIndex: 2,
				LastLogTerm:  5,
			},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5, VoteGranted: true},
			&pb.RequestVoteResponse{Term: 6, VoteGranted: true},
		},
	},
}

func TestHandleRequestVoteRequest(t *testing.T) {
	for _, test := range handleRequestVoteRequestTests {
		t.Run(test.name, func(t *testing.T) {
			for i := 0; i < len(test.req); i++ {
				res := test.r.HandleRequestVoteRequest(test.req[i])

				if !reflect.DeepEqual(res, test.res[i]) {
					t.Errorf("got %+v, want %+v", res, test.res[i])
				}
			}
		})
	}
}
<commit_msg>msghandlers_test.go: Verify persistent state<commit_after>package raft_test

import (
	"reflect"
	"testing"
	"time"

	"github.com/relab/raft"
	pb "github.com/relab/raft/raftpb"
)

type mockStorage struct {
	term     uint64
	votedFor uint64
	log      []*pb.Entry
}

func (s *mockStorage) SaveState(term uint64, votedFor uint64) error {
	s.term = term
	s.votedFor = votedFor

	return nil
}

func (s *mockStorage) SaveEntries(entries []*pb.Entry) error {
	s.log = entries

	return nil
}

func (s *mockStorage) Load() raft.Persistent {
	commands := make(map[raft.UniqueCommand]*pb.ClientCommandRequest)

	for _, entry := range s.log {
		commands[raft.UniqueCommand{
			ClientID:       entry.Data.ClientID,
			SequenceNumber: entry.Data.SequenceNumber,
		}] = entry.Data
	}

	return raft.Persistent{
		CurrentTerm: s.term,
		VotedFor:    s.votedFor,
		Log:         s.log,
		Commands:    commands,
	}
}

var log2 = []*pb.Entry{
	&pb.Entry{
		Term: 4,
		Data: &pb.ClientCommandRequest{
			ClientID:       123,
			SequenceNumber: 456,
			Command:        "first",
		},
	},
	&pb.Entry{
		Term: 5,
		Data: &pb.ClientCommandRequest{
			ClientID:       123,
			SequenceNumber: 457,
			Command:        "second",
		},
	},
}

func newTerm5() mockStorage {
	return mockStorage{term: 5}
}

func newTerm5log2() mockStorage {
	return mockStorage{term: 5, log: log2}
}

var term5 mockStorage
var term5log2 mockStorage

var handleRequestVoteRequestTests = []struct {
	name   string
	s      raft.Storage
	req    []*pb.RequestVoteRequest
	res    []*pb.RequestVoteResponse
	states []*mockStorage
}{
	{
		"reject lower term",
		&term5,
		[]*pb.RequestVoteRequest{&pb.RequestVoteRequest{CandidateID: 1, Term: 1}},
		[]*pb.RequestVoteResponse{&pb.RequestVoteResponse{Term: 5}},
		[]*mockStorage{&mockStorage{term: 5, votedFor: raft.None, log: nil}},
	},
	{
		"accept same term if not voted",
		&term5,
		[]*pb.RequestVoteRequest{&pb.RequestVoteRequest{CandidateID: 1, Term: 5}},
		[]*pb.RequestVoteResponse{&pb.RequestVoteResponse{Term: 5, VoteGranted: true}},
		[]*mockStorage{&mockStorage{term: 5, votedFor: 1, log: nil}},
	},
	{
		"accept one vote per term",
		&term5,
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{CandidateID: 1, Term: 6},
			&pb.RequestVoteRequest{CandidateID: 2, Term: 6},
			&pb.RequestVoteRequest{CandidateID: 1, Term: 6},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 6, VoteGranted: true},
			&pb.RequestVoteResponse{Term: 6, VoteGranted: false},
			// Multiple requests from the same candidate we voted
			// for (in the same term) must always return true. This
			// gives correct behavior even if the response is lost.
			&pb.RequestVoteResponse{Term: 6, VoteGranted: true},
		},
		[]*mockStorage{
			&mockStorage{term: 6, votedFor: 1, log: nil},
			&mockStorage{term: 6, votedFor: 1, log: nil},
			&mockStorage{term: 6, votedFor: 1, log: nil},
		},
	},
	{
		"accept higher terms",
		&term5,
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{CandidateID: 1, Term: 4},
			&pb.RequestVoteRequest{CandidateID: 2, Term: 5},
			&pb.RequestVoteRequest{CandidateID: 3, Term: 6},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5},
			&pb.RequestVoteResponse{Term: 5, VoteGranted: true},
			&pb.RequestVoteResponse{Term: 6, VoteGranted: true},
		},
		[]*mockStorage{
			&mockStorage{term: 5, votedFor: raft.None, log: nil},
			&mockStorage{term: 5, votedFor: 2, log: nil},
			&mockStorage{term: 6, votedFor: 3, log: nil},
		},
	},
	{
		"reject lower prevote term",
		&term5,
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{CandidateID: 1, Term: 4, PreVote: true},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5},
		},
		[]*mockStorage{&mockStorage{term: 5, votedFor: raft.None, log: nil}},
	},
	{
		"accept prevote in same term if not voted",
		&term5,
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{CandidateID: 1, Term: 5, PreVote: true},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5, VoteGranted: true},
		},
		[]*mockStorage{&mockStorage{term: 5, votedFor: raft.None, log: nil}},
	},
	{
		"reject prevote in same term if voted",
		&term5,
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{CandidateID: 1, Term: 5},
			&pb.RequestVoteRequest{CandidateID: 2, Term: 5, PreVote: true},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5, VoteGranted: true},
			&pb.RequestVoteResponse{Term: 5},
		},
		[]*mockStorage{
			&mockStorage{term: 5, votedFor: 1, log: nil},
			&mockStorage{term: 5, votedFor: 1, log: nil},
		},
	},
	// TODO Don't grant pre-vote if heard from leader.
	{
		"accept prevote in higher term",
		&term5,
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{CandidateID: 1, Term: 6, PreVote: true},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 6, VoteGranted: true},
		},
		[]*mockStorage{&mockStorage{term: 5, votedFor: raft.None, log: nil}},
	},
	{
		// A pre-election is actually an election for the next term, so
		// a vote granted in an earlier term should not interfere.
		"accept prevote in higher term even if voted in current",
		&term5,
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{CandidateID: 1, Term: 5},
			&pb.RequestVoteRequest{CandidateID: 2, Term: 6, PreVote: true},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5, VoteGranted: true},
			&pb.RequestVoteResponse{Term: 6, VoteGranted: true},
		},
		[]*mockStorage{
			&mockStorage{term: 5, votedFor: 1, log: nil},
			&mockStorage{term: 5, votedFor: 1, log: nil},
		},
	},
	{
		"reject log not up-to-date",
		&term5log2,
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{
				CandidateID:  1,
				Term:         5,
				LastLogIndex: 0,
				LastLogTerm:  0,
			},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5},
		},
		[]*mockStorage{&mockStorage{term: 5, votedFor: raft.None, log: log2}},
	},
	{
		"reject log not up-to-date shorter log",
		&term5log2,
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{
				CandidateID:  1,
				Term:         5,
				LastLogIndex: 0,
				LastLogTerm:  5,
			},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5},
		},
		[]*mockStorage{&mockStorage{term: 5, votedFor: raft.None, log: log2}},
	},
	{
		"reject log not up-to-date lower term",
		&term5log2,
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{
				CandidateID:  1,
				Term:         5,
				LastLogIndex: 10,
				LastLogTerm:  4,
			},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5},
		},
		[]*mockStorage{&mockStorage{term: 5, votedFor: raft.None, log: log2}},
	},
	{
		"accpet log up-to-date",
		&term5log2,
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{
				CandidateID:  1,
				Term:         5,
				LastLogIndex: 2,
				LastLogTerm:  5,
			},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5, VoteGranted: true},
		},
		[]*mockStorage{&mockStorage{term: 5, votedFor: 1, log: log2}},
	},
	{
		"reject log up-to-date already voted",
		&term5log2,
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{
				CandidateID:  1,
				Term:         5,
				LastLogIndex: 2,
				LastLogTerm:  5,
			},
			&pb.RequestVoteRequest{
				CandidateID:  2,
				Term:         5,
				LastLogIndex: 15,
				LastLogTerm:  5,
			},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5, VoteGranted: true},
			&pb.RequestVoteResponse{Term: 5},
		},
		[]*mockStorage{
			&mockStorage{term: 5, votedFor: 1, log: log2},
			&mockStorage{term: 5, votedFor: 1, log: log2},
		},
	},
	{
		"accept log up-to-date already voted if higher term",
		&term5log2,
		[]*pb.RequestVoteRequest{
			&pb.RequestVoteRequest{
				CandidateID:  1,
				Term:         5,
				LastLogIndex: 2,
				LastLogTerm:  5,
			},
			&pb.RequestVoteRequest{
				CandidateID:  2,
				Term:         6,
				LastLogIndex: 2,
				LastLogTerm:  5,
			},
		},
		[]*pb.RequestVoteResponse{
			&pb.RequestVoteResponse{Term: 5, VoteGranted: true},
			&pb.RequestVoteResponse{Term: 6, VoteGranted: true},
		},
		[]*mockStorage{
			&mockStorage{term: 5, votedFor: 1, log: log2},
			&mockStorage{term: 6, votedFor: 2, log: log2},
		},
	},
}

func TestHandleRequestVoteRequest(t *testing.T) {
	for _, test := range handleRequestVoteRequestTests {
		t.Run(test.name, func(t *testing.T) {
			term5 = newTerm5()
			term5log2 = newTerm5log2()

			r := raft.NewReplica(&raft.Config{
				ElectionTimeout: time.Second,
				Storage:         test.s,
			})

			for i := 0; i < len(test.req); i++ {
				res := r.HandleRequestVoteRequest(test.req[i])

				if !reflect.DeepEqual(res, test.res[i]) {
					t.Errorf("got %+v, want %+v", res, test.res[i])
				}

				if !reflect.DeepEqual(test.s, test.states[i]) {
					t.Errorf("got %+v, want %+v", test.s, test.states[i])
				}
			}
		})
	}
}
<|endoftext|>


<commit_before>// Streaming relation (overlap, distance, KNN) testing of (any number of) sorted files of intervals.
package interfaces

import "strings"

// RelatableChannel
type RelatableChannel chan Relatable

// IPosition allows accessing positional interface for genomic types.
type IPosition interface {
	Chrom() string
	Start() uint32
	End() uint32
}

type RandomGetter interface {
	Get(query IPosition) []IPosition
}

// A RandomChannel accepts a single IPosition and returns a slice of all overlapping positions.
type RandomChannel interface {
	Relate(chan IPosition) chan []IPosition
}

// Relatable provides all the methods for irelate to function.
// See Interval in interval.go for a class that satisfies this interface.
// Related() likely returns and AddRelated() likely appends to a slice of
// relatables. Note that for performance reasons, Relatable should be implemented
// as a pointer to your data-structure (see Interval).
type Relatable interface {
	IPosition
	Related() []Relatable // A slice of related Relatable's filled by IRelate
	AddRelated(Relatable) // Adds to the slice of relatables
	Source() uint32
	SetSource(source uint32)
}

// Info must implement stuff to get info out of a variant info field.
type Info interface {
	Get(key string) (interface{}, error)
	Set(key string, val interface{}) error
	Delete(key string)
	Keys() []string
	String() string
}

// IVariant must implement IPosition as well as Ref, Alt, and Inof() methods for genetic variants
type IVariant interface {
	IPosition
	Ref() string
	Alt() []string
	Info() Info
	Id() string
	String() string
}

func SameChrom(a, b string) bool {
	if a == b {
		return true
	}
	return StripChr(a) == StripChr(b)
}

func StripChr(c string) string {
	if strings.HasPrefix(c, "chr") {
		return c[3:]
	}
	return c
}

func SamePosition(a, b IPosition) bool {
	return a.Start() == b.Start() && a.End() == b.End() && SameChrom(a.Chrom(), b.Chrom())
}

func OverlapsPosition(a, b IPosition) bool {
	return (b.Start() < a.End() && b.End() >= a.Start()) && SameChrom(a.Chrom(), b.Chrom())
}

func SameVariant(a, b IVariant) bool {
	if !SamePosition(a, b) || a.Ref() != b.Ref() {
		return false
	}
	for _, aalt := range a.Alt() {
		for _, balt := range b.Alt() {
			if aalt == balt {
				return true
			}
		}
	}
	return false
}

func Same(a, b IPosition, strict bool) bool {
	// strict only applies if both are IVariants, otherwise, we just check for overlap.
	if !strict {
		return OverlapsPosition(a, b)
	}
	if av, ok := a.(IVariant); ok {
		if bv, ok := b.(IVariant); ok {
			if strict {
				return SameVariant(av, bv)
			}
		}
		return OverlapsPosition(a, b)
	}
	// at most one of them is a variant, just check overlap.
	return OverlapsPosition(a, b)
}
<commit_msg>fix overlap test<commit_after>// Streaming relation (overlap, distance, KNN) testing of (any number of) sorted files of intervals.
package interfaces

import "strings"

// RelatableChannel
type RelatableChannel chan Relatable

// IPosition allows accessing positional interface for genomic types.
type IPosition interface {
	Chrom() string
	Start() uint32
	End() uint32
}

type RandomGetter interface {
	Get(query IPosition) []IPosition
}

// A RandomChannel accepts a single IPosition and returns a slice of all overlapping positions.
type RandomChannel interface {
	Relate(chan IPosition) chan []IPosition
}

// Relatable provides all the methods for irelate to function.
// See Interval in interval.go for a class that satisfies this interface.
// Related() likely returns and AddRelated() likely appends to a slice of
// relatables. Note that for performance reasons, Relatable should be implemented
// as a pointer to your data-structure (see Interval).
type Relatable interface {
	IPosition
	Related() []Relatable // A slice of related Relatable's filled by IRelate
	AddRelated(Relatable) // Adds to the slice of relatables
	Source() uint32
	SetSource(source uint32)
}

// Info must implement stuff to get info out of a variant info field.
type Info interface {
	Get(key string) (interface{}, error)
	Set(key string, val interface{}) error
	Delete(key string)
	Keys() []string
	String() string
}

// IVariant must implement IPosition as well as Ref, Alt, and Inof() methods for genetic variants
type IVariant interface {
	IPosition
	Ref() string
	Alt() []string
	Info() Info
	Id() string
	String() string
}

func SameChrom(a, b string) bool {
	if a == b {
		return true
	}
	return StripChr(a) == StripChr(b)
}

func StripChr(c string) string {
	if strings.HasPrefix(c, "chr") {
		return c[3:]
	}
	return c
}

func SamePosition(a, b IPosition) bool {
	return a.Start() == b.Start() && a.End() == b.End() && SameChrom(a.Chrom(), b.Chrom())
}

func OverlapsPosition(a, b IPosition) bool {
	return (b.Start() < a.End() && b.End() > a.Start()) && SameChrom(a.Chrom(), b.Chrom())
}

func SameVariant(a, b IVariant) bool {
	if !SamePosition(a, b) || a.Ref() != b.Ref() {
		return false
	}
	for _, aalt := range a.Alt() {
		for _, balt := range b.Alt() {
			if aalt == balt {
				return true
			}
		}
	}
	return false
}

func Same(a, b IPosition, strict bool) bool {
	// strict only applies if both are IVariants, otherwise, we just check for overlap.
	if !strict {
		return OverlapsPosition(a, b)
	}
	if av, ok := a.(IVariant); ok {
		if bv, ok := b.(IVariant); ok {
			if strict {
				return SameVariant(av, bv)
			}
		}
		return OverlapsPosition(a, b)
	}
	// at most one of them is a variant, just check overlap.
	return OverlapsPosition(a, b)
}
<|endoftext|>


<commit_before>package internal

import (
	"reflect"
	"testing"
)

func TestJoinStrings(t *testing.T) {
	type args struct {
		strs []string
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			"case1",
			args{
				[]string{"Hello", " world", "!!!!"},
			},
			"Hello world!!!!",
		},
		{
			"case2",
			args{
				[]string{"Hello", " from", " gol"},
			},
			"Hello from gol",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := JoinStrings(tt.args.strs...); got != tt.want {
				t.Errorf("JoinStrings() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestStr2bytes(t *testing.T) {
	type args struct {
		s string
	}
	tests := []struct {
		name string
		args args
		want []byte
	}{
		{
			"case1",
			args{
				"Hello",
			},
			[]byte("Hello"),
		},
		{
			"case2",
			args{
				"Hello world",
			},
			[]byte("Hello world"),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Str2bytes(tt.args.s); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Str2bytes() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestBytes2str(t *testing.T) {
	type args struct {
		b []byte
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			"case1",
			args{
				[]byte("Hello"),
			},
			"Hello",
		},
		{
			"case2",
			args{
				[]byte("Hello world"),
			},
			"Hello world",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Bytes2str(tt.args.b); got != tt.want {
				t.Errorf("Bytes2str() = %v, want %v", got, tt.want)
			}
		})
	}
}
<commit_msg>add test<commit_after>package internal

import (
	"reflect"
	"testing"
)

func TestJoinStrings(t *testing.T) {
	type args struct {
		strs []string
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			"case1",
			args{
				[]string{"Hello", " world", "!!!!"},
			},
			"Hello world!!!!",
		},
		{
			"case2",
			args{
				[]string{"Hello", " from", " gol"},
			},
			"Hello from gol",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := JoinStrings(tt.args.strs...); got != tt.want {
				t.Errorf("JoinStrings() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestStr2bytes(t *testing.T) {
	type args struct {
		s string
	}
	tests := []struct {
		name string
		args args
		want []byte
	}{
		{
			"case1",
			args{
				"Hello",
			},
			[]byte("Hello"),
		},
		{
			"case2",
			args{
				"Hello world",
			},
			[]byte("Hello world"),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Str2bytes(tt.args.s); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("Str2bytes() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestBytes2str(t *testing.T) {
	type args struct {
		b []byte
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			"case1",
			args{
				[]byte("Hello"),
			},
			"Hello",
		},
		{
			"case2",
			args{
				[]byte("Hello world"),
			},
			"Hello world",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Bytes2str(tt.args.b); got != tt.want {
				t.Errorf("Bytes2str() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestJoinBytes(t *testing.T) {
	type args struct {
		bts [][]byte
	}
	tests := []struct {
		name string
		args args
		want []byte
	}{
		{
			"case1",
			args{
				[][]byte{
					[]byte("Hello"),
					[]byte(" world"),
					[]byte("!!!"),
				},
			},
			[]byte("Hello world!!!"),
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := JoinBytes(tt.args.bts...); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("JoinBytes() = %v, want %v", got, tt.want)
			}
		})
	}
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT

package token

import (
	"io"
	"unicode"

	"golang.org/x/text/message"

	"github.com/caixw/apidoc/v6/core"
	"github.com/caixw/apidoc/v6/internal/lexer"
	"github.com/caixw/apidoc/v6/internal/locale"
)

const cdataEscape = "]]]]><![CDATA[>"

// Parser 代码块的解析器
type Parser struct {
	*lexer.Lexer
}

// NewParser 声明新的 Parser 实例
func NewParser(b core.Block) (*Parser, error) {
	l, err := lexer.New(b)
	if err != nil {
		return nil, err
	}

	return &Parser{
		Lexer: l,
	}, nil
}

// Token 返回下一个 token 对象
//
// token 可能的类型为 *StartElement、*EndElement、*Instruction、*Attribute、*CData、*Comment 和 *String。
// 其中 *String 用于表示 XML 元素的内容。
//
// 当返回 nil,nil 时，表示已经结束
func (p *Parser) Token() (interface{}, error) {
	for {
		if p.AtEOF() {
			return nil, io.EOF
		}

		pos := p.Position() // 记录元素的开始位置

		bs := p.Next(1)
		if len(bs) == 0 {
			return nil, io.EOF
		}
		if len(bs) > 1 || bs[0] != '<' {
			p.Rollback() // 当前字符是内容的一部分，返回给 parseContent 解析
			return p.parseContent()
		}

		switch {
		case p.Match("?"):
			return p.parseInstruction(pos)
		case p.Match("![CDATA["):
			return p.parseCData(pos)
		case p.Match("/"):
			return p.parseEndElement(pos)
		case p.Match("!--"):
			return p.parseComment(pos)
		default:
			return p.parseStartElement(pos)
		}
	}
}

func (p *Parser) parseContent() (*String, error) {
	start := p.Position()

	data, found := p.Delim('<', false)
	if !found {
		data = p.All()
		if len(data) == 0 {
			return nil, io.EOF
		}
	}

	return &String{
		Value: string(data),
		Range: core.Range{Start: start.Position, End: p.Position().Position},
	}, nil
}

func (p *Parser) parseComment(pos lexer.Position) (*Comment, error) {
	start := p.Position()

	data, found := p.DelimString("-->", false)
	if !found {
		return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
	}
	end := p.Position()
	p.Next(3) // 跳过 --> 三个字符

	return &Comment{
		Range: core.Range{Start: pos.Position, End: p.Position().Position},
		Value: String{
			Range: core.Range{Start: start.Position, End: end.Position},
			Value: string(data),
		},
	}, nil
}

func (p *Parser) parseStartElement(pos lexer.Position) (*StartElement, error) {
	p.Spaces(0) // 跳过空格

	start := p.Position()
	name, found := p.DelimFunc(func(r rune) bool { return unicode.IsSpace(r) || r == '/' || r == '>' }, false)
	if !found || len(name) == 0 {
		return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
	}

	elem := &StartElement{
		Name: String{
			Range: core.Range{Start: start.Position, End: p.Position().Position},
			Value: string(name),
		},
	}

	attrs, err := p.parseAttributes()
	if err != nil {
		return nil, err
	}
	elem.Attributes = attrs

	if p.Match("/>") {
		elem.Range = core.Range{Start: pos.Position, End: p.Position().Position}
		elem.Close = true
		return elem, nil
	}
	if p.Match(">") {
		elem.Range = core.Range{Start: pos.Position, End: p.Position().Position}
		return elem, nil
	}

	return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
}

// pos 表示当前元素的起始位置，包含了 < 元素
func (p *Parser) parseEndElement(pos lexer.Position) (*EndElement, error) {
	// 名称开始的定位，传递过来的 pos 表示的 < 起始位置
	start := p.Position()

	name, found := p.Delim('>', false)
	if !found || len(name) == 0 {
		return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
	}
	end := p.Position()
	p.Next(1) // 去掉 > 符号

	return &EndElement{
		Range: core.Range{Start: pos.Position, End: p.Position().Position},
		Name: String{
			Range: core.Range{Start: start.Position, End: end.Position},
			Value: string(name),
		},
	}, nil
}

// pos 表示当前元素的起始位置，包含了 < 元素
func (p *Parser) parseCData(pos lexer.Position) (*CData, error) {
	start := p.Position()
	var value []byte

	for {
		v, found := p.DelimString("]]>", false)
		if !found {
			return nil, p.NewError(pos.Position, p.Position().Position, locale.ErrInvalidXML)
		}
		value = append(value, v...)

		curr := p.Position()
		p.Move(curr.SubRune(']').SubRune(']')) // 回滚两个字符，用于匹配转义内容
		if p.Match(cdataEscape) {
			value = append(value, cdataEscape[2:]...)
			continue
		}

		p.Move(curr)
		break
	}

	end := p.Position()
	p.Next(3) // 去掉 ]]>

	return &CData{
		Range: core.Range{Start: pos.Position, End: p.Position().Position},
		Value: String{
			Range: core.Range{Start: start.Position, End: end.Position},
			Value: string(value),
		},
	}, nil
}

// pos 表示当前元素的起始位置，包含了 < 元素
func (p *Parser) parseInstruction(pos lexer.Position) (*Instruction, error) {
	name, nameRange := p.getName()
	if len(name) == 0 {
		return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
	}
	elem := &Instruction{
		Name: String{
			Range: nameRange,
			Value: string(name),
		},
	}

	attrs, err := p.parseAttributes()
	if err != nil {
		return nil, err
	}
	elem.Attributes = attrs

	if p.Match("?>") {
		elem.Range = core.Range{Start: pos.Position, End: p.Position().Position}
		return elem, nil
	}

	return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
}

func (p *Parser) parseAttributes() (attrs []*Attribute, err error) {
	for {
		attr, err := p.parseAttribute()
		if err != nil {
			return nil, err
		}
		if attr == nil {
			break
		}

		attrs = append(attrs, attr)
	}
	p.Spaces(0)

	return attrs, nil
}

func (p *Parser) parseAttribute() (*Attribute, error) {
	p.Spaces(0) // 忽略空格
	pos, start := p.Position(), p.Position()

	name, nameRange := p.getName()
	if len(name) == 0 {
		return nil, nil
	}
	attr := &Attribute{
		Name: String{
			Range: nameRange,
			Value: string(name),
		},
	}

	p.Spaces(0)
	if !p.Match("=") {
		return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
	}

	p.Spaces(0)
	if !p.Match("\"") {
		return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
	}

	pos = p.Position()
	value, found := p.Delim('"', true)
	if !found || len(value) == 0 {
		return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
	}
	end := p.Position().SubRune('"') // 不包含 " 符号
	attr.Value = String{
		Range: core.Range{Start: pos.Position, End: end.Position},
		Value: string(p.Bytes(pos.Offset, end.Offset)),
	}

	attr.Range = core.Range{Start: start.Position, End: p.Position().Position}

	return attr, nil
}

func (p *Parser) getName() ([]byte, core.Range) {
	start := p.Position()

	for {
		if p.AtEOF() {
			break
		}

		rs := p.Next(1)
		if len(rs) != 1 {
			continue
		}

		b := rs[0]
		if b == '"' || b == '=' || b == '<' || b == '>' || b == '?' || b == '/' || unicode.IsSpace(rune(b)) {
			p.Rollback()
			break
		}
	}

	end := p.Position()

	return p.Bytes(start.Offset, end.Offset), core.Range{Start: start.Position, End: end.Position}
}

// NewError 生成本地化的错误信息
//
// 其中的 URI 采用 p.l.Location.URI
func (p *Parser) NewError(start, end core.Position, key message.Reference, v ...interface{}) error {
	return core.NewLocaleError(core.Location{
		URI:   p.Location.URI,
		Range: core.Range{Start: start, End: end},
	}, "", key, v...)
}

// WithError 重新包含 err
func (p *Parser) WithError(start, end core.Position, err error) error {
	return core.WithError(core.Location{
		URI:   p.Location.URI,
		Range: core.Range{Start: start, End: end},
	}, "", err)
}
<commit_msg>docs(internal/token): 修正文档错误<commit_after>// SPDX-License-Identifier: MIT

package token

import (
	"io"
	"unicode"

	"golang.org/x/text/message"

	"github.com/caixw/apidoc/v6/core"
	"github.com/caixw/apidoc/v6/internal/lexer"
	"github.com/caixw/apidoc/v6/internal/locale"
)

const cdataEscape = "]]]]><![CDATA[>"

// Parser 代码块的解析器
type Parser struct {
	*lexer.Lexer
}

// NewParser 声明新的 Parser 实例
func NewParser(b core.Block) (*Parser, error) {
	l, err := lexer.New(b)
	if err != nil {
		return nil, err
	}

	return &Parser{
		Lexer: l,
	}, nil
}

// Token 返回下一个 token 对象
//
// token 可能的类型为 *StartElement、*EndElement、*Instruction、*Attribute、*CData、*Comment 和 *String。
// 其中 *String 用于表示 XML 元素的内容。
//
// 当返回 nil, io.EOF 时，表示已经结束
func (p *Parser) Token() (interface{}, error) {
	for {
		if p.AtEOF() {
			return nil, io.EOF
		}

		pos := p.Position() // 记录元素的开始位置

		bs := p.Next(1)
		if len(bs) == 0 {
			return nil, io.EOF
		}
		if len(bs) > 1 || bs[0] != '<' {
			p.Rollback() // 当前字符是内容的一部分，返回给 parseContent 解析
			return p.parseContent()
		}

		switch {
		case p.Match("?"):
			return p.parseInstruction(pos)
		case p.Match("![CDATA["):
			return p.parseCData(pos)
		case p.Match("/"):
			return p.parseEndElement(pos)
		case p.Match("!--"):
			return p.parseComment(pos)
		default:
			return p.parseStartElement(pos)
		}
	}
}

func (p *Parser) parseContent() (*String, error) {
	start := p.Position()

	data, found := p.Delim('<', false)
	if !found {
		data = p.All()
		if len(data) == 0 {
			return nil, io.EOF
		}
	}

	return &String{
		Value: string(data),
		Range: core.Range{Start: start.Position, End: p.Position().Position},
	}, nil
}

func (p *Parser) parseComment(pos lexer.Position) (*Comment, error) {
	start := p.Position()

	data, found := p.DelimString("-->", false)
	if !found {
		return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
	}
	end := p.Position()
	p.Next(3) // 跳过 --> 三个字符

	return &Comment{
		Range: core.Range{Start: pos.Position, End: p.Position().Position},
		Value: String{
			Range: core.Range{Start: start.Position, End: end.Position},
			Value: string(data),
		},
	}, nil
}

func (p *Parser) parseStartElement(pos lexer.Position) (*StartElement, error) {
	p.Spaces(0) // 跳过空格

	start := p.Position()
	name, found := p.DelimFunc(func(r rune) bool { return unicode.IsSpace(r) || r == '/' || r == '>' }, false)
	if !found || len(name) == 0 {
		return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
	}

	elem := &StartElement{
		Name: String{
			Range: core.Range{Start: start.Position, End: p.Position().Position},
			Value: string(name),
		},
	}

	attrs, err := p.parseAttributes()
	if err != nil {
		return nil, err
	}
	elem.Attributes = attrs

	if p.Match("/>") {
		elem.Range = core.Range{Start: pos.Position, End: p.Position().Position}
		elem.Close = true
		return elem, nil
	}
	if p.Match(">") {
		elem.Range = core.Range{Start: pos.Position, End: p.Position().Position}
		return elem, nil
	}

	return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
}

// pos 表示当前元素的起始位置，包含了 < 元素
func (p *Parser) parseEndElement(pos lexer.Position) (*EndElement, error) {
	// 名称开始的定位，传递过来的 pos 表示的 < 起始位置
	start := p.Position()

	name, found := p.Delim('>', false)
	if !found || len(name) == 0 {
		return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
	}
	end := p.Position()
	p.Next(1) // 去掉 > 符号

	return &EndElement{
		Range: core.Range{Start: pos.Position, End: p.Position().Position},
		Name: String{
			Range: core.Range{Start: start.Position, End: end.Position},
			Value: string(name),
		},
	}, nil
}

// pos 表示当前元素的起始位置，包含了 < 元素
func (p *Parser) parseCData(pos lexer.Position) (*CData, error) {
	start := p.Position()
	var value []byte

	for {
		v, found := p.DelimString("]]>", false)
		if !found {
			return nil, p.NewError(pos.Position, p.Position().Position, locale.ErrInvalidXML)
		}
		value = append(value, v...)

		curr := p.Position()
		p.Move(curr.SubRune(']').SubRune(']')) // 回滚两个字符，用于匹配转义内容
		if p.Match(cdataEscape) {
			value = append(value, cdataEscape[2:]...)
			continue
		}

		p.Move(curr)
		break
	}

	end := p.Position()
	p.Next(3) // 去掉 ]]>

	return &CData{
		Range: core.Range{Start: pos.Position, End: p.Position().Position},
		Value: String{
			Range: core.Range{Start: start.Position, End: end.Position},
			Value: string(value),
		},
	}, nil
}

// pos 表示当前元素的起始位置，包含了 < 元素
func (p *Parser) parseInstruction(pos lexer.Position) (*Instruction, error) {
	name, nameRange := p.getName()
	if len(name) == 0 {
		return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
	}
	elem := &Instruction{
		Name: String{
			Range: nameRange,
			Value: string(name),
		},
	}

	attrs, err := p.parseAttributes()
	if err != nil {
		return nil, err
	}
	elem.Attributes = attrs

	if p.Match("?>") {
		elem.Range = core.Range{Start: pos.Position, End: p.Position().Position}
		return elem, nil
	}

	return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
}

func (p *Parser) parseAttributes() (attrs []*Attribute, err error) {
	for {
		attr, err := p.parseAttribute()
		if err != nil {
			return nil, err
		}
		if attr == nil {
			break
		}

		attrs = append(attrs, attr)
	}
	p.Spaces(0)

	return attrs, nil
}

func (p *Parser) parseAttribute() (*Attribute, error) {
	p.Spaces(0) // 忽略空格
	pos, start := p.Position(), p.Position()

	name, nameRange := p.getName()
	if len(name) == 0 {
		return nil, nil
	}
	attr := &Attribute{
		Name: String{
			Range: nameRange,
			Value: string(name),
		},
	}

	p.Spaces(0)
	if !p.Match("=") {
		return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
	}

	p.Spaces(0)
	if !p.Match("\"") {
		return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
	}

	pos = p.Position()
	value, found := p.Delim('"', true)
	if !found || len(value) == 0 {
		return nil, p.NewError(p.Position().Position, p.Position().Position, locale.ErrInvalidXML)
	}
	end := p.Position().SubRune('"') // 不包含 " 符号
	attr.Value = String{
		Range: core.Range{Start: pos.Position, End: end.Position},
		Value: string(p.Bytes(pos.Offset, end.Offset)),
	}

	attr.Range = core.Range{Start: start.Position, End: p.Position().Position}

	return attr, nil
}

func (p *Parser) getName() ([]byte, core.Range) {
	start := p.Position()

	for {
		if p.AtEOF() {
			break
		}

		rs := p.Next(1)
		if len(rs) != 1 {
			continue
		}

		b := rs[0]
		if b == '"' || b == '=' || b == '<' || b == '>' || b == '?' || b == '/' || unicode.IsSpace(rune(b)) {
			p.Rollback()
			break
		}
	}

	end := p.Position()

	return p.Bytes(start.Offset, end.Offset), core.Range{Start: start.Position, End: end.Position}
}

// NewError 生成本地化的错误信息
//
// 其中的 URI 采用 p.l.Location.URI
func (p *Parser) NewError(start, end core.Position, key message.Reference, v ...interface{}) error {
	return core.NewLocaleError(core.Location{
		URI:   p.Location.URI,
		Range: core.Range{Start: start, End: end},
	}, "", key, v...)
}

// WithError 重新包含 err
func (p *Parser) WithError(start, end core.Position, err error) error {
	return core.WithError(core.Location{
		URI:   p.Location.URI,
		Range: core.Range{Start: start, End: end},
	}, "", err)
}
<|endoftext|>


<commit_before>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tool

import (
	"io"
	"os"

	"v.io/x/devtools/internal/gerrit"
	"v.io/x/devtools/internal/gitutil"
	"v.io/x/devtools/internal/hgutil"
	"v.io/x/devtools/internal/jenkins"
	"v.io/x/devtools/internal/runutil"
	"v.io/x/lib/cmdline"
)

// Context represents an execution context of a tool command
// invocation. Its purpose is to enable sharing of state throughout
// the lifetime of a command invocation.
type Context struct {
	opts ContextOpts
	run  *runutil.Run
}

// ContextOpts records the context options.
type ContextOpts struct {
	Color    *bool
	DryRun   *bool
	Env      map[string]string
	Manifest *string
	Stdin    io.Reader
	Stdout   io.Writer
	Stderr   io.Writer
	Verbose  *bool
}

var (
	e = ""
	f = false
	t = true
)

var defaultOpts = ContextOpts{
	Color:    &f,
	DryRun:   &f,
	Env:      map[string]string{},
	Manifest: &e,
	Stdin:    os.Stdin,
	Stdout:   os.Stdout,
	Stderr:   os.Stdout,
	Verbose:  &t,
}

func processOpts(defaultOpts, opts *ContextOpts) {
	if opts.Color == nil {
		opts.Color = defaultOpts.Color
	}
	if opts.DryRun == nil {
		opts.DryRun = defaultOpts.DryRun
	}
	if opts.Env == nil {
		opts.Env = defaultOpts.Env
	}
	if opts.Manifest == nil {
		opts.Manifest = defaultOpts.Manifest
	}
	if opts.Stdin == nil {
		opts.Stdin = defaultOpts.Stdin
	}
	if opts.Stdout == nil {
		opts.Stdout = defaultOpts.Stdout
	}
	if opts.Stderr == nil {
		opts.Stderr = defaultOpts.Stderr
	}
	if opts.Verbose == nil {
		opts.Verbose = defaultOpts.Verbose
	}
}

// NewContext is the Context factory.
func NewContext(opts ContextOpts) *Context {
	processOpts(&defaultOpts, &opts)
	run := runutil.New(opts.Env, opts.Stdin, opts.Stdout, opts.Stderr, *opts.Color, *opts.DryRun, *opts.Verbose)
	return &Context{
		opts: opts,
		run:  run,
	}
}

// NewContextFromCommand returns a new context instance based on the
// given command.
func NewContextFromCommand(command *cmdline.Command, opts ContextOpts) *Context {
	processOpts(&defaultOpts, &opts)
	opts.Stdout = command.Stdout()
	opts.Stderr = command.Stderr()
	return NewContext(opts)
}

// NewDefaultContext returns a new default context.
func NewDefaultContext() *Context {
	return NewContext(defaultOpts)
}

// Clone creates a clone of the given context, overriding select
// settings using the given options.
func (ctx Context) Clone(opts ContextOpts) *Context {
	processOpts(&ctx.opts, &opts)
	return NewContext(opts)
}

// Color returns the color setting of the context.
func (ctx Context) Color() bool {
	return ctx.run.Opts().Color
}

// DryRun returns the dry run setting of the context.
func (ctx Context) DryRun() bool {
	return ctx.run.Opts().DryRun
}

// Env returns the environment of the context.
func (ctx Context) Env() map[string]string {
	return ctx.run.Opts().Env
}

// Gerrit returns the Gerrit instance of the context.
func (ctx Context) Gerrit(host, username, password string) *gerrit.Gerrit {
	return gerrit.New(host, username, password)
}

type gitOpt interface {
	gitOpt()
}
type hgOpt interface {
	hgOpt()
}
type RootDirOpt string

func (RootDirOpt) gitOpt() {}
func (RootDirOpt) hgOpt()  {}

// Git returns a new git instance.
//
// This method accepts one optional argument: the repository root to
// use for commands issued by the returned instance. If not specified,
// commands will use the current directory as the repository root.
func (ctx Context) Git(opts ...gitOpt) *gitutil.Git {
	rootDir := ""
	for _, opt := range opts {
		switch typedOpt := opt.(type) {
		case RootDirOpt:
			rootDir = string(typedOpt)
		}
	}
	return gitutil.New(ctx.run, rootDir)
}

type HgOpts struct {
	Root string
}

// Hg returns a new hg instance.
//
// This method accepts one optional argument: the repository root to
// use for commands issued by the returned instance. If not specified,
// commands will use the current directory as the repository root.
func (ctx Context) Hg(opts ...hgOpt) *hgutil.Hg {
	rootDir := ""
	for _, opt := range opts {
		switch typedOpt := opt.(type) {
		case RootDirOpt:
			rootDir = string(typedOpt)
		}
	}
	return hgutil.New(ctx.run, rootDir)
}

// Jenkins returns a new Jenkins instance that can be used to
// communicate with a Jenkins server running at the given host.
func (ctx Context) Jenkins(host string) (*jenkins.Jenkins, error) {
	return jenkins.New(host)
}

// Manifest returns the manifest of the context.
func (ctx Context) Manifest() string {
	return *ctx.opts.Manifest
}

// Run returns the run instance of the context.
func (ctx Context) Run() *runutil.Run {
	return ctx.run
}

// Stdin returns the standard input of the context.
func (ctx Context) Stdin() io.Reader {
	return ctx.run.Opts().Stdin
}

// Stdout returns the standard output of the context.
func (ctx Context) Stdout() io.Writer {
	return ctx.run.Opts().Stdout
}

// Stderr returns the standard error output of the context.
func (ctx Context) Stderr() io.Writer {
	return ctx.run.Opts().Stderr
}

// Verbose returns the verbosity setting of the context.
func (ctx Context) Verbose() bool {
	return ctx.run.Opts().Verbose
}
<commit_msg>devtools: fixing accidental sharing of context options across multiple contexts<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tool

import (
	"io"
	"os"

	"v.io/x/devtools/internal/gerrit"
	"v.io/x/devtools/internal/gitutil"
	"v.io/x/devtools/internal/hgutil"
	"v.io/x/devtools/internal/jenkins"
	"v.io/x/devtools/internal/runutil"
	"v.io/x/lib/cmdline"
)

// Context represents an execution context of a tool command
// invocation. Its purpose is to enable sharing of state throughout
// the lifetime of a command invocation.
type Context struct {
	opts ContextOpts
	run  *runutil.Run
}

// ContextOpts records the context options.
type ContextOpts struct {
	Color    *bool
	DryRun   *bool
	Env      map[string]string
	Manifest *string
	Stdin    io.Reader
	Stdout   io.Writer
	Stderr   io.Writer
	Verbose  *bool
}

var (
	e = ""
	f = false
	t = true
)

// newContextOpts is the ContextOpts factory.
func newContextOpts() *ContextOpts {
	return &ContextOpts{
		Color:    &f,
		DryRun:   &f,
		Env:      map[string]string{},
		Manifest: &e,
		Stdin:    os.Stdin,
		Stdout:   os.Stdout,
		Stderr:   os.Stderr,
		Verbose:  &t,
	}
}

// initOpts initializes all unset options to the given defaults.
func initOpts(defaultOpts, opts *ContextOpts) {
	if opts.Color == nil {
		opts.Color = defaultOpts.Color
	}
	if opts.DryRun == nil {
		opts.DryRun = defaultOpts.DryRun
	}
	if opts.Env == nil {
		opts.Env = defaultOpts.Env
	}
	if opts.Manifest == nil {
		opts.Manifest = defaultOpts.Manifest
	}
	if opts.Stdin == nil {
		opts.Stdin = defaultOpts.Stdin
	}
	if opts.Stdout == nil {
		opts.Stdout = defaultOpts.Stdout
	}
	if opts.Stderr == nil {
		opts.Stderr = defaultOpts.Stderr
	}
	if opts.Verbose == nil {
		opts.Verbose = defaultOpts.Verbose
	}
}

// NewContext is the Context factory.
func NewContext(opts ContextOpts) *Context {
	initOpts(newContextOpts(), &opts)
	run := runutil.New(opts.Env, opts.Stdin, opts.Stdout, opts.Stderr, *opts.Color, *opts.DryRun, *opts.Verbose)
	return &Context{
		opts: opts,
		run:  run,
	}
}

// NewContextFromCommand returns a new context instance based on the
// given command.
func NewContextFromCommand(command *cmdline.Command, opts ContextOpts) *Context {
	initOpts(newContextOpts(), &opts)
	opts.Stdout = command.Stdout()
	opts.Stderr = command.Stderr()
	return NewContext(opts)
}

// NewDefaultContext returns a new default context.
func NewDefaultContext() *Context {
	return NewContext(ContextOpts{})
}

// Clone creates a clone of the given context, overriding select
// settings using the given options.
func (ctx Context) Clone(opts ContextOpts) *Context {
	initOpts(&ctx.opts, &opts)
	return NewContext(opts)
}

// Color returns the color setting of the context.
func (ctx Context) Color() bool {
	return ctx.run.Opts().Color
}

// DryRun returns the dry run setting of the context.
func (ctx Context) DryRun() bool {
	return ctx.run.Opts().DryRun
}

// Env returns the environment of the context.
func (ctx Context) Env() map[string]string {
	return ctx.run.Opts().Env
}

// Gerrit returns the Gerrit instance of the context.
func (ctx Context) Gerrit(host, username, password string) *gerrit.Gerrit {
	return gerrit.New(host, username, password)
}

type gitOpt interface {
	gitOpt()
}
type hgOpt interface {
	hgOpt()
}
type RootDirOpt string

func (RootDirOpt) gitOpt() {}
func (RootDirOpt) hgOpt()  {}

// Git returns a new git instance.
//
// This method accepts one optional argument: the repository root to
// use for commands issued by the returned instance. If not specified,
// commands will use the current directory as the repository root.
func (ctx Context) Git(opts ...gitOpt) *gitutil.Git {
	rootDir := ""
	for _, opt := range opts {
		switch typedOpt := opt.(type) {
		case RootDirOpt:
			rootDir = string(typedOpt)
		}
	}
	return gitutil.New(ctx.run, rootDir)
}

type HgOpts struct {
	Root string
}

// Hg returns a new hg instance.
//
// This method accepts one optional argument: the repository root to
// use for commands issued by the returned instance. If not specified,
// commands will use the current directory as the repository root.
func (ctx Context) Hg(opts ...hgOpt) *hgutil.Hg {
	rootDir := ""
	for _, opt := range opts {
		switch typedOpt := opt.(type) {
		case RootDirOpt:
			rootDir = string(typedOpt)
		}
	}
	return hgutil.New(ctx.run, rootDir)
}

// Jenkins returns a new Jenkins instance that can be used to
// communicate with a Jenkins server running at the given host.
func (ctx Context) Jenkins(host string) (*jenkins.Jenkins, error) {
	return jenkins.New(host)
}

// Manifest returns the manifest of the context.
func (ctx Context) Manifest() string {
	return *ctx.opts.Manifest
}

// Run returns the run instance of the context.
func (ctx Context) Run() *runutil.Run {
	return ctx.run
}

// Stdin returns the standard input of the context.
func (ctx Context) Stdin() io.Reader {
	return ctx.run.Opts().Stdin
}

// Stdout returns the standard output of the context.
func (ctx Context) Stdout() io.Writer {
	return ctx.run.Opts().Stdout
}

// Stderr returns the standard error output of the context.
func (ctx Context) Stderr() io.Writer {
	return ctx.run.Opts().Stderr
}

// Verbose returns the verbosity setting of the context.
func (ctx Context) Verbose() bool {
	return ctx.run.Opts().Verbose
}
<|endoftext|>


<commit_before>// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.

package v4

import (
	"net/url"

	"github.com/juju/errgo"
	"gopkg.in/juju/charm.v3"
	"gopkg.in/mgo.v2/bson"

	"github.com/juju/charmstore/internal/mongodoc"
	"github.com/juju/charmstore/params"
)

// GET id/meta/charm-related[?include=meta[&include=meta…]]
// http://tinyurl.com/q7vdmzl
func (h *handler) metaCharmRelated(entity *mongodoc.Entity, id *charm.Reference, path string, flags url.Values) (interface{}, error) {
	if id.Series == "bundle" {
		return nil, nil
	}

	// If the charm does not define any relation we can just return without
	// hitting the db.
	if len(entity.CharmProvidedInterfaces)+len(entity.CharmRequiredInterfaces) == 0 {
		return &params.RelatedResponse{}, nil
	}

	// Build the query to retrieve the related entities.
	query := bson.M{
		"$or": []bson.M{
			bson.M{"charmrequiredinterfaces": bson.M{
				"$elemMatch": bson.M{"$in": entity.CharmProvidedInterfaces},
			}},
			bson.M{"charmprovidedinterfaces": bson.M{
				"$elemMatch": bson.M{"$in": entity.CharmRequiredInterfaces},
			}},
		},
	}
	fields := bson.D{
		{"_id", 1},
		{"charmrequiredinterfaces", 1},
		{"charmprovidedinterfaces", 1},
	}

	// Retrieve the entities from the database.
	var entities []mongodoc.Entity
	if err := h.store.DB.Entities().Find(query).Select(fields).Sort("_id").All(&entities); err != nil {
		return nil, errgo.Notef(err, "cannot retrieve the related charms")
	}

	// If no entities are found there is no need for further processing the
	// results.
	if len(entities) == 0 {
		return &params.RelatedResponse{}, nil
	}

	// Build the results, by grouping entities based on their relations' roles
	// and interfaces.
	includes := flags["include"]
	requires, err := h.getRelatedCharmsResponse(entity.CharmProvidedInterfaces, entities, func(e mongodoc.Entity) []string {
		return e.CharmRequiredInterfaces
	}, includes)
	if err != nil {
		return nil, errgo.Notef(err, "cannot retrieve the charm requires")
	}
	provides, err := h.getRelatedCharmsResponse(entity.CharmRequiredInterfaces, entities, func(e mongodoc.Entity) []string {
		return e.CharmProvidedInterfaces
	}, includes)
	if err != nil {
		return nil, errgo.Notef(err, "cannot retrieve the charm provides")
	}

	// Return the response.
	return &params.RelatedResponse{
		Requires: requires,
		Provides: provides,
	}, nil
}

type entityRelatedInterfacesGetter func(mongodoc.Entity) []string

// getRelatedCharmsResponse returns a response mapping interfaces to related
// charms. For instance:
//   map[string][]params.MetaAnyResponse{
//       "http": []params.MetaAnyResponse{
//           {Id: "cs:utopic/django-42", Meta: ...},
//           {Id: "cs:trusty/wordpress-47", Meta: ...},
//       },
//       "memcache": []params.MetaAnyResponse{
//           {Id: "cs:utopic/memcached-0", Meta: ...},
//       },
//   }
func (h *handler) getRelatedCharmsResponse(
	ifaces []string,
	entities []mongodoc.Entity,
	getInterfaces entityRelatedInterfacesGetter,
	includes []string) (map[string][]params.MetaAnyResponse, error) {
	results := make(map[string][]params.MetaAnyResponse, len(ifaces))
	for _, iface := range ifaces {
		responses, err := h.getRelatedIfaceResponses(iface, entities, getInterfaces, includes)
		if err != nil {
			return nil, err
		}
		if len(responses) > 0 {
			results[iface] = responses
		}
	}
	return results, nil
}

func (h *handler) getRelatedIfaceResponses(
	iface string,
	entities []mongodoc.Entity,
	getInterfaces entityRelatedInterfacesGetter,
	includes []string) ([]params.MetaAnyResponse, error) {
	// Build a list of responses including entities which are related
	// to the given interface.
	responses := make([]params.MetaAnyResponse, 0, len(entities))
	for _, entity := range entities {
		for _, entityIface := range getInterfaces(entity) {
			if entityIface == iface {
				// Retrieve the requested metadata for the entity.
				meta, err := h.GetMetadata(entity.URL, includes)
				if err != nil {
					return nil, err
				}
				// Build the response.
				responses = append(responses, params.MetaAnyResponse{
					Id:   entity.URL,
					Meta: meta,
				})
			}
		}
	}
	return responses, nil
}

// GET id/meta/bundles-containing[?include=meta[&include=meta…]][&any-series=1][&any-revision=1]
// http://tinyurl.com/oqc386r
func (h *handler) metaBundlesContaining(entity *mongodoc.Entity, id *charm.Reference, path string, flags url.Values) (interface{}, error) {
	if id.Series == "bundle" {
		return nil, nil
	}

	// Validate the URL query values.
	anySeries, err := parseBool(flags.Get("any-series"))
	if err != nil {
		return nil, badRequestf(err, "invalid value for any-series")
	}
	anyRevision, err := parseBool(flags.Get("any-revision"))
	if err != nil {
		return nil, badRequestf(err, "invalid value for any-revision")
	}

	// Mutate the reference so that it represents a base URL if required.
	searchId := *id
	if anySeries || anyRevision {
		searchId.Revision = -1
		searchId.Series = ""
	}

	// Retrieve the bundles containing the resulting charm id.
	var entities []mongodoc.Entity
	if err := h.store.DB.Entities().
		Find(bson.D{{"bundlecharms", &searchId}}).
		Select(bson.D{{"_id", 1}, {"bundlecharms", 1}}).
		Sort("_id").
		All(&entities); err != nil {
		return nil, errgo.Notef(err, "cannot retrieve the related bundles")
	}

	// Further filter the entities if required.
	if anySeries != anyRevision {
		predicate := func(e *mongodoc.Entity) bool {
			for _, charmId := range e.BundleCharms {
				if charmId.Name == id.Name &&
					charmId.User == id.User &&
					(anySeries || charmId.Series == id.Series) &&
					(anyRevision || charmId.Revision == id.Revision) {
					return true
				}
			}
			return false
		}
		entities = filterEntities(entities, predicate)
	}

	// Prepare and return the response.
	response := make([]params.MetaAnyResponse, 0, len(entities))
	includes := flags["include"]
	for _, e := range entities {
		meta, err := h.GetMetadata(e.URL, includes)
		if err != nil {
			return nil, errgo.Notef(err, "cannot retrieve bundle metadata")
		}
		response = append(response, params.MetaAnyResponse{
			Id:   e.URL,
			Meta: meta,
		})
	}
	return response, nil
}

// filterEntities returns a slice containing all the entities for which the
// given predicate returns true.
func filterEntities(entities []mongodoc.Entity, predicate func(*mongodoc.Entity) bool) []mongodoc.Entity {
	results := make([]mongodoc.Entity, 0, len(entities))
	for _, entity := range entities {
		if predicate(&entity) {
			results = append(results, entity)
		}
	}
	return results
}
<commit_msg>Restore pointer in bundles-containing responses.<commit_after>// Copyright 2014 Canonical Ltd.
// Licensed under the LGPLv3, see LICENCE file for details.

package v4

import (
	"net/url"

	"github.com/juju/errgo"
	"gopkg.in/juju/charm.v3"
	"gopkg.in/mgo.v2/bson"

	"github.com/juju/charmstore/internal/mongodoc"
	"github.com/juju/charmstore/params"
)

// GET id/meta/charm-related[?include=meta[&include=meta…]]
// http://tinyurl.com/q7vdmzl
func (h *handler) metaCharmRelated(entity *mongodoc.Entity, id *charm.Reference, path string, flags url.Values) (interface{}, error) {
	if id.Series == "bundle" {
		return nil, nil
	}

	// If the charm does not define any relation we can just return without
	// hitting the db.
	if len(entity.CharmProvidedInterfaces)+len(entity.CharmRequiredInterfaces) == 0 {
		return &params.RelatedResponse{}, nil
	}

	// Build the query to retrieve the related entities.
	query := bson.M{
		"$or": []bson.M{
			bson.M{"charmrequiredinterfaces": bson.M{
				"$elemMatch": bson.M{"$in": entity.CharmProvidedInterfaces},
			}},
			bson.M{"charmprovidedinterfaces": bson.M{
				"$elemMatch": bson.M{"$in": entity.CharmRequiredInterfaces},
			}},
		},
	}
	fields := bson.D{
		{"_id", 1},
		{"charmrequiredinterfaces", 1},
		{"charmprovidedinterfaces", 1},
	}

	// Retrieve the entities from the database.
	var entities []mongodoc.Entity
	if err := h.store.DB.Entities().Find(query).Select(fields).Sort("_id").All(&entities); err != nil {
		return nil, errgo.Notef(err, "cannot retrieve the related charms")
	}

	// If no entities are found there is no need for further processing the
	// results.
	if len(entities) == 0 {
		return &params.RelatedResponse{}, nil
	}

	// Build the results, by grouping entities based on their relations' roles
	// and interfaces.
	includes := flags["include"]
	requires, err := h.getRelatedCharmsResponse(entity.CharmProvidedInterfaces, entities, func(e mongodoc.Entity) []string {
		return e.CharmRequiredInterfaces
	}, includes)
	if err != nil {
		return nil, errgo.Notef(err, "cannot retrieve the charm requires")
	}
	provides, err := h.getRelatedCharmsResponse(entity.CharmRequiredInterfaces, entities, func(e mongodoc.Entity) []string {
		return e.CharmProvidedInterfaces
	}, includes)
	if err != nil {
		return nil, errgo.Notef(err, "cannot retrieve the charm provides")
	}

	// Return the response.
	return &params.RelatedResponse{
		Requires: requires,
		Provides: provides,
	}, nil
}

type entityRelatedInterfacesGetter func(mongodoc.Entity) []string

// getRelatedCharmsResponse returns a response mapping interfaces to related
// charms. For instance:
//   map[string][]params.MetaAnyResponse{
//       "http": []params.MetaAnyResponse{
//           {Id: "cs:utopic/django-42", Meta: ...},
//           {Id: "cs:trusty/wordpress-47", Meta: ...},
//       },
//       "memcache": []params.MetaAnyResponse{
//           {Id: "cs:utopic/memcached-0", Meta: ...},
//       },
//   }
func (h *handler) getRelatedCharmsResponse(
	ifaces []string,
	entities []mongodoc.Entity,
	getInterfaces entityRelatedInterfacesGetter,
	includes []string) (map[string][]params.MetaAnyResponse, error) {
	results := make(map[string][]params.MetaAnyResponse, len(ifaces))
	for _, iface := range ifaces {
		responses, err := h.getRelatedIfaceResponses(iface, entities, getInterfaces, includes)
		if err != nil {
			return nil, err
		}
		if len(responses) > 0 {
			results[iface] = responses
		}
	}
	return results, nil
}

func (h *handler) getRelatedIfaceResponses(
	iface string,
	entities []mongodoc.Entity,
	getInterfaces entityRelatedInterfacesGetter,
	includes []string) ([]params.MetaAnyResponse, error) {
	// Build a list of responses including entities which are related
	// to the given interface.
	responses := make([]params.MetaAnyResponse, 0, len(entities))
	for _, entity := range entities {
		for _, entityIface := range getInterfaces(entity) {
			if entityIface == iface {
				// Retrieve the requested metadata for the entity.
				meta, err := h.GetMetadata(entity.URL, includes)
				if err != nil {
					return nil, err
				}
				// Build the response.
				responses = append(responses, params.MetaAnyResponse{
					Id:   entity.URL,
					Meta: meta,
				})
			}
		}
	}
	return responses, nil
}

// GET id/meta/bundles-containing[?include=meta[&include=meta…]][&any-series=1][&any-revision=1]
// http://tinyurl.com/oqc386r
func (h *handler) metaBundlesContaining(entity *mongodoc.Entity, id *charm.Reference, path string, flags url.Values) (interface{}, error) {
	if id.Series == "bundle" {
		return nil, nil
	}

	// Validate the URL query values.
	anySeries, err := parseBool(flags.Get("any-series"))
	if err != nil {
		return nil, badRequestf(err, "invalid value for any-series")
	}
	anyRevision, err := parseBool(flags.Get("any-revision"))
	if err != nil {
		return nil, badRequestf(err, "invalid value for any-revision")
	}

	// Mutate the reference so that it represents a base URL if required.
	searchId := *id
	if anySeries || anyRevision {
		searchId.Revision = -1
		searchId.Series = ""
	}

	// Retrieve the bundles containing the resulting charm id.
	var entities []mongodoc.Entity
	if err := h.store.DB.Entities().
		Find(bson.D{{"bundlecharms", &searchId}}).
		Select(bson.D{{"_id", 1}, {"bundlecharms", 1}}).
		Sort("_id").
		All(&entities); err != nil {
		return nil, errgo.Notef(err, "cannot retrieve the related bundles")
	}

	// Further filter the entities if required.
	if anySeries != anyRevision {
		predicate := func(e *mongodoc.Entity) bool {
			for _, charmId := range e.BundleCharms {
				if charmId.Name == id.Name &&
					charmId.User == id.User &&
					(anySeries || charmId.Series == id.Series) &&
					(anyRevision || charmId.Revision == id.Revision) {
					return true
				}
			}
			return false
		}
		entities = filterEntities(entities, predicate)
	}

	// Prepare and return the response.
	response := make([]*params.MetaAnyResponse, 0, len(entities))
	includes := flags["include"]
	for _, e := range entities {
		meta, err := h.GetMetadata(e.URL, includes)
		if err != nil {
			return nil, errgo.Notef(err, "cannot retrieve bundle metadata")
		}
		response = append(response, &params.MetaAnyResponse{
			Id:   e.URL,
			Meta: meta,
		})
	}
	return response, nil
}

// filterEntities returns a slice containing all the entities for which the
// given predicate returns true.
func filterEntities(entities []mongodoc.Entity, predicate func(*mongodoc.Entity) bool) []mongodoc.Entity {
	results := make([]mongodoc.Entity, 0, len(entities))
	for _, entity := range entities {
		if predicate(&entity) {
			results = append(results, entity)
		}
	}
	return results
}
<|endoftext|>