@@ -90,6 +90,32 @@ func (c *WrappedClient) Store(s string) error {
9090 return c .Client .Store (c .collection , f )
9191}
9292
93+ // apiResponse is the standardized LocalRecall API response wrapper (since 3f73ff3a).
94+ type apiResponse struct {
95+ Success bool `json:"success"`
96+ Message string `json:"message,omitempty"`
97+ Data json.RawMessage `json:"data,omitempty"`
98+ Error * apiError `json:"error,omitempty"`
99+ }
100+
101+ type apiError struct {
102+ Code string `json:"code"`
103+ Message string `json:"message"`
104+ Details string `json:"details,omitempty"`
105+ }
106+
107+ // parseAPIError reads the response body and returns an error from the API response or a generic message.
108+ func parseAPIError (resp * http.Response , body []byte , fallback string ) error {
109+ var wrap apiResponse
110+ if err := json .Unmarshal (body , & wrap ); err == nil && wrap .Error != nil {
111+ if wrap .Error .Details != "" {
112+ return fmt .Errorf ("%s: %s" , wrap .Error .Message , wrap .Error .Details )
113+ }
114+ return errors .New (wrap .Error .Message )
115+ }
116+ return fmt .Errorf ("%s: %s" , fallback , string (body ))
117+ }
118+
93119// Result represents a single result from a query.
94120type Result struct {
95121 ID string
@@ -153,7 +179,8 @@ func (c *Client) CreateCollection(name string) error {
153179 defer resp .Body .Close ()
154180
155181 if resp .StatusCode != http .StatusCreated {
156- return errors .New ("failed to create collection" )
182+ body , _ := io .ReadAll (resp .Body )
183+ return parseAPIError (resp , body , "failed to create collection" )
157184 }
158185
159186 return nil
@@ -176,17 +203,30 @@ func (c *Client) ListCollections() ([]string, error) {
176203 }
177204 defer resp .Body .Close ()
178205
206+ body , err := io .ReadAll (resp .Body )
207+ if err != nil {
208+ return nil , err
209+ }
210+
179211 if resp .StatusCode != http .StatusOK {
180- return nil , errors . New ( "failed to list collections" )
212+ return nil , parseAPIError ( resp , body , "failed to list collections" )
181213 }
182214
183- var collections []string
184- err = json .NewDecoder (resp .Body ).Decode (& collections )
185- if err != nil {
186- return nil , err
215+ var wrap apiResponse
216+ if err := json .Unmarshal (body , & wrap ); err != nil || ! wrap .Success {
217+ if wrap .Error != nil {
218+ return nil , errors .New (wrap .Error .Message )
219+ }
220+ return nil , fmt .Errorf ("invalid response: %w" , err )
187221 }
188222
189- return collections , nil
223+ var data struct {
224+ Collections []string `json:"collections"`
225+ }
226+ if err := json .Unmarshal (wrap .Data , & data ); err != nil {
227+ return nil , err
228+ }
229+ return data .Collections , nil
190230}
191231
192232// ListEntries lists all entries in a collection
@@ -206,17 +246,30 @@ func (c *Client) ListEntries(collection string) ([]string, error) {
206246 }
207247 defer resp .Body .Close ()
208248
249+ body , err := io .ReadAll (resp .Body )
250+ if err != nil {
251+ return nil , err
252+ }
253+
209254 if resp .StatusCode != http .StatusOK {
210- return nil , errors . New ( "failed to list entries" )
255+ return nil , parseAPIError ( resp , body , "failed to list entries" )
211256 }
212257
213- var entries []string
214- err = json .NewDecoder (resp .Body ).Decode (& entries )
215- if err != nil {
216- return nil , err
258+ var wrap apiResponse
259+ if err := json .Unmarshal (body , & wrap ); err != nil || ! wrap .Success {
260+ if wrap .Error != nil {
261+ return nil , errors .New (wrap .Error .Message )
262+ }
263+ return nil , fmt .Errorf ("invalid response: %w" , err )
217264 }
218265
219- return entries , nil
266+ var data struct {
267+ Entries []string `json:"entries"`
268+ }
269+ if err := json .Unmarshal (wrap .Data , & data ); err != nil {
270+ return nil , err
271+ }
272+ return data .Entries , nil
220273}
221274
222275// DeleteEntry deletes an entry in a collection
@@ -246,19 +299,30 @@ func (c *Client) DeleteEntry(collection, entry string) ([]string, error) {
246299 }
247300 defer resp .Body .Close ()
248301
302+ body , err := io .ReadAll (resp .Body )
303+ if err != nil {
304+ return nil , err
305+ }
306+
249307 if resp .StatusCode != http .StatusOK {
250- bodyResult := new (bytes.Buffer )
251- bodyResult .ReadFrom (resp .Body )
252- return nil , errors .New ("failed to delete entry: " + bodyResult .String ())
308+ return nil , parseAPIError (resp , body , "failed to delete entry" )
253309 }
254310
255- var results []string
256- err = json .NewDecoder (resp .Body ).Decode (& results )
257- if err != nil {
258- return nil , err
311+ var wrap apiResponse
312+ if err := json .Unmarshal (body , & wrap ); err != nil || ! wrap .Success {
313+ if wrap .Error != nil {
314+ return nil , errors .New (wrap .Error .Message )
315+ }
316+ return nil , fmt .Errorf ("invalid response: %w" , err )
259317 }
260318
261- return results , nil
319+ var data struct {
320+ RemainingEntries []string `json:"remaining_entries"`
321+ }
322+ if err := json .Unmarshal (wrap .Data , & data ); err != nil {
323+ return nil , err
324+ }
325+ return data .RemainingEntries , nil
262326}
263327
264328// Search searches a collection
@@ -289,17 +353,30 @@ func (c *Client) Search(collection, query string, maxResults int) ([]Result, err
289353 }
290354 defer resp .Body .Close ()
291355
356+ body , err := io .ReadAll (resp .Body )
357+ if err != nil {
358+ return nil , err
359+ }
360+
292361 if resp .StatusCode != http .StatusOK {
293- return nil , errors . New ( "failed to search collection" )
362+ return nil , parseAPIError ( resp , body , "failed to search collection" )
294363 }
295364
296- var results []Result
297- err = json .NewDecoder (resp .Body ).Decode (& results )
298- if err != nil {
299- return nil , err
365+ var wrap apiResponse
366+ if err := json .Unmarshal (body , & wrap ); err != nil || ! wrap .Success {
367+ if wrap .Error != nil {
368+ return nil , errors .New (wrap .Error .Message )
369+ }
370+ return nil , fmt .Errorf ("invalid response: %w" , err )
300371 }
301372
302- return results , nil
373+ var data struct {
374+ Results []Result `json:"results"`
375+ }
376+ if err := json .Unmarshal (wrap .Data , & data ); err != nil {
377+ return nil , err
378+ }
379+ return data .Results , nil
303380}
304381
305382// Reset resets a collection
@@ -320,9 +397,8 @@ func (c *Client) Reset(collection string) error {
320397 defer resp .Body .Close ()
321398
322399 if resp .StatusCode != http .StatusOK {
323- b := new (bytes.Buffer )
324- b .ReadFrom (resp .Body )
325- return errors .New ("failed to reset collection: " + b .String ())
400+ body , _ := io .ReadAll (resp .Body )
401+ return parseAPIError (resp , body , "failed to reset collection" )
326402 }
327403
328404 return nil
@@ -371,20 +447,8 @@ func (c *Client) Store(collection, filePath string) error {
371447 defer resp .Body .Close ()
372448
373449 if resp .StatusCode != http .StatusOK {
374- b := new (bytes.Buffer )
375- b .ReadFrom (resp .Body )
376-
377- type response struct {
378- Error string `json:"error"`
379- }
380-
381- var r response
382- err = json .Unmarshal (b .Bytes (), & r )
383- if err == nil {
384- return errors .New ("failed to upload file: " + r .Error )
385- }
386-
387- return errors .New ("failed to upload file" )
450+ body , _ := io .ReadAll (resp .Body )
451+ return parseAPIError (resp , body , "failed to upload file" )
388452 }
389453
390454 return nil
0 commit comments