Fosi Fossil Service
Small teams. Open source. Built on Fossil.

CaaS / Lamina

Unified Management for Fossil Repositories

Public | Owner: CaaS | Default branch: trunk | Last update: 2026-10-25 00:41

Artifact 661737d0b9e99ec18ffb3488bd0aa8bfd6252fbb51bf43768456bd90e1acdd65:


%%%-------------------------------------------------------------------
-module(nova_multipart_handler).
-moduledoc """
Behaviour for a `nova_multipart_plugin` file sink, configured as `{Mod, InitArgs}`. Chunks arrive straight off the socket, so a handler that writes each one away keeps memory flat regardless of upload size.

Every field of `part_info()` is client controlled. Never build a filesystem path from `filename` and never trust `content_type`.
""".

-export_type([part_info/0]).

-type part_info() :: #{name := binary(),
                        filename := binary(),
                        content_type := binary()}.

%% `{reject, Reason}' answers 400 (the part is unacceptable), `{error, Reason}'
%% answers 500 (the sink failed). Neither leads to handle_data/handle_end.
-callback init(PartInfo :: part_info(), InitArgs :: term()) ->
    {ok, HandlerState :: term()} | {reject, Reason :: term()} | {error, Reason :: term()}.

%% `{error, Reason}' aborts the part: handle_abort/2 runs and the client gets 500.
-callback handle_data(Data :: binary(), HandlerState :: term()) ->
    {ok, NewHandlerState :: term()} | {error, Reason :: term()}.

%% `Result' ends up under `result' in the request's `files' list.
-callback handle_end(HandlerState :: term()) ->
    {ok, Result :: term()} | {error, Reason :: term()}.

%% Cleanup for a part abandoned mid-stream. Must not raise.
-callback handle_abort(Reason :: term(), HandlerState :: term()) -> ok.

Clone

You can clone this repository with the following command:

fossil clone https://fosi.dev/CaaS/Lamina

Contribute

The best way to contribute is through e-mail. See the project documentation for more details.