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

package server

import (
	"errors"
	"fmt"
	"net/http"

	"golang.org/x/text/message"
	"golang.org/x/text/transform"

	"github.com/issue9/web/internal/header"
	"github.com/issue9/web/serializer"
)

type BeforeMarshalFunc func(*Context, any) any

type AfterMarshalFunc func(*Context, []byte) []byte

// OnMarshal 注册编码过程中的钩子函数
func (srv *Server) OnMarshal(mimetype string, before BeforeMarshalFunc, after AfterMarshalFunc) {
	if _, found := srv.beforeMarshals[mimetype]; found {
		panic(fmt.Sprintf("%s 已经存在", mimetype))
	}
	srv.beforeMarshals[mimetype] = before

	if _, found := srv.afterMarshals[mimetype]; found {
		panic(fmt.Sprintf("%s 已经存在", mimetype))
	}
	srv.afterMarshals[mimetype] = after
}

// Marshal 向客户端输出内容
//
// status 想输出给用户状态码，如果出错，那么最终展示给用户的状态码可能不是此值；
// body 表示输出的对象，该对象最终调用 ctx.outputMimetype 编码；
// problem 表示 body 是否为 [Problem] 对象，对于 Problem 对象可能会有特殊的处理；
func (ctx *Context) Marshal(status int, body any, problem bool) error {
	if body == nil {
		ctx.WriteHeader(status)
		return nil
	}

	// 如果 outputMimetype 为空，说明在 Server.Mimetypes() 的配置中就是 nil。
	// 那么不应该执行到此，比如下载文件等直接从 ResponseWriter.Write 输出的。
	if ctx.outputMimetype == nil {
		ctx.WriteHeader(http.StatusNotAcceptable)
		panic(fmt.Sprintf("未对 %s 作处理", ctx.outputMimetypeName))
	}

	if problem {
		ctx.outputMimetypeName = ctx.Server().Problems().mimetype(ctx.outputMimetypeName)
	}
	ctx.Header().Set("Content-Type", header.BuildContentType(ctx.outputMimetypeName, ctx.outputCharsetName))
	if id := ctx.languageTag.String(); id != "" {
		ctx.Header().Set("Content-Language", id)
	}

	if f, found := ctx.Server().beforeMarshals[ctx.outputMimetypeName]; found {
		body = f(ctx, body)
	}

	data, err := ctx.outputMimetype(body)
	switch {
	case err != nil && problem: // 如果在输出 problem 时出错状态码不变
		ctx.WriteHeader(status)
		return err
	case errors.Is(err, serializer.ErrUnsupported):
		ctx.WriteHeader(http.StatusNotAcceptable) // 此处不再输出 Problem 类型错误信息，大概率也是相同的错误。
		return err
	case err != nil:
		ctx.WriteHeader(http.StatusInternalServerError) // 此处不再输出 Problem 类型错误信息，大概率也是相同的错误。
		return err
	}

	if f, found := ctx.Server().afterMarshals[ctx.outputMimetypeName]; found {
		data = f(ctx, data)
	}

	ctx.WriteHeader(status)
	_, err = ctx.Write(data)
	return err
}

// Sprintf 返回翻译后的结果
func (ctx *Context) Sprintf(key message.Reference, v ...any) string {
	return ctx.LocalePrinter().Sprintf(key, v...)
}

func (ctx *Context) Write(bs []byte) (int, error) {
	if !ctx.wrote { // 在第一次有内容输出时，才决定构建 Encoding 和 Charset 的 io.Writer
		ctx.wrote = true

		if ctx.outputEncoding != nil {
			ctx.encodingCloser = ctx.outputEncoding.Get(ctx.respWriter)
			ctx.respWriter = ctx.encodingCloser
		}

		if !header.CharsetIsNop(ctx.outputCharset) {
			ctx.charsetCloser = transform.NewWriter(ctx.respWriter, ctx.outputCharset.NewEncoder())
			ctx.respWriter = ctx.charsetCloser
		}
	}

	return ctx.respWriter.Write(bs)
}

func (ctx *Context) WriteHeader(status int) {
	ctx.Header().Del("Content-Length") // https://github.com/golang/go/issues/14975
	ctx.status = status
	ctx.resp.WriteHeader(status)
}

func (ctx *Context) Header() http.Header { return ctx.resp.Header() }
<commit_msg>fix(server): Server.OnMarshal 考虑空值的情况<commit_after>// SPDX-License-Identifier: MIT

package server

import (
	"errors"
	"fmt"
	"net/http"

	"golang.org/x/text/message"
	"golang.org/x/text/transform"

	"github.com/issue9/web/internal/header"
	"github.com/issue9/web/serializer"
)

type BeforeMarshalFunc func(*Context, any) any

type AfterMarshalFunc func(*Context, []byte) []byte

// OnMarshal 注册编码过程中的钩子函数
func (srv *Server) OnMarshal(mimetype string, before BeforeMarshalFunc, after AfterMarshalFunc) {
	if before != nil {
		if _, found := srv.beforeMarshals[mimetype]; found {
			panic(fmt.Sprintf("%s 已经存在", mimetype))
		}
		srv.beforeMarshals[mimetype] = before
	}

	if after != nil {
		if _, found := srv.afterMarshals[mimetype]; found {
			panic(fmt.Sprintf("%s 已经存在", mimetype))
		}
		srv.afterMarshals[mimetype] = after
	}
}

// Marshal 向客户端输出内容
//
// status 想输出给用户状态码，如果出错，那么最终展示给用户的状态码可能不是此值；
// body 表示输出的对象，该对象最终调用 ctx.outputMimetype 编码；
// problem 表示 body 是否为 [Problem] 对象，对于 Problem 对象可能会有特殊的处理；
func (ctx *Context) Marshal(status int, body any, problem bool) error {
	if body == nil {
		ctx.WriteHeader(status)
		return nil
	}

	// 如果 outputMimetype 为空，说明在 Server.Mimetypes() 的配置中就是 nil。
	// 那么不应该执行到此，比如下载文件等直接从 ResponseWriter.Write 输出的。
	if ctx.outputMimetype == nil {
		ctx.WriteHeader(http.StatusNotAcceptable)
		panic(fmt.Sprintf("未对 %s 作处理", ctx.outputMimetypeName))
	}

	if problem {
		ctx.outputMimetypeName = ctx.Server().Problems().mimetype(ctx.outputMimetypeName)
	}
	ctx.Header().Set("Content-Type", header.BuildContentType(ctx.outputMimetypeName, ctx.outputCharsetName))
	if id := ctx.languageTag.String(); id != "" {
		ctx.Header().Set("Content-Language", id)
	}

	if f, found := ctx.Server().beforeMarshals[ctx.outputMimetypeName]; found {
		body = f(ctx, body)
	}

	data, err := ctx.outputMimetype(body)
	switch {
	case err != nil && problem: // 如果在输出 problem 时出错状态码不变
		ctx.WriteHeader(status)
		return err
	case errors.Is(err, serializer.ErrUnsupported):
		ctx.WriteHeader(http.StatusNotAcceptable) // 此处不再输出 Problem 类型错误信息，大概率也是相同的错误。
		return err
	case err != nil:
		ctx.WriteHeader(http.StatusInternalServerError) // 此处不再输出 Problem 类型错误信息，大概率也是相同的错误。
		return err
	}

	if f, found := ctx.Server().afterMarshals[ctx.outputMimetypeName]; found {
		data = f(ctx, data)
	}

	ctx.WriteHeader(status)
	_, err = ctx.Write(data)
	return err
}

// Sprintf 返回翻译后的结果
func (ctx *Context) Sprintf(key message.Reference, v ...any) string {
	return ctx.LocalePrinter().Sprintf(key, v...)
}

func (ctx *Context) Write(bs []byte) (int, error) {
	if !ctx.wrote { // 在第一次有内容输出时，才决定构建 Encoding 和 Charset 的 io.Writer
		ctx.wrote = true

		if ctx.outputEncoding != nil {
			ctx.encodingCloser = ctx.outputEncoding.Get(ctx.respWriter)
			ctx.respWriter = ctx.encodingCloser
		}

		if !header.CharsetIsNop(ctx.outputCharset) {
			ctx.charsetCloser = transform.NewWriter(ctx.respWriter, ctx.outputCharset.NewEncoder())
			ctx.respWriter = ctx.charsetCloser
		}
	}

	return ctx.respWriter.Write(bs)
}

func (ctx *Context) WriteHeader(status int) {
	ctx.Header().Del("Content-Length") // https://github.com/golang/go/issues/14975
	ctx.status = status
	ctx.resp.WriteHeader(status)
}

func (ctx *Context) Header() http.Header { return ctx.resp.Header() }
<|endoftext|>


<commit_before>package server

import (
	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server/auth"
	"github.com/smancke/guble/server/cluster"
	"github.com/smancke/guble/store"

	log "github.com/Sirupsen/logrus"

	"fmt"
	"runtime"
	"strings"
	"sync"
	"time"
)

const (
	overloadedHandleChannelRatio = 0.9
	handleChannelCapacity        = 500
	subscribeChannelCapacity     = 10
	unsubscribeChannelCapacity   = 10
)

// Router interface provides a mechanism for PubSub messaging
type Router interface {
	AccessManager() (auth.AccessManager, error)
	MessageStore() (store.MessageStore, error)
	KVStore() (store.KVStore, error)
	Cluster() *cluster.Cluster

	Subscribe(r *Route) (*Route, error)
	Unsubscribe(r *Route)
	HandleMessage(message *protocol.Message) error
}

// Helper struct to pass `Route` to subscription channel and provide a notification channel.
type subRequest struct {
	route *Route
	doneC chan bool
}

type router struct {
	routes       map[protocol.Path][]*Route // mapping the path to the route slice
	handleC      chan *protocol.Message
	subscribeC   chan subRequest
	unsubscribeC chan subRequest
	stopC        chan bool      // Channel that signals stop of the router
	stopping     bool           // Flag: the router is in stopping process and no incoming messages are accepted
	wg           sync.WaitGroup // Add any operation that we need to wait upon here

	accessManager auth.AccessManager
	messageStore  store.MessageStore
	kvStore       store.KVStore
	cluster       *cluster.Cluster
}

// NewRouter returns a pointer to Router
func NewRouter(accessManager auth.AccessManager, messageStore store.MessageStore, kvStore store.KVStore, cluster *cluster.Cluster) Router {
	return &router{
		routes: make(map[protocol.Path][]*Route),

		handleC:      make(chan *protocol.Message, handleChannelCapacity),
		subscribeC:   make(chan subRequest, subscribeChannelCapacity),
		unsubscribeC: make(chan subRequest, unsubscribeChannelCapacity),
		stopC:        make(chan bool, 1),

		accessManager: accessManager,
		messageStore:  messageStore,
		kvStore:       kvStore,
		cluster:       cluster,
	}
}

