% vim:ft=erlang:
% ==============================
% Rabbit app section
% ==============================

%%
%% Network Connectivity
%% ====================
%%

%% By default, RabbitMQ will listen on all interfaces, using
%% the standard (reserved) AMQP port.
%%
%% {tcp_listeners, [5672]},
%% To listen on a specific interface, provide a tuple of {IpAddress, Port}.
%% For example, to listen only on localhost for both IPv4 and IPv6:
%%
%% {tcp_listeners, [{"127.0.0.1", 5672},
%%                  {"[::1]",     5672}]},

{mapping, "listeners.tcp", "rabbit.tcp_listeners",[
    {datatype, {enum, [none]}}
]}.

{mapping, "listeners.tcp.$name", "rabbit.tcp_listeners",[
    {datatype, [integer, ip]}
]}.

{translation, "rabbit.tcp_listeners",
fun(Conf) ->
    case cuttlefish:conf_get("listeners.tcp", Conf, undefined) of
        none -> [];
        _ ->
            Settings = cuttlefish_variable:filter_by_prefix("listeners.tcp", Conf),
            [ V || {_, V} <- Settings ]
    end
end}.

%% TLS listeners are configured in the same fashion as TCP listeners,
%% including the option to control the choice of interface.
%%
%% {ssl_listeners, [5671]},

{mapping, "listeners.ssl", "rabbit.ssl_listeners",[
    {datatype, {enum, [none]}}
]}.

{mapping, "listeners.ssl.$name", "rabbit.ssl_listeners",[
    {datatype, [integer, ip]}
]}.

{translation, "rabbit.ssl_listeners",
fun(Conf) ->
    case cuttlefish:conf_get("listeners.ssl", Conf, undefined) of
        none -> [];
        _ ->
            Settings = cuttlefish_variable:filter_by_prefix("listeners.ssl", Conf),
            [ V || {_, V} <- Settings ]
    end
end}.

%% Number of Erlang processes that will accept connections for the TCP
%% and TLS listeners.
%%
%% {num_tcp_acceptors, 10},
%% {num_ssl_acceptors, 1},

{mapping, "num_acceptors.ssl", "rabbit.num_ssl_acceptors", [
    {datatype, integer}
]}.

{mapping, "num_acceptors.tcp", "rabbit.num_tcp_acceptors", [
    {datatype, integer}
]}.


{mapping, "socket_writer.gc_threshold", "rabbit.writer_gc_threshold", [
    {datatype, [{atom, off}, integer]}
]}.

{translation, "rabbit.writer_gc_threshold",
    fun(Conf) ->
            case cuttlefish:conf_get("socket_writer.gc_threshold", Conf, undefined) of
                %% missing from the config
                undefined -> cuttlefish:unset();
                %% explicitly disabled
                off -> undefined;
                Int when is_integer(Int) andalso Int > 0 ->
                    Int;
                _ ->
                    cuttlefish:invalid("should be a non-negative integer")
            end
    end
}.

%% Maximum time for 0-9-1 handshake (after socket connection
%% and TLS handshake), in milliseconds.
%%
%% {handshake_timeout, 10000},

{mapping, "handshake_timeout", "rabbit.handshake_timeout", [
    {datatype, [{atom, infinity}, integer]}
]}.

%% Set to 'true' to perform reverse DNS lookups when accepting a
%% connection. Hostnames will then be shown instead of IP addresses
%% in rabbitmqctl and the management plugin.
%%
%% {reverse_dns_lookups, true},

{mapping, "reverse_dns_lookups", "rabbit.reverse_dns_lookups", [
    {datatype, boolean}
]}.

{mapping, "erlang.K", "vm_args.+K", [
  {default, "true"},
  {level, advanced}
]}.

%%
%% Definition import
%%

%% Original key for definition loading from a JSON file or directory of files. See
%% https://www.rabbitmq.com/docs/management#load-definitions
{mapping, "load_definitions", "rabbit.load_definitions",
    [{datatype, string},
     {validators, ["file_accessible"]}]}.

%% Newer syntax for definition loading from a JSON file or directory of files. See
%% https://www.rabbitmq.com/docs/management#load-definitions
{mapping, "definitions.local.path", "rabbit.definitions.local_path",
    [{datatype, string},
     {validators, ["file_accessible"]}]}.

%% Extensive mechanism for loading definitions from a remote source
{mapping, "definitions.import_backend", "rabbit.definitions.import_backend", [
    {datatype, atom}
]}.

{translation, "rabbit.definitions.import_backend",
fun(Conf) ->
    case cuttlefish:conf_get("definitions.import_backend", Conf, rabbit_definitions_import_local_filesystem) of
        %% short aliases for known backends
        local_filesystem -> rabbit_definitions_import_local_filesystem;
        local            -> rabbit_definitions_import_local_filesystem;
        https            -> rabbit_definitions_import_https;
        http             -> rabbit_definitions_import_https;
        %% accept both rabbitmq_ and rabbit_ (typical core module prefix)
        rabbitmq_definitions_import_local_filesystem -> rabbit_definitions_import_local_filesystem;
        rabbitmq_definitions_import_http             -> rabbit_definitions_import_https;
        %% any other value is used as is
        Module           -> Module
    end
end}.

{mapping, "definitions.skip_if_unchanged", "rabbit.definitions.skip_if_unchanged", [
    {datatype, boolean}]}.

{mapping, "definitions.hashing.algorithm", "rabbit.definitions.hashing_algorithm", [
    {datatype, {enum, [sha, sha224, sha256, sha384, sha512]}}]}.

%% Load definitions from a remote URL over HTTPS. See
%% https://www.rabbitmq.com/docs/management#load-definitions.
{mapping, "definitions.https.url", "rabbit.definitions.url",
    [{datatype, [tagged_string, string]}]}.

%% The tagged_string datatype matches any alphanumeric prefix followed by `:`,
%% which includes URL schemes like `https:`. Treat the `encrypted` tag as a
%% tagged value to be decrypted later; reconstruct the URL for any other tag.
{translation, "rabbit.definitions.url",
fun(Conf) ->
    case cuttlefish:conf_get("definitions.https.url", Conf, undefined) of
        undefined                                -> cuttlefish:unset();
        {encrypted, Rest}                        -> {encrypted, Rest};
        {Scheme, Rest} when is_atom(Scheme)      -> atom_to_list(Scheme) ++ ":" ++ Rest;
        Str                                      -> Str
    end
end}.

%% Client-side TLS settings used by e.g. HTTPS definition loading mechanism.
%% These can be reused by other clients.

{mapping, "definitions.tls.verify", "rabbit.definitions.ssl_options.verify", [
    {datatype, {enum, [verify_peer, verify_none]}}]}.

{mapping, "definitions.tls.fail_if_no_peer_cert", "rabbit.definitions.ssl_options.fail_if_no_peer_cert", [
    {datatype, boolean}]}.

{mapping, "definitions.tls.cacertfile", "rabbit.definitions.ssl_options.cacertfile",
    [{datatype, string}, {validators, ["pem_file"]}]}.

{mapping, "definitions.tls.certfile", "rabbit.definitions.ssl_options.certfile",
    [{datatype, string}, {validators, ["pem_file"]}]}.

{mapping, "definitions.tls.cert", "rabbit.definitions.ssl_options.cert",
    [{datatype, string}]}.

{translation, "rabbit.definitions.ssl_options.cert",
fun(Conf) ->
    list_to_binary(cuttlefish:conf_get("definitions.tls.cert", Conf))
end}.

{mapping, "definitions.tls.reuse_session", "rabbit.definitions.ssl_options.reuse_session",
    [{datatype, boolean}]}.

{mapping, "definitions.tls.crl_check", "rabbit.definitions.ssl_options.crl_check",
    [{datatype, [{enum, [true, false, peer, best_effort]}]}]}.

{mapping, "definitions.tls.depth", "rabbit.definitions.ssl_options.depth",
    [{datatype, byte}]}.

{mapping, "definitions.tls.dh", "rabbit.definitions.ssl_options.dh",
    [{datatype, string}]}.

{translation, "rabbit.definitions.ssl_options.dh",
fun(Conf) ->
    list_to_binary(cuttlefish:conf_get("definitions.tls.dh", Conf))
end}.

{translation, "rabbit.definitions.ssl_options.key",
fun(Conf) ->
    case cuttlefish_variable:filter_by_prefix("definitions.tls.key", Conf) of
        [{[_,_,Key], Val}|_] -> {list_to_atom(Key), list_to_binary(Val)};
        _ -> cuttlefish:unset()
    end
end}.

{mapping, "definitions.tls.keyfile", "rabbit.definitions.ssl_options.keyfile",
    [{datatype, string}, {validators, ["pem_file"]}]}.

{mapping, "definitions.tls.log_alert", "rabbit.definitions.ssl_options.log_alert",
    [{datatype, boolean}]}.

{mapping, "definitions.tls.password", "rabbit.definitions.ssl_options.password",
    [{datatype, [tagged_binary, binary]}]}.

{translation, "rabbit.definitions.ssl_options.password",
fun(Conf) ->
    rabbit_cuttlefish:optionally_tagged_string("definitions.tls.password", Conf)
end}.

{mapping, "definitions.tls.secure_renegotiate", "rabbit.definitions.ssl_options.secure_renegotiate",
    [{datatype, boolean}]}.

{mapping, "definitions.tls.reuse_sessions", "rabbit.definitions.ssl_options.reuse_sessions",
    [{datatype, boolean}]}.

{mapping, "definitions.tls.versions.$version", "rabbit.definitions.ssl_options.versions",
    [{datatype, atom}]}.

{translation, "rabbit.definitions.ssl_options.versions",
fun(Conf) ->
    Settings = cuttlefish_variable:filter_by_prefix("definitions.tls.versions", Conf),
    [V || {_, V} <- Settings]
end}.

{mapping, "definitions.tls.ciphers.$cipher", "rabbit.definitions.ssl_options.ciphers",
    [{datatype, string}]}.

{translation, "rabbit.definitions.ssl_options.ciphers",
fun(Conf) ->
    Settings = cuttlefish_variable:filter_by_prefix("definitions.tls.ciphers", Conf),
    lists:reverse([V || {_, V} <- Settings])
end}.

{mapping, "definitions.tls.log_level", "rabbit.definitions.ssl_options.log_level",
    [{datatype, {enum, [emergency, alert, critical, error, warning, notice, info, debug]}}]}.

%%
%% Seed User, Authentication, Access Control
%%

%% The default "guest" user is only permitted to access the server
%% via a loopback interface (e.g. localhost).
%% {loopback_users, [<<"guest">>]},
%%
%% Uncomment the following line if you want to allow access to the
%% guest user from anywhere on the network.
%% {loopback_users, []},

{mapping, "loopback_users", "rabbit.loopback_users", [
    {datatype, {enum, [none]}}
]}.

{mapping, "loopback_users.$user", "rabbit.loopback_users", [
    {datatype, atom}
]}.

{translation, "rabbit.loopback_users",
fun(Conf) ->
    None = cuttlefish:conf_get("loopback_users", Conf, undefined),
    case None of
        none -> [];
        _    ->
            Settings = cuttlefish_variable:filter_by_prefix("loopback_users", Conf),
            [ list_to_binary(U) || {["loopback_users", U], V} <- Settings, V == true ]
    end
end}.

%% TLS options.
%% See https://www.rabbitmq.com/docs/ssl for full documentation.
%%
%% {ssl_options, [{cacertfile,           "/path/to/testca/cacert.pem"},
%%                {certfile,             "/path/to/server/cert.pem"},
%%                {keyfile,              "/path/to/server/key.pem"},
%%                {verify,               verify_peer},
%%                {fail_if_no_peer_cert, false}]},

{mapping, "ssl_allow_poodle_attack", "rabbit.ssl_allow_poodle_attack",
[{datatype, boolean}]}.

{include_partial, {rabbit, "ssl_options"},
    [{prefix, "ssl_options"},
     {app_prefix, "rabbit.ssl_options"},
     {disable_with, none}]}.

%% `key.*` is kept inline so the no-match branch stays
%% `cuttlefish:unset/0`: the historical primary-listener behavior.

{mapping, "ssl_options.key.RSAPrivateKey", "rabbit.ssl_options.key",
    [{datatype, string}]}.

{mapping, "ssl_options.key.DSAPrivateKey", "rabbit.ssl_options.key",
    [{datatype, string}]}.

{mapping, "ssl_options.key.PrivateKeyInfo", "rabbit.ssl_options.key",
    [{datatype, string}]}.

{translation, "rabbit.ssl_options.key",
fun(Conf) ->
    case cuttlefish_variable:filter_by_prefix("ssl_options.key", Conf) of
        [{[_,_,Key], Val}|_] -> {list_to_atom(Key), list_to_binary(Val)};
        _ -> cuttlefish:unset()
    end
end}.

%% Primary listener extras on top of the shared partial.

{mapping, "ssl_options.client_renegotiation",
    "rabbit.ssl_options.client_renegotiation",
    [{datatype, boolean}]}.

{mapping, "ssl_options.honor_cipher_order",
    "rabbit.ssl_options.honor_cipher_order",
    [{datatype, boolean}]}.

{mapping, "ssl_options.honor_ecc_order",
    "rabbit.ssl_options.honor_ecc_order",
    [{datatype, boolean}]}.

{mapping, "ssl_options.log_level", "rabbit.ssl_options.log_level",
    [{datatype, {enum, [emergency, alert, critical, error,
                        warning, notice, info, debug]}}]}.

{mapping, "ssl_options.crl_sources.$index", "rabbit.ssl_options.crl_cache",
    [{datatype, {enum, [http, dir]}}]}.

{mapping, "ssl_options.crl_sources.$index.timeout",
    "rabbit.ssl_options.crl_cache",
    [{datatype, integer}]}.

{mapping, "ssl_options.crl_sources.$index.path",
    "rabbit.ssl_options.crl_cache",
    [{datatype, string}]}.

{translation, "rabbit.ssl_options.crl_cache",
fun(Conf) ->
    Entries = cuttlefish_variable:filter_by_prefix("ssl_options.crl_sources", Conf),
    case Entries of
        [] -> cuttlefish:unset();
        _ ->
            Sources = lists:foldl(
                fun({[_, _, Index, Opt], Val}, Acc) ->
                    K = list_to_atom(Index),
                    [{K, {list_to_existing_atom(Opt), Val}} | Acc];
                   ({[_, _, Index], Val}, Acc) ->
                    K = list_to_atom(Index),
                    [{K, {type, Val}} | Acc]
                end, [], Entries),
            Grouped = maps:to_list(maps:groups_from_list(fun({K, _}) -> K end, fun({_, V}) -> V end, Sources)),
            CrlOpts = lists:map(
                fun({_, Props}) ->
                    Type = proplists:get_value(type, Props),
                    case Type of
                        http ->
                            case proplists:get_keys(Props) -- [type, timeout] of
                                [] -> {http, proplists:get_value(timeout, Props, 5000)};
                                _ -> cuttlefish:invalid("http source only supports 'timeout' option")
                            end;
                        dir ->
                            case proplists:get_keys(Props) -- [type, path] of
                                [] -> {dir, proplists:get_value(path, Props)};
                                _ -> cuttlefish:invalid("dir source only supports 'path' option")
                            end
                    end
                end, Grouped),
            {ssl_crl_cache, {internal, CrlOpts}}
    end
end}.

