<commit_before>package jsh

import (
	"encoding/json"
	"fmt"
	"reflect"
	"sort"
	"strconv"
	"strings"
)

// Validator provides validation features for resource modeling.
type Validator struct {
	object *Object
	action string
}

// NewValidator returns a new instance of a JSH validator.
func NewValidator(obj *Object, action string) *Validator {
	return &Validator{
		object: obj,
		action: action,
	}
}

// stringValues is a slice of reflect.Value holding *reflect.StringValue.
// It implements sort.Interface to sort by string.
type stringValues []reflect.Value

func (sv stringValues) Len() int           { return len(sv) }
func (sv stringValues) Swap(i, j int)      { sv[i], sv[j] = sv[j], sv[i] }
func (sv stringValues) Less(i, j int) bool { return sv.get(i) < sv.get(j) }
func (sv stringValues) get(i int) string   { return sv[i].String() }

/*
Validate validates that the given struct has no missing/forbidden
fields and relationships for the jsh action (i.e. create, update) according to JSH rules.

Customers must use JSH tags "create" and "update" to allow each field to be provided for create and update requests.
An optional "/required" can be added to the tag to require the field to be provided.

Additionally, relationships fields must fulfill the following requirements:
	- The field must be tagged "one" or "many".
	- The JSON tag of the field should be "-" to prevent it from being included in attributes.
	- If the relationship is tagged "one", the type of the field must be *jsh.IDObject.
	- If the relationship is tagged "many", the type of the field must be either:
		map[int]*jsh.IDObject or map[int]*jsh.IDObject. The map must be non-nil.

Example model:

	type User struct {
		Group *jsh.IDObject `json:"-"        jsh:"one,create,update"`
		Name  string        `json:"username" jsh:"create/required"`
		Email string        `json:"email"    jsh:"create,update"`
	}

The given model should be the result of the unmarshaling of the internal object attributes.
The validator will automatically update the relationship fields during validation.
*/
func (v *Validator) Validate(model interface{}) ([]string, ErrorList) {
	// Check argument is a non-nil pointer
	rv := reflect.ValueOf(model)
	if rv.Kind() != reflect.Ptr || rv.IsNil() {
		return nil, ErrorList{ISE(fmt.Sprintf("The argument to %s must be a non-nil pointer", v.action))}
	}
	// Get pointer element
	rv = rv.Elem()
	// Check pointer element is a struct
	if rv.Kind() != reflect.Struct {
		return nil, ErrorList{ISE(fmt.Sprintf("The argument to %s must be a pointer to a struct", v.action))}
	}
	// Unmarshal to map to retrieve all provided attributes
	return v.validateStruct("", rv, v.object.Attributes)
}

// validateStruct validates all fields of the given struct according to JSH rules.
func (v *Validator) validateStruct(path string, rv reflect.Value, j json.RawMessage) ([]string, ErrorList) {
	// Decode struct provided attributes
	attrs, err := v.decodeKeys(j)
	if err != nil {
		return nil, ErrorList{err}
	}
	// Validate fields
	var fields []string
	var errors ErrorList
	rt := rv.Type()
	for i := 0; i < rt.NumField(); i++ {
		fv := rv.Field(i)
		f := rt.Field(i)
		// Ignore zero reflect.Value and private fields
		if !fv.IsValid() || f.PkgPath != "" {
			continue
		}
		// Decode JSH tags
		tags := decodeFieldTags(f.Tag.Get(tagNameJSH))
		p := toLowerFirstRune(f.Name)
		_, one := tags[tagToOne]
		_, many := tags[tagToMany]
		if one || many {
			// Remove existing field from the relationships map
			var rel *Relationship
			for name, r := range v.object.Relationships {
				if strings.EqualFold(name, f.Name) {
					rel = r
					delete(v.object.Relationships, name)
					break
				}
			}
			// Validate relationship
			hasValue, err := validateModelRelationship(p, many, rel, tags[v.action])
			if err != nil {
				errors = append(errors, err)
			} else if hasValue {
				// Set relationship in model
				if err := setModelRelationship(p, many, rel, fv); err != nil {
					errors = append(errors, err)
				} else {
					fields = append(fields, p)
				}
			}
			continue
		}
		// Ignore fields ignored by json.Unmarshal
		p = decodeJSONTag(f)
		if p == "-" {
			continue
		}
		// Remove existing field from the provided attributes map
		var jValue json.RawMessage
		for name, value := range attrs {
			if strings.EqualFold(name, p) {
				jValue = value
				delete(attrs, name)
				break
			}
		}
		// Validate field
		if path != "" {
			p = path + fieldSep + p
		}
		hasValue, err := validateModelField(p, fv, tags[v.action])
		if err != nil {
			errors = append(errors, err)
		} else if hasValue {
			result, errlist := v.nestedResult(p, fv, jValue)
			if errlist != nil {
				errors = append(errors, errlist...)
			} else {
				fields = append(fields, result...)
			}
		}
	}
	// Add errors for non-existent attributes
	for name := range attrs {
		if path != "" {
			name = path + fieldSep + name
		}
		errors = append(errors, InputError("Attribute does not exist", name))
	}
	// Add errors for non-existent relationships
	for name := range v.object.Relationships {
		if path != "" {
			name = path + fieldSep + name
		}
		errors = append(errors, RelationshipError("Relationship does not exist", name))
	}
	if errors != nil {
		return nil, errors
	}
	return fields, nil
}

// nestedResult recurses until the field type is not a map, slice, array, interface, pointer or struct.
// It calls validateStruct recursively if it encounters a struct type.
func (v *Validator) nestedResult(path string, fv reflect.Value, jValue json.RawMessage) ([]string, ErrorList) {
	fields := []string{path}
	switch fv.Kind() {
	case reflect.Map:
		// Reject unsupported key types
		if fv.Type().Key().Kind() != reflect.String {
			return nil, ErrorList{ISE(fmt.Sprintf("Type %v is not supported", fv.Type()))}
		}
		// Decode map JSON values
		jsonValues, err := v.decodeKeys(jValue)
		if err != nil {
			return nil, ErrorList{err}
		}
		// Sort map by key
		var sv stringValues
		sv = fv.MapKeys()
		sort.Sort(sv)
		// Validate embedded struct values recursively and append field names
		for _, k := range sv {
			key := k.String()
			p := path + fieldSep + key
			result, errlist := v.nestedResult(p, fv.MapIndex(k), jsonValues[key])
			if errlist != nil {
				return nil, errlist
			}
			fields = append(fields, result...)
		}
	case reflect.Slice:
		if fv.Type() == reflect.TypeOf(json.RawMessage{}) {
			break
		}
		fallthrough
	case reflect.Array:
		// Decode JSON array to slice
		jArray, err := v.decodeSlice(jValue)
		if err != nil {
			return nil, ErrorList{err}
		}
		// Validate embedded struct values recusively and append field names
		for i := 0; i < fv.Len(); i++ {
			p := path + fieldSep + strconv.Itoa(i)
			result, errlist := v.nestedResult(p, fv.Index(i), jArray[i])
			if errlist != nil {
				return nil, errlist
			}
			fields = append(fields, result...)
		}
	case reflect.Interface:
		fallthrough
	case reflect.Ptr:
		// If the value is an pointer or interface then encode its element
		if fv.IsNil() {
			break
		}
		return v.nestedResult(path, fv.Elem(), jValue)
	case reflect.Struct:
		// Validate embedded struct values recusively and append field names
		result, errlist := v.validateStruct(path, fv, jValue)
		if errlist != nil {
			return nil, errlist
		}
		fields = append(fields, result...)
	default:
	}
	return fields, nil
}

// decodeKeys decodes the keys of the given JSON message.
func (v *Validator) decodeKeys(j json.RawMessage) (map[string]json.RawMessage, *Error) {
	attrs := make(map[string]json.RawMessage)
	if len(j) > 0 {
		err := json.Unmarshal(j, &attrs)
		if err != nil {
			return nil, ISE(err.Error())
		}
	}
	return attrs, nil
}

// decodeSlice decodes the given JSON array into a slice of JSON messages.
func (v *Validator) decodeSlice(j json.RawMessage) ([]json.RawMessage, *Error) {
	var attrs []json.RawMessage
	if len(j) > 0 {
		err := json.Unmarshal(j, &attrs)
		if err != nil {
			return nil, ISE(err.Error())
		}
	}
	return attrs, nil
}

// setModelRelationship sets the given field (v) of the model to the given relationship value.
func setModelRelationship(name string, many bool, rel *Relationship, v reflect.Value) *Error {
	if many {
		return setModelRelationshipMany(name, v, rel)
	} else {
		return setModelRelationshipOne(v, rel)
	}
}

// setModelRelationshipOne sets the given field (v) of the model to the given to-one relationship value.
// The struct field must be of type *IDObject.
func setModelRelationshipOne(v reflect.Value, rel *Relationship) *Error {
	one := reflect.ValueOf(rel.Data[0])
	if !one.Type().AssignableTo(v.Type()) {
		return ISE("Invalid field type for to-one relation, must be *IDObject")
	}
	v.Set(one)
	return nil
}

// setModelRelationshipMany sets the given field (v) of the model to the given to-many relationship value.
// The struct field must be of type map[string]*IDObject or map[int]*IDObject.
func setModelRelationshipMany(name string, v reflect.Value, rel *Relationship) *Error {
	kind := v.Type().Kind()
	if kind != reflect.Map {
		return ISE("Invalid field type for to-many relation, must be map")
	}
	keyKind := v.Type().Key().Kind()
	if keyKind == reflect.String || keyKind == reflect.Int {
		for _, data := range rel.Data {
			dv := reflect.ValueOf(data)
			if !dv.Type().AssignableTo(v.Type().Elem()) {
				return ISE("Invalid map value type for to-many relation, must be *IDObject")
			}
			if keyKind == reflect.String {
				v.SetMapIndex(reflect.ValueOf(data.ID), dv)
			} else {
				id, err := strconv.Atoi(data.ID)
				if err != nil {
					return RelationshipError("Invalid resource ID", toLowerFirstRune(name))
				}
				v.SetMapIndex(reflect.ValueOf(id), dv)
			}
		}
	} else {
		return ISE("Invalid map key type for to-many relation, must be string or int")
	}
	return nil
}

// validateModelRelationship validates that the given struct has no forbidden or invalid
// relationships for the jsh action (i.e. create, update).
func validateModelRelationship(name string, many bool, rel *Relationship, opts *tagOptions) (bool, *Error) {
	// Check if relationship was not provided
	if rel == nil {
		if opts != nil && opts.required {
			return false, RelationshipError("Required relationship", toLowerFirstRune(name))
		}
		return false, nil
	}
	// Check if relationship has data
	if len(rel.Data) == 0 {
		return false, RelationshipError("Missing relationship data", toLowerFirstRune(name))
	}
	if !many && len(rel.Data) > 1 {
		return false, RelationshipError("Multiple objects for to-one relation", toLowerFirstRune(name))
	}
	// The relationship was provided: it must have jsh tag
	if opts == nil {
		err := ForbiddenError("Operation not allowed")
		err.Source = &ErrorSource{
			Pointer: RelationshipPointer(toLowerFirstRune(name)),
		}
		return false, err
	}
	return true, nil
}

// validateModelField validates that the value for the given field
// is neither missing or forbidden according to jsh tags.
func validateModelField(path string, v reflect.Value, opts *tagOptions) (bool, *Error) {
	// Check if attribute was not provided
	if isZero(v) {
		if opts != nil && opts.required {
			return false, InputError("Required attribute", toLowerFirstRune(path))
		}
		return false, nil
	}
	// The attribute was provided: it must have jsh tag
	if opts == nil {
		err := ForbiddenError("Operation not allowed")
		err.Source = &ErrorSource{
			Pointer: AttributePointer(toLowerFirstRune(path)),
		}
		return false, err
	}
	return true, nil
}

// isZero checks if the given value is the zero value of its type.
func isZero(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Func, reflect.Map, reflect.Slice:
		return v.IsNil()
	case reflect.Array:
		z := true
		for i := 0; i < v.Len(); i++ {
			z = z && isZero(v.Index(i))
		}
		return z
	case reflect.Struct:
		z := true
		for i := 0; i < v.NumField(); i++ {
			z = z && isZero(v.Field(i))
		}
		return z
	}
	// Compare other types directly:
	z := reflect.Zero(v.Type())
	return v.Interface() == z.Interface()
}
<commit_msg>#10 Fix validator comment<commit_after>package jsh

import (
	"encoding/json"
	"fmt"
	"reflect"
	"sort"
	"strconv"
	"strings"
)

// Validator provides validation features for resource modeling.
type Validator struct {
	object *Object
	action string
}

// NewValidator returns a new instance of a JSH validator.
func NewValidator(obj *Object, action string) *Validator {
	return &Validator{
		object: obj,
		action: action,
	}
}

// stringValues is a slice of reflect.Value holding *reflect.StringValue.
// It implements sort.Interface to sort by string.
type stringValues []reflect.Value

func (sv stringValues) Len() int           { return len(sv) }
func (sv stringValues) Swap(i, j int)      { sv[i], sv[j] = sv[j], sv[i] }
func (sv stringValues) Less(i, j int) bool { return sv.get(i) < sv.get(j) }
func (sv stringValues) get(i int) string   { return sv[i].String() }

