Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions doc/vim-rest-console.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ CONTENTS *VrcContents*
5.3 Global Variable Declaration..................... |VrcGlobalVarDecl|
5.4 Line-by-line Request Body.................... |VrcSplitRequestBody|
5.5 Consecutive Request Verbs......................... |VrcConReqVerbs|
5.6 Response handlers............................ |VrcResponseHandlers|
6. Configuration........................................ |VrcConfiguration|
vrc_allow_get_request_body................ |vrc_allow_get_request_body|
vrc_auto_format_response_enabled.... |vrc_auto_format_response_enabled|
Expand Down Expand Up @@ -384,6 +385,151 @@ verb is appended to the output view.
GET /test
DELETE /test
<
------------------------------------------------------------------------------
*VrcResponseHandlers*
5.6 Response Handlers

Response handlers are used to parse response and do one of following actions:
* substitute headers or global variables with values from response
* send parse result to quickfix list
* provide assertions which can abort execution of following requests in same
block

Response handler is effectively a comment of following format:
>
#{prefix}{target}<<{pattern}
<
where {prefix} defines the scope of handler (see below), {target} defines what
to do with result, and {pattern} defines how to parse response to get the
result.

There are three types of handler depending on {target}:
*VrcSubstitutionHandler*
1. when {target} is non-empty string it is considered a substitution
handler. It replaces remaining part of the line with {pattern} output. e.g.
when {target} is "Authorization: " then lines which defines Authorization
header will be replaced with respective value extracted from response.
*VrcTraceHandler*
2. when {target} is empty string it is considered trace handler. It outputs
all pattern results to quickfix list.
*VrcAssertionHandler*
3. when {target} is `?` string it is considered assertion handler. In this
case output of {pattern} is ignored but response code is considered. When
response code is false (i.e. non-`0`) then it terminates execution of current
block. See examples below.

{pattern} is any shell command which consumes response and produces some output
used by {target}.

Handler can have one of following `scopes` depending on {prefix} value:

`>` - local scope. This handler consumes output of current request and
makes substitutions (when it is substitution handler) only in current block and
in global section. These handlers executed automatically right after request
where they are defined. For this handler if {target} is `?` and {pattern} exit
code is non-`0` then it terminates current block execution.
`%` - global scope. This handler consumes whole content of VRC output
buffer and makes substitutions globally. It is ignored during usual request
launch. These handlers can be invoked with `:call VrcHandleResponse()` for
current line or selected region.

*VrcResponseHandlersExamples*
Examples~

Local-scoped subtitution handler used to set request header value:
>
http://somehost
Authorization: Bearer <TBD>
--
--
POST /token
#>Authorization: Bearer << jq -r '.access_token'

GET /protected_resource
<
here we have `Authorization` header defined in global scope. First request
returns some json with `access_token` field which can be used as bearer token
for call which requires auth. This handler executed right after first request,
extracts access token and put it in defined header. Second request will be
executed with respective Authorization header.

Local-scoped subtitution handler used to set global variable value:
>
http://somehost
userId=
--
--
GET /user
#>userId=<< jq -r '.id'

PUT /user/:userId
{
.....
}
<
in this example, first we GET "user" resource. Handler extracts it's `id` from
response and place it in `userId` global variable. In second request we use
this `userId` to update resource.

Local-scoped assertion handler:
>
--
-i
GET /user

#>?<< grep "HTTP/" | grep "404"

POST /user/
{
....
}
<
in this example second request will be executed ONLY when first responds with
`404`.

Global-scoped handler, used to extract response codes of all requests:
>
--
-i
GET /user/1
GET /user/2
GET /user/3

#%<< grep "HTTP/"
<
this handler won't be executed automatically. Put cursor on it (or visually
select line) and `:call VrcHandleResponse()` . It will show in quickfix list
something like:
>
|144| HTTP/2 200
|144| HTTP/2 200
|144| HTTP/2 404
<
i.e. first and second resource exist. Last one - not. This types of handlers
can be used to generate some kind of report after execution of series of
requests.

Note: this handler uses as input whole content of VRC output buffer.

You can automate it even more. For example you need to quickly ensure that all
requests from your batch finished with success (e.g. 200), then you can do:
>
--
-i
GET /user/1
GET /user/2
GET /user/3