%% Replace the partial's plain pass-through translation for password
%% with the rabbit-specific optionally-tagged binary handler.

{translation, "rabbit.ssl_options.password",
fun(Conf) ->
    rabbit_cuttlefish:optionally_tagged_binary("ssl_options.password", Conf)
end}.

{mapping, "ssl_options.bypass_pem_cache", "ssl.bypass_pem_cache",
    [{datatype, boolean}]}.


{mapping, "metadata_store.khepri.default_timeout", "rabbit.khepri_default_timeout",
    [{datatype, integer}]}.

{mapping, "metadata_store.khepri.snapshot_interval", "rabbit.khepri_snapshot_interval",
    [{datatype, {integer, positive}}]}.

%% ===========================================================================

%% Choose the available SASL mechanism(s) to expose.
%% The three default (built in) mechanisms are 'PLAIN', 'AMQPLAIN' and 'ANONYMOUS'.
%% Additional mechanisms can be added via plugins.
%%
%% See https://www.rabbitmq.com/docs/access-control for more details.
%%
%% {auth_mechanisms, ['PLAIN', 'AMQPLAIN', 'ANONYMOUS']},

{mapping, "auth_mechanisms.$name", "rabbit.auth_mechanisms", [
    {datatype, atom}]}.

{translation, "rabbit.auth_mechanisms",
 fun(Conf) ->
         Settings = cuttlefish_variable:filter_by_prefix("auth_mechanisms", Conf),
         Sorted = lists:keysort(1, Settings),
         [V || {_, V} <- Sorted]
 end}.


%% Select an authentication backend to use. RabbitMQ provides an
%% internal backend in the core.
%%
%% {auth_backends, [rabbit_auth_backend_internal]},

{translation, "rabbit.auth_backends",
fun(Conf) ->
    Settings = cuttlefish_variable:filter_by_prefix("auth_backends", Conf),
    BackendModule = fun
        (internal)          -> rabbit_auth_backend_internal;
        (internal_loopback) -> rabbit_auth_backend_internal_loopback;
        (ldap)              -> rabbit_auth_backend_ldap;
        (http)              -> rabbit_auth_backend_http;
        (oauth)             -> rabbit_auth_backend_oauth2;
        (oauth2)            -> rabbit_auth_backend_oauth2;
        (cache)             -> rabbit_auth_backend_cache;
        (amqp)              -> rabbit_auth_backend_amqp;
        (dummy)             -> rabbit_auth_backend_dummy;
        (Other) when is_atom(Other) -> Other;
        (_) -> cuttlefish:invalid("Unknown/unsupported auth backend")
    end,
    AuthBackends  = [{Num, {default, BackendModule(V)}} || {["auth_backends", Num], V}          <- Settings],
    AuthNBackends = [{Num, {authn, BackendModule(V)}}   || {["auth_backends", Num, "authn"], V} <- Settings],
    AuthZBackends = [{Num, {authz, BackendModule(V)}}   || {["auth_backends", Num, "authz"], V} <- Settings],
    Backends = lists:foldl(
        fun({NumStr, {Type, V}}, Acc) ->
            Num = case catch list_to_integer(NumStr) of
                N when is_integer(N) -> N;
                Err ->
                    cuttlefish:invalid(
                        iolist_to_binary(io_lib:format(
                            "Auth backend position in the chain should be an integer ~p", [Err])))
            end,
            NewVal = case dict:find(Num, Acc) of
                {ok, {AuthN, AuthZ}} ->
                    case {Type, AuthN, AuthZ} of
                        {authn, undefined, _} ->
                            {V, AuthZ};
                        {authz, _, undefined} ->
                            {AuthN, V};
                        _ ->
                            cuttlefish:invalid(
                                iolist_to_binary(
                                    io_lib:format(
                                        "Auth backend already defined for the ~pth ~p backend",
                                        [Num, Type])))
                    end;
                error ->
                    case Type of
                        authn   -> {V, undefined};
                        authz   -> {undefined, V};
                        default -> {V, V}
                    end
            end,
            dict:store(Num, NewVal, Acc)
        end,
        dict:new(),
        AuthBackends ++ AuthNBackends ++ AuthZBackends),
    lists:map(
        fun
            ({Num, {undefined, AuthZ}}) ->
                cuttlefish:warn(
                    io_lib:format(
                        "Auth backend undefined for the ~pth authz backend. Using ~p",
                        [Num, AuthZ])),
                {AuthZ, AuthZ};
            ({Num, {AuthN, undefined}}) ->
                cuttlefish:warn(
                    io_lib:format(
                        "Authz backend undefined for the ~pth authn backend. Using ~p",
                        [Num, AuthN])),
                {AuthN, AuthN};
            ({_Num, {Auth, Auth}})   -> Auth;
            ({_Num, {AuthN, AuthZ}}) -> {AuthN, AuthZ}
        end,
        lists:keysort(1, dict:to_list(Backends)))
end}.

{mapping, "auth_backends.$num", "rabbit.auth_backends", [
    {datatype, atom}
]}.

{mapping, "auth_backends.$num.authn", "rabbit.auth_backends",[
    {datatype, atom}
]}.

{mapping, "auth_backends.$num.authz", "rabbit.auth_backends",[
    {datatype, atom}
]}.

%% This pertains to both the rabbitmq_auth_mechanism_ssl plugin and
%% STOMP ssl_cert_login configurations. See the rabbitmq_stomp
%% configuration section later in this file and the README in
%% https://github.com/rabbitmq/rabbitmq-auth-mechanism-ssl for further
%% details.
%%
%% To use the peer certificate's Common Name (CN) field
%% instead of its Distinguished Name (DN) for username extraction.
%%
%% {ssl_cert_login_from, common_name},
%%
%% To use the first SAN value of type DNS:
%%
%% {ssl_cert_login_from,      subject_alternative_name},
%% {ssl_cert_login_san_type,  dns},
%% {ssl_cert_login_san_index, 0}

{mapping, "ssl_cert_login_from", "rabbit.ssl_cert_login_from", [
    {datatype, {enum, [distinguished_name, common_name, subject_alternative_name, subject_alt_name]}}
]}.

{mapping, "ssl_cert_login_san_type", "rabbit.ssl_cert_login_san_type", [
    {datatype, {enum, [dns, ip, email, uri, other_name]}}
]}.

{mapping, "ssl_cert_login_san_index", "rabbit.ssl_cert_login_san_index", [
    {datatype, {integer, non_negative}}
]}.

%% TLS handshake timeout, in milliseconds.
%%
%% {ssl_handshake_timeout, 5000},

{mapping, "ssl_handshake_timeout", "rabbit.ssl_handshake_timeout", [
    {datatype, integer}
]}.

%% Cluster name

{mapping, "cluster_name", "rabbit.cluster_name", [
    {datatype, string}
]}.

%% Default worker process pool size. Used to limit maximum concurrency rate
%% of certain operations, e.g. queue initialisation and recovery on node boot.

{mapping, "default_worker_pool_size", "rabbit.default_worker_pool_size", [
    {datatype, {integer, non_negative}}
]}.

%% Password hashing implementation. Will only affect newly
%% created users. To recalculate hash for an existing user
%% it's necessary to update her password.
%%
%% When importing definitions exported from versions earlier
%% than 3.6.0, it is possible to go back to MD5 (only do this
%% as a temporary measure!) by setting this to rabbit_password_hashing_md5.
%%
%% To use SHA-512, set to rabbit_password_hashing_sha512.
%%
%% {password_hashing_module, rabbit_password_hashing_sha256},

{mapping, "password_hashing_module", "rabbit.password_hashing_module", [
    {datatype, atom}
]}.

%% Credential validation.
%%

{mapping, "credential_validator.validation_backend", "rabbit.credential_validator.validation_backend", [
    {datatype, atom}
]}.

{mapping, "credential_validator.min_length", "rabbit.credential_validator.min_length", [
    {datatype, {integer, non_negative}}
]}.

{mapping, "credential_validator.regexp", "rabbit.credential_validator.regexp", [
    {datatype, string}
]}.

%%
%% Default User / VHost
%% ====================
%%

%% On first start RabbitMQ will create a vhost and a user. These
%% config items control what gets created. See
%% https://www.rabbitmq.com/docs/access-control for further
%% information about vhosts and access control.
%%
%% {default_vhost,       <<"/">>},
%% {default_user,        <<"guest">>},
%% {default_pass,        <<"guest">>},
%% {default_permissions, [<<".*">>, <<".*">>, <<".*">>]},

{mapping, "default_vhost", "rabbit.default_vhost", [
    {datatype, string}
]}.

{translation, "rabbit.default_vhost",
fun(Conf) ->
    list_to_binary(cuttlefish:conf_get("default_vhost", Conf))
end}.

{mapping, "default_user", "rabbit.default_user", [
    {datatype, string}
]}.

{translation, "rabbit.default_user",
fun(Conf) ->
    list_to_binary(cuttlefish:conf_get("default_user", Conf))
end}.

{mapping, "default_pass", "rabbit.default_pass", [
    {datatype, [tagged_binary, binary]}
]}.

{translation, "rabbit.default_pass",
fun(Conf) ->
    rabbit_cuttlefish:optionally_tagged_binary("default_pass", Conf)
end}.

{mapping, "default_permissions.configure", "rabbit.default_permissions", [
    {datatype, string}
]}.

{mapping, "default_permissions.read", "rabbit.default_permissions", [
    {datatype, string}
]}.

{mapping, "default_permissions.write", "rabbit.default_permissions", [
    {datatype, string}
]}.

{translation, "rabbit.default_permissions",
fun(Conf) ->
    Settings  = cuttlefish_variable:filter_by_prefix("default_permissions", Conf),
    Configure = proplists:get_value(["default_permissions", "configure"], Settings),
    Read      = proplists:get_value(["default_permissions", "read"], Settings),
    Write     = proplists:get_value(["default_permissions", "write"], Settings),
    [list_to_binary(Configure), list_to_binary(Read), list_to_binary(Write)]
end}.

%%
%% Extra Default Users
%% ====================
%%

{mapping, "default_users.$name.vhost_pattern", "rabbit.default_users", [
    {validators, ["valid_regex"]},
    {datatype, string}
]}.

{mapping, "default_users.$name.password", "rabbit.default_users", [
    {datatype, [tagged_binary, binary]}
]}.

{mapping, "default_users.$name.configure", "rabbit.default_users", [
    {validators, ["valid_regex"]},
    {datatype, string}
]}.

{mapping, "default_users.$name.read", "rabbit.default_users", [
    {validators, ["valid_regex"]},
    {datatype, string}
]}.

{mapping, "default_users.$name.write", "rabbit.default_users", [
    {validators, ["valid_regex"]},
    {datatype, string}
]}.

{mapping, "default_users.$name.tags", "rabbit.default_users", [
    {datatype, {list, atom}}
]}.

{translation, "rabbit.default_users", fun(Conf) ->
    case rabbit_cuttlefish:aggregate_props(Conf, ["default_users"]) of
        [] -> cuttlefish:unset();
        Props -> Props
    end
end}.

%% Connections that skip SASL layer or use SASL mechanism ANONYMOUS will use this identity.
%% Setting this to a username will allow (anonymous) clients to connect and act as this
%% given user. For production environments, set this value to 'none'.
{mapping, "anonymous_login_user", "rabbit.anonymous_login_user",
    [{datatype, [{enum, [none]}, binary]}]}.

{mapping, "anonymous_login_pass", "rabbit.anonymous_login_pass", [
    {datatype, [tagged_binary, binary]}
]}.

{translation, "rabbit.anonymous_login_pass",
fun(Conf) ->
    rabbit_cuttlefish:optionally_tagged_binary("anonymous_login_pass", Conf)
end}.


%%
%% Default Policies
%% ====================
%%

{mapping, "default_policies.operator.$id.vhost_pattern", "rabbit.default_policies.operator", [
    {validators, ["valid_regex"]},
    {datatype, string}
]}.

{mapping, "default_policies.operator.$id.queue_pattern", "rabbit.default_policies.operator", [
    {validators, ["valid_regex"]},
    {datatype, string}
]}.

{mapping, "default_policies.operator.$id.apply_to", "rabbit.default_policies.operator", [
    {datatype, string}
]}.

{mapping, "default_policies.operator.$id.expires", "rabbit.default_policies.operator", [
    {datatype, {duration, ms}}
]}.

{mapping, "default_policies.operator.$id.message_ttl", "rabbit.default_policies.operator", [
    {datatype, {duration, ms}}
]}.

{mapping, "default_policies.operator.$id.max_length", "rabbit.default_policies.operator", [
    {datatype, {integer, positive}}
]}.

{mapping, "default_policies.operator.$id.max_length_bytes", "rabbit.default_policies.operator", [
    {validators, ["non_zero_positive_integer"]},
    {datatype, bytesize}
]}.

{mapping, "default_policies.operator.$id.max_in_memory_bytes", "rabbit.default_policies.operator", [
        {validators, ["non_zero_positive_integer"]},
        {datatype, bytesize}
    ]}.

{mapping, "default_policies.operator.$id.max_in_memory_length", "rabbit.default_policies.operator",
    [
        {datatype, {integer, positive}}
    ]}.

{mapping, "default_policies.operator.$id.delivery_limit", "rabbit.default_policies.operator", [
    {datatype, {integer, positive}}
]}.

{mapping, "default_policies.operator.$id.classic_queues.ha_mode", "rabbit.default_policies.operator", [
    {datatype, string}
]}.

{mapping, "default_policies.operator.$id.classic_queues.ha_params", "rabbit.default_policies.operator", [
    {datatype, [integer, {list, string}]}
]}.

{mapping, "default_policies.operator.$id.classic_queues.ha_sync_mode", "rabbit.default_policies.operator", [
    {datatype, string}
]}.

{mapping, "default_policies.operator.$id.classic_queues.queue_version", "rabbit.default_policies.operator",
    [
        {datatype, {integer, positive}}
    ]}.

