{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
}
]}.