-
Notifications
You must be signed in to change notification settings - Fork 661
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable WebClient to send binary data #513
Conversation
Codecov Report
@@ Coverage Diff @@
## master #513 +/- ##
==========================================
- Coverage 81.32% 81.32% -0.01%
==========================================
Files 6 6
Lines 241 257 +16
Branches 37 43 +6
==========================================
+ Hits 196 209 +13
- Misses 29 30 +1
- Partials 16 18 +2
Continue to review full report at Codecov.
|
i like the fact that this makes sending binary data arguments via the i'm not sure how i feel about creating the new top-level export of i see these two changes as orthogonal and so i'd like to discuss them separately (unless you see some reason they make more sense being grouped into one change). |
tl;dr: because implementation logistics and developer experience (DX) 👏 👍 ✨. It's important to remember that I made a personal call on the random name generation because:
Swapping out the
The container approach makes changing the implementation somewhat trivial, as seen in this PR: just wrap any binary parameters in the container, then only supply options to
A practical solution would be to have the user just supply an object with web.files.upload({
file: {
data: someBuffer,
name: 'magical kitten gifs'
}
}); In contrast, I find the above breaks some kind of unspoken rule about the API method bindings, wherein we've added our own unique structure to the method, which has some consequences: An application that accepts key-value pairs based on user data (i.e. If a developer is reading the source of an application that uses this SDK, but making their app/bot in anything but this library, they can't really transfer ideas from the application they're attempting to learn from. I can say I've personally learned about numerous concepts and APIs just by reading open source code, be it in a different programming language or with a different library than what I might use. Making this SDK's structure differ from the official API's parameter structure breaks this ability to learn about the Slack API from consumers' apps. An example situation of the above
Consider this situation: you want to write a Slack application in Lua, but you know nothing about Slack's API. You get some of the gist from the official API documentation, but you really want to see some code to solidify the idea. So you search on GitHub for some other Slack apps. You come across GitHub's slack integration. You know some JS, so you're able to read the source and transfer the ideas. At some point, you want to use the web.files.upload({
file: {
data: someBuffer,
name: 'magical kitten gifs'
}
}); So you believe that the Slack API accepts a web.files.upload({
file = {
data = someDataObject,
name = 'magical kitten gifs'
}
}) Alas, you run into an API error. The This also somewhat carries over in code readability; it's easier to identify the data source of a binary field at a quick glance if the data field is always a single object ( I'll agree, functionality-wise, the Side note: one more possible "solution"There is one solution that I haven't mentioned above: exporting a function that somehow attaches the import { attachNameMetadata } from '@slack/client';
web.files.upload({
file: attachNameMetadata(bufferOrStream, "super top-secret kitten gif")
}); This function is limited to not being able to return an object because either it returns an object shape, which I've debunked above, or it returns a custom type, which is the solution I've already suggested above, without the need to call a function. The other option is for the function to set the Also, it's actually surprisingly hard to name this function in a way that both conveys its functionality and also keeps developers at ease knowing it doesn't do anything bad (i.e. For those reasons, I eliminated this solution early on. These reasons are very niche and opinionated, and if you're not sold, I'm more than happy with implementing any of the other solutions I mentioned. I found that the runtime cost of creating one object in the uncommon case of binary data was worth improving the developer experience for consumers and helping consumers write intuitive code for others, all at what feels like little to no cost. |
Sure, its implemented as a container, but does the user care? The user just wants to use the method. Wrapping things up in another container is an additional cognitive step towards reaching that goal. AFAICT the ability to be recognized is only to the benefit of the
I guess my point is, its more trivial for the user without a new type. Just supply a
I agree, I'd like to preserve the structure as much as possible, the chief reason being that the general reference documentation at https://api.slack.com/methods should map cleanly to the methods and arguments presented by this library. You make multiple appeals to the idea that the In the Now from the perspective of this package, the most clean mapping to HTTP is to map the So you see, It might be useful to compare this implementation with the one I'm theoretically suggesting. I'm happy to open a new PR based on this branch to illustrate, and then we'd have something concrete to compare. edit: i've learned that the statement about the |
@clavin #519 is where I'm suggesting we go with this. please take a look if you have a chance. if we can agree on it being a valid next step (whether or not you'd still like to go further with the new class), then i'd like to make a release today (4.1.0). there's some demand for a release that contains landed patches like #500. update: i'm pretty happy with #519 now, would like to merge ASAP. |
@aoberoi That PR has my approval 👍 #519 turns my "it works" PR into a "it just works" solution, which I like. I was definitely too caught up in the idea of giving the consumer an option to specify the I also commend you on the extra details in the comments and test names, some of that flew right over my head 👏 👏 👏 |
Summary
Enables the WebClient to send Buffers and Streams as binary data via
multipart/form-data
requests.This is mostly done using a simple
WebClientBinaryData
container which signals toWebClient
that the request should be sent asmultipart/form-data
. All top-level arguments that areBuffer
s orStream
s are automatically wrapped inWebClientBinaryData
for convenience.WebClientBinaryData
is exposed for future cases where a user might specifically want themultipart/form-data
file name to be something specific.If the name argument is omitted (as it is when wrapping
Buffer
s and mostStream
s), then a random 24-character name is generated to ensure that the data will be sent.Best of all, this solution is generic, so there's nothing in it specific to
files.upload
. The only cost of this generic solution is themultipart/form-data
name of the file won't be the same as thefilename
argument, if provided, but this shouldn't matter as the Web API itself handles renaming the file to the provided value offilename
.Follow-up
If this and #512 are merged, any binary arguments (
file
offiles.upload
andimage
ofusers.setPhoto
) should be modified to also acceptWebClientBinaryData
.Requirements (place an
x
in each[ ]
)