initial commit for APIDQuota
diff --git a/api.go b/api.go
new file mode 100644
index 0000000..50d340f
--- /dev/null
+++ b/api.go
@@ -0,0 +1,62 @@
+package apidQuota
+
+import (
+ "encoding/json"
+ "github.com/30x/apid-core"
+ "github.com/30x/apidQuota/quotaBucket"
+ "io/ioutil"
+ "net/http"
+ "strconv"
+)
+
+func InitAPI(services apid.Services) {
+ Log.Debug("initialized API's exposed by apidQuota plugin")
+ quotaBasePath := Config.GetString(ConfigQuotaBasePath)
+ services.API().HandleFunc(quotaBasePath, getAllQuotaValues).Methods("GET") //yet to implement.
+ services.API().HandleFunc(quotaBasePath+"/{quotaItentifier}", incrementAndCheckQuotaLimit).Methods("POST")
+
+}
+
+func getAllQuotaValues(res http.ResponseWriter, req *http.Request) {
+ stringbytes := []byte("yet to implement")
+ res.Header().Set("Content-Type", "application/json")
+ res.WriteHeader(http.StatusOK)
+ res.Write(stringbytes)
+
+}
+
+func incrementAndCheckQuotaLimit(res http.ResponseWriter, req *http.Request) {
+
+ bodyBytes, err := ioutil.ReadAll(req.Body)
+ defer req.Body.Close()
+ if err != nil {
+ WriteErrorResponse(http.StatusBadRequest, UnableToParseBody, "unable to read request body: "+err.Error(), res, req)
+ return
+ }
+
+ quotaBucketMap := make(map[string]interface{}, 0)
+ if err := json.Unmarshal(bodyBytes, "aBucketMap); err != nil {
+ WriteErrorResponse(http.StatusBadRequest, UnMarshalJSONError, "unable to convert request body to an object: "+err.Error(), res, req)
+ return
+ }
+ Log.Println("quotaBucketMap from request: ", quotaBucketMap)
+
+ // parse the request body into the Event struct
+ qBucket := new(quotaBucket.QuotaBucket)
+ if err = qBucket.FromAPIRequest(quotaBucketMap); err != nil {
+ WriteErrorResponse(http.StatusBadRequest, ErrorConvertReqBodyToEntity, err.Error(), res, req)
+ return
+ }
+
+ quotaCount, err := qBucket.GetQuotaCount()
+ if err != nil {
+ WriteErrorResponse(http.StatusBadRequest, UnMarshalJSONError, "error retrieving count for the give identifier "+err.Error(), res, req)
+ return
+ }
+
+ stringbytes := []byte(strconv.Itoa(quotaCount))
+ res.Header().Set("Content-Type", "application/json")
+ res.WriteHeader(http.StatusOK)
+ res.Write(stringbytes)
+
+}
diff --git a/constants.go b/constants.go
new file mode 100644
index 0000000..588b808
--- /dev/null
+++ b/constants.go
@@ -0,0 +1,9 @@
+package apidQuota
+
+const (
+ UnableToParseBody = "unable_to_parse_body"
+ UnMarshalJSONError = "unmarshal_json_error"
+ ErrorConvertReqBodyToEntity = "error_convert_reqBody_to_entity"
+ ConfigQuotaBasePath = "quota_base_path"
+ quotaBasePathDefault = "/quota"
+)
diff --git a/init.go b/init.go
index 7ec68f9..93e1c1b 100644
--- a/init.go
+++ b/init.go
@@ -4,9 +4,9 @@
"github.com/30x/apid-core"
)
-var(
- log apid.LogService
-
+var (
+ Log apid.LogService
+ Config apid.ConfigService
)
func init() {
@@ -14,8 +14,19 @@
}
func initPlugin(services apid.Services) (apid.PluginData, error) {
- log = services.Log().ForModule("apidQuota")
- log.Debug("start init")
+ Log = services.Log().ForModule("apidQuota")
+ Log.Debug("start init")
+
+ setConfig(services)
+ InitAPI(services)
return pluginData, nil
}
+
+func setConfig(services apid.Services) {
+ // set configuration
+ Config = services.Config()
+ // set plugin config defaults
+ Config.SetDefault(ConfigQuotaBasePath, quotaBasePathDefault)
+
+}
diff --git a/quotaBucket/QuotaDescriptorType.go b/quotaBucket/QuotaDescriptorType.go
new file mode 100644
index 0000000..3e15c4a
--- /dev/null
+++ b/quotaBucket/QuotaDescriptorType.go
@@ -0,0 +1,119 @@
+package quotaBucket
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "time"
+)
+
+const (
+ QuotaTypeCalendar = "calendar" // after start time
+ QuotaTypeFlexi = "flexi" //after first request
+ QuotaTypeRollingWindow = "rollingWindow" // in the past "window" time
+)
+
+type QuotaDescriptorType interface {
+ SetCurrentPeriod(bucket *QuotaBucket) error
+}
+
+func GetQuotaTypeHandler(qType string) (QuotaDescriptorType, error) {
+ var qDescriptor QuotaDescriptorType
+ quotaType := strings.ToLower(strings.TrimSpace(qType))
+ switch quotaType {
+ case QuotaTypeCalendar:
+ qDescriptor = &CanlendarQuotaDescriporType{}
+ return qDescriptor, nil
+ case QuotaTypeFlexi:
+ qDescriptor = &FlexiQuotaDescriptorType{}
+ return qDescriptor, nil
+ case QuotaTypeRollingWindow:
+ qDescriptor = &RollingWindowQuotaDescriptorType{}
+ return qDescriptor, nil
+ default:
+ return nil, errors.New("Ignoring unrecognized quota type in request: " + qType)
+
+ }
+}
+
+type CanlendarQuotaDescriporType struct{}
+
+func (c CanlendarQuotaDescriporType) SetCurrentPeriod(qbucket *QuotaBucket) error {
+ var err error
+ startTime := qbucket.GetStartTime()
+ currentPeriod, err := qbucket.GetPeriod()
+ if err != nil {
+ return err
+ }
+
+ if startTime.Before(time.Now()) || startTime.Equal(time.Now()) {
+ if currentPeriod != nil {
+ if currentPeriod.IsCurrentPeriod(qbucket) {
+ return nil
+ }
+ } else {
+ if currentPeriod.IsCurrentPeriod(qbucket) {
+ return nil
+ } else {
+ qBucketHandler, err := GetQuotaBucketHandler(qbucket.BucketType)
+ if err != nil {
+ return errors.New("error getting QuotaBucketType: " + err.Error())
+ }
+ qBucketHandler.resetCount(qbucket)
+ }
+ }
+ }
+
+ var currentStart, currentEnd time.Time
+ now := time.Now()
+ timeUnit := strings.ToLower(strings.TrimSpace(qbucket.TimeUnit))
+ switch timeUnit {
+ case TimeUnitSECOND:
+ currentStart = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second(), 0, time.UTC)
+ secInDuration := time.Duration(int64(qbucket.Interval) * time.Second.Nanoseconds())
+ currentEnd = currentStart.Add(secInDuration)
+ break
+ case TimeUnitMINUTE:
+ currentStart = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), 0, 0, time.UTC)
+ minInDuration := time.Duration(int64(qbucket.Interval) * time.Minute.Nanoseconds())
+ currentEnd = currentStart.Add(minInDuration)
+ break
+ case TimeUnitHOUR:
+ currentStart = time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, time.UTC)
+ hoursInDuration := time.Duration(int64(qbucket.Interval) * time.Hour.Nanoseconds())
+ currentEnd = currentStart.Add(hoursInDuration)
+
+ break
+ case TimeUnitDAY:
+ currentStart = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
+ currentEnd = currentStart.AddDate(0, 0, 1*qbucket.Interval)
+ break
+ case TimeUnitWEEK:
+ //todo
+ //currentStart = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
+ //currentEnd = currentStart.AddDate(0, 0, 7*qbucket.Interval)
+ break
+ case TimeUnitMONTH:
+ currentStart = time.Date(now.Year(), now.Month(), 0, 0, 0, 0, 0, time.UTC)
+ currentEnd = currentStart.AddDate(0, 1*qbucket.Interval, 0)
+ break
+ }
+
+ qbucket.SetPeriod(currentStart, currentEnd)
+ fmt.Println("inside calendat set period: ", qbucket.quotaBucketData.Period)
+ return nil
+}
+
+type FlexiQuotaDescriptorType struct{}
+
+func (c FlexiQuotaDescriptorType) SetCurrentPeriod(qbucket *QuotaBucket) error {
+ //yet to implement
+ return nil
+}
+
+type RollingWindowQuotaDescriptorType struct{}
+
+func (c RollingWindowQuotaDescriptorType) SetCurrentPeriod(qbucket *QuotaBucket) error {
+ //yet to implement
+ return nil
+}
diff --git a/quotaBucket/apiUtil.go b/quotaBucket/apiUtil.go
new file mode 100644
index 0000000..c00a879
--- /dev/null
+++ b/quotaBucket/apiUtil.go
@@ -0,0 +1,173 @@
+package quotaBucket
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+)
+
+func (qBucket *QuotaBucket) FromAPIRequest(quotaBucketMap map[string]interface{}) error {
+ var edgeOrgID, id, timeUnit, quotaType, bucketType string
+ var interval, maxCount int
+ var startTime int64
+ var preciseAtSecondsLevel bool
+
+ fmt.Println("quotaBucketMap: ", quotaBucketMap)
+
+ value, ok := quotaBucketMap["edgeOrgID"]
+ if !ok {
+ return errors.New(`missing field: 'edgeOrgID' is required`)
+ }
+ if edgeOrgIDType := reflect.TypeOf(value); edgeOrgIDType.Kind() != reflect.String {
+ return errors.New(`invalid type : 'edgeOrgID' should be a string`)
+ }
+ edgeOrgID = value.(string)
+ //fmt.Println("edgeOrgID: ", edgeOrgID)
+
+ value, ok = quotaBucketMap["id"]
+ if !ok {
+ return errors.New(`missing field: 'id' is required`)
+ }
+ if idType := reflect.TypeOf(value); idType.Kind() != reflect.String {
+ return errors.New(`invalid type : 'id' should be a string`)
+ }
+ id = value.(string)
+ //fmt.Println("id: ", id)
+
+ value, ok = quotaBucketMap["interval"]
+ if !ok {
+ return errors.New(`missing field: 'interval' is required`)
+ }
+ //from input when its read its float, need to then convert to int.
+ if intervalType := reflect.TypeOf(value); intervalType.Kind() != reflect.Float64 {
+ return errors.New(`invalid type : 'interval' should be a number`)
+ }
+ intervalFloat := value.(float64)
+ interval = int(intervalFloat)
+ //fmt.Println("interval: ", interval)
+
+ //TimeUnit {SECOND, MINUTE, HOUR, DAY, WEEK, MONTH}
+ value, ok = quotaBucketMap["timeUnit"]
+ if !ok {
+ return errors.New(`missing field: 'timeUnit' is required`)
+ }
+ if timeUnitType := reflect.TypeOf(value); timeUnitType.Kind() != reflect.String {
+ return errors.New(`invalid type : 'timeUnit' should be a string`)
+ }
+ timeUnit = value.(string)
+ //fmt.Println("timeUnit: ", timeUnit)
+
+ //Type {CALENDAR, FLEXI, ROLLING_WINDOW}
+ value, ok = quotaBucketMap["quotaType"]
+ if !ok {
+ return errors.New(`missing field: 'quotaType' is required`)
+ }
+ if quotaTypeType := reflect.TypeOf(value); quotaTypeType.Kind() != reflect.String {
+ return errors.New(`invalid type : 'quotaType' should be a string`)
+ }
+ quotaType = value.(string)
+ //fmt.Println("quotaType: ", quotaType)
+
+ value, ok = quotaBucketMap["preciseAtSecondsLevel"]
+ if !ok {
+ return errors.New(`missing field: 'preciseAtSecondsLevel' is required`)
+ }
+ if preciseAtSecondsLevelType := reflect.TypeOf(value); preciseAtSecondsLevelType.Kind() != reflect.Bool {
+ return errors.New(`invalid type : 'preciseAtSecondsLevel' should be boolean`)
+ }
+ preciseAtSecondsLevel = value.(bool)
+ //fmt.Println("preciseAtSecondsLevel: ", preciseAtSecondsLevel)
+
+ value, ok = quotaBucketMap["startTime"]
+ if !ok {
+ return errors.New(`missing field: 'startTime' is required`)
+ }
+ //from input when its read its float, need to then convert to int.
+ if startTimeType := reflect.TypeOf(value); startTimeType.Kind() != reflect.Float64 {
+ return errors.New(`invalid type : 'startTime' should be UNIX timestamp`)
+ }
+ startTimeFloat := value.(float64)
+ startTime = int64(startTimeFloat)
+ //fmt.Println("startTime: ", startTime)
+
+ quotaPeriod := QuotaPeriod{}
+ value, ok = quotaBucketMap["period"]
+ //if period is not sent in the request, it is calculated based in the startTime, quotaType and interval.
+ if ok {
+ var inStartInt, startInt, endInt int64
+
+ isPeriodMap := reflect.TypeOf(value)
+ if isPeriodMap.Kind() != reflect.Map {
+ return errors.New(`invalid type : 'period' should be a Map`)
+ }
+ periodMap := value.(map[string]interface{})
+
+ inStartTimeValue, ok := periodMap["inputStartTime"]
+ if !ok {
+ //set period.inputStart time from qBucket.startTime
+ inStartInt = startTime
+ } else {
+ if inStartType := reflect.TypeOf(inStartTimeValue); inStartType.Kind() != reflect.Float64 {
+ return errors.New(`invalid type : 'inputStartTime' in 'period' should be UNIX timestamp`)
+ }
+ inStartFloat := inStartTimeValue.(float64)
+ inStartInt = int64(inStartFloat)
+ if startTime != inStartInt {
+ return errors.New(`invalid value : 'inputStartTime' in 'period' should be same as 'startTime'' in request`)
+ }
+ }
+
+ startTimeValue, ok := periodMap["startTime"]
+ if !ok {
+ return errors.New(`missing field : 'startTime' in 'period' cannot be empty`)
+ }
+ if periodStartType := reflect.TypeOf(startTimeValue); periodStartType.Kind() != reflect.Float64 {
+ return errors.New(`invalid type : 'startTime' in 'period' should be UNIX timestamp`)
+ }
+ periodStartFloat := startTimeValue.(float64)
+ startInt = int64(periodStartFloat)
+
+ periodEndValue, ok := periodMap["endTime"]
+ if !ok {
+ return errors.New(`missing field : 'endTime' in 'period' cannot be empty`)
+ }
+ if periodEndType := reflect.TypeOf(periodEndValue); periodEndType.Kind() != reflect.Float64 {
+ return errors.New(`invalid type : 'endTime' in 'period' should be UNIX timestamp`)
+ }
+ periodEndFloat := periodEndValue.(float64)
+ endInt = int64(periodEndFloat)
+
+ quotaPeriod = NewQuotaPeriod(inStartInt, startInt, endInt)
+ }
+
+ value, ok = quotaBucketMap["maxCount"]
+ if !ok {
+ return errors.New(`missing field: 'maxCount' is required`)
+ }
+ //from input when its read its float, need to then convert to int.
+ if maxCountType := reflect.TypeOf(value); maxCountType.Kind() != reflect.Float64 {
+ return errors.New(`invalid type : 'maxCount' should be a number`)
+ }
+ maxCountFloat := value.(float64)
+ maxCount = int(maxCountFloat)
+ //fmt.Println("maxCount: ", maxCount)
+
+ value, ok = quotaBucketMap["bucketType"]
+ if !ok {
+ return errors.New(`missing field: 'bucketType' is required`)
+ }
+ if bucketTypeType := reflect.TypeOf(value); bucketTypeType.Kind() != reflect.String {
+ return errors.New(`invalid type : 'bucketType' should be a string`)
+ }
+ bucketType = value.(string)
+ //fmt.Println("bucketType: ", bucketType)
+
+ newQBucket, err := NewQuotaBucket(edgeOrgID, id, interval, timeUnit, quotaType, preciseAtSecondsLevel, quotaPeriod, startTime, maxCount, bucketType)
+ if err != nil {
+ return errors.New("unable to Unmarshal periodMap to quotaPeriod: " + err.Error())
+
+ }
+ qBucket.quotaBucketData = newQBucket.quotaBucketData
+ return nil
+
+}
diff --git a/quotaBucket/quotaBucket.go b/quotaBucket/quotaBucket.go
new file mode 100644
index 0000000..36c9082
--- /dev/null
+++ b/quotaBucket/quotaBucket.go
@@ -0,0 +1,169 @@
+package quotaBucket
+
+import (
+ "errors"
+ "fmt"
+ "time"
+)
+
+const (
+ TimeUnitSECOND = "second"
+ TimeUnitMINUTE = "minute"
+ TimeUnitHOUR = "hour"
+ TimeUnitDAY = "day"
+ TimeUnitWEEK = "week"
+ TimeUnitMONTH = "month"
+)
+
+type QuotaPeriod struct {
+ inputStartTime time.Time
+ startTime time.Time
+ endTime time.Time
+}
+
+func NewQuotaPeriod(inputStartTime int64, startTime int64, endTime int64) QuotaPeriod {
+ pInStartTime := time.Unix(inputStartTime, 0)
+ pStartTime := time.Unix(startTime, 0)
+ pEndTime := time.Unix(endTime, 0)
+
+ period := &QuotaPeriod{inputStartTime: pInStartTime,
+ startTime: pStartTime,
+ endTime: pEndTime,
+ }
+ return *period
+
+}
+
+type quotaBucketData struct {
+ EdgeOrgID string
+ ID string
+ Interval int
+ TimeUnit string //TimeUnit {SECOND, MINUTE, HOUR, DAY, WEEK, MONTH}
+ QuotaType string //Type {CALENDAR, FLEXI, ROLLING_WINDOW}
+ PreciseAtSecondsLevel bool
+ Period QuotaPeriod
+ StartTime time.Time
+ MaxCount int
+ BucketType string // SyncDistributed, AsyncDistributed, NonDistributed
+}
+
+type QuotaBucket struct {
+ quotaBucketData
+}
+
+func NewQuotaBucket(edgeOrgID string, id string, interval int,
+ timeUnit string, quotaType string, preciseAtSecondsLevel bool, period QuotaPeriod,
+ startTime int64, maxCount int, bucketType string) (*QuotaBucket, error) {
+
+ fromUNIXTime := time.Unix(startTime, 0)
+
+ quotaBucketDataStruct := "aBucketData{
+ EdgeOrgID: edgeOrgID,
+ ID: id,
+ Interval: interval,
+ TimeUnit: timeUnit,
+ QuotaType: quotaType,
+ PreciseAtSecondsLevel: preciseAtSecondsLevel,
+ Period: period,
+ StartTime: fromUNIXTime,
+ MaxCount: maxCount,
+ BucketType: bucketType,
+ }
+
+ quotaBucket := &QuotaBucket{
+ quotaBucketData: *quotaBucketDataStruct,
+ }
+
+ return quotaBucket, nil
+
+}
+
+func (q *QuotaBucket) GetEdgeOrgID() string {
+ return q.quotaBucketData.EdgeOrgID
+}
+
+func (q *QuotaBucket) GetID() string {
+ return q.quotaBucketData.ID
+}
+
+func (q *QuotaBucket) GetInterval() int {
+ return q.quotaBucketData.Interval
+}
+
+func (q *QuotaBucket) GetStartTime() time.Time {
+ return q.quotaBucketData.StartTime
+}
+
+func (q *QuotaBucket) GetQuotaType() string {
+ return q.quotaBucketData.QuotaType
+}
+
+func (q *QuotaBucket) GetPeriod() (*QuotaPeriod, error) {
+ if q.quotaBucketData.QuotaType == QuotaTypeRollingWindow {
+ qRWType := RollingWindowQuotaDescriptorType{}
+ err := qRWType.SetCurrentPeriod(q)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return &q.quotaBucketData.Period, nil
+}
+
+func (q *QuotaBucket) GetMaxCount() int {
+ return q.quotaBucketData.MaxCount
+}
+
+func (q *QuotaBucket) GetBucketType() string {
+ return q.quotaBucketData.BucketType
+}
+
+func (q *quotaBucketData) SetPeriod(startTime time.Time, endTime time.Time) {
+ q.Period = QuotaPeriod{inputStartTime: q.StartTime,
+ startTime: startTime,
+ endTime: endTime,
+ }
+}
+
+func (q *QuotaBucket) setCurrentPeriod() error {
+
+ qDescriptorType, err := GetQuotaTypeHandler(q.GetQuotaType())
+ if err != nil {
+ return err
+ }
+ return qDescriptorType.SetCurrentPeriod(q)
+
+}
+
+func (period *QuotaPeriod) IsCurrentPeriod(qBucket *QuotaBucket) bool {
+ if qBucket != nil && qBucket.GetBucketType() != "" {
+ if qBucket.GetBucketType() == QuotaTypeRollingWindow {
+ return (period.inputStartTime.Equal(time.Now()) || period.inputStartTime.Before(time.Now()))
+ }
+ return ((period.inputStartTime.Equal(time.Now()) || period.inputStartTime.Before(time.Now())) &&
+ period.startTime.String() != "" && period.endTime.String() != "" &&
+ period.startTime.Before(time.Now()) && period.startTime.Equal(time.Now()) &&
+ period.endTime.Before(time.Now()) && period.startTime.Equal(time.Now()))
+ }
+ return false
+}
+
+func (q *QuotaBucket) GetQuotaCount() (int, error) {
+ err := q.setCurrentPeriod()
+ if err != nil {
+ return 0, errors.New("error setCurrentPeriod(): " + err.Error())
+ }
+ period, err := q.GetPeriod()
+ if err != nil {
+ return 0, errors.New("error getting period: " + err.Error())
+ }
+ fmt.Println("period set: ", period)
+
+ //todo API call to counter service using Period start end and keyspace.
+
+ return 10, nil
+}
+
+func (q *QuotaBucket) IncrementQuota() (int, error) {
+ //todo
+ return 0, nil
+}
diff --git a/quotaBucket/quotaBucketType.go b/quotaBucket/quotaBucketType.go
new file mode 100644
index 0000000..843846b
--- /dev/null
+++ b/quotaBucket/quotaBucketType.go
@@ -0,0 +1,57 @@
+package quotaBucket
+
+import (
+ "errors"
+ "strings"
+)
+
+const (
+ QuotaBucketTypeSynchronous = "synchronous"
+ QuotaBucketTypeAsynchronous = "asynchronous"
+ QuotaBucketTypeNonDistributed = "nonDistributed"
+ //todo: Add other bucketTypes
+)
+
+type QuotaBucketType interface {
+ resetCount(bucket *QuotaBucket) error
+}
+
+type SynchronousQuotaBucketType struct{}
+
+func (sQuotaBucket SynchronousQuotaBucketType) resetCount(qBucket *QuotaBucket) error {
+ //do nothing.
+ return nil
+}
+
+type AsynchronousQuotaBucketType struct{}
+
+func (sQuotaBucket AsynchronousQuotaBucketType) resetCount(qBucket *QuotaBucket) error {
+ //yet to implement
+ return nil
+}
+
+type NonDistributedQuotaBucketType struct{}
+
+func (sQuotaBucket NonDistributedQuotaBucketType) resetCount(qBucket *QuotaBucket) error {
+ //yet to implement
+ return nil
+}
+
+func GetQuotaBucketHandler(qBucket string) (QuotaBucketType, error) {
+ var quotaBucketType QuotaBucketType
+ qBucketType := strings.ToLower(strings.TrimSpace(qBucket))
+ switch qBucketType {
+ case QuotaBucketTypeSynchronous:
+ quotaBucketType = &SynchronousQuotaBucketType{}
+ return quotaBucketType, nil
+ case QuotaBucketTypeAsynchronous:
+ quotaBucketType = &AsynchronousQuotaBucketType{}
+ return quotaBucketType, nil
+ case QuotaBucketTypeNonDistributed:
+ quotaBucketType = &NonDistributedQuotaBucketType{}
+ return quotaBucketType, nil
+ default:
+ return nil, errors.New("Ignoring unrecognized quota type in request: " + qBucket)
+
+ }
+}
diff --git a/util.go b/util.go
new file mode 100644
index 0000000..696d5bc
--- /dev/null
+++ b/util.go
@@ -0,0 +1,20 @@
+package apidQuota
+
+import (
+ "encoding/json"
+ "net/http"
+)
+
+// WriteErrorResponse will write the HTTP header and payload for the HTTP ResponseWriter provided
+func WriteErrorResponse(status int, errorType string, errorDescription string, res http.ResponseWriter, req *http.Request) {
+ response := make(map[string]string)
+ response["error"] = errorType
+ response["errorDescription"] = errorDescription
+ responseJson, err := json.Marshal(response)
+ if err != nil {
+ panic(err)
+ }
+ res.Header().Set("Content-Type", "application/json")
+ res.WriteHeader(status)
+ res.Write(responseJson)
+}