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

Additional orchestration capabilities would be useful #405

Closed
woolfas opened this issue Mar 1, 2023 · 9 comments
Closed

Additional orchestration capabilities would be useful #405

woolfas opened this issue Mar 1, 2023 · 9 comments

Comments

@woolfas
Copy link

woolfas commented Mar 1, 2023

Consider the use case where I want to invoke (or skip) a request based on the previous request response. Take a look in this http example which illustrates that:

### Send Fruit
# @name send_fruit
POST https://httpbin.org/anything
Content-Type: application/json

{
        "type": "fruit",
        "name": "Apple"
}

//==========================================
### Buy Item
# @name buy_item
POST https://httpbin.org/anything
Content-Type: application/x-www-form-urlencoded

itemName={{$global.itemName}}

//==========================================
### Shopping Use Case #1
# @forceRef send_fruit
{{
    $global.isFruit = response.parsedBody.json.type == "fruit";
    $global.itemName = "Orange";
}}
# @loop while $global.isFruit
# @forceRef buy_item
{{
    $global.isFruit = false;
    $global.itemName = "Cake";
}}
# @forceRef buy_item

//==========================================
### Shopping Use Case #2
# @forceRef send_fruit
{{
    $global.isMilk = response.parsedBody.json.type == "milk";
    $global.itemName = "Orange";
}}
# @loop while $global.isMilk
# @forceRef buy_item
{{
    $global.isFruit = false;
    $global.itemName = "Cake";
}}
# @forceRef buy_item

In the "Shopping Use Case #1" I want to buy the Orange and Cake if I got "fruit" in the previous request. I made the ugly workaround using the loop for that, but I think it would make more sense having the if condition which is missing right now in HttpYac.
In this case the code would look nicer:

### Shopping Use Case #1
# @forceRef send_fruit
{{
    $global.isFruit = response.parsedBody.json.type == "fruit";
    $global.itemName = "Orange";
}}
# @if $global.isFruit
# @forceRef buy_item
{{
    $global.itemName = "Cake";
}}
# @forceRef buy_item

But if I would like to buy the Cake regardless if I bought Orange or not, then "Shopping Use Case #1" has a problem and it is illustrated in the "Shopping Use Case #2". In this case isMilk is false, therefor the execution stops at the loop line and both buy_item requests are not executed at all.
To fix that issue I suggest to introduce loop and if blocks. e.g.

# @loop while <condition>
   Do something
# @endloop

# @if <condition>
   Do something
# @endif

That will give additional request orchestration possibilities and also will allow to execute multiple requests in the scope of a block.
So if we had that in HttpYac, the previous code could be improved something like this:

### Shopping Use Case #1
# @forceRef send_fruit
{{
    $global.isFruit = response.parsedBody.json.type == "fruit";
}}
# @if $global.isFruit
  {{
      $global.itemName = "Orange";
  }}
  # @forceRef buy_item
# @endif
{{
    $global.itemName = "Cake";
}}
# @forceRef buy_item

So in summary I suggest to introduce two improvements:

  • the @if annotation
  • the @loop - @endloop and @if - @endif blocks

Please consider implementing these improvements

@AnWeber
Copy link
Owner

AnWeber commented Mar 1, 2023

I had thought about an @endloop at one point, but I put it on hold because of the complexity of the implementation at the time. Additionally I had no reasonable idea yet to support nesting as well. My goal is not to develop my own language that supports loops and conditions. Others can do better.
My suggestion would be to turn it around and offer it for complex cases via Javascript to trigger the appropriate requests. This is already possible now (require('httpyac') is supported in scripts), but still complex. Idea would be to offer a simple api $httpyac for these cases.

### Shopping Use Case #1
{{
async function execute(){
  const result = await $httpyac.request("send_fruit");
  if(result .isFruit){
    await $httpyac.request("buy_item", {
      "$global.itemName": "Orange",
    });
  }
  await $httpyac.request("send_fruit", {
      "$global.itemName": "Cake",
    });
}

exports.execute= execute();
}}

The api should support the following cases

  • request a other region with name and optional variables for request
  • return variables created by request call
  • optional: import other httpfile or allow request of region in other httpFile
  • ... more?

