<commit_before>/*--------------------------------------------------------*\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: https://hprose.com                     |
|                                                          |
| rpc/plugins/push/broker.go                               |
|                                                          |
| LastModified: May 17, 2021                               |
| Author: Ma Bingyao <andot@hprose.com>                    |
|                                                          |
\*________________________________________________________*/

package push

import (
	"context"
	"sync"
	"time"

	"github.com/hprose/hprose-golang/v3/rpc/core"
	cmap "github.com/orcaman/concurrent-map"
)

type Broker struct {
	*core.Service
	messages              sync.Map           // map[string]map[string]messageCache
	responders            cmap.ConcurrentMap // map[string]chan map[string][]Message
	signals               cmap.ConcurrentMap // map[string]chan bool
	MessageQueueMaxLength int
	Timeout               time.Duration
	HeartBeat             time.Duration
	OnSubscribe           func(ctx context.Context, id string, topic string)
	OnUnsubscribe         func(ctx context.Context, id string, topic string, messages []Message)
}

func NewBroker(service *core.Service) *Broker {
	broker := &Broker{
		Service:               service,
		responders:            cmap.New(),
		signals:               cmap.New(),
		MessageQueueMaxLength: 10,
		Timeout:               time.Minute * 2,
		HeartBeat:             time.Second * 10,
	}
	service.Use(broker.handler).
		AddFunction(broker.subscribe, "+").
		AddFunction(broker.unsubscribe, "-").
		AddFunction(broker.message, "<").
		AddFunction(broker.Unicast, ">").
		AddFunction(broker.Multicast, ">?").
		AddFunction(broker.Broadcast, ">*").
		AddFunction(broker.Exists, "?").
		AddFunction(broker.IdList, "|")
	return broker
}

func (b *Broker) send(ctx context.Context, id string, responder chan map[string][]Message) bool {
	var topics *sync.Map
	if value, ok := b.messages.Load(id); ok {
		topics = value.(*sync.Map)
	}
	if topics == nil {
		responder <- nil
		return true
	}
	var size int
	result := make(map[string][]Message)
	topics.Range(func(key, value interface{}) bool {
		size++
		topic := key.(string)
		cache := value.(*MessageCache)
		if cache == nil {
			result[topic] = nil
			topics.Delete(topic)
		} else {
			messages := cache.Take()
			if len(messages) > 0 {
				result[topic] = messages
			}
		}
		return true
	})
	if size == 0 {
		responder <- nil
		return true
	}
	if len(result) == 0 {
		return false
	}
	responder <- result
	go b.doHeartBeat(ctx, id)
	return true
}

func (b *Broker) doHeartBeat(ctx context.Context, id string) {
	if b.HeartBeat <= 0 {
		return
	}
	signal := make(chan bool, 1)
	b.signals.Upsert(id, signal, func(exist bool, valueInMap interface{}, newValue interface{}) interface{} {
		if exist {
			close(valueInMap.(chan bool))
		}
		return newValue
	})
	ctx, cancel := context.WithTimeout(ctx, b.HeartBeat)
	defer cancel()
	select {
	case <-ctx.Done():
		if topics, ok := b.messages.Load(id); ok {
			topics := topics.(*sync.Map)
			topics.Range(func(key, value interface{}) bool {
				b.offline(ctx, topics, id, key.(string))
				return true
			})
		}
	case <-signal:
	}
}

func (b *Broker) ID(ctx context.Context) (id string) {
	if id = core.GetServiceContext(ctx).RequestHeaders().GetString("id"); id == "" {
		panic("client unique id not found")
	}
	return
}

func (b *Broker) subscribe(ctx context.Context, topic string) bool {
	id := b.ID(ctx)
	t, ok := b.messages.Load(id)
	if !ok {
		t, _ = b.messages.LoadOrStore(id, new(sync.Map))
	}
	topics := t.(*sync.Map)
	if _, ok := topics.Load(topic); ok {
		return false
	}
	_, loaded := topics.LoadOrStore(topic, new(MessageCache))
	if !loaded && b.OnSubscribe != nil {
		b.OnSubscribe(ctx, id, topic)

	}
	return !loaded
}

func (b *Broker) response(ctx context.Context, id string) {
	if responder, ok := b.responders.Pop(id); ok {
		responder := responder.(chan map[string][]Message)
		if !b.send(ctx, id, responder) {
			if !b.responders.SetIfAbsent(id, responder) {
				responder <- nil
			}
		}
	}
}

func (b *Broker) offline(ctx context.Context, topics *sync.Map, id string, topic string) bool {
	if messages, ok := topics.Load(topic); ok {
		topics.Delete(topic)
		if b.OnUnsubscribe != nil {
			b.OnUnsubscribe(ctx, id, topic, messages.(*MessageCache).Take())
		}
		b.response(ctx, id)
		return true
	}
	return false
}

func (b *Broker) unsubscribe(ctx context.Context, topic string) bool {
	id := b.ID(ctx)
	if topics, ok := b.messages.Load(id); ok {
		return b.offline(ctx, topics.(*sync.Map), id, topic)
	}
	return false
}

func (b *Broker) message(ctx context.Context) map[string][]Message {
	id := b.ID(ctx)
	if responder, ok := b.responders.Pop(id); ok {
		responder.(chan map[string][]Message) <- nil
	}
	if signal, ok := b.signals.Pop(id); ok {
		close(signal.(chan bool))
	}
	responder := make(chan map[string][]Message, 1)
	if !b.send(ctx, id, responder) {
		b.responders.Upsert(id, responder, func(exist bool, valueInMap interface{}, newValue interface{}) interface{} {
			if exist {
				valueInMap.(chan map[string][]Message) <- nil
			}
			return newValue
		})
		if b.Timeout > 0 {
			ctx, cancel := context.WithTimeout(ctx, b.Timeout)
			defer cancel()
			select {
			case <-ctx.Done():
				go b.doHeartBeat(context.Background(), id)
				return map[string][]Message{}
			case result := <-responder:
				return result
			}
		}
	}
	return <-responder
}

func (b *Broker) Unicast(ctx context.Context, data interface{}, topic string, id string, from string) bool {
	if topics, ok := b.messages.Load(id); ok {
		if cache, ok := topics.(*sync.Map).Load(topic); ok && cache != nil {
			cache.(*MessageCache).Append(Message{Data: data, From: from})
			b.response(ctx, id)
			return true
		}
	}
	return false
}

func (b *Broker) Multicast(ctx context.Context, data interface{}, topic string, ids []string, from string) map[string]bool {
	result := make(map[string]bool)
	for _, id := range ids {
		result[id] = b.Unicast(ctx, data, topic, id, from)
	}
	return result
}

func (b *Broker) Broadcast(ctx context.Context, data interface{}, topic string, from string) map[string]bool {
	result := make(map[string]bool)
	b.messages.Range(func(key, value interface{}) bool {
		id := key.(string)
		topics := value.(*sync.Map)
		if cache, ok := topics.Load(topic); ok && cache != nil {
			cache.(*MessageCache).Append(Message{Data: data, From: from})
			b.response(ctx, id)
			result[id] = true
		}
		result[id] = false
		return true
	})
	return result
}

func (b *Broker) Push(data interface{}, topic string, id ...string) map[string]bool {
	ctx := context.Background()
	switch len(id) {
	case 0:
		return b.Broadcast(ctx, data, topic, "")
	case 1:
		return map[string]bool{
			id[0]: b.Unicast(ctx, data, topic, id[0], ""),
		}
	default:
		return b.Multicast(ctx, data, topic, id, "")
	}
}

func (b *Broker) Deny(ctx context.Context, id string, topic string) {
	if topics, ok := b.messages.Load(id); ok {
		topics := topics.(*sync.Map)
		if topic != "" {
			if cache, ok := topics.Load(topic); ok && cache != nil {
				topics.Store(topic, nil)
			}
		} else {
			topics.Range(func(key, _ interface{}) bool {
				topics.Store(key, nil)
				return false
			})
		}
		b.response(ctx, id)
	}
}

func (b *Broker) Exists(topic string, id string) bool {
	if topics, ok := b.messages.Load(id); ok {
		if cache, ok := topics.(*sync.Map).Load(topic); ok {
			return cache != nil
		}
	}
	return false
}

func (b *Broker) IdList(topic string) (idlist []string) {
	b.messages.Range(func(key, value interface{}) bool {
		id := key.(string)
		topics := value.(*sync.Map)
		if cache, ok := topics.Load(topic); ok && cache != nil {
			idlist = append(idlist, id)
		}
		return true
	})
	return
}

func (b *Broker) handler(ctx context.Context, name string, args []interface{}, next core.NextInvokeHandler) (result []interface{}, err error) {
	serviceContext := core.GetServiceContext(ctx)
	var from string
	if id := serviceContext.RequestHeaders().GetString("id"); id != "" {
		from = id
	}
	switch name {
	case ">", ">?", ">*":
		args = append(args, from)
	}
	serviceContext.Items().Set("producer", producer{b, from})
	return next(ctx, name, args)
}
<commit_msg>Removed MessageQueueMaxLength<commit_after>/*--------------------------------------------------------*\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: https://hprose.com                     |
|                                                          |
| rpc/plugins/push/broker.go                               |
|                                                          |
| LastModified: May 23, 2021                               |
| Author: Ma Bingyao <andot@hprose.com>                    |
|                                                          |
\*________________________________________________________*/

package push

import (
	"context"
	"sync"
	"time"

	"github.com/hprose/hprose-golang/v3/rpc/core"
	cmap "github.com/orcaman/concurrent-map"
)

type Broker struct {
	*core.Service
	messages      sync.Map           // map[string]map[string]messageCache
	responders    cmap.ConcurrentMap // map[string]chan map[string][]Message
	signals       cmap.ConcurrentMap // map[string]chan bool
	Timeout       time.Duration
	HeartBeat     time.Duration
	OnSubscribe   func(ctx context.Context, id string, topic string)
	OnUnsubscribe func(ctx context.Context, id string, topic string, messages []Message)
}

func NewBroker(service *core.Service) *Broker {
	broker := &Broker{
		Service:    service,
		responders: cmap.New(),
		signals:    cmap.New(),
		Timeout:    time.Minute * 2,
		HeartBeat:  time.Second * 10,
	}
	service.Use(broker.handler).
		AddFunction(broker.subscribe, "+").
		AddFunction(broker.unsubscribe, "-").
		AddFunction(broker.message, "<").
		AddFunction(broker.Unicast, ">").
		AddFunction(broker.Multicast, ">?").
		AddFunction(broker.Broadcast, ">*").
		AddFunction(broker.Exists, "?").
		AddFunction(broker.IdList, "|")
	return broker
}

