<commit_before>package aws

import (
	"fmt"
	"log"

	"github.com/hashicorp/terraform/helper/schema"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	"github.com/hashicorp/terraform/helper/validation"
)

func resourceAwsCloudWatchMetricAlarm() *schema.Resource {
	return &schema.Resource{
		Create:        resourceAwsCloudWatchMetricAlarmCreate,
		Read:          resourceAwsCloudWatchMetricAlarmRead,
		Update:        resourceAwsCloudWatchMetricAlarmUpdate,
		Delete:        resourceAwsCloudWatchMetricAlarmDelete,
		SchemaVersion: 1,
		MigrateState:  resourceAwsCloudWatchMetricAlarmMigrateState,

		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"alarm_name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"comparison_operator": {
				Type:     schema.TypeString,
				Required: true,
			},
			"evaluation_periods": {
				Type:     schema.TypeInt,
				Required: true,
			},
			"metric_name": {
				Type:     schema.TypeString,
				Required: true,
			},
			"namespace": {
				Type:     schema.TypeString,
				Required: true,
			},
			"period": {
				Type:     schema.TypeInt,
				Required: true,
			},
			"statistic": {
				Type:          schema.TypeString,
				Optional:      true,
				ConflictsWith: []string{"extended_statistic"},
			},
			"threshold": {
				Type:     schema.TypeFloat,
				Required: true,
			},
			"actions_enabled": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
			},
			"alarm_actions": {
				Type:     schema.TypeSet,
				Optional: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Set:      schema.HashString,
			},
			"alarm_description": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"datapoints_to_alarm": {
				Type:         schema.TypeInt,
				Optional:     true,
				Computed:     true,
				ValidateFunc: validation.IntAtLeast(1),
			},
			"dimensions": {
				Type:     schema.TypeMap,
				Optional: true,
			},
			"insufficient_data_actions": {
				Type:     schema.TypeSet,
				Optional: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Set:      schema.HashString,
			},
			"ok_actions": {
				Type:     schema.TypeSet,
				Optional: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Set:      schema.HashString,
			},
			"unit": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"extended_statistic": {
				Type:          schema.TypeString,
				Optional:      true,
				ConflictsWith: []string{"statistic"},
			},
			"treat_missing_data": {
				Type:         schema.TypeString,
				Optional:     true,
				Default:      "missing",
				ValidateFunc: validation.StringInSlice([]string{"breaching", "notBreaching", "ignore", "missing"}, true),
			},
			"evaluate_low_sample_count_percentiles": {
				Type:         schema.TypeString,
				Optional:     true,
				Computed:     true,
				ValidateFunc: validation.StringInSlice([]string{"evaluate", "ignore"}, true),
			},
		},
	}
}

func resourceAwsCloudWatchMetricAlarmCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cloudwatchconn

	_, statisticOk := d.GetOk("statistic")
	_, extendedStatisticOk := d.GetOk("extended_statistic")

	if !statisticOk && !extendedStatisticOk {
		return fmt.Errorf("One of `statistic` or `extended_statistic` must be set for a cloudwatch metric alarm")
	}

	params := getAwsCloudWatchPutMetricAlarmInput(d)

	log.Printf("[DEBUG] Creating CloudWatch Metric Alarm: %#v", params)
	_, err := conn.PutMetricAlarm(&params)
	if err != nil {
		return fmt.Errorf("Creating metric alarm failed: %s", err)
	}
	d.SetId(d.Get("alarm_name").(string))
	log.Println("[INFO] CloudWatch Metric Alarm created")

	return resourceAwsCloudWatchMetricAlarmRead(d, meta)
}

func resourceAwsCloudWatchMetricAlarmRead(d *schema.ResourceData, meta interface{}) error {
	a, err := getAwsCloudWatchMetricAlarm(d, meta)
	if err != nil {
		return err
	}
	if a == nil {
		d.SetId("")
		return nil
	}

	log.Printf("[DEBUG] Reading CloudWatch Metric Alarm: %s", d.Get("alarm_name"))

	d.Set("actions_enabled", a.ActionsEnabled)

	if err := d.Set("alarm_actions", _strArrPtrToList(a.AlarmActions)); err != nil {
		log.Printf("[WARN] Error setting Alarm Actions: %s", err)
	}
	d.Set("alarm_description", a.AlarmDescription)
	d.Set("alarm_name", a.AlarmName)
	d.Set("comparison_operator", a.ComparisonOperator)
	d.Set("datapoints_to_alarm", a.DatapointsToAlarm)
	if err := d.Set("dimensions", flattenDimensions(a.Dimensions)); err != nil {
		return err
	}
	d.Set("evaluation_periods", a.EvaluationPeriods)

	if err := d.Set("insufficient_data_actions", _strArrPtrToList(a.InsufficientDataActions)); err != nil {
		log.Printf("[WARN] Error setting Insufficient Data Actions: %s", err)
	}
	d.Set("metric_name", a.MetricName)
	d.Set("namespace", a.Namespace)

	if err := d.Set("ok_actions", _strArrPtrToList(a.OKActions)); err != nil {
		log.Printf("[WARN] Error setting OK Actions: %s", err)
	}
	d.Set("period", a.Period)
	d.Set("statistic", a.Statistic)
	d.Set("threshold", a.Threshold)
	d.Set("unit", a.Unit)
	d.Set("extended_statistic", a.ExtendedStatistic)
	d.Set("treat_missing_data", a.TreatMissingData)
	d.Set("evaluate_low_sample_count_percentiles", a.EvaluateLowSampleCountPercentile)

	return nil
}

func resourceAwsCloudWatchMetricAlarmUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cloudwatchconn
	params := getAwsCloudWatchPutMetricAlarmInput(d)

	log.Printf("[DEBUG] Updating CloudWatch Metric Alarm: %#v", params)
	_, err := conn.PutMetricAlarm(&params)
	if err != nil {
		return fmt.Errorf("Updating metric alarm failed: %s", err)
	}
	log.Println("[INFO] CloudWatch Metric Alarm updated")

	return resourceAwsCloudWatchMetricAlarmRead(d, meta)
}

func resourceAwsCloudWatchMetricAlarmDelete(d *schema.ResourceData, meta interface{}) error {
	p, err := getAwsCloudWatchMetricAlarm(d, meta)
	if err != nil {
		return err
	}
	if p == nil {
		log.Printf("[DEBUG] CloudWatch Metric Alarm %s is already gone", d.Id())
		return nil
	}

	log.Printf("[INFO] Deleting CloudWatch Metric Alarm: %s", d.Id())

	conn := meta.(*AWSClient).cloudwatchconn
	params := cloudwatch.DeleteAlarmsInput{
		AlarmNames: []*string{aws.String(d.Id())},
	}

	if _, err := conn.DeleteAlarms(&params); err != nil {
		return fmt.Errorf("Error deleting CloudWatch Metric Alarm: %s", err)
	}
	log.Println("[INFO] CloudWatch Metric Alarm deleted")

	return nil
}

func getAwsCloudWatchPutMetricAlarmInput(d *schema.ResourceData) cloudwatch.PutMetricAlarmInput {
	params := cloudwatch.PutMetricAlarmInput{
		AlarmName:          aws.String(d.Get("alarm_name").(string)),
		ComparisonOperator: aws.String(d.Get("comparison_operator").(string)),
		EvaluationPeriods:  aws.Int64(int64(d.Get("evaluation_periods").(int))),
		MetricName:         aws.String(d.Get("metric_name").(string)),
		Namespace:          aws.String(d.Get("namespace").(string)),
		Period:             aws.Int64(int64(d.Get("period").(int))),
		Threshold:          aws.Float64(d.Get("threshold").(float64)),
		TreatMissingData:   aws.String(d.Get("treat_missing_data").(string)),
	}

	if v := d.Get("actions_enabled"); v != nil {
		params.ActionsEnabled = aws.Bool(v.(bool))
	}

	if v, ok := d.GetOk("alarm_description"); ok {
		params.AlarmDescription = aws.String(v.(string))
	}

	if v, ok := d.GetOk("datapoints_to_alarm"); ok {
		params.DatapointsToAlarm = aws.Int64(int64(v.(int)))
	}

	if v, ok := d.GetOk("unit"); ok {
		params.Unit = aws.String(v.(string))
	}

	if v, ok := d.GetOk("statistic"); ok {
		params.Statistic = aws.String(v.(string))
	}

	if v, ok := d.GetOk("extended_statistic"); ok {
		params.ExtendedStatistic = aws.String(v.(string))
	}

	if v, ok := d.GetOk("evaluate_low_sample_count_percentiles"); ok {
		params.EvaluateLowSampleCountPercentile = aws.String(v.(string))
	}

	var alarmActions []*string
	if v := d.Get("alarm_actions"); v != nil {
		for _, v := range v.(*schema.Set).List() {
			str := v.(string)
			alarmActions = append(alarmActions, aws.String(str))
		}
		params.AlarmActions = alarmActions
	}

	var insufficientDataActions []*string
	if v := d.Get("insufficient_data_actions"); v != nil {
		for _, v := range v.(*schema.Set).List() {
			str := v.(string)
			insufficientDataActions = append(insufficientDataActions, aws.String(str))
		}
		params.InsufficientDataActions = insufficientDataActions
	}

	var okActions []*string
	if v := d.Get("ok_actions"); v != nil {
		for _, v := range v.(*schema.Set).List() {
			str := v.(string)
			okActions = append(okActions, aws.String(str))
		}
		params.OKActions = okActions
	}

	a := d.Get("dimensions").(map[string]interface{})
	dimensions := make([]*cloudwatch.Dimension, 0, len(a))
	for k, v := range a {
		dimensions = append(dimensions, &cloudwatch.Dimension{
			Name:  aws.String(k),
			Value: aws.String(v.(string)),
		})
	}
	params.Dimensions = dimensions

	return params
}

func getAwsCloudWatchMetricAlarm(d *schema.ResourceData, meta interface{}) (*cloudwatch.MetricAlarm, error) {
	conn := meta.(*AWSClient).cloudwatchconn

	params := cloudwatch.DescribeAlarmsInput{
		AlarmNames: []*string{aws.String(d.Id())},
	}

	resp, err := conn.DescribeAlarms(&params)
	if err != nil {
		return nil, err
	}

	// Find it and return it
	for idx, ma := range resp.MetricAlarms {
		if *ma.AlarmName == d.Id() {
			return resp.MetricAlarms[idx], nil
		}
	}

	return nil, nil
}

func _strArrPtrToList(strArrPtr []*string) []string {
	var result []string
	for _, elem := range strArrPtr {
		result = append(result, *elem)
	}
	return result
}

func flattenDimensions(dims []*cloudwatch.Dimension) map[string]interface{} {
	flatDims := make(map[string]interface{})
	for _, d := range dims {
		flatDims[*d.Name] = *d.Value
	}
	return flatDims
}
<commit_msg>only adding validation<commit_after>package aws

import (
	"fmt"
	"log"

	"github.com/hashicorp/terraform/helper/schema"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	"github.com/hashicorp/terraform/helper/validation"
)

func resourceAwsCloudWatchMetricAlarm() *schema.Resource {
	return &schema.Resource{
		Create:        resourceAwsCloudWatchMetricAlarmCreate,
		Read:          resourceAwsCloudWatchMetricAlarmRead,
		Update:        resourceAwsCloudWatchMetricAlarmUpdate,
		Delete:        resourceAwsCloudWatchMetricAlarmDelete,
		SchemaVersion: 1,
		MigrateState:  resourceAwsCloudWatchMetricAlarmMigrateState,

		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"alarm_name": {
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"comparison_operator": {
				Type:     schema.TypeString,
				Required: true,
			},
			"evaluation_periods": {
				Type:         schema.TypeInt,
				Required:     true,
				ValidateFunc: validation.IntAtLeast(1),
			},
			"metric_name": {
				Type:     schema.TypeString,
				Required: true,
			},
			"namespace": {
				Type:     schema.TypeString,
				Required: true,
			},
			"period": {
				Type:     schema.TypeInt,
				Required: true,
			},
			"statistic": {
				Type:          schema.TypeString,
				Optional:      true,
				ConflictsWith: []string{"extended_statistic"},
			},
			"threshold": {
				Type:     schema.TypeFloat,
				Required: true,
			},
			"actions_enabled": {
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
			},
			"alarm_actions": {
				Type:     schema.TypeSet,
				Optional: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Set:      schema.HashString,
			},
			"alarm_description": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"datapoints_to_alarm": {
				Type:         schema.TypeInt,
				Optional:     true,
				ValidateFunc: validation.IntAtLeast(1),
			},
			"dimensions": {
				Type:     schema.TypeMap,
				Optional: true,
			},
			"insufficient_data_actions": {
				Type:     schema.TypeSet,
				Optional: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Set:      schema.HashString,
			},
			"ok_actions": {
				Type:     schema.TypeSet,
				Optional: true,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Set:      schema.HashString,
			},
			"unit": {
				Type:     schema.TypeString,
				Optional: true,
			},
			"extended_statistic": {
				Type:          schema.TypeString,
				Optional:      true,
				ConflictsWith: []string{"statistic"},
			},
			"treat_missing_data": {
				Type:         schema.TypeString,
				Optional:     true,
				Default:      "missing",
				ValidateFunc: validation.StringInSlice([]string{"breaching", "notBreaching", "ignore", "missing"}, true),
			},
			"evaluate_low_sample_count_percentiles": {
				Type:         schema.TypeString,
				Optional:     true,
				Computed:     true,
				ValidateFunc: validation.StringInSlice([]string{"evaluate", "ignore"}, true),
			},
		},
	}
}

func resourceAwsCloudWatchMetricAlarmCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cloudwatchconn

	_, statisticOk := d.GetOk("statistic")
	_, extendedStatisticOk := d.GetOk("extended_statistic")

	if !statisticOk && !extendedStatisticOk {
		return fmt.Errorf("One of `statistic` or `extended_statistic` must be set for a cloudwatch metric alarm")
	}

	params := getAwsCloudWatchPutMetricAlarmInput(d)

	log.Printf("[DEBUG] Creating CloudWatch Metric Alarm: %#v", params)
	_, err := conn.PutMetricAlarm(&params)
	if err != nil {
		return fmt.Errorf("Creating metric alarm failed: %s", err)
	}
	d.SetId(d.Get("alarm_name").(string))
	log.Println("[INFO] CloudWatch Metric Alarm created")

	return resourceAwsCloudWatchMetricAlarmRead(d, meta)
}

func resourceAwsCloudWatchMetricAlarmRead(d *schema.ResourceData, meta interface{}) error {
	a, err := getAwsCloudWatchMetricAlarm(d, meta)
	if err != nil {
		return err
	}
	if a == nil {
		d.SetId("")
		return nil
	}

	log.Printf("[DEBUG] Reading CloudWatch Metric Alarm: %s", d.Get("alarm_name"))

	d.Set("actions_enabled", a.ActionsEnabled)

	if err := d.Set("alarm_actions", _strArrPtrToList(a.AlarmActions)); err != nil {
		log.Printf("[WARN] Error setting Alarm Actions: %s", err)
	}
	d.Set("alarm_description", a.AlarmDescription)
	d.Set("alarm_name", a.AlarmName)
	d.Set("comparison_operator", a.ComparisonOperator)
	d.Set("datapoints_to_alarm", a.DatapointsToAlarm)
	if err := d.Set("dimensions", flattenDimensions(a.Dimensions)); err != nil {
		return err
	}
	d.Set("evaluation_periods", a.EvaluationPeriods)

	if err := d.Set("insufficient_data_actions", _strArrPtrToList(a.InsufficientDataActions)); err != nil {
		log.Printf("[WARN] Error setting Insufficient Data Actions: %s", err)
	}
	d.Set("metric_name", a.MetricName)
	d.Set("namespace", a.Namespace)

	if err := d.Set("ok_actions", _strArrPtrToList(a.OKActions)); err != nil {
		log.Printf("[WARN] Error setting OK Actions: %s", err)
	}
	d.Set("period", a.Period)
	d.Set("statistic", a.Statistic)
	d.Set("threshold", a.Threshold)
	d.Set("unit", a.Unit)
	d.Set("extended_statistic", a.ExtendedStatistic)
	d.Set("treat_missing_data", a.TreatMissingData)
	d.Set("evaluate_low_sample_count_percentiles", a.EvaluateLowSampleCountPercentile)

	return nil
}

func resourceAwsCloudWatchMetricAlarmUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).cloudwatchconn
	params := getAwsCloudWatchPutMetricAlarmInput(d)

	log.Printf("[DEBUG] Updating CloudWatch Metric Alarm: %#v", params)
	_, err := conn.PutMetricAlarm(&params)
	if err != nil {
		return fmt.Errorf("Updating metric alarm failed: %s", err)
	}
	log.Println("[INFO] CloudWatch Metric Alarm updated")

	return resourceAwsCloudWatchMetricAlarmRead(d, meta)
}

func resourceAwsCloudWatchMetricAlarmDelete(d *schema.ResourceData, meta interface{}) error {
	p, err := getAwsCloudWatchMetricAlarm(d, meta)
	if err != nil {
		return err
	}
	if p == nil {
		log.Printf("[DEBUG] CloudWatch Metric Alarm %s is already gone", d.Id())
		return nil
	}

	log.Printf("[INFO] Deleting CloudWatch Metric Alarm: %s", d.Id())

	conn := meta.(*AWSClient).cloudwatchconn
	params := cloudwatch.DeleteAlarmsInput{
		AlarmNames: []*string{aws.String(d.Id())},
	}

	if _, err := conn.DeleteAlarms(&params); err != nil {
		return fmt.Errorf("Error deleting CloudWatch Metric Alarm: %s", err)
	}
	log.Println("[INFO] CloudWatch Metric Alarm deleted")

	return nil
}

func getAwsCloudWatchPutMetricAlarmInput(d *schema.ResourceData) cloudwatch.PutMetricAlarmInput {
	params := cloudwatch.PutMetricAlarmInput{
		AlarmName:          aws.String(d.Get("alarm_name").(string)),
		ComparisonOperator: aws.String(d.Get("comparison_operator").(string)),
		EvaluationPeriods:  aws.Int64(int64(d.Get("evaluation_periods").(int))),
		MetricName:         aws.String(d.Get("metric_name").(string)),
		Namespace:          aws.String(d.Get("namespace").(string)),
		Period:             aws.Int64(int64(d.Get("period").(int))),
		Threshold:          aws.Float64(d.Get("threshold").(float64)),
		TreatMissingData:   aws.String(d.Get("treat_missing_data").(string)),
	}

	if v := d.Get("actions_enabled"); v != nil {
		params.ActionsEnabled = aws.Bool(v.(bool))
	}

	if v, ok := d.GetOk("alarm_description"); ok {
		params.AlarmDescription = aws.String(v.(string))
	}

	if v, ok := d.GetOk("datapoints_to_alarm"); ok {
		params.DatapointsToAlarm = aws.Int64(int64(v.(int)))
	}

	if v, ok := d.GetOk("unit"); ok {
		params.Unit = aws.String(v.(string))
	}

	if v, ok := d.GetOk("statistic"); ok {
		params.Statistic = aws.String(v.(string))
	}

	if v, ok := d.GetOk("extended_statistic"); ok {
		params.ExtendedStatistic = aws.String(v.(string))
	}

	if v, ok := d.GetOk("evaluate_low_sample_count_percentiles"); ok {
		params.EvaluateLowSampleCountPercentile = aws.String(v.(string))
	}

	var alarmActions []*string
	if v := d.Get("alarm_actions"); v != nil {
		for _, v := range v.(*schema.Set).List() {
			str := v.(string)
			alarmActions = append(alarmActions, aws.String(str))
		}
		params.AlarmActions = alarmActions
	}

	var insufficientDataActions []*string
	if v := d.Get("insufficient_data_actions"); v != nil {
		for _, v := range v.(*schema.Set).List() {
			str := v.(string)
			insufficientDataActions = append(insufficientDataActions, aws.String(str))
		}
		params.InsufficientDataActions = insufficientDataActions
	}

	var okActions []*string
	if v := d.Get("ok_actions"); v != nil {
		for _, v := range v.(*schema.Set).List() {
			str := v.(string)
			okActions = append(okActions, aws.String(str))
		}
		params.OKActions = okActions
	}

	a := d.Get("dimensions").(map[string]interface{})
	dimensions := make([]*cloudwatch.Dimension, 0, len(a))
	for k, v := range a {
		dimensions = append(dimensions, &cloudwatch.Dimension{
			Name:  aws.String(k),
			Value: aws.String(v.(string)),
		})
	}
	params.Dimensions = dimensions

	return params
}

func getAwsCloudWatchMetricAlarm(d *schema.ResourceData, meta interface{}) (*cloudwatch.MetricAlarm, error) {
	conn := meta.(*AWSClient).cloudwatchconn

	params := cloudwatch.DescribeAlarmsInput{
		AlarmNames: []*string{aws.String(d.Id())},
	}

	resp, err := conn.DescribeAlarms(&params)
	if err != nil {
		return nil, err
	}

	// Find it and return it
	for idx, ma := range resp.MetricAlarms {
		if *ma.AlarmName == d.Id() {
			return resp.MetricAlarms[idx], nil
		}
	}

	return nil, nil
}

func _strArrPtrToList(strArrPtr []*string) []string {
	var result []string
	for _, elem := range strArrPtr {
		result = append(result, *elem)
	}
	return result
}

func flattenDimensions(dims []*cloudwatch.Dimension) map[string]interface{} {
	flatDims := make(map[string]interface{})
	for _, d := range dims {
		flatDims[*d.Name] = *d.Value
	}
	return flatDims
}
<|endoftext|>


<commit_before>package rest

import (
	"bytes"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"sort"

	"github.com/oleiade/reflections"
)

//more work to support http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

func GetHeaderValue(key string, headers map[string][]string) string {
	values := headers[key]
	if values != nil && len(values) > 0 {
		return values[0]
	}
	return ""
}

// currently used primarily for testing, however, could be used to make request to the
// local process for cases where it made more sense to delpoy a typically remote resource locally
func LocalRequest(cr *ClientRequest) (*Request, *Response) {
	response := NewResponse()
	client := NewClient()
	if rawReq, err := client.NewHttpRequest(cr); err != nil {
		panic(err)
	} else {
		request := NewRequest(rawReq, NewRequestContext(), cr.Definition)
		request.Body = cr.Body
		request.Args = cr.args
		return request, response
	}
}

func FullApiWithHandlers(apiSpec interface{}) map[ServerResource]Handler {
	handlers := make(map[ServerResource]Handler)

	items, err := reflections.Items(apiSpec)
	if err != nil {
		panic(err)
	}

	for _, v := range items {
		if target, ok := v.(*ResourceSpec); ok {
			verbs, handler := target.ServeAll()
			if handler == nil {
				panic(fmt.Sprintf("no handler for endpoint %v ", target))
			}
			for _, verb := range verbs {
				handlers[verb] = handler
			}
		}
	}
	return handlers
}

// Bytes, is a helper method to reduce the number of lines to get a byte array out of the
// EndpointResponse
func Bytes(resp *EndpointResponse, err error) ([]byte, error) {
	if err != nil {
		return nil, err
	}

	if resp == nil {
		return nil, errors.New("no response from endpoint or code incorrectly called.")
	}

	if resp.HttpResponse == nil {
		return nil, errors.New("No HttpResponse Response")
	}
	if resp.HttpResponse.StatusCode != http.StatusOK {
		return nil, errors.New(resp.HttpResponse.Status)
	}

	if bytes, err := resp.Bytes(); err != nil {
		return nil, err
	} else {
		return bytes, nil
	}
}

// Encode - Returns a URL that is encoded in a way that makes sense, preseves as many special characters
// as possible. The default encoding from GO makes no sense as it encodes even valid values such as + , :
func Encode(v url.Values) string {
	if v == nil {
		return ""
	}
	var buf bytes.Buffer
	keys := make([]string, 0, len(v))
	for k := range v {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		vs := v[k]
		prefix := k + "="
		for _, v := range vs {
			if buf.Len() > 0 {
				buf.WriteByte('&')
			}
			buf.WriteString(prefix)
			buf.WriteString(v)
		}
	}
	path := buf.String()
	u := &url.URL{
		Path: path,
	}
	return u.RequestURI()
}
<commit_msg>improved url encoding<commit_after>package rest

import (
	"bytes"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"sort"

	"github.com/oleiade/reflections"
)

//more work to support http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

func GetHeaderValue(key string, headers map[string][]string) string {
	values := headers[key]
	if values != nil && len(values) > 0 {
		return values[0]
	}
	return ""
}

// currently used primarily for testing, however, could be used to make request to the
// local process for cases where it made more sense to delpoy a typically remote resource locally
func LocalRequest(cr *ClientRequest) (*Request, *Response) {
	response := NewResponse()
	client := NewClient()
	if rawReq, err := client.NewHttpRequest(cr); err != nil {
		panic(err)
	} else {
		request := NewRequest(rawReq, NewRequestContext(), cr.Definition)
		request.Body = cr.Body
		request.Args = cr.args
		return request, response
	}
}