/*
Validate validates that the given struct has no missing/forbidden
fields and relationships for the jsh action (i.e. create, update) according to JSH rules.

Customers must use JSH tags "create" and "update" to allow each field to be provided for create and update requests.
An optional "/required" can be added to the tag to require the field to be provided.

Additionally, relationships fields must fulfill the following requirements:
	- The field must be tagged "one" or "many".
	- The JSON tag of the field should be "-" to prevent it from being included in attributes.
	- If the relationship is tagged "one", the type of the field must be *jsh.IDObject.
	- If the relationship is tagged "many", the type of the field must be either:
		map[string]*jsh.IDObject or map[int]*jsh.IDObject. The map must be non-nil.

Example model:

	type User struct {
		Group *jsh.IDObject `json:"-"        jsh:"one,create,update"`
		Name  string        `json:"username" jsh:"create/required"`
		Email string        `json:"email"    jsh:"create,update"`
	}

The given model should be the result of the unmarshaling of the internal object attributes.
The validator will automatically update the relationship fields during validation.
*/
func (v *Validator) Validate(model interface{}) ([]string, ErrorList) {
	// Check argument is a non-nil pointer
	rv := reflect.ValueOf(model)
	if rv.Kind() != reflect.Ptr || rv.IsNil() {
		return nil, ErrorList{ISE(fmt.Sprintf("The argument to %s must be a non-nil pointer", v.action))}
	}
	// Get pointer element
	rv = rv.Elem()
	// Check pointer element is a struct
	if rv.Kind() != reflect.Struct {
		return nil, ErrorList{ISE(fmt.Sprintf("The argument to %s must be a pointer to a struct", v.action))}
	}
	// Unmarshal to map to retrieve all provided attributes
	return v.validateStruct("", rv, v.object.Attributes)
}

// validateStruct validates all fields of the given struct according to JSH rules.
func (v *Validator) validateStruct(path string, rv reflect.Value, j json.RawMessage) ([]string, ErrorList) {
	// Decode struct provided attributes
	attrs, err := v.decodeKeys(j)
	if err != nil {
		return nil, ErrorList{err}
	}
	// Validate fields
	var fields []string
	var errors ErrorList
	rt := rv.Type()
	for i := 0; i < rt.NumField(); i++ {
		fv := rv.Field(i)
		f := rt.Field(i)
		// Ignore zero reflect.Value and private fields
		if !fv.IsValid() || f.PkgPath != "" {
			continue
		}
		// Decode JSH tags
		tags := decodeFieldTags(f.Tag.Get(tagNameJSH))
		p := toLowerFirstRune(f.Name)
		_, one := tags[tagToOne]
		_, many := tags[tagToMany]
		if one || many {
			// Remove existing field from the relationships map
			var rel *Relationship
			for name, r := range v.object.Relationships {
				if strings.EqualFold(name, f.Name) {
					rel = r
					delete(v.object.Relationships, name)
					break
				}
			}
			// Validate relationship
			hasValue, err := validateModelRelationship(p, many, rel, tags[v.action])
			if err != nil {
				errors = append(errors, err)
			} else if hasValue {
				// Set relationship in model
				if err := setModelRelationship(p, many, rel, fv); err != nil {
					errors = append(errors, err)
				} else {
					fields = append(fields, p)
				}
			}
			continue
		}
		// Ignore fields ignored by json.Unmarshal
		p = decodeJSONTag(f)
		if p == "-" {
			continue
		}
		// Remove existing field from the provided attributes map
		var jValue json.RawMessage
		for name, value := range attrs {
			if strings.EqualFold(name, p) {
				jValue = value
				delete(attrs, name)
				break
			}
		}
		// Validate field
		if path != "" {
			p = path + fieldSep + p
		}
		hasValue, err := validateModelField(p, fv, tags[v.action])
		if err != nil {
			errors = append(errors, err)
		} else if hasValue {
			result, errlist := v.nestedResult(p, fv, jValue)
			if errlist != nil {
				errors = append(errors, errlist...)
			} else {
				fields = append(fields, result...)
			}
		}
	}
	// Add errors for non-existent attributes
	for name := range attrs {
		if path != "" {
			name = path + fieldSep + name
		}
		errors = append(errors, InputError("Attribute does not exist", name))
	}
	// Add errors for non-existent relationships
	for name := range v.object.Relationships {
		if path != "" {
			name = path + fieldSep + name
		}
		errors = append(errors, RelationshipError("Relationship does not exist", name))
	}
	if errors != nil {
		return nil, errors
	}
	return fields, nil
}

// nestedResult recurses until the field type is not a map, slice, array, interface, pointer or struct.
// It calls validateStruct recursively if it encounters a struct type.
func (v *Validator) nestedResult(path string, fv reflect.Value, jValue json.RawMessage) ([]string, ErrorList) {
	fields := []string{path}
	switch fv.Kind() {
	case reflect.Map:
		// Reject unsupported key types
		if fv.Type().Key().Kind() != reflect.String {
			return nil, ErrorList{ISE(fmt.Sprintf("Type %v is not supported", fv.Type()))}
		}
		// Decode map JSON values
		jsonValues, err := v.decodeKeys(jValue)
		if err != nil {
			return nil, ErrorList{err}
		}
		// Sort map by key
		var sv stringValues
		sv = fv.MapKeys()
		sort.Sort(sv)
		// Validate embedded struct values recursively and append field names
		for _, k := range sv {
			key := k.String()
			p := path + fieldSep + key
			result, errlist := v.nestedResult(p, fv.MapIndex(k), jsonValues[key])
			if errlist != nil {
				return nil, errlist
			}
			fields = append(fields, result...)
		}
	case reflect.Slice:
		if fv.Type() == reflect.TypeOf(json.RawMessage{}) {
			break
		}
		fallthrough
	case reflect.Array:
		// Decode JSON array to slice
		jArray, err := v.decodeSlice(jValue)
		if err != nil {
			return nil, ErrorList{err}
		}
		// Validate embedded struct values recusively and append field names
		for i := 0; i < fv.Len(); i++ {
			p := path + fieldSep + strconv.Itoa(i)
			result, errlist := v.nestedResult(p, fv.Index(i), jArray[i])
			if errlist != nil {
				return nil, errlist
			}
			fields = append(fields, result...)
		}
	case reflect.Interface:
		fallthrough
	case reflect.Ptr:
		// If the value is an pointer or interface then encode its element
		if fv.IsNil() {
			break
		}
		return v.nestedResult(path, fv.Elem(), jValue)
	case reflect.Struct:
		// Validate embedded struct values recusively and append field names
		result, errlist := v.validateStruct(path, fv, jValue)
		if errlist != nil {
			return nil, errlist
		}
		fields = append(fields, result...)
	default:
	}
	return fields, nil
}

// decodeKeys decodes the keys of the given JSON message.
func (v *Validator) decodeKeys(j json.RawMessage) (map[string]json.RawMessage, *Error) {
	attrs := make(map[string]json.RawMessage)
	if len(j) > 0 {
		err := json.Unmarshal(j, &attrs)
		if err != nil {
			return nil, ISE(err.Error())
		}
	}
	return attrs, nil
}

// decodeSlice decodes the given JSON array into a slice of JSON messages.
func (v *Validator) decodeSlice(j json.RawMessage) ([]json.RawMessage, *Error) {
	var attrs []json.RawMessage
	if len(j) > 0 {
		err := json.Unmarshal(j, &attrs)
		if err != nil {
			return nil, ISE(err.Error())
		}
	}
	return attrs, nil
}

// setModelRelationship sets the given field (v) of the model to the given relationship value.
func setModelRelationship(name string, many bool, rel *Relationship, v reflect.Value) *Error {
	if many {
		return setModelRelationshipMany(name, v, rel)
	} else {
		return setModelRelationshipOne(v, rel)
	}
}

// setModelRelationshipOne sets the given field (v) of the model to the given to-one relationship value.
// The struct field must be of type *IDObject.
func setModelRelationshipOne(v reflect.Value, rel *Relationship) *Error {
	one := reflect.ValueOf(rel.Data[0])
	if !one.Type().AssignableTo(v.Type()) {
		return ISE("Invalid field type for to-one relation, must be *IDObject")
	}
	v.Set(one)
	return nil
}

// setModelRelationshipMany sets the given field (v) of the model to the given to-many relationship value.
// The struct field must be of type map[string]*IDObject or map[int]*IDObject.
func setModelRelationshipMany(name string, v reflect.Value, rel *Relationship) *Error {
	kind := v.Type().Kind()
	if kind != reflect.Map {
		return ISE("Invalid field type for to-many relation, must be map")
	}
	keyKind := v.Type().Key().Kind()
	if keyKind == reflect.String || keyKind == reflect.Int {
		for _, data := range rel.Data {
			dv := reflect.ValueOf(data)
			if !dv.Type().AssignableTo(v.Type().Elem()) {
				return ISE("Invalid map value type for to-many relation, must be *IDObject")
			}
			if keyKind == reflect.String {
				v.SetMapIndex(reflect.ValueOf(data.ID), dv)
			} else {
				id, err := strconv.Atoi(data.ID)
				if err != nil {
					return RelationshipError("Invalid resource ID", toLowerFirstRune(name))
				}
				v.SetMapIndex(reflect.ValueOf(id), dv)
			}
		}
	} else {
		return ISE("Invalid map key type for to-many relation, must be string or int")
	}
	return nil
}

// validateModelRelationship validates that the given struct has no forbidden or invalid
// relationships for the jsh action (i.e. create, update).
func validateModelRelationship(name string, many bool, rel *Relationship, opts *tagOptions) (bool, *Error) {
	// Check if relationship was not provided
	if rel == nil {
		if opts != nil && opts.required {
			return false, RelationshipError("Required relationship", toLowerFirstRune(name))
		}
		return false, nil
	}
	// Check if relationship has data
	if len(rel.Data) == 0 {
		return false, RelationshipError("Missing relationship data", toLowerFirstRune(name))
	}
	if !many && len(rel.Data) > 1 {
		return false, RelationshipError("Multiple objects for to-one relation", toLowerFirstRune(name))
	}
	// The relationship was provided: it must have jsh tag
	if opts == nil {
		err := ForbiddenError("Operation not allowed")
		err.Source = &ErrorSource{
			Pointer: RelationshipPointer(toLowerFirstRune(name)),
		}
		return false, err
	}
	return true, nil
}

// validateModelField validates that the value for the given field
// is neither missing or forbidden according to jsh tags.
func validateModelField(path string, v reflect.Value, opts *tagOptions) (bool, *Error) {
	// Check if attribute was not provided
	if isZero(v) {
		if opts != nil && opts.required {
			return false, InputError("Required attribute", toLowerFirstRune(path))
		}
		return false, nil
	}
	// The attribute was provided: it must have jsh tag
	if opts == nil {
		err := ForbiddenError("Operation not allowed")
		err.Source = &ErrorSource{
			Pointer: AttributePointer(toLowerFirstRune(path)),
		}
		return false, err
	}
	return true, nil
}

// isZero checks if the given value is the zero value of its type.
func isZero(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Func, reflect.Map, reflect.Slice:
		return v.IsNil()
	case reflect.Array:
		z := true
		for i := 0; i < v.Len(); i++ {
			z = z && isZero(v.Index(i))
		}
		return z
	case reflect.Struct:
		z := true
		for i := 0; i < v.NumField(); i++ {
			z = z && isZero(v.Field(i))
		}
		return z
	}
	// Compare other types directly:
	z := reflect.Zero(v.Type())
	return v.Interface() == z.Interface()
}
<|endoftext|>


<commit_before>// Copyright 2016 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// Package vars 定义一些全局变量、常量。
package vars

import "time"

const (
	// 主版本号，符合 semver 规范
	mainVersion = "0.12.1"

	// AppName 程序名称
	AppName = "typing"

	// URL 项目的地址
	URL = "https://github.com/caixw/typing"

	// DateFormat 客户配置文件中所使用的的时间格式。
	// 所有的时间字符串，都将使用此格式去解析。
	//
	// 只负责时间的解析，如果是输出时间，则其格式由 meta/config.yaml 中定义。
	DateFormat = time.RFC3339

	// TemplateExtension 模板的扩展名
	TemplateExtension = ".html"

	// DefaultPostTemplateName 默认的文章模板名称
	DefaultPostTemplateName = "post"
)

// 默认的 mime type 类型。
//
// 一般情况下，用户无须修改此处内容，所有的 mime type 值都可以在
// data/meta/config.yaml 中手动指定，此处仅作为在其未指定的情况下
// 的一种默认值。
const (
	ContentTypeHTML       = "text/html"
	ContentTypeXML        = "application/xml"
	ContentTypeAtom       = "application/atom+xml"
	ContentTypeRSS        = "application/rss+xml"
	ContentTypeOpensearch = "application/opensearchdescription+xml"
)

var (
	buildDate  string
	commitHash string
	version    = mainVersion
)

func init() {
	if len(buildDate) > 0 {
		version += "+" + buildDate
	}
}

// Version 获取完整的版本号
func Version() string {
	return version
}

// CommitHash 获取最后一条代码提交记录的 hash 值。
func CommitHash() string {
	return commitHash
}

// ParseDate 分析时间字符串，将其转换成 unix 时间戳
func ParseDate(format string) (time.Time, error) {
	return time.Parse(DateFormat, format)
}
<commit_msg>所有主要功能完成，版本进化到 1.0<commit_after>// Copyright 2016 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// Package vars 定义一些全局变量、常量。
package vars

import "time"

const (
	// 主版本号，符合 semver 规范
	mainVersion = "1.0.0"

	// AppName 程序名称
	AppName = "typing"

	// URL 项目的地址
	URL = "https://github.com/caixw/typing"

	// DateFormat 客户配置文件中所使用的的时间格式。
	// 所有的时间字符串，都将使用此格式去解析。
	//
	// 只负责时间的解析，如果是输出时间，则其格式由 meta/config.yaml 中定义。
	DateFormat = time.RFC3339

	// TemplateExtension 模板的扩展名
	TemplateExtension = ".html"

	// DefaultPostTemplateName 默认的文章模板名称
	DefaultPostTemplateName = "post"
)