func (b *Broker) send(ctx context.Context, id string, responder chan map[string][]Message) bool {
	var topics *sync.Map
	if value, ok := b.messages.Load(id); ok {
		topics = value.(*sync.Map)
	}
	if topics == nil {
		responder <- nil
		return true
	}
	var size int
	result := make(map[string][]Message)
	topics.Range(func(key, value interface{}) bool {
		size++
		topic := key.(string)
		cache := value.(*MessageCache)
		if cache == nil {
			result[topic] = nil
			topics.Delete(topic)
		} else {
			messages := cache.Take()
			if len(messages) > 0 {
				result[topic] = messages
			}
		}
		return true
	})
	if size == 0 {
		responder <- nil
		return true
	}
	if len(result) == 0 {
		return false
	}
	responder <- result
	go b.doHeartBeat(ctx, id)
	return true
}

func (b *Broker) doHeartBeat(ctx context.Context, id string) {
	if b.HeartBeat <= 0 {
		return
	}
	signal := make(chan bool, 1)
	b.signals.Upsert(id, signal, func(exist bool, valueInMap interface{}, newValue interface{}) interface{} {
		if exist {
			close(valueInMap.(chan bool))
		}
		return newValue
	})
	ctx, cancel := context.WithTimeout(ctx, b.HeartBeat)
	defer cancel()
	select {
	case <-ctx.Done():
		if topics, ok := b.messages.Load(id); ok {
			topics := topics.(*sync.Map)
			topics.Range(func(key, value interface{}) bool {
				b.offline(ctx, topics, id, key.(string))
				return true
			})
		}
	case <-signal:
	}
}

func (b *Broker) ID(ctx context.Context) (id string) {
	if id = core.GetServiceContext(ctx).RequestHeaders().GetString("id"); id == "" {
		panic("client unique id not found")
	}
	return
}

func (b *Broker) subscribe(ctx context.Context, topic string) bool {
	id := b.ID(ctx)
	t, ok := b.messages.Load(id)
	if !ok {
		t, _ = b.messages.LoadOrStore(id, new(sync.Map))
	}
	topics := t.(*sync.Map)
	if _, ok := topics.Load(topic); ok {
		return false
	}
	_, loaded := topics.LoadOrStore(topic, new(MessageCache))
	if !loaded && b.OnSubscribe != nil {
		b.OnSubscribe(ctx, id, topic)

	}
	return !loaded
}

func (b *Broker) response(ctx context.Context, id string) {
	if responder, ok := b.responders.Pop(id); ok {
		responder := responder.(chan map[string][]Message)
		if !b.send(ctx, id, responder) {
			if !b.responders.SetIfAbsent(id, responder) {
				responder <- nil
			}
		}
	}
}

func (b *Broker) offline(ctx context.Context, topics *sync.Map, id string, topic string) bool {
	if messages, ok := topics.Load(topic); ok {
		topics.Delete(topic)
		if b.OnUnsubscribe != nil {
			b.OnUnsubscribe(ctx, id, topic, messages.(*MessageCache).Take())
		}
		b.response(ctx, id)
		return true
	}
	return false
}

func (b *Broker) unsubscribe(ctx context.Context, topic string) bool {
	id := b.ID(ctx)
	if topics, ok := b.messages.Load(id); ok {
		return b.offline(ctx, topics.(*sync.Map), id, topic)
	}
	return false
}

func (b *Broker) message(ctx context.Context) map[string][]Message {
	id := b.ID(ctx)
	if responder, ok := b.responders.Pop(id); ok {
		responder.(chan map[string][]Message) <- nil
	}
	if signal, ok := b.signals.Pop(id); ok {
		close(signal.(chan bool))
	}
	responder := make(chan map[string][]Message, 1)
	if !b.send(ctx, id, responder) {
		b.responders.Upsert(id, responder, func(exist bool, valueInMap interface{}, newValue interface{}) interface{} {
			if exist {
				valueInMap.(chan map[string][]Message) <- nil
			}
			return newValue
		})
		if b.Timeout > 0 {
			ctx, cancel := context.WithTimeout(ctx, b.Timeout)
			defer cancel()
			select {
			case <-ctx.Done():
				go b.doHeartBeat(context.Background(), id)
				return map[string][]Message{}
			case result := <-responder:
				return result
			}
		}
	}
	return <-responder
}

func (b *Broker) Unicast(ctx context.Context, data interface{}, topic string, id string, from string) bool {
	if topics, ok := b.messages.Load(id); ok {
		if cache, ok := topics.(*sync.Map).Load(topic); ok && cache != nil {
			cache.(*MessageCache).Append(Message{Data: data, From: from})
			b.response(ctx, id)
			return true
		}
	}
	return false
}

func (b *Broker) Multicast(ctx context.Context, data interface{}, topic string, ids []string, from string) map[string]bool {
	result := make(map[string]bool)
	for _, id := range ids {
		result[id] = b.Unicast(ctx, data, topic, id, from)
	}
	return result
}

func (b *Broker) Broadcast(ctx context.Context, data interface{}, topic string, from string) map[string]bool {
	result := make(map[string]bool)
	b.messages.Range(func(key, value interface{}) bool {
		id := key.(string)
		topics := value.(*sync.Map)
		if cache, ok := topics.Load(topic); ok && cache != nil {
			cache.(*MessageCache).Append(Message{Data: data, From: from})
			b.response(ctx, id)
			result[id] = true
		}
		result[id] = false
		return true
	})
	return result
}

func (b *Broker) Push(data interface{}, topic string, id ...string) map[string]bool {
	ctx := context.Background()
	switch len(id) {
	case 0:
		return b.Broadcast(ctx, data, topic, "")
	case 1:
		return map[string]bool{
			id[0]: b.Unicast(ctx, data, topic, id[0], ""),
		}
	default:
		return b.Multicast(ctx, data, topic, id, "")
	}
}

func (b *Broker) Deny(ctx context.Context, id string, topic string) {
	if topics, ok := b.messages.Load(id); ok {
		topics := topics.(*sync.Map)
		if topic != "" {
			if cache, ok := topics.Load(topic); ok && cache != nil {
				topics.Store(topic, nil)
			}
		} else {
			topics.Range(func(key, _ interface{}) bool {
				topics.Store(key, nil)
				return false
			})
		}
		b.response(ctx, id)
	}
}

func (b *Broker) Exists(topic string, id string) bool {
	if topics, ok := b.messages.Load(id); ok {
		if cache, ok := topics.(*sync.Map).Load(topic); ok {
			return cache != nil
		}
	}
	return false
}

func (b *Broker) IdList(topic string) (idlist []string) {
	b.messages.Range(func(key, value interface{}) bool {
		id := key.(string)
		topics := value.(*sync.Map)
		if cache, ok := topics.Load(topic); ok && cache != nil {
			idlist = append(idlist, id)
		}
		return true
	})
	return
}

func (b *Broker) handler(ctx context.Context, name string, args []interface{}, next core.NextInvokeHandler) (result []interface{}, err error) {
	serviceContext := core.GetServiceContext(ctx)
	var from string
	if id := serviceContext.RequestHeaders().GetString("id"); id != "" {
		from = id
	}
	switch name {
	case ">", ">?", ">*":
		args = append(args, from)
	}
	serviceContext.Items().Set("producer", producer{b, from})
	return next(ctx, name, args)
}
<|endoftext|>


<commit_before>// Copyright 2013 Ooyala, Inc.

/*
Package statsd provides a Go dogstatsd client. Dogstatsd extends the popular statsd,
adding tags and histograms and pushing upstream to Datadog.

Refer to http://docs.datadoghq.com/guides/dogstatsd/ for information about DogStatsD.

Example Usage:

    // Create the client
    c, err := statsd.New("127.0.0.1:8125")
    if err != nil {
        log.Fatal(err)
    }
    // Prefix every metric with the app name
    c.Namespace = "flubber."
    // Send the EC2 availability zone as a tag with every metric
    c.Tags = append(c.Tags, "us-east-1a")
    err = c.Gauge("request.duration", 1.2, nil, 1)

statsd is based on go-statsd-client.
*/
package statsd

import (
	"bytes"
	"fmt"
	"math/rand"
	"net"
	"strconv"
	"strings"
	"sync"
	"time"
)

// A Client is a handle for sending udp messages to dogstatsd.  It is safe to
// use one Client from multiple goroutines simultaneously.
type Client struct {
	conn net.Conn
	// Namespace to prepend to all statsd calls
	Namespace string
	// Tags are global tags to be added to every statsd call
	Tags []string
	// BufferLength is the length of the buffer in commands.
	bufferLength int
	flushTime    time.Duration
	commands     []string
	stop         bool
	sync.Mutex
}

// New returns a pointer to a new Client given an addr in the format "hostname:port".
func New(addr string) (*Client, error) {
	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		return nil, err
	}
	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		return nil, err
	}
	client := &Client{conn: conn}
	return client, nil
}

// NewBuffered returns a Client that buffers its output and sends it in chunks.
// Buflen is the length of the buffer in number of commands.
func NewBuffered(addr string, buflen int) (*Client, error) {
	client, err := New(addr)
	if err != nil {
		return nil, err
	}
	client.bufferLength = buflen
	client.commands = make([]string, 0, buflen)
	client.flushTime = time.Millisecond * 100
	go client.watch()
	return client, nil
}

// format a message from its name, value, tags and rate.  Also adds global
// namespace and tags.
func (c *Client) format(name, value string, tags []string, rate float64) string {
	var buf bytes.Buffer
	if c.Namespace != "" {
		buf.WriteString(c.Namespace)
	}
	buf.WriteString(name)
	buf.WriteString(":")
	buf.WriteString(value)
	if rate < 1 {
		buf.WriteString(`|@`)
		buf.WriteString(strconv.FormatFloat(rate, 'f', -1, 64))
	}

	tags = append(c.Tags, tags...)
	if len(tags) > 0 {
		buf.WriteString("|#")
		buf.WriteString(tags[0])
		for _, tag := range tags[1:] {
			buf.WriteString(",")
			buf.WriteString(tag)
		}
	}
	return buf.String()
}

func (c *Client) watch() {
	for _ = range time.Tick(c.flushTime) {
		if c.stop {
			return
		}
		c.Lock()
		if len(c.commands) > 0 {
			// FIXME: eating error here
			c.flush()
		}
		c.Unlock()
	}
}

