Skip to content
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

Support arbitrary transformations in request and response record fields #53

Closed
ptrf opened this issue Nov 15, 2016 · 16 comments
Closed
Assignees

Comments

@ptrf
Copy link

ptrf commented Nov 15, 2016

Would it be of interest to support arbitrary transformations in requests and response?

The gist of this proposal is to perform additional computation in katt:make_katt_request/3 and katt:make_katt_response/3 which evaluates transformations parametrized with the current Params set, substituting the result of these transformations into the appropriate request / response fields.

Background

Given a set of transactions in a blueprint, the response of one transaction may contain a value that is stored via {{>var}}. In one or more of the following transactions, rather than substituting the contents of var using {{<var}}, it may be more interesting to substitute a transformation of the contents of var - loosely, something like {{{ "type": "erlang", "eval": "mod:transform/1" }}}, where mod:transform/1 is parametrized over Params, and thus accessing var using proplists:get_value/2.

Example

A very made up example could be a client requesting a nonce for challenge, then answering it:

GET /challenge
> Accept: application/json
> Host: localhost
< 200
< Content-Type: application/json; charset=utf-8
{
    "nonce": "{{>nonce}}"
}

POST /challenge
> Content-Type: application/json
> Accept: application/json
{
    "response": "{{{ "type": "erlang", "eval": "challenge_response:answer_from_nonce/1" }}}"
}
< 200
< Content-Type: application/json; charset=utf-8
{
    "response_valid": true
}

However, the evaluation shouldn't necessary be limited to the body, but also be allowed in url and headers (think JWT).

Remarks

The runtime_value validation computation supports some type of arbitrary evaluation, but is only available in the (expected) body of the response. The substitution of params values is performed by katt_callbacks:recall/4, before the contents of the erlang or shell field is evaluated. Although there is some overlap between the expressiveness of this feature and runtime_value validation, this feature is different as it is run before the validation step.

Also, in relation to the discussion in #29 and #33, the type/eval field construction would allow for language independence (in principle), although there are open questions as to how to pass the Params array to the shell function, assuming we are restricted to for instance POSIX sh(1).

@andreineculau
Copy link
Member

Hi! I need to read once again your suggestion, but is it correct (not necessarily enough to reach the ultimate goal, but in the right direction) to split it as

  1. allow runtime_value outside validation flows, and outside body
  2. provide access to Params in runtime_value calls

One other way, is to allow for Params manipulation in between transactions (or even in between request-response). In your example, that would mean calling challenge_response:add_nonce_response/? before POST /challenge and have the request body look like

{
  "response": "{{<nonce_response}}"
}

This pattern would allow for complex scenarios, maybe even making things like runtime_value redundant.

Does this sound like a possible/better alternative?

@ptrf
Copy link
Author

ptrf commented Nov 15, 2016

Params manipulation in between request-response and transactions would be ideal. But as far as I can tell, it would require altering the peg syntax. If so, perhaps the nonterminals request and response could be extended:

request <-
   preamble:preamble? signature:signature headers:request_headers body:body?
...

response <- 
  preamble:preamble? status:response_status headers:response_headers body:body?
...

preamble <-
 bang eval_decl

.. 

bang <- "!" s+ ~;

Or how would you envision bringing about params manipulation?

@ptrf
Copy link
Author

ptrf commented Nov 15, 2016

Given such a solution, my example from before could be rewritten as:

GET /challenge
> Accept: application/json
> Host: localhost
< 200
< Content-Type: application/json; charset=utf-8
{
    "nonce": "{{>nonce}}"
}

! erlang: "fun(Params) -> lists:map(fun ({nonce, V}) -> {challenge_response, transform:nonce(V)}; (KV) -> KV end, Params)"
POST /challenge
> Content-Type: application/json
> Accept: application/json
{
    "response": "{{<challenge_response}}"
}
< 200
< Content-Type: application/json; charset=utf-8
{
    "response_valid": true
}

I haven't given the format eval_decl proper thoughts, but the idea is that you can support multiple different evaluation formats.
Additionally, multiple preambles could be supported, e.g.

! ...
! ...
[...]
POST /
>
[...]

@andreineculau
Copy link
Member

