In yesterday's post I explained some changes to the Tourmaline API, and my rationale for making those changes. After writing that post I had a somewhat better idea of where exactly I wanted to take those changes, so in this post I'll go over the conclusions I made.
First of all, I had mentioned thinking about replacing
Filters. After a little bit of deliberation I decided this was the route I wanted to go, so I did so. The handler code was a massive mess, and was never my ideal way of doing things.
Filters make things much simpler and cleaner, at the expense of a tiny bit more work for the end user.
This does not mean the
Command annotation is going away. On the contrary, using the
Filters made it even easier to implement additional annotations like
Command, but without a massive, messy, bug-prone macro. But wait, that's not all!
Something that annoyed me about the previous system was the lack of a clear context. Every handler was forced to have it's own
_Context object, such as
EventContext. This was a simple struct that wrapped the
Update, and potentially
InlineQuery, etc etc. It conveniently, though potentially buggily, forwarded missing arguments to each item in the struct, allowing you to do things like
ctx.reply rather than
ctx.message.reply. This was nice, but had a couple of issues.
The first issue was predictability. Ideally code should be as predictable as possible, and with each
Handler having their own context this was not the case. Additionally each additional handler increased the maintenance burden twofold. First I had to worry about the handler itself, then I also had to worry about the
Context object. I'm happy to announce that this complexity has been reduced enormously!
any escape hatch. Crystal on the other hand, being statically typed, doesn't allow these kinds of runtime modifications. If an object says it has certain properties, it has those properties. No more and no less.
This is obviously great for safety and preventing runtime errors, but it's not so great for cases like this where you'd like to be able to have certain methods modify a context and pass it around. Luckily, I found a workable solution by looking at the code for Crystal's new
Log module. In specific, the
Log::Context seemed to have exactly what I needed. Feel free to take a look at it's code and the code for `Tourmaline::UpdateContext and you'll be sure to see the similarities.
So what does this all mean for you? Well a couple things. First of all,
Filters now add relevant information to
update.context as you can see in this example:
update.context is very similar to objects like
YAML::Any. It is a container object that holds other container objects. Since it doesn't know what any of these objects are for sure, you have to coerce the internal object to the right type (hence the
as_s for "as string"). To see what items individual filters add to the context, you can just look at the filter's entry in the API documentation. For instance, in the the
CommandFilter entry, under "Context additions" you can see that it adds
Anyway, that's all for now. Big changes, and a new release will be out soon.