go-mastodon icon indicating copy to clipboard operation
go-mastodon copied to clipboard

Feature: Save API JSON (briefly) if requested by client

Open rjp opened this issue 1 year ago • 5 comments

I'm using this library for archival purposes (eg. saving my favourites to SQLite) and I find it useful to keep the raw API response around after the API call since it often contains extensions, fields, etc., that aren't necessarily parsed out and returned in library objects.

e.g. from my Akkoma instance, status responses have information about quote-boosting which doesn't exist in the mastodon.Status struct. Also various pleroma and akkoma extensions for the status text and instance information.

Since almost no-one is going to need this, it's gated behind Client.SaveJSON being true (and limited to 100MB of JSON to be safe.) By default, the library will behave exactly the same as before.

(I admit it's not a great solution but I don't think there is one for a situation like this without extending every API method to optionally return the JSON or similar.)

rjp avatar May 23 '23 08:05 rjp

I prefer to do with TeeReader.

type Client struct {
    ...
    JsonWriter io.Writer
}
if c.JsonWriter != nil {
    return json.NewDecoder(io.TeeReader(resp.Body, c.JsonWriter)).Decode(&res)
} else {
    return json.NewDecoder(resp.Body).Decode(&res)
}
client := mastodon.NewClient()
var buf bytes.Buffer
client.JsonWriter = &buf
// something to do with a client

mattn avatar May 23 '23 10:05 mattn

Neat solution! It does require the caller to remember to issue a buf.Reset() before every API call they want the JSON from though -- a definite footgun (I know because I just encountered it) and a bit untidy. Can't do the Reset() from the library though because it's not available on io.Writer.

A localised WriteResetter interface fixes things and keeps the client code tidy.

type WriteResetter interface {
    io.Writer
    Reset()
}

type Client struct {
    ...
    JSONWriter WriteResetter
}
    if c.JSONWriter != nil {
        c.JSONWriter.Reset()
        return json.NewDecoder(io.TeeReader(resp.Body, c.JSONWriter)).Decode(&res)
    } else {
        return json.NewDecoder(resp.Body).Decode(&res)
    }

Thoughts?

rjp avatar May 23 '23 11:05 rjp

Reverted to simple io.Writer with client-initiated resetting. Think making JSONWriter a simple *bytes.Buffer and moving the reset to the library makes more sense (when would someone supply a non-bytes.Buffer as somewhere to save the JSON?) but could live with this solution.

rjp avatar May 25 '23 07:05 rjp

It can check before call Write()

if resetter, ok := c.JSONWriter.(WriterResetter); ok {
  resetter.Reset()
}

mattn avatar May 25 '23 09:05 mattn

Ah, I think I got almost to that solution twice with the WriteResetter and then checking if an interface{} could call Reset(). Never checked a type assertion on an interface that's bigger than a type before. Thanks.

rjp avatar May 25 '23 10:05 rjp