{translation, "rabbit.default_policies.operator", fun(Conf) ->
    Props = rabbit_cuttlefish:aggregate_props(
                Conf,
                ["default_policies", "operator"],
                fun({["default_policies","operator",ID,"classic_queues"|T], V}) ->
                    NewV = case T of
                              ["ha_sync_mode"] ->
                                  list_to_binary(V);
                              ["ha_mode"] ->
                                  list_to_binary(V);
                              _ -> V
                          end,
                    {["default_policies","operator",ID|T], NewV};
                   ({["default_policies","operator",ID, "queue_pattern"], V}) ->
                    {["default_policies","operator",ID, "queue_pattern"], list_to_binary(V)};
                   ({["default_policies","operator",ID, "apply_to"], V}) ->
                    {["default_policies","operator",ID, "apply_to"], list_to_binary(V)};
                   (E) -> E
                end),
    case Props of
        [] -> cuttlefish:unset();
        Props -> Props
    end
end}.

%%
%% Default VHost Limits
%% ====================
%%

{mapping, "default_limits.vhosts.$id.pattern", "rabbit.default_limits.vhosts", [
    {validators, ["valid_regex"]},
    {datatype, string}
]}.

{mapping, "default_limits.vhosts.$id.max_connections", "rabbit.default_limits.vhosts", [
    {datatype, {integer, positive}}
]}.

{mapping, "default_limits.vhosts.$id.max_queues", "rabbit.default_limits.vhosts", [
    {datatype, {integer, positive}}
]}.

{translation, "rabbit.default_limits.vhosts", fun(Conf) ->
    case rabbit_cuttlefish:aggregate_props(Conf, ["default_limits", "vhosts"]) of
        [] -> cuttlefish:unset();
        Props -> Props
    end
end}.

%% Tags for default user
%%
%% For more details about tags, see the documentation for the
%% Management Plugin at https://www.rabbitmq.com/docs/management.
%%
%% {default_user_tags, [administrator]},

{mapping, "default_user_tags.$tag", "rabbit.default_user_tags",
    [{datatype, boolean}]}.

{translation, "rabbit.default_user_tags",
fun(Conf) ->
    Settings  = cuttlefish_variable:filter_by_prefix("default_user_tags", Conf),
    [ list_to_atom(Key) || {[_,Key], Val} <- Settings, Val == true ]
end}.

%%
%% Additional network and protocol related configuration
%% =====================================================
%%

%% Set the default connection heartbeat timeout (in seconds).
%%
%% {heartbeat, 600},

{mapping, "heartbeat", "rabbit.heartbeat", [{datatype, integer}]}.

%% Set the max permissible size of an AMQP 0-9-1 frame (in bytes).
%%
%% {frame_max, 131072},

{mapping, "frame_max", "rabbit.frame_max", [{datatype, bytesize}]}.

%% Set the max frame size the server will accept before connection
%% tuning starts
%%
%% {initial_frame_max, 4096},

{mapping, "initial_frame_max", "rabbit.initial_frame_max", [{datatype, bytesize}]}.

%% Set the max permissible number of channels per connection.
%% 0 means "no limit".
%%
%% {max_channels_per_connection, 0},

{mapping, "max_channels_per_connection", "rabbit.channel_max",
  [{datatype, integer}, {alias, "channel_max"}]}.

{mapping, "channel_max_per_node", "rabbit.channel_max_per_node",
  [{datatype, [{atom, infinity}, integer]}]}.

{translation, "rabbit.channel_max_per_node",
 fun(Conf) ->
  case cuttlefish:conf_get("channel_max_per_node", Conf, undefined) of
      undefined -> cuttlefish:unset();
      infinity  -> infinity;
      Val when is_integer(Val) andalso Val > 0 -> Val;
      _         -> cuttlefish:invalid("should be positive integer or 'infinity'")
  end
 end
}.

%% Set the max allowed number of consumers per channel.
%% `infinity` means "no limit".
%%
%% {consumer_max_per_channel, infinity},

{mapping, "consumer_max_per_channel", "rabbit.consumer_max_per_channel",
  [{datatype, [{atom, infinity}, integer]}]}.

{translation, "rabbit.consumer_max_per_channel",
 fun(Conf) ->
  case cuttlefish:conf_get("consumer_max_per_channel", Conf, undefined) of
      undefined -> cuttlefish:unset();
      infinity  -> infinity;
      Val when is_integer(Val) andalso Val > 0 -> Val;
      _         -> cuttlefish:invalid("should be positive integer or 'infinity'")
  end
 end
}.

%% Sets the maximum number of AMQP 1.0 sessions that can be simultaneously
%% active on an AMQP 1.0 connection.
%%
%% {session_max_per_connection, 1},
{mapping, "session_max_per_connection", "rabbit.session_max_per_connection",
 [{datatype, {integer, [{min, 1}, {max, 65535}]}}]}.

%% Sets the maximum number of AMQP 1.0 links that can be simultaneously
%% active on an AMQP 1.0 session.
%%
%% {link_max_per_session, 10},
{mapping, "link_max_per_session", "rabbit.link_max_per_session",
 [{datatype, {integer, [{min, 1}, {max, 4294967295}]}}]}.

%% Set the max permissible number of client connections per node.
%% `infinity` means "no limit".
%%
%% {max_connections, infinity},

{mapping, "max_connections", "rabbit.connection_max",
  [{datatype, [{atom, infinity}, integer]},
   {alias, "connection_max"}]}.

{translation, "rabbit.connection_max",
 fun(Conf) ->
  case cuttlefish:conf_get("max_connections", Conf, undefined) of
      undefined -> cuttlefish:unset();
      infinity  -> infinity;
      Val when is_integer(Val) -> Val;
      _         -> cuttlefish:invalid("should be a non-negative integer")
  end
 end
}.

{mapping, "ranch_connection_max", "rabbit.ranch_connection_max",
  [{datatype, [{atom, infinity}, integer]}]}.

{translation, "rabbit.ranch_connection_max",
 fun(Conf) ->
  case cuttlefish:conf_get("ranch_connection_max", Conf, undefined) of
      undefined -> cuttlefish:unset();
      infinity  -> infinity;
      Val when is_integer(Val) -> Val;
      _         -> cuttlefish:invalid("should be a non-negative integer")
  end
 end
}.

{mapping, "vhost_max", "rabbit.vhost_max",
  [{datatype, [{atom, infinity}, integer]}, {validators, ["non_negative_integer"]}]}.

{translation, "rabbit.vhost_max",
 fun(Conf) ->
  case cuttlefish:conf_get("vhost_max", Conf, undefined) of
      undefined -> cuttlefish:unset();
      infinity  -> infinity;
      Val when is_integer(Val) -> Val;
      _         -> cuttlefish:invalid("should be a non-negative integer")
  end
 end
}.

{mapping, "max_message_size", "rabbit.max_message_size",
    [{datatype, integer}, {validators, ["max_message_size"]}]}.

{mapping, "cluster_exchange_limit", "rabbit.cluster_exchange_limit",
  [{datatype, [{atom, infinity}, integer]}, {validators, ["non_negative_integer"]}]}.

{translation, "rabbit.cluster_exchange_limit",
 fun(Conf) ->
  case cuttlefish:conf_get("cluster_exchange_limit", Conf, undefined) of
      undefined -> cuttlefish:unset();
      infinity  -> infinity;
      Val when is_integer(Val) -> Val;
      _         -> cuttlefish:invalid("should be a non-negative integer")
  end
 end
}.