func (c *Client) append(cmd string) error {
	c.Lock()
	c.commands = append(c.commands, cmd)
	// if we should flush, lets do it
	if len(c.commands) == c.bufferLength {
		if err := c.flush(); err != nil {
			c.Unlock()
			return err
		}
	}
	c.Unlock()
	return nil
}

// flush the commands in the buffer.  Lock must be held by caller.
func (c *Client) flush() error {
	data := strings.Join(c.commands, "\n")
	_, err := c.conn.Write([]byte(data))
	// clear the slice with a slice op, doesn't realloc
	c.commands = c.commands[:0]
	return err
}

func (c *Client) sendMsg(msg string) error {
	// if this client is buffered, then we'll just append this
	if c.bufferLength > 0 {
		return c.append(msg)
	}
	c.Lock()
	_, err := c.conn.Write([]byte(msg))
	c.Unlock()
	return err
}

// send handles sampling and sends the message over UDP. It also adds global namespace prefixes and tags.
func (c *Client) send(name, value string, tags []string, rate float64) error {
	if c == nil {
		return nil
	}
	if rate < 1 && rand.Float64() > rate {
		return nil
	}
	data := c.format(name, value, tags, rate)
	return c.sendMsg(data)
}

// Gauge measures the value of a metric at a particular time.
func (c *Client) Gauge(name string, value float64, tags []string, rate float64) error {
	stat := fmt.Sprintf("%f|g", value)
	return c.send(name, stat, tags, rate)
}

// Count tracks how many times something happened per second.
func (c *Client) Count(name string, value int64, tags []string, rate float64) error {
	stat := fmt.Sprintf("%d|c", value)
	return c.send(name, stat, tags, rate)
}

// Histogram tracks the statistical distribution of a set of values.
func (c *Client) Histogram(name string, value float64, tags []string, rate float64) error {
	stat := fmt.Sprintf("%f|h", value)
	return c.send(name, stat, tags, rate)
}

// Set counts the number of unique elements in a group.
func (c *Client) Set(name string, value string, tags []string, rate float64) error {
	stat := fmt.Sprintf("%s|s", value)
	return c.send(name, stat, tags, rate)
}

// TimeInMilliseconds sends timing information in milliseconds.
// It is flushed by statsd with percentiles, mean and other info (https://github.com/etsy/statsd/blob/master/docs/metric_types.md#timing)
func (c *Client) TimeInMilliseconds(name string, value float64, tags []string, rate float64) error {
	stat := fmt.Sprintf("%f|ms", value)
	return c.send(name, stat, tags, rate)
}

// Event sends the provided Event.
func (c *Client) Event(e *Event) error {
	stat, err := e.Encode(c.Tags...)
	if err != nil {
		return err
	}
	return c.sendMsg(stat)
}

// SimpleEvent sends an event with the provided title and text.
func (c *Client) SimpleEvent(title, text string) error {
	e := NewEvent(title, text)
	return c.Event(e)
}

// Close the client connection.
func (c *Client) Close() error {
	if c == nil {
		return nil
	}
	c.stop = true
	return c.conn.Close()
}

// Events support

type eventAlertType string

const (
	// Info is the "info" AlertType for events
	Info eventAlertType = "info"
	// Error is the "error" AlertType for events
	Error eventAlertType = "error"
	// Warning is the "warning" AlertType for events
	Warning eventAlertType = "warning"
	// Success is the "success" AlertType for events
	Success eventAlertType = "success"
)

type eventPriority string

const (
	// Normal is the "normal" Priority for events
	Normal eventPriority = "normal"
	// Low is the "low" Priority for events
	Low eventPriority = "low"
)

// An Event is an object that can be posted to your DataDog event stream.
type Event struct {
	// Title of the event.  Required.
	Title string
	// Text is the description of the event.  Required.
	Text string
	// Timestamp is a timestamp for the event.  If not provided, the dogstatsd
	// server will set this to the current time.
	Timestamp time.Time
	// Hostname for the event.
	Hostname string
	// AggregationKey groups this event with others of the same key.
	AggregationKey string
	// Priority of the event.  Can be statsd.Low or statsd.Normal.
	Priority eventPriority
	// SourceTypeName is a source type for the event.
	SourceTypeName string
	// AlertType can be statsd.Info, statsd.Error, statsd.Warning, or statsd.Success.
	// If absent, the default value applied by the dogstatsd server is Info.
	AlertType eventAlertType
	// Tags for the event.
	Tags []string
}

// NewEvent creates a new event with the given title and text.  Error checking
// against these values is done at send-time, or upon running e.Check.
func NewEvent(title, text string) *Event {
	return &Event{
		Title: title,
		Text:  text,
	}
}

// Check verifies that an event is valid.
func (e Event) Check() error {
	if len(e.Title) == 0 {
		return fmt.Errorf("statsd.Event title is required")
	}
	if len(e.Text) == 0 {
		return fmt.Errorf("statsd.Event text is required")
	}
	return nil
}

// Encode returns the dogstatsd wire protocol representation for an event.
// Tags may be passed which will be added to the encoded output but not to
// the Event's list of tags, eg. for default tags.
func (e Event) Encode(tags ...string) (string, error) {
	err := e.Check()
	if err != nil {
		return "", err
	}
	text := e.escapedText()

	var buffer bytes.Buffer
	buffer.WriteString("_e{")
	buffer.WriteString(strconv.FormatInt(int64(len(e.Title)), 10))
	buffer.WriteRune(',')
	buffer.WriteString(strconv.FormatInt(int64(len(text)), 10))
	buffer.WriteString("}:")
	buffer.WriteString(e.Title)
	buffer.WriteRune('|')
	buffer.WriteString(text)

	if !e.Timestamp.IsZero() {
		buffer.WriteString("|d:")
		buffer.WriteString(strconv.FormatInt(int64(e.Timestamp.Unix()), 10))
	}

	if len(e.Hostname) != 0 {
		buffer.WriteString("|h:")
		buffer.WriteString(e.Hostname)
	}

	if len(e.AggregationKey) != 0 {
		buffer.WriteString("|k:")
		buffer.WriteString(e.AggregationKey)

	}

	if len(e.Priority) != 0 {
		buffer.WriteString("|p:")
		buffer.WriteString(string(e.Priority))
	}

	if len(e.SourceTypeName) != 0 {
		buffer.WriteString("|s:")
		buffer.WriteString(e.SourceTypeName)
	}

	if len(e.AlertType) != 0 {
		buffer.WriteString("|t:")
		buffer.WriteString(string(e.AlertType))
	}

	if len(tags)+len(e.Tags) > 0 {
		all := make([]string, 0, len(tags)+len(e.Tags))
		all = append(all, tags...)
		all = append(all, e.Tags...)
		buffer.WriteString("|#")
		buffer.WriteString(all[0])
		for _, tag := range all[1:] {
			buffer.WriteString(",")
			buffer.WriteString(tag)
		}
	}

	return buffer.String(), nil
}

func (e Event) escapedText() string {
	return strings.Replace(e.text, "\n", "\\n", -1)
}
<commit_msg>Fixing typo introduced in 694a2805f0014581c9da2bfbe2acd3192517eac7<commit_after>// Copyright 2013 Ooyala, Inc.

/*
Package statsd provides a Go dogstatsd client. Dogstatsd extends the popular statsd,
adding tags and histograms and pushing upstream to Datadog.

Refer to http://docs.datadoghq.com/guides/dogstatsd/ for information about DogStatsD.

Example Usage:

    // Create the client
    c, err := statsd.New("127.0.0.1:8125")
    if err != nil {
        log.Fatal(err)
    }
    // Prefix every metric with the app name
    c.Namespace = "flubber."
    // Send the EC2 availability zone as a tag with every metric
    c.Tags = append(c.Tags, "us-east-1a")
    err = c.Gauge("request.duration", 1.2, nil, 1)

statsd is based on go-statsd-client.
*/
package statsd

import (
	"bytes"
	"fmt"
	"math/rand"
	"net"
	"strconv"
	"strings"
	"sync"
	"time"
)

// A Client is a handle for sending udp messages to dogstatsd.  It is safe to
// use one Client from multiple goroutines simultaneously.
type Client struct {
	conn net.Conn
	// Namespace to prepend to all statsd calls
	Namespace string
	// Tags are global tags to be added to every statsd call
	Tags []string
	// BufferLength is the length of the buffer in commands.
	bufferLength int
	flushTime    time.Duration
	commands     []string
	stop         bool
	sync.Mutex
}

// New returns a pointer to a new Client given an addr in the format "hostname:port".
func New(addr string) (*Client, error) {
	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		return nil, err
	}
	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		return nil, err
	}
	client := &Client{conn: conn}
	return client, nil
}

// NewBuffered returns a Client that buffers its output and sends it in chunks.
// Buflen is the length of the buffer in number of commands.
func NewBuffered(addr string, buflen int) (*Client, error) {
	client, err := New(addr)
	if err != nil {
		return nil, err
	}
	client.bufferLength = buflen
	client.commands = make([]string, 0, buflen)
	client.flushTime = time.Millisecond * 100
	go client.watch()
	return client, nil
}

// format a message from its name, value, tags and rate.  Also adds global
// namespace and tags.
func (c *Client) format(name, value string, tags []string, rate float64) string {
	var buf bytes.Buffer
	if c.Namespace != "" {
		buf.WriteString(c.Namespace)
	}
	buf.WriteString(name)
	buf.WriteString(":")
	buf.WriteString(value)
	if rate < 1 {
		buf.WriteString(`|@`)
		buf.WriteString(strconv.FormatFloat(rate, 'f', -1, 64))
	}

	tags = append(c.Tags, tags...)
	if len(tags) > 0 {
		buf.WriteString("|#")
		buf.WriteString(tags[0])
		for _, tag := range tags[1:] {
			buf.WriteString(",")
			buf.WriteString(tag)
		}
	}
	return buf.String()
}

func (c *Client) watch() {
	for _ = range time.Tick(c.flushTime) {
		if c.stop {
			return
		}
		c.Lock()
		if len(c.commands) > 0 {
			// FIXME: eating error here
			c.flush()
		}
		c.Unlock()
	}
}

func (c *Client) append(cmd string) error {
	c.Lock()
	c.commands = append(c.commands, cmd)
	// if we should flush, lets do it
	if len(c.commands) == c.bufferLength {
		if err := c.flush(); err != nil {
			c.Unlock()
			return err
		}
	}
	c.Unlock()
	return nil
}

// flush the commands in the buffer.  Lock must be held by caller.
func (c *Client) flush() error {
	data := strings.Join(c.commands, "\n")
	_, err := c.conn.Write([]byte(data))
	// clear the slice with a slice op, doesn't realloc
	c.commands = c.commands[:0]
	return err
}