func (router *router) Start() error {
	router.panicIfInternalDependenciesAreNil()
	resetRouterMetrics()
	logger.Info("Starting router")

	go func() {
		router.wg.Add(1)
		for {
			if router.stopping && router.channelsAreEmpty() {
				router.closeRoutes()
				router.wg.Done()
				return
			}

			func() {
				defer protocol.PanicLogger()

				select {
				case message := <-router.handleC:
					router.routeMessage(message)
					runtime.Gosched()
				case subscriber := <-router.subscribeC:
					router.subscribe(subscriber.route)
					subscriber.doneC <- true
				case unsubscriber := <-router.unsubscribeC:
					router.unsubscribe(unsubscriber.route)
					unsubscriber.doneC <- true
				case <-router.stopC:
					router.stopping = true
				}
			}()
		}
	}()

	return nil
}

// Stop stops the router by closing the stop channel, and waiting on the WaitGroup
func (router *router) Stop() error {
	logger.Info("Stopping router")

	router.stopC <- true
	router.wg.Wait()
	return nil
}

func (router *router) Check() error {
	if router.accessManager == nil || router.messageStore == nil || router.kvStore == nil {
		logger.WithField("err", ErrServiceNotProvided).Error("Some mandatory services are not provided")
		return ErrServiceNotProvided
	}
	err := router.messageStore.Check()
	if err != nil {
		logger.WithField("err", err).Error("MessageStore check failed")
		return err
	}
	err = router.kvStore.Check()
	if err != nil {
		logger.WithField("err", err).Error("KVStore check failed")
		return err
	}
	return nil
}

// HandleMessage stores the message in the MessageStore(and gets a new ID for it iff the message was created locally)
// and then passes it to: the internal channel, and asynchronously to the cluster (if available).
func (router *router) HandleMessage(message *protocol.Message) error {
	mTotalMessagesIncoming.Add(1)
	if logger.Level == log.DebugLevel {
		logger.WithFields(log.Fields{
			"userID": message.UserID,
			"path":   message.Path,
		}).Debug("HandleMessage")
	}

	if err := router.isStopping(); err != nil {
		logger.WithField("err", err).Error("Router is stopping")
		return err
	}

	if !router.accessManager.IsAllowed(auth.WRITE, message.UserID, message.Path) {
		return &PermissionDeniedError{message.UserID, auth.WRITE, message.Path}
	}

	lenMessage := int64(len(message.Bytes()))
	mTotalMessagesIncomingBytes.Add(lenMessage)

	msgPathPartition := message.Path.Partition()
	if router.cluster == nil || (router.cluster != nil && message.NodeID == router.cluster.Config.ID) {
		// for a new locally-generated message, we need to generate a new message-ID
		txCallback := func(msgId uint64) []byte {
			message.ID = msgId
			message.Time = time.Now().Unix()
			return message.Bytes()
		}
		if err := router.messageStore.StoreTx(msgPathPartition, txCallback); err != nil {
			logger.WithFields(log.Fields{
				"err":          err,
				"msgPartition": msgPathPartition,
			}).Error("Error storing new local message in partition")
			mTotalMessageStoreErrors.Add(1)
			return err
		}
	} else {
		if err := router.messageStore.Store(msgPathPartition, message.ID, message.Bytes()); err != nil {
			logger.WithFields(log.Fields{
				"err":          err,
				"msgPartition": msgPathPartition,
			}).Error("Error storing message from cluster in partition")
			mTotalMessageStoreErrors.Add(1)
			return err
		}
	}
	mTotalMessagesStoredBytes.Add(lenMessage)

	router.handleOverloadedChannel()

	router.handleC <- message

	if router.cluster != nil && message.NodeID == router.cluster.Config.ID {
		go router.cluster.BroadcastMessage(message)
	}

	return nil
}

// Subscribe adds a route to the subscribers. If there is already a route with same Application Id and Path, it will be replaced.
func (router *router) Subscribe(r *Route) (*Route, error) {
	logger.WithFields(log.Fields{
		"accessManager": router.accessManager,
		"userID":        r.UserID,
		"path":          r.Path,
	}).Debug("Subscribe")

	if err := router.isStopping(); err != nil {
		return nil, err
	}

	accessAllowed := router.accessManager.IsAllowed(auth.READ, r.UserID, r.Path)
	if !accessAllowed {
		return r, &PermissionDeniedError{UserID: r.UserID, AccessType: auth.READ, Path: r.Path}
	}
	req := subRequest{
		route: r,
		doneC: make(chan bool),
	}
	router.subscribeC <- req
	<-req.doneC
	return r, nil
}

func (router *router) Unsubscribe(r *Route) {
	logger.WithFields(log.Fields{
		"accessManager": router.accessManager,
		"userID":        r.UserID,
		"path":          r.Path,
	}).Debug("Unsubscribe")

	req := subRequest{
		route: r,
		doneC: make(chan bool),
	}
	router.unsubscribeC <- req
	<-req.doneC
}

func (router *router) subscribe(r *Route) {
	logger.WithFields(log.Fields{
		"userID": r.UserID,
		"path":   r.Path,
	}).Debug("Internal subscribe")
	mTotalSubscriptionAttempts.Add(1)

	slice, present := router.routes[r.Path]
	var removed bool
	if present {
		// Try to remove, to avoid double subscriptions of the same app
		slice, removed = removeIfMatching(slice, r)
	} else {
		// Path not present yet. Initialize the slice
		slice = make([]*Route, 0, 1)
		router.routes[r.Path] = slice
		mCurrentRoutes.Add(1)
	}
	router.routes[r.Path] = append(slice, r)
	if removed {
		mTotalDuplicateSubscriptionsAttempts.Add(1)
	} else {
		mTotalSubscriptions.Add(1)
		mCurrentSubscriptions.Add(1)
	}
}

func (router *router) unsubscribe(r *Route) {
	logger.WithFields(log.Fields{
		"userID": r.UserID,
		"path":   r.Path,
	}).Debug("Internal unsubscribe")
	mTotalUnsubscriptionAttempts.Add(1)

	slice, present := router.routes[r.Path]
	if !present {
		mTotalInvalidTopicOnUnsubscriptionAttempts.Add(1)
		return
	}
	var removed bool
	router.routes[r.Path], removed = removeIfMatching(slice, r)
	if removed {
		mTotalUnsubscriptions.Add(1)
		mCurrentSubscriptions.Add(-1)
	} else {
		mTotalInvalidUnsubscriptionAttempts.Add(1)
	}
	if len(router.routes[r.Path]) == 0 {
		delete(router.routes, r.Path)
		mCurrentRoutes.Add(-1)
	}
}

func (router *router) panicIfInternalDependenciesAreNil() {
	if router.accessManager == nil || router.kvStore == nil || router.messageStore == nil {
		panic(fmt.Sprintf("router: the internal dependencies marked with `true` are not set: AccessManager=%v, KVStore=%v, MessageStore=%v",
			router.accessManager == nil, router.kvStore == nil, router.messageStore == nil))
	}
}

func (router *router) channelsAreEmpty() bool {
	return len(router.handleC) == 0 && len(router.subscribeC) == 0 && len(router.unsubscribeC) == 0
}

func (router *router) isStopping() error {
	if router.stopping {
		return &ModuleStoppingError{"Router"}
	}
	return nil
}

func (router *router) routeMessage(message *protocol.Message) {
	mTotalMessagesRouted.Add(1)
	if logger.Level == log.DebugLevel {
		logger.WithField("msgMetadata", message.Metadata()).Debug("Called routeMessage for data")
	}

	matched := false
	for path, pathRoutes := range router.routes {
		if matchesTopic(message.Path, path) {
			matched = true
			for _, route := range pathRoutes {
				if err := route.Deliver(message); err == ErrInvalidRoute {
					router.unsubscribe(route)
				}
			}
		}
	}
	if !matched {
		mTotalMessagesNotMatchingTopic.Add(1)
	}
}

func (router *router) closeRoutes() {
	logger.Debug("Called closeRoutes")

	for _, currentRouteList := range router.routes {
		for _, route := range currentRouteList {
			router.unsubscribe(route)
			log.WithFields(log.Fields{
				"module": "router",
				"route":  route.String(),
			}).Debug("Closing route")
			route.Close()
		}
	}
}

func (router *router) handleOverloadedChannel() {
	if float32(len(router.handleC))/float32(cap(router.handleC)) > overloadedHandleChannelRatio {
		logger.WithFields(log.Fields{
			"currentLength": len(router.handleC),
			"maxCapacity":   cap(router.handleC),
		}).Warn("handleC channel is almost full")
		mTotalOverloadedHandleChannel.Add(1)
	}
}

// matchesTopic checks whether the supplied routePath matches the message topic
func matchesTopic(messagePath, routePath protocol.Path) bool {
	messagePathLen := len(string(messagePath))
	routePathLen := len(string(routePath))
	return strings.HasPrefix(string(messagePath), string(routePath)) &&
		(messagePathLen == routePathLen ||
			(messagePathLen > routePathLen && string(messagePath)[routePathLen] == '/'))
}

// removeIfMatching removes a route from the supplied list, based on same ApplicationID id and same path (if existing)
// returns: the (possibly updated) slide, and a boolean value (true if route was removed, false otherwise)
func removeIfMatching(slice []*Route, route *Route) ([]*Route, bool) {
	position := -1
	for p, r := range slice {
		if r.ApplicationID == route.ApplicationID && r.Path == route.Path {
			position = p
		}
	}
	if position == -1 {
		return slice, false
	}
	return append(slice[:position], slice[position+1:]...), true
}

// AccessManager returns the `accessManager` provided for the router
func (router *router) AccessManager() (auth.AccessManager, error) {
	if router.accessManager == nil {
		return nil, ErrServiceNotProvided
	}
	return router.accessManager, nil
}

// MessageStore returns the `messageStore` provided for the router
func (router *router) MessageStore() (store.MessageStore, error) {
	if router.messageStore == nil {
		return nil, ErrServiceNotProvided
	}
	return router.messageStore, nil
}

// KVStore returns the `kvStore` provided for the router
func (router *router) KVStore() (store.KVStore, error) {
	if router.kvStore == nil {
		return nil, ErrServiceNotProvided
	}
	return router.kvStore, nil
}

// Cluster returns the `cluster` provided for the router, or nil if no cluster was set-up
func (router *router) Cluster() *cluster.Cluster {
	return router.cluster
}
<commit_msg>cleanup in logging<commit_after>package server

import (
	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server/auth"
	"github.com/smancke/guble/server/cluster"
	"github.com/smancke/guble/store"

	log "github.com/Sirupsen/logrus"

	"fmt"
	"runtime"
	"strings"
	"sync"
	"time"
)

const (
	overloadedHandleChannelRatio = 0.9
	handleChannelCapacity        = 500
	subscribeChannelCapacity     = 10
	unsubscribeChannelCapacity   = 10
)

// Router interface provides a mechanism for PubSub messaging
type Router interface {
	AccessManager() (auth.AccessManager, error)
	MessageStore() (store.MessageStore, error)
	KVStore() (store.KVStore, error)
	Cluster() *cluster.Cluster

	Subscribe(r *Route) (*Route, error)
	Unsubscribe(r *Route)
	HandleMessage(message *protocol.Message) error
}

