package glpi import ( "context" "encoding/json" "fmt" "net/http" "net/url" "strconv" "strings" ) // SearchRow is one CI hit from a search: the well-known fields 2 (id) // and 1 (name) plus the raw row keyed by field-id string, exactly as // GLPI returns it under forcedisplay. type SearchRow struct { ID int Name string Fields map[string]any } // SearchCI searches any itemtype's CIs (GET /search/) with a // contains criterion on field 1 (name) and forcedisplay 1,2. func (c *Client) SearchCI(ctx context.Context, itemtype, term string) ([]SearchRow, error) { if itemtype == "" { return nil, fmt.Errorf("%w: itemtype required", ErrValidation) } q := url.Values{} q.Add("forcedisplay[]", "1") q.Add("forcedisplay[]", "2") if term != "" { crit, err := json.Marshal([]map[string]any{{ "field": 1, "searchtype": "contains", "value": term, }}) if err != nil { return nil, fmt.Errorf("%w: cannot encode criteria", ErrMalformedResponse) } q.Set("criteria", string(crit)) } var body struct { Total int `json:"totalcount"` Data []map[string]any `json:"data"` } if err := c.call(ctx, http.MethodGet, "/search/"+esc(itemtype), q, nil, &body); err != nil { return nil, err } rows := make([]SearchRow, 0, len(body.Data)) for _, d := range body.Data { rows = append(rows, SearchRow{ Name: asString(d["1"]), ID: asInt(d["2"]), Fields: d, }) } return rows, nil } // GetItem fetches one CI of any itemtype raw (GET //), // used by `ci show`. The full server object is returned untouched. func (c *Client) GetItem(ctx context.Context, itemtype string, id int) (map[string]any, error) { var obj map[string]any if err := c.call(ctx, http.MethodGet, "/"+esc(itemtype)+"/"+itoa(id), nil, nil, &obj); err != nil { return nil, err } return obj, nil } // esc path-escapes an itemtype. func esc(s string) string { return url.PathEscape(s) } // eqfold is strings.EqualFold kept local for the status table. func eqfold(a, b string) bool { return strings.EqualFold(a, b) } // trimFloat renders a JSON number without a trailing ".0". func trimFloat(f float64) string { return strconv.FormatFloat(f, 'f', -1, 64) } // parseLeadingInt parses an int from a possibly messy search cell. func parseLeadingInt(s string) (int, error) { return strconv.Atoi(strings.TrimSpace(s)) }