func (c *Client) sendMsg(msg string) error {
	// if this client is buffered, then we'll just append this
	if c.bufferLength > 0 {
		return c.append(msg)
	}
	c.Lock()
	_, err := c.conn.Write([]byte(msg))
	c.Unlock()
	return err
}

// send handles sampling and sends the message over UDP. It also adds global namespace prefixes and tags.
func (c *Client) send(name, value string, tags []string, rate float64) error {
	if c == nil {
		return nil
	}
	if rate < 1 && rand.Float64() > rate {
		return nil
	}
	data := c.format(name, value, tags, rate)
	return c.sendMsg(data)
}

// Gauge measures the value of a metric at a particular time.
func (c *Client) Gauge(name string, value float64, tags []string, rate float64) error {
	stat := fmt.Sprintf("%f|g", value)
	return c.send(name, stat, tags, rate)
}

// Count tracks how many times something happened per second.
func (c *Client) Count(name string, value int64, tags []string, rate float64) error {
	stat := fmt.Sprintf("%d|c", value)
	return c.send(name, stat, tags, rate)
}

// Histogram tracks the statistical distribution of a set of values.
func (c *Client) Histogram(name string, value float64, tags []string, rate float64) error {
	stat := fmt.Sprintf("%f|h", value)
	return c.send(name, stat, tags, rate)
}

// Set counts the number of unique elements in a group.
func (c *Client) Set(name string, value string, tags []string, rate float64) error {
	stat := fmt.Sprintf("%s|s", value)
	return c.send(name, stat, tags, rate)
}

// TimeInMilliseconds sends timing information in milliseconds.
// It is flushed by statsd with percentiles, mean and other info (https://github.com/etsy/statsd/blob/master/docs/metric_types.md#timing)
func (c *Client) TimeInMilliseconds(name string, value float64, tags []string, rate float64) error {
	stat := fmt.Sprintf("%f|ms", value)
	return c.send(name, stat, tags, rate)
}

// Event sends the provided Event.
func (c *Client) Event(e *Event) error {
	stat, err := e.Encode(c.Tags...)
	if err != nil {
		return err
	}
	return c.sendMsg(stat)
}

// SimpleEvent sends an event with the provided title and text.
func (c *Client) SimpleEvent(title, text string) error {
	e := NewEvent(title, text)
	return c.Event(e)
}

// Close the client connection.
func (c *Client) Close() error {
	if c == nil {
		return nil
	}
	c.stop = true
	return c.conn.Close()
}

// Events support

type eventAlertType string

const (
	// Info is the "info" AlertType for events
	Info eventAlertType = "info"
	// Error is the "error" AlertType for events
	Error eventAlertType = "error"
	// Warning is the "warning" AlertType for events
	Warning eventAlertType = "warning"
	// Success is the "success" AlertType for events
	Success eventAlertType = "success"
)

type eventPriority string

const (
	// Normal is the "normal" Priority for events
	Normal eventPriority = "normal"
	// Low is the "low" Priority for events
	Low eventPriority = "low"
)

// An Event is an object that can be posted to your DataDog event stream.
type Event struct {
	// Title of the event.  Required.
	Title string
	// Text is the description of the event.  Required.
	Text string
	// Timestamp is a timestamp for the event.  If not provided, the dogstatsd
	// server will set this to the current time.
	Timestamp time.Time
	// Hostname for the event.
	Hostname string
	// AggregationKey groups this event with others of the same key.
	AggregationKey string
	// Priority of the event.  Can be statsd.Low or statsd.Normal.
	Priority eventPriority
	// SourceTypeName is a source type for the event.
	SourceTypeName string
	// AlertType can be statsd.Info, statsd.Error, statsd.Warning, or statsd.Success.
	// If absent, the default value applied by the dogstatsd server is Info.
	AlertType eventAlertType
	// Tags for the event.
	Tags []string
}

// NewEvent creates a new event with the given title and text.  Error checking
// against these values is done at send-time, or upon running e.Check.
func NewEvent(title, text string) *Event {
	return &Event{
		Title: title,
		Text:  text,
	}
}

// Check verifies that an event is valid.
func (e Event) Check() error {
	if len(e.Title) == 0 {
		return fmt.Errorf("statsd.Event title is required")
	}
	if len(e.Text) == 0 {
		return fmt.Errorf("statsd.Event text is required")
	}
	return nil
}

// Encode returns the dogstatsd wire protocol representation for an event.
// Tags may be passed which will be added to the encoded output but not to
// the Event's list of tags, eg. for default tags.
func (e Event) Encode(tags ...string) (string, error) {
	err := e.Check()
	if err != nil {
		return "", err
	}
	text := e.escapedText()

	var buffer bytes.Buffer
	buffer.WriteString("_e{")
	buffer.WriteString(strconv.FormatInt(int64(len(e.Title)), 10))
	buffer.WriteRune(',')
	buffer.WriteString(strconv.FormatInt(int64(len(text)), 10))
	buffer.WriteString("}:")
	buffer.WriteString(e.Title)
	buffer.WriteRune('|')
	buffer.WriteString(text)

	if !e.Timestamp.IsZero() {
		buffer.WriteString("|d:")
		buffer.WriteString(strconv.FormatInt(int64(e.Timestamp.Unix()), 10))
	}

	if len(e.Hostname) != 0 {
		buffer.WriteString("|h:")
		buffer.WriteString(e.Hostname)
	}

	if len(e.AggregationKey) != 0 {
		buffer.WriteString("|k:")
		buffer.WriteString(e.AggregationKey)

	}

	if len(e.Priority) != 0 {
		buffer.WriteString("|p:")
		buffer.WriteString(string(e.Priority))
	}

	if len(e.SourceTypeName) != 0 {
		buffer.WriteString("|s:")
		buffer.WriteString(e.SourceTypeName)
	}

	if len(e.AlertType) != 0 {
		buffer.WriteString("|t:")
		buffer.WriteString(string(e.AlertType))
	}

	if len(tags)+len(e.Tags) > 0 {
		all := make([]string, 0, len(tags)+len(e.Tags))
		all = append(all, tags...)
		all = append(all, e.Tags...)
		buffer.WriteString("|#")
		buffer.WriteString(all[0])
		for _, tag := range all[1:] {
			buffer.WriteString(",")
			buffer.WriteString(tag)
		}
	}

	return buffer.String(), nil
}

func (e Event) escapedText() string {
	return strings.Replace(e.Text, "\n", "\\n", -1)
}
<|endoftext|>


<commit_before>package statsd

import (
	"math/rand"
	"sync"
	"time"
)

type worker struct {
	pool   *bufferPool
	buffer *statsdBuffer
	sender *sender
	random *rand.Rand
	sync.Mutex

	inputMetrics chan metric
	stop         chan struct{}
}

func newWorker(pool *bufferPool, sender *sender) *worker {
	// Each worker uses its own random source to prevent workers in separate
	// goroutines from contending for the lock on the "math/rand" package-global
	// random source (e.g. calls like "rand.Float64()" must acquire a shared
	// lock to get the next pseudorandom number).
	// Note that calling "time.Now().UnixNano()" repeatedly quickly may return
	// very similar values. That's fine for seeding the worker-specific random
	// source because we just need an evenly distributed stream of float values.
	// Do not use this random source for cryptographic randomness.
	random := rand.New(rand.NewSource(time.Now().UnixNano()))
	return &worker{
		pool:   pool,
		sender: sender,
		buffer: pool.borrowBuffer(),
		random: random,
		stop:   make(chan struct{}),
	}
}

func (w *worker) startReceivingMetric(bufferSize int) {
	w.inputMetrics = make(chan metric, bufferSize)
	go w.pullMetric()
}

func (w *worker) stopReceivingMetric() {
	w.stop <- struct{}{}
}

func (w *worker) pullMetric() {
	for {
		select {
		case m := <-w.inputMetrics:
			w.processMetric(m)
		case <-w.stop:
			return
		}
	}
}

func (w *worker) processMetric(m metric) error {
	if !w.shouldSample(m.rate) {
		return nil
	}
	w.Lock()
	var err error
	if err = w.writeMetricUnsafe(m); err == errBufferFull {
		w.flushUnsafe()
		err = w.writeMetricUnsafe(m)
	}
	w.Unlock()
	return err
}

func (w *worker) shouldSample(rate float64) bool {
	if rate < 1 && w.random.Float64() > rate {
		return false
	}
	return true
}

func (w *worker) writeAggregatedMetricUnsafe(m metric, metricSymbol []byte) error {
	globalPos := 0

	// first check how much data we can write to the buffer:
	//   +3 + len(metricSymbol) because the message will include '|<metricSymbol>|#' before the tags
	//   +1 for the coma between the two set of tags
	tagsSize := len(m.stags) + 4 + len(metricSymbol)
	for _, t := range m.globalTags {
		tagsSize += len(t) + 1
	}

	for {
		pos, err := w.buffer.writeAggregated(metricSymbol, m.namespace, m.globalTags, m.name, m.fvalues[globalPos:], m.stags, tagsSize)
		if err == errPartialWrite {
			// We successfully wrote part of the histogram metrics.
			// We flush the current buffer and finish the histogram
			// in a new one.
			w.flushUnsafe()
			globalPos += pos
		} else {
			return err
		}
	}
}

func (w *worker) writeMetricUnsafe(m metric) error {
	switch m.metricType {
	case gauge:
		return w.buffer.writeGauge(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate)
	case count:
		return w.buffer.writeCount(m.namespace, m.globalTags, m.name, m.ivalue, m.tags, m.rate)
	case histogram:
		return w.buffer.writeHistogram(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate)
	case distribution:
		return w.buffer.writeDistribution(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate)
	case set:
		return w.buffer.writeSet(m.namespace, m.globalTags, m.name, m.svalue, m.tags, m.rate)
	case timing:
		return w.buffer.writeTiming(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate)
	case event:
		return w.buffer.writeEvent(*m.evalue, m.globalTags)
	case serviceCheck:
		return w.buffer.writeServiceCheck(*m.scvalue, m.globalTags)
	case histogramAggregated:
		return w.writeAggregatedMetricUnsafe(m, histogramSymbol)
	case distributionAggregated:
		return w.writeAggregatedMetricUnsafe(m, distributionSymbol)
	case timingAggregated:
		return w.writeAggregatedMetricUnsafe(m, timingSymbol)
	default:
		return nil
	}
}

func (w *worker) flush() {
	w.Lock()
	w.flushUnsafe()
	w.Unlock()
}

func (w *worker) pause() {
	w.Lock()
}

