Skip to content

Commit

Permalink
Initial files.
Browse files Browse the repository at this point in the history
  • Loading branch information
moraes committed Oct 3, 2012
1 parent 1b78401 commit be94eef
Show file tree
Hide file tree
Showing 6 changed files with 1,169 additions and 0 deletions.
27 changes: 27 additions & 0 deletions LICENSE
@@ -0,0 +1,27 @@
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
195 changes: 195 additions & 0 deletions cache.go
@@ -0,0 +1,195 @@
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package schema

import (
"errors"
"reflect"
"strconv"
"strings"
"sync"
)

var invalidPath = errors.New("schema: invalid path")

// newCache returns a new cache.
func newCache() *cache {
c := cache{
m: make(map[reflect.Type]*structInfo),
conv: make(map[reflect.Type]Converter),
}
for k, v := range converters {
c.conv[k] = v
}
return &c
}

// cache caches meta-data about a struct.
type cache struct {
l sync.Mutex
m map[reflect.Type]*structInfo
conv map[reflect.Type]Converter
}

// parsePath parses a path in dotted notation verifying that it is a valid
// path to a struct field.
//
// It returns "path parts" which contain indices to fields to be used by
// reflect.Value.FieldByIndex(). Multiple parts are required for slices of
// structs.
func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) {
var struc *structInfo
var field *fieldInfo
var index64 int64
var err error
parts := make([]pathPart, 0)
path := make([]int, 0)
keys := strings.Split(p, ".")
for i := 0; i < len(keys); i++ {
if struc = c.get(t); struc == nil {
return nil, invalidPath
}
if field = struc.get(keys[i]); field == nil {
return nil, invalidPath
}
// Valid field. Append index.
path = append(path, field.idx)
if field.ss {
// Parse a special case: slices of structs.
// i+1 must be the slice index, and i+2 must exist.
i++
if i+1 >= len(keys) {
return nil, invalidPath
}
if index64, err = strconv.ParseInt(keys[i], 10, 0); err != nil {
return nil, invalidPath
}
parts = append(parts, pathPart{
path: path,
field: field,
index: int(index64),
})
path = make([]int, 0)

// Get the next struct type, dropping ptrs.
if field.typ.Kind() == reflect.Ptr {
t = field.typ.Elem()
} else {
t = field.typ
}
if t.Kind() == reflect.Slice {
t = t.Elem()
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
}
} else if field.typ.Kind() == reflect.Struct {
t = field.typ
} else if field.typ.Kind() == reflect.Ptr && field.typ.Elem().Kind() == reflect.Struct {
t = field.typ.Elem()
}
}
// Add the remaining.
parts = append(parts, pathPart{
path: path,
field: field,
index: -1,
})
return parts, nil
}

// get returns a cached structInfo, creating it if necessary.
func (c *cache) get(t reflect.Type) *structInfo {
c.l.Lock()
info := c.m[t]
c.l.Unlock()
if info == nil {
info = c.create(t)
c.l.Lock()
c.m[t] = info
c.l.Unlock()
}
return info
}

// creat creates a structInfo with meta-data about a struct.
func (c *cache) create(t reflect.Type) *structInfo {
info := &structInfo{fields: make(map[string]*fieldInfo)}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
alias := fieldAlias(field)
if alias == "-" {
// Ignore this field.
continue
}
// Check if the type is supported and don't cache it if not.
// First let's get the basic type.
isSlice, isStruct := false, false
ft := field.Type
if ft.Kind() == reflect.Ptr {
ft = ft.Elem()
}
if isSlice = ft.Kind() == reflect.Slice; isSlice {
ft = ft.Elem()
if ft.Kind() == reflect.Ptr {
ft = ft.Elem()
}
}
if isStruct = ft.Kind() == reflect.Struct; !isStruct {
if conv := c.conv[ft]; conv == nil {
// Type is not supported.
continue
}
}
info.fields[alias] = &fieldInfo{
idx: i,
typ: field.Type,
ss: isSlice && isStruct,
}
}
return info
}

// ----------------------------------------------------------------------------

type structInfo struct {
fields map[string]*fieldInfo
}

func (i *structInfo) get(alias string) *fieldInfo {
return i.fields[alias]
}

type fieldInfo struct {
typ reflect.Type
idx int // field index in the struct.
ss bool // true if this is a slice of structs.
}

type pathPart struct {
field *fieldInfo
path []int // path to the field: walks structs using field indices.
index int // struct index in slices of structs.
}

