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

Refactor rest Operation #420

Closed
cdavernas opened this issue Jul 7, 2021 · 65 comments · Fixed by #652
Closed

Refactor rest Operation #420

cdavernas opened this issue Jul 7, 2021 · 65 comments · Fixed by #652
Assignees
Labels
change: feature New feature or request. Impacts in a minor version change
Milestone

Comments

@cdavernas
Copy link
Member

cdavernas commented Jul 7, 2021

What would you like to be added:

  1. Rename the rest operation type into openapi: the actual REST operation is an openapi operation, and could therefore be confusing to some.
  2. Create a rest operation type, which allows configuring a request from top to bottom, thus enabling out-of-the-box support for legacy systems.

The rest operation definition could look like this:

- name: MyOperation
  operation: 'http://mysite.com/api/{pathParam}#POST'
  type: rest

And it's reference like this:

- name: MyAction
  functionRef:
    refName: MyOperation
    arguments:
      headers:
        - name: headerParam
          value: 'headerValue'
      path:
        - name: pathParam   #references the {pathParam} value in the operation url
          value: 'myendpoint' #replaces the {pathParam} value in the operation url
      query:
        - name: queryParam
          value: 'queryvalue'
      cookie:
        - name: cookieParam
          value: 'cookievalue'
      body:
        firstName: Tihomir
        lastName: Surdilovic

Why is this needed:

  1. Calling a cat a cat 😸
  2. Allows out-of-the-box support for legacy systems and broadens SW use cases even further 🔫

Notes for implementers:

The idea is basically to allow users to somehow mimic an OpenAPI doc in place of a function reference's arguments property, thus allowing the invocation of "dumb" http services.

@JBBianchi
Copy link
Member

Good idea!

Not 100% sure about the spec, for instance

operation: 'http://mysite.com/api/{pathParam}#POST'

The # could be part of the url. I think a separated property for the verb/method would be cleaner.

Also, not sure having cookie is required, it's a set of headers after all?

@cdavernas
Copy link
Member Author

cdavernas commented Jul 7, 2021

@JBBianchi Great point for the #. But this is just an example, a first glimpse.
As for cookies, I added them because so does OpenApi, didn't even bothered to think 😄

@JBBianchi
Copy link
Member

It might be easier to have cookies separated, not sure.

@tsurdilo
Copy link
Contributor

tsurdilo commented Jul 7, 2021

@cdavernas @JBBianchi nice +1 on doing this.

The only two things I would change:

  1. First the path param - make it an array of strings, for example:

    "path": ["a", "b", "${ .c}"]
    

and in your operation param you could do for example:

  "operation": "http://mysite.com/{1}/{2}/{3}"
  1. Instead of hard-coding the method (post/put/get/delete) make it a property (enum) and make user pick it not in the function definition but where you reference it. That way you can reuse the same function definition in different states depending on what method user wants to call it with.

@cdavernas
Copy link
Member Author

@tsurdilo Great idea! I'm gonna get working then!

@JBBianchi
Copy link
Member

JBBianchi commented Jul 7, 2021

Not sure about the array thing of 1., the named parametters are less inclined to introduce errors. With arrays, the user needs to count, removing, adding or moving an item is trickier than with named ones.

@tsurdilo
Copy link
Contributor

tsurdilo commented Jul 7, 2021

@JBBianchi thats fair too, you could then make the "path" param an array of objects types that have name and value pairs if you wanted as well and have for example

  "operation": "http://mysite.com/{one}/{two}/{three}"

whatever you decide to do, this way you can with validation up-front tell user that "hey your function def needs 3 paths but you only defined 2" :)

@cdavernas
Copy link
Member Author

cdavernas commented Jul 9, 2021

@tsurdilo for the way I described path parameters, I used the OpenAPI way, which is verbose but, like @JBBianchi said, is IMHO somewhat cleaner and less error prone. Plus, it can be formatted as such by modern logging technologies and can be queried by Kibana, for instance.

As for the verb, you idea does make a lot of sense. However, that way, the FunctionDef has little if no purpose IMO. That's why I wanted to delegate to it as much responsibility as possible. Actually, I even believe it should have a copy of the arguments property, to set up configuration work common to all invocations (typically, headers or query param values). WDYT?

@cdavernas cdavernas added the change: feature New feature or request. Impacts in a minor version change label Jul 9, 2021
@cdavernas cdavernas self-assigned this Jul 9, 2021
@tsurdilo
Copy link
Contributor

tsurdilo commented Jul 9, 2021