func (w *worker) unpause() {
	w.Unlock()
}

// flush the current buffer. Lock must be held by caller.
// flushed buffer written to the network asynchronously.
func (w *worker) flushUnsafe() {
	if len(w.buffer.bytes()) > 0 {
		w.sender.send(w.buffer)
		w.buffer = w.pool.borrowBuffer()
	}
}
<commit_msg>Fix worker random race condition<commit_after>package statsd

import (
	"math/rand"
	"sync"
	"time"
)

type worker struct {
	pool       *bufferPool
	buffer     *statsdBuffer
	sender     *sender
	random     *rand.Rand
	randomLock sync.Mutex
	sync.Mutex

	inputMetrics chan metric
	stop         chan struct{}
}

func newWorker(pool *bufferPool, sender *sender) *worker {
	// Each worker uses its own random source and random lock to prevent
	// workers in separate goroutines from contending for the lock on the
	// "math/rand" package-global random source (e.g. calls like
	// "rand.Float64()" must acquire a shared lock to get the next
	// pseudorandom number).
	// Note that calling "time.Now().UnixNano()" repeatedly quickly may return
	// very similar values. That's fine for seeding the worker-specific random
	// source because we just need an evenly distributed stream of float values.
	// Do not use this random source for cryptographic randomness.
	random := rand.New(rand.NewSource(time.Now().UnixNano()))
	return &worker{
		pool:   pool,
		sender: sender,
		buffer: pool.borrowBuffer(),
		random: random,
		stop:   make(chan struct{}),
	}
}

func (w *worker) startReceivingMetric(bufferSize int) {
	w.inputMetrics = make(chan metric, bufferSize)
	go w.pullMetric()
}

func (w *worker) stopReceivingMetric() {
	w.stop <- struct{}{}
}

func (w *worker) pullMetric() {
	for {
		select {
		case m := <-w.inputMetrics:
			w.processMetric(m)
		case <-w.stop:
			return
		}
	}
}

func (w *worker) processMetric(m metric) error {
	if !w.shouldSample(m.rate) {
		return nil
	}
	w.Lock()
	var err error
	if err = w.writeMetricUnsafe(m); err == errBufferFull {
		w.flushUnsafe()
		err = w.writeMetricUnsafe(m)
	}
	w.Unlock()
	return err
}

func (w *worker) shouldSample(rate float64) bool {
	// rand.NewSource is not thread safe.
	// TODO: use defer once the lowest Go version we support is 1.14 (defer
	// has an overhead before that).
	w.randomLock.Lock()
	if rate < 1 && w.random.Float64() > rate {
		w.randomLock.Unlock()
		return false
	}
	w.randomLock.Unlock()
	return true
}

func (w *worker) writeAggregatedMetricUnsafe(m metric, metricSymbol []byte) error {
	globalPos := 0

	// first check how much data we can write to the buffer:
	//   +3 + len(metricSymbol) because the message will include '|<metricSymbol>|#' before the tags
	//   +1 for the coma between the two set of tags
	tagsSize := len(m.stags) + 4 + len(metricSymbol)
	for _, t := range m.globalTags {
		tagsSize += len(t) + 1
	}

	for {
		pos, err := w.buffer.writeAggregated(metricSymbol, m.namespace, m.globalTags, m.name, m.fvalues[globalPos:], m.stags, tagsSize)
		if err == errPartialWrite {
			// We successfully wrote part of the histogram metrics.
			// We flush the current buffer and finish the histogram
			// in a new one.
			w.flushUnsafe()
			globalPos += pos
		} else {
			return err
		}
	}
}

func (w *worker) writeMetricUnsafe(m metric) error {
	switch m.metricType {
	case gauge:
		return w.buffer.writeGauge(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate)
	case count:
		return w.buffer.writeCount(m.namespace, m.globalTags, m.name, m.ivalue, m.tags, m.rate)
	case histogram:
		return w.buffer.writeHistogram(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate)
	case distribution:
		return w.buffer.writeDistribution(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate)
	case set:
		return w.buffer.writeSet(m.namespace, m.globalTags, m.name, m.svalue, m.tags, m.rate)
	case timing:
		return w.buffer.writeTiming(m.namespace, m.globalTags, m.name, m.fvalue, m.tags, m.rate)
	case event:
		return w.buffer.writeEvent(*m.evalue, m.globalTags)
	case serviceCheck:
		return w.buffer.writeServiceCheck(*m.scvalue, m.globalTags)
	case histogramAggregated:
		return w.writeAggregatedMetricUnsafe(m, histogramSymbol)
	case distributionAggregated:
		return w.writeAggregatedMetricUnsafe(m, distributionSymbol)
	case timingAggregated:
		return w.writeAggregatedMetricUnsafe(m, timingSymbol)
	default:
		return nil
	}
}

func (w *worker) flush() {
	w.Lock()
	w.flushUnsafe()
	w.Unlock()
}

func (w *worker) pause() {
	w.Lock()
}

func (w *worker) unpause() {
	w.Unlock()
}

// flush the current buffer. Lock must be held by caller.
// flushed buffer written to the network asynchronously.
func (w *worker) flushUnsafe() {
	if len(w.buffer.bytes()) > 0 {
		w.sender.send(w.buffer)
		w.buffer = w.pool.borrowBuffer()
	}
}
<|endoftext|>


<commit_before>package main

import (
	cwe "github.com/aws/aws-sdk-go/service/cloudwatchevents"
)

// Rules is for store unmarshalized configuration yaml
type Rules struct {
	Rules []Rule
}

// Rule is for expression CloudWatch Events Rule
type Rule struct {
	Description        string   `yaml:"description"`
	EventPattern       string   `yaml:"event_pattern"`
	Name               string   `yaml:"name"`
	RoleArn            string   `yaml:"role_arn"`
	ScheduleExpression string   `yaml:"schedule_expression"`
	State              string   `yaml:"state"`
	Targets            []Target `yaml:"targets"`
	ActualRule         cwe.Rule
	NeedUpdate         bool
	NeedDelete         bool
}

// Target is for expression CloudWatch Events Target
type Target struct {
	Arn          string `yaml:"arn"`
	Id           string `yaml:"id"`
	Input        string `yaml:"input"`
	InputPath    string `yaml:"input_path"`
	ActualTarget cwe.Target
	NeedUpdate   bool
	NeedDelete   bool
}

// LambdaPolicy is for JSON that return from Lambda.GetPolicy
type LambdaPolicy struct {
	Version   string             `json:"Version"`
	Id        string             `json:"Id"`
	Statement *[]PolicyStatement `json:"Statement"`
}

// PolicyStatement is a part of the LambdaPolicy
type PolicyStatement struct {
	Resource    string           `json:"Resource"`
	Condition   *PolicyCondition `json:"Condition"`
	StatementId string           `json:"Sid"`
	Effect      string           `json:"Effect"`
	Principal   *PolicyPrincipal `json:"Principal"`
	Action      string           `json:"Action"`
}

// PolicyCondition is a part of the LambdaPolicy
type PolicyCondition struct {
	ArnLike *PolicyArnLike `json:"ArnLike"`
}

// PolicyArnLike is a part of the LambdaPolicy
type PolicyArnLike struct {
	AwsSourceArn string `json:"AWS:SourceArn"`
}

// PolicyPrincipal is a part of the LambdaPolicy
type PolicyPrincipal struct {
	Service string `json:"Service"`
}
<commit_msg>Add fields and struct definition for taget<commit_after>package main

import (
	cwe "github.com/aws/aws-sdk-go/service/cloudwatchevents"
)

// Rules is for store unmarshalized configuration yaml
type Rules struct {
	Rules []Rule
}

// Rule is for expression CloudWatch Events Rule
type Rule struct {
	Description        string   `yaml:"description"`
	EventPattern       string   `yaml:"event_pattern"`
	Name               string   `yaml:"name"`
	RoleArn            string   `yaml:"role_arn"`
	ScheduleExpression string   `yaml:"schedule_expression"`
	State              string   `yaml:"state"`
	Targets            []Target `yaml:"targets"`
	ActualRule         cwe.Rule
	NeedUpdate         bool
	NeedDelete         bool
}

// Target is for expression CloudWatch Events Target
type Target struct {
	Arn               string            `yaml:"arn"`
	Id                string            `yaml:"id"`
	Input             string            `yaml:"input"`
	InputPath         string            `yaml:"input_path"`
	EcsParameters     EcsParameters     `yaml:"ecs_parameters"`
	KinesisParameters KinesisParameters `yaml:"kinesis_parameters"`
	RoleArn           string            `yaml:"role_arn"`
	ActualTarget      cwe.Target
	NeedUpdate        bool
	NeedDelete        bool
}

// EcsParameters is a part of the Target
type EcsParameters struct {
	TaskCount         int64  `yaml:"task_count"`
	TaskDefinitionArn string `yaml:"task_definition_arn"`
}

// KinesisParameters is a part of the Target
type KinesisParameters struct {
	PartitionKeyPath string `yaml:"partition_key_path"`
}

// LambdaPolicy is for JSON that return from Lambda.GetPolicy
type LambdaPolicy struct {
	Version   string             `json:"Version"`
	Id        string             `json:"Id"`
	Statement *[]PolicyStatement `json:"Statement"`
}

// PolicyStatement is a part of the LambdaPolicy
type PolicyStatement struct {
	Resource    string           `json:"Resource"`
	Condition   *PolicyCondition `json:"Condition"`
	StatementId string           `json:"Sid"`
	Effect      string           `json:"Effect"`
	Principal   *PolicyPrincipal `json:"Principal"`
	Action      string           `json:"Action"`
}

// PolicyCondition is a part of the LambdaPolicy
type PolicyCondition struct {
	ArnLike *PolicyArnLike `json:"ArnLike"`
}

// PolicyArnLike is a part of the LambdaPolicy
type PolicyArnLike struct {
	AwsSourceArn string `json:"AWS:SourceArn"`
}

// PolicyPrincipal is a part of the LambdaPolicy
type PolicyPrincipal struct {
	Service string `json:"Service"`
}
<|endoftext|>


<commit_before>package subscription

import (
	"sort"
	"strings"

	"github.com/liam-lai/ptt-alertor/crawler"
	log "github.com/liam-lai/ptt-alertor/log"
	boardProto "github.com/liam-lai/ptt-alertor/models/ptt/board"
	board "github.com/liam-lai/ptt-alertor/models/ptt/board/redis"
)

type Subscriptions []Subscription