// 默认的 mime type 类型。
//
// 一般情况下，用户无须修改此处内容，所有的 mime type 值都可以在
// data/meta/config.yaml 中手动指定，此处仅作为在其未指定的情况下
// 的一种默认值。
const (
	ContentTypeHTML       = "text/html"
	ContentTypeXML        = "application/xml"
	ContentTypeAtom       = "application/atom+xml"
	ContentTypeRSS        = "application/rss+xml"
	ContentTypeOpensearch = "application/opensearchdescription+xml"
)

var (
	buildDate  string
	commitHash string
	version    = mainVersion
)

func init() {
	if len(buildDate) > 0 {
		version += "+" + buildDate
	}
}

// Version 获取完整的版本号
func Version() string {
	return version
}

// CommitHash 获取最后一条代码提交记录的 hash 值。
func CommitHash() string {
	return commitHash
}

// ParseDate 分析时间字符串，将其转换成 unix 时间戳
func ParseDate(format string) (time.Time, error) {
	return time.Parse(DateFormat, format)
}
<|endoftext|>


<commit_before>package wireup

import (
	"crypto/tls"
	"fmt"
	"math/rand"
	"net/http"
	"net/url"
	"time"

	"github.com/smartystreets/smartystreets-go-sdk"
	internal "github.com/smartystreets/smartystreets-go-sdk/internal/sdk"
	international_autocomplete "github.com/smartystreets/smartystreets-go-sdk/international-autocomplete-api"
	international_street "github.com/smartystreets/smartystreets-go-sdk/international-street-api"
	"github.com/smartystreets/smartystreets-go-sdk/us-autocomplete-api"
	autocomplete_pro "github.com/smartystreets/smartystreets-go-sdk/us-autocomplete-pro-api"
	"github.com/smartystreets/smartystreets-go-sdk/us-extract-api"
	us_reverse_geo "github.com/smartystreets/smartystreets-go-sdk/us-reverse-geo-api"
	"github.com/smartystreets/smartystreets-go-sdk/us-street-api"
	"github.com/smartystreets/smartystreets-go-sdk/us-zipcode-api"
)

// clientBuilder is responsible for accepting credentials and other configuration options to combine
// all components necessary to assemble a fully functional Client for use in an application.
type clientBuilder struct {
	credential    sdk.Credential
	baseURL       *url.URL
	proxy         *url.URL
	retries       int
	timeout       time.Duration
	debug         bool
	close         bool
	trace         bool
	headers       http.Header
	idleConns     int
	http2Disabled bool
	client        *http.Client
	licenses      []string
}

func newClientBuilder() *clientBuilder {
	return &clientBuilder{
		credential: &internal.NopCredential{},
		retries:    4,
		timeout:    time.Second * 10,
		headers:    initializeHeadersWithUserAgent(),
	}
}

func initializeHeadersWithUserAgent() http.Header {
	headers := make(http.Header)
	headers.Add("User-Agent", fmt.Sprintf("smartystreets (sdk:go@%s)", sdk.VERSION))
	return headers
}

func (b *clientBuilder) withSecretKeyCredential(authID, authToken string) *clientBuilder {
	b.credential = sdk.NewSecretKeyCredential(authID, authToken)
	return b
}

func (b *clientBuilder) withWebsiteKeyCredential(key, hostnameOrIP string) *clientBuilder {
	b.credential = sdk.NewWebsiteKeyCredential(key, hostnameOrIP)
	return b
}

func (b *clientBuilder) withCustomBaseURL(address string) *clientBuilder {
	parsed, err := url.Parse(address)
	if err != nil {
		panic(fmt.Sprint("Could not parse provided address:", err.Error()))
	}
	b.baseURL = parsed
	return b
}

func (b *clientBuilder) withMaxRetry(retries int) *clientBuilder {
	if retries < 0 {
		panic(fmt.Sprintf("Please provide a non-negative number of retry attempts (you supplied %d).", retries))
	}
	b.retries = retries
	return b
}

func (b *clientBuilder) withTimeout(duration time.Duration) *clientBuilder {
	if duration < 0 {
		panic(fmt.Sprintf("Please provide a non-negative duration (you supplied %s).", duration.String()))
	}
	b.timeout = duration
	return b
}

func (b *clientBuilder) withDebugHTTPOutput() *clientBuilder {
	b.debug = true
	return b
}

func (b *clientBuilder) withHTTPRequestTracing() *clientBuilder {
	b.trace = true
	return b
}

func (b *clientBuilder) withCustomHeader(key, value string) *clientBuilder {
	b.headers.Add(key, value)
	return b
}

func (b *clientBuilder) withoutKeepAlive() *clientBuilder {
	b.close = true
	return b
}

func (b *clientBuilder) disableHTTP2() *clientBuilder {
	b.http2Disabled = true
	return b
}

func (b *clientBuilder) viaProxy(address string) *clientBuilder {
	proxy, err := url.Parse(address)
	if err != nil {
		panic(fmt.Sprint("Could not parse provided address:", err.Error()))
	}
	b.proxy = proxy
	return b
}

func (b *clientBuilder) withMaxIdleConnections(max int) *clientBuilder {
	b.idleConns = max
	return b
}

func (b *clientBuilder) buildUSStreetAPIClient() *street.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_USStreetAPI)
	return street.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) buildUSZIPCodeAPIClient() *zipcode.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_USZIPCodeAPI)
	return zipcode.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) buildUSAutocompleteAPIClient() *autocomplete.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_USAutocompleteAPI)
	return autocomplete.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) buildUSAutocompleteProAPIClient() *autocomplete_pro.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_USAutocompleteProAPI)
	return autocomplete_pro.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) buildUSExtractAPIClient() *extract.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_USExtractAPI)
	return extract.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) buildInternationalStreetAPIClient() *international_street.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_InternationalStreetAPI)
	return international_street.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) buildInternationalAutocompleteAPIClient() *international_autocomplete.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_InternationalAutocompleteAPI)
	return international_autocomplete.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) buildUSReverseGeocodingAPIClient() *us_reverse_geo.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_USReverseGeocodingAPI)
	return us_reverse_geo.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) ensureBaseURLNotNil(u *url.URL) {
	if b.baseURL == nil {
		b.baseURL = u
	}
}

func (b *clientBuilder) buildHTTPSender() *internal.HTTPSender {
	client := b.buildHTTPClient()
	return internal.NewHTTPSender(client)
}

func (b *clientBuilder) buildHTTPClient() (wrapped internal.HTTPClient) {
	// inner-most
	wrapped = b.buildClient()
	wrapped = internal.NewTracingClient(wrapped, b.trace)
	wrapped = internal.NewDebugOutputClient(wrapped, b.debug)
	wrapped = internal.NewRetryClient(wrapped, b.retries, rand.New(rand.NewSource(time.Now().UnixNano())), time.Sleep)
	wrapped = internal.NewSigningClient(wrapped, b.credential)
	wrapped = internal.NewCustomHeadersClient(wrapped, b.headers)
	wrapped = internal.NewBaseURLClient(wrapped, b.baseURL)
	wrapped = internal.NewKeepAliveCloseClient(wrapped, b.close)
	wrapped = internal.NewLicenseClient(wrapped, b.licenses...)
	// outer-most
	return wrapped
}

func (b *clientBuilder) buildClient() *http.Client {
	if b.client != nil {
		return b.client
	}
	return &http.Client{Timeout: b.timeout, Transport: b.buildTransport()}
}

func (b *clientBuilder) buildTransport() *http.Transport {
	transport := &http.Transport{}
	if b.proxy != nil {
		transport.Proxy = http.ProxyURL(b.proxy)
	}
	if b.idleConns > 0 {
		transport.MaxIdleConnsPerHost = b.idleConns
	}
	if b.http2Disabled { // https://golang.org/pkg/net/http/ ("Programs that must disable HTTP/2 can do so by setting Transport.TLSNextProto to a non-nil, empty map.")
		transport.TLSNextProto = make(map[string]func(authority string, c *tls.Conn) http.RoundTripper, 0)
	}
	return transport
}

var (
	defaultBaseURL_InternationalStreetAPI       = &url.URL{Scheme: "https", Host: "international-street.api.smartystreets.com"}
	defaultBaseURL_InternationalAutocompleteAPI = &url.URL{Scheme: "https", Host: "international-autocomplete.api.smartystreets.com"}
	defaultBaseURL_USStreetAPI                  = &url.URL{Scheme: "https", Host: "us-street.api.smartystreets.com"}
	defaultBaseURL_USZIPCodeAPI                 = &url.URL{Scheme: "https", Host: "us-zipcode.api.smartystreets.com"}
	defaultBaseURL_USAutocompleteAPI            = &url.URL{Scheme: "https", Host: "us-autocomplete.api.smartystreets.com"}
	defaultBaseURL_USExtractAPI                 = &url.URL{Scheme: "https", Host: "us-extract.api.smartystreets.com"}
	defaultBaseURL_USReverseGeocodingAPI        = &url.URL{Scheme: "https", Host: "us-reverse-geo.api.smartystreets.com"}
	defaultBaseURL_USAutocompleteProAPI         = &url.URL{Scheme: "https", Host: "us-autocomplete-pro.api.smartystreets.com"}
)
<commit_msg>Added a DialContext as to avoid context cancelled errors.<commit_after>package wireup

import (
	"crypto/tls"
	"fmt"
	"math/rand"
	"net"
	"net/http"
	"net/url"
	"time"

	"github.com/smartystreets/smartystreets-go-sdk"
	internal "github.com/smartystreets/smartystreets-go-sdk/internal/sdk"
	international_autocomplete "github.com/smartystreets/smartystreets-go-sdk/international-autocomplete-api"
	international_street "github.com/smartystreets/smartystreets-go-sdk/international-street-api"
	"github.com/smartystreets/smartystreets-go-sdk/us-autocomplete-api"
	autocomplete_pro "github.com/smartystreets/smartystreets-go-sdk/us-autocomplete-pro-api"
	"github.com/smartystreets/smartystreets-go-sdk/us-extract-api"
	us_reverse_geo "github.com/smartystreets/smartystreets-go-sdk/us-reverse-geo-api"
	"github.com/smartystreets/smartystreets-go-sdk/us-street-api"
	"github.com/smartystreets/smartystreets-go-sdk/us-zipcode-api"
)

// clientBuilder is responsible for accepting credentials and other configuration options to combine
// all components necessary to assemble a fully functional Client for use in an application.
type clientBuilder struct {
	credential    sdk.Credential
	baseURL       *url.URL
	proxy         *url.URL
	retries       int
	timeout       time.Duration
	debug         bool
	close         bool
	trace         bool
	headers       http.Header
	idleConns     int
	http2Disabled bool
	client        *http.Client
	licenses      []string
}

func newClientBuilder() *clientBuilder {
	return &clientBuilder{
		credential: &internal.NopCredential{},
		retries:    4,
		timeout:    time.Second * 10,
		headers:    initializeHeadersWithUserAgent(),
	}
}

func initializeHeadersWithUserAgent() http.Header {
	headers := make(http.Header)
	headers.Add("User-Agent", fmt.Sprintf("smartystreets (sdk:go@%s)", sdk.VERSION))
	return headers
}

func (b *clientBuilder) withSecretKeyCredential(authID, authToken string) *clientBuilder {
	b.credential = sdk.NewSecretKeyCredential(authID, authToken)
	return b
}

func (b *clientBuilder) withWebsiteKeyCredential(key, hostnameOrIP string) *clientBuilder {
	b.credential = sdk.NewWebsiteKeyCredential(key, hostnameOrIP)
	return b
}

func (b *clientBuilder) withCustomBaseURL(address string) *clientBuilder {
	parsed, err := url.Parse(address)
	if err != nil {
		panic(fmt.Sprint("Could not parse provided address:", err.Error()))
	}
	b.baseURL = parsed
	return b
}

func (b *clientBuilder) withMaxRetry(retries int) *clientBuilder {
	if retries < 0 {
		panic(fmt.Sprintf("Please provide a non-negative number of retry attempts (you supplied %d).", retries))
	}
	b.retries = retries
	return b
}

func (b *clientBuilder) withTimeout(duration time.Duration) *clientBuilder {
	if duration < 0 {
		panic(fmt.Sprintf("Please provide a non-negative duration (you supplied %s).", duration.String()))
	}
	b.timeout = duration
	return b
}

func (b *clientBuilder) withDebugHTTPOutput() *clientBuilder {
	b.debug = true
	return b
}

func (b *clientBuilder) withHTTPRequestTracing() *clientBuilder {
	b.trace = true
	return b
}

func (b *clientBuilder) withCustomHeader(key, value string) *clientBuilder {
	b.headers.Add(key, value)
	return b
}

func (b *clientBuilder) withoutKeepAlive() *clientBuilder {
	b.close = true
	return b
}

func (b *clientBuilder) disableHTTP2() *clientBuilder {
	b.http2Disabled = true
	return b
}

func (b *clientBuilder) viaProxy(address string) *clientBuilder {
	proxy, err := url.Parse(address)
	if err != nil {
		panic(fmt.Sprint("Could not parse provided address:", err.Error()))
	}
	b.proxy = proxy
	return b
}

func (b *clientBuilder) withMaxIdleConnections(max int) *clientBuilder {
	b.idleConns = max
	return b
}

