Skip to content
This repository has been archived by the owner on Nov 8, 2024. It is now read-only.

Add support for defining hypermedia affordances #19

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog
All notable changes to the Refract project will be documented in this file.

## Master
- Add support for [affordances](https://github.com/apiaryio/api-elements/issues/18)

## [1.0.0-rc1]
- Initial production-ready version of API Elements
55 changes: 55 additions & 0 deletions docs/element-definitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,10 @@ Note: At the moment only the HTTP protocol is supported.
The value of `relation` attribute SHOULD be interpreted as a link relation
between transition's parent resource and the transition's target resource
as specified in the `href` attribute.

If a custom relation is being used, it SHOULD be defined using an
Affordance Element, and the value of the `relation` property SHOULD be
the `id` of the defined affordance.

- `href` (Templated Href) - The URI template for this transition.

Expand All @@ -211,6 +215,7 @@ Note: At the moment only the HTTP protocol is supported.
Definition of any input message-body attribute for this transition.

- `contentTypes` (array[String]) - A collection of content types that MAY be used for the transition.

- `content` (array)
- (Copy) - Transition description's copy text.
- (HTTP Transaction) - An instance of transaction example.
Expand All @@ -234,6 +239,49 @@ Note: At the moment only the HTTP protocol is supported.
}
```

### Affordance (Base API Element)

An affordance element provides a way to provide affordance definitions for the
API. These definitions can describe link relations used in a Transition Element,
along with defining unsafe and idempotent transitions.

While data structures are used for defining semantics around data models,
affordances are used for defining semantics around hypermedia state transitions.

The control types used here are based on the types used in [ALPS][]. The meta
property `id` SHOULD be used to define the machine-usable name of the
affordance.

#### Properties

- `element`: affordance
- `attributes`
- `controlType` (enum)
- safe - A hypermedia control that triggers a safe, idempotent state
transition (e.g. HTTP.GET or HTTP.HEAD).
- unsafe - A hypermedia control that triggers an unsafe, non-idempotent
state transition (e.g. HTTP.POST).
- idempotent - A hypermedia control that triggers an unsafe, idempotent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the names here are good. Both GET and DELETE are idempotent. So, just saying idempotent is not enough.

What if we make controlType an array of enum?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this borrows from what has already been done by Mike Amundsen and others in ALPS. It appears they get around this with overloading the "idempotent" term to mean an "unsafe, idempotent" control. This would mean that you would use safe for GET and idempotent for DELETE. Conversely, idempotent in this scenario would not be applied to GET.

I'm going to reach out to Mike and others about this, because I think you bring up a good point. While what we have solves the problem you mention, it is a little confusing to say "idempotent" must also be "unsafe." It may be better to do an enum and let users decide how they describe it.

But one note on that—it may require that idempotent always has either safe or unsafe with it if this is to support automatically mapping to other protocols. I'm betting that's why they went the direction they did.

I'll post what I find here and we can chat about where to go from there.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are conversing through emails, I wouldn't mind being cced on them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pksunkara in my email, I listed two options we have in addition to the ALPS way:

  1. An array of enum (like you said)
  2. Two boolean attributes: one for safety, one for idempotency

Another option is to make up our own terms and categories. I just mention that here because it might be something good to just think about. However I can't come up with anything right now :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pksunkara per my discussions on the mailing list, I think it would be best to remove this controlType attribute and think through it in another issue/PR. Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think the way they are overriding the keywords unsafe and idempotent is confusing and shouldn't be done. I actually like your proposal of boolean attributes. My only concern regarding that is whether "safe + non-idempotent" exists and/or makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually curious if there is something we could do differently with this to make it better. I think I agree with your concern here and think we should move away from it. I have a few thoughts on that along with yours, and I think it would be better to discuss specifically rather than just here in comments.

Thanks for bringing this up. Good catch on this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am okay with separating the discussion about this. Please feel free to create an issue and remove the relevant parts from here.

state transition (e.g. HTTP.PUT or HTTP.DELETE).
- `content` (array)
- (Copy)

#### Example

```json
{
"element": "affordance",
"meta": {
"id": "self",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the id is to be unique across the whole API Elements document, I wonder if a better name could be used in this example than self. Self probably would not be unique across multiple resources defined across API Elements document.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment below.

"title": "Self"
},
"attributes": {
"controlType": "safe"
},
"content": []
}
```

### Category (Base API Element)

Grouping element – a set of elements forming a logical unit of an API such as
Expand All @@ -260,6 +308,7 @@ transitions.
- scenario - Category is set of steps.
- transitions - Category is a group of transitions.
- authSchemes - Category is a group of authentication and authorization scheme definitions
- affordances - Category is a group of affordances
- `attributes`
- `meta` (array[Member Element]) - Arbitrary metadata

Expand Down Expand Up @@ -531,6 +580,11 @@ HTTP response message.
- `element`: httpResponse (string, fixed)
- `attributes`
- `statusCode` (number) - HTTP response status code.
- `content` (array)
- (Affordance) - Expected affordance for the given payload

The Ref Element MAY be used to reference existing affordances defined
elsewhere.

## Data Structure Elements

Expand Down Expand Up @@ -1756,6 +1810,7 @@ These elements and definitions are referenced as part of the base Refract specif
[MSON]: https://github.com/apiaryio/mson
[MSON Reference]: https://github.com/apiaryio/mson/blob/master/MSON%20Reference.md
[Refract]: https://github.com/refractproject/refract-spec/blob/master/refract-spec.md
[ALPS]: https://tools.ietf.org/html/draft-amundsen-richardson-foster-alps-01#section-2.2.12

[API Description Elements]: definitions/api-description-elements.md
[Data Structure Elements]: definitions/data-structure-elements.md
Expand Down
6 changes: 6 additions & 0 deletions docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ It is also helpful to know the relationship between elements. The list below sho
- Data Structure
- Category (Group of Resource Elements)
- Category (Group of Authentication and Authorization Scheme Definitions)
- Category (Group of Affordance Elements)
- Resource
- Copy
- Data Structure
Expand All @@ -52,6 +53,7 @@ It is also helpful to know the relationship between elements. The list below sho
- HTTP Response
- Copy
- Data Structure
- Affordance
- Asset

This main API Category element MAY also be wrapped in a Parse Result element for conveying parsing information, such as source maps, warnings, and errors.
Expand Down Expand Up @@ -104,6 +106,10 @@ const transaction = query(apielements, {element: 'httpTransaction'})[0];

Given that API Elements use [Refract][], the structure of the document is recursive by nature. When looking for specific elements, it is best then to walk the tree to look for a match. Querying the tree means that your code will be decoupled not only from specific API description documents, but it will also be decoupled from the structure of those documents.

## Managing Compatibility

When parsing an API Elements document, parsers should safely ignore any elements that were not in the API Elements specification at the time of implementation. This allows for the specification to evolve using semantic versioning and introduce non-breaking changes.

[Refract]: https://github.com/refractproject/refract-spec/blob/master/refract-spec.md
[MSON]: https://github.com/apiaryio/mson
[RFC 2119]: https://datatracker.ietf.org/doc/rfc2119/