Would this idea also be possible / useful?

@woolfas
Copy link
Author

woolfas commented Mar 2, 2023

Well yes this looks promising.

The pros are that in case of complex orchestration use cases, such API would allow to implement basically anything.
However looking into your example, for some people it may still look a bit too complicated to read. So cons are that such code will be readable for developers, but for business analysts or QA's who are less technical, might be hard to understand.

But this idea is definitely worth consideration as it will bring endless possibilities to httpyac and will lift it to another level.

@AnWeber
Copy link
Owner

AnWeber commented Mar 15, 2023

I once created a first implementation for the api. I'm not documenting this properly on httpyac.github.io yet, but just want some feedback.

{{
  exports.wait = (async function test() {
    await $httpyac.import("./refWithJs.http");
    await $httpyac.execute("json");
  })();
}}
POST https://httpbin.org/anything
{{json.args.id}}

However looking into your example, for some people it may still look a bit too complicated to read.

I am not sure if your design is more understandable. But yes, we will probably lose some QA staff. However, the complexity in implementation is considerably less and you gain much more possibilities with the API.

AnWeber added a commit to httpyac/httpyac.github.io that referenced this issue Mar 15, 2023
@woolfas
Copy link
Author

woolfas commented Mar 16, 2023

I tried that out and.. well it just works :)
That's really awesome, but I also thought about api improvement. Would it be possible to make $httpyac api work in a global context? I'm not really sure if this suggestion is feasible, but that might allow to write much cleaner code, e.g. something similar to this:

{{
    $httpyac.import("./refWithJs.http");
    $httpyac.execute("json");
}}
POST https://httpbin.org/anything
{{json.args.id}}

So this is something to think about, but what you already implemented is really cool. This api brings totally new and awesome possibilities to httpyac. Thanks!

@AnWeber
Copy link
Owner

AnWeber commented Mar 17, 2023

I'm not really sure if this suggestion is feasible, but that might allow to write much cleaner code, e.g. something similar to this:

I understand your desire, but I'm not comfortable with the change. For one thing, you are still missing the specification of await which would be mandatory. On the other hand, this is something called Top Level Await. This is not supported by CommonJS script. It could be done and I already had a PR #398 about it recently, but I would deviate from the default here. I still have it in the back of my mind, and wanted to think about it at my leisure.

@AnWeber AnWeber closed this as completed Mar 17, 2023
@AnWeber
Copy link
Owner

AnWeber commented Mar 23, 2023

@woolfas I have released a version with async support. Now it should also be possible similar to your example. You need to add await Statements

@woolfas
Copy link
Author

woolfas commented Mar 24, 2023

Thank you for this improvement. That's great! I tested it and it works. But I also found the issue.
Consider this http script:

### Send Fruit
# @name send_fruit
POST https://httpbin.org/anything
Content-Type: application/json
{
        "type": "fruit",
        "name": "Apple"
}

### httpyac api test
{{
    await $httpyac.execute("send_fruit");
    $global.isFruit = response.parsedBody.json.type == "fruit";
    if ($global.isFruit) {
        console.log("It is fruit")
    }
}}

If you will execute httpyac api test, you will get the error ERROR: ReferenceError - response is not defined.
Looks like that response var is initialized only after all the script is executed but not right after $httpyac.execute("send_fruit"); call.
So I suppose this is something what can be improved in the future. However I found a workaround for that. You can simply make two subsequent scripts and it will work:

### httpyac api test
{{
    await $httpyac.execute("send_fruit");
}}
{{
    $global.isFruit = response.parsedBody.json.type == "fruit";
    if ($global.isFruit) {
        console.log("It is fruit")
    }
}}

@AnWeber
Copy link
Owner

AnWeber commented Mar 26, 2023

That the variables are available in the global context not only in the templates but also in the javascript is simply because I was too lazy to distinguish between them. It would be possible to put the variables back to the global context, but it doesn't feel right. I implement the execute method instead, so that it returns the new variables.

@woolfas
Copy link
Author

woolfas commented Apr 5, 2023

Perfect! That solves the issue. Thanks

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

No branches or pull requests

2 participants