// Helper struct to pass `Route` to subscription channel and provide a notification channel.
type subRequest struct {
	route *Route
	doneC chan bool
}

type router struct {
	routes       map[protocol.Path][]*Route // mapping the path to the route slice
	handleC      chan *protocol.Message
	subscribeC   chan subRequest
	unsubscribeC chan subRequest
	stopC        chan bool      // Channel that signals stop of the router
	stopping     bool           // Flag: the router is in stopping process and no incoming messages are accepted
	wg           sync.WaitGroup // Add any operation that we need to wait upon here

	accessManager auth.AccessManager
	messageStore  store.MessageStore
	kvStore       store.KVStore
	cluster       *cluster.Cluster
}

// NewRouter returns a pointer to Router
func NewRouter(accessManager auth.AccessManager, messageStore store.MessageStore, kvStore store.KVStore, cluster *cluster.Cluster) Router {
	return &router{
		routes: make(map[protocol.Path][]*Route),

		handleC:      make(chan *protocol.Message, handleChannelCapacity),
		subscribeC:   make(chan subRequest, subscribeChannelCapacity),
		unsubscribeC: make(chan subRequest, unsubscribeChannelCapacity),
		stopC:        make(chan bool, 1),

		accessManager: accessManager,
		messageStore:  messageStore,
		kvStore:       kvStore,
		cluster:       cluster,
	}
}

func (router *router) Start() error {
	router.panicIfInternalDependenciesAreNil()
	resetRouterMetrics()
	logger.Info("Starting router")

	go func() {
		router.wg.Add(1)
		for {
			if router.stopping && router.channelsAreEmpty() {
				router.closeRoutes()
				router.wg.Done()
				return
			}

			func() {
				defer protocol.PanicLogger()

				select {
				case message := <-router.handleC:
					router.routeMessage(message)
					runtime.Gosched()
				case subscriber := <-router.subscribeC:
					router.subscribe(subscriber.route)
					subscriber.doneC <- true
				case unsubscriber := <-router.unsubscribeC:
					router.unsubscribe(unsubscriber.route)
					unsubscriber.doneC <- true
				case <-router.stopC:
					router.stopping = true
				}
			}()
		}
	}()

	return nil
}

// Stop stops the router by closing the stop channel, and waiting on the WaitGroup
func (router *router) Stop() error {
	logger.Info("Stopping router")

	router.stopC <- true
	router.wg.Wait()
	return nil
}

func (router *router) Check() error {
	if router.accessManager == nil || router.messageStore == nil || router.kvStore == nil {
		logger.WithField("err", ErrServiceNotProvided).Error("Some mandatory services are not provided")
		return ErrServiceNotProvided
	}
	err := router.messageStore.Check()
	if err != nil {
		logger.WithField("err", err).Error("MessageStore check failed")
		return err
	}
	err = router.kvStore.Check()
	if err != nil {
		logger.WithField("err", err).Error("KVStore check failed")
		return err
	}
	return nil
}

// HandleMessage stores the message in the MessageStore(and gets a new ID for it iff the message was created locally)
// and then passes it to: the internal channel, and asynchronously to the cluster (if available).
func (router *router) HandleMessage(message *protocol.Message) error {
	mTotalMessagesIncoming.Add(1)
	logger.WithFields(log.Fields{
		"userID": message.UserID,
		"path":   message.Path,
	}).Debug("HandleMessage")

	if err := router.isStopping(); err != nil {
		logger.WithField("err", err).Error("Router is stopping")
		return err
	}

	if !router.accessManager.IsAllowed(auth.WRITE, message.UserID, message.Path) {
		return &PermissionDeniedError{message.UserID, auth.WRITE, message.Path}
	}

	lenMessage := int64(len(message.Bytes()))
	mTotalMessagesIncomingBytes.Add(lenMessage)

	msgPathPartition := message.Path.Partition()
	if router.cluster == nil || (router.cluster != nil && message.NodeID == router.cluster.Config.ID) {
		// for a new locally-generated message, we need to generate a new message-ID
		txCallback := func(msgId uint64) []byte {
			message.ID = msgId
			message.Time = time.Now().Unix()
			return message.Bytes()
		}
		if err := router.messageStore.StoreTx(msgPathPartition, txCallback); err != nil {
			logger.WithFields(log.Fields{
				"err":          err,
				"msgPartition": msgPathPartition,
			}).Error("Error storing new local message in partition")
			mTotalMessageStoreErrors.Add(1)
			return err
		}
	} else {
		if err := router.messageStore.Store(msgPathPartition, message.ID, message.Bytes()); err != nil {
			logger.WithFields(log.Fields{
				"err":          err,
				"msgPartition": msgPathPartition,
			}).Error("Error storing message from cluster in partition")
			mTotalMessageStoreErrors.Add(1)
			return err
		}
	}
	mTotalMessagesStoredBytes.Add(lenMessage)

	router.handleOverloadedChannel()

	router.handleC <- message

	if router.cluster != nil && message.NodeID == router.cluster.Config.ID {
		go router.cluster.BroadcastMessage(message)
	}

	return nil
}

// Subscribe adds a route to the subscribers. If there is already a route with same Application Id and Path, it will be replaced.
func (router *router) Subscribe(r *Route) (*Route, error) {
	logger.WithFields(log.Fields{
		"accessManager": router.accessManager,
		"userID":        r.UserID,
		"path":          r.Path,
	}).Debug("Subscribe")
	if err := router.isStopping(); err != nil {
		return nil, err
	}

	accessAllowed := router.accessManager.IsAllowed(auth.READ, r.UserID, r.Path)
	if !accessAllowed {
		return r, &PermissionDeniedError{UserID: r.UserID, AccessType: auth.READ, Path: r.Path}
	}
	req := subRequest{
		route: r,
		doneC: make(chan bool),
	}
	router.subscribeC <- req
	<-req.doneC
	return r, nil
}

func (router *router) Unsubscribe(r *Route) {
	logger.WithFields(log.Fields{
		"accessManager": router.accessManager,
		"userID":        r.UserID,
		"path":          r.Path,
	}).Debug("Unsubscribe")

	req := subRequest{
		route: r,
		doneC: make(chan bool),
	}
	router.unsubscribeC <- req
	<-req.doneC
}

func (router *router) subscribe(r *Route) {
	mTotalSubscriptionAttempts.Add(1)
	logger.WithFields(log.Fields{
		"userID": r.UserID,
		"path":   r.Path,
	}).Debug("Internal subscribe")

	slice, present := router.routes[r.Path]
	var removed bool
	if present {
		// Try to remove, to avoid double subscriptions of the same app
		slice, removed = removeIfMatching(slice, r)
	} else {
		// Path not present yet. Initialize the slice
		slice = make([]*Route, 0, 1)
		router.routes[r.Path] = slice
		mCurrentRoutes.Add(1)
	}
	router.routes[r.Path] = append(slice, r)
	if removed {
		mTotalDuplicateSubscriptionsAttempts.Add(1)
	} else {
		mTotalSubscriptions.Add(1)
		mCurrentSubscriptions.Add(1)
	}
}

func (router *router) unsubscribe(r *Route) {
	mTotalUnsubscriptionAttempts.Add(1)
	logger.WithFields(log.Fields{
		"userID": r.UserID,
		"path":   r.Path,
	}).Debug("Internal unsubscribe")

	slice, present := router.routes[r.Path]
	if !present {
		mTotalInvalidTopicOnUnsubscriptionAttempts.Add(1)
		return
	}
	var removed bool
	router.routes[r.Path], removed = removeIfMatching(slice, r)
	if removed {
		mTotalUnsubscriptions.Add(1)
		mCurrentSubscriptions.Add(-1)
	} else {
		mTotalInvalidUnsubscriptionAttempts.Add(1)
	}
	if len(router.routes[r.Path]) == 0 {
		delete(router.routes, r.Path)
		mCurrentRoutes.Add(-1)
	}
}

func (router *router) panicIfInternalDependenciesAreNil() {
	if router.accessManager == nil || router.kvStore == nil || router.messageStore == nil {
		panic(fmt.Sprintf("router: the internal dependencies marked with `true` are not set: AccessManager=%v, KVStore=%v, MessageStore=%v",
			router.accessManager == nil, router.kvStore == nil, router.messageStore == nil))
	}
}

func (router *router) channelsAreEmpty() bool {
	return len(router.handleC) == 0 && len(router.subscribeC) == 0 && len(router.unsubscribeC) == 0
}

func (router *router) isStopping() error {
	if router.stopping {
		return &ModuleStoppingError{"Router"}
	}
	return nil
}

func (router *router) routeMessage(message *protocol.Message) {
	mTotalMessagesRouted.Add(1)
	if logger.Level == log.DebugLevel {
		logger.WithField("msgMetadata", message.Metadata()).Debug("routeMessage")
	}

	matched := false
	for path, pathRoutes := range router.routes {
		if matchesTopic(message.Path, path) {
			matched = true
			for _, route := range pathRoutes {
				if err := route.Deliver(message); err == ErrInvalidRoute {
					router.unsubscribe(route)
				}
			}
		}
	}
	if !matched {
		mTotalMessagesNotMatchingTopic.Add(1)
	}
}

func (router *router) closeRoutes() {
	logger.Info("closeRoutes in router")

	for _, currentRouteList := range router.routes {
		for _, route := range currentRouteList {
			router.unsubscribe(route)
			logger.WithField("route", route.String()).Debug("Closing route")
			route.Close()
		}
	}
}

func (router *router) handleOverloadedChannel() {
	if float32(len(router.handleC))/float32(cap(router.handleC)) > overloadedHandleChannelRatio {
		logger.WithFields(log.Fields{
			"currentLength": len(router.handleC),
			"maxCapacity":   cap(router.handleC),
		}).Warn("handleC channel is almost full")
		mTotalOverloadedHandleChannel.Add(1)
	}
}

// matchesTopic checks whether the supplied routePath matches the message topic
func matchesTopic(messagePath, routePath protocol.Path) bool {
	messagePathLen := len(string(messagePath))
	routePathLen := len(string(routePath))
	return strings.HasPrefix(string(messagePath), string(routePath)) &&
		(messagePathLen == routePathLen ||
			(messagePathLen > routePathLen && string(messagePath)[routePathLen] == '/'))
}

// removeIfMatching removes a route from the supplied list, based on same ApplicationID id and same path (if existing)
// returns: the (possibly updated) slide, and a boolean value (true if route was removed, false otherwise)
func removeIfMatching(slice []*Route, route *Route) ([]*Route, bool) {
	position := -1
	for p, r := range slice {
		if r.ApplicationID == route.ApplicationID && r.Path == route.Path {
			position = p
		}
	}
	if position == -1 {
		return slice, false
	}
	return append(slice[:position], slice[position+1:]...), true
}

// AccessManager returns the `accessManager` provided for the router
func (router *router) AccessManager() (auth.AccessManager, error) {
	if router.accessManager == nil {
		return nil, ErrServiceNotProvided
	}
	return router.accessManager, nil
}

