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

Is it OK to not have output specified in a profile TD #259

Closed
danielpeintner opened this issue Aug 9, 2022 · 7 comments
Closed

Is it OK to not have output specified in a profile TD #259

danielpeintner opened this issue Aug 9, 2022 · 7 comments
Assignees
Labels
P1 Priority 1 to be discussed (e.g., next week) Profile-1.0

Comments

@danielpeintner
Copy link
Contributor

The examples (e.g,. Example 3) describe actions without describing output .

  "actions": {
    "fade": {
      "title": "Fade",
      "description": "Fade the lamp to a given level",
      "input": {
        "type": "object",
        "properties": {
          "level": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "unit": "percent"
          },
          "duration": {
            "type": "integer",
            "minimum": 0,
            "unit": "milliseconds"
          }
        }
      },
      "forms": [{"href": "actions/fade"}]
    }
  }

The result of an action in the Profile is always ActionStatus. The Profile TD does not describe that situation using output .
I understand the idea to reduce TD verbosity but wouldn't it be better to have at least

      "output": {
        "type": "object"
      }

A generic implementation of a WoT runtime otherwise would not expect any result.

Side-note: It may depend on the interpretation but 7.2.5.1 Mandatory fields mentions only title being mandatory. Is this correct? Don't we always need output ... even if being implicit?

@benfrancis
Copy link
Member

benfrancis commented Aug 10, 2022

This is a good question because I think it highlights some of the shortcomings in Thing Descriptions that the Profile work has uncovered.

The first thing to say is that a WoT Consumer which doesn't implement the protocol binding in the HTTP Baseline Profile unfortunately won't be able to consume all of the actions API it describes. This is because it's not possible to completely describe it in a Thing Description (see the long discussions in w3c/wot-thing-description#1408, w3c/wot-thing-description#899, #91 and others). I tried in #91 (see rendered example here) but I wasn't able to figure out a way to describe the action queue due to missing features in the Thing Description specification.

Any WoT Consumer should be able to invoke an action on a Thing which conforms with the HTTP Baseline Profile, but it may not be able to read its output and definitely won't know how to query the status of an ongoing asynchronous action unless it explicitly implements the protocol binding defined in the HTTP Baseline Profile.

To address your specific question, the reason there is no output for the action in the example Thing Description is that the fade action in the example has no output. That is to say, if you think of the action as a function then the return type of the function is void.

We could add an example of an action which does have an output (e.g. an integer) to make it clear what that would look like.

Note that I am differentiating here between the output of the action and the response to the invokeaction HTTP POST request in the protocol binding, because I don't think they're the same thing. In the protocol binding the output of an action (if any) may be contained in the response (in the output member of the ActionStatus object), but it may not.

The following four examples are all valid responses to an invokeaction request in the HTTP Baseline Profile. One of them contains an action output, but the others do not.

{
  "status": "pending",
  "href": "/things/lamp/actions/fade/123e4567-e89b-12d3-a456-426655"
}
{
  "status": "completed"
}
{
  "status": "failed"
}
{
  "status": "completed",
  "output": 42
}

A key missing feature needed to describe the data schema of a response, as distinct from the data schema of the output of an action, is a schema term in ExpectedResponse. There was a PR for this feature, but it was postponed until 2.0.

In conclusion:

  1. Yes it's OK for for an action not to have an output in a Thing Description conforming with the HTTP Baseline Profile
  2. We could add an example which does have an action with an output to show that this is possible
  3. A WoT Consumer will not be able to consume all of the actions API of a Thing conforming with the HTTP Baseline Profile, unless it explicitly implements the protocol binding from the specification, and I think that's OK. Profiles can provide a shorthand way of expressing complex protocol bindings.

@danielpeintner
Copy link
Contributor Author

To address your specific question, the reason there is no output for the action in the example Thing Description is that the fade action in the example has no output. That is to say, if you think of the action as a function then the return type of the function is void.

I think your statement helps me to see the misunderstanding (or different understanding) I am having here.

From the TD point of view output should describe the result one gets back once an action is invoked and not the possible output a given profile wants to report as actual result.
Since the Profile is a subset of the TD it should be compliant and follow the same rules... I think.

Hence output might be something like the following (missing enums for status et cetera)

	"output": {
		"type": "object",
		"properties": {
			"status": {
				"type": "string"
			},
			"output": {},
			"error": {
				"type": "object"
			},
			"href": {
				"type": "string"
			},
			"timeRequested": {
				"type": "string"
			},
			"timeEnded": {
				"type": "string"
			}
		},
		"required": ["status"]
	}

