# multipart/form-data Content Parsing Plugin for Nova Framework
`nova_multipart_plugin` is a middleware plugin for the [Nova Web Framework](https://github.com/novaframework/nova) that handles `multipart/form-data` requests.
## Features
When processing `multipart/form-data` content, this plugin automatically uploads files to a temporary directory and augments the Nova `Req` (Request) object with the following maps:
* `files` — A list of uploaded temporary files.
* `fields` — A list of parsed form fields.
---
## Installation
### 1. Add Dependency
Add `nova_multipart_plugin` to your `rebar.config` dependencies:
```erlang
{deps, [
%% ...
{nova, "0.14.1"},
nova_multipart_plugin,
%% ...
]}.
```
### 2. Configure sys.config
Register the plugin under the `plugins` section for the `pre_request` stage in your `sys.config`. You must specify the `tmp_dir` option (where temporary files will be stored) as a **binary string**.
```erlang
[
{kernel, [{logger_level, error}]},
{nova, [
{cowboy_configuration, #{port => 8080}},
{bootstrap_application, my_app},
{json_lib, thoas},
%% ...
{plugins, [
{pre_request, nova_multipart_plugin, #{tmp_dir => <<"/tmp/nova/">>}},
{pre_request, nova_request_plugin, #{decode_json_body => true, parse_qs => true}}
%% ...
]}
]}
].
```
---
## Usage
You can extract the `files` and `fields` maps directly from the Nova `Req` object within your controllers:
```erlang
upload_file(#{files := Files, fields := Fields} = Req) ->
Body = lists:foldl(
fun(File, Acc) ->
{FieldName, {FileName, ContentType, TmpPath}} = File,
SubDir = binary:encode_hex(crypto:strong_rand_bytes(16)),
Path = filename:join(SubDir, FileName),
NewPath = filename:join(<<"./uploads/">>, Path),
filelib:ensure_dir(NewPath),
move_file(TmpPath, NewPath),
Acc#{
FieldName => #{
<<"path">> => Path,
<<"file_name">> => FileName,
<<"content_type">> => ContentType
}
}
end,
#{},
Files
),
{json, 200, #{}, Body}.
move_file(Source, Dest) ->
case file:rename(Source, Dest) of
ok -> ok;
{error, exdev} ->
case file:copy(Source, Dest) of
{ok, _Bytes} -> file:delete(Source);
{error, Reason} -> {error, Reason}
end;
{error, Reason} -> {error, Reason}
end.
```
---
## Design Philosophy
The output generated by this plugin mirrors the exact structure of the incoming `multipart/form-data` payload without introducing opinions.
Decisions regarding persistent storage architecture, file access control, handling duplicate filenames, or parsing field names into hierarchical structures are highly domain-specific. Therefore, this plugin leaves those responsibilities entirely to the application developer.