func FullApiWithHandlers(apiSpec interface{}) map[ServerResource]Handler {
	handlers := make(map[ServerResource]Handler)

	items, err := reflections.Items(apiSpec)
	if err != nil {
		panic(err)
	}

	for _, v := range items {
		if target, ok := v.(*ResourceSpec); ok {
			verbs, handler := target.ServeAll()
			if handler == nil {
				panic(fmt.Sprintf("no handler for endpoint %v ", target))
			}
			for _, verb := range verbs {
				handlers[verb] = handler
			}
		}
	}
	return handlers
}

// Bytes, is a helper method to reduce the number of lines to get a byte array out of the
// EndpointResponse
func Bytes(resp *EndpointResponse, err error) ([]byte, error) {
	if err != nil {
		return nil, err
	}

	if resp == nil {
		return nil, errors.New("no response from endpoint or code incorrectly called.")
	}

	if resp.HttpResponse == nil {
		return nil, errors.New("No HttpResponse Response")
	}
	if resp.HttpResponse.StatusCode != http.StatusOK {
		return nil, errors.New(resp.HttpResponse.Status)
	}

	if bytes, err := resp.Bytes(); err != nil {
		return nil, err
	} else {
		return bytes, nil
	}
}

// Encode - Returns a URL that is encoded in a way that is actually used in practice, preseves as many special characters
// as possible. The default encoding from GO makes no sense as it encodes even valid values such as + , :
func Encode(v url.Values) string {
	if v == nil {
		return ""
	}
	var buf bytes.Buffer
	keys := make([]string, 0, len(v))
	for k := range v {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		vs := v[k]
		prefix := escape(k, encodePath) + "="
		for _, v := range vs {
			if buf.Len() > 0 {
				buf.WriteByte('&')
			}
			buf.WriteString(prefix)
			buf.WriteString(escape(v, encodeQueryComponent))
		}
	}
	path := buf.String()
	u := &url.URL{
		Path: path,
	}
	return u.RequestURI()
}

func escape(s string, mode encoding) string {
	spaceCount, hexCount := 0, 0
	for i := 0; i < len(s); i++ {
		c := s[i]
		if shouldEscape(c, mode) {
			if c == ' ' && mode == encodeQueryComponent {
				spaceCount++
			} else {
				hexCount++
			}
		}
	}

	if spaceCount == 0 && hexCount == 0 {
		return s
	}

	t := make([]byte, len(s)+2*hexCount)
	j := 0
	for i := 0; i < len(s); i++ {
		switch c := s[i]; {
		case c == ' ' && mode == encodeQueryComponent:
			t[j] = '+'
			j++
		case shouldEscape(c, mode):
			t[j] = '%'
			t[j+1] = "0123456789ABCDEF"[c>>4]
			t[j+2] = "0123456789ABCDEF"[c&15]
			j += 3
		default:
			t[j] = s[i]
			j++
		}
	}
	return string(t)
}

type encoding int

const (
	encodePath encoding = 1 + iota
	encodeUserPassword
	encodeQueryComponent
	encodeFragment
)

// Return true if the specified character should be escaped when
// appearing in a URL string, according to RFC 3986.
// When 'all' is true the full range of reserved characters are matched.
func shouldEscape(c byte, mode encoding) bool {
	// §2.3 Unreserved characters (alphanum)
	if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
		return false
	}

	switch c {
	case '-', '_', '.', '~': // §2.3 Unreserved characters (mark)
		return false

	case '$', '&', '+', ',', '/', ':', ';', '=', '?', '@': // §2.2 Reserved characters (reserved)
		// Different sections of the URL allow a few of
		// the reserved characters to appear unescaped.
		switch mode {
		case encodePath: // §3.3
			// The RFC allows : @ & = + $ but saves / ; , for assigning
			// meaning to individual path segments. This package
			// only manipulates the path as a whole, so we allow those
			// last two as well. That leaves only ? to escape.
			return c == '?'

		case encodeUserPassword: // §3.2.2
			// The RFC allows ; : & = + $ , in userinfo, so we must escape only @ and /.
			// The parsing of userinfo treats : as special so we must escape that too.
			return c == '@' || c == '/' || c == ':'

		case encodeQueryComponent: // §3.4
			//screw RFC, in practice many api's require these characters including google cse
			return c == '?'

		case encodeFragment: // §4.1
			// The RFC text is silent but the grammar allows
			// everything, so escape nothing.
			return false
		}
	}

	// Everything else must be escaped.
	return true
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/url"
	"os"

	functions "github.com/funcy/functions_go"
	"github.com/urfave/cli"
)

// Version of Functions CLI
var Version = "0.3.53"

func version() cli.Command {
	r := versionCmd{VersionApi: functions.NewVersionApi()}
	return cli.Command{
		Name:   "version",
		Usage:  "displays fn and functions daemon versions",
		Action: r.version,
	}
}

type versionCmd struct {
	*functions.VersionApi
}

func (r *versionCmd) version(c *cli.Context) error {
	apiURL := os.Getenv("API_URL")
	if apiURL == "" {
		apiURL = "http://localhost:8080"
	}

	u, err := url.Parse(apiURL)
	if err != nil {
		return err
	}
	r.Configuration.BasePath = u.String()

	fmt.Println("Client version:", Version)
	v, _, err := r.VersionGet()
	if err != nil {
		return err
	}
	fmt.Println("Server version", v.Version)
	return nil
}
<commit_msg>fn tool: 0.3.54 release [skip ci]<commit_after>package main

import (
	"fmt"
	"net/url"
	"os"

	functions "github.com/funcy/functions_go"
	"github.com/urfave/cli"
)

// Version of Functions CLI
var Version = "0.3.54"

func version() cli.Command {
	r := versionCmd{VersionApi: functions.NewVersionApi()}
	return cli.Command{
		Name:   "version",
		Usage:  "displays fn and functions daemon versions",
		Action: r.version,
	}
}

type versionCmd struct {
	*functions.VersionApi
}

func (r *versionCmd) version(c *cli.Context) error {
	apiURL := os.Getenv("API_URL")
	if apiURL == "" {
		apiURL = "http://localhost:8080"
	}

	u, err := url.Parse(apiURL)
	if err != nil {
		return err
	}
	r.Configuration.BasePath = u.String()

	fmt.Println("Client version:", Version)
	v, _, err := r.VersionGet()
	if err != nil {
		return err
	}
	fmt.Println("Server version", v.Version)
	return nil
}
<|endoftext|>


<commit_before>package record

import "io"

type Writer struct {
}

func NewWriter(w io.Writer) *Writer {

}

func (w *Writer) Append() (int64, *io.Writer, error) {

}

func (w *Writer) Flush() {

}
<commit_msg>Implement Writer<commit_after>package record

import "io"

// import "fmt"
import "errors"
import "hash/crc32"
import "encoding/binary"

var crcTable = crc32.MakeTable(crc32.Koopman)

type flusher interface {
	Flush() error
}

const bufferSize = 32 * 1024

type recordWriter struct {
	w      *Writer
	offset int64
	buf    []byte
}

func (r *recordWriter) Write(p []byte) (int, error) {
	if r.w.offset != r.offset {
		return 0, errors.New("stale writer")
	}
	r.buf = append(r.buf, p...)
	return len(p), nil
}

func (r *recordWriter) finalize() error {
	if len(r.buf) == 0 {
		return nil
	}
	l := len(r.buf)
	lBuf := make([]byte, 4)
	binary.LittleEndian.PutUint32(lBuf, uint32(l))
	crc := crc32.Checksum(lBuf, crcTable)
	crc = crc32.Update(crc, crcTable, r.buf)
	crcBuf := make([]byte, 4)
	binary.LittleEndian.PutUint32(crcBuf, crc)
	// Write CRC
	if _, err := r.w.w.Write(crcBuf); err != nil {
		return err
	}
	// Write length
	if _, err := r.w.w.Write(lBuf); err != nil {
		return err
	}
	if _, err := r.w.w.Write(r.buf); err != nil {
		return err
	}
	newOffset, err := r.w.w.Seek(0, 2)
	if newOffset == 0 {
		panic("Cannot update offset")
	}
	r.w.offset = newOffset
	return err
}

type Writer struct {
	offset int64
	// Writer given out to the client to write data
	ongoingWrite *recordWriter
	// Underlying writer
	w io.WriteSeeker
}

func NewWriter(w io.WriteSeeker) (*Writer, error) {
	// Seek to the end
	offset, err := w.Seek(0, 2)
	if err != nil {
		return nil, err
	}
	return &Writer{offset, nil, w}, nil
}

// Not thread-safe
func (w *Writer) Append() (offset int64, value io.Writer, err error) {
	if w.ongoingWrite != nil {
		if err := w.ongoingWrite.finalize(); err != nil {
			return 0, nil, err
		}
	}
	offset = w.offset
	w.ongoingWrite = &recordWriter{w, w.offset, make([]byte, 0, 1000)}
	value = w.ongoingWrite
	err = nil
	return
}

func (w *Writer) Flush() error {
	if w.ongoingWrite != nil {
		if err := w.ongoingWrite.finalize(); err != nil {
			return err
		}
	}
	f, ok := w.w.(flusher)
	if ok {
		return f.Flush()
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gregex_test

import (
	"bytes"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/text/gregex"
	"strings"
)

func ExampleIsMatch() {
	patternStr := `\d+`
	g.Dump(gregex.IsMatch(patternStr, []byte("hello 2022! hello gf!")))
	g.Dump(gregex.IsMatch(patternStr, nil))
	g.Dump(gregex.IsMatch(patternStr, []byte("hello gf!")))

	// Output:
	// true
	// false
	// false
}

func ExampleIsMatchString() {
	patternStr := `\d+`
	g.Dump(gregex.IsMatchString(patternStr, "hello 2022! hello gf!"))
	g.Dump(gregex.IsMatchString(patternStr, "hello gf!"))
	g.Dump(gregex.IsMatchString(patternStr, ""))

	// Output:
	// true
	// false
	// false
}

func ExampleMatch() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	// This method looks for the first match index
	result, err := gregex.Match(patternStr, []byte(matchStr))
	g.Dump(result)
	g.Dump(err)

	// Output:
	// ["pageId=1114219","pageId","1114219"]
	// <nil>
}

func ExampleMatchString() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	// This method looks for the first match index
	result, err := gregex.MatchString(patternStr, matchStr)
	g.Dump(result)
	g.Dump(err)

	// Output:
	// ["pageId=1114219","pageId","1114219"]
	// <nil>
}

func ExampleMatchAll() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	result, err := gregex.MatchAll(patternStr, []byte(matchStr))
	g.Dump(result)
	g.Dump(err)

	// Output:
	// [["pageId=1114219","pageId","1114219",],["searchId=8QC5D1D2E","searchId","8QC5D1D2E"]]
	// <nil>
}

func ExampleMatchAllString() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	result, err := gregex.MatchAllString(patternStr, matchStr)
	g.Dump(result)
	g.Dump(err)

	// Output:
	// [["pageId=1114219","pageId","1114219",],["searchId=8QC5D1D2E","searchId","8QC5D1D2E"]]
	// <nil>
}

func ExampleQuote() {
	result := gregex.Quote(`[1-9]\d+`)
	g.Dump(result)

	// Output:
	// "\[1-9\]\\d\+"
}

func ExampleReplace() {
	var (
		patternStr  = `\d+`
		str         = "hello gf 2020!"
		repStr      = "2021"
		result, err = gregex.Replace(patternStr, []byte(repStr), []byte(str))
	)
	g.Dump(err)
	g.Dump(result)

	// Output:
	// <nil>
	// "hello gf 2021!"
}

func ExampleReplaceFunc() {
	// In contrast to [ExampleReplaceFunc]
	// the result contains the `pattern' of all subpattern that use the matching function
	result, err := gregex.ReplaceFuncMatch(`(\d+)~(\d+)`, []byte("hello gf 2018~2020!"), func(match [][]byte) []byte {
		g.Dump(match)
		match[2] = []byte("2021")
		return bytes.Join(match[1:], []byte("~"))
	})
	g.Dump(result)
	g.Dump(err)

	// Output::
	// [
	//     "2018~2020",
	//     "2018",
	//     "2020",
	// ]
	// "hello gf 2018~2021!"
	// <nil>
}