func (ss Subscriptions) String() string {

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

	str := "關鍵字\n"
	for _, sub := range ss {
		if sub.String() != "" {
			str += sub.String() + "\n"
		}
	}
	str += "----\n作者\n"
	for _, sub := range ss {
		if sub.StringAuthor() != "" {
			str += sub.StringAuthor() + "\n"
		}
	}
	return str
}

func (ss *Subscriptions) Add(sub Subscription) error {
	sub.Board = strings.ToLower(sub.Board)
	if ok, suggestion := checkBoardExist(sub.Board); !ok {
		return boardProto.BoardNotExistError{suggestion}
	}
	sub.CleanUp()
	for i, s := range *ss {
		if strings.EqualFold(s.Board, sub.Board) {
			s.Keywords.AppendNonRepeat(sub.Keywords, false)
			s.Authors.AppendNonRepeat(sub.Authors, false)
			(*ss)[i] = s
			return nil
		}
	}
	*ss = append(*ss, sub)

	return nil
}

func (ss *Subscriptions) Remove(sub Subscription) error {
	sub.Board = strings.ToLower(sub.Board)
	if ok, suggestion := checkBoardExist(sub.Board); !ok {
		return boardProto.BoardNotExistError{suggestion}
	}
	sub.CleanUp()
	for i := 0; i < len(*ss); i++ {
		s := (*ss)[i]
		if strings.EqualFold(s.Board, sub.Board) {
			s.DeleteKeywords(sub.Keywords)
			s.DeleteAuthors(sub.Authors)
			(*ss)[i] = s
		}
		if len((*ss)[i].Keywords) == 0 && len((*ss)[i].Authors) == 0 {
			*ss = append((*ss)[:i], (*ss)[i+1:]...)
			i--
		}
	}
	return nil
}

func checkBoardExist(boardName string) (bool, string) {
	bd := new(board.Board)
	bd.Name = boardName
	if bd.Exist() {
		return true, ""
	}
	if crawler.CheckBoardExist(boardName) {
		bd.Create()
		return true, ""
	}

	suggestBoard := bd.SuggestBoardName()
	log.WithFields(log.Field{
		"inputBoard":   boardName,
		"suggestBoard": suggestBoard,
	}).Warning("Board Not Exist")
	return false, suggestBoard
}
<commit_msg>fix package name<commit_after>package subscription

import (
	"sort"
	"strings"

	"github.com/liam-lai/ptt-alertor/crawler"
	boardProto "github.com/liam-lai/ptt-alertor/models/ptt/board"
	board "github.com/liam-lai/ptt-alertor/models/ptt/board/redis"
	log "github.com/meifamily/logrus"
)

type Subscriptions []Subscription

func (ss Subscriptions) String() string {

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

	str := "關鍵字\n"
	for _, sub := range ss {
		if sub.String() != "" {
			str += sub.String() + "\n"
		}
	}
	str += "----\n作者\n"
	for _, sub := range ss {
		if sub.StringAuthor() != "" {
			str += sub.StringAuthor() + "\n"
		}
	}
	return str
}

func (ss *Subscriptions) Add(sub Subscription) error {
	sub.Board = strings.ToLower(sub.Board)
	if ok, suggestion := checkBoardExist(sub.Board); !ok {
		return boardProto.BoardNotExistError{suggestion}
	}
	sub.CleanUp()
	for i, s := range *ss {
		if strings.EqualFold(s.Board, sub.Board) {
			s.Keywords.AppendNonRepeat(sub.Keywords, false)
			s.Authors.AppendNonRepeat(sub.Authors, false)
			(*ss)[i] = s
			return nil
		}
	}
	*ss = append(*ss, sub)

	return nil
}

func (ss *Subscriptions) Remove(sub Subscription) error {
	sub.Board = strings.ToLower(sub.Board)
	if ok, suggestion := checkBoardExist(sub.Board); !ok {
		return boardProto.BoardNotExistError{suggestion}
	}
	sub.CleanUp()
	for i := 0; i < len(*ss); i++ {
		s := (*ss)[i]
		if strings.EqualFold(s.Board, sub.Board) {
			s.DeleteKeywords(sub.Keywords)
			s.DeleteAuthors(sub.Authors)
			(*ss)[i] = s
		}
		if len((*ss)[i].Keywords) == 0 && len((*ss)[i].Authors) == 0 {
			*ss = append((*ss)[:i], (*ss)[i+1:]...)
			i--
		}
	}
	return nil
}

func checkBoardExist(boardName string) (bool, string) {
	bd := new(board.Board)
	bd.Name = boardName
	if bd.Exist() {
		return true, ""
	}
	if crawler.CheckBoardExist(boardName) {
		bd.Create()
		return true, ""
	}

	suggestBoard := bd.SuggestBoardName()
	log.WithFields(log.Fields{
		"inputBoard":   boardName,
		"suggestBoard": suggestBoard,
	}).Warning("Board Not Exist")
	return false, suggestBoard
}
<|endoftext|>


<commit_before>package users

import (
	"fmt"

	"github.com/synapse-garden/sg-proto/store"

	"github.com/boltdb/bolt"
	"github.com/pkg/errors"
)

var UserBucket = []byte("users")

type User struct {
	Name string `json:"name,omitempty"`
	Coin int64  `json:"coin"`
}

type ErrExists string

func (e ErrExists) Error() string { return fmt.Sprintf("user %#q already exists", string(e)) }

type ErrMissing string

func (e ErrMissing) Error() string { return fmt.Sprintf("user %#q not found", string(e)) }

func CheckUserExists(u *User) func(*bolt.Tx) error {
	return func(tx *bolt.Tx) error {
		err := store.CheckExists(UserBucket, []byte(u.Name))(tx)
		if store.IsMissing(err) {
			return ErrMissing(u.Name)
		}
		return err
	}
}

func CheckUserNotExist(u *User) func(*bolt.Tx) error {
	return func(tx *bolt.Tx) error {
		err := store.CheckNotExist(UserBucket, []byte(u.Name))(tx)
		if store.IsExists(err) {
			return ErrExists(u.Name)
		}
		return err
	}
}

// Create returns a writing transaction which checks that the user has
// not yet been created, then Puts its JSON representation in UserBucket.
func Create(u *User) func(*bolt.Tx) error {
	return store.Marshal(UserBucket, u, []byte(u.Name))
}

func Delete(u *User) func(*bolt.Tx) error {
	return store.Delete(UserBucket, []byte(u.Name))
}

// ValidateNew validates a new User.
func ValidateNew(u *User) error {
	switch {
	case len(u.Name) == 0:
		return errors.New("name must not be blank")
	case u.Coin != 0:
		return errors.New("user cannot be created with coin")
	}
	return nil
}
<commit_msg>Make users error checks smarter<commit_after>package users

import (
	"fmt"

	"github.com/synapse-garden/sg-proto/store"

	"github.com/boltdb/bolt"
	"github.com/pkg/errors"
)

var UserBucket = []byte("users")

type User struct {
	Name string `json:"name,omitempty"`
	Coin int64  `json:"coin"`
}

type ErrExists string

func (e ErrExists) Error() string { return fmt.Sprintf("user %#q already exists", string(e)) }

type ErrMissing string

func (e ErrMissing) Error() string { return fmt.Sprintf("user %#q not found", string(e)) }

func IsExists(err error) bool {
	if err == nil {
		return false
	}
	_, ok := err.(ErrExists)
	return ok
}

func IsMissing(err error) bool {
	if err == nil {
		return false
	}
	_, ok := err.(ErrMissing)
	return ok
}

// CheckUsersExist checks that the Users with the given names exist.
func CheckUsersExist(names ...string) func(*bolt.Tx) error {
	return func(tx *bolt.Tx) error {
		for _, name := range names {
			err := store.CheckExists(UserBucket, []byte(name))(tx)
			if store.IsMissing(err) {
				return ErrMissing(name)
			} else if err != nil {
				return err
			}
		}

		return nil
	}
}

func CheckUserNotExist(u *User) func(*bolt.Tx) error {
	return func(tx *bolt.Tx) error {
		err := store.CheckNotExist(UserBucket, []byte(u.Name))(tx)
		if store.IsExists(err) {
			return ErrExists(u.Name)
		}
		return err
	}
}

// Create returns a writing transaction which checks that the user has
// not yet been created, then Puts its JSON representation in UserBucket.
func Create(u *User) func(*bolt.Tx) error {
	return store.Marshal(UserBucket, u, []byte(u.Name))
}

func Delete(u *User) func(*bolt.Tx) error {
	return store.Delete(UserBucket, []byte(u.Name))
}

// ValidateNew validates a new User.
func ValidateNew(u *User) error {
	switch {
	case len(u.Name) == 0:
		return errors.New("name must not be blank")
	case u.Coin != 0:
		return errors.New("user cannot be created with coin")
	}
	return nil
}
<|endoftext|>


<commit_before>package controller

import (
	"fmt"

	"github.com/almighty/almighty-core/app"
	"github.com/almighty/almighty-core/application"
	"github.com/almighty/almighty-core/errors"
	"github.com/almighty/almighty-core/jsonapi"
	"github.com/almighty/almighty-core/rest"
	"github.com/almighty/almighty-core/space"
	"github.com/almighty/almighty-core/workitem"

	"github.com/goadesign/goa"
	errs "github.com/pkg/errors"
	uuid "github.com/satori/go.uuid"
)

const (
	sourceLinkTypesRouteEnd = "/source-link-types"
	targetLinkTypesRouteEnd = "/target-link-types"
)

// WorkitemtypeController implements the workitemtype resource.
type WorkitemtypeController struct {
	*goa.Controller
	db     application.DB
	config WorkItemControllerConfiguration
}

type WorkItemControllerConfiguration interface {
	GetCacheControlWorkItemType() string
}

// NewWorkitemtypeController creates a workitemtype controller.
func NewWorkitemtypeController(service *goa.Service, db application.DB, config WorkItemControllerConfiguration) *WorkitemtypeController {
	return &WorkitemtypeController{
		Controller: service.NewController("WorkitemtypeController"),
		db:         db,
		config:     config,
	}
}

// Show runs the show action.
func (c *WorkitemtypeController) Show(ctx *app.ShowWorkitemtypeContext) error {
	spaceID, err := uuid.FromString(ctx.ID)
	if err != nil {
		return errors.NewNotFoundError("spaceID", ctx.ID)
	}

	return application.Transactional(c.db, func(appl application.Application) error {
		witModel, err := appl.WorkItemTypes().Load(ctx.Context, spaceID, ctx.WitID)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		witData := convertTypeFromModel(goa.ContextRequest(ctx), witModel)
		wit := &app.WorkItemTypeSingle{Data: &witData}
		return ctx.Conditional(*wit, c.config.GetCacheControlWorkItemType, func() error {
			return ctx.OK(wit)
		})
	})
}