func (b *clientBuilder) buildUSStreetAPIClient() *street.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_USStreetAPI)
	return street.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) buildUSZIPCodeAPIClient() *zipcode.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_USZIPCodeAPI)
	return zipcode.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) buildUSAutocompleteAPIClient() *autocomplete.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_USAutocompleteAPI)
	return autocomplete.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) buildUSAutocompleteProAPIClient() *autocomplete_pro.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_USAutocompleteProAPI)
	return autocomplete_pro.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) buildUSExtractAPIClient() *extract.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_USExtractAPI)
	return extract.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) buildInternationalStreetAPIClient() *international_street.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_InternationalStreetAPI)
	return international_street.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) buildInternationalAutocompleteAPIClient() *international_autocomplete.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_InternationalAutocompleteAPI)
	return international_autocomplete.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) buildUSReverseGeocodingAPIClient() *us_reverse_geo.Client {
	b.ensureBaseURLNotNil(defaultBaseURL_USReverseGeocodingAPI)
	return us_reverse_geo.NewClient(b.buildHTTPSender())
}

func (b *clientBuilder) ensureBaseURLNotNil(u *url.URL) {
	if b.baseURL == nil {
		b.baseURL = u
	}
}

func (b *clientBuilder) buildHTTPSender() *internal.HTTPSender {
	client := b.buildHTTPClient()
	return internal.NewHTTPSender(client)
}

func (b *clientBuilder) buildHTTPClient() (wrapped internal.HTTPClient) {
	// inner-most
	wrapped = b.buildClient()
	wrapped = internal.NewTracingClient(wrapped, b.trace)
	wrapped = internal.NewDebugOutputClient(wrapped, b.debug)
	wrapped = internal.NewRetryClient(wrapped, b.retries, rand.New(rand.NewSource(time.Now().UnixNano())), time.Sleep)
	wrapped = internal.NewSigningClient(wrapped, b.credential)
	wrapped = internal.NewCustomHeadersClient(wrapped, b.headers)
	wrapped = internal.NewBaseURLClient(wrapped, b.baseURL)
	wrapped = internal.NewKeepAliveCloseClient(wrapped, b.close)
	wrapped = internal.NewLicenseClient(wrapped, b.licenses...)
	// outer-most
	return wrapped
}

func (b *clientBuilder) buildClient() *http.Client {
	if b.client != nil {
		return b.client
	}
	return &http.Client{Timeout: b.timeout, Transport: b.buildTransport()}
}

func (b *clientBuilder) buildTransport() *http.Transport {
	transport := &http.Transport{}
	if b.proxy != nil {
		transport.Proxy = http.ProxyURL(b.proxy)
	}
	if b.idleConns > 0 {
		transport.MaxIdleConnsPerHost = b.idleConns
	}
	if b.http2Disabled { // https://golang.org/pkg/net/http/ ("Programs that must disable HTTP/2 can do so by setting Transport.TLSNextProto to a non-nil, empty map.")
		transport.TLSNextProto = make(map[string]func(authority string, c *tls.Conn) http.RoundTripper, 0)
	}

	transport.DialContext  = (&net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
	}).DialContext

	return transport
}

var (
	defaultBaseURL_InternationalStreetAPI       = &url.URL{Scheme: "https", Host: "international-street.api.smartystreets.com"}
	defaultBaseURL_InternationalAutocompleteAPI = &url.URL{Scheme: "https", Host: "international-autocomplete.api.smartystreets.com"}
	defaultBaseURL_USStreetAPI                  = &url.URL{Scheme: "https", Host: "us-street.api.smartystreets.com"}
	defaultBaseURL_USZIPCodeAPI                 = &url.URL{Scheme: "https", Host: "us-zipcode.api.smartystreets.com"}
	defaultBaseURL_USAutocompleteAPI            = &url.URL{Scheme: "https", Host: "us-autocomplete.api.smartystreets.com"}
	defaultBaseURL_USExtractAPI                 = &url.URL{Scheme: "https", Host: "us-extract.api.smartystreets.com"}
	defaultBaseURL_USReverseGeocodingAPI        = &url.URL{Scheme: "https", Host: "us-reverse-geo.api.smartystreets.com"}
	defaultBaseURL_USAutocompleteProAPI         = &url.URL{Scheme: "https", Host: "us-autocomplete-pro.api.smartystreets.com"}
)
<|endoftext|>


<commit_before>package jsonrpc

import (
	"fmt"
)

const Version = "2.0"

const (
	ParseError          = -32700
	InvalidRequestError = -32600
	MethodNotFoundError = -32601
	InvalidParamsError  = -32602
	InternalError       = -32603
)

type RequestParams map[string]interface{}

type Request struct {
	Version    string        `json:"jsonrpc"`
	Method     string        `json:"method"`
	HttpMethod string        `json:"http_method"`
	Params     RequestParams `json:"params,omitempty"`
	ID         string        `json:"id"`
	// extention
	Ep string `json:"ep"`
}

type Response struct {
	Version string  `json:"jsonrpc"`
	Result  string  `json:"result,omitempty"`
	Error   *Error  `json:"error,omitempty"`
	ID      string  `json:"id"`
	Time    float64 `json:"time,omitempty"`
}

type Error struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	//Data    interface{} `json:"data"`
}

func ValidateRequests(reqs *[]Request) error {
	idMap := make(map[string]bool, len(*reqs))
	for _, r := range *reqs {
		if _, ok := idMap[r.ID]; ok {
			return fmt.Errorf("ID:%d is duplicated.", r.ID)
		}
		if err := validateRequest(&r); err != nil {
			return err
		}
		idMap[r.ID] = true
	}
	return nil
}

func validateRequest(r *Request) error {
	if r.Version != Version {
		return fmt.Errorf("malformed JSON-RPC version: %s", r.Version)
	}
	if r.Method == "" {
		return fmt.Errorf("empty method")
	}
	// empty method is treated as GET.
	if r.HttpMethod != "" && r.HttpMethod != "GET" && r.HttpMethod != "POST" {
		return fmt.Errorf("malformed HTTP method: %s", r.HttpMethod)
	}
	if r.ID == "" {
		return fmt.Errorf("empty id")
	}
	return nil
}
<commit_msg>jsonrpc: fixed invalid format specifier in error.<commit_after>package jsonrpc

import (
	"fmt"
)

const Version = "2.0"

const (
	ParseError          = -32700
	InvalidRequestError = -32600
	MethodNotFoundError = -32601
	InvalidParamsError  = -32602
	InternalError       = -32603
)

type RequestParams map[string]interface{}

type Request struct {
	Version    string        `json:"jsonrpc"`
	Method     string        `json:"method"`
	HttpMethod string        `json:"http_method"`
	Params     RequestParams `json:"params,omitempty"`
	ID         string        `json:"id"`
	// extention
	Ep string `json:"ep"`
}

type Response struct {
	Version string  `json:"jsonrpc"`
	Result  string  `json:"result,omitempty"`
	Error   *Error  `json:"error,omitempty"`
	ID      string  `json:"id"`
	Time    float64 `json:"time,omitempty"`
}

type Error struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	//Data    interface{} `json:"data"`
}

func ValidateRequests(reqs *[]Request) error {
	idMap := make(map[string]bool, len(*reqs))
	for _, r := range *reqs {
		if _, ok := idMap[r.ID]; ok {
			return fmt.Errorf("ID:%s is duplicated.", r.ID)
		}
		if err := validateRequest(&r); err != nil {
			return err
		}
		idMap[r.ID] = true
	}
	return nil
}

func validateRequest(r *Request) error {
	if r.Version != Version {
		return fmt.Errorf("malformed JSON-RPC version: %s", r.Version)
	}
	if r.Method == "" {
		return fmt.Errorf("empty method")
	}
	// empty method is treated as GET.
	if r.HttpMethod != "" && r.HttpMethod != "GET" && r.HttpMethod != "POST" {
		return fmt.Errorf("malformed HTTP method: %s", r.HttpMethod)
	}
	if r.ID == "" {
		return fmt.Errorf("empty id")
	}
	return nil
}
<|endoftext|>


<commit_before>// Tideland Go REST Server Library - JSON Web Token - Unit Tests
//
// Copyright (C) 2016 Frank Mueller / Tideland / Oldenburg / Germany
//
// All rights reserved. Use of this source code is governed
// by the new BSD license.

package jwt_test

//--------------------
// IMPORTS
//--------------------

import (
	"encoding/json"
	"testing"
	"time"

	"github.com/tideland/golib/audit"

	"github.com/tideland/gorest/jwt"
)

//--------------------
// TESTS
//--------------------

// TestClaimsMarshalling tests the marshalling of Claims
// to JSON and back.
func TestClaimsMarshalling(t *testing.T) {
	assert := audit.NewTestingAssertion(t, true)
	assert.Logf("testing claims marshalling")
	// First with uninitialised or empty claims.
	var claims jwt.Claims
	jsonValue, err := json.Marshal(claims)
	assert.Equal(string(jsonValue), "{}")
	assert.Nil(err)
	claims = jwt.NewClaims()
	jsonValue, err = json.Marshal(claims)
	assert.Equal(string(jsonValue), "{}")
	assert.Nil(err)
	// Now fill it.
	claims.Set("foo", "yadda")
	claims.Set("bar", 12345)
	assert.Length(claims, 2)
	jsonValue, err = json.Marshal(claims)
	assert.NotNil(jsonValue)
	assert.Nil(err)
	var unmarshalled jwt.Claims
	err = json.Unmarshal(jsonValue, &unmarshalled)
	assert.Nil(err)
	assert.Length(unmarshalled, 2)
	foo, ok := claims.Get("foo")
	assert.Equal(foo, "yadda")
	assert.True(ok)
	bar, ok := claims.GetInt("bar")
	assert.Equal(bar, 12345)
	assert.True(ok)
}

// TestClaimsBasic tests the low level operations
// on claims.
func TestClaimsBasic(t *testing.T) {
	assert := audit.NewTestingAssertion(t, true)
	assert.Logf("testing claims basic functions handling")
	// First with uninitialised claims.
	var claims jwt.Claims
	ok := claims.Contains("foo")
	assert.False(ok)
	nothing, ok := claims.Get("foo")
	assert.Nil(nothing)
	assert.False(ok)
	old := claims.Set("foo", "bar")
	assert.Nil(old)
	old = claims.Delete("foo")
	assert.Nil(old)
	// Now initialise it.
	claims = jwt.NewClaims()
	ok = claims.Contains("foo")
	assert.False(ok)
	nothing, ok = claims.Get("foo")
	assert.Nil(nothing)
	assert.False(ok)
	old = claims.Set("foo", "bar")
	assert.Nil(old)
	ok = claims.Contains("foo")
	assert.True(ok)
	foo, ok := claims.Get("foo")
	assert.Equal(foo, "bar")
	assert.True(ok)
	old = claims.Set("foo", "yadda")
	assert.Equal(old, "bar")
	// Finally delete it.
	old = claims.Delete("foo")
	assert.Equal(old, "yadda")
	old = claims.Delete("foo")
	assert.Nil(old)
	ok = claims.Contains("foo")
	assert.False(ok)
}

// TestClaimsString tests the string operations
// on claims.
func TestClaimsString(t *testing.T) {
	assert := audit.NewTestingAssertion(t, true)
	assert.Logf("testing claims string handling")
	claims := jwt.NewClaims()
	nothing := claims.Set("foo", "bar")
	assert.Nil(nothing)
	var foo string
	foo, ok := claims.GetString("foo")
	assert.Equal(foo, "bar")
	assert.True(ok)
	claims.Set("foo", 4711)
	foo, ok = claims.GetString("foo")
	assert.Equal(foo, "4711")
	assert.True(ok)
}

// TestClaimsInt tests the int operations
// on claims.
func TestClaimsInt(t *testing.T) {
	assert := audit.NewTestingAssertion(t, true)
	assert.Logf("testing claims int handling")
	claims := jwt.NewClaims()
	claims.Set("foo", 4711)
	claims.Set("bar", "4712")
	claims.Set("baz", 4713.0)
	claims.Set("yadda", "nope")
	foo, ok := claims.GetInt("foo")
	assert.Equal(foo, 4711)
	assert.True(ok)
	bar, ok := claims.GetInt("bar")
	assert.Equal(bar, 4712)
	assert.True(ok)
	baz, ok := claims.GetInt("baz")
	assert.Equal(baz, 4713)
	assert.True(ok)
	yadda, ok := claims.GetInt("yadda")
	assert.Equal(yadda, 0)
	assert.False(ok)
}

// TestClaimsFloat64 tests the float64 operations
// on claims.
func TestClaimsFloat64(t *testing.T) {
	assert := audit.NewTestingAssertion(t, true)
	assert.Logf("testing claims float64 handling")
	claims := jwt.NewClaims()
	claims.Set("foo", 4711)
	claims.Set("bar", "4712")
	claims.Set("baz", 4713.0)
	claims.Set("yadda", "nope")
	foo, ok := claims.GetFloat64("foo")
	assert.Equal(foo, 4711.0)
	assert.True(ok)
	bar, ok := claims.GetFloat64("bar")
	assert.Equal(bar, 4712.0)
	assert.True(ok)
	baz, ok := claims.GetFloat64("baz")
	assert.Equal(baz, 4713.0)
	assert.True(ok)
	yadda, ok := claims.GetFloat64("yadda")
	assert.Equal(yadda, 0.0)
	assert.False(ok)
}

// TestClaimsTime tests the time operations
// on claims.
func TestClaimsTime(t *testing.T) {
	assert := audit.NewTestingAssertion(t, true)
	assert.Logf("testing claims time handling")
	goLaunch := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
	claims := jwt.NewClaims()
	claims.SetTime("foo", goLaunch)
	claims.Set("bar", goLaunch.Unix())
	claims.Set("baz", goLaunch.Format(time.RFC3339))
	claims.Set("yadda", "nope")
	foo, ok := claims.GetTime("foo")
	assert.Equal(foo.Unix(), goLaunch.Unix())
	assert.True(ok)
	bar, ok := claims.GetTime("bar")
	assert.Equal(bar.Unix(), goLaunch.Unix())
	assert.True(ok)
	baz, ok := claims.GetTime("baz")
	assert.Equal(baz.Unix(), goLaunch.Unix())
	assert.True(ok)
	yadda, ok := claims.GetTime("yadda")
	assert.Equal(yadda, time.Time{})
	assert.False(ok)
}

// TestClaimsValidity checks the validation of the not before
// and the expiring time.
func TestClaimsValidity(t *testing.T) {
	assert := audit.NewTestingAssertion(t, true)
	assert.Logf("testing claims validity")
	claims := jwt.NewClaims()
	// Valid claims.
	now := time.Now()
	nbf := now.Add(-time.Hour)
	exp := now.Add(time.Hour)
	err := claims.ValidateTimes()
	assert.Nil(err)
	claims.SetNotBefore(nbf)
	err = claims.ValidateTimes()
	assert.Nil(err)
	claims.SetExpiration(exp)
	err = claims.ValidateTimes()
	assert.Nil(err)
	// Invalid claims.
	nbf = now.Add(time.Hour)
	exp = now.Add(-time.Hour)
	claims.SetNotBefore(nbf)
	claims.DeleteExpiration()
	err = claims.ValidateTimes()
	assert.ErrorMatch(err, ".*token is not yet valid.*")
	claims.DeleteNotBefore()
	claims.SetExpiration(exp)
	err = claims.ValidateTimes()
	assert.ErrorMatch(err, ".*token is expired.*")
}

// EOF
<commit_msg>Quickly added two invalid values in claims time verification<commit_after>// Tideland Go REST Server Library - JSON Web Token - Unit Tests
//
// Copyright (C) 2016 Frank Mueller / Tideland / Oldenburg / Germany
//
// All rights reserved. Use of this source code is governed
// by the new BSD license.

package jwt_test

//--------------------
// IMPORTS
//--------------------

import (
	"encoding/json"
	"testing"
	"time"

	"github.com/tideland/golib/audit"

	"github.com/tideland/gorest/jwt"
)

//--------------------
// TESTS
//--------------------

// TestClaimsMarshalling tests the marshalling of Claims
// to JSON and back.
func TestClaimsMarshalling(t *testing.T) {
	assert := audit.NewTestingAssertion(t, true)
	assert.Logf("testing claims marshalling")
	// First with uninitialised or empty claims.
	var claims jwt.Claims
	jsonValue, err := json.Marshal(claims)
	assert.Equal(string(jsonValue), "{}")
	assert.Nil(err)
	claims = jwt.NewClaims()
	jsonValue, err = json.Marshal(claims)
	assert.Equal(string(jsonValue), "{}")
	assert.Nil(err)
	// Now fill it.
	claims.Set("foo", "yadda")
	claims.Set("bar", 12345)
	assert.Length(claims, 2)
	jsonValue, err = json.Marshal(claims)
	assert.NotNil(jsonValue)
	assert.Nil(err)
	var unmarshalled jwt.Claims
	err = json.Unmarshal(jsonValue, &unmarshalled)
	assert.Nil(err)
	assert.Length(unmarshalled, 2)
	foo, ok := claims.Get("foo")
	assert.Equal(foo, "yadda")
	assert.True(ok)
	bar, ok := claims.GetInt("bar")
	assert.Equal(bar, 12345)
	assert.True(ok)
}

// TestClaimsBasic tests the low level operations
// on claims.
func TestClaimsBasic(t *testing.T) {
	assert := audit.NewTestingAssertion(t, true)
	assert.Logf("testing claims basic functions handling")
	// First with uninitialised claims.
	var claims jwt.Claims
	ok := claims.Contains("foo")
	assert.False(ok)
	nothing, ok := claims.Get("foo")
	assert.Nil(nothing)
	assert.False(ok)
	old := claims.Set("foo", "bar")
	assert.Nil(old)
	old = claims.Delete("foo")
	assert.Nil(old)
	// Now initialise it.
	claims = jwt.NewClaims()
	ok = claims.Contains("foo")
	assert.False(ok)
	nothing, ok = claims.Get("foo")
	assert.Nil(nothing)
	assert.False(ok)
	old = claims.Set("foo", "bar")
	assert.Nil(old)
	ok = claims.Contains("foo")
	assert.True(ok)
	foo, ok := claims.Get("foo")
	assert.Equal(foo, "bar")
	assert.True(ok)
	old = claims.Set("foo", "yadda")
	assert.Equal(old, "bar")
	// Finally delete it.
	old = claims.Delete("foo")
	assert.Equal(old, "yadda")
	old = claims.Delete("foo")
	assert.Nil(old)
	ok = claims.Contains("foo")
	assert.False(ok)
}

// TestClaimsString tests the string operations
// on claims.
func TestClaimsString(t *testing.T) {
	assert := audit.NewTestingAssertion(t, true)
	assert.Logf("testing claims string handling")
	claims := jwt.NewClaims()
	nothing := claims.Set("foo", "bar")
	assert.Nil(nothing)
	var foo string
	foo, ok := claims.GetString("foo")
	assert.Equal(foo, "bar")
	assert.True(ok)
	claims.Set("foo", 4711)
	foo, ok = claims.GetString("foo")
	assert.Equal(foo, "4711")
	assert.True(ok)
}

// TestClaimsInt tests the int operations
// on claims.
func TestClaimsInt(t *testing.T) {
	assert := audit.NewTestingAssertion(t, true)
	assert.Logf("testing claims int handling")
	claims := jwt.NewClaims()
	claims.Set("foo", 4711)
	claims.Set("bar", "4712")
	claims.Set("baz", 4713.0)
	claims.Set("yadda", "nope")
	foo, ok := claims.GetInt("foo")
	assert.Equal(foo, 4711)
	assert.True(ok)
	bar, ok := claims.GetInt("bar")
	assert.Equal(bar, 4712)
	assert.True(ok)
	baz, ok := claims.GetInt("baz")
	assert.Equal(baz, 4713)
	assert.True(ok)
	yadda, ok := claims.GetInt("yadda")
	assert.Equal(yadda, 0)
	assert.False(ok)
}

// TestClaimsFloat64 tests the float64 operations
// on claims.
func TestClaimsFloat64(t *testing.T) {
	assert := audit.NewTestingAssertion(t, true)
	assert.Logf("testing claims float64 handling")
	claims := jwt.NewClaims()
	claims.Set("foo", 4711)
	claims.Set("bar", "4712")
	claims.Set("baz", 4713.0)
	claims.Set("yadda", "nope")
	foo, ok := claims.GetFloat64("foo")
	assert.Equal(foo, 4711.0)
	assert.True(ok)
	bar, ok := claims.GetFloat64("bar")
	assert.Equal(bar, 4712.0)
	assert.True(ok)
	baz, ok := claims.GetFloat64("baz")
	assert.Equal(baz, 4713.0)
	assert.True(ok)
	yadda, ok := claims.GetFloat64("yadda")
	assert.Equal(yadda, 0.0)
	assert.False(ok)
}

// TestClaimsTime tests the time operations
// on claims.
func TestClaimsTime(t *testing.T) {
	assert := audit.NewTestingAssertion(t, true)
	assert.Logf("testing claims time handling")
	goLaunch := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
	claims := jwt.NewClaims()
	claims.SetTime("foo", goLaunch)
	claims.Set("bar", goLaunch.Unix())
	claims.Set("baz", goLaunch.Format(time.RFC3339))
	claims.Set("yadda", "nope")
	foo, ok := claims.GetTime("foo")
	assert.Equal(foo.Unix(), goLaunch.Unix())
	assert.True(ok)
	bar, ok := claims.GetTime("bar")
	assert.Equal(bar.Unix(), goLaunch.Unix())
	assert.True(ok)
	baz, ok := claims.GetTime("baz")
	assert.Equal(baz.Unix(), goLaunch.Unix())
	assert.True(ok)
	yadda, ok := claims.GetTime("yadda")
	assert.Equal(yadda, time.Time{})
	assert.False(ok)
}

// TestClaimsValidity checks the validation of the not before
// and the expiring time.
func TestClaimsValidity(t *testing.T) {
	assert := audit.NewTestingAssertion(t, true)
	assert.Logf("testing claims validity")
	claims := jwt.NewClaims()
	// Valid claims.
	now := time.Now()
	nbf := now.Add(-time.Hour)
	exp := now.Add(time.Hour)
	err := claims.ValidateTimes()
	assert.Nil(err)
	claims.SetNotBefore(nbf)
	err = claims.ValidateTimes()
	assert.Nil(err)
	claims.SetExpiration(exp)
	err = claims.ValidateTimes()
	assert.Nil(err)
	// Invalid claims.
	nbf = now.Add(time.Hour)
	exp = now.Add(-time.Hour)
	claims.SetNotBefore(nbf)
	claims.DeleteExpiration()
	err = claims.ValidateTimes()
	assert.ErrorMatch(err, ".*token is not yet valid.*")
	claims.DeleteNotBefore()
	claims.SetExpiration(exp)
	err = claims.ValidateTimes()
	assert.ErrorMatch(err, ".*token is expired.*")
	// Now both are invalid, so error check is only on not
	// nil due to possible changing internal verification
	// order.
	claims.SetNotBefore(nbf)
	err = claims.ValidateTimes()
	assert.NotNil(err)
}

// EOF
<|endoftext|>


<commit_before>package jsonschema

import (
	// "encoding/json"
	"fmt"
	"net"
	"net/mail"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	hostname       string = `^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`
	unescapedTilda        = `\~[^01]`
	endingTilda           = `\~$`
	schemePrefix          = `^[^\:]+\:`
	uriTemplate           = `\{[^\{\}\\]*\}`
)

var (
	// emailPattern           = regexp.MustCompile(email)
	hostnamePattern        = regexp.MustCompile(hostname)
	unescaptedTildaPattern = regexp.MustCompile(unescapedTilda)
	endingTildaPattern     = regexp.MustCompile(endingTilda)
	schemePrefixPattern    = regexp.MustCompile(schemePrefix)
	uriTemplatePattern     = regexp.MustCompile(uriTemplate)
	disallowedIdnChars     = map[string]bool{"\u0020": true, "\u002D": true, "\u00A2": true, "\u00A3": true, "\u00A4": true, "\u00A5": true, "\u034F": true, "\u0640": true, "\u07FA": true, "\u180B": true, "\u180C": true, "\u180D": true, "\u200B": true, "\u2060": true, "\u2104": true, "\u2108": true, "\u2114": true, "\u2117": true, "\u2118": true, "\u211E": true, "\u211F": true, "\u2123": true, "\u2125": true, "\u2282": true, "\u2283": true, "\u2284": true, "\u2285": true, "\u2286": true, "\u2287": true, "\u2288": true, "\u2616": true, "\u2617": true, "\u2619": true, "\u262F": true, "\u2638": true, "\u266C": true, "\u266D": true, "\u266F": true, "\u2752": true, "\u2756": true, "\u2758": true, "\u275E": true, "\u2761": true, "\u2775": true, "\u2794": true, "\u2798": true, "\u27AF": true, "\u27B1": true, "\u27BE": true, "\u3004": true, "\u3012": true, "\u3013": true, "\u3020": true, "\u302E": true, "\u302F": true, "\u3031": true, "\u3032": true, "\u3035": true, "\u303B": true, "\u3164": true, "\uFFA0": true}
)

// for json pointers

// func FormatType(data interface{}) string {
// 	switch
// }
// Note: Date and time format names are derived from RFC 3339, section
// 5.6  [RFC3339].
// http://json-schema.org/latest/json-schema-validation.html#RFC3339

type format string

func newFormat() Validator {
	return new(format)
}

func (f format) Validate(data interface{}) []ValError {
	var err error
	if str, ok := data.(string); ok {
		switch f {
		case "date-time":
			err = isValidDateTime(str)
		case "date":
			err = isValidDate(str)
		case "email":
			err = isValidEmail(str)
		case "hostname":
			err = isValidHostname(str)
		case "idn-email":
			err = isValidIdnEmail(str)
		case "idn-hostname":
			err = isValidIdnHostname(str)
		case "ipv4":
			err = isValidIPv4(str)
		case "ipv6":
			err = isValidIPv6(str)
		case "iri-reference":
			err = isValidIriRef(str)
		case "iri":
			err = isValidIri(str)
		case "json-pointer":
			err = isValidJSONPointer(str)
		case "regex":
			err = isValidRegex(str)
		case "relative-json-pointer":
			err = isValidRelJSONPointer(str)
		case "time":
			err = isValidTime(str)
		case "uri-reference":
			err = isValidURIRef(str)
		case "uri-template":
			err = isValidURITemplate(str)
		case "uri":
			err = isValidURI(str)
		default:
			err = nil
		}
		if err != nil {
			return []ValError{
				{Message: err.Error()},
			}
		}
	}
	return nil
}

// A string instance is valid against "date-time" if it is a valid
// representation according to the "date-time" production derived
// from RFC 3339, section 5.6 [RFC3339]
// https://tools.ietf.org/html/rfc3339#section-5.6
func isValidDateTime(dateTime string) error {
	if _, err := time.Parse(time.RFC3339, dateTime); err != nil {
		return fmt.Errorf("date-time incorrectly formatted: %s", err.Error())
	}
	return nil
}

// A string instance is valid against "date" if it is a valid
// representation according to the "full-date" production derived
// from RFC 3339, section 5.6 [RFC3339]
// https://tools.ietf.org/html/rfc3339#section-5.6
func isValidDate(date string) error {
	arbitraryTime := "T08:30:06.283185Z"
	dateTime := fmt.Sprintf("%s%s", date, arbitraryTime)
	return isValidDateTime(dateTime)
}

