NAME

    Protocol::Tus - Tus protocol handling

VERSION

    This document describes Protocol::Tus version 0.001.

SYNOPSIS

       use Protocol::Tus;
    
       my $tus = Protocol::Tus->new(
          model => {
             class => 'Protocol::Tus::LocalDir',
             args  => { root => '/path/to/somewhere' },
          }
       );
    
       # assume we have some way of getting requests... This method takes
       # care of everything, including X-HTTP-Method-Override
       my $request = get_some_input_request();
    
       my $response = $tus->HTTP_request(
          $request->{method},   # POST, PATCH, ...
          $request->{headers},  # hash reference
          $request->{id},       # id of the upload or undef/''
          $request->{body},     # or, better, passed as a ref to a scalar
       );
       # The $response is-a Protocol::Tus::Response
    
       # or you can decide to call the relevant methods directly. 
       $response = $tus->HTTP_HEAD($request->{headers}, $request->{id});
       $response = $tus->HTTP_OPTIONS($request->{headers});
       $response = $tus->HTTP_PATCH($request->@{qw< headers id body >});
       $response = $tus->HTTP_POST($request->@{qw< headers id body >});
       $response = $tus->HTTP_DELETE($request->{headers}, $request->{id});
       # Again, the $response is-a Protocol::Tus::Response

DESCRIPTION

    Implement handling for the Tus protocol in Perl.

 Usage Overview

    The constructor requires psasing a model, either as an instance or with
    a specification useful for creating an instance:

       my $tus = Protocol::Tus->new(
          model => {
             class => 'Protocol::Tus::LocalDir',
             args  => { root => '/path/to/somewhere' },
          }
       );

    The model can be retrieved through the model accessor. It SHOULD be an
    instance of a class derived from Protocol::Tus::AbstractModel and it
    sure MUST implement its whole interface.

    The main method is HTTP_request, which accepts data gathered from an
    input request and figures out the best way to address it. This is the
    suggested way of using the module, because the Tus specification
    requires honoring the X-HTTP-Method-Override header before any action
    is done.

    Ancillary methods for addressing each specific request method are
    available too.

 Example Usage in Mojolicious

    A possible usage in a Mojolicious application might start from defining
    the base path for the endpoint and how to represent the different
    uploads; one possible way is in the example below (note: untested):

       use v5.24;
       use warnings;
       use Mojolicious::Lite -signatures;
       use Protocol::Tus;
       use Ouch qw< bleep >;
    
       # /tus is the endpoint for creating new uploads or getting general
       # info about the API, while /tus/:id is to interact with one upload.
       # Both are folded onto the same callback that will provide a wrapper
       # around Protocol::Tus->HTTP_request.
       any '/tus'     => \&call_tus;
       any '/tus/:id' => \&call_tus;
    
       app->start;
    
       sub call_tus ($c) {
          state $tus = Protocol::Tus->new(
             model => {
                class => 'Protocol::Tus::LocalDir',
                args  => { root => '/path/to/storage' },
             }
          );
    
          my $request = $c->req;
          my $body = $request->body;
          my $tus_response = $tus->HTTP_request(
             $request->method,
             $request->headers->to_hash,
             $c->param('id'), # possibly undefined, it's OK
             \$body,
          );
    
          # log any exception. It's a Ouch object, so you might want to
          # take a look into $exception->data too.
          if (defined(my $exception = $tus_response->exception)) {
             $c->app->log->error(bleep($exception));
          }
    
          # prepare the response to the client
          my $response = $c->res;
    
          # transfer headers from the $tus_response
          my $response_headers = $response->headers;
          my $thdrs = $tus_response->headers; # hash ref
          $response_headers->header($_ => $thdrs->{$_}) for keys($thdrs->%*);
    
          # rendering depends on the status code
          my $status = $tus_response->status;
          if ($status == 201) {  # set the Location header to the upload URL
             my $id = $tus_response->id;
             $response_headers->header(Location => "/tus/$id");
             $c->rendered(201);
          }
          elsif ($status == 204) {
             $c->rendered(204);
          }
          else {
             my $message = $tus_response->body // '';
             $c->render(status => $status, text => $message);
          );
    
          return;
       }