// Create runs the create action.
func (c *WorkitemtypeController) Create(ctx *app.CreateWorkitemtypeContext) error {
	spaceID, err := uuid.FromString(ctx.ID)
	if err != nil {
		return errors.NewNotFoundError("spaceID", ctx.ID)
	}

	return application.Transactional(c.db, func(appl application.Application) error {
		var fields = map[string]app.FieldDefinition{}
		for key, fd := range ctx.Payload.Data.Attributes.Fields {
			fields[key] = *fd
		}
		// Set the space to the Payload
		if ctx.Payload.Data != nil && ctx.Payload.Data.Relationships != nil {
			// We overwrite or use the space ID in the URL to set the space of this WI
			spaceSelfURL := rest.AbsoluteURL(goa.ContextRequest(ctx), app.SpaceHref(spaceID.String()))
			ctx.Payload.Data.Relationships.Space = space.NewSpaceRelation(spaceID, spaceSelfURL)
		}
		modelFields, err := ConvertFieldDefinitionsToModel(fields)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		witTypeModel, err := appl.WorkItemTypes().Create(
			ctx.Context,
			*ctx.Payload.Data.Relationships.Space.Data.ID,
			ctx.Payload.Data.ID,
			ctx.Payload.Data.Attributes.ExtendedTypeName,
			ctx.Payload.Data.Attributes.Name,
			ctx.Payload.Data.Attributes.Description,
			ctx.Payload.Data.Attributes.Icon,
			modelFields)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		witData := convertTypeFromModel(goa.ContextRequest(ctx), witTypeModel)
		wit := &app.WorkItemTypeSingle{Data: &witData}
		ctx.ResponseData.Header().Set("Location", app.WorkitemtypeHref(*ctx.Payload.Data.Relationships.Space.Data.ID, wit.Data.ID))
		return ctx.Created(wit)
	})
}

// List runs the list action
func (c *WorkitemtypeController) List(ctx *app.ListWorkitemtypeContext) error {
	spaceID, err := uuid.FromString(ctx.ID)
	if err != nil {
		return errors.NewNotFoundError("spaceID", ctx.ID)
	}

	start, limit, err := parseLimit(ctx.Page)
	if err != nil {
		return jsonapi.JSONErrorResponse(ctx, errs.Wrap(err, "Could not parse paging"))
	}
	return application.Transactional(c.db, func(appl application.Application) error {
		witModels, err := appl.WorkItemTypes().List(ctx.Context, spaceID, start, &limit)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, errs.Wrap(err, "Error listing work item types"))
		}
		// convert from model to app
		result := &app.WorkItemTypeList{}
		result.Data = make([]*app.WorkItemTypeData, len(witModels))
		for index, value := range witModels {
			wit := convertTypeFromModel(goa.ContextRequest(ctx), &value)
			result.Data[index] = &wit
		}

		return ctx.Conditional(*result, c.config.GetCacheControlWorkItemType, func() error {
			return ctx.OK(result)
		})
	})
}

// ListSourceLinkTypes runs the list-source-link-types action.
func (c *WorkitemtypeController) ListSourceLinkTypes(ctx *app.ListSourceLinkTypesWorkitemtypeContext) error {
	spaceID, err := uuid.FromString(ctx.ID)
	if err != nil {
		return errors.NewNotFoundError("spaceID", ctx.ID)
	}

	return application.Transactional(c.db, func(appl application.Application) error {
		// Test that work item type exists
		_, err := appl.WorkItemTypes().Load(ctx.Context, spaceID, ctx.WitID)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		// Fetch all link types where this work item type can be used in the
		// source of the link
		result, err := appl.WorkItemLinkTypes().ListSourceLinkTypes(ctx.Context, ctx.WitID)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		// Enrich link types
		hrefFunc := func(obj interface{}) string {
			return fmt.Sprintf(app.WorkItemLinkTypeHref(spaceID, "%v"), obj)
		}
		linkCtx := newWorkItemLinkContext(ctx.Context, appl, c.db, ctx.RequestData, ctx.ResponseData, hrefFunc, nil)
		err = enrichLinkTypeList(linkCtx, result)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		return ctx.Conditional(*result, c.config.GetCacheControlWorkItemType, func() error {
			return ctx.OK(result)
		})
	})
}

// ListTargetLinkTypes runs the list-target-link-types action.
func (c *WorkitemtypeController) ListTargetLinkTypes(ctx *app.ListTargetLinkTypesWorkitemtypeContext) error {
	spaceID, err := uuid.FromString(ctx.ID)
	if err != nil {
		return errors.NewNotFoundError("spaceID", ctx.ID)
	}

	return application.Transactional(c.db, func(appl application.Application) error {
		// Test that work item type exists
		_, err := appl.WorkItemTypes().Load(ctx.Context, spaceID, ctx.WitID)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		// Fetch all link types where this work item type can be used in the
		// target of the linkg
		result, err := appl.WorkItemLinkTypes().ListTargetLinkTypes(ctx.Context, ctx.WitID)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		// Enrich link types
		hrefFunc := func(obj interface{}) string {
			return fmt.Sprintf(app.WorkItemLinkTypeHref(spaceID, "%v"), obj)
		}
		linkCtx := newWorkItemLinkContext(ctx.Context, appl, c.db, ctx.RequestData, ctx.ResponseData, hrefFunc, nil)
		err = enrichLinkTypeList(linkCtx, result)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		return ctx.Conditional(*result, c.config.GetCacheControlWorkItemType, func() error {
			return ctx.OK(result)
		})
	})
}

// converts from models to app representation
func convertTypeFromModel(request *goa.RequestData, t *workitem.WorkItemType) app.WorkItemTypeData {
	spaceSelfURL := rest.AbsoluteURL(request, app.SpaceHref(t.SpaceID.String()))
	id := t.ID
	createdAt := t.CreatedAt.UTC()
	updatedAt := t.UpdatedAt.UTC()
	var converted = app.WorkItemTypeData{
		Type: "workitemtypes",
		ID:   &id,
		Attributes: &app.WorkItemTypeAttributes{
			CreatedAt:   &createdAt,
			UpdatedAt:   &updatedAt,
			Version:     &t.Version,
			Description: t.Description,
			Icon:        t.Icon,
			Name:        t.Name,
			Fields:      map[string]*app.FieldDefinition{},
		},
		Relationships: &app.WorkItemTypeRelationships{
			Space: space.NewSpaceRelation(t.SpaceID, spaceSelfURL),
		},
	}
	for name, def := range t.Fields {
		ct := convertFieldTypeFromModel(def.Type)
		converted.Attributes.Fields[name] = &app.FieldDefinition{
			Required:    def.Required,
			Label:       def.Label,
			Description: def.Description,
			Type:        &ct,
		}
	}
	return converted
}

// converts the field type from modesl to app representation
func convertFieldTypeFromModel(t workitem.FieldType) app.FieldType {
	result := app.FieldType{}
	result.Kind = string(t.GetKind())
	switch t2 := t.(type) {
	case workitem.ListType:
		kind := string(t2.ComponentType.GetKind())
		result.ComponentType = &kind
	case workitem.EnumType:
		kind := string(t2.BaseType.GetKind())
		result.BaseType = &kind
		result.Values = t2.Values
	}

	return result
}

func convertFieldTypeToModel(t app.FieldType) (workitem.FieldType, error) {
	kind, err := workitem.ConvertStringToKind(t.Kind)
	if err != nil {
		return nil, errs.WithStack(err)
	}
	switch *kind {
	case workitem.KindList:
		componentType, err := workitem.ConvertAnyToKind(*t.ComponentType)
		if err != nil {
			return nil, errs.WithStack(err)
		}
		if !componentType.IsSimpleType() {
			return nil, fmt.Errorf("Component type is not list type: %T", componentType)
		}
		return workitem.ListType{workitem.SimpleType{*kind}, workitem.SimpleType{*componentType}}, nil
	case workitem.KindEnum:
		bt, err := workitem.ConvertAnyToKind(*t.BaseType)
		if err != nil {
			return nil, errs.WithStack(err)
		}
		if !bt.IsSimpleType() {
			return nil, fmt.Errorf("baseType type is not list type: %T", bt)
		}
		baseType := workitem.SimpleType{*bt}

		values := t.Values
		converted, err := workitem.ConvertList(func(ft workitem.FieldType, element interface{}) (interface{}, error) {
			return ft.ConvertToModel(element)
		}, baseType, values)
		if err != nil {
			return nil, errs.WithStack(err)
		}
		return workitem.EnumType{workitem.SimpleType{*kind}, baseType, converted}, nil
	default:
		return workitem.SimpleType{*kind}, nil
	}
}