Oh, no, no. What I meant is KATT could have an extra callback e.g. pre-request and you could manipulate the Params there.

The blueprint markup would not require any change at all, since you'd write your"{{{ "type": "erlang", "eval": "challenge_response:answer_from_nonce/1" }}}" in the initial post as {{<nonce_response}}, except maybe, to diminish the element of surprise, you would add a comment like NOTE: <nonce_response> must be calculated externally.

That's why I wrote that runtime_value could become redundant.

@ptrf
Copy link
Author

ptrf commented Nov 15, 2016

Ok, I think I understand what you mean. I do think it's unfortunate if the callback invocations are implicit, as you mention yourself. And how would you control the invocation of a callback on a test case basis

Also, given your reply, would it make sense to have an additional pre-response callback as well?

@andreineculau
Copy link
Member

And how would you control the invocation of a callback on a test case basis

I don't follow this part. Care to explain?

Also, given your reply, would it make sense to have an additional pre-response callback as well?

Yes, or have a generic pre-foo callback. All of this is not binding in anyway, just brainstorming. I need to properly digest the consequences :)

@ptrf
Copy link
Author

ptrf commented Nov 15, 2016

Regarding controlling the callback invocation: In my syntax-altering proposal, the invocation is controlled explicitly, i.e. before a transaction request or response; you have to specify a given eval block before each request/response.

As I understand your proposal, you would have a pre-request callback which I assume you would pass in the Callbacks parameter to katt:run/?. However, how do you envision katt or the callback to know, if it should be called for a given transaction request? Vice versa for pre-response.

To give you an example, say we define a pre-request callback along with a blueprint containing 3 transactions. But the callback should only be invoked when executing the second transaction.

I'm thinking perhaps a header akin to x-katt-description, maybe call x-katt-callback-tag, containing a value that would be passed to the callback along with Params, for the callback to match against?

@ptrf
Copy link
Author

ptrf commented Nov 16, 2016

As a solution for the problem that made me submit this issue, I have made this temporary solution:

As in test/katt_run_tests.erl, I meck katt_util:external_http_request/6 with a wrapper function which depending on the request either:

  • Deconstructs the parameters to obtain the data to be transform, followed by creating a response tuple encapsulating the transformed data, thus allowing the result to be {{>var}} stored in the katt transaction response.
  • Pass the call through to the original external_http_request function.

In my case, I match against the http method, passing through unless method is MKCOL.

@andreineculau
Copy link
Member

However, how do you envision katt or the callback to know, if it should be called for a given transaction request?

KATT wouldn't know. It would call the callback fun every time, and pass in decisional information e.g. the upcoming request information. Based on that, the callback might just return asap, or do actual processing.

@ptrf
Copy link
Author

ptrf commented Nov 16, 2016

How about specifying something like this:

Transforms = #{tag => {{request, Fun :: function()}, {response, Fun :: function()}}},
...
katt:run(...,...,...,Callbacks, Transforms).

And then in the bluprint, you would have

GET /
> x-katt-transaction-tag: tag
[...]

That way, the callback would pop the x-katt-transaction-tag the same way x-katt-description is popped from headers, and use the value to look up in the transforms map, whether there are any request or response related transformations to be run on the Params.

@ptrf
Copy link
Author

ptrf commented Nov 29, 2016

@andreineculau Any comments to my transaction-tag proposal above?

@andreineculau
Copy link
Member

@ptrf hej, i'm sorry, but i've been caught up in other things. I'll get back to you and the topic within the next week

@andreineculau
Copy link
Member

@ptrf I apologize for not keeping to my word, but 28 days later... I submit a PR to address this

@andreineculau andreineculau modified the milestone: 1.6.0 Jan 5, 2017
@ptrf
Copy link
Author

ptrf commented Jan 16, 2017

@andreineculau and I apologize for being slow to answer. The PR looks good from a cursory look, so I'll get back to you when I have had a thorough look. Thanks!

andreineculau added a commit that referenced this issue Mar 12, 2017
introduce transform callback. fixes #53
@andreineculau
Copy link
Member

@ptrf I have merged my PR. It won't get better the older it gets :)

@ptrf
Copy link
Author

ptrf commented Mar 13, 2017

@andreineculau Thank you very much! The fix addresses our issues well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants