Example #1
0
func Search(sourceType string, params *SearchParams, r *http.Request) (map[string]interface{}, error) {
	conn := api.Conn().SearchConnection()

	if sourceType != "usgov.hhs.npi" {
		api.AddMessage(r, "Warning: Use of the dataset, '"+sourceType+"', without an API key is for development-use only. Use of this API without a key may be rate-limited in the future. For hosted, production access, please email '*****@*****.**' for an API key.")
		api.AddMessage(r, "Warning: This query used the experimental dataset, '"+sourceType+"'. To ensure you're notified in case breaking changes need to be made, email [email protected] and ask for an API key.")
	}

	matches := phraseMatches(params.paramSets, r)

	query := map[string]interface{}{
		"from": params.Offset,
		"size": params.Limit,
		"query": map[string]interface{}{
			"bool": map[string]interface{}{
				"must": matches,
			},
		},
	}

	result, err := conn.Search("source", sourceType, nil, query)
	if err != nil {
		switch terr := err.(type) {
		case elastigo.ESError:
			if esTypeExceptionRegex.MatchString(terr.What) {
				return nil, api.NewParamsError("one or more values were of an unexpected type", map[string]string{})
			} else {
				return nil, err
			}
		default:
			return nil, err
		}
	}

	hits := make([]interface{}, len(result.Hits.Hits))
	for i, hit := range result.Hits.Hits {
		var source map[string]interface{}
		json.Unmarshal(*hit.Source, &source)
		hits[i] = source
	}

	cleanedResult := map[string]interface{}{
		"meta": map[string]interface{}{
			"rowCount": result.Hits.Total,
		},
		"result": hits,
	}

	return cleanedResult, nil
}
func ParseSearchParams(params map[string][]string) (*SearchParams, error) {
	// Ensure no unknown parameters
	unknown := make([]string, 0)
	for key, _ := range params {
		if key != "limit" &&
			key != "offset" &&
			key != "callback" &&
			key != "secret" &&
			!keyRegexp.MatchString(key) &&
			!valueRegexp.MatchString(key) &&
			!opRegexp.MatchString(key) {
			unknown = append(unknown, key)
		}
	}
	if len(unknown) > 0 {
		message := strings.Join(unknown, ", ") + " are unknown parameters"

		paramsMap := map[string]string{}
		for _, key := range unknown {
			paramsMap[key] = "is an unknown parameter"
		}

		return nil, api.NewParamsError(message, paramsMap)
	}

	// Ensure params in sets of key/op/value
	paramSets := map[string]*SearchParamSet{}
	for key, values := range params {
		if keyRegexp.MatchString(key) {
			sub := keyRegexp.FindStringSubmatch(key)
			index := sub[1]
			_, ok := paramSets[index]
			if !ok {
				paramSets[index] = &SearchParamSet{}
			}

			paramSets[index].SetKey(values[0])
		}

		if valueRegexp.MatchString(key) {
			sub := valueRegexp.FindStringSubmatch(key)
			index := sub[1]
			_, ok := paramSets[index]
			if !ok {
				paramSets[index] = &SearchParamSet{}
			}

			paramSets[index].SetValues(values)
		}

		if opRegexp.MatchString(key) {
			sub := opRegexp.FindStringSubmatch(key)
			index := sub[1]
			_, ok := paramSets[index]
			if !ok {
				paramSets[index] = &SearchParamSet{}
			}

			paramSets[index].SetOp(values[0])
		}
	}

	for _, set := range paramSets {
		if set.Key == "" {
			return nil, api.NewParamsError("one or more key/op/value sets are missing a key", map[string]string{})
		}

		if set.Values == nil || len(set.Values) == 0 {
			return nil, api.NewParamsError("one or more key/op/value sets are missing a value", map[string]string{})
		}

		hasValue := false
		for _, value := range set.Values {
			if value != "" {
				hasValue = true
				break
			}
		}

		if !hasValue {
			return nil, api.NewParamsError("one or more key/op/value sets are missing a value", map[string]string{})
		}

		if set.Op == "" {
			return nil, api.NewParamsError("one or more key/op/value sets are missing a op", map[string]string{})
		}

		if set.Op != "eq" &&
			set.Op != "fuzzy" &&
			set.Op != "prefix" &&
			set.Op != "gte" &&
			set.Op != "gt" &&
			set.Op != "lte" &&
			set.Op != "lt" {
			return nil, api.NewParamsError("invalid operation "+set.Op, map[string]string{})
		}
	}

	var err error
	// Ensure offset/ limit are positive integers
	var offsetValue uint64
	if offset, ok := params["offset"]; ok {
		offsetValue, err = strconv.ParseUint(offset[0], 0, 64)
		if err != nil {
			return nil, api.NewParamsError("offset must be a positive number",
				map[string]string{"offset": "must be a positive number"})
		}
	}

	var limitValue uint64
	if limit, ok := params["limit"]; ok {
		limitValue, err = strconv.ParseUint(limit[0], 0, 64)
		if err != nil {
			return nil, api.NewParamsError("limit must be a positive number",
				map[string]string{"limit": "must be a positive number"})
		}

		// Ensure limit is less than 100
		if limitValue > 100 {
			return nil, api.NewParamsError("limit must be less than 100",
				map[string]string{"limit": "must less than 100"})
		}
	}

	if limitValue == 0 {
		limitValue = 100
	}

	listSets := make([]*SearchParamSet, len(paramSets))
	i := 0
	for _, v := range paramSets {
		listSets[i] = v
		i += 1
	}

	return &SearchParams{
		offsetValue,
		limitValue,
		listSets,
	}, nil
}
Example #3
0
func Search(sourceType string, params *SearchParams, r *http.Request) (map[string]interface{}, error) {
	conn := api.Conn().SearchConnection()

	apiKey, ok := context.Get(r, "api_key").(string)
	if !ok {
		apiKey = ""
	}

	if apiKey == "" {
		api.AddMessage(r, "Warning: Use of the dataset, '"+sourceType+"', without an API key is for development-use only. Use of this API without a key is rate-limited. For hosted, production access, please email '*****@*****.**' for an API key.")
	}

	matches := phraseMatches(params.paramSets, r)

	var order = "asc"
	if params.Order != "" {
		order = params.Order
	}

	query := map[string]interface{}{
		"from": params.Offset,
		"size": params.Limit,
		"query": map[string]interface{}{
			"bool": map[string]interface{}{
				"must": matches,
			},
		},
	}

	if params.Sort != "" {
		if _, ok := context.Get(r, "api_key").(string); !ok {
			return nil, api.NewParamsError("'sort' is unsupported without a BloomAPI user account", map[string]string{})
		}

		query["sort"] = map[string]interface{}{
			params.Sort: map[string]interface{}{
				"order": order,
			},
		}

		api.AddFeature(r, "sort")
		api.AddMessage(r, experimentalSort)
	}

	result, err := conn.Search(sourceType, "main", nil, query)
	if err != nil {
		switch terr := err.(type) {
		case elastigo.ESError:
			if esTypeExceptionRegex.MatchString(terr.What) {
				return nil, api.NewParamsError("one or more values were of an unexpected type", map[string]string{})
			} else {
				return nil, err
			}
		default:
			return nil, err
		}
	}

	hits := make([]interface{}, len(result.Hits.Hits))
	for i, hit := range result.Hits.Hits {
		var source map[string]interface{}
		json.Unmarshal(*hit.Source, &source)
		hits[i] = source
	}

	cleanedResult := map[string]interface{}{
		"meta": map[string]interface{}{
			"rowCount": result.Hits.Total,
		},
		"result": hits,
	}

	return cleanedResult, nil
}