// Copyright 2015 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

/*
Package search provides a client for App Engine's search service.


Basic Operations

Indexes contain documents. Each index is identified by its name: a
human-readable ASCII string.

Within an index, documents are associated with an ID, which is also
a human-readable ASCII string. A document's contents are a mapping from
case-sensitive field names to values. Valid types for field values are:
  - string,
  - search.Atom,
  - search.HTML,
  - time.Time (stored with millisecond precision),
  - float64 (value between -2,147,483,647 and 2,147,483,647 inclusive),
  - appengine.GeoPoint.

The Get and Put methods on an Index load and save a document.
A document's contents are typically represented by a struct pointer.

Example code:

	type Doc struct {
		Author   string
		Comment  string
		Creation time.Time
	}

	index, err := search.Open("comments")
	if err != nil {
		return err
	}
	newID, err := index.Put(ctx, "", &Doc{
		Author:   "gopher",
		Comment:  "the truth of the matter",
		Creation: time.Now(),
	})
	if err != nil {
		return err
	}

A single document can be retrieved by its ID. Pass a destination struct
to Get to hold the resulting document.

	var doc Doc
	err := index.Get(ctx, id, &doc)
	if err != nil {
		return err
	}


Search and Listing Documents

Indexes have two methods for retrieving multiple documents at once: Search and
List.

Searching an index for a query will result in an iterator. As with an iterator
from package datastore, pass a destination struct to Next to decode the next
result. Next will return Done when the iterator is exhausted.

	for t := index.Search(ctx, "Comment:truth", nil); ; {
		var doc Doc
		id, err := t.Next(&doc)
		if err == search.Done {
			break
		}
		if err != nil {
			return err
		}
		fmt.Fprintf(w, "%s -> %#v\n", id, doc)
	}

Search takes a string query to determine which documents to return. The query
can be simple, such as a single word to match, or complex. The query
language is described at
https://cloud.google.com/appengine/docs/standard/go/search/query_strings

Search also takes an optional SearchOptions struct which gives much more
control over how results are calculated and returned.

Call List to iterate over all documents in an index.

	for t := index.List(ctx, nil); ; {
		var doc Doc
		id, err := t.Next(&doc)
		if err == search.Done {
			break
		}
		if err != nil {
			return err
		}
		fmt.Fprintf(w, "%s -> %#v\n", id, doc)
	}


Fields and Facets

A document's contents can be represented by a variety of types. These are
typically struct pointers, but they can also be represented by any type
implementing the FieldLoadSaver interface. The FieldLoadSaver allows metadata
to be set for the document with the DocumentMetadata type. Struct pointers are
more strongly typed and are easier to use; FieldLoadSavers are more flexible.

A document's contents can be expressed in two ways: fields and facets.

Fields are the most common way of providing content for documents. Fields can
store data in multiple types and can be matched in searches using query
strings.

Facets provide a way to attach categorical information to a document. The only
valid types for facets are search.Atom and float64. Facets allow search
results to contain summaries of the categories matched in a search, and to
restrict searches to only match against specific categories.

By default, for struct pointers, all of the struct fields are used as document
fields, and the field name used is the same as on the struct (and hence must
start with an upper case letter). Struct fields may have a
`search:"name,options"` tag. The name must start with a letter and be
composed only of word characters. A "-" tag name means that the field will be
ignored.  If options is "facet" then the struct field will be used as a
document facet. If options is "" then the comma may be omitted. There are no
other recognized options.

Example code:

	// A and B are renamed to a and b.
	// A, C and I are facets.
	// D's tag is equivalent to having no tag at all (E).
	// F and G are ignored entirely by the search package.
	// I has tag information for both the search and json packages.
	type TaggedStruct struct {
		A float64 `search:"a,facet"`
		B float64 `search:"b"`
		C float64 `search:",facet"`
		D float64 `search:""`
		E float64
		F float64 `search:"-"`
		G float64 `search:"-,facet"`
		I float64 `search:",facet" json:"i"`
	}


The FieldLoadSaver Interface

A document's contents can also be represented by any type that implements the
FieldLoadSaver interface. This type may be a struct pointer, but it
does not have to be. The search package will call Load when loading the
document's contents, and Save when saving them. In addition to a slice of
Fields, the Load and Save methods also use the DocumentMetadata type to
provide additional information about a document (such as its Rank, or set of
Facets). Possible uses for this interface include deriving non-stored fields,
verifying fields or setting specific languages for string and HTML fields.

Example code:

	type CustomFieldsExample struct {
		// Item's title and which language it is in.
		Title string
		Lang  string
		// Mass, in grams.
		Mass int
	}

	func (x *CustomFieldsExample) Load(fields []search.Field, meta *search.DocumentMetadata) error {
		// Load the title field, failing if any other field is found.
		for _, f := range fields {
			if f.Name != "title" {
				return fmt.Errorf("unknown field %q", f.Name)
			}
			s, ok := f.Value.(string)
			if !ok {
				return fmt.Errorf("unsupported type %T for field %q", f.Value, f.Name)
			}
			x.Title = s
			x.Lang = f.Language
		}
		// Load the mass facet, failing if any other facet is found.
		for _, f := range meta.Facets {
			if f.Name != "mass" {
				return fmt.Errorf("unknown facet %q", f.Name)
			}
			m, ok := f.Value.(float64)
			if !ok {
				return fmt.Errorf("unsupported type %T for facet %q", f.Value, f.Name)
			}
			x.Mass = int(m)
		}
		return nil
	}

	func (x *CustomFieldsExample) Save() ([]search.Field, *search.DocumentMetadata, error) {
		fields := []search.Field{
			{Name: "title", Value: x.Title, Language: x.Lang},
		}
		meta := &search.DocumentMetadata{
			Facets: {
				{Name: "mass", Value: float64(x.Mass)},
			},
		}
		return fields, meta, nil
	}
*/
package search