// MessageStore returns the `messageStore` provided for the router
func (router *router) MessageStore() (store.MessageStore, error) {
	if router.messageStore == nil {
		return nil, ErrServiceNotProvided
	}
	return router.messageStore, nil
}

// KVStore returns the `kvStore` provided for the router
func (router *router) KVStore() (store.KVStore, error) {
	if router.kvStore == nil {
		return nil, ErrServiceNotProvided
	}
	return router.kvStore, nil
}

// Cluster returns the `cluster` provided for the router, or nil if no cluster was set-up
func (router *router) Cluster() *cluster.Cluster {
	return router.cluster
}
<|endoftext|>


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

import (
	"bytes"
	"testing"

	"github.com/davecgh/go-spew/spew"
	"github.com/stretchr/testify/assert"
)

func TestParse(t *testing.T) {
	samples := []struct {
		text   string
		syntax Rule
		tree   *Tree
		err    error
	}{
		{
			"",
			Terminal(""),
			&Tree{
				Rule:  Terminal(""),
				Data:  []byte{},
				Start: 1,
				End:   1,
			},
			nil,
		},
		{
			"foo",
			Terminal("foo"),
			&Tree{
				Rule:  Terminal("foo"),
				Data:  []byte("foo"),
				Start: 1,
				End:   4,
			},
			nil,
		},
		{
			"bar",
			Terminal("foo"),
			nil,
			NewErrUnexpectedToken([]byte("bar"), 1),
		},
		{
			"foobar",
			Terminal("foo"),
			nil,
			NewErrUnexpectedToken([]byte("b"), 4),
		},
	}

	for k, sample := range samples {
		reader := bytes.NewBufferString(sample.text)
		tree, err := Parse(sample.syntax, reader)

		msg := spew.Sdump(k, sample)
		assert.Equal(t, sample.err, err, msg)
		assert.Equal(t, sample.tree, tree, msg)
	}
}
<commit_msg>Added a fixme to get rid of implementation dependant things<commit_after>// Copyright © 2017 Dmitry Moskowski
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package parse

import (
	"bytes"
	"testing"

	"github.com/davecgh/go-spew/spew"
	"github.com/stretchr/testify/assert"
)

func TestParse(t *testing.T) {
	samples := []struct {
		text   string
		syntax Rule
		tree   *Tree
		err    error
	}{
		{
			"",
			Terminal(""),
			&Tree{
				Rule:  Terminal(""),
				Data:  []byte{},
				Start: 1,
				End:   1,
			},
			nil,
		},
		{
			"foo",
			Terminal("foo"),
			&Tree{
				Rule:  Terminal("foo"),
				Data:  []byte("foo"),
				Start: 1,
				End:   4,
			},
			nil,
		},
		{
			"bar",
			Terminal("foo"),
			nil,
			// FIXME: Probably it should fail with 'b'
			// The fact that it fails with 'bar' is a
			// implementation detail
			NewErrUnexpectedToken([]byte("bar"), 1),
		},
		{
			"foobar",
			Terminal("foo"),
			nil,
			NewErrUnexpectedToken([]byte("b"), 4),
		},
	}

	for k, sample := range samples {
		reader := bytes.NewBufferString(sample.text)
		tree, err := Parse(sample.syntax, reader)

		msg := spew.Sdump(k, sample)
		assert.Equal(t, sample.err, err, msg)
		assert.Equal(t, sample.tree, tree, msg)
	}
}
<|endoftext|>


<commit_before>package narcissus

import (
	"testing"

	"honnef.co/go/augeas"
)

type foo struct {
	augeasPath string
	A          string `path:"a"`
}

type bar struct{}

func TestParseNotAPtr(t *testing.T) {
	aug, err := augeas.New("", "", augeas.None)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(foo{
		augeasPath: "/files/some/path",
	})

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "not a ptr" {
		t.Errorf("Expected error not a ptr, got %s", err.Error())
	}
}

func TestParseNotAStruct(t *testing.T) {
	aug, err := augeas.New("", "", augeas.None)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	f := "foo"
	err = n.Parse(&f)

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "not a struct" {
		t.Errorf("Expected error not a struct, got %s", err.Error())
	}
}

func TestParseFieldNotFound(t *testing.T) {
	aug, err := augeas.New("", "", augeas.None)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(&foo{
		augeasPath: "/files/some/path",
	})

	t.Skip("Fix this")

	if err == nil {
		t.Error("Expected an error, got nothing")
	}
}

func TestNoAugeasPathValue(t *testing.T) {
	aug, err := augeas.New("", "", augeas.None)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(&foo{})

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "no augeasPath value and no default" {
		t.Errorf("Expected error no augeasPath value and no default, got %s", err.Error())
	}
}

func TestNoAugeasPathField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.None)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(&bar{})

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "no augeasPath field" {
		t.Errorf("Expected error no augeasPath field, got %s", err.Error())
	}
}

type simpleValues struct {
	augeasPath string
	Str        string `path:"str"`
	Int        int    `path:"int"`
	Bool       bool   `path:"bool"`
}

func TestGetStringField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.None)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/str", "foo")
	n.Augeas.Set("/test/int", "42")
	n.Augeas.Set("/test/bool", "true")
	n.Augeas.Set("/test/slstr[1]", "a")
	n.Augeas.Set("/test/slstr[2]", "b")
	n.Augeas.Set("/test/slint[1]", "1")
	n.Augeas.Set("/test/slint[2]", "2")
	n.Augeas.Set("/test/slbool[1]", "true")
	n.Augeas.Set("/test/slbool[2]", "false")
	s := &simpleValues{
		augeasPath: "/test",
	}
	err = n.Parse(s)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if s.Str != "foo" {
		t.Errorf("Expected foo, got %s", s.Str)
	}

	if s.Int != 42 {
		t.Errorf("Expected 42, got %v", s.Int)
	}

	if s.Bool != true {
		t.Errorf("Expected true, got %v", s.Bool)
	}
}

type sliceValues struct {
	augeasPath string
	SlStr      []string `path:"slstr"`
	SlInt      []int    `path:"slint"`
	SlBool     []bool   `path:"slbool"`
	SlStrSeq   []string `type:"seq"`
}

func TestGetSliceField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.None)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/slstr[1]", "a")
	n.Augeas.Set("/test/slstr[2]", "b")
	n.Augeas.Set("/test/slint[1]", "1")
	n.Augeas.Set("/test/slint[2]", "2")
	n.Augeas.Set("/test/slbool[1]", "true")
	n.Augeas.Set("/test/slbool[2]", "false")
	n.Augeas.Set("/test/1", "foo")
	n.Augeas.Set("/test/2", "bar")
	s := &sliceValues{
		augeasPath: "/test",
	}
	err = n.Parse(s)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if len(s.SlStr) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlStr))
	}
	if s.SlStr[1] != "b" {
		t.Errorf("Expected element to be b, got %s", s.SlStr[1])
	}

	if len(s.SlInt) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlInt))
	}
	if s.SlInt[1] != 2 {
		t.Errorf("Expected element to be 2, got %v", s.SlInt[1])
	}

	if len(s.SlBool) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlBool))
	}
	if s.SlBool[0] != true {
		t.Errorf("Expected element to be true, got %v", s.SlBool[0])
	}
	if s.SlBool[1] != false {
		t.Errorf("Expected element to be false, got %v", s.SlBool[1])
	}

	if len(s.SlStrSeq) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlStrSeq))
	}
	if s.SlStrSeq[1] != "bar" {
		t.Errorf("Expected element to be bar, got %s", s.SlStrSeq[1])
	}
}

type mapValues struct {
	augeasPath string
	Entries    map[string]struct {
		Str   string   `path:"str"`
		Int   int      `path:"int"`
		Bool  bool     `path:"bool"`
		SlStr []string `path:"slstr"`
	} `path:"mstruct"`
}

func TestGetMapField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.None)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/mstruct[1]", "one")
	n.Augeas.Set("/test/mstruct[1]/str", "a")
	n.Augeas.Set("/test/mstruct[1]/int", "42")
	n.Augeas.Set("/test/mstruct[1]/bool", "true")
	n.Augeas.Set("/test/mstruct[1]/slstr[1]", "alpha")
	n.Augeas.Set("/test/mstruct[1]/slstr[2]", "beta")
	n.Augeas.Set("/test/mstruct[2]", "two")
	n.Augeas.Set("/test/mstruct[2]/str", "b")
	n.Augeas.Set("/test/mstruct[2]/int", "43")
	n.Augeas.Set("/test/mstruct[2]/bool", "false")
	n.Augeas.Set("/test/mstruct[2]/slstr[1]", "gamma")
	n.Augeas.Set("/test/mstruct[2]/slstr[2]", "delta")
	m := &mapValues{
		augeasPath: "/test",
	}
	err = n.Parse(m)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if len(m.Entries) != 2 {
		t.Errorf("Expected 2 entries, got %v", len(m.Entries))
	}
	if m.Entries["two"].Str != "b" {
		t.Errorf("Expected element to be b, got %s", m.Entries["two"].Str)
	}
}
<commit_msg>Test failed conversions<commit_after>package narcissus

import (
	"testing"

	"honnef.co/go/augeas"
)

type foo struct {
	augeasPath string
	A          string `path:"a"`
}

type bar struct{}

func TestParseNotAPtr(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(foo{
		augeasPath: "/files/some/path",
	})

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "not a ptr" {
		t.Errorf("Expected error not a ptr, got %s", err.Error())
	}
}

func TestParseNotAStruct(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	f := "foo"
	err = n.Parse(&f)

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "not a struct" {
		t.Errorf("Expected error not a struct, got %s", err.Error())
	}
}

func TestParseFieldNotFound(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(&foo{
		augeasPath: "/files/some/path",
	})

	t.Skip("Fix this")

	if err == nil {
		t.Error("Expected an error, got nothing")
	}
}

func TestNoAugeasPathValue(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(&foo{})

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "no augeasPath value and no default" {
		t.Errorf("Expected error no augeasPath value and no default, got %s", err.Error())
	}
}

func TestNoAugeasPathField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	err = n.Parse(&bar{})

	if err == nil {
		t.Error("Expected an error, got nothing")
	}

	if err.Error() != "no augeasPath field" {
		t.Errorf("Expected error no augeasPath field, got %s", err.Error())
	}
}

type simpleValues struct {
	augeasPath string
	Str        string `path:"str"`
	Int        int    `path:"int"`
	Bool       bool   `path:"bool"`
}

func TestGetSimpleField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/str", "foo")
	n.Augeas.Set("/test/int", "42")
	n.Augeas.Set("/test/bool", "true")
	n.Augeas.Set("/test/slstr[1]", "a")
	n.Augeas.Set("/test/slstr[2]", "b")
	n.Augeas.Set("/test/slint[1]", "1")
	n.Augeas.Set("/test/slint[2]", "2")
	n.Augeas.Set("/test/slbool[1]", "true")
	n.Augeas.Set("/test/slbool[2]", "false")
	s := &simpleValues{
		augeasPath: "/test",
	}
	err = n.Parse(s)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if s.Str != "foo" {
		t.Errorf("Expected foo, got %s", s.Str)
	}

	if s.Int != 42 {
		t.Errorf("Expected 42, got %v", s.Int)
	}

	if s.Bool != true {
		t.Errorf("Expected true, got %v", s.Bool)
	}
}

