| % 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"). |
| -include_lib("mem3/include/mem3.hrl"). |
| -include("couch_db_int.hrl"). |
| |
| -type subject() :: |
| #db{} |
| | #httpd{} |
| | #shard{} |
| | #ordered_shard{} |
| | 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, {couch_db:normalize_dbname(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{name = Name}) -> |
| subject_key(Name); |
| subject_key(#httpd{path_parts = [Name | _Rest]}) -> |
| subject_key(Name); |
| subject_key(#httpd{path_parts = []}) -> |
| <<>>; |
| subject_key(#shard{name = Name}) -> |
| subject_key(Name); |
| subject_key(#ordered_shard{name = Name}) -> |
| subject_key(Name); |
| 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. |