#%?<< [[ -z $(grep "HTTP/" | grep -v "200") ]]
<
it will produce in quickfix list:
>
|145| True
<
if all requests respond with `200`, or `False` otherwise. You can visually
select several handlers and `:call VrcHandleResponse()` for region. They will
be executed line by line. Output will be appended to quickfix list.

==============================================================================
*VrcConfiguration*
6. CONFIGURATION~
Expand Down
129 changes: 120 additions & 9 deletions ftplugin/rest.vim
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,33 @@ function! s:ParseVerbQuery(start, end)
return [lineNum, s:StrTrim(getline(lineNum))]
endfunction

"""
" Parse pipe command
"
" @param int a:start
" @param int a:end
" @return string
"
function! s:ParsePipe(start, end)
let pipe = ''
if (a:end < a:start)
return ''
endif

let lineBuf = getline(a:start, a:end)
for line in lineBuf
let line = s:StrTrim(line)
if line ==? '' || line =~? s:vrc_comment_delim || line =~? '\v^--?\w+'
continue
endif

if line =~ '^|'
let pipe = line
endif
endfor
return pipe
endfunction

"""
" Parse header options between the given line numbers (inclusive end).
"
Expand All @@ -194,22 +221,21 @@ endfunction
"
function! s:ParseHeaders(start, end)
let contentTypeOpt = s:GetOpt('vrc_header_content_type', 'application/json')
let headers = {'Content-Type': contentTypeOpt}
let headers = {'content-type': contentTypeOpt}
if (a:end < a:start)
return headers
endif

let lineBuf = getline(a:start, a:end)
let hasContentType = 0
for line in lineBuf
let line = s:StrTrim(line)
if line ==? '' || line =~? s:vrc_comment_delim || line =~? '\v^--?\w+'
if line =~ '^|' || line ==? '' || line =~? s:vrc_comment_delim || line =~? '\v^--?\w+'
continue
endif
let sepIdx = stridx(line, ':')
let sepIdx = stridx(line, ': ')
if sepIdx > -1
let key = s:StrTrim(line[0:sepIdx - 1])
let headers[key] = s:StrTrim(line[sepIdx + 1:])
let key = tolower(s:StrTrim(line[0:sepIdx - 1]))
let headers[key] = s:StrTrim(line[sepIdx + 2:])
endif
endfor
return headers
Expand All @@ -232,7 +258,7 @@ function! s:ParseVals(start, end)

for line in lineBuf
let line = s:StrTrim(line)
if line ==? '' || line =~? s:vrc_comment_delim
if line =~ '^|' || line ==? '' || line =~? s:vrc_comment_delim
continue
endif
let sepIdx = stridx(line, '=')
Expand Down Expand Up @@ -260,6 +286,7 @@ function! s:ParseGlobSection()
\ 'headers': {},
\ 'curlOpts': {},
\ 'vals': {},
\ 'pipe': '',
\}

""" Search for the line of the global section delimiter.
Expand All @@ -280,11 +307,15 @@ function! s:ParseGlobSection()
""" Parse global vals.
let vals = s:ParseVals(hostLine + 1, lastLine - 1)

""" parse pipe command
let pipe = s:ParsePipe(hostLine + 1, lastLine - 1)

let globSection = {
\ 'host': host,
\ 'headers': headers,
\ 'curlOpts': curlOpts,
\ 'vals': vals,
\ 'pipe': pipe,
\}
return globSection
endfunction
Expand Down Expand Up @@ -331,6 +362,7 @@ endfunction
" 'httpVerb': string,
" 'requestPath': string,
" 'dataBody': string,
" 'pipe': string,
" }
"
function! s:ParseRequest(start, resumeFrom, end, globSection)
Expand Down Expand Up @@ -374,6 +406,12 @@ function! s:ParseRequest(start, resumeFrom, end, globSection)
let curlOpts = get(a:globSection, 'curlOpts', {})
call extend(curlOpts, localCurlOpts)

""" Parse local pipe and use global pipe if empty
let pipe = s:ParsePipe(lineNumHost + 1, lineNumVerb - 1)
if pipe == ""
let pipe = get(a:globSection, 'pipe', '')
endif