func ExampleReplaceFuncMatch() {
	var (
		patternStr = `(\d+)~(\d+)`
		str        = "hello gf 2018~2020!"
	)
	// In contrast to [ExampleReplaceFunc]
	// the result contains the `pattern' of all subpatterns that use the matching function
	result, err := gregex.ReplaceFuncMatch(patternStr, []byte(str), func(match [][]byte) []byte {
		g.Dump(match)
		match[2] = []byte("2021")
		return bytes.Join(match[1:], []byte("-"))
	})
	g.Dump(result)
	g.Dump(err)

	// Output:
	// ["2018~2020","2018","2020"]
	// "hello gf 2018-2021!"
	// <nil>
}

func ExampleReplaceString() {
	patternStr := `\d+`
	str := "hello gf 2020!"
	replaceStr := "2021"
	result, err := gregex.ReplaceString(patternStr, replaceStr, str)

	g.Dump(result)
	g.Dump(err)

	// Output:
	// "hello gf 2021!"
	// <nil>
}

func ExampleReplaceStringFunc() {
	replaceStrMap := map[string]string{
		"2020": "2021",
	}
	// When the regular statement can match multiple results
	// func can be used to further control the value that needs to be modified
	result, err := gregex.ReplaceStringFunc(`\d+`, `hello gf 2018~2020!`, func(b string) string {
		g.Dump(b)
		if replaceStr, ok := replaceStrMap[b]; ok {
			return replaceStr
		}
		return b
	})
	g.Dump(result)
	g.Dump(err)

	result, err = gregex.ReplaceStringFunc(`[a-z]*`, "gf@goframe.org", strings.ToUpper)
	g.Dump(result)
	g.Dump(err)

	// Output:
	// "2018"
	// "2020"
	// "hello gf 2018~2021!"
	// <nil>
	// "GF@GOFRAME.ORG"
	// <nil>
}

func ExampleReplaceStringFuncMatch() {
	var (
		patternStr = `([A-Z])\w+`
		str        = "hello Golang 2018~2021!"
	)
	// In contrast to [ExampleReplaceFunc]
	// the result contains the `pattern' of all subpatterns that use the matching function
	result, err := gregex.ReplaceStringFuncMatch(patternStr, str, func(match []string) string {
		g.Dump(match)
		match[0] = "Gf"
		return match[0]
	})
	g.Dump(result)
	g.Dump(err)

	// Output:
	// ["Golang","G"]
	// "hello Gf 2018~2021!"
	// <nil>
}

func ExampleSplit() {
	patternStr := `\d+`
	str := "hello2020gf"
	result := gregex.Split(patternStr, str)
	g.Dump(result)

	// Output:
	// ["hello","gf"]
}

func ExampleValidate() {
	// Valid match statement
	g.Dump(gregex.Validate(`\d+`))
	// Mismatched statement
	g.Dump(gregex.Validate(`[a-9]\d+`))

	// Output:
	// <nil>
	// {
	//	"Code": "invalid character class range",
	//	"Expr": "a-9"
	// }
}
<commit_msg>:memo: add example<commit_after>// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gregex_test

import (
	"bytes"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/text/gregex"
	"strings"
)

func ExampleIsMatch() {
	patternStr := `\d+`
	g.Dump(gregex.IsMatch(patternStr, []byte("hello 2022! hello gf!")))
	g.Dump(gregex.IsMatch(patternStr, nil))
	g.Dump(gregex.IsMatch(patternStr, []byte("hello gf!")))

	// Output:
	// true
	// false
	// false
}

func ExampleIsMatchString() {
	patternStr := `\d+`
	g.Dump(gregex.IsMatchString(patternStr, "hello 2022! hello gf!"))
	g.Dump(gregex.IsMatchString(patternStr, "hello gf!"))
	g.Dump(gregex.IsMatchString(patternStr, ""))

	// Output:
	// true
	// false
	// false
}

func ExampleMatch() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	// This method looks for the first match index
	result, err := gregex.Match(patternStr, []byte(matchStr))
	g.Dump(result)
	g.Dump(err)

	// Output:
	// [
	//    "pageId=1114219",
	//    "pageId",
	//    "1114219",
	// ]
	// <nil>
}

func ExampleMatchString() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	// This method looks for the first match index
	result, err := gregex.MatchString(patternStr, matchStr)
	g.Dump(result)
	g.Dump(err)

	// Output:
	// [
	//    "pageId=1114219",
	//    "pageId",
	//    "1114219",
	// ]
	// <nil>
}

func ExampleMatchAll() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	result, err := gregex.MatchAll(patternStr, []byte(matchStr))
	g.Dump(result)
	g.Dump(err)

	// Output:
	// [
	//    [
	//        "pageId=1114219",
	//        "pageId",
	//        "1114219",
	//    ],
	//    [
	//        "searchId=8QC5D1D2E",
	//        "searchId",
	//        "8QC5D1D2E",
	//    ],
	// ]
	// <nil>
}

func ExampleMatchAllString() {
	patternStr := `(\w+)=(\w+)`
	matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!"
	result, err := gregex.MatchAllString(patternStr, matchStr)
	g.Dump(result)
	g.Dump(err)

	// Output:
	// [
	//    [
	//        "pageId=1114219",
	//        "pageId",
	//        "1114219",
	//    ],
	//    [
	//        "searchId=8QC5D1D2E",
	//        "searchId",
	//        "8QC5D1D2E",
	//    ],
	// ]
	// <nil>
}

func ExampleQuote() {
	result := gregex.Quote(`[1-9]\d+`)
	g.Dump(result)

	// Output:
	// "\[1-9\]\\d\+"
}

func ExampleReplace() {
	var (
		patternStr  = `\d+`
		str         = "hello gf 2020!"
		repStr      = "2021"
		result, err = gregex.Replace(patternStr, []byte(repStr), []byte(str))
	)
	g.Dump(err)
	g.Dump(result)

	// Output:
	// <nil>
	// "hello gf 2021!"
}

func ExampleReplaceFunc() {
	// In contrast to [ExampleReplaceFunc]
	// the result contains the `pattern' of all subpattern that use the matching function
	result, err := gregex.ReplaceFuncMatch(`(\d+)~(\d+)`, []byte("hello gf 2018~2020!"), func(match [][]byte) []byte {
		g.Dump(match)
		match[2] = []byte("2021")
		return bytes.Join(match[1:], []byte("~"))
	})
	g.Dump(result)
	g.Dump(err)

	// Output:
	// [
	//    "2018~2020",
	//    "2018",
	//    "2020",
	// ]
	// "hello gf 2018~2021!"
	// <nil>
}

func ExampleReplaceFuncMatch() {
	var (
		patternStr = `(\d+)~(\d+)`
		str        = "hello gf 2018~2020!"
	)
	// In contrast to [ExampleReplaceFunc]
	// the result contains the `pattern' of all subpatterns that use the matching function
	result, err := gregex.ReplaceFuncMatch(patternStr, []byte(str), func(match [][]byte) []byte {
		g.Dump(match)
		match[2] = []byte("2021")
		return bytes.Join(match[1:], []byte("-"))
	})
	g.Dump(result)
	g.Dump(err)

	// Output:
	// [
	//    "2018~2020",
	//    "2018",
	//    "2020",
	// ]
	// "hello gf 2018-2021!"
	// <nil>
}

func ExampleReplaceString() {
	patternStr := `\d+`
	str := "hello gf 2020!"
	replaceStr := "2021"
	result, err := gregex.ReplaceString(patternStr, replaceStr, str)

	g.Dump(result)
	g.Dump(err)

	// Output:
	// "hello gf 2021!"
	// <nil>
}

func ExampleReplaceStringFunc() {
	replaceStrMap := map[string]string{
		"2020": "2021",
	}
	// When the regular statement can match multiple results
	// func can be used to further control the value that needs to be modified
	result, err := gregex.ReplaceStringFunc(`\d+`, `hello gf 2018~2020!`, func(b string) string {
		g.Dump(b)
		if replaceStr, ok := replaceStrMap[b]; ok {
			return replaceStr
		}
		return b
	})
	g.Dump(result)
	g.Dump(err)

	result, err = gregex.ReplaceStringFunc(`[a-z]*`, "gf@goframe.org", strings.ToUpper)
	g.Dump(result)
	g.Dump(err)

	// Output:
	// "2018"
	// "2020"
	// "hello gf 2018~2021!"
	// <nil>
	// "GF@GOFRAME.ORG"
	// <nil>
}

func ExampleReplaceStringFuncMatch() {
	var (
		patternStr = `([A-Z])\w+`
		str        = "hello Golang 2018~2021!"
	)
	// In contrast to [ExampleReplaceFunc]
	// the result contains the `pattern' of all subpatterns that use the matching function
	result, err := gregex.ReplaceStringFuncMatch(patternStr, str, func(match []string) string {
		g.Dump(match)
		match[0] = "Gf"
		return match[0]
	})
	g.Dump(result)
	g.Dump(err)

	// Output:
	// [
	//    "Golang",
	//    "G",
	// ]
	// "hello Gf 2018~2021!"
	// <nil>
}

func ExampleSplit() {
	patternStr := `\d+`
	str := "hello2020gf"
	result := gregex.Split(patternStr, str)
	g.Dump(result)

	// Output:
	// [
	//    "hello",
	//    "gf",
	// ]
}

func ExampleValidate() {
	// Valid match statement
	g.Dump(gregex.Validate(`\d+`))
	// Mismatched statement
	g.Dump(gregex.Validate(`[a-9]\d+`))

	// Output:
	// <nil>
	// {
	//    Code: "invalid character class range",
	//    Expr: "a-9",
	// }
}
<|endoftext|>


<commit_before>package dsdk

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	_path "path"
	"strings"

	uuid "github.com/google/uuid"
)

var (
	EndLogging = make(chan bool)
)

type LogsUpload struct {
	Path string `json:"path,omitempty"`
}

type LogsUploadRequest struct {
	Ctxt  context.Context
	Files []string
}

func newLogsUpload(path string) *LogsUpload {
	return &LogsUpload{
		Path: _path.Join(path, "logs_upload"),
	}
}

func logsUpload(ctxt context.Context, file string) error {
	conn := GetConn(ctxt)
	tid, ok := ctxt.Value("tid").(string)
	if !ok {
		tid = "nil"
	}
	reqId := uuid.Must(uuid.NewRandom()).String()
	var err error
	if conn.apikey == "" {
		if _, err = conn.Login(ctxt); err != nil {
			return err
		}
	}
	key := conn.apikey
	conn.baseUrl.Path = _path.Join(conn.baseUrl.Path, "logs_upload")
	url := conn.baseUrl.String()

	var b bytes.Buffer
	w := multipart.NewWriter(&b)

	f, err := os.Open(file)
	if err != nil {
		return err
	}
	defer f.Close()
	err = w.WriteField("ecosystem", "kubernetes")
	if err != nil {
		return err
	}

	fw, err := w.CreateFormFile("logs.tar.gz", file)
	if err != nil {
		return err
	}
	if _, err = io.Copy(fw, f); err != nil {
		return err
	}
	// Don't forget to close the multipart writer.
	// If you don't close it, your request will be missing the terminating boundary.
	w.Close()

	// Now that you have a form, you can submit it to your handler.
	req, err := http.NewRequest(http.MethodPut, url, &b)
	if err != nil {
		return err
	}
	// Don't forget to set the content type, this will contain the boundary.
	req.Header.Set("Content-Type", w.FormDataContentType())
	req.Header.Set("Auth-Token", key)

	// Submit the request
	client := &http.Client{}
	sheaders, err := json.Marshal(req.Header)
	if err != nil {
		Log().Errorf("Couldn't stringify headers, %s", req.Header)
	}
	Log().Debugf(strings.Join([]string{"\nDatera Trace ID: %s",
		"Datera Request ID: %s",
		"Datera Request URL: %s",
		"Datera Request Method: %s",
		"Datera Request Payload: %s",
		"Datera Request Headers: %s\n"}, "\n"),
		tid, reqId, url, http.MethodPut, req.Body, sheaders)
	res, err := client.Do(req)
	if err != nil {
		return err
	}
	Log().Debugf("Status Code: %d", res.StatusCode)
	// Check the response
	if res.StatusCode != http.StatusOK {
		err = fmt.Errorf("bad status: %s", res.Status)
		bodyBytes, _ := ioutil.ReadAll(res.Body)
		Log().Error(err)
		Log().Error(string(bodyBytes))
		return err
	}
	return nil
}

func rotateLogs(rule string) error {
	if _, err := RunCmd("logrotate", "-f", rule); err != nil {
		return err
	}
	return nil
}