func TestGetSimpleFieldWrongTypes(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/int", "a")
	s := &simpleValues{
		augeasPath: "/test",
	}
	err = n.Parse(s)

	if err == nil {
		t.Error("Expected an error, got nil")
	}

	if err.Error() != "failed to retrieve field Int: failed to convert a to int: strconv.ParseInt: parsing \"a\": invalid syntax" {
		t.Errorf("Expected int conversion error, got %v", err)
	}

	n.Augeas.Remove("/test/int")
	n.Augeas.Set("/test/bool", "a")
	err = n.Parse(s)

	if err == nil {
		t.Error("Expected an error, got nil")
	}

	if err.Error() != "failed to retrieve field Bool: failed to convert a to bool: strconv.ParseBool: parsing \"a\": invalid syntax" {
		t.Errorf("Expected bool conversion error, got %v", err)
	}
}

type sliceValues struct {
	augeasPath string
	SlStr      []string `path:"slstr"`
	SlInt      []int    `path:"slint"`
	SlBool     []bool   `path:"slbool"`
	SlStrSeq   []string `type:"seq"`
}

func TestGetSliceField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/slstr[1]", "a")
	n.Augeas.Set("/test/slstr[2]", "b")
	n.Augeas.Set("/test/slint[1]", "1")
	n.Augeas.Set("/test/slint[2]", "2")
	n.Augeas.Set("/test/slbool[1]", "true")
	n.Augeas.Set("/test/slbool[2]", "false")
	n.Augeas.Set("/test/1", "foo")
	n.Augeas.Set("/test/2", "bar")
	s := &sliceValues{
		augeasPath: "/test",
	}
	err = n.Parse(s)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if len(s.SlStr) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlStr))
	}
	if s.SlStr[1] != "b" {
		t.Errorf("Expected element to be b, got %s", s.SlStr[1])
	}

	if len(s.SlInt) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlInt))
	}
	if s.SlInt[1] != 2 {
		t.Errorf("Expected element to be 2, got %v", s.SlInt[1])
	}

	if len(s.SlBool) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlBool))
	}
	if s.SlBool[0] != true {
		t.Errorf("Expected element to be true, got %v", s.SlBool[0])
	}
	if s.SlBool[1] != false {
		t.Errorf("Expected element to be false, got %v", s.SlBool[1])
	}

	if len(s.SlStrSeq) != 2 {
		t.Errorf("Expected 2 elements, got %v", len(s.SlStrSeq))
	}
	if s.SlStrSeq[1] != "bar" {
		t.Errorf("Expected element to be bar, got %s", s.SlStrSeq[1])
	}
}

type mapValues struct {
	augeasPath string
	Entries    map[string]struct {
		Str   string   `path:"str"`
		Int   int      `path:"int"`
		Bool  bool     `path:"bool"`
		SlStr []string `path:"slstr"`
	} `path:"mstruct"`
}

func TestGetMapField(t *testing.T) {
	aug, err := augeas.New("", "", augeas.NoModlAutoload)
	if err != nil {
		t.Fatal("Failed to create Augeas handler")
	}
	n := New(&aug)
	n.Augeas.Set("/test/mstruct[1]", "one")
	n.Augeas.Set("/test/mstruct[1]/str", "a")
	n.Augeas.Set("/test/mstruct[1]/int", "42")
	n.Augeas.Set("/test/mstruct[1]/bool", "true")
	n.Augeas.Set("/test/mstruct[1]/slstr[1]", "alpha")
	n.Augeas.Set("/test/mstruct[1]/slstr[2]", "beta")
	n.Augeas.Set("/test/mstruct[2]", "two")
	n.Augeas.Set("/test/mstruct[2]/str", "b")
	n.Augeas.Set("/test/mstruct[2]/int", "43")
	n.Augeas.Set("/test/mstruct[2]/bool", "false")
	n.Augeas.Set("/test/mstruct[2]/slstr[1]", "gamma")
	n.Augeas.Set("/test/mstruct[2]/slstr[2]", "delta")
	m := &mapValues{
		augeasPath: "/test",
	}
	err = n.Parse(m)

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}

	if len(m.Entries) != 2 {
		t.Errorf("Expected 2 entries, got %v", len(m.Entries))
	}
	if m.Entries["two"].Str != "b" {
		t.Errorf("Expected element to be b, got %s", m.Entries["two"].Str)
	}
}
<|endoftext|>


<commit_before>package server

import (
	"io"
	"log"
	"net"

	"github.com/vito/garden/messagereader"
	protocol "github.com/vito/garden/protocol"
)

func Start(socketPath string) error {
	listener, err := net.Listen("unix", socketPath)
	if err != nil {
		return err
	}

	go handleConnections(listener)

	return nil
}

func handleConnections(listener net.Listener) {
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println("error accepting connection:", err)
			continue
		}

		go serveConnection(conn)
	}
}

func serveConnection(conn net.Conn) {
	for {
		request, err := messagereader.ReadRequest(conn)
		if err == io.EOF {
			break
		}

		if err != nil {
			log.Println("error reading request:", err)
			continue
		}

		switch request.(type) {
		case *protocol.PingRequest:
			protocol.Messages(&protocol.PingResponse{}).WriteTo(conn)
		case *protocol.EchoRequest:
			protocol.Messages(&protocol.EchoResponse{
				Message: request.(*protocol.EchoRequest).Message,
			}).WriteTo(conn)
		}
	}
}
<commit_msg>tiny refactor<commit_after>package server

import (
	"io"
	"log"
	"net"

	"code.google.com/p/gogoprotobuf/proto"

	"github.com/vito/garden/messagereader"
	protocol "github.com/vito/garden/protocol"
)

func Start(socketPath string) error {
	listener, err := net.Listen("unix", socketPath)
	if err != nil {
		return err
	}

	go handleConnections(listener)

	return nil
}

func handleConnections(listener net.Listener) {
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println("error accepting connection:", err)
			continue
		}

		go serveConnection(conn)
	}
}

func serveConnection(conn net.Conn) {
	for {
		request, err := messagereader.ReadRequest(conn)
		if err == io.EOF {
			break
		}

		if err != nil {
			log.Println("error reading request:", err)
			continue
		}

		var response proto.Message

		switch request.(type) {
		case *protocol.PingRequest:
			response = &protocol.PingResponse{}
		case *protocol.EchoRequest:
			response = &protocol.EchoResponse{
				Message: request.(*protocol.EchoRequest).Message,
			}
		default:
			log.Println("unhandled request:", request)
			continue
		}

		protocol.Messages(response).WriteTo(conn)
	}
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"github.com/bivas/rivi/bot"
	"gopkg.in/gin-gonic/gin.v1"
	"strings"
)

type BotServer struct {
	Bot  bot.Bot
	Uri  string
	Port int
}

func (server *BotServer) initDefaults() {
	if server.Port == 0 {
		server.Port = 8080
	}
	if server.Uri == "" {
		server.Uri = "/"
	} else if !strings.HasPrefix(server.Uri, "/") {
		server.Uri = "/" + server.Uri
	}
}

func (server *BotServer) Run() error {
	gin.SetMode(gin.ReleaseMode)
	engine := gin.Default()
	engine.GET("/", func(c *gin.Context) {
		c.String(200, "Running RiviBot")
	})
	if server.Uri != "/" {
		engine.GET(server.Uri, func(c *gin.Context) {
			c.String(200, "Running RiviBot")
		})
	}
	engine.POST(server.Uri, func(c *gin.Context) {
		server.Bot.HandleEvent(c.Request)
		c.Status(200)
	})
	if server.Port == 0 {
		server.Port = 8080
	}
	return engine.Run(fmt.Sprintf(":%d", server.Port))
}
<commit_msg>remove double check<commit_after>package server

import (
	"fmt"
	"github.com/bivas/rivi/bot"
	"gopkg.in/gin-gonic/gin.v1"
	"strings"
)

type BotServer struct {
	Bot  bot.Bot
	Uri  string
	Port int
}

func (server *BotServer) initDefaults() {
	if server.Port == 0 {
		server.Port = 8080
	}
	if server.Uri == "" {
		server.Uri = "/"
	} else if !strings.HasPrefix(server.Uri, "/") {
		server.Uri = "/" + server.Uri
	}
}

func (server *BotServer) Run() error {
	gin.SetMode(gin.ReleaseMode)
	engine := gin.Default()
	engine.GET("/", func(c *gin.Context) {
		c.String(200, "Running RiviBot")
	})
	if server.Uri != "/" {
		engine.GET(server.Uri, func(c *gin.Context) {
			c.String(200, "Running RiviBot")
		})
	}
	engine.POST(server.Uri, func(c *gin.Context) {
		server.Bot.HandleEvent(c.Request)
		c.Status(200)
	})
	return engine.Run(fmt.Sprintf(":%d", server.Port))
}
<|endoftext|>


<commit_before>package server

import (
	"errors"
	"log"
	"net"
	"net/url"
	"sync"
	"time"

	"github.com/koron/go-mqtt/internal/backoff"
	"github.com/koron/go-mqtt/packet"
)

var (
	// ErrInvalidCloudAdapter indicates Adapter#Connect() returns invalid CloudAdapter.
	ErrInvalidCloudAdapter = errors.New("invalid CloudAdapter")
)

// Server is a instance of MQTT server.
type Server struct {
	Addr    string
	Adapter Adapter
	Options *Options

	logger   *log.Logger
	quit     chan bool
	listener net.Listener
	wg       sync.WaitGroup // for client#serve()
	cl       sync.Mutex
	cs       map[*client]bool
}

func (srv *Server) addr() string {
	if srv.Addr == "" {
		return "tcp://127.0.0.1:1883"
	}
	return srv.Addr
}

func (srv *Server) options() *Options {
	if srv.Options == nil {
		return DefaultOptions
	}
	return srv.Options
}

// ListenAndServe listens on the TCP network address
func (srv *Server) ListenAndServe() error {
	u, err := url.Parse(srv.addr())
	if err != nil {
		return err
	}
	l, err := net.Listen(u.Scheme, u.Host)
	if err != nil {
		return err
	}
	return srv.Serve(l)
}

// Serve accepts incoming connections on the Listener.
func (srv *Server) Serve(l net.Listener) error {
	defer l.Close()
	srv.logger = srv.options().Logger
	srv.quit = make(chan bool, 1)
	srv.listener = l
	srv.wg = sync.WaitGroup{}
	srv.cs = make(map[*client]bool)
	srv.logServerStart(l)
	delay := backoff.Exp{Min: time.Millisecond * 5}
	for {
		conn, err := l.Accept()
		select {
		case <-srv.quit:
			return nil
		default:
		}
		if err != nil {
			if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
				srv.logTemporaryError(nerr, &delay, nil)
				delay.Wait()
				continue
			}
			return err
		}
		delay.Reset()

		// start client goroutine.
		c := newClient(srv, conn)
		srv.wg.Add(1)
		go func() {
			c.serve()
			srv.wg.Done()
		}()
	}
}