// ----------------------------------------------------------------------------

// fieldAlias parses a field tag to get a field alias.
func fieldAlias(field reflect.StructField) string {
var alias string
if tag := field.Tag.Get("schema"); tag != "" {
// For now tags only support the name but let's folow the
// comma convention from encoding/json and others.
if idx := strings.Index(tag, ","); idx == -1 {
alias = tag
} else {
alias = tag[:idx]
}
}
if alias == "" {
alias = field.Name
}
return alias
}
143 changes: 143 additions & 0 deletions converter.go
@@ -0,0 +1,143 @@
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package schema

import (
"reflect"
"strconv"
)

type Converter func(string) reflect.Value

var (
invalidValue = reflect.Value{}
boolType = reflect.TypeOf(false)
float32Type = reflect.TypeOf(float32(0))
float64Type = reflect.TypeOf(float64(0))
intType = reflect.TypeOf(int(0))
int8Type = reflect.TypeOf(int8(0))
int16Type = reflect.TypeOf(int16(0))
int32Type = reflect.TypeOf(int32(0))
int64Type = reflect.TypeOf(int64(0))
stringType = reflect.TypeOf("")
uintType = reflect.TypeOf(uint(0))
uint8Type = reflect.TypeOf(uint8(0))
uint16Type = reflect.TypeOf(uint16(0))
uint32Type = reflect.TypeOf(uint32(0))
uint64Type = reflect.TypeOf(uint64(0))
)

// Default converters for basic types.
var converters = map[reflect.Type]Converter{
boolType: convertBool,
float32Type: convertFloat32,
float64Type: convertFloat64,
intType: convertInt,
int8Type: convertInt8,
int16Type: convertInt16,
int32Type: convertInt32,
int64Type: convertInt64,
stringType: convertString,
uintType: convertUint,
uint8Type: convertUint8,
uint16Type: convertUint16,
uint32Type: convertUint32,
uint64Type: convertUint64,
}

func convertBool(value string) reflect.Value {
if v, err := strconv.ParseBool(value); err == nil {
return reflect.ValueOf(v)
}
return invalidValue
}

func convertFloat32(value string) reflect.Value {
if v, err := strconv.ParseFloat(value, 32); err == nil {
return reflect.ValueOf(float32(v))
}
return invalidValue
}

func convertFloat64(value string) reflect.Value {
if v, err := strconv.ParseFloat(value, 64); err == nil {
return reflect.ValueOf(v)
}
return invalidValue
}

func convertInt(value string) reflect.Value {
if v, err := strconv.ParseInt(value, 10, 0); err == nil {
return reflect.ValueOf(int(v))
}
return invalidValue
}

func convertInt8(value string) reflect.Value {
if v, err := strconv.ParseInt(value, 10, 8); err == nil {
return reflect.ValueOf(int8(v))
}
return invalidValue
}

func convertInt16(value string) reflect.Value {
if v, err := strconv.ParseInt(value, 10, 16); err == nil {
return reflect.ValueOf(int16(v))
}
return invalidValue
}

func convertInt32(value string) reflect.Value {
if v, err := strconv.ParseInt(value, 10, 32); err == nil {
return reflect.ValueOf(int32(v))
}
return invalidValue
}

func convertInt64(value string) reflect.Value {
if v, err := strconv.ParseInt(value, 10, 64); err == nil {
return reflect.ValueOf(v)
}
return invalidValue
}

func convertString(value string) reflect.Value {
return reflect.ValueOf(value)
}

func convertUint(value string) reflect.Value {
if v, err := strconv.ParseUint(value, 10, 0); err == nil {
return reflect.ValueOf(uint(v))
}
return invalidValue
}

func convertUint8(value string) reflect.Value {
if v, err := strconv.ParseUint(value, 10, 8); err == nil {
return reflect.ValueOf(uint8(v))
}
return invalidValue
}

func convertUint16(value string) reflect.Value {
if v, err := strconv.ParseUint(value, 10, 16); err == nil {
return reflect.ValueOf(uint16(v))
}
return invalidValue
}

func convertUint32(value string) reflect.Value {
if v, err := strconv.ParseUint(value, 10, 32); err == nil {
return reflect.ValueOf(uint32(v))
}
return invalidValue
}

func convertUint64(value string) reflect.Value {
if v, err := strconv.ParseUint(value, 10, 64); err == nil {
return reflect.ValueOf(v)
}
return invalidValue
}

0 comments on commit be94eef

Please sign in to comment.