func (e *LogsUpload) Upload(ro *LogsUploadRequest) (*LogsUpload, *ApiErrorResponse, error) {
	return nil, nil, logsUpload(ro.Ctxt, ro.Files[0])
}

func (e *LogsUpload) RotateUploadRemove(ctxt context.Context, rule, rotated string) error {
	if err := rotateLogs(rule); err != nil {
		return err
	}

	// Determine if filtered logs exist
	lf, err := os.Open(rotated)
	if err != nil {
		return err
	}
	defer os.Remove(rotated)
	defer lf.Close()
	fstat, err := lf.Stat()
	if err != nil {
		return err
	}
	// Even a single line of logs will be greater than 100 bytes
	if fstat.Size() > 100 {
		Log().Debug("Uploading logs")
		go func() {
			_, apierr, err := e.Upload(&LogsUploadRequest{
				Ctxt:  ctxt,
				Files: []string{rotated},
			})
			if apierr != nil {
				Log().Errorf("%s\n", Pretty(apierr))
			}
			if err != nil {
				Log().Error(err)
			}
		}()
	} else {
		Log().Debugf("No new filtered logs detected.  Size: %d\n", fstat.Size())
	}
	return nil
}
<commit_msg>Removing goroutine call from log upload<commit_after>package dsdk

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	_path "path"
	"strings"

	uuid "github.com/google/uuid"
)

var (
	EndLogging = make(chan bool)
)

type LogsUpload struct {
	Path string `json:"path,omitempty"`
}

type LogsUploadRequest struct {
	Ctxt  context.Context
	Files []string
}

func newLogsUpload(path string) *LogsUpload {
	return &LogsUpload{
		Path: _path.Join(path, "logs_upload"),
	}
}

func logsUpload(ctxt context.Context, file string) error {
	conn := GetConn(ctxt)
	tid, ok := ctxt.Value("tid").(string)
	if !ok {
		tid = "nil"
	}
	reqId := uuid.Must(uuid.NewRandom()).String()
	var err error
	if conn.apikey == "" {
		if _, err = conn.Login(ctxt); err != nil {
			return err
		}
	}
	key := conn.apikey
	conn.baseUrl.Path = _path.Join(conn.baseUrl.Path, "logs_upload")
	url := conn.baseUrl.String()

	var b bytes.Buffer
	w := multipart.NewWriter(&b)

	f, err := os.Open(file)
	if err != nil {
		return err
	}
	defer f.Close()
	err = w.WriteField("ecosystem", "kubernetes")
	if err != nil {
		return err
	}

	fw, err := w.CreateFormFile("logs.tar.gz", file)
	if err != nil {
		return err
	}
	if _, err = io.Copy(fw, f); err != nil {
		return err
	}
	// Don't forget to close the multipart writer.
	// If you don't close it, your request will be missing the terminating boundary.
	w.Close()

	// Now that you have a form, you can submit it to your handler.
	req, err := http.NewRequest(http.MethodPut, url, &b)
	if err != nil {
		return err
	}
	// Don't forget to set the content type, this will contain the boundary.
	req.Header.Set("Content-Type", w.FormDataContentType())
	req.Header.Set("Auth-Token", key)

	// Submit the request
	client := &http.Client{}
	sheaders, err := json.Marshal(req.Header)
	if err != nil {
		Log().Errorf("Couldn't stringify headers, %s", req.Header)
	}
	Log().Debugf(strings.Join([]string{"\nDatera Trace ID: %s",
		"Datera Request ID: %s",
		"Datera Request URL: %s",
		"Datera Request Method: %s",
		"Datera Request Payload: %s",
		"Datera Request Headers: %s\n"}, "\n"),
		tid, reqId, url, http.MethodPut, req.Body, sheaders)
	res, err := client.Do(req)
	if err != nil {
		return err
	}
	Log().Debugf("Status Code: %d", res.StatusCode)
	// Check the response
	if res.StatusCode != http.StatusOK {
		err = fmt.Errorf("bad status: %s", res.Status)
		bodyBytes, _ := ioutil.ReadAll(res.Body)
		Log().Error(err)
		Log().Error(string(bodyBytes))
		return err
	}
	return nil
}

func rotateLogs(rule string) error {
	if _, err := RunCmd("logrotate", "-f", rule); err != nil {
		return err
	}
	return nil
}

func (e *LogsUpload) Upload(ro *LogsUploadRequest) (*LogsUpload, *ApiErrorResponse, error) {
	return nil, nil, logsUpload(ro.Ctxt, ro.Files[0])
}

func (e *LogsUpload) RotateUploadRemove(ctxt context.Context, rule, rotated string) error {
	if err := rotateLogs(rule); err != nil {
		return err
	}

	// Determine if filtered logs exist
	lf, err := os.Open(rotated)
	if err != nil {
		return err
	}
	defer os.Remove(rotated)
	defer lf.Close()
	fstat, err := lf.Stat()
	if err != nil {
		return err
	}
	// Even a single line of logs will be greater than 100 bytes
	if fstat.Size() > 100 {
		Log().Debug("Uploading logs")
		_, apierr, err := e.Upload(&LogsUploadRequest{
			Ctxt:  ctxt,
			Files: []string{rotated},
		})
		if apierr != nil {
			Log().Errorf("%s\n", Pretty(apierr))
		}
		if err != nil {
			Log().Error(err)
		}
	} else {
		Log().Debugf("No new filtered logs detected.  Size: %d\n", fstat.Size())
	}
	return nil
}
<|endoftext|>


<commit_before>package rest

import (
	"code.google.com/p/goprotobuf/proto"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/golang/glog"
	"github.com/gorilla/mux"
	"github.com/qorio/omni/api"
	"github.com/qorio/omni/auth"
	"io"
	"io/ioutil"
	"net/http"
	"reflect"
	"strconv"
	"strings"
)

var (
	ErrMissingInput                 = errors.New("error-missing-input")
	ErrUnknownContentType           = errors.New("error-no-content-type")
	ErrUnknownMethod                = errors.New("error-unknown-method")
	ErrIncompatibleType             = errors.New("error-incompatible-type")
	ErrNotSupportedUrlParameterType = errors.New("error-not-supported-url-query-param-type")
	ErrNoHttpHeaderSpec             = errors.New("error-no-http-header-spec")
)

var (
	json_marshaler = func(contentType string, resp http.ResponseWriter, typed interface{}) error {
		if buff, err := json.Marshal(typed); err == nil {
			resp.Header().Add("Content-Type", contentType)
			resp.Write(buff)
			return nil
		} else {
			return err
		}
	}

	json_unmarshaler = func(body io.ReadCloser, typed interface{}) error {
		dec := json.NewDecoder(body)
		return dec.Decode(typed)
	}

	proto_marshaler = func(contentType string, resp http.ResponseWriter, any interface{}) error {
		typed, ok := any.(proto.Message)
		if !ok {
			return ErrIncompatibleType
		}
		if buff, err := proto.Marshal(typed); err == nil {
			resp.Header().Add("Content-Type", contentType)
			resp.Write(buff)
			return nil
		} else {
			return err
		}
	}

	proto_unmarshaler = func(body io.ReadCloser, any interface{}) error {
		typed, ok := any.(proto.Message)
		if !ok {
			return ErrIncompatibleType
		}
		buff, err := ioutil.ReadAll(body)
		if err != nil {
			return err
		}
		return proto.Unmarshal(buff, typed)
	}

	marshalers = map[string]func(string, http.ResponseWriter, interface{}) error{
		"":                     json_marshaler,
		"application/json":     json_marshaler,
		"application/protobuf": proto_marshaler,
	}

	unmarshalers = map[string]func(io.ReadCloser, interface{}) error{
		"":                     json_unmarshaler,
		"application/json":     json_unmarshaler,
		"application/protobuf": proto_unmarshaler,
	}
)

type Handler func(http.ResponseWriter, *http.Request)

type ServiceMethodImpl struct {
	Api                  api.MethodSpec // note this is by copy -- so that behavior is deterministic after initialization
	Handler              Handler
	AuthenticatedHandler auth.HttpHandler
	ServiceId            string
}

func SetHandler(m api.MethodSpec, h Handler) *ServiceMethodImpl {
	if m.AuthScope != "" {
		panic(errors.New(fmt.Sprintf("Method %s has oauth scopes but binding to unauthed handler.", m)))
	}
	return &ServiceMethodImpl{
		Api:     m,
		Handler: h,
	}
}

func SetAuthenticatedHandler(serviceId string, m api.MethodSpec, h auth.HttpHandler) *ServiceMethodImpl {
	if m.AuthScope == "" {
		panic(errors.New(fmt.Sprintf("Method %s has no oauth scopes but binding to authenticated handler.", m)))
	}
	return &ServiceMethodImpl{
		Api:                  m,
		AuthenticatedHandler: h,
		ServiceId:            serviceId,
	}
}

type EngineEvent struct {
	Domain        string
	Service       string
	ServiceMethod api.ServiceMethod
	Body          interface{}
}

type Engine interface {
	Bind(...*ServiceMethodImpl)
	ServeHTTP(http.ResponseWriter, *http.Request)
	GetUrlParameter(*http.Request, string) string
	GetHttpHeaders(*http.Request, api.HttpHeaders) (map[string][]string, error)
	GetUrlQueries(*http.Request, api.UrlQueries) (api.UrlQueries, error)
	Unmarshal(*http.Request, proto.Message) error
	Marshal(*http.Request, proto.Message, http.ResponseWriter) error
	UnmarshalJSON(*http.Request, interface{}) error
	MarshalJSON(*http.Request, interface{}, http.ResponseWriter) error
	HandleError(http.ResponseWriter, *http.Request, string, int) error
	EventChannel() chan<- *EngineEvent
}

type engine struct {
	spec       *api.ServiceMethods
	router     *mux.Router
	auth       auth.Service
	event_chan chan *EngineEvent
	done_chan  chan bool
	webhooks   WebhookManager
}

func NewEngine(spec *api.ServiceMethods, auth auth.Service, webhooks WebhookManager) *engine {
	e := &engine{
		spec:       spec,
		router:     mux.NewRouter(),
		auth:       auth,
		event_chan: make(chan *EngineEvent),
		done_chan:  make(chan bool),
		webhooks:   webhooks,
	}
	return e
}

func (this *engine) Router() *mux.Router {
	return this.router
}

func (this *engine) ServeHTTP(resp http.ResponseWriter, request *http.Request) {
	// Also start listening on the event channel for any webhook calls
	go func() {
		for {
			select {

			case message := <-this.event_chan:
				this.do_callback(message)

			case done := <-this.done_chan:
				if done {
					glog.Infoln("REST engine event channel stopped.")
					return
				}
			}
		}
	}()

	this.router.ServeHTTP(resp, request)
}

func (this *engine) GetUrlParameter(req *http.Request, key string) string {
	vars := mux.Vars(req)
	if val, has := vars[key]; has {
		return val
	} else if err := req.ParseForm(); err == nil {
		if _, has := req.Form[key]; has {
			return req.Form[key][0]
		}
	}
	return ""
}

func (this *engine) GetHttpHeaders(req *http.Request, m api.HttpHeaders) (map[string][]string, error) {
	if m == nil {
		return nil, ErrNoHttpHeaderSpec
	}
	q := make(map[string][]string)
	for k, h := range m {
		if l, ok := req.Header[h]; ok {
			// Really strange -- you can have a 1 element list with value that's actually comma-delimited.
			if len(l) == 1 {
				q[k] = strings.Split(l[0], ", ")
			} else {
				q[k] = l
			}
		}
	}
	return q, nil
}

func (this *engine) GetUrlQueries(req *http.Request, m api.UrlQueries) (api.UrlQueries, error) {
	result := make(api.UrlQueries)
	for key, default_value := range m {
		actual := this.GetUrlParameter(req, key)
		if actual != "" {
			// Check the type and do conversion
			switch reflect.TypeOf(default_value).Kind() {
			case reflect.Bool:
				if v, err := strconv.ParseBool(actual); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			case reflect.String:
				result[key] = actual
			case reflect.Int:
				if v, err := strconv.Atoi(actual); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			case reflect.Float32:
				if v, err := strconv.ParseFloat(actual, 32); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			case reflect.Float64:
				if v, err := strconv.ParseFloat(actual, 64); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			default:
				return nil, ErrNotSupportedUrlParameterType
			}

		} else {
			result[key] = default_value
		}
	}
	return result, nil
}