let vals = get(a:globSection, 'vals', {})

""" Parse http verb, query path, and data body.
Expand Down Expand Up @@ -402,7 +440,8 @@ function! s:ParseRequest(start, resumeFrom, end, globSection)
\ 'curlOpts': curlOpts,
\ 'httpVerb': httpVerb,
\ 'requestPath': queryPath,
\ 'dataBody': dataBody
\ 'dataBody': dataBody,
\ 'pipe': pipe
\}
endfunction

Expand Down Expand Up @@ -743,10 +782,20 @@ function! s:RunQuery(start, end)
silent !clear
redraw!

call add(outputInfo['outputChunks'], system(curlCmd))
let output = system(curlCmd)

" NOTE: If doest work swap these
" call add(outputInfo['outputChunks'], output)
call add(outputInfo['outputChunks'], system(curlCmd . request.pipe))
if shouldShowCommand
call add(outputInfo['commands'], curlCmd)
endif

if s:HandleResponse(a:start, resumeFrom, a:end, output, outputInfo) != 0
break
endif
let globSection = s:ParseGlobSection()

let resumeFrom = request.resumeFrom
endwhile

Expand All @@ -759,6 +808,46 @@ function! s:RunQuery(start, end)
\)
endfunction

"""
" Handle output of request
"
" @param int a:start
" @param int a:end
" @param int a:input - request output
" @param int a:outputInfo - produced output
" @return int - return code of handler execution
"
function! s:HandleResponse(start, resumeFrom, end, input, outputInfo)
let currVerb = s:ParseVerbQuery(a:resumeFrom, a:end)[0]
let nextVerb = s:ParseVerbQuery(currVerb + 1, a:end)[0]
if nextVerb == 0 | let nextVerb = a:end | endif

for i in range(currVerb, nextVerb)
let line = getline(i)
if match(line, '^\(#>\).*<<.\+') != 0 | continue | endif
let cmd = map(split(line, '#>\|<<', 1)[1:2], {k,v -> trim(v, ' ', 1)})
let output = system(cmd[1], a:input)
let code = v:shell_error
if len(cmd[0]) && trim(cmd[0]) != '?'
call setline(1, map(getline(1, s:LineNumGlobSectionDelim()), {k,v -> substitute(v, '^'..cmd[0]..'.*', cmd[0]..trim(output), '')}))
call setline(a:start, map(getline(a:start, a:end), {k,v -> substitute(v, '^'..cmd[0]..'.*', cmd[0]..trim(output), '')}))
else
if trim(cmd[0]) == '?'
if code != 0
echohl WarningMsg | echo "Execution failed at line "..i.." (code: "..code..")" | echohl None
call cursor(i, 1)
return code
endif
else
if s:GetOpt('vrc_show_command', 0)
call add(a:outputInfo['commands'], output)
endif
endif
endif
endfor
return 0
endfunction

"""
" Restore the win line to the given previous line.
"
Expand Down Expand Up @@ -809,6 +898,28 @@ function! VrcQuery()
endif
endfunction

"""
" Handle content of output buffer
"
function! VrcHandleResponse()
if match(getline("."), '^#%.*<<.\+') != 0 | return | endif
let cmd = map(split(getline("."), '#%\|<<', 1)[1:2], {k,v -> trim(v, ' ', 1)})
let input = getbufline(bufnr(s:GetOpt('vrc_output_buffer_name', '__REST_response__')), 1, "$")
if len(input)
let output = system(cmd[1], input)
let code = v:shell_error
if len(cmd[0]) && trim(cmd[0]) != '?'
call setline(1, map(getline(1, '$'), {k,v -> substitute(v, '^'..cmd[0]..'.*', cmd[0]..trim(output), '')}))
else
if trim(cmd[0]) == '?' | let output = (code == 0) ? "True" : "False" | endif
call setqflist([], line('.') == a:firstline ? 'r' : 'a',
\ {'items': map(split(output, '\n'), {k,v -> {'text':v, 'lnum':line('.')}})})
let currWin = winnr()
copen | execute currWin . 'wincmd w'
endif
endif
endfunction

"""
" Do the key map.
"
Expand Down