Skip to main content

rebar.config

{erl_opts, [
    debug_info,
    warnings_as_errors,
    warn_missing_spec
]}.

{deps, [
    {mimerl, "1.5.0"},
    {h1, "~> 0.7.0", {pkg, erlang_h1}},
    {h2, "~> 0.10.2"},
    {quic, "~> 1.6.5"},
    {ws, "~> 0.3.0", {pkg, erlang_ws}},
    {instrument, "~> 1.1.4"},
    {webtransport, "~> 0.4.1"},
    {hackney, "~> 4.4.5"},
    {barrel_mcp, "~> 2.2.4"}
]}.

{profiles, [
    {test, [
        {erl_opts, [
            debug_info,
            nowarn_export_all,
            nowarn_missing_spec,
            nowarn_deprecated_catch
        ]},
        {extra_src_dirs, [{"examples", [{recursive, false}]}]},
        %% cowboy is a git dep because rebar3 cannot resolve the
        %% `>= x and < y' hex requirements cowboy >= 2.13 declares;
        %% cowlib/ranch are pinned from hex to satisfy it.
        {deps, [
            {proper, "1.5.0"},
            {cowlib, "2.17.1"},
            {ranch, "1.8.1"},
            {cowboy, {git, "https://github.com/ninenines/cowboy.git", {tag, "2.16.1"}}}
        ]}
    ]},
    {examples, [
        {extra_src_dirs, [{"examples", [{recursive, false}]}]},
        {erl_opts, [debug_info, nowarn_missing_spec]}
    ]},
    {bench, [
        {extra_src_dirs, [{"bench", [{recursive, false}]}]},
        {erl_opts, [debug_info, nowarn_missing_spec, nowarn_deprecated_catch]},
        %% cowboy (+ cowlib/ranch) for the livery-vs-cowboy comparison;
        %% same pins as the test profile. See rebar.config test deps.
        {deps, [
            {cowlib, "2.17.1"},
            {ranch, "1.8.1"},
            {cowboy, {git, "https://github.com/ninenines/cowboy.git", {tag, "2.16.1"}}}
        ]}
    ]}
]}.

{xref_checks, [
    undefined_function_calls,
    undefined_functions,
    locals_not_used,
    deprecated_function_calls,
    deprecated_functions
]}.

{dialyzer, [
    {warnings, [unknown]},
    {plt_apps, all_deps}
]}.

{cover_enabled, true}.
{cover_opts, [verbose]}.

{project_plugins, [
    {erlfmt, "1.7.0"},
    {rebar3_lint, "4.1.1"},
    rebar3_ex_doc
]}.

{erlfmt, [
    write,
    {files, [
        "{src,include,test,bench,examples}/**/*.{hrl,erl,app.src}",
        "rebar.config"
    ]}
]}.

{ex_doc, [
    {source_url, <<"https://github.com/benoitc/livery">>},
    {homepage_url, <<"https://benoitc.github.io/livery/">>},
    {extras, [
        {"docs/README.md", #{title => <<"Documentation">>}},
        {"docs/overview.md", #{title => <<"Overview">>}},
        {"docs/quickstart.md", #{title => <<"Quickstart">>}},

        {"docs/tutorials/your-first-service.md", #{title => <<"Your first service">>}},
        {"docs/tutorials/middleware-stack.md", #{title => <<"Compose a middleware stack">>}},
        {"docs/tutorials/streaming-responses.md", #{title => <<"Stream a response">>}},
        {"docs/tutorials/testing-handlers.md", #{title => <<"Test your handlers">>}},
        {"docs/tutorials/call-a-service.md", #{title => <<"Call another service">>}},
        {"docs/tutorials/build-a-complete-service.md", #{
            title => <<"Build a complete service">>
        }},

        {"docs/guides/mount-a-router.md", #{title => <<"Mount a router on a service">>}},
        {"docs/guides/bind-listen-address.md", #{
            title => <<"Bind to an address or IPv6">>
        }},
        {"docs/guides/serve-multiple-certs-sni.md", #{
            title => <<"Serve several certificates by hostname (SNI)">>
        }},
        {"docs/guides/parse-json-bodies.md", #{title => <<"Parse a JSON body">>}},
        {"docs/guides/read-query-strings.md", #{title => <<"Read query strings">>}},
        {"docs/guides/parse-form-bodies.md", #{title => <<"Parse form bodies">>}},
        {"docs/guides/handle-file-uploads.md", #{title => <<"Handle file uploads">>}},
        {"docs/guides/read-headers.md", #{title => <<"Read headers">>}},
        {"docs/guides/bearer-tokens.md", #{title => <<"Extract a bearer token">>}},
        {"docs/guides/token-introspection.md", #{
            title => <<"Verify opaque tokens (introspection)">>
        }},
        {"docs/guides/session-cookies.md", #{title => <<"Use signed session cookies">>}},
        {"docs/guides/read-streaming-body.md", #{title => <<"Read a streaming request body">>}},
        {"docs/guides/stream-chunked.md", #{title => <<"Return a streaming response">>}},
        {"docs/guides/server-sent-events.md", #{title => <<"Return Server-Sent Events">>}},
        {"docs/guides/return-trailers.md", #{title => <<"Return trailers">>}},
        {"docs/guides/serve-a-file.md", #{title => <<"Serve a file">>}},
        {"docs/guides/serve-static-files.md", #{title => <<"Serve static files">>}},
        {"docs/guides/empty-and-redirects.md", #{title => <<"Empty and redirect responses">>}},
        {"docs/guides/custom-middleware.md", #{title => <<"Write a custom middleware">>}},
        {"docs/guides/share-config.md", #{title => <<"Share config across handlers">>}},
        {"docs/guides/cap-body-size.md", #{title => <<"Cap request body size">>}},
        {"docs/guides/add-deadlines.md", #{title => <<"Add per-request deadlines">>}},
        {"docs/guides/limit-concurrency.md", #{title => <<"Limit concurrency">>}},
        {"docs/guides/rate-limit-requests.md", #{title => <<"Rate-limit requests">>}},
        {"docs/guides/log-requests.md", #{title => <<"Log every request">>}},
        {"docs/guides/make-http-requests.md", #{title => <<"Make outbound HTTP requests">>}},
        {"docs/guides/load-balance-requests.md", #{
            title => <<"Load-balance outbound requests">>
        }},
        {"docs/guides/use-a-cookie-jar.md", #{title => <<"Use a cookie jar">>}},
        {"docs/guides/propagate-request-ids.md", #{title => <<"Propagate request IDs">>}},
        {"docs/guides/handler-errors.md", #{title => <<"Catch handler errors">>}},
        {"docs/guides/cancel-on-disconnect.md", #{
            title => <<"Cancel on client disconnect">>
        }},
        {"docs/guides/enable-cors.md", #{title => <<"Enable CORS">>}},
        {"docs/guides/set-security-headers.md", #{title => <<"Set security headers">>}},
        {"docs/guides/compress-responses.md", #{title => <<"Compress responses">>}},
        {"docs/guides/http-caching.md", #{title => <<"Add HTTP caching">>}},
        {"docs/guides/openapi-and-validation.md", #{title => <<"OpenAPI docs and validation">>}},
        {"docs/guides/serve-mcp-tools.md", #{title => <<"Serve MCP tools">>}},
        {"docs/guides/serve-webtransport.md", #{title => <<"Serve WebTransport">>}},
        {"docs/guides/graceful-shutdown.md", #{title => <<"Shut down gracefully">>}},
        {"docs/guides/health-checks.md", #{title => <<"Health and readiness checks">>}},
        {"docs/guides/export-metrics.md", #{title => <<"Export Prometheus metrics">>}},
        {"docs/guides/test-handlers.md", #{title => <<"Test handlers without a socket">>}},
        {"docs/guides/migrate-from-cowboy.md", #{title => <<"Migrate from Cowboy">>}},

        {"docs/concepts/architecture.md", #{title => <<"Architecture">>}},
        {"docs/concepts/request-and-response.md", #{title => <<"Request and response">>}},
        {"docs/concepts/middleware-pipeline.md", #{title => <<"The middleware pipeline">>}},
        {"docs/concepts/routing.md", #{title => <<"Routing">>}},
        {"docs/concepts/request-lifecycle.md", #{title => <<"Request lifecycle">>}},
        {"docs/concepts/adapters.md", #{title => <<"Adapters">>}},
        {"docs/concepts/streaming-and-backpressure.md", #{
            title => <<"Streaming and backpressure">>
        }},

        {"docs/ecosystem.md", #{title => <<"Ecosystem">>}},

        {"docs/design.md", #{title => <<"Architecture (long form)">>}}
    ]},
    {main, <<"readme">>},
    {groups_for_extras, [
        {<<"Start here">>, [
            <<"docs/README.md">>,
            <<"docs/overview.md">>,
            <<"docs/quickstart.md">>
        ]},
        {<<"Tutorials">>, [
            <<"docs/tutorials/your-first-service.md">>,
            <<"docs/tutorials/middleware-stack.md">>,
            <<"docs/tutorials/streaming-responses.md">>,
            <<"docs/tutorials/testing-handlers.md">>,
            <<"docs/tutorials/call-a-service.md">>,
            <<"docs/tutorials/build-a-complete-service.md">>
        ]},
        {<<"How-to guides">>, [
            <<"docs/guides/mount-a-router.md">>,
            <<"docs/guides/bind-listen-address.md">>,
            <<"docs/guides/serve-multiple-certs-sni.md">>,
            <<"docs/guides/parse-json-bodies.md">>,
            <<"docs/guides/read-query-strings.md">>,
            <<"docs/guides/parse-form-bodies.md">>,
            <<"docs/guides/handle-file-uploads.md">>,
            <<"docs/guides/read-headers.md">>,
            <<"docs/guides/bearer-tokens.md">>,
            <<"docs/guides/token-introspection.md">>,
            <<"docs/guides/session-cookies.md">>,
            <<"docs/guides/read-streaming-body.md">>,
            <<"docs/guides/stream-chunked.md">>,
            <<"docs/guides/server-sent-events.md">>,
            <<"docs/guides/return-trailers.md">>,
            <<"docs/guides/serve-a-file.md">>,
            <<"docs/guides/serve-static-files.md">>,
            <<"docs/guides/empty-and-redirects.md">>,
            <<"docs/guides/custom-middleware.md">>,
            <<"docs/guides/share-config.md">>,
            <<"docs/guides/cap-body-size.md">>,
            <<"docs/guides/add-deadlines.md">>,
            <<"docs/guides/limit-concurrency.md">>,
            <<"docs/guides/rate-limit-requests.md">>,
            <<"docs/guides/log-requests.md">>,
            <<"docs/guides/make-http-requests.md">>,
            <<"docs/guides/load-balance-requests.md">>,
            <<"docs/guides/use-a-cookie-jar.md">>,
            <<"docs/guides/propagate-request-ids.md">>,
            <<"docs/guides/handler-errors.md">>,
            <<"docs/guides/cancel-on-disconnect.md">>,
            <<"docs/guides/enable-cors.md">>,
            <<"docs/guides/set-security-headers.md">>,
            <<"docs/guides/compress-responses.md">>,
            <<"docs/guides/http-caching.md">>,
            <<"docs/guides/openapi-and-validation.md">>,
            <<"docs/guides/serve-mcp-tools.md">>,
            <<"docs/guides/serve-webtransport.md">>,
            <<"docs/guides/graceful-shutdown.md">>,
            <<"docs/guides/health-checks.md">>,
            <<"docs/guides/export-metrics.md">>,
            <<"docs/guides/test-handlers.md">>,
            <<"docs/guides/migrate-from-cowboy.md">>
        ]},
        {<<"Concepts">>, [
            <<"docs/concepts/architecture.md">>,
            <<"docs/concepts/request-and-response.md">>,
            <<"docs/concepts/middleware-pipeline.md">>,
            <<"docs/concepts/routing.md">>,
            <<"docs/concepts/request-lifecycle.md">>,
            <<"docs/concepts/adapters.md">>,
            <<"docs/concepts/streaming-and-backpressure.md">>
        ]},
        {<<"Ecosystem">>, [
            <<"docs/ecosystem.md">>
        ]},
        {<<"Project">>, [
            <<"docs/design.md">>
        ]}
    ]},
    {groups_for_modules, [
        {<<"Public API">>, [livery]},
        {<<"Request and response">>, [
            livery_req,
            livery_resp,
            livery_ext,
            livery_multipart,
            livery_static
        ]},
        {<<"Routing and middleware">>, [livery_router, livery_middleware]},
        {<<"Built-in middleware">>, [
            livery_request_id,
            livery_body_limit,
            livery_timeout,
            livery_access_log,
            livery_cors,
            livery_security_headers,
            livery_concurrency,
            livery_ratelimit,
            livery_etag
        ]},
        {<<"Compression">>, [
            livery_compress,
            livery_codec,
            livery_codec_gzip,
            livery_codec_deflate
        ]},
        {<<"Authentication">>, [
            livery_auth,
            livery_auth_bearer,
            livery_auth_introspect,
            livery_auth_session,
            livery_auth_oidc,
            livery_auth_jwks
        ]},
        {<<"MCP">>, [livery_mcp]},
        {<<"Health and metrics">>, [
            livery_health,
            livery_metrics,
            livery_instrument_metrics,
            livery_instrument_trace
        ]},
        {<<"WebSocket and WebTransport">>, [livery_ws, livery_wt]},
        {<<"Adapters">>, [livery_adapter, livery_test_adapter]},
        {<<"Body reader">>, [livery_body]},
        {<<"Runtime">>, [
            livery_app,
            livery_sup,
            livery_req_proc,
            livery_req_sup,
            livery_ratelimit_store
        ]}
    ]}
]}.

{hex, [{doc, #{provider => ex_doc}}]}.

{elvis, [
    #{
        dirs => ["src", "src/client", "src/middleware", "src/auth", "src/codec"],
        filter => "*.erl",
        rules => [
            {elvis_style, no_macros, disable},
            {elvis_style, no_common_caveats_call, disable},
            {elvis_style, no_init_lists, disable},
            {elvis_style, param_pattern_matching, disable},
            %% `livery' and `livery_resp' are intentional public
            %% facades; `livery_req'/`livery_resp' expose req/0 and
            %% resp/0 as the public request/response value types.
            %% livery_client is the client's public facade (verbs,
            %% accessors, layer and sugar constructors), like livery_req/resp.
            {elvis_style, god_modules, #{
                ignore => [livery_req, livery_resp, livery_client]
            }},
            {elvis_style, private_data_types, #{
                ignore => [livery_req, livery_resp]
            }},
            {elvis_style, export_used_types, #{
                ignore => [
                    livery_middleware,
                    livery_req,
                    livery_resp,
                    livery_router,
                    livery_ws_h2,
                    livery_ws_h3
                ]
            }},
            %% The adapter behaviour is dispatched dynamically by
            %% design (Adapter:send_*, accept_ws/accept_wt).
            %% livery_client dispatches to the configured client adapter;
            %% livery_client_discover dispatches to the discovery provider;
            %% livery_client_cookie dispatches to the cookie store module.
            {elvis_style, invalid_dynamic_call, #{
                ignore => [
                    livery,
                    livery_mcp,
                    livery_ws,
                    livery_wt,
                    livery_compress,
                    livery_client,
                    livery_client_discover,
                    livery_client_cookie
                ]
            }},
            %% Per-stream translators (and the client push-stream relay)
            %% block on receive and exit on the monitored peer's DOWN, so an
            %% after clause is wrong.
            {elvis_style, no_receive_without_timeout, #{
                ignore => [livery_h1, livery_h2, livery_h3, livery_client_hackney]
            }},
            %% Internal records used directly in specs; introducing a
            %% wrapper type would not add clarity.
            {elvis_style, no_spec_with_records, #{
                ignore => [
                    livery_auth,
                    livery_router,
                    livery_service,
                    livery_test_adapter
                ]
            }},
            {elvis_style, state_record_and_type, #{
                ignore => [livery_service]
            }},
            {elvis_style, no_throw, #{ignore => [livery_service]}},
            {elvis_style, no_catch_expressions, #{
                ignore => [livery_ws_h2, livery_ws_h3]
            }},
            {elvis_style, no_boolean_in_comparison, #{
                ignore => [livery_req]
            }},
            %% Capitalised OTP record names ('RSAPublicKey', ...) and
            %% camelCase JSON-Schema keyword atoms are external shapes.
            {elvis_style, atom_naming_convention, #{
                ignore => [livery_auth, livery_openapi_validate]
            }},
            %% The new-bucket (insert_new) and existing-bucket (CAS
            %% select_replace) branches share the allow shape but differ
            %% in the atomic write primitive; keeping them apart is clearer.
            {elvis_style, dont_repeat_yourself, #{
                ignore => [livery, livery_openapi, livery_ratelimit_store]
            }},
            {elvis_style, max_function_length, #{
                ignore => [
                    livery,
                    livery_client_hackney,
                    livery_h1,
                    livery_h2,
                    livery_h3,
                    livery_mcp,
                    livery_openapi_validate,
                    livery_router
                ]
            }},
            {elvis_style, max_function_clause_length, #{
                ignore => [
                    livery,
                    livery_client_hackney,
                    livery_h1,
                    livery_h2,
                    livery_h3,
                    livery_mcp,
                    livery_openapi_validate,
                    livery_router
                ]
            }},
            %% The per-stream translator loops thread immutable stream
            %% context (conn, ids, monitors, body ceiling) positionally.
            {elvis_style, max_function_arity, #{
                ignore => [livery_h1, livery_h2, livery_h3]
            }},
            %% Long lines are CDN URL string literals in the doc-UI
            %% HTML; they cannot be wrapped without splitting them.
            {elvis_text_style, line_length, #{ignore => [livery_openapi]}}
        ],
        ruleset => erl_files_strict
    },
    #{
        dirs => ["."],
        filter => "rebar.config",
        rules => [
            {elvis_project, no_branch_deps, disable}
        ],
        ruleset => rebar_config
    }
]}.