// A string instance is valid against "email" if it is a valid
// representation as defined by RFC 5322, section 3.4.1 [RFC5322].
// https://tools.ietf.org/html/rfc5322#section-3.4.1
func isValidEmail(email string) error {
	// if !emailPattern.MatchString(email) {
	// 	return fmt.Errorf("invalid email format")
	// }
	if _, err := mail.ParseAddress(email); err != nil {
		return fmt.Errorf("email address incorrectly formatted: %s", err.Error())
	}
	return nil
}

// A string instance is valid against "hostname" if it is a valid
// representation as defined by RFC 1034, section 3.1 [RFC1034],
// including host names produced using the Punycode algorithm
// specified in RFC 5891, section 4.4 [RFC5891].
// https://tools.ietf.org/html/rfc1034#section-3.1
// https://tools.ietf.org/html/rfc5891#section-4.4
func isValidHostname(hostname string) error {
	if !hostnamePattern.MatchString(hostname) || len(hostname) > 255 {
		return fmt.Errorf("invalid hostname string")
	}
	return nil
}

// A string instance is valid against "idn-email" if it is a valid
// representation as defined by RFC 6531 [RFC6531]
// https://tools.ietf.org/html/rfc6531
func isValidIdnEmail(idnEmail string) error {
	if _, err := mail.ParseAddress(idnEmail); err != nil {
		return fmt.Errorf("email address incorrectly formatted: %s", err.Error())
	}
	return nil
}

// A string instance is valid against "hostname" if it is a valid
// representation as defined by either RFC 1034 as for hostname, or
// an internationalized hostname as defined by RFC 5890, section
// 2.3.2.3 [RFC5890].
// https://tools.ietf.org/html/rfc1034
// https://tools.ietf.org/html/rfc5890#section-2.3.2.3
// https://pdfs.semanticscholar.org/9275/6bcecb29d3dc407e23a997b256be6ff4149d.pdf
func isValidIdnHostname(idnHostname string) error {
	if len(idnHostname) > 255 {
		return fmt.Errorf("invalid idn hostname string")
	}
	for _, r := range idnHostname {
		s := string(r)
		if disallowedIdnChars[s] {
			return fmt.Errorf("invalid hostname: contains illegal character %#U", r)
		}
	}
	return nil
}

// A string instance is valid against "ipv4" if it is a valid
// representation of an IPv4 address according to the "dotted-quad"
// ABNF syntax as defined in RFC 2673, section 3.2 [RFC2673].
// https://tools.ietf.org/html/rfc2673#section-3.2
func isValidIPv4(ipv4 string) error {
	parsedIP := net.ParseIP(ipv4)
	hasDots := strings.Contains(ipv4, ".")
	if !hasDots || parsedIP == nil {
		return fmt.Errorf("invalid IPv4 address")
	}
	return nil
}

// A string instance is valid against "ipv6" if it is a valid
// representation of an IPv6 address as defined in RFC 4291, section
// 2.2 [RFC4291].
// https://tools.ietf.org/html/rfc4291#section-2.2
func isValidIPv6(ipv6 string) error {
	parsedIP := net.ParseIP(ipv6)
	hasColons := strings.Contains(ipv6, ":")
	if !hasColons || parsedIP == nil {
		return fmt.Errorf("invalid IPv4 address")
	}
	return nil
}

// A string instance is a valid against "iri-reference" if it is a
// valid IRI Reference (either an IRI or a relative-reference),
// according to [RFC3987].
// https://tools.ietf.org/html/rfc3987
func isValidIriRef(iriRef string) error {
	return isValidURIRef(iriRef)
}

// A string instance is a valid against "iri" if it is a valid IRI,
// according to [RFC3987].
// https://tools.ietf.org/html/rfc3987
func isValidIri(iri string) error {
	return isValidURI(iri)
}

// A string instance is a valid against "json-pointer" if it is a
// valid JSON string representation of a JSON Pointer, according to
// RFC 6901, section 5 [RFC6901].
// https://tools.ietf.org/html/rfc6901#section-5
func isValidJSONPointer(jsonPointer string) error {
	if len(jsonPointer) == 0 {
		return nil
	}
	if jsonPointer[0] != '/' {
		return fmt.Errorf("non-empty references must begin with a '/' character")
	}
	str := jsonPointer[1:]
	if unescaptedTildaPattern.MatchString(str) {
		return fmt.Errorf("unescaped tilda error")
	}
	if endingTildaPattern.MatchString(str) {
		return fmt.Errorf("unescaped tilda error")
	}
	return nil
}

// A string instance is a valid against "regex" if it is a valid
// regular expression according to the ECMA 262 [ecma262] regular
// expression dialect. Implementations that validate formats MUST
// accept at least the subset of ECMA 262 defined in the Regular
// Expressions [regexInterop] section of this specification, and
// SHOULD accept all valid ECMA 262 expressions.
// http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
// http://json-schema.org/latest/json-schema-validation.html#regexInterop
// https://tools.ietf.org/html/rfc7159
func isValidRegex(regex string) error {
	if _, err := regexp.Compile(regex); err != nil {
		return fmt.Errorf("invalid regex expression")
	}
	return nil
}

// A string instance is a valid against "relative-json-pointer" if it
// is a valid Relative JSON Pointer [relative-json-pointer].
// https://tools.ietf.org/html/draft-handrews-relative-json-pointer-00
func isValidRelJSONPointer(relJSONPointer string) error {
	parts := strings.Split(relJSONPointer, "/")
	if len(parts) == 1 {
		parts = strings.Split(relJSONPointer, "#")
	}
	if i, err := strconv.Atoi(parts[0]); err != nil || i < 0 {
		return fmt.Errorf("RJP must begin with positive integer")
	}
	//skip over first part
	str := relJSONPointer[len(parts[0]):]
	if len(str) > 0 && str[0] == '#' {
		return nil
	}
	return isValidJSONPointer(str)
}

// A string instance is valid against "time" if it is a valid
// representation according to the "full-time" production derived
// from RFC 3339, section 5.6 [RFC3339]
// https://tools.ietf.org/html/rfc3339#section-5.6
func isValidTime(time string) error {
	arbitraryDate := "1963-06-19"
	dateTime := fmt.Sprintf("%sT%s", arbitraryDate, time)
	return isValidDateTime(dateTime)
	return nil
}

// A string instance is a valid against "uri-reference" if it is a
// valid URI Reference (either a URI or a relative-reference),
// according to [RFC3986].
// https://tools.ietf.org/html/rfc3986
func isValidURIRef(uriRef string) error {
	if _, err := url.Parse(uriRef); err != nil {
		return fmt.Errorf("uri incorrectly formatted: %s", err.Error())
	}
	if strings.Contains(uriRef, "\\") {
		return fmt.Errorf("invalid uri")
	}
	return nil
}

// A string instance is a valid against "uri-template" if it is a
// valid URI Template (of any level), according to [RFC6570]. Note
// that URI Templates may be used for IRIs; there is no separate IRI
// Template specification.
// https://tools.ietf.org/html/rfc6570
func isValidURITemplate(uriTemplate string) error {
	arbitraryValue := "aaa"
	uriRef := uriTemplatePattern.ReplaceAllString(uriTemplate, arbitraryValue)
	if strings.Contains(uriRef, "{") || strings.Contains(uriRef, "}") {
		return fmt.Errorf("invalid uri template")
	}
	return isValidURIRef(uriRef)
}

// A string instance is a valid against "uri" if it is a valid URI,
// according to [RFC3986].
// https://tools.ietf.org/html/rfc3986
func isValidURI(uri string) error {
	if _, err := url.Parse(uri); err != nil {
		return fmt.Errorf("uri incorrectly formatted: %s", err.Error())
	}
	if !schemePrefixPattern.MatchString(uri) {
		return fmt.Errorf("uri missing scheme prefix")
	}
	return nil
}
<commit_msg>chore: renamed idn to follow capitilization convention<commit_after>package jsonschema

import (
	// "encoding/json"
	"fmt"
	"net"
	"net/mail"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	hostname       string = `^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$`
	unescapedTilda        = `\~[^01]`
	endingTilda           = `\~$`
	schemePrefix          = `^[^\:]+\:`
	uriTemplate           = `\{[^\{\}\\]*\}`
)

var (
	// emailPattern           = regexp.MustCompile(email)
	hostnamePattern        = regexp.MustCompile(hostname)
	unescaptedTildaPattern = regexp.MustCompile(unescapedTilda)
	endingTildaPattern     = regexp.MustCompile(endingTilda)
	schemePrefixPattern    = regexp.MustCompile(schemePrefix)
	uriTemplatePattern     = regexp.MustCompile(uriTemplate)

	disallowedIdnChars = map[string]bool{"\u0020": true, "\u002D": true, "\u00A2": true, "\u00A3": true, "\u00A4": true, "\u00A5": true, "\u034F": true, "\u0640": true, "\u07FA": true, "\u180B": true, "\u180C": true, "\u180D": true, "\u200B": true, "\u2060": true, "\u2104": true, "\u2108": true, "\u2114": true, "\u2117": true, "\u2118": true, "\u211E": true, "\u211F": true, "\u2123": true, "\u2125": true, "\u2282": true, "\u2283": true, "\u2284": true, "\u2285": true, "\u2286": true, "\u2287": true, "\u2288": true, "\u2616": true, "\u2617": true, "\u2619": true, "\u262F": true, "\u2638": true, "\u266C": true, "\u266D": true, "\u266F": true, "\u2752": true, "\u2756": true, "\u2758": true, "\u275E": true, "\u2761": true, "\u2775": true, "\u2794": true, "\u2798": true, "\u27AF": true, "\u27B1": true, "\u27BE": true, "\u3004": true, "\u3012": true, "\u3013": true, "\u3020": true, "\u302E": true, "\u302F": true, "\u3031": true, "\u3032": true, "\u3035": true, "\u303B": true, "\u3164": true, "\uFFA0": true}
)

// for json pointers

// func FormatType(data interface{}) string {
// 	switch
// }
// Note: Date and time format names are derived from RFC 3339, section
// 5.6  [RFC3339].
// http://json-schema.org/latest/json-schema-validation.html#RFC3339

type format string

func newFormat() Validator {
	return new(format)
}

func (f format) Validate(data interface{}) []ValError {
	var err error
	if str, ok := data.(string); ok {
		switch f {
		case "date-time":
			err = isValidDateTime(str)
		case "date":
			err = isValidDate(str)
		case "email":
			err = isValidEmail(str)
		case "hostname":
			err = isValidHostname(str)
		case "idn-email":
			err = isValidIDNEmail(str)
		case "idn-hostname":
			err = isValidIDNHostname(str)
		case "ipv4":
			err = isValidIPv4(str)
		case "ipv6":
			err = isValidIPv6(str)
		case "iri-reference":
			err = isValidIriRef(str)
		case "iri":
			err = isValidIri(str)
		case "json-pointer":
			err = isValidJSONPointer(str)
		case "regex":
			err = isValidRegex(str)
		case "relative-json-pointer":
			err = isValidRelJSONPointer(str)
		case "time":
			err = isValidTime(str)
		case "uri-reference":
			err = isValidURIRef(str)
		case "uri-template":
			err = isValidURITemplate(str)
		case "uri":
			err = isValidURI(str)
		default:
			err = nil
		}
		if err != nil {
			return []ValError{
				{Message: err.Error()},
			}
		}
	}
	return nil
}

// A string instance is valid against "date-time" if it is a valid
// representation according to the "date-time" production derived
// from RFC 3339, section 5.6 [RFC3339]
// https://tools.ietf.org/html/rfc3339#section-5.6
func isValidDateTime(dateTime string) error {
	if _, err := time.Parse(time.RFC3339, dateTime); err != nil {
		return fmt.Errorf("date-time incorrectly formatted: %s", err.Error())
	}
	return nil
}

// A string instance is valid against "date" if it is a valid
// representation according to the "full-date" production derived
// from RFC 3339, section 5.6 [RFC3339]
// https://tools.ietf.org/html/rfc3339#section-5.6
func isValidDate(date string) error {
	arbitraryTime := "T08:30:06.283185Z"
	dateTime := fmt.Sprintf("%s%s", date, arbitraryTime)
	return isValidDateTime(dateTime)
}

// A string instance is valid against "email" if it is a valid
// representation as defined by RFC 5322, section 3.4.1 [RFC5322].
// https://tools.ietf.org/html/rfc5322#section-3.4.1
func isValidEmail(email string) error {
	// if !emailPattern.MatchString(email) {
	// 	return fmt.Errorf("invalid email format")
	// }
	if _, err := mail.ParseAddress(email); err != nil {
		return fmt.Errorf("email address incorrectly formatted: %s", err.Error())
	}
	return nil
}

// A string instance is valid against "hostname" if it is a valid
// representation as defined by RFC 1034, section 3.1 [RFC1034],
// including host names produced using the Punycode algorithm
// specified in RFC 5891, section 4.4 [RFC5891].
// https://tools.ietf.org/html/rfc1034#section-3.1
// https://tools.ietf.org/html/rfc5891#section-4.4
func isValidHostname(hostname string) error {
	if !hostnamePattern.MatchString(hostname) || len(hostname) > 255 {
		return fmt.Errorf("invalid hostname string")
	}
	return nil
}

// A string instance is valid against "idn-email" if it is a valid
// representation as defined by RFC 6531 [RFC6531]
// https://tools.ietf.org/html/rfc6531
func isValidIDNEmail(idnEmail string) error {
	if _, err := mail.ParseAddress(idnEmail); err != nil {
		return fmt.Errorf("email address incorrectly formatted: %s", err.Error())
	}
	return nil
}

