Hypermedia Controls in Cortex
Hypermedia controls provide information on what is required to change the state of a resource and the links to invoke the change. See Martin Fowler’s article on the Richardson REST Maturity Model for a more detailed definition of hypermedia controls.
Cortex has four key hypermedia controls:
- Lists
- Forms
- Selectors
- NeedInfo
- Structured Messages
Lists
A list is a collection resource that returns ordered links to other resources. Cortex lists follow this format:
- An element link for each resource in the list
- A type set to
elasticpath.collections.links
This type indicates there are no properties associated with the list, only links to other resources
lineitems
are a list resource that contain links to each lineitem in the shopper’s cart. In the lineitems resource, each lineitem is represented as an element link with a type set to elasticpath.collections.links
:
{
"self": {
"type": "elasticpath.collections.links",
"uri": "/carts/mobee/guz=/lineitems",
"href": "http://api.elasticpath.net/cortex/carts/mobee/guz=/lineitems"
},
"links": [
{
"rel": "element",
"rev": "list",
"type": "elasticpath.carts.line-item",
"uri": "/carts/mobee/guz=/lineitems/hfq=",
"href": "http://api.elasticpath.net/cortex/carts/mobee/guz=/lineitems/hfq="
},
{
"rel": "element",
"rev": "list",
"type": "elasticpath.carts.line-item",
"uri": "/carts/mobee/guz=/lineitems/gbq=",
"href": "http://api.elasticpath.net/cortex/carts/mobee/guz=/lineitems/gbq="
},
{
"rel": "cart",
"rev": "lineitems",
"type": "elasticpath.carts.cart",
"uri": "/carts/mobee/guz=",
"href": "http://api.elasticpath.net/cortex/carts/mobee/guz="
}
]
}
Pagination
Lists can be paginated when large numbers of links return. Cortex paginated lists follow this format:
- An element link for each resource on a page
- A next link to retrieve the next page of links
- A previous link to retrieve the previous page of links
- A type set to elasticpath.collections.pagination, which indicates the list is paginated
- A pagination object with the following fields:
current
: The current page numberpage-size
: The number of links on each page, excluding the next/previous pagination linkspages
: The total number of pagesresults
: The total number of links, excluding the next/previous pagination linksresults-on-page
: The number of links on the current page, excluding the next/previous pagination links
An item keyword search returns a paginated list resource that links to each item found by the keyword search:
{
"self": {
"type": "elasticpath.collections.pagination",
"uri": "/searches/mobee/keywords/items/m43g=/pages/2",
"href": "http://api.elasticpath.net/cortex/searches/mobee/keywords/items/m43g=/pages/2"
},
"pagination": {
"current": 2,
"page-size": 5,
"pages": 6,
"results": 29,
"results-on-page": 5
},
"links": [
{
"rel": "element",
"type": "elasticpath.items.item",
"uri": "/items/mobee/m5yi=",
"href": "http://api.elasticpath.net/cortex/items/mobee/m5yi="
},
{
"rel": "element",
"type": "elasticpath.items.item",
"uri": "/items/mobee/m5ypi=",
"href": "http://api.elasticpath.net/cortex/items/mobee/m5ypi="
},
{
"rel": "element",
"type": "elasticpath.items.item",
"uri": "/items/mobee/m5yq=",
"href": "http://api.elasticpath.net/cortex/items/mobee/m5yq="
},
{
"rel": "element",
"type": "elasticpath.items.item",
"uri": "/items/mobee/m5ya=",
"href": "http://api.elasticpath.net/cortex/items/mobee/m5ya="
},
{
"rel": "element",
"type": "elasticpath.items.item",
"uri": "/items/mobee/m5y6=",
"href": "http://api.elasticpath.net/cortex/items/mobee/m5y6="
},
{
"rel": "previous",
"type": "elasticpath.collections.pagination",
"uri": "/searches/mobee/keywords/items/m43g=/pages/1",
"href": "http://api.elasticpath.net/cortex/searches/mobee/keywords/items/m43g=/pages/1"
},
{
"rel": "next",
"type": "elasticpath.collections.pagination",
"uri": "/searches/mobee/keywords/items/m43g=/pages/3",
"href": "http://api.elasticpath.net/cortex/searches/mobee/keywords/items/m43g=/pages/3"
}
]
}
Forms
Forms are a hypermedia control that creates new resources. Forms are generally rendered on the UI as input fields or submit buttons. With forms, you can discover:
- The properties required to create a resource
- The link to invoke the creation of a resource
Cortex forms follow this format:
- A type that indicates the properties that will be created in the resource
- Properties that define the data required to create a resource Properties are optional, as some resources don’t require data to create them
- Links, called action links, invoke the creation of a resource If an action link doesn’t exist on the form, the resource cannot be created
Example: Adding Item to Cart
The items
resource’s addtocartform
form link enables you to add the item to a cart, which means a new cart lineitem
resource is created for the item. The form’s type application/vnd.elasticpath.cart.lineitem
indicates the form creates a cart lineitem
resource:
{
"rel": "addtocartform",
"type": "elasticpath.carts.line-item",
"uri": "/carts/items/mobee/m5yxi=/form",
"href": "http://api.elasticpath.net/cortex/carts/items/mobee/m5yxi=/form"
}
Retrieving the addtocartform form link returns the form resource, which lists the properties and actions available for the form. In this example, the form contains:
quantity
- this property indicates the item’s quantity must be specified before the item can be added to cartaddtodefaultcart
- this action link adds the item to the shopper’s cart
{
"self": {
"type": "elasticpath.carts.line-item",
"uri": "/carts/items/mobee/m5yxi=/form",
"href": "http://api.elasticpath.net/cortex/carts/items/mobee/m5yxi=/form"
},
"quantity": 0
"links": [
{
"rel": "addtodefaultcartaction",
"uri": "/carts/mobee/default/lineitems/items/mobee/m5yxi=",
"href": "http://api.elasticpath.net/cortex/carts/mobee/default/lineitems/items/mobee/m5yxi="
}
]
}
To add the item to the cart, construct a POST
request using the form fields as the request body and the URL defined by the action link:
POST /carts/mobee/default/lineitems/items/mobee/m5yxi=
Content-Type: application/json
{
"quantity" : 1
}
If the action succeeds, Cortex returns status code 201 CREATED
and a Location header with a reference to the newly created resource. If the action succeeds but results in modifying an existing resource rather than creating a new resource, then Cortex returns status code 200 OK
and a Location header with a reference to the modified resource.
201 CREATED
Location: http://api.elasticpath.net/cortex/carts/mobee/mjt=/lineitems/gyz=
Forms can be used with the FollowLocation request parameter to reduce the number of calls required to retrieve a newly created resource. Followlocation instructs Cortex to retrieve the resource referenced in the Location header and return it in the response body.
Selectors
Selectors are a hypermedia control that change a resource’s state. Selectors provide a list of predefined options that a shopper can choose from. Once a choice is made through the selector, the resource’s state updates accordingly. Selectors are generally rendered on the UI as radio button groups, combo boxes, or check boxes.
With selectors, you can discover:
- The options available for a resource
- The options already selected for a resource
- The
selection-rule
that determines how many options can be selected at once - The links to make the selection
Cortex selectors follow this format:
- A
name
property that identifies which options the selector handles - A
selection-rule
property that identifies the number of options that can be selected at once - A
choice
link for options that can be selected - A
chosen
link for options that are already selected
Cortex choice
and chosen
follow this format:
- A description link that links to a description of the option
- A selectaction link that when followed selects the option The link appears only on choice resources as chosen resources are already selected
Example: Selecting a Billing Address
The billingaddressinfo
resource contains a selector
that allows shoppers to select their billing address for an order. The selector provides a list of addresses shoppers have previously saved to their account from which the shopper can choose from. The selector’s type is elasticpath.controls.selector
, which indicates the resource is a selector control:
{
"rel": "selector",
"rev": "billingaddressinfo",
"type": "elasticpath.controls.selector",
"uri": "/orders/mobee/mu3=/billingaddressinfo/selector",
"href": "http://api.elasticpath.net/cortex/orders/mobee/mu3=/billingaddressinfo/selector"
}
NeedInfo
Note: As of Elastic Path 7.1, NeedInfos are provided for backwards compatability with older Cortex API resources. To implement blocking functionality on a form, use Advisors.
NeedInfo is a hypermedia control that prevent a resource from changing its state until a set of conditions are satisfied. Needinfos are generally used to control a button’s enabled state on the user interface.
Example: Completing a Purchase
The purchase form resource creates a purchase. The submitorderaction
link appears on the purchase form when the purchases preconditions are satisfied and a purchase can be placed. Below is a purchase form with its preconditions satisfied.
{
"self": {
"type": "elasticpath.purchases.purchase",
"uri": "/purchases/orders/mobee/mu3=/form",
"href": "http://api.elasticpath.net/cortex/purchases/orders/mobee/mu3=/form"
}
"links": [
{
"rel": "submitorderaction",
"uri": "/purchases/orders/mobee/mu3=",
"href": "http://api.elasticpath.net/cortex/purchases/orders/mobee/mu3="
}
]
}
If a purchases preconditions are not satisfied and more information is required from the shopper, needinfo
links appear instead of the submitorderaction
link for each piece of missing information. needinfo
s points to an info resource that allows the shopper to select or create the missing data. The example below shows needinfo
s appearing on a purchase form.
{
"self": {
"type": "elasticpath.purchases.purchase",
"uri": "/purchases/orders/mobee/mu3=/form",
"href": "http://api.elasticpath.net/cortex/purchases/orders/mobee/mu3=/form"
}
"links": [
{
"rel": "needinfo",
"type": "elasticpath.controls.info",
"uri": "/orders/mobee/grr=/billingaddressinfo",
"href": "http://api.elasticpath.net/cortex/orders/mobee/grr=/billingaddressinfo"
},
{
"rel": "needinfo",
"type": "elasticpath.controls.info",
"uri": "/orders/mobee/grr=/emailinfo",
"href": "http://api.elasticpath.net/cortex/orders/mobee/grr=/emailinfo"
}
]
}
Structured Messages
Structured messages are returned in the messages
array in a Cortex response and are used to communicate the following types of conditions:
Input validation errors
Request Methods:
POST
,PUT
HTTP Response Status Code:
400 Bad Request
Business state errors
Request Methods:
POST
,PUT
,DELETE
HTTP Response Status Code:
409 Conflict
Blocking conditions
Request Method:
GET
HTTP Response Status Code:
200 OK
Request Method:
POST
HTTP Response Status Code:
409 Conflict
Informational messages:
Request Methods:
GET
HTTP Response Status Code:
200 OK
Structured messages are created by validation constraints and business rules in the service layer, and by advisors in the API prototype layer. Structured messages are very flexible and can be used to represent a variety of conditions based on the type of the message and how it was generated.
Message Fields
type
The type of the message.
"type": "error"
id
A unique identifier for the message condition.
"id": "field.required"
debug-message
A text description of the error as a debugging convenience.
"debug-message": "given-name is required"
data
A set of name/value pairs to convey message-specific data.
"data": {
"field-name": "given-name"
}
blocks
Optional field. The action that is blocked by the message condition.
"blocks": {
"rel": "submitorderaction"
}
linked-to
Optional field. A resource for users to act upon to resolve the condition.
"linked-to": {
"uri": "/orders/{scope}/{id}/emailinfo",
"href": "https://{host}/{context}/orders/...",
"type": "controls.info"
}
Example Structured Message
Here is an example the messages returned when attempting to register a new account with an invalid password and email address.
400 Bad Request
{
"links": [],
"messages": [
{
"type": "error",
"id": "field.invalid.size",
"debug-message": "Password must be between 8 to 255 characters inclusive",
"data": {
"field-name": "password",
"min": "8",
"max": "255"
}
},
{
"type": "error",
"id": "field.invalid.email.format",
"debug-message": "not a well-formed email address",
"data": {
"field-name": "email",
"invalid-value": "bar"
}
}
],
"entity": {}
}
Error Messages
Structured messages with a type field of error
are generated in Cortex from field validation errors, business state errors, and non-resolvable blocking conditions.
Field Validation Errors
Field validation errors are generated from service layer constraint violations. Their characteristics are:
- They are returned on
POST
andPUT
operations with Status Code400 Bad Request
- The
type
iserror
- The
id
begins withfield.
- The
data
field always contains afield-name
entry, and may contain other entries depending on the error - There are no
blocks
orlinked-to
fields
Example:
{
"type": "error",
"id": "field.invalid.size",
"debug-message": "Password must be between 8 to 255 characters inclusive",
"data": {
"field-name": "password",
"min": "8",
"max": "255"
}
}
See Field Validation Errors for a list of messages returned by Cortex.
Business State Errors
Business state errors are generated by service layer business rules. Their characteristics are:
- They are returned on
POST
,PUT
andDELETE
operations with Status Code409 Conflict
- The
type
iserror
- The
data
field may contain entries that provide additional business context - There may be
blocks
orlinked-to
fields if the error is resolvable by the user
Example:
{
"type": "error",
"id": "cart.item.not.available",
"debug-message": "Item '{item-code}' is not available for purchase.",
"linked-to": {
"uri": "/availabilities/carts/{scope}/{id}/lineitems/{id}",
"href": "http://{host}/{context}/availabilities/carts/{scope}/{id}/lineitems/{id}",
"type": "availabilities.availability-for-cart-line-item"
},
"data": {
"item-code": "physical_product_with_fixed_inventory_sku"
}
}
See Business State Errors for a list of messages returned by Cortex.
NeedInfo Messages
Structured messages with a type
field of needinfo
are generated in Cortex from resolvable blocking conditions. A needinfo
message indicates that additional information needs to be provided by the user. Their characteristics are:
- They are returned on
GET
operations with Status Code200 OK
, or onPOST
operations with Status Code409 Conflict
- The
type
isneedinfo
- The
data
field may contain entries that provide additional business context - The
blocks
field indicates that action that is blocked - The
linked-to
field indicates the action the user needs to take to resolve the condition
The following example indicates that a billing address is required in order to submit an order.
{
"type": "needinfo",
"id": "need.billing.address",
"debug-message": "Billing address is required",
"data": {
},
"blocks": {
"rel": "submitorderaction"
},
"linked-to": {
"uri": "/orders/{scope}/{id}/billingaddressinfo",
"href": "https://{host}/{context}/orders/{scope}/{id}/billingaddressinfo",
"type": "orders.billingaddress-info"
}
}
Informational Messages
Structured Messages with a custom type field are generated in Cortex as a result of an advisor. Custom structured messages can be actionable or non-actionable.
Cortex provides two out of the box informational type fields, information
and promotion
.
Actional Informational Messages
Custom structured messages with a linked-to field are actionable. They have the following characteristics:
- They are returned on
GET
operations with Status Code200 OK
- The
type
may beinformation
,promotion
, or another custom type - The
data
field may contain entries that provide additional business context - The
linked-to
field is present if the message is actionable
Example:
{
"type": "promotion",
"id": "promo.buy.more.get.one.free",
"debug-message": "If you purchase 2 more 'AA-12358' items, you get 1 for free.",
"data": {
"item-code" : "AA-12358",
"threshold" : "3",
"quantity" : "1"
},
"linked-to": {
"uri": "/items/{scope}/{id}/addtocartform",
"href": "https://{host}/{context}/items/{scope}/{id}/addtocartform",
"type": "carts.line-item"
}
}
Non-Actionable Informational Messages
Custom structured messages without a linked-to
field are non-actionable. Otherwise, they have the same characteristics as an actionable custom structured message.
{
"type": "promotion",
"id": "promo.free.shipping",
"debug-message": "Get free shipping if you buy $5.37 more",
"data": {
"amount": "$5.37",
}
}
Localizing Structured Messages
A front-end developer is responsible for localizing and presenting structured messages to the user. The debug-message
field is povided as a developer convenience and is not suitable for presentation to end users.
To localize a structured message:
- Use the message
id
and the user’s locale to lookup a localized message template - Localize values in the
data
field if required - Substitute placeholders in the localized message template with the localized values from the
data
field
Localization Example
Let’s use the following message as an example of localization into English.
{
"type": "error",
"id": "field.required",
"debug-message": "given-name attribute is required",
"data": {
"field-name": "given-name"
}
}
Lookup a localized message template for
id=field.required
andlocale=en
. For example, the template might be:localized-template = "{field-name} is required"
Localize the
field-name
data value ofgiven-name
to match what the field in called in the user interface (e.g. "First name")localized-field-name = "First name"
Substitute the {field-name} placeholder with the localized data value.
localized-message = "First name is required"