Note: there are two output. I think you are talking about the inner output while I am talking about the outer output.

Do you see what I mean?

@benfrancis
Copy link
Member

benfrancis commented Aug 10, 2022

@danielpeintner wrote:

Since the Profile is a subset of the TD it should be compliant and follow the same rules... I think.

The HTTP Baseline Profile is not just a subset of the TD (see #243). The profile includes operations in the actions protocol binding which can not be described declaratively in a Thing Description using a protocol binding template.

There was a work item in the current charter to fix this:

This work item will document the behavior of complex interactions, including:

  1. The ability to initiate, monitor, and cancel ongoing actions.
  2. Support for action and event queues.
  3. Error recovery via hypermedia responses.

Unfortunately, whilst the first point was arguably delivered by the (at risk) queryaction and cancelaction operations in the Thing Description specification, for the second point it's still not possible to use them to fully describe an asynchronous action queue like the one used in the HTTP Baseline Profile (or brownfield APIs like the Web Thing API).

Whilst I agree it would be neat if everything in a profile could alternatively be described declaratively in a Thing Description (as I tried to do in #91) so that even non-conformant Consumers could fully consume them, it's not strictly necessary. Profiles can go beyond what's possible with declarative protocol binding templates, and non-conformant Consumers will still be able to consume a subset of their functionality by just following the already established defaults in the Thing Description specification.

Note: there are two output. I think you are talking about the inner output while I am talking about the outer output.
Do you see what I mean?

Yes, I see what you mean, and yes I am talking about the inner output in your example. But I think your example conflates the data schema of the action output with the protocol binding. What if that same action has multiple forms for different protocols with different payload formats?

Events are another example of this. The HTTP SSE Profile includes the event data in the data field of an SSE event, e.g.

event: overheatedl\n
data: 42\n
id: 2021-11-17T15:33:20.827Z\n\n

The (WIP) HTTP Webhook Profile provides event data in the data member of a JSON event payload, e.g.

{
  "source": "http://192.168.0.124:8080/events/overheated",
  "time": "2021-11-10T11:43:25.135Z",
  "data": 96
}

In both cases, the event data following the data schema for the event provided in the Thing Description is only part of the whole payload, which differs between protocol bindings.

Another example would be a WebSocket sub-protocol where there are no built-in semantics at all and every operation will need a payload format which wraps around the actual data described in the data schema.

Do you see the problem? In my view these are the kinds of limitations of Thing Descriptions that Profiles can help overcome, using concrete protocol bindings defined in a specification.

@danielpeintner
Copy link
Contributor Author

I am still not sure if some of those things we are discussing here should be allowed or used in a profile.
@sebastiankb @egekorkan any input/advice ?

@sebastiankb
Copy link
Contributor

I think what is missing here is the information wheather the action is asynchronous (in TD synchronous=false can be used).

In combination of the Profile and the asynchronous flag implicit assumption can be made such as that the input dataSchema is identical to 'inner' output dataSchema in addition to the mandatory status term and the optional href term. However, based on the example above this is not the case since it returns only a (level?) value without any context. I think, this is useless for the client application since it has no context what the inner output value means.

I think, there are several options in the BaselineProfile with the asynchronous actions context:

  1. make an implicit assumption that the input dataSchema == 'inner' output dataSchema, and consumer also implicit knows there is a special payload structure followed that include status, (optional) href, and optional 'inner' output value.
  2. make an implicit assumption that the output dataSchema definition at the action level will describe the 'inner' output. The consumer implicit knows there is a special payload structure followed that also include status, optional href, and optional 'inner' output value.
  3. describe the full payload in the output dataSchema definition at the action level. status should be defined as required and href and output optional. Based on the (profile) context, the consumer implicit knows how to interpret the payload message.
  4. use different variations of the expectedResponses with different schema definitions

@benfrancis
Copy link
Member

@sebastiankb wrote:

I think what is missing here is the information wheather the action is asynchronous (in TD synchronous=false can be used).

Good idea, I can create a PR for that.

  1. make an implicit assumption that the input dataSchema == 'inner' output dataSchema, and consumer also implicit knows there is a special payload structure followed that include status, (optional) href, and optional 'inner' output value.

I'm not sure I understand. In Example 3 the input schema is an object with level and duration fields. This would not map to the output member of the ActionStatus object because it is input, not output. If the action returned an output it would most likely be different to the input.

  1. make an implicit assumption that the output dataSchema definition at the action level will describe the 'inner' output. The consumer implicit knows there is a special payload structure followed that also include status, optional href, and optional 'inner' output value.

If I understand correctly, this is what we do today.

  1. describe the full payload in the output dataSchema definition at the action level. status should be defined as required and href and output optional. Based on the (profile) context, the consumer implicit knows how to interpret the payload message.

We could do that for actions, yes. That would mean that every Thing Description which conforms with the profile would have to include that duplicate wrapper data schema in every ActionAffordance. In my view a goal of profiles is to avoid the need for these verbose declarative protocol bindings by providing a concrete protocol binding in a specification which applies a set of assumptions for conforming implementations. Also, I don't think this approach would not work for events, since different protocol bindings use different payload structures.

  1. use different variations of the expectedResponses with different schema definitions

There is only one ExpectedResponse. Do you mean AdditionalExpectedResponses in the additionalResponses member? Would this mean leaving output blank? IMO it would be better if ExpectedResponse included a schema member like AdditionalExpectedResponse (see w3c/wot-thing-description#1053).


I do agree with @danielpeintner in that that I think it would be nice if Consumers which don't implement the profile could understand the output of actions, at least for synchronous actions where this is possible.

One other option is that we change the protocol binding so that when synchronous: true the response is just the action output with no ActionStatus wrapper. When synchronous: false though, I think you have got to assume that the output schema does not map onto the response to the invokeaction request, because that's basically what asynchronous means (I'm going to file an issue to clarify this in the Thing Description specification).

The downside of having synchronous actions just return an output would be that you lose:

  • status - so it assumes all actions either complete or fail before the HTTP response comes back
  • error - Errors would have to be a separate response with an HTTP error code
  • timeRequested - Not important because the Consumer already knows when it invoked the action
  • timeEnded - Not important because the action can be assumed to have ended when the HTTP response arrives

I think that would be OK. It would just mean that synchronous actions which don't complete within the duration of an HTTP timeout would have to return a 408 Request Timeout response.

Synchronous Action

TD:

  "actions": {
    "collectWidgets": {
      "description": "Collect all the widgets",
      "synchronous": true
      "output": {
        "type": "object",
        "properties": {
          "widgetsCollected": {
            "type": "integer"
          },
          "timeTaken": {
            "type": "number",
            "unit": "seconds"
          }
        }
      },
      "forms": [{"href": "/actions/collectWidgets"}]
    }
  }

Request:

POST /actions/collectWidgets
Host: mythingserver.com
Accept: application/json

Response:

HTTP/1.1 200 OK
Content-Type: application/json
{
  "widgetsCollected": 7
  "timeTaken": 30
}

Asynchronous Action

TD:

  "actions": {
    "collectWidgets": {
      "description": "Collect all the widgets",
      "synchronous": false
      "output": {
        "type": "object",
        "properties": {
          "widgetsCollected": {
            "type": "integer"
          },
          "timeTaken": {
            "type": "number",
            "unit": "seconds"
          }
        }
      },
      "forms": [{"href": "/actions/collectWidgets"}]
    }
  }

Request:

POST /actions/collectWidgets
Host: mythingserver.com
Accept: application/json

Response:

HTTP/1.1 201 CREATED
Content-Type: application/json
Location: /actions/collectWidgets/123e4567-e89b-12d3-a456-426655
{
  "status": "pending",
  "href": "/actions/collectWidgets/123e4567-e89b-12d3-a456-426655",
  "timeRequested": "2021-11-10T11:43:19.135Z"
}

I can create a PR which:

  • Adds use of the use of the synchronous member of ActionAffordance to distinguish between synchronous and asynchronous actions (this will also help clear up a current ambiguity about whether the same ActionAffordance can provide both synchronous and asynchoronous responses)
  • Adds an example of an action with an output

Do you think I should also change the protocol binding for synchronous actions, as described above?

@benfrancis
Copy link
Member

@danielpeintner @sebastiankb I've created a draft PR (#266) which uses the synchronous member of an Action Affordance, and changes the response of a synchronous action response to contain only the action output, as discussed above. It still needs additional examples but hopefully this helps illustrate what the change might look like.

@mlagally mlagally added Profile-1.0 P1 Priority 1 to be discussed (e.g., next week) labels Sep 7, 2022
@benfrancis benfrancis self-assigned this Sep 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
P1 Priority 1 to be discussed (e.g., next week) Profile-1.0
Projects
None yet
Development

No branches or pull requests

4 participants