func (this *engine) Bind(endpoints ...*ServiceMethodImpl) {
	for i, ep := range endpoints {
		switch {
		case ep.Handler != nil:
			this.router.HandleFunc(ep.Api.UrlRoute, ep.Handler).Methods(string(ep.Api.HttpMethod))

		case ep.AuthenticatedHandler != nil:
			this.router.HandleFunc(ep.Api.UrlRoute,
				this.auth.RequiresAuth(ep.Api.AuthScope, func(token *auth.Token) []string {
					return strings.Split(token.GetString(ep.ServiceId+"/@scopes"), ",")
				}, ep.AuthenticatedHandler)).Methods(string(ep.Api.HttpMethod))

		case ep.Handler == nil && ep.AuthenticatedHandler == nil:
			panic(errors.New(fmt.Sprintf("No implementation for REST endpoint[%d]: %s", i, ep)))
		}

		// check the content type
		for _, ct := range ep.Api.ContentTypes {
			if _, has := marshalers[ct]; !has {
				panic(errors.New(fmt.Sprintf("Bad content type: %s", ct)))
			}
			if _, has := unmarshalers[ct]; !has {
				panic(errors.New(fmt.Sprintf("Bad content type: %s", ct)))
			}
		}
	}
}

func content_type_from_request(req *http.Request) (t string) {
	if t = req.Header.Get("Accept"); t == "" {
		return req.Header.Get("Content-Type")
	}
	return
}

func (this *engine) Unmarshal(req *http.Request, typed proto.Message) (err error) {
	contentType := content_type_from_request(req)
	if unmarshaler, has := unmarshalers[contentType]; has {
		return unmarshaler(req.Body, typed)
	} else {
		return ErrUnknownContentType
	}
}

func (this *engine) Marshal(req *http.Request, typed proto.Message, resp http.ResponseWriter) (err error) {
	contentType := content_type_from_request(req)
	if marshaler, has := marshalers[contentType]; has {
		return marshaler(contentType, resp, typed)
	} else {
		return ErrUnknownContentType
	}
}

func (this *engine) UnmarshalJSON(req *http.Request, any interface{}) (err error) {
	contentType := content_type_from_request(req)
	if unmarshaler, has := unmarshalers[contentType]; has {
		return unmarshaler(req.Body, any)
	} else {
		return ErrUnknownContentType
	}
}

func (this *engine) MarshalJSON(req *http.Request, any interface{}, resp http.ResponseWriter) (err error) {
	if buff, err := json.Marshal(any); err == nil {
		resp.Header().Add("Content-Type", "application/json")
		resp.Write(buff)
		return nil
	} else {
		return err
	}
}

func (this *engine) HandleError(resp http.ResponseWriter, req *http.Request, message string, code int) (err error) {
	resp.WriteHeader(code)
	resp.Write([]byte(fmt.Sprintf("{\"error\":\"%s\"}", message)))
	return
}

func (this *engine) EventChannel() chan<- *EngineEvent {
	return this.event_chan
}

func (this *engine) do_callback(message *EngineEvent) error {
	if this.webhooks == nil {
		return nil
	}
	if m, has := (*this.spec)[message.ServiceMethod]; has {
		if m.CallbackEvent != api.EventKey("") {
			return this.webhooks.Send(message.Domain, message.Service, string(m.CallbackEvent), message.Body, m.CallbackBodyTemplate)
		}
	}
	return ErrUnknownMethod
}
<commit_msg>Proper handling of Content-Type vs Accept headers<commit_after>package rest

import (
	"code.google.com/p/goprotobuf/proto"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/golang/glog"
	"github.com/gorilla/mux"
	"github.com/qorio/omni/api"
	"github.com/qorio/omni/auth"
	"io"
	"io/ioutil"
	"net/http"
	"reflect"
	"strconv"
	"strings"
)

var (
	ErrMissingInput                 = errors.New("error-missing-input")
	ErrUnknownContentType           = errors.New("error-no-content-type")
	ErrUnknownMethod                = errors.New("error-unknown-method")
	ErrIncompatibleType             = errors.New("error-incompatible-type")
	ErrNotSupportedUrlParameterType = errors.New("error-not-supported-url-query-param-type")
	ErrNoHttpHeaderSpec             = errors.New("error-no-http-header-spec")
)

var (
	json_marshaler = func(contentType string, resp http.ResponseWriter, typed interface{}) error {
		if buff, err := json.Marshal(typed); err == nil {
			resp.Header().Add("Content-Type", contentType)
			resp.Write(buff)
			return nil
		} else {
			return err
		}
	}

	json_unmarshaler = func(body io.ReadCloser, typed interface{}) error {
		dec := json.NewDecoder(body)
		return dec.Decode(typed)
	}

	proto_marshaler = func(contentType string, resp http.ResponseWriter, any interface{}) error {
		typed, ok := any.(proto.Message)
		if !ok {
			return ErrIncompatibleType
		}
		if buff, err := proto.Marshal(typed); err == nil {
			resp.Header().Add("Content-Type", contentType)
			resp.Write(buff)
			return nil
		} else {
			return err
		}
	}

	proto_unmarshaler = func(body io.ReadCloser, any interface{}) error {
		typed, ok := any.(proto.Message)
		if !ok {
			return ErrIncompatibleType
		}
		buff, err := ioutil.ReadAll(body)
		if err != nil {
			return err
		}
		return proto.Unmarshal(buff, typed)
	}

	marshalers = map[string]func(string, http.ResponseWriter, interface{}) error{
		"":                     json_marshaler,
		"application/json":     json_marshaler,
		"application/protobuf": proto_marshaler,
	}

	unmarshalers = map[string]func(io.ReadCloser, interface{}) error{
		"":                     json_unmarshaler,
		"application/json":     json_unmarshaler,
		"application/protobuf": proto_unmarshaler,
	}
)

type Handler func(http.ResponseWriter, *http.Request)

type ServiceMethodImpl struct {
	Api                  api.MethodSpec // note this is by copy -- so that behavior is deterministic after initialization
	Handler              Handler
	AuthenticatedHandler auth.HttpHandler
	ServiceId            string
}

func SetHandler(m api.MethodSpec, h Handler) *ServiceMethodImpl {
	if m.AuthScope != "" {
		panic(errors.New(fmt.Sprintf("Method %s has oauth scopes but binding to unauthed handler.", m)))
	}
	return &ServiceMethodImpl{
		Api:     m,
		Handler: h,
	}
}

func SetAuthenticatedHandler(serviceId string, m api.MethodSpec, h auth.HttpHandler) *ServiceMethodImpl {
	if m.AuthScope == "" {
		panic(errors.New(fmt.Sprintf("Method %s has no oauth scopes but binding to authenticated handler.", m)))
	}
	return &ServiceMethodImpl{
		Api:                  m,
		AuthenticatedHandler: h,
		ServiceId:            serviceId,
	}
}

type EngineEvent struct {
	Domain        string
	Service       string
	ServiceMethod api.ServiceMethod
	Body          interface{}
}

type Engine interface {
	Bind(...*ServiceMethodImpl)
	ServeHTTP(http.ResponseWriter, *http.Request)
	GetUrlParameter(*http.Request, string) string
	GetHttpHeaders(*http.Request, api.HttpHeaders) (map[string][]string, error)
	GetUrlQueries(*http.Request, api.UrlQueries) (api.UrlQueries, error)
	Unmarshal(*http.Request, proto.Message) error
	Marshal(*http.Request, proto.Message, http.ResponseWriter) error
	UnmarshalJSON(*http.Request, interface{}) error
	MarshalJSON(*http.Request, interface{}, http.ResponseWriter) error
	HandleError(http.ResponseWriter, *http.Request, string, int) error
	EventChannel() chan<- *EngineEvent
}

type engine struct {
	spec       *api.ServiceMethods
	router     *mux.Router
	auth       auth.Service
	event_chan chan *EngineEvent
	done_chan  chan bool
	webhooks   WebhookManager
}

func NewEngine(spec *api.ServiceMethods, auth auth.Service, webhooks WebhookManager) *engine {
	e := &engine{
		spec:       spec,
		router:     mux.NewRouter(),
		auth:       auth,
		event_chan: make(chan *EngineEvent),
		done_chan:  make(chan bool),
		webhooks:   webhooks,
	}
	return e
}

func (this *engine) Router() *mux.Router {
	return this.router
}

func (this *engine) ServeHTTP(resp http.ResponseWriter, request *http.Request) {
	// Also start listening on the event channel for any webhook calls
	go func() {
		for {
			select {

			case message := <-this.event_chan:
				this.do_callback(message)

			case done := <-this.done_chan:
				if done {
					glog.Infoln("REST engine event channel stopped.")
					return
				}
			}
		}
	}()

	this.router.ServeHTTP(resp, request)
}

func (this *engine) GetUrlParameter(req *http.Request, key string) string {
	vars := mux.Vars(req)
	if val, has := vars[key]; has {
		return val
	} else if err := req.ParseForm(); err == nil {
		if _, has := req.Form[key]; has {
			return req.Form[key][0]
		}
	}
	return ""
}

func (this *engine) GetHttpHeaders(req *http.Request, m api.HttpHeaders) (map[string][]string, error) {
	if m == nil {
		return nil, ErrNoHttpHeaderSpec
	}
	q := make(map[string][]string)
	for k, h := range m {
		if l, ok := req.Header[h]; ok {
			// Really strange -- you can have a 1 element list with value that's actually comma-delimited.
			if len(l) == 1 {
				q[k] = strings.Split(l[0], ", ")
			} else {
				q[k] = l
			}
		}
	}
	return q, nil
}

func (this *engine) GetUrlQueries(req *http.Request, m api.UrlQueries) (api.UrlQueries, error) {
	result := make(api.UrlQueries)
	for key, default_value := range m {
		actual := this.GetUrlParameter(req, key)
		if actual != "" {
			// Check the type and do conversion
			switch reflect.TypeOf(default_value).Kind() {
			case reflect.Bool:
				if v, err := strconv.ParseBool(actual); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			case reflect.String:
				result[key] = actual
			case reflect.Int:
				if v, err := strconv.Atoi(actual); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			case reflect.Float32:
				if v, err := strconv.ParseFloat(actual, 32); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			case reflect.Float64:
				if v, err := strconv.ParseFloat(actual, 64); err != nil {
					return nil, err
				} else {
					result[key] = v
				}
			default:
				return nil, ErrNotSupportedUrlParameterType
			}

		} else {
			result[key] = default_value
		}
	}
	return result, nil
}

func (this *engine) Bind(endpoints ...*ServiceMethodImpl) {
	for i, ep := range endpoints {
		switch {
		case ep.Handler != nil:
			this.router.HandleFunc(ep.Api.UrlRoute, ep.Handler).Methods(string(ep.Api.HttpMethod))

		case ep.AuthenticatedHandler != nil:
			this.router.HandleFunc(ep.Api.UrlRoute,
				this.auth.RequiresAuth(ep.Api.AuthScope, func(token *auth.Token) []string {
					return strings.Split(token.GetString(ep.ServiceId+"/@scopes"), ",")
				}, ep.AuthenticatedHandler)).Methods(string(ep.Api.HttpMethod))

		case ep.Handler == nil && ep.AuthenticatedHandler == nil:
			panic(errors.New(fmt.Sprintf("No implementation for REST endpoint[%d]: %s", i, ep)))
		}

		// check the content type
		for _, ct := range ep.Api.ContentTypes {
			if _, has := marshalers[ct]; !has {
				panic(errors.New(fmt.Sprintf("Bad content type: %s", ct)))
			}
			if _, has := unmarshalers[ct]; !has {
				panic(errors.New(fmt.Sprintf("Bad content type: %s", ct)))
			}
		}
	}
}

func content_type_for_request(req *http.Request) string {
	t := "application/json"
	if req.Method == "POST" || req.Method == "PUT" {
		t = req.Header.Get("Content-Type")
	}
	if t == "*/*" || t == "" {
		return "application/json" // default
	} else {
		return t
	}
}

func content_type_for_response(req *http.Request) string {
	t := req.Header.Get("Accept")
	if t == "*/*" || t == "" {
		return "application/json" // default
	} else {
		return t
	}
}

func (this *engine) Unmarshal(req *http.Request, typed proto.Message) (err error) {
	contentType := content_type_for_request(req)
	if unmarshaler, has := unmarshalers[contentType]; has {
		return unmarshaler(req.Body, typed)
	} else {
		return ErrUnknownContentType
	}
}

func (this *engine) Marshal(req *http.Request, typed proto.Message, resp http.ResponseWriter) (err error) {
	contentType := content_type_for_response(req)
	if marshaler, has := marshalers[contentType]; has {
		return marshaler(contentType, resp, typed)
	} else {
		return ErrUnknownContentType
	}
}

