mattn / go-mastodon

mastodon client for golang
MIT License
599 stars 85 forks source link

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

Open rjp opened 1 year ago

rjp commented 1 year ago

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.)

mattn commented 1 year ago

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
rjp commented 1 year ago

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 commented 1 year ago

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.

mattn commented 1 year ago

It can check before call Write()

if resetter, ok := c.JSONWriter.(WriterResetter); ok {
  resetter.Reset()
}
rjp commented 1 year ago

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.