// Close terminates the server by shutting down all the client connections and
// closing.
func (srv *Server) Close() error {
	// terminate server.
	close(srv.quit)
	srv.listener.Close()
	// terminate all clients
	srv.cl.Lock()
	for c := range srv.cs {
		c.terminate()
	}
	srv.cl.Unlock()
	// wait to terminate all clients.
	srv.wg.Wait()
	return nil
}

func (srv *Server) logf(fmt string, a ...interface{}) {
	if srv.logger == nil {
		return
	}
	srv.logger.Printf(fmt, a...)
}

func (srv *Server) logServerStart(l net.Listener) {
	srv.logf("server starts to listen: %s\n", l.Addr().String())
}

func (srv *Server) logTemporaryError(err net.Error, d *backoff.Exp, c *client) {
	if c != nil {
		srv.logf("client;%s detect temporary error: %v", c.id(), err)
		return
	}
	srv.logf("server detect temporary error: %v", err)
}

// logAdapterError logs continuable AdapterError
func (srv *Server) logAdapterError(err AdapterError, p packet.Packet, c *client) {
	srv.logf("client:%s rejects packet:%#v but continue: %s",
		c.id(), p, err.Error())
}

func (srv *Server) logEstablishFailure(c *client, err error) {
	srv.logf("client fails to connect: %v", err)
}

func (srv *Server) logSendPacketError(c *client, p packet.Packet, err error) {
	srv.logf("failed to send packet;%#v to client;%s: %v", c.id, p, err)
}

func (srv *Server) adapter() Adapter {
	if srv.Adapter == nil {
		return DefaultAdapter
	}
	return srv.Adapter
}

func (srv *Server) clientOnConnect(c *client, p *packet.Connect) (ClientAdapter, error) {
	ca, err := srv.adapter().Connect(srv, c, p)
	if err != nil {
		return nil, err
	}
	if ca == nil {
		return nil, ErrInvalidCloudAdapter
	}
	return ca, nil
}

func (srv *Server) clientOnStart(c *client) {
	srv.cl.Lock()
	defer srv.cl.Unlock()
	srv.cs[c] = true
}

func (srv *Server) clientOnStop(c *client) {
	srv.cl.Lock()
	defer srv.cl.Unlock()
	delete(srv.cs, c)
}

func (srv *Server) clientOnDisconnect(c *client, err error) {
	srv.adapter().Disconnect(srv, c.ca, err)
}
<commit_msg>replace local variable l with srv.listener<commit_after>package server

import (
	"errors"
	"log"
	"net"
	"net/url"
	"sync"
	"time"

	"github.com/koron/go-mqtt/internal/backoff"
	"github.com/koron/go-mqtt/packet"
)

var (
	// ErrInvalidCloudAdapter indicates Adapter#Connect() returns invalid CloudAdapter.
	ErrInvalidCloudAdapter = errors.New("invalid CloudAdapter")
)

// Server is a instance of MQTT server.
type Server struct {
	Addr    string
	Adapter Adapter
	Options *Options

	logger   *log.Logger
	quit     chan bool
	listener net.Listener
	wg       sync.WaitGroup // for client#serve()
	cl       sync.Mutex
	cs       map[*client]bool
}

func (srv *Server) addr() string {
	if srv.Addr == "" {
		return "tcp://127.0.0.1:1883"
	}
	return srv.Addr
}

func (srv *Server) options() *Options {
	if srv.Options == nil {
		return DefaultOptions
	}
	return srv.Options
}

// ListenAndServe listens on the TCP network address
func (srv *Server) ListenAndServe() error {
	u, err := url.Parse(srv.addr())
	if err != nil {
		return err
	}
	l, err := net.Listen(u.Scheme, u.Host)
	if err != nil {
		return err
	}
	return srv.Serve(l)
}

// Serve accepts incoming connections on the Listener.
func (srv *Server) Serve(l net.Listener) error {
	defer l.Close()
	srv.logger = srv.options().Logger
	srv.quit = make(chan bool, 1)
	srv.listener = l
	srv.wg = sync.WaitGroup{}
	srv.cs = make(map[*client]bool)
	srv.logServerStart()
	delay := backoff.Exp{Min: time.Millisecond * 5}
	for {
		conn, err := srv.listener.Accept()
		select {
		case <-srv.quit:
			return nil
		default:
		}
		if err != nil {
			if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
				srv.logTemporaryError(nerr, &delay, nil)
				delay.Wait()
				continue
			}
			return err
		}
		delay.Reset()

		// start client goroutine.
		c := newClient(srv, conn)
		srv.wg.Add(1)
		go func() {
			c.serve()
			srv.wg.Done()
		}()
	}
}

// Close terminates the server by shutting down all the client connections and
// closing.
func (srv *Server) Close() error {
	// terminate server.
	close(srv.quit)
	srv.listener.Close()
	// terminate all clients
	srv.cl.Lock()
	for c := range srv.cs {
		c.terminate()
	}
	srv.cl.Unlock()
	// wait to terminate all clients.
	srv.wg.Wait()
	return nil
}

func (srv *Server) logf(fmt string, a ...interface{}) {
	if srv.logger == nil {
		return
	}
	srv.logger.Printf(fmt, a...)
}

func (srv *Server) logServerStart() {
	srv.logf("server starts to listen: %s\n", srv.listener.Addr().String())
}

func (srv *Server) logTemporaryError(err net.Error, d *backoff.Exp, c *client) {
	if c != nil {
		srv.logf("client;%s detect temporary error: %v", c.id(), err)
		return
	}
	srv.logf("server detect temporary error: %v", err)
}

// logAdapterError logs continuable AdapterError
func (srv *Server) logAdapterError(err AdapterError, p packet.Packet, c *client) {
	srv.logf("client:%s rejects packet:%#v but continue: %s",
		c.id(), p, err.Error())
}

func (srv *Server) logEstablishFailure(c *client, err error) {
	srv.logf("client fails to connect: %v", err)
}

func (srv *Server) logSendPacketError(c *client, p packet.Packet, err error) {
	srv.logf("failed to send packet;%#v to client;%s: %v", c.id, p, err)
}

func (srv *Server) adapter() Adapter {
	if srv.Adapter == nil {
		return DefaultAdapter
	}
	return srv.Adapter
}

func (srv *Server) clientOnConnect(c *client, p *packet.Connect) (ClientAdapter, error) {
	ca, err := srv.adapter().Connect(srv, c, p)
	if err != nil {
		return nil, err
	}
	if ca == nil {
		return nil, ErrInvalidCloudAdapter
	}
	return ca, nil
}

func (srv *Server) clientOnStart(c *client) {
	srv.cl.Lock()
	defer srv.cl.Unlock()
	srv.cs[c] = true
}

func (srv *Server) clientOnStop(c *client) {
	srv.cl.Lock()
	defer srv.cl.Unlock()
	delete(srv.cs, c)
}

func (srv *Server) clientOnDisconnect(c *client, err error) {
	srv.adapter().Disconnect(srv, c.ca, err)
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"
	"html/template"
	"net/http"
	"net/url"
)

func RootHandler(w http.ResponseWriter, r *http.Request, processes []*Process) {
	tmpl, err := template.ParseFiles("templates/root.html")
	if err != nil {
		fmt.Printf("Error parsing template")
	}
	tmpl.Execute(w, nil)
}

func ElectionHandler(w http.ResponseWriter, r *http.Request, processes []*Process) {
     	processes[1].God <- &Force{Election: true}
	fmt.Fprintf(w, "Forcing an election")
}

func LagHandler(w http.ResponseWriter, r *http.Request) {
	if _, err := template.ParseFiles("templates/lag.html"); err != nil {
		http.Error(w, "Error parsing lag template", http.StatusInternalServerError)
	} else {
		if err := r.ParseForm(); err != nil {
			http.Error(w, "Error parsing lag", http.StatusInternalServerError)
			return
		}
		lag, err := getFormValues(&r.Form)
		if err != nil {
			http.Error(w, "Unable to get form values", http.StatusInternalServerError)
		}
		//	t.Execute(w, userInput)
		fmt.Fprintf(w, "Adding %s lag", lag)
	}
}

func getFormValues(form *url.Values) (lag string, err error) {
	for key, value := range *form {
		switch key {
		case "lag":
			return value[0], nil
		case "processId":
			return value[0], nil
		default:
			return "", fmt.Errorf("Unable to parse form")
		}
	}
	return "", fmt.Errorf("No form values")
}
<commit_msg>use a pointer to true<commit_after>package server

import (
	"fmt"
	"html/template"
	"net/http"
	"net/url"
)

func RootHandler(w http.ResponseWriter, r *http.Request, processes []*Process) {
	tmpl, err := template.ParseFiles("templates/root.html")
	if err != nil {
		fmt.Printf("Error parsing template")
	}
	tmpl.Execute(w, nil)
}

func ElectionHandler(w http.ResponseWriter, r *http.Request, processes []*Process) {
	processes[1].God <- &Force{Election: &True}
	fmt.Fprintf(w, "Forcing an election")
}

func LagHandler(w http.ResponseWriter, r *http.Request) {
	if _, err := template.ParseFiles("templates/lag.html"); err != nil {
		http.Error(w, "Error parsing lag template", http.StatusInternalServerError)
	} else {
		if err := r.ParseForm(); err != nil {
			http.Error(w, "Error parsing lag", http.StatusInternalServerError)
			return
		}
		lag, err := getFormValues(&r.Form)
		if err != nil {
			http.Error(w, "Unable to get form values", http.StatusInternalServerError)
		}
		//	t.Execute(w, userInput)
		fmt.Fprintf(w, "Adding %s lag", lag)
	}
}

func getFormValues(form *url.Values) (lag string, err error) {
	for key, value := range *form {
		switch key {
		case "lag":
			return value[0], nil
		case "processId":
			return value[0], nil
		default:
			return "", fmt.Errorf("Unable to parse form")
		}
	}
	return "", fmt.Errorf("No form values")
}
<|endoftext|>


<commit_before>package main

import (
	// General
	"github.com/golang/glog"
	"flag"
	"github.com/iambc/xerrors"
	"reflect"
	"os"
	"time"

	//API
	"net/http"
	"encoding/json"

	//DB
	"database/sql"
	_ "github.com/lib/pq"
)

/*
TODO:
2) Add different input/output formats for the API
6) quote of the day
*/

type image_board_clusters struct {
    Id int
    Descr string
    LongDescr string
    BoardLimitCount int
}

type boards struct {
    Id int
    Name string
    Descr string
    ImageBoardClusterId string
    MaxThreadCount int //to be checked in insert thread
    MaxActiveThreadCount int //to be checked  in insert thread
    MaxPostsPerThread int // to be checked in insert thread
    AreAttachmentsAllowed bool // to be checked in insert post
    PostLimitsReachedActionId int // to be checked in insert post
}