func (this *engine) UnmarshalJSON(req *http.Request, any interface{}) (err error) {
	contentType := content_type_for_request(req)
	if unmarshaler, has := unmarshalers[contentType]; has {
		return unmarshaler(req.Body, any)
	} else {
		return ErrUnknownContentType
	}
}

func (this *engine) MarshalJSON(req *http.Request, any interface{}, resp http.ResponseWriter) (err error) {
	if buff, err := json.Marshal(any); err == nil {
		resp.Header().Add("Content-Type", "application/json")
		resp.Write(buff)
		return nil
	} else {
		return err
	}
}

func (this *engine) HandleError(resp http.ResponseWriter, req *http.Request, message string, code int) (err error) {
	resp.WriteHeader(code)
	resp.Write([]byte(fmt.Sprintf("{\"error\":\"%s\"}", message)))
	return
}

func (this *engine) EventChannel() chan<- *EngineEvent {
	return this.event_chan
}

func (this *engine) do_callback(message *EngineEvent) error {
	if this.webhooks == nil {
		return nil
	}
	if m, has := (*this.spec)[message.ServiceMethod]; has {
		if m.CallbackEvent != api.EventKey("") {
			return this.webhooks.Send(message.Domain, message.Service, string(m.CallbackEvent), message.Body, m.CallbackBodyTemplate)
		}
	}
	return ErrUnknownMethod
}
<|endoftext|>


<commit_before>package db

import (
    // "log"
    "fmt"
    "strings"
    "reflect"
    "strconv"
)

type QueryBuilder struct {
    db           *DB
    tableName    string
    operation    string

    whereConditions   []map[string]interface{}

    selects      string
    limit        int
}

func (qb *QueryBuilder) Table(name string) *QueryBuilder {
    qb.tableName = name
    return qb
}

func (qb *QueryBuilder) Where(query interface{}, args ...interface{}) *QueryBuilder {
    qb.whereConditions = append(qb.whereConditions, map[string]interface{}{"query": query, "args": args})
    return qb
}

func (qb *QueryBuilder) Select(selects string) *QueryBuilder {
    qb.operation = "SELECT"
    qb.selects   = selects
    return qb
}

func (qb *QueryBuilder) Limit(limit int) *QueryBuilder {
    qb.limit = limit
    return qb
}

/* TODO 暂时默认 query 只会是string，以后再扩充功能 */
/* TODO 类型断言太恶心了，要改 */
func (qb *QueryBuilder) buildWhereCondition(cond map[string]interface{}) (sql string) {
    sql = fmt.Sprintf("(%v)", cond["query"])

    args := cond["args"].([]interface{})
    for _, arg := range args {
        switch reflect.ValueOf(arg).Kind() {
        // 数组 Eg: where("id in (?)", []int(1,2))
        case reflect.Slice:
            values := reflect.ValueOf(arg)
            var tmps []string
            for i := 0; i < values.Len(); i++ {
                tmp := values.Index(i).Interface()
                if tmp, ok := tmp.(string); ok {
                    tmps = append(tmps, "'" + tmp + "'")
                }
                if tmp, ok := tmp.(int); ok {
                    tmps = append(tmps, strconv.Itoa(tmp))
                }
            }
            sql = strings.Replace(sql, "?", strings.Join(tmps, ","), 1)
        default:
            arg = reflect.ValueOf(arg).Interface()
            if arg, ok := arg.(string); ok {
                sql = strings.Replace(sql, "?", "'" + arg + "'", 1)
            }
            if arg, ok := arg.(int); ok {
                sql = strings.Replace(sql, "?", strconv.Itoa(arg), 1)
            }
        }
    }
    return sql
}

func (qb *QueryBuilder) buildWhereSQL() (string) {
    var whereConditions []string
    for _, cond := range qb.whereConditions {
        if sql := qb.buildWhereCondition(cond); sql != "" {
            whereConditions = append(whereConditions, sql)
        }
    }
    return strings.Join(whereConditions, " AND ")
}

func (qb *QueryBuilder) buildSelect() (sql string) {
    sql = fmt.Sprintf("SELECT %v FROM %v WHERE %v", qb.selects, qb.tableName, qb.buildWhereSQL())
    return sql
}

func (qb *QueryBuilder) ToString() string {
    return qb.buildSelect())
}
<commit_msg>简单的数据连接<commit_after>package db

import (
    "fmt"
    "strings"
    "reflect"
    "strconv"
)

type QueryBuilder struct {
    db           *DB
    tableName    string
    operation    string

    whereConditions   []map[string]interface{}

    selects      string
    limit        int
}

func (qb *QueryBuilder) Table(name string) *QueryBuilder {
    qb.tableName = name
    return qb
}

func (qb *QueryBuilder) Where(query interface{}, args ...interface{}) *QueryBuilder {
    qb.whereConditions = append(qb.whereConditions, map[string]interface{}{"query": query, "args": args})
    return qb
}

func (qb *QueryBuilder) Select(selects string) *QueryBuilder {
    qb.operation = "SELECT"
    qb.selects   = selects
    return qb
}

func (qb *QueryBuilder) Limit(limit int) *QueryBuilder {
    qb.limit = limit
    return qb
}

/* TODO 暂时默认 query 只会是string，以后再扩充功能 */
/* TODO 类型断言太恶心了，要改 */
func (qb *QueryBuilder) buildWhereCondition(cond map[string]interface{}) (sql string) {
    sql = fmt.Sprintf("(%v)", cond["query"])

    args := cond["args"].([]interface{})
    for _, arg := range args {
        switch reflect.ValueOf(arg).Kind() {
        // 数组 Eg: where("id in (?)", []int(1,2))
        case reflect.Slice:
            values := reflect.ValueOf(arg)
            var tmps []string
            for i := 0; i < values.Len(); i++ {
                tmp := values.Index(i).Interface()
                if tmp, ok := tmp.(string); ok {
                    tmps = append(tmps, "'" + tmp + "'")
                }
                if tmp, ok := tmp.(int); ok {
                    tmps = append(tmps, strconv.Itoa(tmp))
                }
            }
            sql = strings.Replace(sql, "?", strings.Join(tmps, ","), 1)
        default:
            arg = reflect.ValueOf(arg).Interface()
            if arg, ok := arg.(string); ok {
                sql = strings.Replace(sql, "?", "'" + arg + "'", 1)
            }
            if arg, ok := arg.(int); ok {
                sql = strings.Replace(sql, "?", strconv.Itoa(arg), 1)
            }
        }
    }
    return sql
}

func (qb *QueryBuilder) buildWhereSQL() (string) {
    var whereConditions []string
    for _, cond := range qb.whereConditions {
        if sql := qb.buildWhereCondition(cond); sql != "" {
            whereConditions = append(whereConditions, sql)
        }
    }
    return strings.Join(whereConditions, " AND ")
}

func (qb *QueryBuilder) buildSelect() (sql string) {
    sql = fmt.Sprintf("SELECT %v FROM %v WHERE %v", qb.selects, qb.tableName, qb.buildWhereSQL())
    return sql
}

func (qb *QueryBuilder) ToString() string {
    return qb.buildSelect())
}
<|endoftext|>


<commit_before>package client

import (
	"strings"
	"time"
)

// We parse an incoming line into this struct. Line.Cmd is used as the trigger
// name for incoming event handlers, see *Conn.recv() for details.
//   Raw =~ ":nick!user@host cmd args[] :text"
//   Src == "nick!user@host"
//   Cmd == e.g. PRIVMSG, 332
type Line struct {
	Nick, Ident, Host, Src string
	Cmd, Raw               string
	Args                   []string
	Time                   *time.Time
}

// NOTE: this doesn't copy l.Time (this should be read-only anyway)
func (l *Line) Copy() *Line {
	nl := *l
	nl.Args = make([]string, len(l.Args))
	copy(nl.Args, l.Args)
	return &nl
}

func parseLine(s string) *Line {
	line := &Line{Raw: s}
	if s[0] == ':' {
		// remove a source and parse it
		if idx := strings.Index(s, " "); idx != -1 {
			line.Src, s = s[1:idx], s[idx+1:]
		} else {
			// pretty sure we shouldn't get here ...
			line.Src = s[1:]
			return line
		}

		// src can be the hostname of the irc server or a nick!user@host
		line.Host = line.Src
		nidx, uidx := strings.Index(line.Src, "!"), strings.Index(line.Src, "@")
		if uidx != -1 && nidx != -1 {
			line.Nick = line.Src[:nidx]
			line.Ident = line.Src[nidx+1 : uidx]
			line.Host = line.Src[uidx+1:]
		}
	}

	// now we're here, we've parsed a :nick!user@host or :server off
	// s should contain "cmd args[] :text"
	args := strings.SplitN(s, " :", 2)
	if len(args) > 1 {
		args = append(strings.Fields(args[0]), args[1])
	} else {
		args = strings.Fields(args[0])
	}
	line.Cmd = strings.ToUpper(args[0])
	if len(args) > 1 {
		line.Args = args[1:]
	}

	// So, I think CTCP and (in particular) CTCP ACTION are better handled as
	// separate events as opposed to forcing people to have gargantuan PRIVMSG
	// handlers to cope with the possibilities.
	if line.Cmd == "PRIVMSG" &&
		len(line.Args[1]) > 2 &&
		strings.HasPrefix(line.Args[1], "\001") &&
		strings.HasSuffix(line.Args[1], "\001") {
		// WOO, it's a CTCP message
		t := strings.SplitN(strings.Trim(line.Args[1], "\001"), " ", 2)
		if len(t) > 1 {
			// Replace the line with the unwrapped CTCP
			line.Args[1] = t[1]
		}
		if c := strings.ToUpper(t[0]); c == "ACTION" {
			// make a CTCP ACTION it's own event a-la PRIVMSG
			line.Cmd = c
		} else {
			// otherwise, dispatch a generic CTCP event that
			// contains the type of CTCP in line.Args[0]
			line.Cmd = "CTCP"
			line.Args = append([]string{c}, line.Args...)
		}
	}
	return line
}
<commit_msg>Line parsing needs to handle CTCPREPLY too.<commit_after>package client

import (
	"strings"
	"time"
)

// We parse an incoming line into this struct. Line.Cmd is used as the trigger
// name for incoming event handlers, see *Conn.recv() for details.
//   Raw =~ ":nick!user@host cmd args[] :text"
//   Src == "nick!user@host"
//   Cmd == e.g. PRIVMSG, 332
type Line struct {
	Nick, Ident, Host, Src string
	Cmd, Raw               string
	Args                   []string
	Time                   *time.Time
}

// NOTE: this doesn't copy l.Time (this should be read-only anyway)
func (l *Line) Copy() *Line {
	nl := *l
	nl.Args = make([]string, len(l.Args))
	copy(nl.Args, l.Args)
	return &nl
}

func parseLine(s string) *Line {
	line := &Line{Raw: s}
	if s[0] == ':' {
		// remove a source and parse it
		if idx := strings.Index(s, " "); idx != -1 {
			line.Src, s = s[1:idx], s[idx+1:]
		} else {
			// pretty sure we shouldn't get here ...
			line.Src = s[1:]
			return line
		}

		// src can be the hostname of the irc server or a nick!user@host
		line.Host = line.Src
		nidx, uidx := strings.Index(line.Src, "!"), strings.Index(line.Src, "@")
		if uidx != -1 && nidx != -1 {
			line.Nick = line.Src[:nidx]
			line.Ident = line.Src[nidx+1 : uidx]
			line.Host = line.Src[uidx+1:]
		}
	}

	// now we're here, we've parsed a :nick!user@host or :server off
	// s should contain "cmd args[] :text"
	args := strings.SplitN(s, " :", 2)
	if len(args) > 1 {
		args = append(strings.Fields(args[0]), args[1])
	} else {
		args = strings.Fields(args[0])
	}
	line.Cmd = strings.ToUpper(args[0])
	if len(args) > 1 {
		line.Args = args[1:]
	}

	// So, I think CTCP and (in particular) CTCP ACTION are better handled as
	// separate events as opposed to forcing people to have gargantuan
	// handlers to cope with the possibilities.
	if (line.Cmd == "PRIVMSG" || line.Cmd == "NOTICE") &&
		len(line.Args[1]) > 2 &&
		strings.HasPrefix(line.Args[1], "\001") &&
		strings.HasSuffix(line.Args[1], "\001") {
		// WOO, it's a CTCP message
		t := strings.SplitN(strings.Trim(line.Args[1], "\001"), " ", 2)
		if len(t) > 1 {
			// Replace the line with the unwrapped CTCP
			line.Args[1] = t[1]
		}
		if c := strings.ToUpper(t[0]); c == "ACTION" && line.Cmd == "PRIVMSG" {
			// make a CTCP ACTION it's own event a-la PRIVMSG
			line.Cmd = c
		} else {
			// otherwise, dispatch a generic CTCP/CTCPREPLY event that
			// contains the type of CTCP in line.Args[0]
			if line.Cmd == "PRIVMSG" {
				line.Cmd = "CTCP"
			} else {
				line.Cmd = "CTCPREPLY"
			}
			line.Args = append([]string{c}, line.Args...)
		}
	}
	return line
}
<|endoftext|>


