package rules import ( "encoding/json" "errors" "fmt" "github.com/olivere/elastic/v7" ) // Predicate is a rule predicate type Predicate struct { Field string `json:"field"` Operands []interface{} `json:"operands"` } // NewPredicate Create new QueryString predicate // TODO: Need to think about fields agrument, to search only in limited set of fields. See https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-multi-field func NewPredicate(field string, operands ...interface{}) Predicate { return Predicate{Field: field, Operands: operands} } // Create query for Query String func (predicate Predicate) sourceQueryString() (interface{}, error) { if len(predicate.Operands) != 1 { return nil, fmt.Errorf("bad operands count, expect %v, got %v", 1, len(predicate.Operands)) } value, ok := predicate.Operands[0].(string) if !ok { return nil, fmt.Errorf("bad operand type, need string, got %T", predicate.Operands[0]) } if predicate.Field != "" { return elastic.NewQueryStringQuery(value).DefaultField(predicate.Field).Source() } return elastic.NewQueryStringQuery(value).Source() } // Source returns the JSON-serializable query request. func (predicate Predicate) Source() (interface{}, error) { return predicate.sourceQueryString() } // Parse predicate recursive func getPredicate(p interface{}) (*Predicate, error) { result := new(Predicate) var value map[string]interface{} value, ok := p.(map[string]interface{}) if !ok { return nil, fmt.Errorf("bad interface, need map[string]interface{}, got %T", p) } // Field if _, ok := value["field"]; ok { if v, ok := value["field"].(string); ok { result.Field = v } else { return nil, fmt.Errorf("bad preticat field: %T, %v", value["field"], value["field"]) } } // Operands var operands interface{} if cur, ok := value["operands"]; ok { operands = cur } else if cur, ok := value["value"]; ok { operands = cur } else { return nil, errors.New("no operands found") } // Now, processing operands switch v := operands.(type) { case string: result.Operands = make([]interface{}, 1) result.Operands[0] = v case []interface{}: result.Operands = make([]interface{}, len(v)) for i, cur := range v { switch curOperand := cur.(type) { case string: result.Operands[i] = curOperand case interface{}: op, err := getPredicate(curOperand) if err != nil { return nil, fmt.Errorf("can't parse predicat's operand #%v -> %v (%v)", i, curOperand, err) } result.Operands[i] = op default: return nil, fmt.Errorf("can't parse operand #%v with type %T and value %v", i, v, v) } } default: return nil, fmt.Errorf("can't parse operands with type %T and value %v", v, v) } return result, nil } func (predicate *Predicate) UnmarshalJSON(b []byte) error { var alias interface{} err := json.Unmarshal(b, &alias) if err != nil { return err } res, err := getPredicate(alias) if err != nil { return err } predicate.Field = res.Field predicate.Operands = res.Operands return nil }