%% Customising Socket Options.
%%
%% See (https://www.erlang.org/doc/man/inet.html#setopts-2) for
%% further documentation.
%%
%% {tcp_listen_options, [{backlog,       128},
%%                       {nodelay,       true},
%%                       {exit_on_close, false}]},

%% TCP listener section ======================================================

{include_partial, {rabbit, "tcp_listen_options"},
    [{prefix, "tcp_listen_options"},
     {app_prefix, "rabbit.tcp_listen_options"},
     {disable_with, none}]}.


%% ==========================================================================

%%
%% Resource Limits & Flow Control
%% ==============================
%%
%% See https://www.rabbitmq.com/docs/memory for full details.

%% Memory-based Flow Control threshold.
%%
%% {vm_memory_high_watermark, 0.6},

%% Alternatively, we can set a limit (in bytes) of RAM used by the node.
%%
%% {vm_memory_high_watermark, {absolute, 1073741824}},
%%
%% Or you can set absolute value using memory unit symbols (with RabbitMQ 3.6.0+).
%%
%% {vm_memory_high_watermark, {absolute, "1024M"}},
%%
%% Supported unit symbols:
%%
%% k, kiB: kibibytes (2^10 - 1,024 bytes)
%% M, MiB: mebibytes (2^20 - 1,048,576 bytes)
%% G, GiB: gibibytes (2^30 - 1,073,741,824 bytes)
%% kB: kilobytes (10^3 - 1,000 bytes)
%% MB: megabytes (10^6 - 1,000,000 bytes)
%% GB: gigabytes (10^9 - 1,000,000,000 bytes)

{mapping, "vm_memory_high_watermark.relative", "rabbit.vm_memory_high_watermark", [
    {datatype, float}
]}.

{mapping, "vm_memory_high_watermark.absolute", "rabbit.vm_memory_high_watermark", [
    {datatype, [integer, string]},
    {validators, ["is_supported_information_unit"]}
]}.


{translation, "rabbit.vm_memory_high_watermark",
fun(Conf) ->
    Settings = cuttlefish_variable:filter_by_prefix("vm_memory_high_watermark", Conf),
    Absolute = proplists:get_value(["vm_memory_high_watermark", "absolute"], Settings),
    Relative = proplists:get_value(["vm_memory_high_watermark", "relative"], Settings),
    case {Absolute, Relative} of
        {undefined, undefined} -> cuttlefish:invalid("No vm watermark defined");
        {_,         undefined} -> {absolute, Absolute};
        {undefined, _}         -> Relative;
        _                      ->
            cuttlefish:warn("Both vm_memory_high_watermark.absolute and "
                            "vm_memory_high_watermark.relative are configured. "
                            "vm_memory_high_watermark.absolute has precedence"),
            {absolute, Absolute}
    end
end}.

%% DEPRECATED. Not used since RabbitMQ 4.0
%%
%% Fraction of the high watermark limit at which queues start to
%% page message out to disc in order to free up memory.
%%
%% Values greater than 0.9 can be dangerous and should be used carefully.
%%
%% {vm_memory_high_watermark_paging_ratio, 0.5},

{mapping, "vm_memory_high_watermark_paging_ratio",
          "rabbit.vm_memory_high_watermark_paging_ratio",
          [{datatype, {float, [{gt, 0}, {lt, 1}]}}]}.

%% DEPRECATED. Not used since RabbitMQ 4.0
%%
%% Interval (in milliseconds) at which we perform the check of the memory
%% levels against the watermarks.
%%
%% {memory_monitor_interval, 2500},

{mapping, "memory_monitor_interval", "rabbit.memory_monitor_interval",
    [{datatype, integer}]}.

%% Selects Erlang VM memory consumption calculation strategy.
%% Can be `allocated`, `rss` or `legacy` (aliased as `erlang`).
%%
%% {vm_memory_calculation_strategy, rss},

{mapping, "vm_memory_calculation_strategy", "rabbit.vm_memory_calculation_strategy",
    [{datatype, {enum, [rss, erlang, allocated, legacy]}}]}.

%% The total memory available can be calculated from the OS resources
%% (default option) or provided as a configuration parameter
{mapping, "total_memory_available_override_value", "rabbit.total_memory_available_override_value", [
    {datatype, [integer, string]}]}.

%% Set disk free limit (in bytes). Once free disk space reaches this
%% lower bound, a disk alarm will be set - see the documentation
%% listed above for more details.
%%
%% {disk_free_limit, 50000000},
%%
%% Or you can set it using memory units (same as in vm_memory_high_watermark)
%% {disk_free_limit, "50MB"},
%% {disk_free_limit, "50000kB"},
%% {disk_free_limit, "2GB"},

%% Alternatively, we can set a limit relative to total available RAM.
%%
%% Values lower than 1.0 can be dangerous and should be used carefully.
%% {disk_free_limit, {mem_relative, 2.0}},

{mapping, "disk_free_limit.relative", "rabbit.disk_free_limit", [
    {datatype, float}]}.

{mapping, "disk_free_limit.absolute", "rabbit.disk_free_limit", [
    {datatype, [integer, string]},
    {validators, ["is_supported_information_unit"]}
]}.


{translation, "rabbit.disk_free_limit",
fun(Conf) ->
    Settings = cuttlefish_variable:filter_by_prefix("disk_free_limit", Conf),
    Absolute = proplists:get_value(["disk_free_limit", "absolute"], Settings),
    Relative = proplists:get_value(["disk_free_limit", "relative"], Settings),
    case {Absolute, Relative} of
        {undefined, undefined} -> cuttlefish:invalid("No disk limit defined");
        {_,         undefined} -> Absolute;
        {undefined, _}         -> {mem_relative, Relative};
        _                      ->
            cuttlefish:warn("Both disk_free_limit.absolute and "
                            "disk_free_limit.relative are configured. "
                            "disk_free_limit.absolute has precedence"),
            Absolute
    end
end}.

%%
%% Clustering
%% =====================
%%

%% No longer used, exists only for backwards compatibility
{mapping, "cluster_partition_handling", "rabbit.cluster_partition_handling",
    [{datatype, {enum, [ignore, pause_minority, autoheal, pause_if_all_down]}}]}.

%% No longer used, exists only for backwards compatibility
{mapping, "cluster_partition_handling.pause_if_all_down.recover",
          "rabbit.cluster_partition_handling",
          [{datatype, {enum, [ignore, autoheal]}}]}.

%% No longer used, exists only for backwards compatibility
{mapping, "cluster_partition_handling.pause_if_all_down.nodes.$name",
          "rabbit.cluster_partition_handling",
          [{datatype, atom}]}.

%% No longer used, exists only for backwards compatibility
{translation, "rabbit.cluster_partition_handling",
fun(Conf) ->
    case cuttlefish:conf_get("cluster_partition_handling", Conf) of
        pause_if_all_down ->
            PauseIfAllDownNodes = cuttlefish_variable:filter_by_prefix(
                "cluster_partition_handling.pause_if_all_down.nodes",
                Conf),
            case PauseIfAllDownNodes of
                [] ->
                    cuttlefish:invalid("Nodes required for pause_if_all_down");
                _  ->
                    Nodes = [ V || {K,V} <- PauseIfAllDownNodes ],
                    PauseIfAllDownRecover = cuttlefish:conf_get(
                        "cluster_partition_handling.pause_if_all_down.recover",
                        Conf),
                    case PauseIfAllDownRecover of
                        Recover when Recover == ignore; Recover == autoheal ->
                            {pause_if_all_down, Nodes, Recover};
                        Invalid ->
                            cuttlefish:invalid("Recover strategy required for pause_if_all_down")
                    end
            end;
        Other -> Other
    end
end}.

%% Number of delegate processes to use for intra-cluster
%% communication. On a machine which has a very large number of cores
%% and is also part of a cluster, you may wish to increase this value.
%%

{mapping, "delegate_count", "rabbit.delegate_count", [
    {datatype, {integer, non_negative}}
]}.

%% Mirror sync batch size, in messages. Increasing this will speed
%% up syncing but total batch size in bytes must not exceed 2 GiB.
%% Available in RabbitMQ 3.6.0 or later.
%%
%% {mirroring_sync_batch_size, 4096},

{mapping, "mirroring_sync_batch_size", "rabbit.mirroring_sync_batch_size",
    [{datatype, bytesize}, {validators, ["mirroring_sync_batch_size"]}]}.

%% Mirror sync max throughput (in bytes) per second.
%% Supported unit symbols:
%% k, kiB: kibibytes (2^10 - 1,024 bytes)
%% M, MiB: mebibytes (2^20 - 1,048,576 bytes)
%% G, GiB: gibibytes (2^30 - 1,073,741,824 bytes)
%% kB: kilobytes (10^3 - 1,000 bytes)
%% MB: megabytes (10^6 - 1,000,000 bytes)
%% GB: gigabytes (10^9 - 1,000,000,000 bytes)
%%
%% 0 means "no limit".
%%
%% {mirroring_sync_max_throughput, 0},

{mapping, "mirroring_sync_max_throughput", "rabbit.mirroring_sync_max_throughput", [
    {datatype, [integer, string]}
]}.

%% Peer discovery backend used by cluster formation.
%%

{mapping, "cluster_formation.peer_discovery_backend", "rabbit.cluster_formation.peer_discovery_backend", [
    {datatype, atom}
]}.

{translation, "rabbit.cluster_formation.peer_discovery_backend",
fun(Conf) ->
    case cuttlefish:conf_get("cluster_formation.peer_discovery_backend", Conf, rabbit_peer_discovery_classic_config) of
        classic_config -> rabbit_peer_discovery_classic_config;
        classic        -> rabbit_peer_discovery_classic_config;
        config         -> rabbit_peer_discovery_classic_config;
        dns            -> rabbit_peer_discovery_dns;
        aws            -> rabbit_peer_discovery_aws;
        consul         -> rabbit_peer_discovery_consul;
        etcd           -> rabbit_peer_discovery_etcd;
        kubernetes     -> rabbit_peer_discovery_k8s;
        k8s            -> rabbit_peer_discovery_k8s;
        Module         -> Module
    end
end}.

%% Own node type, used by cluster formation.
%%

{mapping, "cluster_formation.node_type", "rabbit.cluster_formation.node_type", [
    {datatype, {enum, [disc, disk]}}
]}.

{translation, "rabbit.cluster_formation.node_type",
fun(Conf) ->
    %% if peer discovery backend isn't configured, don't generate
    %% node type
    case cuttlefish:conf_get("cluster_formation.peer_discovery_backend", Conf, undefined) of
        undefined -> cuttlefish:unset();
        _Backend  ->
            case cuttlefish:conf_get("cluster_formation.node_type", Conf) of
                disc      -> disc;
                %% always cast to `disc`
                disk      -> disc;
                _Other    -> disc
            end
    end
end}.

%% Register node during cluster formation when backend supports registration.
%%

{mapping, "cluster_formation.registration.enabled", "rabbit.cluster_formation.perform_registration", [
    {datatype, boolean}
]}.

%% Cluster formation: lock acquisition retries as passed to https://erlang.org/doc/man/global.html#set_lock-3
%%
%% Currently used in classic, k8s, and aws peer discovery backends.

{mapping, "cluster_formation.internal_lock_retries", "rabbit.cluster_formation.internal_lock_retries",
    [
     {datatype, {integer, positive}}
    ]}.

%% Cluster formation: discovery failure retries

{mapping, "cluster_formation.lock_retry_limit", "rabbit.cluster_formation.lock_retry_limit",
    [
     {datatype, {integer, positive}}
    ]}.
{mapping, "cluster_formation.lock_retry_timeout", "rabbit.cluster_formation.lock_retry_timeout",
    [
     {datatype, {integer, positive}}
    ]}.

{mapping, "cluster_formation.discovery_retry_limit", "rabbit.cluster_formation.discovery_retry_limit",
    [{datatype, [{atom, unlimited}, integer]}]}.

{translation, "rabbit.cluster_formation.discovery_retry_limit",
 fun(Conf) ->
   case cuttlefish:conf_get("cluster_formation.discovery_retry_limit", Conf, undefined) of
      undefined -> cuttlefish:unset();
      unlimited  -> unlimited;
      Val when is_integer(Val) andalso Val > 0 -> Val;
      _         -> cuttlefish:invalid("should be positive integer or 'unlimited'")
  end
 end
}.

{mapping, "cluster_formation.discovery_retry_interval", "rabbit.cluster_formation.discovery_retry_interval",
    [
     {datatype, {integer, positive}}
    ]}.


%% Target cluster size hint may be used by certain core features or plugins to perform
%% actions that should only be performed when a certain number of nodes (or a quorum of a certain number)
%% has already joined (started).
%%

{mapping, "cluster_formation.target_cluster_size_hint", "rabbit.cluster_formation.target_cluster_size_hint", [
    {datatype, {integer, positive}}
]}.


%% Classic config-driven peer discovery backend.
%%
%% Make clustering happen *automatically* at startup - only applied
%% to nodes that have just been reset or started for the first time.
%% See https://www.rabbitmq.com/docs/clustering#auto-config for
%% further details.
%%
%% {cluster_nodes, {['rabbit@my.host.com'], disc}},

{mapping, "cluster_formation.classic_config.nodes.$node", "rabbit.cluster_nodes",
    [{datatype, atom}]}.

{translation, "rabbit.cluster_nodes",
fun(Conf) ->
    Nodes = [V || {_, V} <- cuttlefish_variable:filter_by_prefix("cluster_formation.classic_config.nodes", Conf)],

    case Nodes of
        []    -> cuttlefish:unset();
        Other ->
            case cuttlefish:conf_get("cluster_formation.node_type", Conf, disc) of
                disc -> {Other, disc};
                %% Always cast to `disc`
                disk -> {Other, disc}
            end
    end
end}.

%% DNS (A records and reverse lookups)-based peer discovery.
%%

{mapping, "cluster_formation.dns.hostname", "rabbit.cluster_formation.peer_discovery_dns.hostname",
    [{datatype, string}]}.

{translation, "rabbit.cluster_formation.peer_discovery_dns.hostname",
fun(Conf) ->
    case cuttlefish:conf_get("cluster_formation.dns.hostname", Conf, undefined) of
        undefined -> cuttlefish:unset();
        Value     -> list_to_binary(Value)
    end
end}.


{mapping, "cluster_queue_limit", "rabbit.cluster_queue_limit",
  [{datatype, [{atom, infinity}, integer]}]}.

{translation, "rabbit.cluster_queue_limit",
 fun(Conf) ->
  case cuttlefish:conf_get("cluster_queue_limit", Conf, undefined) of
      undefined -> cuttlefish:unset();
      infinity  -> infinity;
      Val when is_integer(Val) andalso Val > 0 -> Val;
      _         -> cuttlefish:invalid("should be positive integer or 'infinity'")
  end
 end
}.


%% Interval (in milliseconds) at which we send keepalive messages
%% to other cluster members. Note that this is not the same thing
%% as net_ticktime; missed keepalive messages will not cause nodes
%% to be considered down.
%%
%% {cluster_keepalive_interval, 10000},

{mapping, "cluster_keepalive_interval", "rabbit.cluster_keepalive_interval",
    [{datatype, integer}]}.

%% Queue master locator (classic queues)
%%
%% For backwards compatibility only as of 4.0.
%% We still allow values of min-masters, random and client-local
%% but the behaviour is only local or balanced.
%% Use queue_leader_locator instead.

{mapping, "queue_master_locator", "rabbit.queue_master_locator",
    [{datatype, string}]}.

{translation, "rabbit.queue_master_locator",
fun(Conf) ->
    list_to_binary(cuttlefish:conf_get("queue_master_locator", Conf))
end}.

%% Queue leader locator (quorum queues and streams)
%%

{mapping, "queue_leader_locator", "rabbit.queue_leader_locator",
    [{datatype, string}]}.

{translation, "rabbit.queue_leader_locator",
fun(Conf) ->
    list_to_binary(cuttlefish:conf_get("queue_leader_locator", Conf))
end}.

%%
%% Statistics Collection
%% =====================
%%

%% Set (internal) statistics collection granularity.
%%
%% {collect_statistics, none},

{mapping, "collect_statistics", "rabbit.collect_statistics",
    [{datatype, {enum, [none, coarse, fine]}}]}.

%% Statistics collection interval (in milliseconds). Increasing
%% this will reduce the load on management database.
%%
%% {collect_statistics_interval, 5000},

{mapping, "collect_statistics_interval", "rabbit.collect_statistics_interval",
    [{datatype, integer}]}.

%%
%% Misc/Advanced Options
%% =====================
%%
%% NB: Change these only if you understand what you are doing!
%%

%% Explicitly enable/disable hipe compilation.
%%
%% {hipe_compile, true},
%%
%% DEPRECATED: this is a no-op and is kept only to allow old configs.

{mapping, "hipe_compile", "rabbit.hipe_compile",
    [{datatype, boolean}]}.

%% Timeout used when waiting for Mnesia tables in a cluster to
%% become available.
%%
%% {mnesia_table_loading_retry_timeout, 30000},

{mapping, "mnesia_table_loading_retry_timeout", "rabbit.mnesia_table_loading_retry_timeout",
    [{datatype, integer}]}.

%% Retries when waiting for Mnesia tables in the cluster startup. Note that
%% this setting is not applied to Mnesia upgrades or node deletions.
%%
%% {mnesia_table_loading_retry_limit, 10},

{mapping, "mnesia_table_loading_retry_limit", "rabbit.mnesia_table_loading_retry_limit",
    [{datatype, integer}]}.

{mapping, "message_store_shutdown_timeout", "rabbit.msg_store_shutdown_timeout",
    [
        {datatype, {integer, positive}}
    ]}.

%% Size in bytes below which to embed messages in the queue index. See
%% https://www.rabbitmq.com/docs/persistence-conf
%%
%% {queue_index_embed_msgs_below, 4096}

{mapping, "queue_index_embed_msgs_below", "rabbit.queue_index_embed_msgs_below",
    [{datatype, bytesize}]}.

%% Whether or not to enable background GC.
%%
%% {background_gc_enabled, true}

{mapping, "background_gc_enabled", "rabbit.background_gc_enabled",
    [{datatype, boolean}]}.

%% Interval (in milliseconds) at which we run background GC.
%%
%% {background_gc_target_interval, 60000}

{mapping, "background_gc_target_interval", "rabbit.background_gc_target_interval",
    [{datatype, integer}]}.

%% Whether or not to enable proxy protocol support.
%%
%% {proxy_protocol, false}

{mapping, "proxy_protocol", "rabbit.proxy_protocol",
    [{datatype, boolean}]}.

%% Whether to stop the rabbit application if a vhost has
%% to terminate for any reason.

{mapping, "vhost_restart_strategy", "rabbit.vhost_restart_strategy",
    [{datatype, {enum, [stop_node, continue, transient, persistent]}}]}.

%% Approximate maximum time a consumer can spend processing a message before
%% the channel is terminated, in milliseconds.
%%
%% {consumer_timeout, 1800000},

{mapping, "consumer_timeout", "rabbit.consumer_timeout", [
    {datatype, {integer, positive}}
]}.

%% Time to wait before returning messages to the queue when a consumer's
%% node becomes unreachable (due to network partition), in milliseconds.
%% Only applies to quorum queues.
%%
%% {consumer_disconnected_timeout, 10000},

{mapping, "consumer_disconnected_timeout", "rabbit.consumer_disconnected_timeout", [
    {datatype, {integer, positive}}
]}.

%% Product name & version overrides.

{mapping, "product.name", "rabbit.product_name", [
    {datatype, string}
]}.
{mapping, "product.version", "rabbit.product_version", [
    {datatype, string}
]}.

%% Message of the day file.
%% The content of that file is added to the banners, both logged and
%% printed.

{mapping, "motd_file", "rabbit.motd_file", [
    {datatype, string}
]}.


%% Whether to verify if this is the first time a node starts.
%% When enabled, nodes will create a marker file on first startup
%% and refuse to start if the marker exists but tables are empty.
%%

{mapping, "prevent_startup_if_node_was_reset", "rabbit.prevent_startup_if_node_was_reset", [
    {datatype, boolean}
]}.

% ==========================
% Logging section
% ==========================

{mapping, "log.summarize_process_state", "rabbit.summarize_process_state_when_logged", [
    {datatype, boolean}
]}.

{mapping, "log.error_logger_format_depth", "kernel.error_logger_format_depth", [
    {datatype, [{atom, unlimited}, integer]}
]}.
{translation, "kernel.error_logger_format_depth",
 fun(Conf) ->
   case cuttlefish:conf_get("log.error_logger_format_depth", Conf, undefined) of
     undefined -> cuttlefish:unset();
     unlimited -> unlimited;
     Val when is_integer(Val) andalso Val > 0 -> Val;
     _ -> cuttlefish:invalid("should be positive integer or 'unlimited'")
   end
 end
}.

{mapping, "log.dir", "rabbit.log_root", [
    {datatype, string},
    {validators, ["dir_writable"]}]}.

{mapping, "log.console", "rabbit.log.console.enabled", [
    {datatype, boolean}
]}.
{mapping, "log.console.level", "rabbit.log.console.level", [
    {datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.console.stdio", "rabbit.log.console.stdio", [
    {default, stdout},
    {datatype, {enum, [stdout, stderr]}}
]}.
{mapping, "log.console.use_colors", "rabbit.log.console.formatter", [
    {default, on},
    {datatype, flag}
]}.
{mapping, "log.console.color_esc_seqs.debug", "rabbit.log.console.formatter", [
    {default, "\033[38;5;246m"},
    {datatype, string}
]}.
{mapping, "log.console.color_esc_seqs.info", "rabbit.log.console.formatter", [
    {default, ""},
    {datatype, string}
]}.
{mapping, "log.console.color_esc_seqs.notice", "rabbit.log.console.formatter", [
    {default, "\033[38;5;87m"},
    {datatype, string}
]}.
{mapping, "log.console.color_esc_seqs.warning", "rabbit.log.console.formatter", [
    {default, "\033[38;5;214m"},
    {datatype, string}
]}.
{mapping, "log.console.color_esc_seqs.error", "rabbit.log.console.formatter", [
    {default, "\033[38;5;160m"},
    {datatype, string}
]}.
{mapping, "log.console.color_esc_seqs.critical", "rabbit.log.console.formatter", [
    {default, "\033[1;37m\033[48;5;20m"},
    {datatype, string}
]}.
{mapping, "log.console.color_esc_seqs.alert", "rabbit.log.console.formatter", [
    {default, "\033[1;37m\033[48;5;93m"},
    {datatype, string}
]}.
{mapping, "log.console.color_esc_seqs.emergency", "rabbit.log.console.formatter", [
    {default, "\033[1;37m\033[48;5;196m"},
    {datatype, string}
]}.
{mapping, "log.console.formatter", "rabbit.log.console.formatter", [
    {default, plaintext},
    {datatype, {enum, [plaintext, json]}}
]}.
{mapping, "log.console.formatter.time_format", "rabbit.log.console.formatter", [
    {default, rfc3339_space},
    {datatype, {enum, [rfc3339_space, rfc3339_T, epoch_usecs, epoch_secs, lager_default]}}
]}.
{mapping, "log.console.formatter.level_format", "rabbit.log.console.formatter", [
    {default, lc},
    {datatype, {enum, [lc, uc, lc3, uc3, lc4, uc4]}}
]}.
{mapping, "log.console.formatter.single_line", "rabbit.log.console.formatter", [
    {default, off},
    {datatype, flag}
]}.
{mapping, "log.console.formatter.plaintext.format", "rabbit.log.console.formatter", [
    {default, "$time [$level] $pid $msg"},
    {datatype, string}
]}.
{mapping, "log.console.formatter.json.field_map", "rabbit.log.console.formatter", [
    {default, "time level msg gl:- logger_formatter:- report_cb:- error_logger:-"},
    {datatype, string}
]}.
{mapping, "log.console.formatter.json.verbosity_map", "rabbit.log.console.formatter", [
    {default, ""},
    {datatype, string}
]}.
{translation, "rabbit.log.console.formatter",
 fun(Conf) ->
         rabbit_prelaunch_early_logging:translate_formatter_conf("log.console.formatter", Conf)
 end}.

{mapping, "log.exchange", "rabbit.log.exchange.enabled", [
    {datatype, boolean}
]}.
{mapping, "log.exchange.level", "rabbit.log.exchange.level", [
    {datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.exchange.formatter", "rabbit.log.exchange.formatter", [
    {default, plaintext},
    {datatype, {enum, [plaintext, json]}}
]}.
{mapping, "log.exchange.formatter.time_format", "rabbit.log.console.formatter", [
    {default, rfc3339_space},
    {datatype, [{enum, [rfc3339_space, rfc3339_T, epoch_usecs, epoch_secs, lager_default]}, string]}
]}.
{mapping, "log.exchange.formatter.level_format", "rabbit.log.exchange.formatter", [
    {default, lc},
    {datatype, {enum, [lc, uc, lc3, uc3, lc4, uc4]}}
]}.
{mapping, "log.exchange.formatter.single_line", "rabbit.log.exchange.formatter", [
    {default, off},
    {datatype, flag}
]}.
{mapping, "log.exchange.formatter.plaintext.format", "rabbit.log.exchange.formatter", [
    {default, "$time [$level] $pid $msg"},
    {datatype, string}
]}.
{mapping, "log.exchange.formatter.json.field_map", "rabbit.log.exchange.formatter", [
    {default, "time level msg gl:- logger_formatter:- report_cb:- error_logger:-"},
    {datatype, string}
]}.
{mapping, "log.exchange.formatter.json.verbosity_map", "rabbit.log.exchange.formatter", [
    {default, ""},
    {datatype, string}
]}.
{translation, "rabbit.log.exchange.formatter",
 fun(Conf) ->
         rabbit_prelaunch_early_logging:translate_formatter_conf("log.exchange.formatter", Conf)
 end}.

{mapping, "log.journald", "rabbit.log.journald.enabled", [
    {datatype, boolean}
]}.
{mapping, "log.journald.level", "rabbit.log.journald.level", [
    {datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.journald.fields", "rabbit.log.journald.fields", [
    {default, "SYSLOG_IDENTIFIER=\"rabbitmq-server\" syslog_timestamp syslog_pid priority ERL_PID=pid CODE_FILE=file CODE_LINE=line CODE_MFA=mfa"},
    {datatype, string}
]}.
{translation, "rabbit.log.journald.fields",
 fun(Conf) ->
         rabbit_prelaunch_early_logging:translate_journald_fields_conf("log.journald.fields", Conf)
 end}.

{mapping, "log.syslog", "rabbit.log.syslog.enabled", [
    {datatype, boolean}
]}.

{mapping, "log.syslog.level", "rabbit.log.syslog.level", [
    {datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.syslog.formatter", "rabbit.log.syslog.formatter", [
    {default, plaintext},
    {datatype, {enum, [plaintext, json]}}
]}.
{mapping, "log.syslog.formatter.time_format", "rabbit.log.console.formatter", [
    {default, rfc3339_space},
    {datatype, [{enum, [rfc3339_space, rfc3339_T, epoch_usecs, epoch_secs, lager_default]}, string]}
]}.
{mapping, "log.syslog.formatter.level_format", "rabbit.log.syslog.formatter", [
    {default, lc},
    {datatype, {enum, [lc, uc, lc3, uc3, lc4, uc4]}}
]}.
{mapping, "log.syslog.formatter.single_line", "rabbit.log.syslog.formatter", [
    {default, off},
    {datatype, flag}
]}.
{mapping, "log.syslog.formatter.plaintext.format", "rabbit.log.syslog.formatter", [
    {default, "$msg"},
    {datatype, string}
]}.
{mapping, "log.syslog.formatter.json.field_map", "rabbit.log.syslog.formatter", [
    {default, "time level msg gl:- logger_formatter:- report_cb:- error_logger:-"},
    {datatype, string}
]}.
{mapping, "log.syslog.formatter.json.verbosity_map", "rabbit.log.syslog.formatter", [
    {default, ""},
    {datatype, string}
]}.
{translation, "rabbit.log.syslog.formatter",
 fun(Conf) ->
         rabbit_prelaunch_early_logging:translate_formatter_conf("log.syslog.formatter", Conf)
 end}.

{mapping, "log.syslog.identity", "syslog.app_name", [
    {datatype, string}
]}.

{mapping, "log.syslog.facility", "syslog.facility", [
    {datatype, {enum, [kern, kernel, user, mail, daemon, auth, syslog, lpr,
                       news, uucp, cron, authpriv, ftp, ntp, audit, alert,
                       clock, local0, local1, local2, local3, local4,
                       local5, local6, local7]}}
]}.

{mapping, "log.syslog.multiline_mode", "syslog.multiline_mode", [
    {datatype, boolean}
]}.

{mapping, "log.syslog.ip", "syslog.dest_host", [
    {datatype, string},
    {validators, ["is_ip"]}
]}.

{mapping, "log.syslog.host", "syslog.dest_host", [
    {datatype, string}
]}.

{translation, "syslog.dest_host",
fun(Conf) ->
    case cuttlefish:conf_get("log.syslog", Conf) of
        true ->
            case cuttlefish:conf_get("log.syslog.ip", Conf, undefined) of
                undefined ->
                    % If log.syslog.ip is not set, then this must be set
                    cuttlefish:conf_get("log.syslog.host", Conf);
                IpAddr ->
                    IpAddr
            end;
        _ ->
            cuttlefish:invalid("log.syslog must be set to true to set log.syslog.host or log.syslog.ip")
    end
end}.

{mapping, "log.syslog.port", "syslog.dest_port", [
    {datatype, integer}
]}.

{mapping, "log.syslog.transport", "syslog.protocol", [
    {datatype, {enum, [udp, tcp, tls, ssl]}}
]}.
{mapping, "log.syslog.protocol", "syslog.protocol", [
    {datatype, {enum, [rfc3164, rfc5424]}}
]}.
{mapping, "log.syslog.ssl_options.verify", "syslog.protocol", [
    {datatype, {enum, [verify_peer, verify_none]}}]}.

{mapping, "log.syslog.ssl_options.fail_if_no_peer_cert", "syslog.protocol", [
    {datatype, boolean}]}.

{mapping, "log.syslog.ssl_options.cacertfile", "syslog.protocol",
    [{datatype, string}, {validators, ["pem_file"]}]}.

{mapping, "log.syslog.ssl_options.certfile", "syslog.protocol",
    [{datatype, string}, {validators, ["pem_file"]}]}.

{mapping, "log.syslog.ssl_options.cert", "syslog.protocol",
    [{datatype, string}]}.

{mapping, "log.syslog.ssl_options.client_renegotiation", "syslog.protocol",
    [{datatype, boolean}]}.

{mapping, "log.syslog.ssl_options.crl_check", "syslog.protocol",
    [{datatype, [{enum, [true, false, peer, best_effort]}]}]}.

{mapping, "log.syslog.ssl_options.depth", "syslog.protocol",
    [{datatype, byte}]}.

{mapping, "log.syslog.ssl_options.dh", "syslog.protocol",
    [{datatype, string}]}.

{mapping, "log.syslog.ssl_options.dhfile", "syslog.protocol",
    [{datatype, string}, {validators, ["file_accessible"]}]}.

{mapping, "log.syslog.ssl_options.honor_cipher_order", "syslog.protocol",
    [{datatype, boolean}]}.

{mapping, "log.syslog.ssl_options.honor_ecc_order", "syslog.protocol",
    [{datatype, boolean}]}.

{mapping, "log.syslog.ssl_options.key.RSAPrivateKey", "syslog.protocol",
    [{datatype, string}]}.

{mapping, "log.syslog.ssl_options.key.DSAPrivateKey", "syslog.protocol",
    [{datatype, string}]}.

{mapping, "log.syslog.ssl_options.key.PrivateKeyInfo", "syslog.protocol",
    [{datatype, string}]}.

{mapping, "log.syslog.ssl_options.keyfile", "syslog.protocol",
    [{datatype, string}, {validators, ["pem_file"]}]}.

{mapping, "log.syslog.ssl_options.log_alert", "syslog.protocol",
    [{datatype, boolean}]}.

{mapping, "log.syslog.ssl_options.password", "syslog.protocol",
    [{datatype, [tagged_binary, binary]}]}.

{mapping, "log.syslog.ssl_options.psk_identity", "syslog.protocol",
    [{datatype, string}]}.

{mapping, "log.syslog.ssl_options.reuse_sessions", "syslog.protocol",
    [{datatype, boolean}]}.

{mapping, "log.syslog.ssl_options.secure_renegotiate", "syslog.protocol",
    [{datatype, boolean}]}.

{mapping, "log.syslog.ssl_options.versions.$version", "syslog.protocol",
    [{datatype, atom}]}.

{translation, "syslog.protocol",
fun(Conf) ->
    ParseSslOptions = fun() ->
        RawSettings = [
            {verify,               cuttlefish:conf_get("log.syslog.ssl_options.verify", Conf, undefined)},
            {fail_if_no_peer_cert, cuttlefish:conf_get("log.syslog.ssl_options.fail_if_no_peer_cert", Conf, undefined)},
            {cacertfile,           cuttlefish:conf_get("log.syslog.ssl_options.cacertfile", Conf, undefined)},
            {certfile,             cuttlefish:conf_get("log.syslog.ssl_options.certfile", Conf, undefined)},
            {cert,                 cuttlefish:conf_get("log.syslog.ssl_options.cert", Conf, undefined)},
            {client_renegotiation, cuttlefish:conf_get("log.syslog.ssl_options.client_renegotiation", Conf, undefined)},
            {crl_check,            cuttlefish:conf_get("log.syslog.ssl_options.crl_check", Conf, undefined)},
            {depth,                cuttlefish:conf_get("log.syslog.ssl_options.depth", Conf, undefined)},
            {dh,                   cuttlefish:conf_get("log.syslog.ssl_options.dh", Conf, undefined)},
            {dhfile,               cuttlefish:conf_get("log.syslog.ssl_options.dhfile", Conf, undefined)},
            {honor_cipher_order,   cuttlefish:conf_get("log.syslog.ssl_options.honor_cipher_order", Conf, undefined)},
            {honor_ecc_order,      cuttlefish:conf_get("log.syslog.ssl_options.honor_ecc_order", Conf, undefined)},

            {keyfile,              cuttlefish:conf_get("log.syslog.ssl_options.keyfile", Conf, undefined)},
            {log_alert,            cuttlefish:conf_get("log.syslog.ssl_options.log_alert", Conf, undefined)},
            {password,             cuttlefish:conf_get("log.syslog.ssl_options.password", Conf, undefined)},
            {psk_identity,         cuttlefish:conf_get("log.syslog.ssl_options.psk_identity", Conf, undefined)},
            {reuse_sessions,       cuttlefish:conf_get("log.syslog.ssl_options.reuse_sessions", Conf, undefined)},
            {secure_renegotiate,   cuttlefish:conf_get("log.syslog.ssl_options.secure_renegotiate", Conf, undefined)}
            ],
        DefinedSettings = [{K, V} || {K, V} <- RawSettings, V  =/= undefined],

        lists:map(
            fun({K, Val}) when K == dh; K == cert -> {K, list_to_binary(Val)};
               ({K, Val}) -> {K, Val}
            end,
            DefinedSettings) ++
        [ {K, V}
          || {K, V} <-
            [{versions, [ V || {_, V} <- cuttlefish_variable:filter_by_prefix("log.syslog.ssl_options.versions", Conf)]},
             {key, case cuttlefish_variable:filter_by_prefix("log.syslog.ssl_options.key", Conf) of
                      [{[_,_,Key], Val}|_] -> {list_to_atom(Key), list_to_binary(Val)};
                      _ -> undefined
                   end}],
          V =/= undefined,
          V =/= []]
    end,

    Proto = cuttlefish:conf_get("log.syslog.protocol", Conf, undefined),
    Transport = cuttlefish:conf_get("log.syslog.transport", Conf, udp),
    case Transport of
        TLS when TLS == tls; TLS == ssl ->
            case Proto of
                rfc3164 ->
                    cuttlefish:invalid("Syslog protocol rfc3164 is not compatible with TLS");
                _       ->
                    {rfc5424, tls, ParseSslOptions()}
            end;
        _ when Transport == udp; Transport == tcp ->
            case Proto of
                undefined -> {rfc3164, Transport};
                _         -> {Proto, Transport}
            end;
        _ -> cuttlefish:invalid("Invalid syslog transport ~p~n", [Transport])
    end
end}.

{mapping, "log.file", "rabbit.log.file.file", [
    {datatype, [{enum, [false]}, string]}
]}.
{mapping, "log.file.level", "rabbit.log.file.level", [
    {datatype,
        {enum, ['=debug', debug,
                info, '!=info',
                notice, '<=notice',
                '<warning', warning,
                error,
                critical,
                alert,
                emergency,
                none]}}
]}.
{mapping, "log.file.rotation.date", "rabbit.log.file.date", [
    {datatype, string}
]}.
{mapping, "log.file.rotation.compress", "rabbit.log.file.compress", [
    {default, false},
    {datatype, boolean}
]}.
{mapping, "log.file.rotation.size", "rabbit.log.file.size", [
    {datatype, integer}
]}.
{mapping, "log.file.rotation.count", "rabbit.log.file.count", [
    {datatype, integer}
]}.
{mapping, "log.file.formatter", "rabbit.log.file.formatter", [
    {default, plaintext},
    {datatype, {enum, [plaintext, json]}}
]}.
{mapping, "log.file.formatter.time_format", "rabbit.log.file.formatter", [
    {default, rfc3339_space},
    {datatype, [{enum, [rfc3339_space, rfc3339_T, epoch_usecs, epoch_secs, lager_default]}, string]}
]}.
{mapping, "log.file.formatter.level_format", "rabbit.log.file.formatter", [
    {default, lc},
    {datatype, {enum, [lc, uc, lc3, uc3, lc4, uc4]}}
]}.
{mapping, "log.file.formatter.single_line", "rabbit.log.file.formatter", [
    {default, off},
    {datatype, flag}
]}.
{mapping, "log.file.formatter.plaintext.format", "rabbit.log.file.formatter", [
    {default, "$time [$level] $pid $msg"},
    {datatype, string}
]}.
{mapping, "log.file.formatter.json.field_map", "rabbit.log.file.formatter", [
    {default, "time level msg gl:- logger_formatter:- report_cb:- error_logger:-"},
    {datatype, string}
]}.
{mapping, "log.file.formatter.json.verbosity_map", "rabbit.log.file.formatter", [
    {default, ""},
    {datatype, string}
]}.
{translation, "rabbit.log.file.formatter",
 fun(Conf) ->
         rabbit_prelaunch_early_logging:translate_formatter_conf("log.file.formatter", Conf)
 end}.

%% Connection log.
{mapping, "log.connection.level", "rabbit.log.categories.connection.level", [
    {datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.connection.file", "rabbit.log.categories.connection.file", [
    {datatype, string}
]}.
{mapping, "log.connection.rotation.date", "rabbit.log.categories.connection.rotate_on_date", [
    {datatype, string}
]}.
{mapping, "log.connection.rotation.compress", "rabbit.log.categories.connection.compress_on_rotate", [
    {default, false},
    {datatype, boolean}
]}.
{mapping, "log.connection.rotation.size", "rabbit.log.categories.connection.max_no_bytes", [
    {datatype, integer}
]}.
{mapping, "log.connection.rotation.count", "rabbit.log.categories.connection.max_no_files", [
    {datatype, integer}
]}.

%% Channel log.
{mapping, "log.channel.level", "rabbit.log.categories.channel.level", [
    {datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.channel.file", "rabbit.log.categories.channel.file", [
    {datatype, string}
]}.
{mapping, "log.channel.rotation.date", "rabbit.log.categories.channel.rotate_on_date", [
    {datatype, string}
]}.
{mapping, "log.channel.rotation.compress", "rabbit.log.categories.channel.compress_on_rotate", [
    {default, false},
    {datatype, boolean}
]}.
{mapping, "log.channel.rotation.size", "rabbit.log.categories.channel.max_no_bytes", [
    {datatype, integer}
]}.
{mapping, "log.channel.rotation.count", "rabbit.log.categories.channel.max_no_files", [
    {datatype, integer}
]}.

%% Mirroring log.
{mapping, "log.mirroring.level", "rabbit.log.categories.mirroring.level", [
    {datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.mirroring.file", "rabbit.log.categories.mirroring.file", [
    {datatype, string}
]}.
{mapping, "log.mirroring.rotation.date", "rabbit.log.categories.mirroring.rotate_on_date", [
    {datatype, string}
]}.
{mapping, "log.mirroring.rotation.compress", "rabbit.log.categories.mirroring.compress_on_rotate", [
    {default, false},
    {datatype, boolean}
]}.
{mapping, "log.mirroring.rotation.size", "rabbit.log.categories.mirroring.max_no_bytes", [
    {datatype, integer}
]}.
{mapping, "log.mirroring.rotation.count", "rabbit.log.categories.mirroring.max_no_files", [
    {datatype, integer}
]}.

%% Queue log.
{mapping, "log.queue.level", "rabbit.log.categories.queue.level", [
    {datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.queue.file", "rabbit.log.categories.queue.file", [
    {datatype, string}
]}.
{mapping, "log.queue.rotation.date", "rabbit.log.categories.queue.rotate_on_date", [
    {datatype, string}
]}.
{mapping, "log.queue.rotation.compress", "rabbit.log.categories.queue.compress_on_rotate", [
    {default, false},
    {datatype, boolean}
]}.
{mapping, "log.queue.rotation.size", "rabbit.log.categories.queue.max_no_bytes", [
    {datatype, integer}
]}.
{mapping, "log.queue.rotation.count", "rabbit.log.categories.queue.max_no_files", [
    {datatype, integer}
]}.

%% Federation log.
{mapping, "log.federation.level", "rabbit.log.categories.federation.level", [
    {datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.federation.file", "rabbit.log.categories.federation.file", [
    {datatype, string}
]}.
{mapping, "log.federation.rotation.date", "rabbit.log.categories.federation.rotate_on_date", [
    {datatype, string}
]}.
{mapping, "log.federation.rotation.compress", "rabbit.log.categories.federation.compress_on_rotate", [
    {default, false},
    {datatype, boolean}
]}.
{mapping, "log.federation.rotation.size", "rabbit.log.categories.federation.max_no_bytes", [
    {datatype, integer}
]}.
{mapping, "log.federation.rotation.count", "rabbit.log.categories.federation.max_no_files", [
    {datatype, integer}
]}.

%% Upgrade log.
{mapping, "log.upgrade.level", "rabbit.log.categories.upgrade.level", [
    {datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.upgrade.file", "rabbit.log.categories.upgrade.file", [
    {datatype, string}
]}.
{mapping, "log.upgrade.rotation.date", "rabbit.log.categories.upgrade.rotate_on_date", [
    {datatype, string}
]}.
{mapping, "log.upgrade.rotation.compress", "rabbit.log.categories.upgrade.compress_on_rotate", [
    {default, false},
    {datatype, boolean}
]}.
{mapping, "log.upgrade.rotation.size", "rabbit.log.categories.upgrade.max_no_bytes", [
    {datatype, integer}
]}.
{mapping, "log.upgrade.rotation.count", "rabbit.log.categories.upgrade.max_no_files", [
    {datatype, integer}
]}.

%% User log.
{mapping, "log.user.level", "rabbit.log.categories.user.level", [
    {datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.user.file", "rabbit.log.categories.user.file", [
    {datatype, string}
]}.
{mapping, "log.user.rotation.date", "rabbit.log.categories.user.rotate_on_date", [
    {datatype, string}
]}.
{mapping, "log.user.rotation.compress", "rabbit.log.categories.user.compress_on_rotate", [
    {default, false},
    {datatype, boolean}
]}.
{mapping, "log.user.rotation.size", "rabbit.log.categories.user.max_no_bytes", [
    {datatype, integer}
]}.
{mapping, "log.user.rotation.count", "rabbit.log.categories.user.max_no_files", [
    {datatype, integer}
]}.

%% Ra log.
{mapping, "log.ra.level", "rabbit.log.categories.ra.level", [
    {datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.ra.file", "rabbit.log.categories.ra.file", [
    {datatype, string}
]}.
{mapping, "log.ra.rotation.date", "rabbit.log.categories.ra.rotate_on_date", [
    {datatype, string}
]}.
{mapping, "log.ra.rotation.compress", "rabbit.log.categories.ra.compress_on_rotate", [
    {default, false},
    {datatype, boolean}
]}.
{mapping, "log.ra.rotation.size", "rabbit.log.categories.ra.max_no_bytes", [
    {datatype, integer}
]}.
{mapping, "log.ra.rotation.count", "rabbit.log.categories.ra.max_no_files", [
    {datatype, integer}
]}.

%% Default logging config.
{mapping, "log.default.level", "rabbit.log.categories.default.level", [
    {datatype, {enum, [debug, info, notice, warning, error, critical, alert, emergency, none]}}
]}.
{mapping, "log.default.rotation.date", "rabbit.log.categories.default.rotate_on_date", [
    {datatype, string}
]}.
{mapping, "log.default.rotation.compress", "rabbit.log.categories.default.compress_on_rotate", [
    {default, false},
    {datatype, boolean}
]}.
{mapping, "log.default.rotation.size", "rabbit.log.categories.default.max_no_bytes", [
    {datatype, integer}
]}.
{mapping, "log.default.rotation.count", "rabbit.log.categories.default.max_no_files", [
    {datatype, integer}
]}.

%%
%% Feature flags and deprecated features
%% =====================================
%%

{mapping,
 "deprecated_features.permit.$name", "rabbit.permit_deprecated_features",
 [{datatype, boolean}]
}.

%% This converts:
%%     deprecated_features.permit.my_feature = true
%% to:
%%     {rabbit, [{permit_deprecated_features, #{my_feature => true}}]}.

{translation, "rabbit.permit_deprecated_features",
 fun(Conf) ->
         Settings = cuttlefish_variable:filter_by_prefix(
                      "deprecated_features.permit", Conf),
         maps:from_list(
           [{list_to_atom(FeatureName), State}
            || {["deprecated_features", "permit", FeatureName], State}
               <- Settings])
 end}.

% ==========================
% Kernel section
% ==========================

{mapping, "net_ticktime", "kernel.net_ticktime",[
    {datatype, {integer, positive}}
]}.

{mapping, "distribution.listener.port_range.min", "kernel.inet_dist_listen_min", [
    {datatype, {integer, positive}}
]}.

{mapping, "distribution.listener.port_range.max", "kernel.inet_dist_listen_max", [
    {datatype, {integer, positive}}
]}.

{mapping, "distribution.listener.interface", "kernel.inet_dist_use_interface", [
    {datatype, [string]},
    {validators, ["is_ip"]}
]}.

{translation, "kernel.inet_dist_use_interface",
    fun(Conf) ->
            case cuttlefish:conf_get("distribution.listener.interface", Conf, undefined) of
                undefined ->
                    cuttlefish:unset();
                Value when is_list(Value) ->
                    case inet:parse_address(Value) of
                        {ok, Parsed} -> Parsed;
                        {error, _}   -> cuttlefish:invalid("should be a valid IP address")
                    end;
                _ ->
                    cuttlefish:invalid("should be a valid IP address")
            end
    end
}.

% ==========================
% sysmon_handler section
% ==========================

%% @doc The threshold at which to warn about the number of processes
%% that are overly busy. Processes with large heaps or that take a
%% long time to garbage collect will count toward this threshold.
{mapping, "sysmon_handler.thresholds.busy_processes", "sysmon_handler.process_limit", [
    {datatype, integer},
    hidden
]}.

{translation, "sysmon_handler.process_limit",
    fun(Conf) ->
            case cuttlefish:conf_get("sysmon_handler.thresholds.busy_processes", Conf, undefined) of
                undefined ->
                    cuttlefish:unset();
                Int when is_integer(Int) ->
                    Int;
                _ ->
                    cuttlefish:invalid("should be a non-negative integer")
            end
    end
}.

%% @doc The threshold at which to warn about the number of ports that
%% are overly busy. Ports with full input buffers count toward this
%% threshold.
{mapping, "sysmon_handler.thresholds.busy_ports", "sysmon_handler.port_limit", [
  {datatype, integer},
  hidden
]}.

{translation, "sysmon_handler.port_limit",
    fun(Conf) ->
            case cuttlefish:conf_get("sysmon_handler.thresholds.busy_ports", Conf, undefined) of
                undefined ->
                    cuttlefish:unset();
                Int when is_integer(Int) ->
                    Int;
                _ ->
                    cuttlefish:invalid("should be a non-negative integer")
            end
    end
}.

%% @doc A process will become busy when it exceeds this amount of time
%% doing garbage collection.
%% @see sysmon_handler.thresholds.busy_processes
{mapping, "sysmon_handler.triggers.process.garbage_collection", "sysmon_handler.gc_ms_limit", [
  {datatype, [{atom, off},
              {duration, ms}]},
  hidden
]}.

{translation, "sysmon_handler.gc_ms_limit",
    fun(Conf) ->
            case cuttlefish:conf_get("sysmon_handler.triggers.process.garbage_collection", Conf, undefined) of
                undefined ->
                    cuttlefish:unset();
                off ->
                    0;
                Int when is_integer(Int) ->
                    Int;
                _ ->
                    cuttlefish:invalid("should be a non-negative integer")
            end
    end
}.

%% @doc A process will become busy when it exceeds this amount of time
%% during a single process scheduling & execution cycle.
{mapping, "sysmon_handler.triggers.process.long_scheduled_execution", "sysmon_handler.schedule_ms_limit", [
  {datatype, [{atom, off},
              {duration, ms}]},
  hidden
]}.

{translation, "sysmon_handler.schedule_ms_limit",
    fun(Conf) ->
            case cuttlefish:conf_get("sysmon_handler.triggers.process.long_scheduled_execution", Conf, undefined) of
                undefined ->
                    cuttlefish:unset();
                off ->
                    0;
                Int when is_integer(Int) ->
                    Int;
                _ ->
                    cuttlefish:invalid("should be a non-negative integer")
            end
    end
}.

%% @doc A process will become busy when its heap exceeds this size.
%% @see sysmon_handler.thresholds.busy_processes
{mapping, "sysmon_handler.triggers.process.heap_size", "sysmon_handler.heap_word_limit", [
  {datatype, [{atom, off},
              bytesize]},
  hidden
]}.

{translation, "sysmon_handler.heap_word_limit",
    fun(Conf) ->
            case cuttlefish:conf_get("sysmon_handler.triggers.process.heap_size", Conf, undefined) of
                undefined ->
                    cuttlefish:unset();
                off ->
                    0;
                Bytes when is_integer(Bytes) ->
                    WordSize = erlang:system_info(wordsize),
                    Bytes div WordSize;
                _ ->
                    cuttlefish:invalid("should be a non-negative integer")
            end
    end
}.

%% @doc Whether ports with full input buffers will be counted as
%% busy. Ports can represent open files or network sockets.
%% @see sysmon_handler.thresholds.busy_ports
{mapping, "sysmon_handler.triggers.port", "sysmon_handler.busy_port", [
  {datatype, flag},
  hidden
]}.

{translation, "sysmon_handler.busy_port",
    fun(Conf) ->
            case cuttlefish:conf_get("sysmon_handler.triggers.port", Conf, undefined) of
                undefined ->
                    cuttlefish:unset();
                Val -> Val
            end
    end
}.

%% @doc Whether distribution ports with full input buffers will be
%% counted as busy. Distribution ports connect Erlang nodes within a
%% single cluster.
%% @see sysmon_handler.thresholds.busy_ports
{mapping, "sysmon_handler.triggers.distribution_port", "sysmon_handler.busy_dist_port", [
  {datatype, flag},
  hidden
]}.

{translation, "sysmon_handler.busy_dist_port",
    fun(Conf) ->
            case cuttlefish:conf_get("sysmon_handler.triggers.distribution_port", Conf, undefined) of
                undefined ->
                    cuttlefish:unset();
                Val -> Val
            end
    end
}.

%%
%% Ra
%%

{mapping, "raft.segment_max_entries", "ra.segment_max_entries", [
  {datatype, {integer, [{min, 1}, {max, 65535}]}}
]}.

{translation, "ra.segment_max_entries",
    fun(Conf) ->
        case cuttlefish:conf_get("raft.segment_max_entries", Conf, undefined) of
            undefined -> cuttlefish:unset();
            Val       -> Val
        end
    end
}.

{mapping, "raft.wal_max_size_bytes", "ra.wal_max_size_bytes", [
  {datatype, {integer, positive}}
]}.

{translation, "ra.wal_max_size_bytes",
    fun(Conf) ->
        case cuttlefish:conf_get("raft.wal_max_size_bytes", Conf, undefined) of
            undefined -> cuttlefish:unset();
            Val       -> Val
        end
    end
}.

{mapping, "raft.wal_max_entries", "ra.wal_max_entries", [
  {datatype, {integer, positive}}
]}.

{translation, "ra.wal_max_entries",
    fun(Conf) ->
        case cuttlefish:conf_get("raft.wal_max_entries", Conf, undefined) of
            undefined -> cuttlefish:unset();
            Val       -> Val
        end
    end
}.

{mapping, "raft.wal_hibernate_after", "ra.wal_hibernate_after", [
  {datatype, {integer, positive}}
]}.

{translation, "ra.wal_hibernate_after",
    fun(Conf) ->
        case cuttlefish:conf_get("raft.wal_hibernate_after", Conf, undefined) of
            undefined -> cuttlefish:unset();
            Val       -> Val
        end
    end
}.

{mapping, "raft.wal_max_batch_size", "ra.wal_max_batch_size", [
  {datatype, {integer, positive}}
]}.

{translation, "ra.wal_max_batch_size",
    fun(Conf) ->
        case cuttlefish:conf_get("raft.wal_max_batch_size", Conf, undefined) of
            undefined -> cuttlefish:unset();
            Val       -> Val
        end
    end
}.

{mapping, "raft.snapshot_chunk_size", "ra.snapshot_chunk_size", [
  {datatype, {integer, positive}}
]}.

{translation, "ra.snapshot_chunk_size",
    fun(Conf) ->
        case cuttlefish:conf_get("raft.snapshot_chunk_size", Conf, undefined) of
            undefined -> cuttlefish:unset();
            Val       -> Val
        end
    end
}.

{mapping, "raft.data_dir", "ra.data_dir", [
  {datatype, string}
]}.

{translation, "ra.data_dir",
    fun(Conf) ->
        case cuttlefish:conf_get("raft.data_dir", Conf, undefined) of
            undefined -> cuttlefish:unset();
            Val       -> Val
        end
    end
}.

{mapping, "raft.adaptive_failure_detector.poll_interval", "aten.poll_interval", [
  {datatype, {integer, positive}}
]}.

{translation, "aten.poll_interval",
  fun(Conf) ->
      case cuttlefish:conf_get("raft.adaptive_failure_detector.poll_interval", Conf, undefined) of
          undefined -> cuttlefish:unset();
          Val       -> Val
      end
  end
}.

{mapping, "raft.adaptive_failure_detector.scaling_factor", "aten.scaling_factor", [
  {datatype, float}
]}.

{translation, "aten.scaling_factor",
  fun(Conf) ->
      case cuttlefish:conf_get("raft.adaptive_failure_detector.scaling_factor", Conf, undefined) of
          undefined -> cuttlefish:unset();
          Val       -> Val
      end
  end
}.

{mapping, "raft.adaptive_failure_detector.heartbeat_interval", "aten.heartbeat_interval", [
  {datatype, {integer, positive}}
]}.

{translation, "aten.heartbeat_interval",
  fun(Conf) ->
      case cuttlefish:conf_get("raft.adaptive_failure_detector.heartbeat_interval", Conf, undefined) of
          undefined -> cuttlefish:unset();
          Val       -> Val
      end
  end
}.

{mapping, "raft.adaptive_failure_detector.detection_threshold", "aten.detection_threshold", [
  {datatype, float}
]}.

{translation, "aten.detection_threshold",
  fun(Conf) ->
      case cuttlefish:conf_get("raft.adaptive_failure_detector.detection_threshold", Conf, undefined) of
          undefined -> cuttlefish:unset();
          Val       -> Val
      end
  end
}.

{mapping, "default_queue_type", "rabbit.default_queue_type", [
    {datatype, atom}
]}.

{translation, "rabbit.default_queue_type",
fun(Conf) ->
    case cuttlefish:conf_get("default_queue_type", Conf, rabbit_classic_queue) of
        classic        -> rabbit_classic_queue;
        quorum         -> rabbit_quorum_queue;
        stream         -> rabbit_stream_queue;
        Module         -> Module
    end
end}.

%% Enable or disable local random exchange
{mapping, "exchange_types.local_random.enabled", "rabbit.local_random_exchange_enabled", [
    {datatype, boolean}
]}.

%% Enable queue types

{mapping, "queue_types.classic.enabled", "rabbit.classic_queues_enabled", [
    {datatype, boolean}
]}.
{mapping, "queue_types.stream.enabled", "rabbit.stream_queues_enabled", [
    {datatype, boolean}
]}.
{mapping, "queue_types.quorum.enabled", "rabbit.quorum_queues_enabled", [
    {datatype, boolean}
]}.

%%
%% Backing queue version
%%

%% DEPRECATED. Not used since RabbitMQ 4.0
{mapping, "classic_queue.default_version", "rabbit.classic_queue_default_version", [
  {datatype, {integer, positive}}
]}.

{translation, "rabbit.classic_queue_default_version",
    fun(Conf) ->
        case cuttlefish:conf_get("classic_queue.default_version", Conf, 2) of
            1 -> cuttlefish:invalid("Classic queues v1 are no longer supported");
            2 -> 2;
            _ -> cuttlefish:unset()
        end
    end
}.

{mapping, "quorum_queue.compute_checksums", "rabbit.quorum_compute_checksums", [
    {datatype, boolean}]}.

{mapping, "quorum_queue.property_equivalence.relaxed_checks_on_redeclaration", "rabbit.quorum_relaxed_checks_on_redeclaration", [
    {datatype, boolean}]}.

{mapping, "quorum_queue.initial_cluster_size", "rabbit.quorum_cluster_size", [
  {datatype, {integer, positive}}
]}.

{mapping, "quorum_queue.commands_soft_limit", "rabbit.quorum_commands_soft_limit", [
  {datatype, {integer, positive}}
]}.

{mapping, "quorum_queue.wal_compute_checksums", "rabbit.quorum_wal_compute_checksums", [
  {datatype, boolean}
]}.

{mapping, "quorum_queue.segment_compute_checksums", "rabbit.quorum_segment_compute_checksums", [
  {datatype, boolean}
]}.

{mapping, "quorum_queue.max_append_entries_rpc_batch_size", "rabbit.quorum_max_append_entries_rpc_batch_size", [
  {datatype, {integer, positive}}
]}.

{mapping, "quorum_queue.compress_mem_tables", "rabbit.quorum_compress_mem_tables", [
  {datatype, boolean}
]}.

{mapping, "quorum_queue.segment_max_size_bytes", "rabbit.quorum_segment_max_size_bytes", [
  {datatype, {integer, positive}}
]}.

{mapping, "quorum_queue.segment_max_entries", "rabbit.quorum_segment_max_entries", [
  {datatype, {integer, [{min, 1}, {max, 65535}]}}
]}.

{mapping, "quorum_queue.wal_max_size_bytes", "rabbit.quorum_wal_max_size_bytes", [
  {datatype, {integer, positive}}
]}.

{mapping, "quorum_queue.wal_max_entries", "rabbit.quorum_wal_max_entries", [
  {datatype, {integer, positive}}
]}.

{mapping, "quorum_queue.wal_max_batch_size", "rabbit.quorum_wal_max_batch_size", [
  {datatype, {integer, positive}}
]}.

{mapping, "quorum_queue.snapshot_chunk_size", "rabbit.quorum_snapshot_chunk_size", [
  {datatype, {integer, positive}}
]}.

%%
%% Quorum Queue membership reconciliation
%%

{mapping, "quorum_queue.continuous_membership_reconciliation.enabled", "rabbit.quorum_membership_reconciliation_enabled", [
    {datatype, boolean}]}.

{mapping, "quorum_queue.continuous_membership_reconciliation.auto_remove", "rabbit.quorum_membership_reconciliation_auto_remove", [
    {datatype, boolean}]}.

{mapping, "quorum_queue.continuous_membership_reconciliation.interval", "rabbit.quorum_membership_reconciliation_interval", [
    {datatype, {integer, non_negative}}
]}.

{mapping, "quorum_queue.continuous_membership_reconciliation.trigger_interval", "rabbit.quorum_membership_reconciliation_trigger_interval", [
    {datatype, {integer, non_negative}}
]}.

{mapping, "quorum_queue.continuous_membership_reconciliation.target_group_size", "rabbit.quorum_membership_reconciliation_target_group_size", [
    {datatype, {integer, non_negative}}
]}.


%%
%% Runtime parameters
%%

{mapping, "runtime_parameters.limits.$category", "rabbit.runtime_parameters.limits", [
    {datatype, {integer, non_negative}}
]}.

{translation, "rabbit.runtime_parameters.limits",
    fun(Conf) ->
        case cuttlefish_variable:filter_by_prefix("runtime_parameters.limits", Conf) of
            [] -> cuttlefish:unset();
            Ss -> [ {list_to_binary(Category), Limit} || {[_, _, Category], Limit} <- Ss ]
        end
    end
}.

%%
%% Message interceptors
%%

{mapping, "message_interceptors.$stage.$name.$key", "rabbit.message_interceptors", [
    {datatype, boolean}]}.

{translation, "rabbit.message_interceptors",
 fun(Conf) ->
         case cuttlefish_variable:filter_by_prefix("message_interceptors", Conf) of
             [] ->
                 cuttlefish:unset();
             L ->
                 lists:foldr(
                   fun({["message_interceptors", "incoming", "set_header_routing_node", "overwrite"], Overwrite}, Acc)
                         when is_boolean(Overwrite) ->
                           Mod = rabbit_msg_interceptor_routing_node,
                           Cfg = #{overwrite => Overwrite},
                           [{Mod, Cfg} | Acc];
                      ({["message_interceptors", "incoming", "set_header_timestamp", "overwrite"], Overwrite}, Acc)
                        when is_boolean(Overwrite) ->
                           Mod = rabbit_msg_interceptor_timestamp,
                           Cfg = #{incoming => true,
                                   overwrite => Overwrite},
                           case lists:keytake(Mod, 1, Acc) of
                               false ->
                                   [{Mod, Cfg} | Acc];
                               {value, {Mod, Cfg1}, Acc1} ->
                                   Cfg2 = maps:merge(Cfg1, Cfg),
                                   [{Mod, Cfg2} | Acc1]
                           end;
                      ({["message_interceptors", "outgoing", "timestamp", "enabled"], Enabled}, Acc) ->
                           case Enabled of
                               true ->
                                   Mod = rabbit_msg_interceptor_timestamp,
                                   Cfg = #{outgoing => true},
                                   case lists:keytake(Mod, 1, Acc) of
                                       false ->
                                           [{Mod, Cfg} | Acc];
                                       {value, {Mod, Cfg1}, Acc1} ->
                                           Cfg2 = maps:merge(Cfg1, Cfg),
                                           [{Mod, Cfg2} | Acc1]
                                   end;
                               false ->
                                   Acc
                           end;
                      (Other, _Acc) ->
                           cuttlefish:invalid(io_lib:format("~p is invalid", [Other]))
                   end, [], L)
         end
 end
}.

{mapping, "stream.replication.address_family", "osiris.replica_ip_address_family", [
    {datatype, [{enum, [inet6, inet, ipv6, ipv4]}]}
]}.

{translation, "osiris.replica_ip_address_family",
    fun(Conf) ->
        case cuttlefish:conf_get("stream.replication.address_family", Conf, undefined) of
            undefined -> cuttlefish:unset();
            inet      -> inet;
            inet6     -> inet6;
            ipv4      -> inet;
            ipv6      -> inet6;
            Other     -> cuttlefish:invalid(io_lib:format("~p is not a supported address family", [Other]))
        end
    end
}.

{mapping, "stream.replication.port_range.min", "osiris.port_range", [
    {datatype, {integer, positive}}
]}.
{mapping, "stream.replication.port_range.max", "osiris.port_range", [
    {datatype, {integer, positive}}
]}.

{translation, "osiris.port_range",
fun(Conf) ->
    Min = cuttlefish:conf_get("stream.replication.port_range.min", Conf, undefined),
    Max = cuttlefish:conf_get("stream.replication.port_range.max", Conf, undefined),

    case {Min, Max} of
        {undefined, undefined} ->
            cuttlefish:unset();
        {Mn, undefined}  ->
            {Mn, Mn + 500};
        {undefined, Mx} ->
            {Mx - 500, Mx};
        _ ->
            {Min, Max}
    end
end}.

{mapping, "stream.data_dir", "osiris.data_dir", [
  {datatype, string}
]}.

{translation, "osiris.data_dir",
    fun(Conf) ->
        case cuttlefish:conf_get("stream.data_dir", Conf, undefined) of
            undefined -> cuttlefish:unset();
            Val       -> Val
        end
    end
}.

{mapping, "stream.read_ahead", "rabbit.stream_read_ahead",
    [{datatype, [boolean, integer, string]}]}.

{mapping, "stream.read_ahead_limit", "rabbit.stream_read_ahead_limit", [
    {datatype, [integer, string]},
    {validators, ["is_supported_information_unit"]}
]}.

{mapping, "cluster_tags.$tag", "rabbit.cluster_tags", [
    {datatype, [binary]}
]}.

{translation, "rabbit.cluster_tags",
fun(Conf) ->
    case cuttlefish:conf_get("cluster_tags", Conf, undefined) of
        none -> [];
        _ ->
            Settings = cuttlefish_variable:filter_by_prefix("cluster_tags", Conf),
            [ {list_to_binary(K), V} || {[_, K], V} <- Settings]
    end
end}.

{mapping, "node_tags.$tag", "rabbit.node_tags", [
    {datatype, [binary]}
]}.

{translation, "rabbit.node_tags",
fun(Conf) ->
    case cuttlefish:conf_get("node_tags", Conf, undefined) of
        none -> [];
        _ ->
            Settings = cuttlefish_variable:filter_by_prefix("node_tags", Conf),
            [ {list_to_binary(K), V} || {[_, K], V} <- Settings]
    end
end}.

%% ----------------------------------------------------------------------------
%%  AMQP client 1.0 TLS options
%% ----------------------------------------------------------------------------

{mapping, "amqp10_client.ssl_options", "amqp10_client.ssl_options", [
    {datatype, {enum, [none]}}
]}.

{translation, "amqp10_client.ssl_options",
fun(Conf) ->
    case cuttlefish:conf_get("amqp10_client.ssl_options", Conf, undefined) of
        none -> [];
        _    -> cuttlefish:invalid("Invalid amqp10_client.ssl_options")
    end
end}.

{mapping, "amqp10_client.ssl_options.verify", "amqp10_client.ssl_options.verify", [
    {datatype, {enum, [verify_peer, verify_none]}}]}.

{mapping, "amqp10_client.ssl_options.cacertfile", "amqp10_client.ssl_options.cacertfile",
    [{datatype, string}, {validators, ["file_accessible"]}]}.

{mapping, "amqp10_client.ssl_options.certfile", "amqp10_client.ssl_options.certfile",
    [{datatype, string}, {validators, ["file_accessible"]}]}.

{mapping, "amqp10_client.ssl_options.cacerts.$name", "amqp10_client.ssl_options.cacerts",
    [{datatype, string}]}.

{translation, "amqp10_client.ssl_options.cacerts",
fun(Conf) ->
    Settings = cuttlefish_variable:filter_by_prefix("amqp10_client.ssl_options.cacerts", Conf),
    [ list_to_binary(V) || {_, V} <- Settings ]
end}.

{mapping, "amqp10_client.ssl_options.cert", "amqp10_client.ssl_options.cert",
    [{datatype, string}]}.

{translation, "amqp10_client.ssl_options.cert",
fun(Conf) ->
    list_to_binary(cuttlefish:conf_get("amqp10_client.ssl_options.cert", Conf))
end}.

{mapping, "amqp10_client.ssl_options.crl_check", "amqp10_client.ssl_options.crl_check",
    [{datatype, [{enum, [true, false, peer, best_effort]}]}]}.

{mapping, "amqp10_client.ssl_options.depth", "amqp10_client.ssl_options.depth",
    [{datatype, byte}]}.

{mapping, "amqp10_client.ssl_options.key.RSAPrivateKey", "amqp10_client.ssl_options.key",
    [{datatype, string}]}.

{mapping, "amqp10_client.ssl_options.key.DSAPrivateKey", "amqp10_client.ssl_options.key",
    [{datatype, string}]}.

{mapping, "amqp10_client.ssl_options.key.PrivateKeyInfo", "amqp10_client.ssl_options.key",
    [{datatype, string}]}.

{translation, "amqp10_client.ssl_options.key",
fun(Conf) ->
    case cuttlefish_variable:filter_by_prefix("amqp10_client.ssl_options.key", Conf) of
        [{[_,_,Key], Val}|_] -> {list_to_atom(Key), list_to_binary(Val)};
        _ -> cuttlefish:unset()
    end
end}.

{mapping, "amqp10_client.ssl_options.keyfile", "amqp10_client.ssl_options.keyfile",
    [{datatype, string}, {validators, ["file_accessible"]}]}.

{mapping, "amqp10_client.ssl_options.log_alert", "amqp10_client.ssl_options.log_alert",
    [{datatype, boolean}]}.

{mapping, "amqp10_client.ssl_options.password", "amqp10_client.ssl_options.password",
    [{datatype, [tagged_binary, binary]}]}.

{mapping, "amqp10_client.ssl_options.psk_identity", "amqp10_client.ssl_options.psk_identity",
    [{datatype, string}]}.

{mapping, "amqp10_client.ssl_options.reuse_sessions", "amqp10_client.ssl_options.reuse_sessions",
    [{datatype, boolean}]}.

{mapping, "amqp10_client.ssl_options.secure_renegotiate", "amqp10_client.ssl_options.secure_renegotiate",
    [{datatype, boolean}]}.

{mapping, "amqp10_client.ssl_options.versions.$version", "amqp10_client.ssl_options.versions",
    [{datatype, atom}]}.

{translation, "amqp10_client.ssl_options.versions",
fun(Conf) ->
    Settings = cuttlefish_variable:filter_by_prefix("amqp10_client.ssl_options.versions", Conf),
    [ V || {_, V} <- Settings ]
end}.

{mapping, "amqp10_client.ssl_options.sni", "amqp10_client.ssl_options.server_name_indication",
    [{datatype, [{enum, [none]}, string]}]}.

{translation, "amqp10_client.ssl_options.server_name_indication",
fun(Conf) ->
    case cuttlefish:conf_get("amqp10_client.ssl_options.sni", Conf, undefined) of
        undefined -> cuttlefish:unset();
        none      -> disable;
        Hostname  -> Hostname
    end
end}.

% ===============================
% AMQP 0.9.1
% ===============================

%% ----------------------------------------------------------------------------
%%  AMQP client 0.9.1 TLS options
%% ----------------------------------------------------------------------------

{mapping, "amqp_client.ssl_options", "amqp_client.ssl_options", [
    {datatype, {enum, [none]}}
]}.

{translation, "amqp_client.ssl_options",
fun(Conf) ->
    case cuttlefish:conf_get("amqp_client.ssl_options", Conf, undefined) of
        none -> [];
        _    -> cuttlefish:invalid("Invalid amqp_client.ssl_options")
    end
end}.

{mapping, "amqp_client.ssl_options.verify", "amqp_client.ssl_options.verify", [
    {datatype, {enum, [verify_peer, verify_none]}}]}.

{mapping, "amqp_client.ssl_options.cacertfile", "amqp_client.ssl_options.cacertfile",
    [{datatype, string}, {validators, ["file_accessible"]}]}.

{mapping, "amqp_client.ssl_options.certfile", "amqp_client.ssl_options.certfile",
    [{datatype, string}, {validators, ["file_accessible"]}]}.

{mapping, "amqp_client.ssl_options.cacerts.$name", "amqp_client.ssl_options.cacerts",
    [{datatype, string}]}.

{translation, "amqp_client.ssl_options.cacerts",
fun(Conf) ->
    Settings = cuttlefish_variable:filter_by_prefix("amqp_client.ssl_options.cacerts", Conf),
    [ list_to_binary(V) || {_, V} <- Settings ]
end}.

{mapping, "amqp_client.ssl_options.cert", "amqp_client.ssl_options.cert",
    [{datatype, string}]}.

{translation, "amqp_client.ssl_options.cert",
fun(Conf) ->
    list_to_binary(cuttlefish:conf_get("amqp_client.ssl_options.cert", Conf))
end}.

{mapping, "amqp_client.ssl_options.crl_check", "amqp_client.ssl_options.crl_check",
    [{datatype, [{enum, [true, false, peer, best_effort]}]}]}.

{mapping, "amqp_client.ssl_options.depth", "amqp_client.ssl_options.depth",
    [{datatype, byte}]}.

{mapping, "amqp_client.ssl_options.key.RSAPrivateKey", "amqp_client.ssl_options.key",
    [{datatype, string}]}.

{mapping, "amqp_client.ssl_options.key.DSAPrivateKey", "amqp_client.ssl_options.key",
    [{datatype, string}]}.

{mapping, "amqp_client.ssl_options.key.PrivateKeyInfo", "amqp_client.ssl_options.key",
    [{datatype, string}]}.

{translation, "amqp_client.ssl_options.key",
fun(Conf) ->
    case cuttlefish_variable:filter_by_prefix("amqp_client.ssl_options.key", Conf) of
        [{[_,_,Key], Val}|_] -> {list_to_atom(Key), list_to_binary(Val)};
        _ -> cuttlefish:unset()
    end
end}.

{mapping, "amqp_client.ssl_options.keyfile", "amqp_client.ssl_options.keyfile",
    [{datatype, string}, {validators, ["file_accessible"]}]}.

{mapping, "amqp_client.ssl_options.log_alert", "amqp_client.ssl_options.log_alert",
    [{datatype, boolean}]}.

{mapping, "amqp_client.ssl_options.password", "amqp_client.ssl_options.password",
    [{datatype, [tagged_binary, binary]}]}.

{mapping, "amqp_client.ssl_options.psk_identity", "amqp_client.ssl_options.psk_identity",
    [{datatype, string}]}.

{mapping, "amqp_client.ssl_options.reuse_sessions", "amqp_client.ssl_options.reuse_sessions",
    [{datatype, boolean}]}.

{mapping, "amqp_client.ssl_options.secure_renegotiate", "amqp_client.ssl_options.secure_renegotiate",
    [{datatype, boolean}]}.

{mapping, "amqp_client.ssl_options.versions.$version", "amqp_client.ssl_options.versions",
    [{datatype, atom}]}.

{translation, "amqp_client.ssl_options.versions",
fun(Conf) ->
    Settings = cuttlefish_variable:filter_by_prefix("amqp_client.ssl_options.versions", Conf),
    [ V || {_, V} <- Settings ]
end}.

{mapping, "amqp_client.ssl_options.sni", "amqp_client.ssl_options.server_name_indication",
    [{datatype, [{enum, [none]}, string]}]}.

{translation, "amqp_client.ssl_options.server_name_indication",
fun(Conf) ->
    case cuttlefish:conf_get("amqp_client.ssl_options.sni", Conf, undefined) of
        undefined -> cuttlefish:unset();
        none      -> disable;
        Hostname  -> Hostname
    end
end}.

% ===============================
% Validators
% ===============================

{validator, "mirroring_sync_batch_size", "Batch size should be greater than 0 and less than 1M",
fun(Size) when is_integer(Size) ->
    Size > 0 andalso Size =< 1000000
end}.

{validator, "max_message_size", "Max message size should be between 0 and 512MB",
fun(Size) when is_integer(Size) ->
    Size > 0 andalso Size =< 536870912
end}.

{validator, "less_than_1", "Float is not between 0 and 1",
fun(Float) when is_float(Float) ->
    Float > 0 andalso Float < 1
end}.

{validator, "port", "Invalid port number",
fun(Port) when is_integer(Port) ->
    Port > 0 andalso Port < 65535
end}.

{validator, "byte", "Integer must be in the range [0, 255]",
fun(Int) when is_integer(Int) ->
    Int >= 0 andalso Int =< 255
end}.

{validator, "dir_writable", "Directory must be writable",
fun(Dir) ->
    TestFile = filename:join(Dir, "test_file"),
    file:delete(TestFile),
    Res = ok == file:write_file(TestFile, <<"test">>),
    file:delete(TestFile),
    Res
end}.

{validator, "file_accessible", "file does not exist or cannot be read by the node",
fun(File) ->
    case file:read_file_info(File) of
        {ok, FileInfo} -> (element(4, FileInfo) == read) or (element(4, FileInfo) == read_write);
        _ -> false
    end
end}.

{validator, "pem_file", "PEM file does not exist, cannot be read, or does not contain valid PEM-encoded X.509 certificate or private key data",
fun(File) ->
    case file:read_file(File) of
        {ok, Bin} -> public_key:pem_decode(Bin) =/= [];
        _ -> false
    end
end}.

{validator, "is_ip", "value should be a valid IP address",
fun(IpStr) ->
    Res = inet:parse_address(IpStr),
    element(1, Res) == ok
end}.

{validator, "non_negative_integer", "number should be greater or equal to zero",
fun(infinity) -> true;
   (Int) when is_integer(Int) -> Int >= 0;
   (_) -> false
end}.

{validator, "non_zero_positive_integer", "number should be greater or equal to one",
fun(Int) when is_integer(Int) ->
    Int >= 1
end}.

{validator, "positive_16_bit_unsigned_integer", "number should be between 1 and 65535",
 fun(Int) when is_integer(Int) ->
         (Int >= 1) and (Int =< 16#ff_ff)
 end}.

{validator, "positive_32_bit_unsigned_integer", "number should be between 1 and 4294967295",
 fun(Int) when is_integer(Int) ->
         (Int >= 1) and (Int =< 16#ff_ff_ff_ff)
 end}.

{validator, "valid_regex", "string must be a valid regular expression",
 fun("")     -> false;
    (String) -> {Res, _ } = re:compile(String),
                Res =:= ok
 end}.

{validator, "is_supported_information_unit", "supported formats: 500MB, 500MiB, 10GB, 10GiB, 2TB, 2TiB, 10000000000",
 fun(S0) ->
    case is_integer(S0) of
       true -> true;
       false ->
         %% this is a string
         S = string:strip(S0, right),
         %% The suffix is optional
         {ok, HasIUSuffix} = re:compile("([0-9]+)([a-zA-Z]){1,3}$", [dollar_endonly, caseless]),
         %% Here are the prefixes we accept. This must match
         %% what rabbit_resource_monitor_misc and 'rabbitmq-diagnostics status' can format.
         {ok, SuffixExtractor} = re:compile("(k|ki|kb|kib|m|mi|mb|mib|g|gi|gb|gib|t|ti|tb|tib|p|pi|pb|pib)$", [dollar_endonly, caseless]),
         case re:run(S, HasIUSuffix) of
             nomatch -> false;
             {match, _} ->
                 case re:split(S, SuffixExtractor) of
                     [] -> false;
                    [_CompleteMatch] -> false;
                     [_CompleteMatch, Suffix | _] -> true
                 end
         end
    end
 end}.
