-
Notifications
You must be signed in to change notification settings - Fork 346
Description
Thanks for creating buf curl! I think it's a very promising tool that will make it a lot easier to work with gRPC and Connect APIs.
An "obvious" and killer feature that's missing from buf curl is tab completion of services and methods. It would make for a fantastic developer experience if buf curl https://my-api.com/<TAB> could list out the available endpoints and methods.
A few ideas for how this could be done:
- Check and see if we're in a folder (or sub-folder) that contains a Buf module or workspace. In that case the module could be built, and the endpoints and methods enumerated.
- Attempt to do reflection on the written URL (and with any header flags, if passed into the command).
- If the
--schemaflag is passed, inspect that to find the data.
I don't know how buf curl is typically used. But for my personal usage, I'm mostly using it against local services I'm actively working on, for manual testing and debugging. Prior to buf curl being developed, I've used a Fish script called bufcurl that wraps grpcurl and uses the approach I described in point 1. I've implemented tab completion for that (it only lists services, I haven't gotten around to implementing the methods). Pasting this below, for inspiration/context on what I'm thinking.
A final note: I'm configuring my bufcurl script through environment variables. In the folder containing services I'm working on I typically set these environment variables using an environment file. I think this is crucial to providing the best possible DX, as it simplifies the command needed to be invoked.
The script itself
function bufcurl
if test -n "$BUFCURL_PLAINTEXT"
set plaintext_arg '-plaintext'
end
# Check if we get passed a data arg, with
# a trailing `=`
if string match -q -- "*-d=*" "$argv"
set idx (contains --index -- -d= $argv)
set data_args $argv[$idx]
# only do it once, because it's the same arg
set --erase argv[$idx]
# Check if we get a data arg, without
# trailing `=`
else if contains -- -d $argv
set idx (contains --index -- -d $argv)
set data_args $argv[$idx] $argv[(math "$idx + 1")]
# do it twice, both flag and value
set --erase argv[$idx]
set --erase argv[$idx]
end
if test -n "$BUFCURL_HEADER"
set header_arg -H="$BUFCURL_HEADER"
end
if test -n "$BUFCURL_VERBOSE"
set verbose_arg "-v"
end
# If we get passed a target, this overwrites
# env var target.
if string match -q -- "*localhost*" "$argv"
set --erase BUFCURL_TARGET
end
set working_dir (pwd)
while not test -e buf.yaml -o -e buf.yml -o -e buf.work.yaml -o -e buf.work.yml
if test (pwd) = "/"
echo could not find Buf config! >&2
cd $working_dir
return 1
end
__bufcurl_log pwd=(pwd), going backwards
cd ..
end
__bufcurl_log found buf_dir=(pwd)
set buf_dir (pwd)
cd $working_dir
__bufcurl_log plaintext: $plaintext_arg
__bufcurl_log target: $BUFCURL_TARGET
grpcurl $verbose_arg $plaintext_arg $header_arg $data_args -protoset (buf build $buf_dir -o - | psub) $BUFCURL_TARGET $argv
endThe tab completions
$ cat $HOME/.config/fish/completions/bufcurl.fish
function __bufcurl_list_services
buf build --exclude-source-info --output -#format=json | \
jq --raw-output --compact-output \
'.file | map ({service: .service, package: .package} | select(.service) | {service: .service[0].name, package: .package} | .package +"."+ .service ) | .[]'
end
# Not implemented...
function __bufcurl_list_methods --argument-names service
end
function __bufcurl_complete
# no buf setup!
if not test -e buf.yaml && not test -e buf.work.yaml
return
end
set -l cmd (commandline -poc)
set -e cmd[1]
if not __fish_seen_subcommand_from (__bufcurl_list_services)
__bufcurl_list_services
return
end
__bufcurl_list_methods
end
complete -c bufcurl --no-files -a "(__bufcurl_complete)"