an API quandary
I’ve been working on egg-mode a lot more in the past couple months. It’s been nice to jump back into it and get some long-standing issues resolved.
Right now i’m working off-and-on to get the Direct Messages code up-to-date. Back in 2018, Twitter changed how the API for DMs worked, which broke the existing code. However, egg-mode hasn’t been updated to the new API until now.
One of the changes was how they exposed getting a list of DMs for a user. Before, the endpoints to pull a user’s DMs were structured similarly to pulling a list of tweets: you would get a page of results and could page back and forth based on the IDs of the messages you wanted to go before/after. Now, pagination is more like getting a list of users: you get a page of results, as well as an ID that you use to pull the next page.
The existing “get IDs for the next page” APIs are wrapped in egg-mode with the CursorIter
type
and Cursor
trait. This wraps the functionality to load a page of results, save the next/previous
page IDs, and query the next/previous page as desired. However, the new DM cursoring API works
differently from the Cursor
trait: the page IDs are strings instead of numbers, and there is no
“previous page” ID to page backwards through results.
In my current draft branch, i’ve decided to create a new ActivityCursorIter
type that wraps the DM
cursoring APIs to provide a Stream
-like API for them. However, there’s one design wart in
CursorIter
that i don’t quite like: CursorIter
is generic over the Cursor
type, which isn’t
actually the type the user cares about, in the end. If you’re using CursorIter
to load a list of
users, you want to eventually get a Vec<TwitterUser>
, but you interact with a
CursorIter<UserCursor>
(and call functions that return that type).
So now, my current draft ActivityCursorIter
instead structures its support trait and types around
the item type, not the cursor type. There are still some raw types that most users won’t care
about, but i feel like it’s a bit better to see a function that returns
ActivityCursorIter<DirectMessage>
instead of ActivityCursorIter<DMCursor>
or the like. At least
that way, you know you’re eventually getting some direct messages in the end, without having to
squint at the type name or click through a couple trait implementations.
One unfortunate side effect of this is that the DM cursor types are the same as the “raw” DM
deserialization types. As well, since i need to access those types from the trait implementation of
the public types, i need to expose those raw types publicly, even if it’s masked by a
#[doc(hidden)]
attribute to hide it from the public documentation.
(In all honestly, though, they would likely get exposed in the raw
module that was introduced in
egg-mode 0.15.0. Since DMs aren’t given directly in the response JSON like the other Twitter types
are, i want to expose a way for users of the raw
API to load these types as well. The bad part
about doing it like this is that it requires me to create lots of sub-modules to break apart the
“public” types from the “raw” ones. In the end, i’m just very thankful for pub(crate)
for letting
me expose things internally and then relocate them later for public consumption.)
So this leaves the “quandary” from the post title. The thing i’m currently procrastinating working
on is deciding the question: Should i implement ActivityCursorIter
like CursorIter
(and have the
“raw” types in the public API) or should i write it like i mentioned above? If i do the latter,
should i also refactor CursorIter
to work the same way? It all just seems like tedious busy-work,
and a potentially unnecessary API breakage. On the other hand, I like the idea of cleaning up the
main API to make it potentially easier to understand. I’m just not sure it would be worth the
effort.