...
 
Commits (2)
......@@ -151,6 +151,20 @@ HTTP API
describe the other endpoints
API Response Versioning
-----------------------
As your site evolves, it's possible for your API client to encounter cart data that's generated by a version of Lorikeet that's newer than you're expecting (if it's loaded from the API, and the server has been upgraded since the last page refresh), or even older (if it's loaded from ``localStorage`` after an update).
The :http:get:`/_cart/` endpoint returns two properties that clients can use to detect these issues.
- ``compatible_version`` (currently ``2``): This number is incremented for changes that add new functionality in a backwards-compatible way; for instance, cases where new keys have been added to the JSON object but existing ones have not been changed, such as the addition of adjustments in version 0.1.9. Your client should reject carts that have a ``compatibleVersion`` less than what you're expecting, because they might be missing properties you depend on.
- ``incompatible_version`` (currently ``1``): This number is incremented for changes that alter the structure or semantics of existing properties of the cart data. Your client should reject carts that have a version that is **greater or lesser** than what you're expecting.
.. warning::
If you have been using Lorikeet before version 0.1.10, you should also handle the case where ``compatibleVersion`` and ``incompatibleVersion`` are both missing.
Why does Lorikeet's API work like this?
---------------------------------------
......
......@@ -2,6 +2,8 @@ const csrftoken = decodeURIComponent(
/(?:^|;)\s*csrftoken=([^;]+)/.exec(document.cookie)[1],
)
const localStorageKey = "au.com.cmv.open-source.lorikeet.cart-data"
const COMPATIBLE_VERSION = 2
const INCOMPATIBLE_VERSION = 1
var setImmediate = window.setImmediate || (x => window.setTimeout(x, 0))
......@@ -220,8 +222,26 @@ class CartClient {
return
}
// If we didn't get it _from_ local storage, post it _to_ local storage
// If the cart we recieved was generated by an old version of Lorikeet, bail
// TODO: provide some sort of hook to prompt the user to reload the page
if (
typeof cart.compatible_version == "undefined" ||
cart.compatible_version < COMPATIBLE_VERSION ||
typeof cart.incompatible_version == "undefined" ||
cart.incompatible_version != INCOMPATIBLE_VERSION
) {
console.warn(
"Encountered an incompatible cart, ignoring it",
"compatible_version:",
cart.compatible_version,
"incompatible_version:",
cart.incompatible_version,
)
return
}
if (!receivedFromLocalStorage) {
// If we didn't get it _from_ local storage, post it _to_ local storage
localStorage.setItem(localStorageKey, JSON.stringify(cart))
}
......
......@@ -203,6 +203,8 @@ class CartSerializer(serializers.ModelSerializer):
checkout_url = fields.SerializerMethodField()
generated_at = fields.SerializerMethodField()
email = fields.EmailField()
compatible_version = fields.SerializerMethodField()
incompatible_version = fields.SerializerMethodField()
def get_new_item_url(self, _):
return reverse('lorikeet:add-to-cart')
......@@ -257,6 +259,12 @@ class CartSerializer(serializers.ModelSerializer):
def get_checkout_url(self, _):
return reverse('lorikeet:checkout')
def get_compatible_version(self, _):
return 2
def get_incompatible_version(self, _):
return 1
class Meta:
model = models.Cart
fields = ('items', 'new_item_url', 'delivery_addresses',
......@@ -264,7 +272,8 @@ class CartSerializer(serializers.ModelSerializer):
'new_payment_method_url', 'grand_total', 'generated_at',
'is_complete', 'incomplete_reasons', 'checkout_url',
'is_authenticated', 'email', 'adjustments',
'new_adjustment_url', 'subtotal')
'new_adjustment_url', 'subtotal', 'compatible_version',
'incompatible_version')
class CartUpdateSerializer(serializers.ModelSerializer):
......
......@@ -49,6 +49,8 @@ def test_empty_cart(client):
'checkout_url': '/_cart/checkout/',
'is_authenticated': False,
'email': None,
'compatible_version': 2,
'incompatible_version': 1,
}
......@@ -90,6 +92,8 @@ def test_empty_cart_logged_in(admin_client):
'checkout_url': '/_cart/checkout/',
'is_authenticated': True,
'email': None,
'compatible_version': 2,
'incompatible_version': 1,
}
......
{
"name": "lorikeet",
"version": "0.1.9",
"version": "0.1.10",
"main": "index.js",
"repository": "gitlab:abre/lorikeet",
"files": ["index.js", "react.js"],
......
[tool.poetry]
name = "lorikeet"
version = "0.1.9"
version = "0.1.10"
# Don't forget to also change the version in:
# - package.json
# - docs/conf.py
......