<commit_before>package pcstat

/*
 * Copyright 2015 Albert P. Tobey <atobey@datastax.com> @AlTobey
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// https://github.com/torvalds/linux/blob/master/arch/x86/syscalls/syscall_64.tbl
const SYS_SETNS = 308
<commit_msg>Fix github link<commit_after>package pcstat

/*
 * Copyright 2015 Albert P. Tobey <atobey@datastax.com> @AlTobey
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl
const SYS_SETNS = 308
<|endoftext|>


<commit_before>package torrent

import (
	"github.com/cenkalti/rain/internal/infodownloader"
	"github.com/cenkalti/rain/internal/peerprotocol"
)

func (t *torrent) nextInfoDownload() *infodownloader.InfoDownloader {
	for pe := range t.peers {
		if _, ok := t.infoDownloaders[pe]; ok {
			continue
		}
		if pe.ExtensionHandshake == nil {
			continue
		}
		if pe.ExtensionHandshake.MetadataSize == 0 {
			continue
		}
		if pe.ExtensionHandshake.MetadataSize > int(t.session.config.MaxMetadataSize) {
			continue
		}
		_, ok := pe.ExtensionHandshake.M[peerprotocol.ExtensionKeyMetadata]
		if !ok {
			continue
		}
		t.log.Debugln("downloading info from", pe.String())
		return infodownloader.New(pe)
	}
	return nil
}
<commit_msg>add log message for large metadata sizes<commit_after>package torrent

import (
	"github.com/cenkalti/rain/internal/infodownloader"
	"github.com/cenkalti/rain/internal/peerprotocol"
)

func (t *torrent) nextInfoDownload() *infodownloader.InfoDownloader {
	for pe := range t.peers {
		if _, ok := t.infoDownloaders[pe]; ok {
			continue
		}
		if pe.ExtensionHandshake == nil {
			continue
		}
		if pe.ExtensionHandshake.MetadataSize == 0 {
			continue
		}
		if pe.ExtensionHandshake.MetadataSize > int(t.session.config.MaxMetadataSize) {
			t.log.Debugf("metadata size larger than allowed: %d", pe.ExtensionHandshake.MetadataSize)
			continue
		}
		_, ok := pe.ExtensionHandshake.M[peerprotocol.ExtensionKeyMetadata]
		if !ok {
			continue
		}
		t.log.Debugln("downloading info from", pe.String())
		return infodownloader.New(pe)
	}
	return nil
}
<|endoftext|>


<commit_before>package helixclient

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/gempir/gempbot/pkg/log"
	"github.com/nicklaw5/helix/v2"
)

// UserData exported data from twitch
type UserData struct {
	ID              string `json:"id"`
	Login           string `json:"login"`
	DisplayName     string `json:"display_name"`
	Type            string `json:"type"`
	BroadcasterType string `json:"broadcaster_type"`
	Description     string `json:"description"`
	ProfileImageURL string `json:"profile_image_url"`
	OfflineImageURL string `json:"offline_image_url"`
	ViewCount       int    `json:"view_count"`
	Email           string `json:"email"`
}

func chunkBy(items []string, chunkSize int) (chunks [][]string) {
	for chunkSize < len(items) {
		items, chunks = items[chunkSize:], append(chunks, items[0:chunkSize:chunkSize])
	}

	return append(chunks, items)
}

// GetUsersByUserIds receive userData for given ids
func (c *Client) GetUsersByUserIds(userIDs []string) (map[string]UserData, error) {
	var filteredUserIDs []string

	for _, id := range userIDs {
		if _, ok := userCacheByID[id]; !ok {
			filteredUserIDs = append(filteredUserIDs, id)
		}
	}

	if len(filteredUserIDs) > 0 {
		chunks := chunkBy(filteredUserIDs, 100)

		for _, chunk := range chunks {
			resp, err := c.Client.GetUsers(&helix.UsersParams{
				IDs: chunk,
			})
			if err != nil {
				return map[string]UserData{}, err
			}
			if resp.StatusCode == http.StatusUnauthorized {
				return map[string]UserData{}, fmt.Errorf("bad helix response: %v", resp.ErrorMessage)
			}

			log.Debugf("%d GetUsersByUserIds %v", resp.StatusCode, chunk)

			for _, user := range resp.Data.Users {
				data := &UserData{
					ID:              user.ID,
					Login:           user.Login,
					DisplayName:     user.Login,
					Type:            user.Type,
					BroadcasterType: user.BroadcasterType,
					Description:     user.Description,
					ProfileImageURL: user.ProfileImageURL,
					OfflineImageURL: user.OfflineImageURL,
					ViewCount:       user.ViewCount,
					Email:           user.Email,
				}
				cacheMutex.Lock()
				userCacheByID[user.ID] = data
				userCacheByUsername[user.Login] = data
				cacheMutex.Unlock()
			}
		}
	}

	result := make(map[string]UserData)

	for _, id := range userIDs {
		val, ok := userCacheByID[id]
		if !ok {
			log.Debugf("Could not find userId, channel might be banned: %s", id)
			continue
		}
		result[id] = *val
	}

	return result, nil
}

// GetUsersByUsernames fetches userdata from helix
func (c *Client) GetUsersByUsernames(usernames []string) (map[string]UserData, error) {
	var filteredUsernames []string

	for _, username := range usernames {
		if _, ok := userCacheByUsername[strings.ToLower(username)]; !ok {
			filteredUsernames = append(filteredUsernames, strings.ToLower(username))
		}
	}

	if len(filteredUsernames) > 0 {
		resp, err := c.Client.GetUsers(&helix.UsersParams{
			Logins: filteredUsernames,
		})
		if err != nil {
			return map[string]UserData{}, err
		}

		log.Infof("[helix] %d GetUsersByUsernames %v", resp.StatusCode, filteredUsernames)
		if resp.StatusCode > http.StatusMultipleChoices {
			return map[string]UserData{}, fmt.Errorf("bad helix response: %v", resp.ErrorMessage)
		}

		for _, user := range resp.Data.Users {
			data := &UserData{
				ID:              user.ID,
				Login:           user.Login,
				DisplayName:     user.Login,
				Type:            user.Type,
				BroadcasterType: user.BroadcasterType,
				Description:     user.Description,
				ProfileImageURL: user.ProfileImageURL,
				OfflineImageURL: user.OfflineImageURL,
				ViewCount:       user.ViewCount,
				Email:           user.Email,
			}
			userCacheByID[user.ID] = data
			userCacheByUsername[user.Login] = data
		}
	}

	result := make(map[string]UserData)

	for _, username := range usernames {
		val, ok := userCacheByUsername[strings.ToLower(username)]
		if !ok {
			log.Debugf("Could not find userId, channel might be banned: %s", username)
			continue
		}
		result[strings.ToLower(username)] = *val
	}

	return result, nil
}

func (c *Client) GetUserByUsername(username string) (UserData, error) {
	result, err := c.GetUsersByUsernames([]string{username})
	if err != nil || len(result) != 1 {
		return UserData{}, err
	}

	return result[username], nil
}

func (c *Client) GetUserByUserID(userID string) (UserData, error) {
	result, err := c.GetUsersByUserIds([]string{userID})
	if err != nil || len(result) != 1 {
		return UserData{}, err
	}

	return result[userID], nil
}
<commit_msg>better logging<commit_after>package helixclient

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/gempir/gempbot/pkg/log"
	"github.com/nicklaw5/helix/v2"
)

// UserData exported data from twitch
type UserData struct {
	ID              string `json:"id"`
	Login           string `json:"login"`
	DisplayName     string `json:"display_name"`
	Type            string `json:"type"`
	BroadcasterType string `json:"broadcaster_type"`
	Description     string `json:"description"`
	ProfileImageURL string `json:"profile_image_url"`
	OfflineImageURL string `json:"offline_image_url"`
	ViewCount       int    `json:"view_count"`
	Email           string `json:"email"`
}

func chunkBy(items []string, chunkSize int) (chunks [][]string) {
	for chunkSize < len(items) {
		items, chunks = items[chunkSize:], append(chunks, items[0:chunkSize:chunkSize])
	}

	return append(chunks, items)
}

// GetUsersByUserIds receive userData for given ids
func (c *Client) GetUsersByUserIds(userIDs []string) (map[string]UserData, error) {
	var filteredUserIDs []string

	for _, id := range userIDs {
		if _, ok := userCacheByID[id]; !ok {
			filteredUserIDs = append(filteredUserIDs, id)
		}
	}

	if len(filteredUserIDs) > 0 {
		chunks := chunkBy(filteredUserIDs, 100)

		for _, chunk := range chunks {
			resp, err := c.Client.GetUsers(&helix.UsersParams{
				IDs: chunk,
			})
			if err != nil {
				return map[string]UserData{}, err
			}
			if resp.StatusCode == http.StatusUnauthorized {
				return map[string]UserData{}, fmt.Errorf("bad helix response: %v", resp.ErrorMessage)
			}

			log.Infof("%d GetUsersByUserIds %v", resp.StatusCode, chunk)

			for _, user := range resp.Data.Users {
				data := &UserData{
					ID:              user.ID,
					Login:           user.Login,
					DisplayName:     user.Login,
					Type:            user.Type,
					BroadcasterType: user.BroadcasterType,
					Description:     user.Description,
					ProfileImageURL: user.ProfileImageURL,
					OfflineImageURL: user.OfflineImageURL,
					ViewCount:       user.ViewCount,
					Email:           user.Email,
				}
				cacheMutex.Lock()
				userCacheByID[user.ID] = data
				userCacheByUsername[user.Login] = data
				cacheMutex.Unlock()
			}
		}
	}

	result := make(map[string]UserData)

	for _, id := range userIDs {
		val, ok := userCacheByID[id]
		if !ok {
			log.Infof("Could not find userId, channel might be banned: %s", id)
			continue
		}
		result[id] = *val
	}

	return result, nil
}

// GetUsersByUsernames fetches userdata from helix
func (c *Client) GetUsersByUsernames(usernames []string) (map[string]UserData, error) {
	var filteredUsernames []string

	for _, username := range usernames {
		if _, ok := userCacheByUsername[strings.ToLower(username)]; !ok {
			filteredUsernames = append(filteredUsernames, strings.ToLower(username))
		}
	}

	if len(filteredUsernames) > 0 {
		resp, err := c.Client.GetUsers(&helix.UsersParams{
			Logins: filteredUsernames,
		})
		if err != nil {
			return map[string]UserData{}, err
		}

		log.Infof("[helix] %d GetUsersByUsernames %v", resp.StatusCode, filteredUsernames)
		if resp.StatusCode > http.StatusMultipleChoices {
			return map[string]UserData{}, fmt.Errorf("bad helix response: %v", resp.ErrorMessage)
		}

		for _, user := range resp.Data.Users {
			data := &UserData{
				ID:              user.ID,
				Login:           user.Login,
				DisplayName:     user.Login,
				Type:            user.Type,
				BroadcasterType: user.BroadcasterType,
				Description:     user.Description,
				ProfileImageURL: user.ProfileImageURL,
				OfflineImageURL: user.OfflineImageURL,
				ViewCount:       user.ViewCount,
				Email:           user.Email,
			}
			userCacheByID[user.ID] = data
			userCacheByUsername[user.Login] = data
		}
	}

	result := make(map[string]UserData)

	for _, username := range usernames {
		val, ok := userCacheByUsername[strings.ToLower(username)]
		if !ok {
			log.Infof("Could not find userId, channel might be banned: %s", username)
			continue
		}
		result[strings.ToLower(username)] = *val
	}

	return result, nil
}

func (c *Client) GetUserByUsername(username string) (UserData, error) {
	result, err := c.GetUsersByUsernames([]string{username})
	if err != nil || len(result) != 1 {
		return UserData{}, err
	}

	return result[username], nil
}

func (c *Client) GetUserByUserID(userID string) (UserData, error) {
	result, err := c.GetUsersByUserIds([]string{userID})
	if err != nil || len(result) != 1 {
		return UserData{}, err
	}

	return result[userID], nil
}
<|endoftext|>