summaryrefslogtreecommitdiff
path: root/src/couch/src/couch_flags.erl
blob: 5bd133e290ed8635721782957d27044e50930120 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.

% This module serves two functions
% - provides public API to use to get value for a given feature flag and subject
% - implements {feature_flags, couch_flags} service

% The module relies on couch_epi_data_gen which uses the data returned by
% `couch_flags_config:data()` to generate callback module `couch_epi_data_gen_flags_config`.
% The generated module shouldn't be used directly. We use following APIs
% - `couch_epi:get_handle({flags, config})` - to get handler (name of generated module)
% - `couch_epi:get_value(Handle, Key) - to do efficient matching
%
% The generated module implements clauses like the following
%  - get(couch, {binary_match_rule()}) ->
%       {matched_pattern(), size(matched_pattern()), [flag()]} | undefined
% For example
%  - get(couch, {<<"/shards/test/exact">>}) ->
%        {<<"/shards/test/exact">>,18,[baz,flag_bar,flag_foo]};
%  - get(couch, {<<"/shards/test", _/binary>>}) ->
%        {<<"/shards/test*">>,13,[baz,flag_bar,flag_foo]};
%  - get(couch, {<<"/shards/exact">>}) ->
%        {<<"/shards/exact">>,13,[flag_bar,flag_foo]};
%  - get(couch, {<<"/shards/blacklist", _/binary>>}) ->
%        {<<"/shards/blacklist*">>,18,[]};
%  - get(couch, {<<"/", _/binary>>}) ->
%        {<<"/*">>,2,[flag_foo]};
%  - get(_, _) -> undefined.
%
% The `couch_epi:get/2` uses the Handler module to implement efficient matching.

% In order to distinguish between shards and clustered db the following
% convention is used.
% - it is a shard if pattern starts with `/`

-module(couch_flags).

%% Public API
-export([
    enabled/1,
    is_enabled/2
]).

%% For internal use
-export([
    rules/0
]).

%% For use from plugin
-export([
    subject_key/1
]).

-include_lib("couch/include/couch_db.hrl").

-type subject()
    :: map()
        | #httpd{}
        | string()
        | binary().

-define(SERVICE_ID, feature_flags).

-spec enabled(subject()) -> [atom()].

enabled(Subject) ->
    Key = maybe_handle(subject_key, [Subject], fun subject_key/1),
    Handle = couch_epi:get_handle({flags, config}),
    lists:usort(enabled(Handle, {<<"/", Key/binary>>})
        ++ enabled(Handle, {Key})).

-spec is_enabled(FlagId :: atom(), subject()) -> boolean().

is_enabled(FlagId, Subject) ->
    lists:member(FlagId, enabled(Subject)).

-spec rules() ->
    [{Key :: string(), Value :: string()}].

rules() ->
    Handle = couch_epi:get_handle(?SERVICE_ID),
    lists:flatten(couch_epi:apply(Handle, ?SERVICE_ID, rules, [], [])).

-spec enabled(Handle :: couch_epi:handle(), Key :: {binary()}) -> [atom()].

enabled(Handle, Key) ->
    case couch_epi:get_value(Handle, couch, Key) of
        {_, _, Flags} -> Flags;
        undefined -> []
    end.

-spec subject_key(subject()) -> binary().

subject_key(#{} = Db) ->
    subject_key(fabric2_db:name(Db));
subject_key(#httpd{path_parts=[Name | _Rest]}) ->
    subject_key(Name);
subject_key(#httpd{path_parts=[]}) ->
    <<>>;
subject_key(Name) when is_list(Name) ->
    subject_key(list_to_binary(Name));
subject_key(Name) when is_binary(Name) ->
    Name.

-spec maybe_handle(
        Function :: atom(),
        Args :: [term()],
        Default :: fun((Args :: [term()]) -> term())) ->
    term().

maybe_handle(Func, Args, Default) ->
    Handle = couch_epi:get_handle(?SERVICE_ID),
    case couch_epi:decide(Handle, ?SERVICE_ID, Func, Args, []) of
        no_decision when is_function(Default) ->
            apply(Default, Args);
        {decided, Result} ->
            Result
    end.