@cdavernas I think that if you delegate specific invocation info to functiondef you are losing reusability. What you could do is define something like "invocationTemplates" meaning in the function definition you could add

 {
  "name": "...",
  "operation": "...",
  "templates": [
    {
      "type": "rest",
      "arguments": {...} 
    },
    {
      "type": "grpc",
      "arguments": {...} 
    },
    {
      "type": "graphql",
      "arguments": {...} 
    }
  ]
}

and then reference the template in the function ref and we can use {1}, {2} ... also to pass in params to the template.
wdyt?
If we just move the invocation info to function definition alone, then we remove reusability.
For functions that have a single type of invocation there is gonna be a single template and its really like what I think you are suggestion to do in those cases, but it helps with functions that might have multiple
invocation types, for example CRUD type of functions.
wdyt?

@cdavernas
Copy link
Member Author

cdavernas commented Jul 9, 2021

@tsurdilo I'm not sure we are speaking of the same thing. Basically, I wanted to add a configuration or whatever property to the functionDef where you could set headers, path, cookies and query parameters (strictly excluding body parameters, expected to be set, if need be, in the ref) that would be included (merged) into the functionRef arguments (which takes precedence and can therefore override values). That way, you could reuse a, say, POST function definition, with common headers values (UserAgent, PragmaNoCache, ...), for example, but with different payloads.

I certainly did not fully get your suggestion, because I can't really make sense of it. Providing templates to an operation of a specific kind seems indeed error prone. What if I set as operation a ref to a proto file but just defined/referenced a rest template that has nothing to do there?

That's why I'd only add the property in case operation type is set to rest. However, you could argue that grpc calls might need headers/path/cookie/query args too (for both def and ref), because they are not described in the proto file.

@cdavernas
Copy link
Member Author

@manuelstein @ricardozanini you are both awfully silent 😭
Any comments/ideas/suggestions? WYT about those ideas?

@tsurdilo
Copy link
Contributor

tsurdilo commented Jul 9, 2021

@cdavernas ok.from the example it looks as those are all "arguments" in the function def and that threw me off. I think separating that name as in arguments/parameters vs like headers or metadata that would help

@ricardozanini
Copy link
Member

ricardozanini commented Jul 13, 2021

I liked the idea and this will help us on Kogito to reuse some work :)

About the REST function definition per se, I like the JAX-RS approach of using annotations, we could leverage their docs to avoid reinventing the wheel. I remember proposing something like this in the past before having openapi functions. 🤔

{
    "name": "GetPetById",
    "operation": "/pets/{id}",
    "type": "rest",
    "metadata": {
         "verb": "GET"
    }
}

Usage:

{
  "name": "MyAction",
  "functionRef": {
       "refName": "GetPetById",
       "arguments": {
           "name": "id",
           "value": "$ .pet.id"
       }
   }

I'd let all the parameters defined in the function definition. Either in the metadata (worse) or create a new optional argument for parameter template/definition. Same for verb definition (GET by default, obviously). For POST/PUT operations, we can use the argument name body as default. In this case:

{
    "name": "NewPet",
    "operation": "/pets",
    "type": "rest",
    "metadata": {
         "verb": "POST"
    }
}

Usage:

{
  "name": "MyAction",
  "functionRef": {
       "refName": "NewPet",
       "arguments": {
           "name": "body",
           "value": "$ .pet"
       }
   }

Querystrings:

{
    "name": "SearchPets",
    "operation": "/pets?name={name}",
    "type": "rest",
    "metadata": {
         "verb": "GET"
    }
}

Header/Cookies we rely on parameter definition:

{
    "name": "Authenticate",
    "operation": "/auth",
    "type": "rest",
    "parameterDef": [{
         "type": "header",
         "name": "BasicAuth",
         "value": "$.creds"
     }],
    "metadata": {
         "verb": "POST"
    }
}

@tsurdilo tsurdilo added this to the v0.8 milestone Aug 18, 2021
@tsurdilo
Copy link
Contributor

@cdavernas should we move on this?

@cdavernas
Copy link
Member Author

@tsurdilo with pleasure!

@cdavernas
Copy link
Member Author

cdavernas commented Sep 21, 2021

@ricardozanini Even though I like what you proposed, it seems a bit too loose to my taste, especially in the case of query strings. I'm not opposed to doing it that way, but woul definitly prefer something like what OpenAPI does, meaning allowing to specifiy the location of an argument (i.e. in:query).
For example you could very well call twice the same REST function, once with a query string param, the second time without. Your proposal wouldn't allow that, AFAIK.

WDYT?

@ricardozanini
Copy link
Member

@cdavernas that proposal was just a dump from my brain that I come up with in five minutes. We can definitely improve it.

About calling the GET REST function twice without the parameter, you can just do this:

{
  "name": "SearchPets1",
  "functionRef": {
       "refName": "SearchPets",
       "arguments": {
           "name": "name",
           "value": "$ .pet.name"
       }
}
{
  "name": "SearchPets2",
  "functionRef": {
       "refName": "SearchPets"
   }
}

Without the argument, we can make the call without the querystring.

But I agree that we should enforce/make it more reliable instead of using metadata fields for everything.

@github-actions
Copy link

github-actions bot commented Nov 6, 2021

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@tsurdilo tsurdilo modified the milestones: v0.8, v0.9 Dec 4, 2021
@github-actions
Copy link

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@JBBianchi
Copy link
Member

JBBianchi commented Mar 4, 2022

Sorry for the late reply, I tend to agree with the principle with @ricardozanini but I also agree with @cdavernas remark.

Using a well established standard synthax, like the one of OpenAPI, is:

  1. easy for users because it's a well known/documented standard
  2. easy for the implementers because they already implemented OpenAPI

To summarize, the Function Definition should described the endpoint and its possible arguments:

{
   "functions":[
      {
         "name":"get-my-pet",
         "type":"rest",
         "operation":"https://petstore.swagger.io/pet/{petId}",
         "metadata":{
            "get":{
               "parameters":[
                  {
                     "name":"petId",
                     "in":"path",
                     "description":"ID of pet to return",
                     "required":true,
                     "type":"integer",
                     "format":"int64"
                  }
               ]
            }
         }
      },
      {
         "name":"update-my-pet",
         "type":"rest",
         "operation":"https://petstore.swagger.io/pet/{petId}",
         "metadata":{
            "post":{
               "parameters":[
                  {
                     "name":"petId",
                     "in":"path",
                     "description":"ID of pet that needs to be updated",
                     "required":true,
                     "type":"integer",
                     "format":"int64"
                  },
                  {
                     "name":"name",
                     "in":"formData",
                     "description":"Updated name of the pet",
                     "required":false,
                     "type":"string"
                  },
                  {
                     "name":"status",
                     "in":"formData",
                     "description":"Updated status of the pet",
                     "required":false,
                     "type":"string"
                  }
               ]
            }
         }
      }
   ]
}

And then used by a Function Reference like any other:

{
   "actions":[
      {
         "name":"Get My Pet",
         "functionRef":{
            "refName":"get-my-pet",
            "arguments":{
               "petId":"${ .petId }"
            }
         },
         "actionDataFilter":{
            "toStateData":"${ .pet }"
         }
      },
      {
         "name":"Update My Pet",
         "functionRef":{
            "refName":"update-my-pet",
            "arguments":{
               "petId":"${ .petId }",
               "name":"${ .pet.name }",
               "status":"sleeping"
            }
         },
         "actionDataFilter":{
            "useResults":false
         }
      }
   ]
}

WDYT?

@ricardozanini
Copy link
Member

Using a well established standard synthax, like the one of OpenAPI, is:

@JBBianchi, do you propose to copy the OpenAPI scheme? I think we all agree that we should rely on standards, so I'm happy if we embrace either JAX-RS or OpenAPI. We just have to establish the boundaries and what we support.

This should be a "lightweight" version of the standard. We recommend interacting with legacy REST endpoints that do not offer an OpenAPI spec.

To be honest, what I did in the past was to create the OpenAPI spec myself and feed it to the engine. See this: https://apitransform.com/how-to-convert-postman-collection-to-openapi/.

I'm trying to say that instead of importing the OpenAPI standard to our metadata field, we point people to generate the OpenAPI spec even if the legacy endpoint does not offer one. Today is pretty straightforward to do it. So that could be a suitable option IMO.

@github-actions
Copy link

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

ricardozanini added a commit to ricardozanini/sw-specification that referenced this issue Oct 10, 2022
…n based on OpenAPI Path Item

Signed-off-by: Ricardo Zanini <zanini@redhat.com>
@github-actions
Copy link

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@github-actions
Copy link

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@github-actions
Copy link

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@github-actions
Copy link

github-actions bot commented Apr 4, 2023

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@github-actions
Copy link

github-actions bot commented Jul 8, 2023

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

ricardozanini added a commit to ricardozanini/sw-specification that referenced this issue Oct 18, 2023
…n based on OpenAPI Path Item

Signed-off-by: Ricardo Zanini <zanini@redhat.com>
ricardozanini added a commit that referenced this issue Oct 19, 2023
Fixes #420 - Add REST invocation function definition based on OpenAPI Path Object
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
change: feature New feature or request. Impacts in a minor version change
Projects
Status: Done
5 participants