INTERFACE

    The interface is designed to work in a framework-agnostic way, provided
    that methods are fed with the correct inputs, which in general are:

    HTTP method

      the HTTP method that was used to send the request. It's a string
      containing the HTTP method name, case insensitive (it will be turned
      into a uppercase string).

    headers

      a reference to a hash holding the request headers. It is assumed that
      keys are unique in a case-insensitive way, i.e. there are no two keys
      of interest (as per the Tus specification) that fold onto the same
      lowercase representation; apart from that, the keys can have whatever
      case in line with the HTTP specification.

    id

      the identifier of an upload that the API is supposed to operate on.
      The Tus specification does not dictate any specific path or query
      structure and this module follows this pattern, it's up to the caller
      to figure out the right *upload identifier* for a request, if any.

    body

      the body of a request, e.g. in a PATCH or POST interaction. As the
      data might be relevant and best not copied too much around, it's
      possible and suggested to pass a reference to the scalar value that
      holds the data.

    Each method receives the arguments that it needs as a positional
    argument list, as detailed below. Method starting with HTTP_ return an
    instance of Protocol::Tus::Response and never raise exceptions.

    You are encouraged to determine the structure of the identifier and, in
    case, make sure that it is consistent with the model adopted. As an
    example, when using Protocol::Tus::LocalDir, identifiers are generated
    by the model and are always valid directory names. In any case, it will
    be up to the caller of the different methods to figure out how to
    represent these identifiers in the external API and how to extract them
    from the requests coming from clients.

 new

       my $tus = Protocol::Tus->new(model => $spec_or_object);
       my $tus = Protocol::Tus->new({model => $spec_or_object});

    Create a new instance of a Protocol::Tus.

    The following keys are supported:

    id_to_location

      set the callback to turn an identifier into a Location header.

    model

      set the model object or the specification to instantiate one.
      Required parameter.

      If an object is passed, it's assumed to be already valid; otherwise,
      the $spec_of_object is supposed to be a hash reference with keys
      class and args to create one.

 id_to_location

       my $coderef = $tus->id_to_location;

    Accessor to the optional callback to turn an upload identifier into a
    Location header needed in the 201 response when creating a new upload.
    If not set, the caller is supposed to generate the Location header
    independently.

 model

       my $model = $tus->model;

    Accessor to the model object. Most probably it will be something
    derived from Protocol::Tus::AbstractModel, like
    Protocol::Tus::LocalDir.

 HTTP_request

       my $response = $tus->HTTP_request(
          $method,   # POST, PATCH, ...
          $headers,  # hash reference
          $id,       # id of the upload or undef/''
          $body,     # or, better, passed as a ref to a scalar
       );

    Handle an incoming request. Not all arguments will be used but they are
    required to provide a single entry point for the whole Tus interface.

  HTTP_HEAD

       my $response = $tus->HTTP_HEAD($headers, $id);

 HTTP_OPTIONS

       my $response = $tus->HTTP_OPTIONS;

 HTTP_PATCH

       my $response = $tus->HTTP_PATCH($headers, $id, $body);

 HTTP_POST

       my $response = $tus->HTTP_POST($headers, $id, $body);

 HTTP_DELETE

       my $response = $tus->HTTP_DELETE($headers, $id);

BUGS AND LIMITATIONS

    Minimul perl version 5.24.

    Report bugs through Codeberg (patches welcome) at
    https://codeberg.org/polettix/Protocol-Tus.

AUTHOR

    Flavio Poletti <flavio@polettix.it>

COPYRIGHT AND LICENSE

    Copyright 2026 by Flavio Poletti <flavio@polettix.it>

    Licensed under the Apache License, Version 2.0 (the "License"); you may
    not use this file except in compliance with the License. You may obtain
    a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    implied. See the License for the specific language governing
    permissions and limitations under the License.

