APIs
Data model
The feature structure depends on the type of the strategy. A feature can have the following strategies:
NO_STRATEGY
: active or inactiveRELEASE_DATE
: active on a specific dateDATE_RANGE
: active between two dateSCRIPT
: Activation depends on the execution of a js scriptGLOBAL_SCRIPT
: Activation depends on the execution of a js script shared across featurePERCENTAGE
: Active for a percentage of client
NO_STRATEGY format
{
"id": "basic:feature",
"enabled": true,
"activationStrategy": "NO_STRATEGY"
}
RELEASE_DATE format
{
"id": "feature:with:date",
"enabled": true,
"parameters": {
"releaseDate": "13/12/2017 10:29:04"
},
"activationStrategy": "RELEASE_DATE"
}
DATE_RANGE format
{
"id": "feature:with:datetange",
"enabled": true,
"parameters": {
"from": "2017-12-13 10:29:04",
"to": "2017-12-14 00:45:23"
},
"activationStrategy": "DATE_RANGE"
}
HOUR_RANGE format
{
"id": "feature:with:hour:range",
"enabled": true,
"parameters": {
"startDate": "10:15",
"endDate": "18:30"
},
"activationStrategy": "HOUR_RANGE"
}
SCRIPT format
The field type
can be
- javascript
- kotlin
- scala
{
"id": "feature:with:script",
"enabled": true,
"parameters": {
"type": "javascript",
"script": "/**\n * context: a JSON object containing app specific value \n * to evaluate the state of the feature\n * enabled: a callback to mark the feature as active \n * for this request\n * disabled: a callback to mark the feature as inactive \n * for this request \n * http: a http client\n */ \nfunction enabled(context, enabled, disabled, http) {\n if (context.user === 'ragnar.lodbrok@gmail.com') {\n return enabled();\n }\n return disabled();\n}"
},
"activationStrategy": "SCRIPT"
}
GLOBAL_SCRIPT format
{
"id": "feature:with:global:script",
"enabled": false,
"parameters": {
"ref": "project:script:ref"
},
"activationStrategy": "GLOBAL_SCRIPT"
}
PERCENTAGE format
{
"id": "feature:with:percentage",
"enabled": false,
"parameters": {
"percentage": 60
},
"activationStrategy": "PERCENTAGE"
}
CRUD API
Features expose a classic CRUD REST api :
List all
curl -X GET \
'http://localhost:9000/api/features?pattern=feature:*&page=1&pageSize=15' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' | jq
Will respond with a 200 status code:
{
"results": [
{
"id": "feature:with:date",
"enabled": true,
"parameters": {
"releaseDate": "13/12/2017 10:29:04"
},
"activationStrategy": "RELEASE_DATE"
},
{
"id": "feature:with:script",
"enabled": true,
"parameters": {
"type": "javascript",
"script": "/**\n * context: a JSON object containing app specific value \n * to evaluate the state of the feature\n * enabled: a callback to mark the feature as active \n * for this request\n * disabled: a callback to mark the feature as inactive \n * for this request \n * http: a http client\n */ \nfunction enabled(context, enabled, disabled, http) {\n if (context.user === 'ragnar.lodbrok@gmail.com') {\n return enabled();\n }\n return disabled();\n}"
},
"activationStrategy": "SCRIPT"
},
{
"id": "feature:with:global:script",
"enabled": true,
"parameters": {
"ref": "project:script2"
},
"activationStrategy": "GLOBAL_SCRIPT"
}
],
"metadata": {
"page": 1,
"pageSize": 15,
"count": 3,
"nbPages": 1
}
}
The query params are optional and the value used in this example are the default one.
Create a feature
curl -XPOST \
'http://localhost:9000/api/features' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '{ "id": "really:basic:feature", "enabled": true, "activationStrategy": "NO_STRATEGY" }' | jq
Will respond with a 201 status code:
{
"id": "really:basic:feature",
"enabled": true,
"activationStrategy": "NO_STRATEGY"
}
Get a feature
curl -X GET \
'http://localhost:9000/api/features/really:basic:feature' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' | jq
Will respond with a 200 status code:
{
"id": "really:basic:feature",
"enabled": true,
"activationStrategy": "NO_STRATEGY"
}
Update a feature
curl -X PUT \
'http://localhost:9000/api/features/really:basic:feature' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '{ "id": "really:basic:feature", "enabled": false, "activationStrategy": "NO_STRATEGY" }' | jq
Will respond with a 200 status code:
{
"id": "really:basic:feature",
"enabled": false,
"activationStrategy": "NO_STRATEGY"
}
Patch a feature
Partial updates can be done using json patch :
curl -X PATCH \
'http://localhost:9000/api/features/really:basic:feature' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '[{"op": "replace", "path": "enabled", "value": false}]'
Will respond with a 200 status code:
{
"id": "really:basic:feature",
"enabled": false,
"activationStrategy": "NO_STRATEGY"
}
Delete a feature
curl -X DELETE \
'http://localhost:9000/api/features/really:basic:feature' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' | jq
Will respond with a 200 status code:
{
"id": "really:basic:feature",
"enabled": false,
"activationStrategy": "NO_STRATEGY"
}
As you can see, the keys are split with :
and the values are
Check features
We can divide the feature in two kind :
- feature without context:
NO_STRATEGY
featuresRELEASE_DATE
featuresDATE_RANGE
featuresHOUR_RANGE
features
- feature needing context:
SCRIPT
featuresGLOBAL_SCRIPT
featuresPERCENTAGE
features
Check a feature without context
curl -X GET \
'http://localhost:9000/api/features/really:basic:feature/check' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' | jq
Will respond with a 200 status code:
{
"active": true
}
Check a feature with context
curl -X POST \
'http://localhost:9000/api/features/feature:with:script/check' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '{ "user": "ragnar.lodbrok@gmail.com" }' | jq
Will respond with a 200 status code:
{
"active": true
}
The percentage strategy need a context with an id field in order to decide if the feature is active for this id.
curl -X POST \
'http://localhost:9000/api/features/feature:with:percentage/check' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '{ "id": "ragnar.lodbrok@gmail.com" }' | jq
Will respond with a 200 status code:
{
"active": true
}
The Tree API
With the tree api you can check the features and get the results as tree. Be careful using this API because the results are not paged so be sure to use an appropriate pattern :
Check the features without context
curl -X GET \
'http://localhost:9000/api/tree/features?pattern=feature:*' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' | jq
We will get the following response:
{
"feature": {
"simple": {
"active": true
},
"with": {
"date": {
"active": true
},
"script": {
"active": false
},
"global": {
"script": {
"active": false
}
}
}
}
}
Or with context:
curl -X POST \
'http://localhost:9000/api/tree/features?pattern=feature:*' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '{ "user": "ragnar.lodbrok@gmail.com" }' \
| jq
{
"feature": {
"simple": {
"active": true
},
"with": {
"date": {
"active": true
},
"script": {
"active": false
},
"global": {
"script": {
"active": true
}
}
}
}
}
List features with the active attribute without context
curl -X GET \
'http://localhost:9000/api/features?pattern=feature:*&active=true' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' | jq
The result is the following
{
"results": [
{
"id": "feature:simple",
"enabled": true,
"activationStrategy": "NO_STRATEGY",
"active": true
},
{
"id": "feature:with:date",
"enabled": true,
"parameters": {
"releaseDate": "13/12/2017 10:29:04"
},
"activationStrategy": "RELEASE_DATE",
"active": true
},
{
"id": "feature:with:script",
"enabled": true,
"parameters": {
"type": "javascript",
"script": "/**\n * context: a JSON object containing app specific value \n * to evaluate the state of the feature\n * enabled: a callback to mark the feature as active \n * for this request\n * disabled: a callback to mark the feature as inactive \n * for this request \n * http: a http client\n */ \nfunction enabled(context, enabled, disabled, http) {\n if (context.user === 'ragnar.lodbrok@gmail.com') {\n return enabled();\n }\n return disabled();\n}"
},
"activationStrategy": "SCRIPT",
"active": false
},
{
"id": "feature:with:global:script",
"enabled": true,
"parameters": {
"ref": "project:script2"
},
"activationStrategy": "GLOBAL_SCRIPT",
"active": false
}
],
"metadata": {
"page": 1,
"pageSize": 15,
"count": 4,
"nbPages": 1
}
}
List features with the active attribute with context
curl -X POST \
'http://localhost:9000/api/features/_checks?pattern=feature:*' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '{ "user": "ragnar.lodbrok@gmail.com" }' \
| jq
The result is the following
{
"results": [
{
"id": "feature:simple",
"enabled": true,
"activationStrategy": "NO_STRATEGY",
"active": true
},
{
"id": "feature:with:date",
"enabled": true,
"parameters": {
"releaseDate": "13/12/2017 10:29:04"
},
"activationStrategy": "RELEASE_DATE",
"active": true
},
{
"id": "feature:with:script",
"enabled": true,
"parameters": {
"type": "javascript",
"script": "/**\n * context: a JSON object containing app specific value \n * to evaluate the state of the feature\n * enabled: a callback to mark the feature as active \n * for this request\n * disabled: a callback to mark the feature as inactive \n * for this request \n * http: a http client\n */ \nfunction enabled(context, enabled, disabled, http) {\n if (context.user === 'ragnar.lodbrok@gmail.com') {\n return enabled();\n }\n return disabled();\n}"
},
"activationStrategy": "SCRIPT",
"active": true
},
{
"id": "feature:with:global:script",
"enabled": true,
"parameters": {
"ref": "project:script2"
},
"activationStrategy": "GLOBAL_SCRIPT",
"active": false
}
],
"metadata": {
"page": 1,
"pageSize": 15,
"count": 4,
"nbPages": 1
}
}
Download and Upload
Download and upload is done using the nd-json format.
Download
curl -X GET \
'http://localhost:9000/api/features.ndjson' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' | jq
Will return each item separated by a \n
.
{
"id": "feature:simple1",
"enabled": true,
"activationStrategy": "NO_STRATEGY"
}
{
"id": "feature:simple2",
"enabled": true,
"activationStrategy": "NO_STRATEGY"
}
Upload
curl -X POST \
'http://localhost:9000/api/features.ndjson' \
-H 'Content-Type: application/nd-json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '{"id": "feature:simple1","enabled": true,"activationStrategy": "NO_STRATEGY","active": true}
{"id": "feature:simple2","enabled": true,"activationStrategy": "NO_STRATEGY","active": true}' \
| jq
Will return
{
"success": 2,
"errors": {
"errors": [],
"fieldErrors": {}
}
}