// Package xrequestid implements an http middleware for Negroni that assigns a random id to each request
//
// Example:
//		package main
//
//		import (
//			"fmt"
//			"net/http"
//
//			"github.com/codegangsta/negroni"
//			"github.com/pilu/xrequestid"
//		)
//
//		func main() {
//			mux := http.NewServeMux()
//			mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
//				fmt.Fprintf(w, "X-Request-Id is `%s`", r.Header.Get("X-Request-Id"))
//			})
//
//			n := negroni.New()
//			n.Use(xrequestid.New(16))
//			n.UseHandler(mux)
//			n.Run(":3000")
//		}
package xrequestid

import (
	"crypto/rand"
	"encoding/hex"
	"net/http"
)

// By default the middleware set the generated random string to this key in the request header
const DefaultHeaderKey = "X-Request-Id"

// GenerateFunc is the func used by the middleware to generates the random string.
type GenerateFunc func(int) (string, error)

// XRequestID is a middleware that adds a random ID to the request X-Request-Id header
type XRequestID struct {
	// Size specifies the length of the random length. The length of the result string is twice of n.
	Size int
	// Generate is a GenerateFunc that generates the random string. The default one uses crypto/rand
	Generate GenerateFunc
	// HeaderKey is the header name where the middleware set the random string. By default it uses the DefaultHeaderKey constant value
	HeaderKey string
}

// New returns a new XRequestID middleware instance. n specifies the length of the random length. The length of the result string is twice of n.
func New(n int) *XRequestID {
	return &XRequestID{
		Size:      n,
		Generate:  generateID,
		HeaderKey: DefaultHeaderKey,
	}
}

func (m *XRequestID) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
	id, err := m.Generate(m.Size)
	if err == nil {
		r.Header.Set(m.HeaderKey, id)
		rw.Header().Set(m.HeaderKey, id)
	}

	next(rw, r)
}

func generateID(n int) (string, error) {
	r := make([]byte, n)
	_, err := rand.Read(r)
	if err != nil {
		return "", err
	}

	return hex.EncodeToString(r), nil
}
