Skip to content

Commit 5c0a617

Browse files
authored
Fb/more validations and docs (#25)
* wip 1 * wip 2 * wip 3 * wip 4 * spelling consistency * progress 1 * docs look good * remove todo * clearSubScopes docs 1 * clearSubScopes docs 2 * more polish 1 * more polish 2 * more polish 3 * more polish 4
1 parent 769e415 commit 5c0a617

File tree

6 files changed

+225
-129
lines changed

6 files changed

+225
-129
lines changed

docs/service/index.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ These service endpoints will enable operations teams to understand and modify to
3636
check the [http file](https://github.com/cap-js-community/feature-toggle-library/blob/main/example-cap-server/http/feature-service.http)
3737
in our example CAP Server.
3838

39-
### Read Server Memory State
39+
### Read Feature Toggles State
4040

4141
Get all information about the current in-memory state of all toggles.
4242

@@ -74,7 +74,7 @@ Get all information about the current in-memory state of all toggles.
7474

7575
---
7676

77-
### Update Toggle
77+
### Update Feature Toggle
7878

7979
Update the toggle state on Redis, which in turn is published to all server instances.
8080

@@ -100,6 +100,28 @@ Update the toggle state on Redis, which in turn is published to all server insta
100100
...
101101
```
102102

103+
- Valid Request with [clearSubScopes]({{ base_url }}/usage/#updating-feature-value)
104+
```http
105+
POST /rest/feature/redisUpdate
106+
Authorization: ...
107+
Content-Type: application/json
108+
```
109+
```json
110+
{
111+
"key": "/check/priority",
112+
"value": 10,
113+
"options": {
114+
"clearSubScopes": true
115+
}
116+
}
117+
```
118+
- Response
119+
120+
```
121+
HTTP/1.1 204 No Content
122+
...
123+
```
124+
103125
- Invalid Request
104126
```http
105127
POST /rest/feature/redisUpdate

docs/usage/index.md

Lines changed: 100 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -46,44 +46,75 @@ deployments. The configuration is a key-value map describing each individual fea
4646
type: string
4747
fallbackValue: info
4848
appUrl: \.cfapps\.sap\.hana\.ondemand\.com$
49-
validation: ^(?:error|warn|info|verbose|debug)$
49+
validations:
50+
- regex: ^(error|warn|info|verbose|debug)$
5051
```
5152
5253
The semantics of these properties are as follows.
5354
5455
| property | required | meaning |
5556
| :------------ | :------- | :--------------------------------------------------------------- |
56-
| active | | if this is `false`, the corresponding feature toggle is inactive |
5757
| type | true | one of the allowed types `boolean`, `number`, `string` |
58-
| fallbackValue | true | see below |
59-
| appUrl | | see below |
60-
| validation | | regex for input validation |
61-
| allowedScopes | | see below |
58+
| fallbackValue | true | emergency fallback value |
59+
| active | | if this is `false`, the corresponding feature toggle is inactive |
60+
| appUrl | | activate toggle only if appUrl regex is matched |
61+
| validations | | list of validations |
62+
63+
_type_<br>
64+
You can use the type `string` to encode complex data types like arrays or objects, but need to take care of
65+
serialization/deserialization yourself. In these cases, make sure to use [external validation](#external-validation)
66+
so that new values can be deserialized correctly.
6267

6368
_fallbackValue_<br>
6469
This value gets set initially when the feature toggle is introduced, and it is also used as a fallback when
6570
communication with Redis is interrupted during startup.
6671

72+
_active_<br>
73+
Using _active_ or _appUrl_ to block the activation of a feature toggle, will cause all usage code reading it to
74+
always get the fallback value.
75+
6776
_appUrl_<br>
68-
Regex for activating feature toggle _only_ if the cf app's url matches
77+
Regular expression for activating a feature toggle _only_ if at least one of its Cloud Foundry application's urls
78+
match. When the library is not running in `CF_REDIS` [integration mode](#integration-mode), this check is disabled.
79+
Here are some examples:
6980

70-
- for CANARY landscape `\.cfapps\.sap\.hana\.ondemand\.com$`
71-
- for EU10 landscape `\.cfapps\.eu10\.hana\.ondemand\.com$`
72-
- specific CANARY app `<cf-app-name>\.cfapps\.sap\.hana\.ondemand\.com$`
81+
- for CANARY landscape `\.cfapps\.sap\.hana\.ondemand\.com$`,
82+
- for EU10 landscape `\.cfapps\.eu10\.hana\.ondemand\.com$`,
83+
- specific CANARY app `<cf-app-name>\.cfapps\.sap\.hana\.ondemand\.com$`.
7384

74-
_allowedScopes_<br>
75-
This is an additional form of change validation. AllowedScopes can be set to a list of strings, for example
76-
`allowedScopes: [tenant, user]`. With this configuration only matching scopes can be used when setting feature toggle
77-
values.
85+
_validations_<br>
86+
List of validations that will guard all changes of the associated feature toggle. All validations must pass
87+
successfully for a change to occur. Each kind of validation can happen multiple times. Here is a practical example with
88+
all possible validation kinds:
7889

79-
{: .info }
80-
You can use the type `string` to encode more complex data types, like arrays or objects, but need to take care of the
81-
serialization/deserialization yourself. In these cases, make sure to use [external validation](#external-validation)
82-
so that new values can be deserialized correctly.
90+
```yaml
91+
# info: check api priority; 0 means access is disabled
92+
/check/priority:
93+
type: number
94+
fallbackValue: 0
95+
validations:
96+
- scopes: [user, tenant]
97+
- regex: ^\d+$
98+
- { module: "$CONFIG_DIR/validators.js", call: validateTenantScope }
99+
```
83100

84-
{: .warn }
85-
When using _active_ or _appUrl_ to block activation of a feature toggle, then user code accessing the
86-
feature toggle value will always get the fallback value.
101+
The semantics of these properties are as follows.
102+
103+
| property | meaning |
104+
| :------- | :----------------------------------------- |
105+
| scopes | restrict which scopes are allowed |
106+
| regex | value converted to string must match regex |
107+
| module | register external validation module |
108+
109+
_module_<br>
110+
Module points to a module, where an [external validation](#external-validation) is implemented. These external checks
111+
get registered during initialization and will be called during change attempts. You can specify just the module and
112+
export the validation function directly. Alternatively, you can specify both the module and a property to call on the
113+
module.
114+
115+
For the module path, you can specify it either relative to the runtime working directory (usually the project root),
116+
e.g., `module: ./path-from-root/validations.js`, or you can use the location of the configuration file as a relative
117+
anchor, e.g., `module: $CONFIG_DIR/validation.js`.
87118

88119
## Initialization for CAP Projects
89120

@@ -162,6 +193,23 @@ const config = await readConfigFromFile(FEATURES_FILEPATH);
162193
await initializeFeatures({ config });
163194
```
164195

196+
## Integration Mode
197+
198+
After successful initialization, the library will write one info log of the form:
199+
200+
```
201+
13:40:13.775 | INFO | /FeatureToggles | finished initialization with 2 feature toggles with NO_REDIS
202+
```
203+
204+
It tells you both how many toggles where initialized, and the integration mode, that the library detected. Here are all
205+
possible modes:
206+
207+
| mode | meaning |
208+
| :------------ | :------------------------------------------------------------------------------- |
209+
| `NO_REDIS` | no redis detected, all changes are only in memory and will be lost on restart |
210+
| `LOCAL_REDIS` | local redis server detected, changes are persisted in that local redis |
211+
| `CF_REDIS` | Cloud Foundry redis service binding detected, changes will be persisted normally |
212+
165213
## Environment Variables
166214

167215
The following environment variables can be used to fine-tune the library's behavior:
@@ -177,9 +225,9 @@ The following environment variables can be used to fine-tune the library's behav
177225
In this section, we will assume that the [initialization](#initialization) has happened and the configuration contained
178226
a feature toggle with the key `/srv/util/logger/logLevel`, similar to the one described [here](#format).
179227

180-
### Querying Feature Value
228+
### Reading Feature Value
181229

182-
You can query the current in memory state of any feature toggle:
230+
You can read the current in memory state of any feature toggle:
183231

184232
```javascript
185233
const {
@@ -247,10 +295,38 @@ async function changeIt(newValue, scopeMap) {
247295
The change API `changeFeatureValue` will return when the change is published to Redis, so there may be a slight
248296
processing delay until the change is picked up by all subscribers.
249297

250-
{: .info }
251298
Setting a feature value to `null` will delete the associated remote state and effectively reset it to its fallback
252299
value.
253300

301+
Since setting values for scope-combinations happens additively, it can become hard to keep track of which combinations
302+
have dedicated values attached to them. If you want to set a value _and_ make sure that there isn't a more specific
303+
scope-combination, which overrides that value, then you can use the option `{ clearSubScopes: true }` as a third
304+
argument. For example
305+
306+
```javascript
307+
await changeFeatureValue("/srv/util/logger/logLevel", "error", {}, { clearSubScopes: true });
308+
```
309+
310+
will set the root-scope value to `"error"` and remove all sub-scopes. See
311+
[scoping]({{ base_url }}/architecture/#scoping) for context.
312+
313+
### Resetting Feature Value
314+
315+
There is a convenience reset API just to reset a feature toggle and remove all associated persisted values. Reading
316+
the feature toggle afterward will only yield the fallback value until new changes are made.
317+
318+
```javascript
319+
const {
320+
singleton: { resetFeatureValue, changeFeatureValue },
321+
} = require("@cap-js-community/feature-toggle-library");
322+
323+
// ... in some function
324+
await resetFeatureValue("/srv/util/logger/logLevel");
325+
326+
// this is functionally equivalent to
327+
await changeFeatureValue("/srv/util/logger/logLevel", null, {}, { clearSubScopes: true });
328+
```
329+
254330
### External Validation
255331

256332
The `string`-type feature toggles can theoretically encode very complex data structures, so it's sensible to validate

0 commit comments

Comments
 (0)