type threads struct{
    Id int
    Name string
    Descr string
    BoardId int
    MaxPostsPerThread int
    AreAttachmentsAllowed bool
    LimitsReachedActionId int
    PostCount int
    PostCountWithAttachment int
}

type thread_posts struct{
    Id int
    Body string
    ThreadId int
    AttachmentUrl *string
    InsertedAt time.Time
    SourceIp *string
}

type thread_limits_reached_actions struct{
    Id	    int
    Name    string
    Descr   string
}

type api_request struct{
    Status  string
    Msg	    *string
    Payload interface{}
}

func getBoards(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
	return []byte{}, xerrors.NewSysErr()
    }

    values := req.URL.Query()
    api_key := values[`api_key`][0]
    rows, err := dbh.Query("select b.id, b.name, b.descr from boards b join image_board_clusters ibc on ibc.id = b.image_board_cluster_id where api_key = $1;", api_key)
    if err != nil {
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `002`, true)
    }
    defer rows.Close()

    var curr_boards []boards
    for rows.Next() {
	var board boards
	err = rows.Scan(&board.Id, &board.Name, &board.Descr)
	if err != nil {
	    return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `003`, true)
	}
	curr_boards = append(curr_boards, board)
    }
    bytes, err1 := json.Marshal(api_request{"ok", nil, &curr_boards})
    if err1 != nil {
	return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `004`, true)
    }
    return bytes, nil
}

func getActiveThreadsForBoard(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    board_id, is_passed := values[`board_id`]
    if !is_passed {
	return []byte{}, xerrors.NewUIErr(`Invalid params: No board_id given!`, `Invalid params: No board_id given!`, `005`, true)
    }

    api_key := values[`api_key`][0]
    rows, err := dbh.Query(`select t.id, t.name, count(*), (select count(*) from thread_posts where thread_id = t.id and attachment_url is not null) from threads t
				join boards b on b.id = t.board_id
				join image_board_clusters ibc on ibc.id = b.image_board_cluster_id
				left join thread_posts tp on tp.thread_id = t.id
			    where t.is_active = TRUE and t.board_id = $1 and ibc.api_key = $2 group by 1,2 order by t.id;`, board_id[0], api_key)
    if err != nil {
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `006`, true)
    }
    defer rows.Close()

    var active_threads []threads
    for rows.Next() {
	glog.Info("Popped new thread")
        var thread threads
        err = rows.Scan(&thread.Id, &thread.Name, &thread.PostCount, &thread.PostCountWithAttachment)
        if err != nil {
            return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `007`, true)
        }
        active_threads = append(active_threads, thread)
    }
    var bytes []byte
    var err1 error
    if(len(active_threads) == 0){
        errMsg := "No objects returned."
        bytes, err1 = json.Marshal(api_request{"error", &errMsg, &active_threads})
    }else {
        bytes, err1 = json.Marshal(api_request{"ok", nil, &active_threads})
    }

    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `008`, true)
    }

    return bytes, nil
}

func getPostsForThread(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    thread_id, is_passed := values[`thread_id`]
    if !is_passed {
        return []byte{},xerrors.NewUIErr(`Invalid params: No thread_id given!`, `Invalid params: No thread_id given!`, `006`, true)
    }

    api_key := values[`api_key`][0]
    rows, err := dbh.Query(`select tp.id, tp.body, tp.attachment_url, tp.inserted_at, tp.source_ip
			    from thread_posts tp join threads t on t.id = tp.thread_id
						 join boards b on b.id = t.board_id
						 join image_board_clusters ibc on ibc.id = b.image_board_cluster_id
			    where tp.thread_id = $1 and ibc.api_key = $2 and t.is_active = true;`, thread_id[0], api_key)
    if err != nil {
	glog.Error(err)
        return []byte{}, xerrors.NewSysErr()
    }
    defer rows.Close()

    var curr_posts []thread_posts
    for rows.Next() {
	glog.Info("new post for thread with id: ", thread_id[0])
        var curr_post thread_posts
        err = rows.Scan(&curr_post.Id, &curr_post.Body, &curr_post.AttachmentUrl, &curr_post.InsertedAt, &curr_post.SourceIp)
        if err != nil {
	    glog.Error(err)
            return []byte{}, xerrors.NewSysErr()
        }
        curr_posts = append(curr_posts, curr_post)
    }

    var bytes []byte
    var err1 error
    if(len(curr_posts) == 0){
	errMsg := "No objects returned."
	bytes, err1 = json.Marshal(api_request{"error", &errMsg, &curr_posts})
    }else {
	bytes, err1 = json.Marshal(api_request{"ok", nil, &curr_posts})
    }

    if err1 != nil {
        return []byte{}, xerrors.NewSysErr()
    }

    return bytes, nil
}

func addPostToThread(res http.ResponseWriter, req *http.Request) ([]byte,error) {
    if req == nil || res == nil{
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    thread_id, is_passed := values[`thread_id`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_id given!`, `Invalid params: No thread_id given!`, `001`, true)
    }

    thread_body_post, is_passed := values[`thread_post_body`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_post_body given!`, `Invalid params: No thread_post_body given!`, `001`, true)
    }

    var is_limit_reached bool
    err := dbh.QueryRow("select (select count(*) from thread_posts  where thread_id = $1) > max_posts_per_thread  from threads where id = $1;", thread_id[0]).Scan(&is_limit_reached)
    if err != nil {
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `009`, true)
    }

    if is_limit_reached {
	dbh.QueryRow("UPDATE threads set is_active = false where id = $1",  thread_id[0]).Scan()
	return []byte{}, xerrors.NewUIErr(`Thread post limit reached!`, `Thread post limit reached!`, `010`, true)
    }

    attachment_urls, is_passed := values[`attachment_url`]
    var attachment_url *string
    if !is_passed{
	attachment_url = nil
    }else{
	attachment_url = &attachment_urls[0]
    }

    if(*attachment_url == ``){
	attachment_url = nil
    }

    _, err = dbh.Query("INSERT INTO thread_posts(body, thread_id, attachment_url, source_ip) VALUES($1, $2, $3, $4)", thread_body_post[0], thread_id[0], attachment_url, req.RemoteAddr)

    if err != nil {
	glog.Error(err)
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `011`, true)
    }

    bytes, err1 := json.Marshal(api_request{"ok", nil, nil})
    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `012`, true)
    }

    return bytes, nil
}

func addThread(res http.ResponseWriter, req *http.Request) ([]byte,error) {
    if req == nil || res == nil{
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()

    thread_name, is_passed := values[`thread_name`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_name given!`, `Invalid params: No thread_name given!`, `013`, true)
    }

    board_id, is_passed := values[`board_id`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No board_id given!`, `Invalid params: No board_id given!`, `014`, true)
    }

    var is_limit_reached bool
    err := dbh.QueryRow("select (select count(*) from threads  where board_id = $1) > thread_setting_max_thread_count  from boards where id = $1;", board_id[0]).Scan(&is_limit_reached)
    if err != nil {
	glog.Error("COULD NOT SELECT thread_count")
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `015`, true)
    }
    if is_limit_reached {
	return []byte{}, xerrors.NewUIErr(`Thread limit reached!`, `Thread limit reached!`, `016`, true)
    }

    _, err = dbh.Query("INSERT INTO threads(name, board_id, limits_reached_action_id, max_posts_per_thread) VALUES($1, $2, 1, 10)", thread_name[0], board_id[0])

    if err != nil {
	glog.Error("INSERT FAILED")
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `017`, true)
    }

    bytes, err1 := json.Marshal(api_request{"ok", nil, nil})
    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `018`, true)
    }

    return bytes, nil
}

var dbConnString = ``
var dbh *sql.DB

// sample usage
func main() {
    flag.Parse()

    var err error
    dbConnString = os.Getenv("ABC_DB_CONN_STRING") // DB will return error if empty string
    dbh, err = sql.Open("postgres", dbConnString)
    if err != nil {
	glog.Fatal(err)
    }

    commands := map[string]func(http.ResponseWriter, *http.Request) ([]byte, error){
				"getBoards": getBoards,
				"getActiveThreadsForBoard": getActiveThreadsForBoard,
				"getPostsForThread": getPostsForThread,
				"addPostToThread": addPostToThread,
				"addThread": addThread,
			       }

    http.HandleFunc("/api", func(res http.ResponseWriter, req *http.Request) {
					values := req.URL.Query()
					command, is_passed := values[`command`]
					if !is_passed {
					    res.Write([]byte(`{"Status":"error","Msg":"Paremeter 'command' is undefined.","Payload":null}`))
					    return
					}

					_, is_passed = values[`api_key`]
					if !is_passed {
					    res.Write([]byte(`{"Status":"error","Msg":"Paremeter 'api_key' is undefined.","Payload":null}`))
					    return
					}

					_, is_passed = commands[command[0]]
					if !is_passed{
					    res.Write([]byte(`{"Status":"error","Msg":"No such command exists.","Payload":null}`))
					    glog.Error("command: ", command[0])
					    return
					}

					res.Header().Set("Access-Control-Allow-Origin", "*")
					bytes, err := commands[command[0]](res, req)

					if err != nil{
					    if string(reflect.TypeOf(err).Name())  == `SysErr` {
						res.Write([]byte(`{"Status":"`+ err.(xerrors.XError).Code +`","Msg":"` + err.Error()  +`","Payload":null}`))
					    } else if string(reflect.TypeOf(err).Name())  == `UIErr` {
						res.Write([]byte(`{"Status":"` + err.(xerrors.XError).Code + `","Msg":"`+ err.Error() +`","Payload":null}`))
					    } else {
						res.Write([]byte(`{"Status":"000","Msg":"Application Error!","Payload":null}`))
					    }
					    glog.Error(err)
					    return
					}

					glog.Info(string(bytes))
					res.Write(bytes)
    })

    http.Handle("/f/", http.StripPrefix("/f/", http.FileServer(http.Dir(os.Getenv("ABC_FILES_DIR")))))

    http.ListenAndServe(`:`+ os.Getenv("ABC_SERVER_ENDPOINT_URL"), nil)
}

<commit_msg>feat: added constraints for post length (min, max)<commit_after>package main

import (
	// General
	"github.com/golang/glog"
	"flag"
	"github.com/iambc/xerrors"
	"reflect"
	"os"
	"time"
	"strconv"

	//API
	"net/http"
	"encoding/json"

	//DB
	"database/sql"
	_ "github.com/lib/pq"
)

/*
TODO:
2) Add different input/output formats for the API
6) quote of the day
*/

// LAST STATUS ID: 20

type image_board_clusters struct {
    Id int
    Descr string
    LongDescr string
    BoardLimitCount int
}

type boards struct {
    Id int
    Name string
    Descr string
    ImageBoardClusterId string
    MaxThreadCount int //to be checked in insert thread
    MaxActiveThreadCount int //to be checked  in insert thread
    MaxPostsPerThread int // to be checked in insert thread
    AreAttachmentsAllowed bool // to be checked in insert post
    PostLimitsReachedActionId int // to be checked in insert post
}

