gopkg.in/rethinkdb/rethinkdb-go.v6@v6.2.2/pseudotypes.go (about)

     1  package rethinkdb
     2  
     3  import (
     4  	"encoding/base64"
     5  	"math"
     6  	"strconv"
     7  	"time"
     8  
     9  	"gopkg.in/rethinkdb/rethinkdb-go.v6/types"
    10  
    11  	"fmt"
    12  )
    13  
    14  func convertPseudotype(obj map[string]interface{}, opts map[string]interface{}) (interface{}, error) {
    15  	if reqlType, ok := obj["$reql_type$"]; ok {
    16  		if reqlType == "TIME" {
    17  			// load timeFormat, set to native if the option was not set
    18  			timeFormat := "native"
    19  			if opt, ok := opts["time_format"]; ok {
    20  				if sopt, ok := opt.(string); ok {
    21  					timeFormat = sopt
    22  				} else {
    23  					return nil, fmt.Errorf("Invalid time_format run option \"%s\".", opt)
    24  				}
    25  			}
    26  
    27  			if timeFormat == "native" {
    28  				return reqlTimeToNativeTime(obj["epoch_time"].(float64), obj["timezone"].(string))
    29  			} else if timeFormat == "raw" {
    30  				return obj, nil
    31  			} else {
    32  				return nil, fmt.Errorf("Unknown time_format run option \"%s\".", reqlType)
    33  			}
    34  		} else if reqlType == "GROUPED_DATA" {
    35  			// load groupFormat, set to native if the option was not set
    36  			groupFormat := "native"
    37  			if opt, ok := opts["group_format"]; ok {
    38  				if sopt, ok := opt.(string); ok {
    39  					groupFormat = sopt
    40  				} else {
    41  					return nil, fmt.Errorf("Invalid group_format run option \"%s\".", opt)
    42  				}
    43  			}
    44  
    45  			if groupFormat == "native" || groupFormat == "slice" {
    46  				return reqlGroupedDataToSlice(obj)
    47  			} else if groupFormat == "map" {
    48  				return reqlGroupedDataToMap(obj)
    49  			} else if groupFormat == "raw" {
    50  				return obj, nil
    51  			} else {
    52  				return nil, fmt.Errorf("Unknown group_format run option \"%s\".", reqlType)
    53  			}
    54  		} else if reqlType == "BINARY" {
    55  			binaryFormat := "native"
    56  			if opt, ok := opts["binary_format"]; ok {
    57  				if sopt, ok := opt.(string); ok {
    58  					binaryFormat = sopt
    59  				} else {
    60  					return nil, fmt.Errorf("Invalid binary_format run option \"%s\".", opt)
    61  				}
    62  			}
    63  
    64  			if binaryFormat == "native" {
    65  				return reqlBinaryToNativeBytes(obj)
    66  			} else if binaryFormat == "raw" {
    67  				return obj, nil
    68  			} else {
    69  				return nil, fmt.Errorf("Unknown binary_format run option \"%s\".", reqlType)
    70  			}
    71  		} else if reqlType == "GEOMETRY" {
    72  			geometryFormat := "native"
    73  			if opt, ok := opts["geometry_format"]; ok {
    74  				if sopt, ok := opt.(string); ok {
    75  					geometryFormat = sopt
    76  				} else {
    77  					return nil, fmt.Errorf("Invalid geometry_format run option \"%s\".", opt)
    78  				}
    79  			}
    80  
    81  			if geometryFormat == "native" {
    82  				return reqlGeometryToNativeGeometry(obj)
    83  			} else if geometryFormat == "raw" {
    84  				return obj, nil
    85  			} else {
    86  				return nil, fmt.Errorf("Unknown geometry_format run option \"%s\".", reqlType)
    87  			}
    88  		} else {
    89  			return obj, nil
    90  		}
    91  	}
    92  
    93  	return obj, nil
    94  }
    95  
    96  func recursivelyConvertPseudotype(obj interface{}, opts map[string]interface{}) (interface{}, error) {
    97  	var err error
    98  
    99  	switch obj := obj.(type) {
   100  	case []interface{}:
   101  		for key, val := range obj {
   102  			obj[key], err = recursivelyConvertPseudotype(val, opts)
   103  			if err != nil {
   104  				return nil, err
   105  			}
   106  		}
   107  	case map[string]interface{}:
   108  		for key, val := range obj {
   109  			obj[key], err = recursivelyConvertPseudotype(val, opts)
   110  			if err != nil {
   111  				return nil, err
   112  			}
   113  		}
   114  
   115  		pobj, err := convertPseudotype(obj, opts)
   116  		if err != nil {
   117  			return nil, err
   118  		}
   119  
   120  		return pobj, nil
   121  	}
   122  
   123  	return obj, nil
   124  }
   125  
   126  // Pseudo-type helper functions
   127  
   128  func reqlTimeToNativeTime(timestamp float64, timezone string) (time.Time, error) {
   129  	sec, ms := math.Modf(timestamp)
   130  
   131  	// Convert to native time rounding to milliseconds
   132  	t := time.Unix(int64(sec), int64(math.Floor(ms*1000+0.5))*1000*1000)
   133  
   134  	// Caclulate the timezone
   135  	if timezone != "" {
   136  		hours, err := strconv.Atoi(timezone[1:3])
   137  		if err != nil {
   138  			return time.Time{}, err
   139  		}
   140  		minutes, err := strconv.Atoi(timezone[4:6])
   141  		if err != nil {
   142  			return time.Time{}, err
   143  		}
   144  		tzOffset := ((hours * 60) + minutes) * 60
   145  		if timezone[:1] == "-" {
   146  			tzOffset = 0 - tzOffset
   147  		}
   148  
   149  		t = t.In(time.FixedZone(timezone, tzOffset))
   150  	}
   151  
   152  	return t, nil
   153  }
   154  
   155  func reqlGroupedDataToSlice(obj map[string]interface{}) (interface{}, error) {
   156  	if data, ok := obj["data"]; ok {
   157  		ret := []interface{}{}
   158  		for _, v := range data.([]interface{}) {
   159  			v := v.([]interface{})
   160  			ret = append(ret, map[string]interface{}{
   161  				"group":     v[0],
   162  				"reduction": v[1],
   163  			})
   164  		}
   165  		return ret, nil
   166  	}
   167  	return nil, fmt.Errorf("pseudo-type GROUPED_DATA object %v does not have the expected field \"data\"", obj)
   168  }
   169  
   170  func reqlGroupedDataToMap(obj map[string]interface{}) (interface{}, error) {
   171  	if data, ok := obj["data"]; ok {
   172  		ret := map[interface{}]interface{}{}
   173  		for _, v := range data.([]interface{}) {
   174  			v := v.([]interface{})
   175  			ret[v[0]] = v[1]
   176  		}
   177  		return ret, nil
   178  	}
   179  	return nil, fmt.Errorf("pseudo-type GROUPED_DATA object %v does not have the expected field \"data\"", obj)
   180  }
   181  
   182  func reqlBinaryToNativeBytes(obj map[string]interface{}) (interface{}, error) {
   183  	if data, ok := obj["data"]; ok {
   184  		if data, ok := data.(string); ok {
   185  			b, err := base64.StdEncoding.DecodeString(data)
   186  			if err != nil {
   187  				return nil, fmt.Errorf("error decoding pseudo-type BINARY object %v", obj)
   188  			}
   189  
   190  			return b, nil
   191  		}
   192  		return nil, fmt.Errorf("pseudo-type BINARY object %v field \"data\" is not valid", obj)
   193  	}
   194  	return nil, fmt.Errorf("pseudo-type BINARY object %v does not have the expected field \"data\"", obj)
   195  }
   196  
   197  func reqlGeometryToNativeGeometry(obj map[string]interface{}) (interface{}, error) {
   198  	if typ, ok := obj["type"]; !ok {
   199  		return nil, fmt.Errorf("pseudo-type GEOMETRY object %v does not have the expected field \"type\"", obj)
   200  	} else if typ, ok := typ.(string); !ok {
   201  		return nil, fmt.Errorf("pseudo-type GEOMETRY object %v field \"type\" is not valid", obj)
   202  	} else if coords, ok := obj["coordinates"]; !ok {
   203  		return nil, fmt.Errorf("pseudo-type GEOMETRY object %v does not have the expected field \"coordinates\"", obj)
   204  	} else if typ == "Point" {
   205  		point, err := types.UnmarshalPoint(coords)
   206  		if err != nil {
   207  			return nil, err
   208  		}
   209  
   210  		return types.Geometry{
   211  			Type:  "Point",
   212  			Point: point,
   213  		}, nil
   214  	} else if typ == "LineString" {
   215  		line, err := types.UnmarshalLineString(coords)
   216  		if err != nil {
   217  			return nil, err
   218  		}
   219  		return types.Geometry{
   220  			Type: "LineString",
   221  			Line: line,
   222  		}, nil
   223  	} else if typ == "Polygon" {
   224  		lines, err := types.UnmarshalPolygon(coords)
   225  		if err != nil {
   226  			return nil, err
   227  		}
   228  		return types.Geometry{
   229  			Type:  "Polygon",
   230  			Lines: lines,
   231  		}, nil
   232  	} else {
   233  		return nil, fmt.Errorf("pseudo-type GEOMETRY object %v field has unknown type %s", obj, typ)
   234  	}
   235  }