1// Copyright 2013 Matthew Baird
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//     http://www.apache.org/licenses/LICENSE-2.0
6// Unless required by applicable law or agreed to in writing, software
7// distributed under the License is distributed on an "AS IS" BASIS,
8// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9// See the License for the specific language governing permissions and
10// limitations under the License.
11
12package elastigo
13
14import (
15	"encoding/json"
16	"fmt"
17	"reflect"
18	"strings"
19)
20
21type Mapping map[string]MappingOptions
22
23type MappingOptions struct {
24	Id               IdOptions                `json:"_id"`
25	Timestamp        TimestampOptions         `json:"_timestamp"`
26	Analyzer         *AnalyzerOptions         `json:"_analyzer,omitempty"`
27	Parent           *ParentOptions           `json:"_parent,omitempty"`
28	Routing          *RoutingOptions          `json:"_routing,omitempty"`
29	Size             *SizeOptions             `json:"_size,omitempty"`
30	Source           *SourceOptions           `json:"_source,omitempty"`
31	TTL              *TTLOptions              `json:"_ttl,omitempty"`
32	Type             *TypeOptions             `json:"_type,omitempty"`
33	Properties       map[string]interface{}   `json:"properties"`
34	DynamicTemplates []map[string]interface{} `json:"dynamic_templates,omitempty"`
35}
36
37type TimestampOptions struct {
38	Enabled bool `json:"enabled"`
39}
40
41type AnalyzerOptions struct {
42	Path  string `json:"path,omitempty"`
43	Index string `json:"index,omitempty"`
44}
45
46type ParentOptions struct {
47	Type string `json:"type"`
48}
49
50type RoutingOptions struct {
51	Required bool   `json:"required,omitempty"`
52	Path     string `json:"path,omitempty"`
53}
54
55type SizeOptions struct {
56	Enabled bool `json:"enabled,omitempty"`
57	Store   bool `json:"store,omitempty"`
58}
59
60type SourceOptions struct {
61	Enabled  bool     `json:"enabled,omitempty"`
62	Includes []string `json:"includes,omitempty"`
63	Excludes []string `json:"excludes,omitempty"`
64}
65
66type TypeOptions struct {
67	Store bool   `json:"store,omitempty"`
68	Index string `json:"index,omitempty"`
69}
70
71type TTLOptions struct {
72	Enabled bool   `json:"enabled"`
73	Default string `json:"default,omitempty"`
74}
75
76type IdOptions struct {
77	Index string `json:"index,omitempty"`
78	Path  string `json:"path,omitempty"`
79}
80
81func (m_ Mapping) Options() MappingOptions {
82	m := map[string]MappingOptions(m_)
83	for _, v := range m {
84		return v
85	}
86	panic(fmt.Errorf("Malformed input: %v", m_))
87}
88
89func MappingForType(typeName string, opts MappingOptions) Mapping {
90	return map[string]MappingOptions{typeName: opts}
91}
92
93func (c *Conn) PutMapping(index string, typeName string, instance interface{}, opt MappingOptions) error {
94	instanceType := reflect.TypeOf(instance)
95	if instanceType.Kind() != reflect.Struct {
96		return fmt.Errorf("instance kind was not struct")
97	}
98
99	if opt.Properties == nil {
100		opt.Properties = make(map[string]interface{})
101	}
102	getProperties(instanceType, opt.Properties)
103	body, err := json.Marshal(MappingForType(typeName, opt))
104	if err != nil {
105		return err
106	}
107	_, err = c.DoCommand("PUT", fmt.Sprintf("/%s/%s/_mapping", index, typeName), nil, string(body))
108	if err != nil {
109		return err
110	}
111
112	return nil
113}
114
115//Same as PutMapping, but takes a []byte for mapping and provides no check of structure
116func (c *Conn) PutMappingFromJSON(index string, typeName string, mapping []byte) error {
117	_, err := c.DoCommand("PUT", fmt.Sprintf("/%s/%s/_mapping", index, typeName), nil, string(mapping))
118	return err
119}
120
121func getProperties(t reflect.Type, prop map[string]interface{}) {
122	n := t.NumField()
123	for i := 0; i < n; i++ {
124		field := t.Field(i)
125
126		name := strings.Split(field.Tag.Get("json"), ",")[0]
127		if name == "-" {
128			continue
129		} else if name == "" {
130			name = field.Name
131		}
132
133		attrMap := make(map[string]interface{})
134		attrs := splitTag(field.Tag.Get("elastic"))
135		for _, attr := range attrs {
136			keyvalue := strings.Split(attr, ":")
137			attrMap[keyvalue[0]] = keyvalue[1]
138		}
139
140		if len(attrMap) == 0 || attrMap["type"] == "nested" {
141
142			// We are looking for tags on any inner struct, independently of
143			// whether the field is a struct, a pointer to struct, or a slice of structs
144			targetType := field.Type
145			if targetType.Kind() == reflect.Ptr ||
146				targetType.Kind() == reflect.Slice {
147				targetType = field.Type.Elem()
148			}
149			if targetType.Kind() == reflect.Struct {
150				if field.Anonymous {
151					getProperties(targetType, prop)
152				} else {
153					innerStructProp := make(map[string]interface{})
154					getProperties(targetType, innerStructProp)
155					attrMap["properties"] = innerStructProp
156				}
157			}
158		}
159		if len(attrMap) != 0 {
160			prop[name] = attrMap
161		}
162	}
163}
164
165func splitTag(tag string) []string {
166	tag = strings.Trim(tag, " ")
167	if tag == "" {
168		return []string{}
169	} else {
170		return strings.Split(tag, ",")
171	}
172}
173