type threads struct{
    Id int
    Name string
    Descr string
    BoardId int
    MaxPostsPerThread int
    AreAttachmentsAllowed bool
    LimitsReachedActionId int
    PostCount int
    PostCountWithAttachment int
}

type thread_posts struct{
    Id int
    Body string
    ThreadId int
    AttachmentUrl *string
    InsertedAt time.Time
    SourceIp *string
}

type thread_limits_reached_actions struct{
    Id	    int
    Name    string
    Descr   string
}

type api_request struct{
    Status  string
    Msg	    *string
    Payload interface{}
}

func getBoards(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
	return []byte{}, xerrors.NewSysErr()
    }

    values := req.URL.Query()
    api_key := values[`api_key`][0]
    rows, err := dbh.Query("select b.id, b.name, b.descr from boards b join image_board_clusters ibc on ibc.id = b.image_board_cluster_id where api_key = $1;", api_key)
    if err != nil {
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `002`, true)
    }
    defer rows.Close()

    var curr_boards []boards
    for rows.Next() {
	var board boards
	err = rows.Scan(&board.Id, &board.Name, &board.Descr)
	if err != nil {
	    return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `003`, true)
	}
	curr_boards = append(curr_boards, board)
    }
    bytes, err1 := json.Marshal(api_request{"ok", nil, &curr_boards})
    if err1 != nil {
	return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `004`, true)
    }
    return bytes, nil
}

func getActiveThreadsForBoard(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    board_id, is_passed := values[`board_id`]
    if !is_passed {
	return []byte{}, xerrors.NewUIErr(`Invalid params: No board_id given!`, `Invalid params: No board_id given!`, `005`, true)
    }

    api_key := values[`api_key`][0]
    rows, err := dbh.Query(`select t.id, t.name, count(*), (select count(*) from thread_posts where thread_id = t.id and attachment_url is not null) from threads t
				join boards b on b.id = t.board_id
				join image_board_clusters ibc on ibc.id = b.image_board_cluster_id
				left join thread_posts tp on tp.thread_id = t.id
			    where t.is_active = TRUE and t.board_id = $1 and ibc.api_key = $2 group by 1,2 order by t.id;`, board_id[0], api_key)
    if err != nil {
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `006`, true)
    }
    defer rows.Close()

    var active_threads []threads
    for rows.Next() {
	glog.Info("Popped new thread")
        var thread threads
        err = rows.Scan(&thread.Id, &thread.Name, &thread.PostCount, &thread.PostCountWithAttachment)
        if err != nil {
            return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `007`, true)
        }
        active_threads = append(active_threads, thread)
    }
    var bytes []byte
    var err1 error
    if(len(active_threads) == 0){
        errMsg := "No objects returned."
        bytes, err1 = json.Marshal(api_request{"error", &errMsg, &active_threads})
    }else {
        bytes, err1 = json.Marshal(api_request{"ok", nil, &active_threads})
    }

    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `008`, true)
    }

    return bytes, nil
}

func getPostsForThread(res http.ResponseWriter, req *http.Request)  ([]byte, error) {
    if req == nil || res == nil {
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    thread_id, is_passed := values[`thread_id`]
    if !is_passed {
        return []byte{},xerrors.NewUIErr(`Invalid params: No thread_id given!`, `Invalid params: No thread_id given!`, `006`, true)
    }

    api_key := values[`api_key`][0]
    rows, err := dbh.Query(`select tp.id, tp.body, tp.attachment_url, tp.inserted_at, tp.source_ip
			    from thread_posts tp join threads t on t.id = tp.thread_id
						 join boards b on b.id = t.board_id
						 join image_board_clusters ibc on ibc.id = b.image_board_cluster_id
			    where tp.thread_id = $1 and ibc.api_key = $2 and t.is_active = true;`, thread_id[0], api_key)
    if err != nil {
	glog.Error(err)
        return []byte{}, xerrors.NewSysErr()
    }
    defer rows.Close()

    var curr_posts []thread_posts
    for rows.Next() {
	glog.Info("new post for thread with id: ", thread_id[0])
        var curr_post thread_posts
        err = rows.Scan(&curr_post.Id, &curr_post.Body, &curr_post.AttachmentUrl, &curr_post.InsertedAt, &curr_post.SourceIp)
        if err != nil {
	    glog.Error(err)
            return []byte{}, xerrors.NewSysErr()
        }
        curr_posts = append(curr_posts, curr_post)
    }

    var bytes []byte
    var err1 error
    if(len(curr_posts) == 0){
	errMsg := "No objects returned."
	bytes, err1 = json.Marshal(api_request{"error", &errMsg, &curr_posts})
    }else {
	bytes, err1 = json.Marshal(api_request{"ok", nil, &curr_posts})
    }

    if err1 != nil {
        return []byte{}, xerrors.NewSysErr()
    }

    return bytes, nil
}

func addPostToThread(res http.ResponseWriter, req *http.Request) ([]byte,error) {
    if req == nil || res == nil{
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()
    thread_id, is_passed := values[`thread_id`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_id given!`, `Invalid params: No thread_id given!`, `001`, true)
    }

    thread_body_post, is_passed := values[`thread_post_body`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_post_body given!`, `Invalid params: No thread_post_body given!`, `001`, true)
    }

    var is_limit_reached bool
    var max_post_length int
    var min_post_length int
    err := dbh.QueryRow("select (select count(*) from thread_posts  where thread_id = $1) > max_posts_per_thread, min_post_length, max_post_length  from threads where id = $1;", thread_id[0]).Scan(&is_limit_reached, &min_post_length, &max_post_length)
    if err != nil {
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `009`, true)
    }

    if is_limit_reached {
	dbh.QueryRow("UPDATE threads set is_active = false where id = $1",  thread_id[0]).Scan()
	return []byte{}, xerrors.NewUIErr(`Thread post limit reached!`, `Thread post limit reached!`, `010`, true)
    }

   if(min_post_length > len(thread_body_post[0])  && min_post_length != -1){
	return []byte{}, xerrors.NewUIErr(`Post length is less than minimum length!`, `Post length is less than minimum length! post length: ` + strconv.Itoa(len(thread_body_post[0]))  +` min length: ` + strconv.Itoa(min_post_length) , `020`, false)
    }
   if(max_post_length < len(thread_body_post[0])  && max_post_length != -1){
	return []byte{}, xerrors.NewUIErr(`Post length is more than maximum length!`, `Post length is more than maximum length! post length: ` + strconv.Itoa(len(thread_body_post[0]))  +` max length: ` + strconv.Itoa(max_post_length) , `021`, false)
    }

    attachment_urls, is_passed := values[`attachment_url`]
    var attachment_url *string
    if !is_passed{
	attachment_url = nil
    }else{
	attachment_url = &attachment_urls[0]
    }

    if(*attachment_url == ``){
	attachment_url = nil
    }

    _, err = dbh.Query("INSERT INTO thread_posts(body, thread_id, attachment_url, source_ip) VALUES($1, $2, $3, $4)", thread_body_post[0], thread_id[0], attachment_url, req.RemoteAddr)

    if err != nil {
	glog.Error(err)
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `011`, true)
    }

    bytes, err1 := json.Marshal(api_request{"ok", nil, nil})
    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `012`, true)
    }

    return bytes, nil
}

func addThread(res http.ResponseWriter, req *http.Request) ([]byte,error) {
    if req == nil || res == nil{
        return []byte{}, xerrors.NewSysErr()
    }
    values := req.URL.Query()

    thread_name, is_passed := values[`thread_name`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No thread_name given!`, `Invalid params: No thread_name given!`, `013`, true)
    }

    board_id, is_passed := values[`board_id`]
    if !is_passed {
        return []byte{}, xerrors.NewUIErr(`Invalid params: No board_id given!`, `Invalid params: No board_id given!`, `014`, true)
    }

    var is_limit_reached bool
    err := dbh.QueryRow("select (select count(*) from threads  where board_id = $1) > thread_setting_max_thread_count  from boards where id = $1;", board_id[0]).Scan(&is_limit_reached)
    if err != nil {
	glog.Error("COULD NOT SELECT thread_count")
	return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `015`, true)
    }
    if is_limit_reached {
	return []byte{}, xerrors.NewUIErr(`Thread limit reached!`, `Thread limit reached!`, `016`, true)
    }

    _, err = dbh.Query("INSERT INTO threads(name, board_id, limits_reached_action_id, max_posts_per_thread) VALUES($1, $2, 1, 10)", thread_name[0], board_id[0])

    if err != nil {
	glog.Error("INSERT FAILED")
        return []byte{}, xerrors.NewUIErr(err.Error(), err.Error(), `017`, true)
    }

    bytes, err1 := json.Marshal(api_request{"ok", nil, nil})
    if err1 != nil {
        return []byte{}, xerrors.NewUIErr(err1.Error(), err1.Error(), `018`, true)
    }

    return bytes, nil
}

var dbConnString = ``
var dbh *sql.DB

// sample usage
func main() {
    flag.Parse()

    var err error
    dbConnString = os.Getenv("ABC_DB_CONN_STRING") // DB will return error if empty string
    dbh, err = sql.Open("postgres", dbConnString)
    if err != nil {
	glog.Fatal(err)
    }

    commands := map[string]func(http.ResponseWriter, *http.Request) ([]byte, error){
				"getBoards": getBoards,
				"getActiveThreadsForBoard": getActiveThreadsForBoard,
				"getPostsForThread": getPostsForThread,
				"addPostToThread": addPostToThread,
				"addThread": addThread,
			       }

    http.HandleFunc("/api", func(res http.ResponseWriter, req *http.Request) {
					values := req.URL.Query()
					command, is_passed := values[`command`]
					if !is_passed {
					    res.Write([]byte(`{"Status":"error","Msg":"Paremeter 'command' is undefined.","Payload":null}`))
					    return
					}

					_, is_passed = values[`api_key`]
					if !is_passed {
					    res.Write([]byte(`{"Status":"error","Msg":"Paremeter 'api_key' is undefined.","Payload":null}`))
					    return
					}

					_, is_passed = commands[command[0]]
					if !is_passed{
					    res.Write([]byte(`{"Status":"error","Msg":"No such command exists.","Payload":null}`))
					    glog.Error("command: ", command[0])
					    return
					}

					res.Header().Set("Access-Control-Allow-Origin", "*")
					bytes, err := commands[command[0]](res, req)

					if err != nil{
					    if string(reflect.TypeOf(err).Name())  == `XError` {
						res.Write([]byte(`{"Status":"`+ err.(xerrors.XError).Code +`","Msg":"` + err.Error()  +`","Payload":null}`))
					    } else {
						res.Write([]byte(`{"Status":"000","Msg":"Application Error!","Payload":null}`))
					    }
					    glog.Error(err)
					    return
					}

					glog.Info(string(bytes))
					res.Write(bytes)
    })

    http.Handle("/f/", http.StripPrefix("/f/", http.FileServer(http.Dir(os.Getenv("ABC_FILES_DIR")))))

    http.ListenAndServe(`:`+ os.Getenv("ABC_SERVER_ENDPOINT_URL"), nil)
}

<|endoftext|>