// A string instance is valid against "hostname" if it is a valid
// representation as defined by either RFC 1034 as for hostname, or
// an internationalized hostname as defined by RFC 5890, section
// 2.3.2.3 [RFC5890].
// https://tools.ietf.org/html/rfc1034
// https://tools.ietf.org/html/rfc5890#section-2.3.2.3
// https://pdfs.semanticscholar.org/9275/6bcecb29d3dc407e23a997b256be6ff4149d.pdf
func isValidIDNHostname(idnHostname string) error {
	if len(idnHostname) > 255 {
		return fmt.Errorf("invalid idn hostname string")
	}
	for _, r := range idnHostname {
		s := string(r)
		if disallowedIdnChars[s] {
			return fmt.Errorf("invalid hostname: contains illegal character %#U", r)
		}
	}
	return nil
}

// A string instance is valid against "ipv4" if it is a valid
// representation of an IPv4 address according to the "dotted-quad"
// ABNF syntax as defined in RFC 2673, section 3.2 [RFC2673].
// https://tools.ietf.org/html/rfc2673#section-3.2
func isValidIPv4(ipv4 string) error {
	parsedIP := net.ParseIP(ipv4)
	hasDots := strings.Contains(ipv4, ".")
	if !hasDots || parsedIP == nil {
		return fmt.Errorf("invalid IPv4 address")
	}
	return nil
}

// A string instance is valid against "ipv6" if it is a valid
// representation of an IPv6 address as defined in RFC 4291, section
// 2.2 [RFC4291].
// https://tools.ietf.org/html/rfc4291#section-2.2
func isValidIPv6(ipv6 string) error {
	parsedIP := net.ParseIP(ipv6)
	hasColons := strings.Contains(ipv6, ":")
	if !hasColons || parsedIP == nil {
		return fmt.Errorf("invalid IPv4 address")
	}
	return nil
}

// A string instance is a valid against "iri-reference" if it is a
// valid IRI Reference (either an IRI or a relative-reference),
// according to [RFC3987].
// https://tools.ietf.org/html/rfc3987
func isValidIriRef(iriRef string) error {
	return isValidURIRef(iriRef)
}

// A string instance is a valid against "iri" if it is a valid IRI,
// according to [RFC3987].
// https://tools.ietf.org/html/rfc3987
func isValidIri(iri string) error {
	return isValidURI(iri)
}

// A string instance is a valid against "json-pointer" if it is a
// valid JSON string representation of a JSON Pointer, according to
// RFC 6901, section 5 [RFC6901].
// https://tools.ietf.org/html/rfc6901#section-5
func isValidJSONPointer(jsonPointer string) error {
	if len(jsonPointer) == 0 {
		return nil
	}
	if jsonPointer[0] != '/' {
		return fmt.Errorf("non-empty references must begin with a '/' character")
	}
	str := jsonPointer[1:]
	if unescaptedTildaPattern.MatchString(str) {
		return fmt.Errorf("unescaped tilda error")
	}
	if endingTildaPattern.MatchString(str) {
		return fmt.Errorf("unescaped tilda error")
	}
	return nil
}

// A string instance is a valid against "regex" if it is a valid
// regular expression according to the ECMA 262 [ecma262] regular
// expression dialect. Implementations that validate formats MUST
// accept at least the subset of ECMA 262 defined in the Regular
// Expressions [regexInterop] section of this specification, and
// SHOULD accept all valid ECMA 262 expressions.
// http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
// http://json-schema.org/latest/jsoxn-schema-validation.html#regexInterop
// https://tools.ietf.org/html/rfc7159
func isValidRegex(regex string) error {
	if _, err := regexp.Compile(regex); err != nil {
		return fmt.Errorf("invalid regex expression")
	}
	return nil
}

// A string instance is a valid against "relative-json-pointer" if it
// is a valid Relative JSON Pointer [relative-json-pointer].
// https://tools.ietf.org/html/draft-handrews-relative-json-pointer-00
func isValidRelJSONPointer(relJSONPointer string) error {
	parts := strings.Split(relJSONPointer, "/")
	if len(parts) == 1 {
		parts = strings.Split(relJSONPointer, "#")
	}
	if i, err := strconv.Atoi(parts[0]); err != nil || i < 0 {
		return fmt.Errorf("RJP must begin with positive integer")
	}
	//skip over first part
	str := relJSONPointer[len(parts[0]):]
	if len(str) > 0 && str[0] == '#' {
		return nil
	}
	return isValidJSONPointer(str)
}

// A string instance is valid against "time" if it is a valid
// representation according to the "full-time" production derived
// from RFC 3339, section 5.6 [RFC3339]
// https://tools.ietf.org/html/rfc3339#section-5.6
func isValidTime(time string) error {
	arbitraryDate := "1963-06-19"
	dateTime := fmt.Sprintf("%sT%s", arbitraryDate, time)
	return isValidDateTime(dateTime)
	return nil
}

// A string instance is a valid against "uri-reference" if it is a
// valid URI Reference (either a URI or a relative-reference),
// according to [RFC3986].
// https://tools.ietf.org/html/rfc3986
func isValidURIRef(uriRef string) error {
	if _, err := url.Parse(uriRef); err != nil {
		return fmt.Errorf("uri incorrectly formatted: %s", err.Error())
	}
	if strings.Contains(uriRef, "\\") {
		return fmt.Errorf("invalid uri")
	}
	return nil
}

// A string instance is a valid against "uri-template" if it is a
// valid URI Template (of any level), according to [RFC6570]. Note
// that URI Templates may be used for IRIs; there is no separate IRI
// Template specification.
// https://tools.ietf.org/html/rfc6570
func isValidURITemplate(uriTemplate string) error {
	arbitraryValue := "aaa"
	uriRef := uriTemplatePattern.ReplaceAllString(uriTemplate, arbitraryValue)
	if strings.Contains(uriRef, "{") || strings.Contains(uriRef, "}") {
		return fmt.Errorf("invalid uri template")
	}
	return isValidURIRef(uriRef)
}

// A string instance is a valid against "uri" if it is a valid URI,
// according to [RFC3986].
// https://tools.ietf.org/html/rfc3986
func isValidURI(uri string) error {
	if _, err := url.Parse(uri); err != nil {
		return fmt.Errorf("uri incorrectly formatted: %s", err.Error())
	}
	if !schemePrefixPattern.MatchString(uri) {
		return fmt.Errorf("uri missing scheme prefix")
	}
	return nil
}
<|endoftext|>


<commit_before>package api

import (
	"fmt"
	"regexp"
	"strings"
)

const (
	// Resource types
	RESOURCE_GROUP  = "group"
	RESOURCE_USER   = "user"
	RESOURCE_POLICY = "policy"

	// Constraints
	MAX_EXTERNAL_ID_LENGTH = 128
	MAX_NAME_LENGTH        = 128
	MAX_ACTION_LENGTH      = 128
	MAX_PATH_LENGTH        = 512

	// Actions

	// User actions
	USER_ACTION_CREATE_USER          = "iam:CreateUser"
	USER_ACTION_DELETE_USER          = "iam:DeleteUser"
	USER_ACTION_GET_USER             = "iam:GetUser"
	USER_ACTION_LIST_USERS           = "iam:ListUsers"
	USER_ACTION_UPDATE_USER          = "iam:UpdateUser"
	USER_ACTION_LIST_GROUPS_FOR_USER = "iam:ListGroupsForUser"
	USER_ACTION_LIST_ORG_USERS       = "iam:ListOrgUsers"

	// Group actions
	GROUP_ACTION_CREATE_GROUP                 = "iam:CreateGroup"
	GROUP_ACTION_DELETE_GROUP                 = "iam:DeleteGroup"
	GROUP_ACTION_GET_GROUP                    = "iam:GetGroup"
	GROUP_ACTION_LIST_GROUPS                  = "iam:ListGroups"
	GROUP_ACTION_UPDATE_GROUP                 = "iam:UpdateGroup"
	GROUP_ACTION_LIST_MEMBERS                 = "iam:ListMembers"
	GROUP_ACTION_ADD_MEMBER                   = "iam:AddMember"
	GROUP_ACTION_REMOVE_MEMBER                = "iam:RemoveMember"
	GROUP_ACTION_ATTACH_GROUP_POLICY          = "iam:AttachGroupPolicy"
	GROUP_ACTION_DETACH_GROUP_POLICY          = "iam:DetachGroupPolicy"
	GROUP_ACTION_LIST_ATTACHED_GROUP_POLICIES = "iam:ListAttachedGroupPolicies"
	GROUP_ACTION_LIST_ALL_GROUPS              = "iam:ListAllGroups"

	// Policy actions
	POLICY_ACTION_CREATE_POLICY        = "iam:CreatePolicy"
	POLICY_ACTION_DELETE_POLICY        = "iam:DeletePolicy"
	POLICY_ACTION_UPDATE_POLICY        = "iam:UpdatePolicy"
	POLICY_ACTION_GET_POLICY           = "iam:GetPolicy"
	POLICY_ACTION_LIST_ATTACHED_GROUPS = "iam:ListAttachedGroups"
	POLICY_ACTION_LIST_POLICIES        = "iam:ListPolicies"
	POLICY_ACTION_LIST_ALL_POLICIES    = "iam:ListAllPolicies"
)

func CreateUrn(org string, resource string, path string, name string) string {
	switch resource {
	case RESOURCE_USER:
		return fmt.Sprintf("urn:iws:iam::user%v%v", path, name)
	default:
		return fmt.Sprintf("urn:iws:iam:%v:%v%v%v", org, resource, path, name)
	}
}

func GetUrnPrefix(org string, resource string, path string) string {
	switch resource {
	case RESOURCE_USER:
		return fmt.Sprintf("urn:iws:iam::user%v*", path)
	default:
		return fmt.Sprintf("urn:iws:iam:%v:%v%v*", org, resource, path)
	}
}

func IsValidUserExternalID(externalID string) bool {
	r, _ := regexp.Compile(`^[\w+.@\-]+$`)
	return r.MatchString(externalID) && len(externalID) < MAX_EXTERNAL_ID_LENGTH
}

// this func validates group and policy names
func IsValidName(name string) bool {
	r, _ := regexp.Compile(`^[\w\-]+$`)
	return r.MatchString(name) && len(name) < MAX_NAME_LENGTH
}

func IsValidPath(path string) bool {
	r, _ := regexp.Compile(`^/$|^/[\w+/\-]+\w+/$`)
	r2, _ := regexp.Compile(`[/]{2,}`)
	return r.MatchString(path) && !r2.MatchString(path) && len(path) < MAX_PATH_LENGTH
}

func IsValidEffect(effect string) error {
	if effect != "allow" && effect != "deny" {
		return &Error{
			Code:    REGEX_NO_MATCH,
			Message: fmt.Sprintf("No regex match in effect: %v", effect),
		}
	}
	return nil
}

func IsValidAction(actions []string) error {
	r, _ := regexp.Compile(`^[\w\-:]+[\w-*]+$`)
	r2, _ := regexp.Compile(`[*]{2,}|[:]{2,}`)
	for _, action := range actions {
		if !r.MatchString(action) || r2.MatchString(action) || len(action) > MAX_ACTION_LENGTH {
			return &Error{
				Code:    REGEX_NO_MATCH,
				Message: fmt.Sprintf("No regex match in action: %v", action),
			}
		}
	}
	return nil
}

func IsValidResources(resources []string) error {
	//err generator helper
	errFunc := func(resource string) error {
		return &Error{
			Code:    REGEX_NO_MATCH,
			Message: fmt.Sprintf("No regex match in resource: %v", resource),
		}
	}

	wordRegex, _ := regexp.Compile(`^[\w+\-.@]+$`)
	wordPrefixRegex, _ := regexp.Compile(`^[\w+\-.@]+\*$`)

	r, _ := regexp.Compile(`^\*$|^[\w+\-@.]+\*?$|^[\w+\-@.]+\*?$|^[\w+\-@.]+(/?([\w+\-@.]+/)*([\w+\-@.]|[*])+)?$`)
	r2, _ := regexp.Compile(`[/]{2,}|[:]{2,}|[*]{2,}`)

	for _, resource := range resources {
		blocks := strings.Split(resource, ":")
		for n, block := range blocks {
			switch n {
			case 0:
				if len(blocks) < 2 { // This is the last block
					if block != "*" {
						return errFunc(resource)
					}
				} else {
					if block != "urn" {
						return errFunc(resource)
					}
				}
			case 1:
				if len(blocks) < 3 { // This is the last block
					if block != "*" && !wordPrefixRegex.MatchString(block) {
						return errFunc(resource)
					}
				} else {
					if !wordRegex.MatchString(block) {
						return errFunc(resource)
					}
				}
			case 2:
				if len(blocks) < 4 { // This is the last block
					if block != "*" && !wordPrefixRegex.MatchString(block) {
						return errFunc(resource)
					}
				} else {
					if !wordRegex.MatchString(block) {
						return errFunc(resource)
					}
				}
			case 3:
				if len(blocks) < 5 { // This is the last block
					if block != "*" && !wordPrefixRegex.MatchString(block) {
						return errFunc(resource)
					}
				} else {
					if block != "" && !wordRegex.MatchString(block) {
						return errFunc(resource)
					}
				}
			case 4:
				if !r.MatchString(block) || r2.MatchString(block) {
					return errFunc(resource)
				}
			default:
				return &Error{
					Code:    INVALID_PARAMETER_ERROR,
					Message: fmt.Sprintf("Invalid resource definition: %v", resource),
				}

			}
		}
	}
	return nil
}