func ConvertFieldDefinitionsToModel(fields map[string]app.FieldDefinition) (map[string]workitem.FieldDefinition, error) {
	modelFields := map[string]workitem.FieldDefinition{}
	// now process new fields, checking whether they are ok to add.
	for field, definition := range fields {
		ct, err := convertFieldTypeToModel(*definition.Type)
		if err != nil {
			return nil, errs.WithStack(err)
		}
		converted := workitem.FieldDefinition{
			Label:       definition.Label,
			Description: definition.Description,
			Required:    definition.Required,
			Type:        ct,
		}
		modelFields[field] = converted
	}
	return modelFields, nil
}
<commit_msg>Temp workaround spaces with no WITs (#1047)<commit_after>package controller

import (
	"fmt"

	"github.com/almighty/almighty-core/app"
	"github.com/almighty/almighty-core/application"
	"github.com/almighty/almighty-core/errors"
	"github.com/almighty/almighty-core/jsonapi"
	"github.com/almighty/almighty-core/rest"
	"github.com/almighty/almighty-core/space"
	"github.com/almighty/almighty-core/workitem"

	"github.com/goadesign/goa"
	errs "github.com/pkg/errors"
	uuid "github.com/satori/go.uuid"
)

const (
	sourceLinkTypesRouteEnd = "/source-link-types"
	targetLinkTypesRouteEnd = "/target-link-types"
)

// WorkitemtypeController implements the workitemtype resource.
type WorkitemtypeController struct {
	*goa.Controller
	db     application.DB
	config WorkItemControllerConfiguration
}

type WorkItemControllerConfiguration interface {
	GetCacheControlWorkItemType() string
}

// NewWorkitemtypeController creates a workitemtype controller.
func NewWorkitemtypeController(service *goa.Service, db application.DB, config WorkItemControllerConfiguration) *WorkitemtypeController {
	return &WorkitemtypeController{
		Controller: service.NewController("WorkitemtypeController"),
		db:         db,
		config:     config,
	}
}

// Show runs the show action.
func (c *WorkitemtypeController) Show(ctx *app.ShowWorkitemtypeContext) error {
	spaceID, err := uuid.FromString(ctx.ID)
	if err != nil {
		return errors.NewNotFoundError("spaceID", ctx.ID)
	}

	return application.Transactional(c.db, func(appl application.Application) error {
		witModel, err := appl.WorkItemTypes().Load(ctx.Context, spaceID, ctx.WitID)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		witData := convertTypeFromModel(goa.ContextRequest(ctx), witModel)
		wit := &app.WorkItemTypeSingle{Data: &witData}
		return ctx.Conditional(*wit, c.config.GetCacheControlWorkItemType, func() error {
			return ctx.OK(wit)
		})
	})
}

// Create runs the create action.
func (c *WorkitemtypeController) Create(ctx *app.CreateWorkitemtypeContext) error {
	spaceID, err := uuid.FromString(ctx.ID)
	if err != nil {
		return errors.NewNotFoundError("spaceID", ctx.ID)
	}

	return application.Transactional(c.db, func(appl application.Application) error {
		var fields = map[string]app.FieldDefinition{}
		for key, fd := range ctx.Payload.Data.Attributes.Fields {
			fields[key] = *fd
		}
		// Set the space to the Payload
		if ctx.Payload.Data != nil && ctx.Payload.Data.Relationships != nil {
			// We overwrite or use the space ID in the URL to set the space of this WI
			spaceSelfURL := rest.AbsoluteURL(goa.ContextRequest(ctx), app.SpaceHref(spaceID.String()))
			ctx.Payload.Data.Relationships.Space = space.NewSpaceRelation(spaceID, spaceSelfURL)
		}
		modelFields, err := ConvertFieldDefinitionsToModel(fields)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		witTypeModel, err := appl.WorkItemTypes().Create(
			ctx.Context,
			*ctx.Payload.Data.Relationships.Space.Data.ID,
			ctx.Payload.Data.ID,
			ctx.Payload.Data.Attributes.ExtendedTypeName,
			ctx.Payload.Data.Attributes.Name,
			ctx.Payload.Data.Attributes.Description,
			ctx.Payload.Data.Attributes.Icon,
			modelFields)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		witData := convertTypeFromModel(goa.ContextRequest(ctx), witTypeModel)
		wit := &app.WorkItemTypeSingle{Data: &witData}
		ctx.ResponseData.Header().Set("Location", app.WorkitemtypeHref(*ctx.Payload.Data.Relationships.Space.Data.ID, wit.Data.ID))
		return ctx.Created(wit)
	})
}

// List runs the list action
func (c *WorkitemtypeController) List(ctx *app.ListWorkitemtypeContext) error {
	spaceID, err := uuid.FromString(ctx.ID)
	if err != nil {
		return errors.NewNotFoundError("spaceID", ctx.ID)
	}

	start, limit, err := parseLimit(ctx.Page)
	if err != nil {
		return jsonapi.JSONErrorResponse(ctx, errs.Wrap(err, "Could not parse paging"))
	}
	return application.Transactional(c.db, func(appl application.Application) error {
		witModels, err := appl.WorkItemTypes().List(ctx.Context, spaceID, start, &limit)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, errs.Wrap(err, "Error listing work item types"))
		}

		// TEMP!!!!! Until Space Template can setup a Space, redirect to SystemSpace WITs if non are found
		// for the space.
		if len(witModels) == 0 {
			witModels, err = appl.WorkItemTypes().List(ctx.Context, space.SystemSpace, start, &limit)
			if err != nil {
				return jsonapi.JSONErrorResponse(ctx, errs.Wrap(err, "Error listing work item types"))
			}
		}
		// convert from model to app
		result := &app.WorkItemTypeList{}
		result.Data = make([]*app.WorkItemTypeData, len(witModels))
		for index, value := range witModels {
			wit := convertTypeFromModel(goa.ContextRequest(ctx), &value)
			result.Data[index] = &wit
		}

		return ctx.Conditional(*result, c.config.GetCacheControlWorkItemType, func() error {
			return ctx.OK(result)
		})
	})
}

// ListSourceLinkTypes runs the list-source-link-types action.
func (c *WorkitemtypeController) ListSourceLinkTypes(ctx *app.ListSourceLinkTypesWorkitemtypeContext) error {
	spaceID, err := uuid.FromString(ctx.ID)
	if err != nil {
		return errors.NewNotFoundError("spaceID", ctx.ID)
	}

	return application.Transactional(c.db, func(appl application.Application) error {
		// Test that work item type exists
		_, err := appl.WorkItemTypes().Load(ctx.Context, spaceID, ctx.WitID)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		// Fetch all link types where this work item type can be used in the
		// source of the link
		result, err := appl.WorkItemLinkTypes().ListSourceLinkTypes(ctx.Context, ctx.WitID)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		// Enrich link types
		hrefFunc := func(obj interface{}) string {
			return fmt.Sprintf(app.WorkItemLinkTypeHref(spaceID, "%v"), obj)
		}
		linkCtx := newWorkItemLinkContext(ctx.Context, appl, c.db, ctx.RequestData, ctx.ResponseData, hrefFunc, nil)
		err = enrichLinkTypeList(linkCtx, result)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		return ctx.Conditional(*result, c.config.GetCacheControlWorkItemType, func() error {
			return ctx.OK(result)
		})
	})
}

// ListTargetLinkTypes runs the list-target-link-types action.
func (c *WorkitemtypeController) ListTargetLinkTypes(ctx *app.ListTargetLinkTypesWorkitemtypeContext) error {
	spaceID, err := uuid.FromString(ctx.ID)
	if err != nil {
		return errors.NewNotFoundError("spaceID", ctx.ID)
	}

	return application.Transactional(c.db, func(appl application.Application) error {
		// Test that work item type exists
		_, err := appl.WorkItemTypes().Load(ctx.Context, spaceID, ctx.WitID)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		// Fetch all link types where this work item type can be used in the
		// target of the linkg
		result, err := appl.WorkItemLinkTypes().ListTargetLinkTypes(ctx.Context, ctx.WitID)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		// Enrich link types
		hrefFunc := func(obj interface{}) string {
			return fmt.Sprintf(app.WorkItemLinkTypeHref(spaceID, "%v"), obj)
		}
		linkCtx := newWorkItemLinkContext(ctx.Context, appl, c.db, ctx.RequestData, ctx.ResponseData, hrefFunc, nil)
		err = enrichLinkTypeList(linkCtx, result)
		if err != nil {
			return jsonapi.JSONErrorResponse(ctx, err)
		}
		return ctx.Conditional(*result, c.config.GetCacheControlWorkItemType, func() error {
			return ctx.OK(result)
		})
	})
}

// converts from models to app representation
func convertTypeFromModel(request *goa.RequestData, t *workitem.WorkItemType) app.WorkItemTypeData {
	spaceSelfURL := rest.AbsoluteURL(request, app.SpaceHref(t.SpaceID.String()))
	id := t.ID
	createdAt := t.CreatedAt.UTC()
	updatedAt := t.UpdatedAt.UTC()
	var converted = app.WorkItemTypeData{
		Type: "workitemtypes",
		ID:   &id,
		Attributes: &app.WorkItemTypeAttributes{
			CreatedAt:   &createdAt,
			UpdatedAt:   &updatedAt,
			Version:     &t.Version,
			Description: t.Description,
			Icon:        t.Icon,
			Name:        t.Name,
			Fields:      map[string]*app.FieldDefinition{},
		},
		Relationships: &app.WorkItemTypeRelationships{
			Space: space.NewSpaceRelation(t.SpaceID, spaceSelfURL),
		},
	}
	for name, def := range t.Fields {
		ct := convertFieldTypeFromModel(def.Type)
		converted.Attributes.Fields[name] = &app.FieldDefinition{
			Required:    def.Required,
			Label:       def.Label,
			Description: def.Description,
			Type:        &ct,
		}
	}
	return converted
}

// converts the field type from modesl to app representation
func convertFieldTypeFromModel(t workitem.FieldType) app.FieldType {
	result := app.FieldType{}
	result.Kind = string(t.GetKind())
	switch t2 := t.(type) {
	case workitem.ListType:
		kind := string(t2.ComponentType.GetKind())
		result.ComponentType = &kind
	case workitem.EnumType:
		kind := string(t2.BaseType.GetKind())
		result.BaseType = &kind
		result.Values = t2.Values
	}

	return result
}

func convertFieldTypeToModel(t app.FieldType) (workitem.FieldType, error) {
	kind, err := workitem.ConvertStringToKind(t.Kind)
	if err != nil {
		return nil, errs.WithStack(err)
	}
	switch *kind {
	case workitem.KindList:
		componentType, err := workitem.ConvertAnyToKind(*t.ComponentType)
		if err != nil {
			return nil, errs.WithStack(err)
		}
		if !componentType.IsSimpleType() {
			return nil, fmt.Errorf("Component type is not list type: %T", componentType)
		}
		return workitem.ListType{workitem.SimpleType{*kind}, workitem.SimpleType{*componentType}}, nil
	case workitem.KindEnum:
		bt, err := workitem.ConvertAnyToKind(*t.BaseType)
		if err != nil {
			return nil, errs.WithStack(err)
		}
		if !bt.IsSimpleType() {
			return nil, fmt.Errorf("baseType type is not list type: %T", bt)
		}
		baseType := workitem.SimpleType{*bt}

		values := t.Values
		converted, err := workitem.ConvertList(func(ft workitem.FieldType, element interface{}) (interface{}, error) {
			return ft.ConvertToModel(element)
		}, baseType, values)
		if err != nil {
			return nil, errs.WithStack(err)
		}
		return workitem.EnumType{workitem.SimpleType{*kind}, baseType, converted}, nil
	default:
		return workitem.SimpleType{*kind}, nil
	}
}

func ConvertFieldDefinitionsToModel(fields map[string]app.FieldDefinition) (map[string]workitem.FieldDefinition, error) {
	modelFields := map[string]workitem.FieldDefinition{}
	// now process new fields, checking whether they are ok to add.
	for field, definition := range fields {
		ct, err := convertFieldTypeToModel(*definition.Type)
		if err != nil {
			return nil, errs.WithStack(err)
		}
		converted := workitem.FieldDefinition{
			Label:       definition.Label,
			Description: definition.Description,
			Required:    definition.Required,
			Type:        ct,
		}
		modelFields[field] = converted
	}
	return modelFields, nil
}
<|endoftext|>