func IsValidStatement(statements *[]Statement) error {
	for _, statement := range *statements {
		err := IsValidEffect(statement.Effect)
		if err != nil {
			return err
		}
		err = IsValidAction(statement.Action)
		if err != nil {
			return err
		}
		err = IsValidResources(statement.Resources)
		if err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>Compile regexp once<commit_after>package api

import (
	"fmt"
	"regexp"
	"strings"
)

const (
	// Resource types
	RESOURCE_GROUP  = "group"
	RESOURCE_USER   = "user"
	RESOURCE_POLICY = "policy"

	// Constraints
	MAX_EXTERNAL_ID_LENGTH = 128
	MAX_NAME_LENGTH        = 128
	MAX_ACTION_LENGTH      = 128
	MAX_PATH_LENGTH        = 512

	// Actions

	// User actions
	USER_ACTION_CREATE_USER          = "iam:CreateUser"
	USER_ACTION_DELETE_USER          = "iam:DeleteUser"
	USER_ACTION_GET_USER             = "iam:GetUser"
	USER_ACTION_LIST_USERS           = "iam:ListUsers"
	USER_ACTION_UPDATE_USER          = "iam:UpdateUser"
	USER_ACTION_LIST_GROUPS_FOR_USER = "iam:ListGroupsForUser"
	USER_ACTION_LIST_ORG_USERS       = "iam:ListOrgUsers"

	// Group actions
	GROUP_ACTION_CREATE_GROUP                 = "iam:CreateGroup"
	GROUP_ACTION_DELETE_GROUP                 = "iam:DeleteGroup"
	GROUP_ACTION_GET_GROUP                    = "iam:GetGroup"
	GROUP_ACTION_LIST_GROUPS                  = "iam:ListGroups"
	GROUP_ACTION_UPDATE_GROUP                 = "iam:UpdateGroup"
	GROUP_ACTION_LIST_MEMBERS                 = "iam:ListMembers"
	GROUP_ACTION_ADD_MEMBER                   = "iam:AddMember"
	GROUP_ACTION_REMOVE_MEMBER                = "iam:RemoveMember"
	GROUP_ACTION_ATTACH_GROUP_POLICY          = "iam:AttachGroupPolicy"
	GROUP_ACTION_DETACH_GROUP_POLICY          = "iam:DetachGroupPolicy"
	GROUP_ACTION_LIST_ATTACHED_GROUP_POLICIES = "iam:ListAttachedGroupPolicies"
	GROUP_ACTION_LIST_ALL_GROUPS              = "iam:ListAllGroups"

	// Policy actions
	POLICY_ACTION_CREATE_POLICY        = "iam:CreatePolicy"
	POLICY_ACTION_DELETE_POLICY        = "iam:DeletePolicy"
	POLICY_ACTION_UPDATE_POLICY        = "iam:UpdatePolicy"
	POLICY_ACTION_GET_POLICY           = "iam:GetPolicy"
	POLICY_ACTION_LIST_ATTACHED_GROUPS = "iam:ListAttachedGroups"
	POLICY_ACTION_LIST_POLICIES        = "iam:ListPolicies"
	POLICY_ACTION_LIST_ALL_POLICIES    = "iam:ListAllPolicies"
)

var (
	rUserExtID, _          = regexp.Compile(`^[\w+.@\-]+$`)
	rName, _               = regexp.Compile(`^[\w\-]+$`)
	rPath, _               = regexp.Compile(`^/$|^/[\w+/\-]+\w+/$`)
	rPathExclude, _        = regexp.Compile(`[/]{2,}`)
	rAction, _             = regexp.Compile(`^[\w\-:]+[\w-*]+$`)
	rActionExclude, _      = regexp.Compile(`[*]{2,}|[:]{2,}`)
	rWordResource, _       = regexp.Compile(`^[\w+\-.@]+$`)
	rWordResourcePrefix, _ = regexp.Compile(`^[\w+\-.@]+\*$`)
	rUrn, _                = regexp.Compile(`^\*$|^[\w+\-@.]+\*?$|^[\w+\-@.]+\*?$|^[\w+\-@.]+(/?([\w+\-@.]+/)*([\w+\-@.]|[*])+)?$`)
	rUrnExclude, _         = regexp.Compile(`[/]{2,}|[:]{2,}|[*]{2,}`)
)

func CreateUrn(org string, resource string, path string, name string) string {
	switch resource {
	case RESOURCE_USER:
		return fmt.Sprintf("urn:iws:iam::user%v%v", path, name)
	default:
		return fmt.Sprintf("urn:iws:iam:%v:%v%v%v", org, resource, path, name)
	}
}

func GetUrnPrefix(org string, resource string, path string) string {
	switch resource {
	case RESOURCE_USER:
		return fmt.Sprintf("urn:iws:iam::user%v*", path)
	default:
		return fmt.Sprintf("urn:iws:iam:%v:%v%v*", org, resource, path)
	}
}

func IsValidUserExternalID(externalID string) bool {
	return rUserExtID.MatchString(externalID) && len(externalID) < MAX_EXTERNAL_ID_LENGTH
}

// this func validates group and policy names
func IsValidName(name string) bool {
	return rName.MatchString(name) && len(name) < MAX_NAME_LENGTH
}

func IsValidPath(path string) bool {
	return rPath.MatchString(path) && !rPathExclude.MatchString(path) && len(path) < MAX_PATH_LENGTH
}

func IsValidEffect(effect string) error {
	if effect != "allow" && effect != "deny" {
		return &Error{
			Code:    REGEX_NO_MATCH,
			Message: fmt.Sprintf("No regex match in effect: %v", effect),
		}
	}
	return nil
}

func IsValidAction(actions []string) error {

	for _, action := range actions {
		if !rAction.MatchString(action) || rActionExclude.MatchString(action) || len(action) > MAX_ACTION_LENGTH {
			return &Error{
				Code:    REGEX_NO_MATCH,
				Message: fmt.Sprintf("No regex match in action: %v", action),
			}
		}
	}
	return nil
}

func IsValidResources(resources []string) error {
	//err generator helper
	errFunc := func(resource string) error {
		return &Error{
			Code:    REGEX_NO_MATCH,
			Message: fmt.Sprintf("No regex match in resource: %v", resource),
		}
	}

	for _, resource := range resources {
		blocks := strings.Split(resource, ":")
		for n, block := range blocks {
			switch n {
			case 0:
				if len(blocks) < 2 { // This is the last block
					if block != "*" {
						return errFunc(resource)
					}
				} else {
					if block != "urn" {
						return errFunc(resource)
					}
				}
			case 1:
				if len(blocks) < 3 { // This is the last block
					if block != "*" && !rWordResourcePrefix.MatchString(block) {
						return errFunc(resource)
					}
				} else {
					if !rWordResource.MatchString(block) {
						return errFunc(resource)
					}
				}
			case 2:
				if len(blocks) < 4 { // This is the last block
					if block != "*" && !rWordResourcePrefix.MatchString(block) {
						return errFunc(resource)
					}
				} else {
					if !rWordResource.MatchString(block) {
						return errFunc(resource)
					}
				}
			case 3:
				if len(blocks) < 5 { // This is the last block
					if block != "*" && !rWordResourcePrefix.MatchString(block) {
						return errFunc(resource)
					}
				} else {
					if block != "" && !rWordResource.MatchString(block) {
						return errFunc(resource)
					}
				}
			case 4:
				if !rUrn.MatchString(block) || rUrnExclude.MatchString(block) {
					return errFunc(resource)
				}
			default:
				return &Error{
					Code:    INVALID_PARAMETER_ERROR,
					Message: fmt.Sprintf("Invalid resource definition: %v", resource),
				}

			}
		}
	}
	return nil
}

func IsValidStatement(statements *[]Statement) error {
	for _, statement := range *statements {
		err := IsValidEffect(statement.Effect)
		if err != nil {
			return err
		}
		err = IsValidAction(statement.Action)
		if err != nil {
			return err
		}
		err = IsValidResources(statement.Resources)
		if err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package apidCounters

import (
	"encoding/json"
	"fmt"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
)

// BeforeSuite setup and AfterSuite cleanup is in apidCounters_suite_test.go

var _ = Describe("api", func() {

	It("should get a counter", func() {

		counter := "api_test_1"
		count, err := getDBCounter(counter)
		Expect(err).NotTo(HaveOccurred())
		Expect(count).To(Equal(0))

		uri, err := url.Parse(testServer.URL)
		Expect(err).NotTo(HaveOccurred())
		uri.Path = fmt.Sprintf(countersBasePath+"/%s", counter)

		resp, err := http.Get(uri.String())
		defer resp.Body.Close()
		Expect(err).ShouldNot(HaveOccurred())
		Expect(resp.StatusCode).Should(Equal(http.StatusOK))

		body, err := ioutil.ReadAll(resp.Body)
		Expect(string(body)).Should(Equal(strconv.Itoa(count)))
	})

	It("should increment a counter", func() {

		counter := "api_test_2"
		count, err := getDBCounter(counter)
		Expect(err).NotTo(HaveOccurred())
		Expect(count).To(Equal(0))

		uri, err := url.Parse(testServer.URL)
		Expect(err).NotTo(HaveOccurred())
		uri.Path = fmt.Sprintf(countersBasePath+"/%s", counter)

		req, err := http.NewRequest("POST", uri.String(), nil)
		req.Header.Add("Content-Type", "application/json")
		resp, err := http.DefaultClient.Do(req)
		defer resp.Body.Close()
		Expect(err).ShouldNot(HaveOccurred())
		Expect(resp.StatusCode).Should(Equal(http.StatusOK))

		body, err := ioutil.ReadAll(resp.Body)
		Expect(string(body)).Should(Equal(strconv.Itoa(count)))

		count, err = getDBCounter(counter)
		Expect(err).NotTo(HaveOccurred())
		Expect(count).To(Equal(1))
	})

	It("should retrieve a list of counters", func() {

		counter1 := "api_test_3"
		counter2 := "api_test_4"
		err := incrementDBCounter(counter1)
		Expect(err).NotTo(HaveOccurred())
		err = incrementDBCounter(counter2)
		Expect(err).NotTo(HaveOccurred())
		err = incrementDBCounter(counter2)
		Expect(err).NotTo(HaveOccurred())

		uri, err := url.Parse(testServer.URL)
		Expect(err).NotTo(HaveOccurred())
		uri.Path = countersBasePath

		resp, err := http.Get(uri.String())
		defer resp.Body.Close()
		Expect(err).ShouldNot(HaveOccurred())
		Expect(resp.StatusCode).Should(Equal(http.StatusOK))

		body, err := ioutil.ReadAll(resp.Body)
		Expect(err).ShouldNot(HaveOccurred())
		var counterMap map[string]int
		json.Unmarshal(body, &counterMap)

		Expect(counterMap[counter1]).Should(Equal(1))
		Expect(counterMap[counter2]).Should(Equal(2))
	})
})
<commit_msg>hack fix a test<commit_after>package apidCounters

import (
	"encoding/json"
	"fmt"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

// BeforeSuite setup and AfterSuite cleanup is in apidCounters_suite_test.go

var _ = Describe("api", func() {

	It("should get a counter", func() {

		counter := "api_test_1"
		count, err := getDBCounter(counter)
		Expect(err).NotTo(HaveOccurred())
		Expect(count).To(Equal(0))

		uri, err := url.Parse(testServer.URL)
		Expect(err).NotTo(HaveOccurred())
		uri.Path = fmt.Sprintf(countersBasePath+"/%s", counter)

		resp, err := http.Get(uri.String())
		defer resp.Body.Close()
		Expect(err).ShouldNot(HaveOccurred())
		Expect(resp.StatusCode).Should(Equal(http.StatusOK))

		body, err := ioutil.ReadAll(resp.Body)
		Expect(string(body)).Should(Equal(strconv.Itoa(count)))
	})

	It("should increment a counter", func() {

		counter := "api_test_2"
		count, err := getDBCounter(counter)
		Expect(err).NotTo(HaveOccurred())
		Expect(count).To(Equal(0))

		uri, err := url.Parse(testServer.URL)
		Expect(err).NotTo(HaveOccurred())
		uri.Path = fmt.Sprintf(countersBasePath+"/%s", counter)

		req, err := http.NewRequest("POST", uri.String(), nil)
		req.Header.Add("Content-Type", "application/json")
		resp, err := http.DefaultClient.Do(req)
		defer resp.Body.Close()
		Expect(err).ShouldNot(HaveOccurred())
		Expect(resp.StatusCode).Should(Equal(http.StatusOK))

		body, err := ioutil.ReadAll(resp.Body)
		Expect(string(body)).Should(Equal(strconv.Itoa(count)))

		time.Sleep(50 * time.Millisecond) // todo: hack - make this a listen event on incr event finished
		count, err = getDBCounter(counter)
		Expect(err).NotTo(HaveOccurred())
		Expect(count).To(Equal(1))
	})

	It("should retrieve a list of counters", func() {

		counter1 := "api_test_3"
		counter2 := "api_test_4"
		err := incrementDBCounter(counter1)
		Expect(err).NotTo(HaveOccurred())
		err = incrementDBCounter(counter2)
		Expect(err).NotTo(HaveOccurred())
		err = incrementDBCounter(counter2)
		Expect(err).NotTo(HaveOccurred())

		uri, err := url.Parse(testServer.URL)
		Expect(err).NotTo(HaveOccurred())
		uri.Path = countersBasePath

		resp, err := http.Get(uri.String())
		defer resp.Body.Close()
		Expect(err).ShouldNot(HaveOccurred())
		Expect(resp.StatusCode).Should(Equal(http.StatusOK))

		body, err := ioutil.ReadAll(resp.Body)
		Expect(err).ShouldNot(HaveOccurred())
		var counterMap map[string]int
		json.Unmarshal(body, &counterMap)

		Expect(counterMap[counter1]).Should(Equal(1))
		Expect(counterMap[counter2]).Should(Equal(2))
	})
})
<|endoftext|>