.. _library_s3:

``s3``
======

Portable S3-compatible client library layered on top of the normalized
``http_client`` stack. The current implementation focuses on
general-purpose bucket operations and AWS Signature Version 4 request
signing.

The library depends on the contributed ``xml_parser`` parser to decode
S3 XML responses and wraps it behind the ``s3_xml`` adapter so the
public API exposes stable S3-specific terms instead of generic XML parse
trees.

This library can be used with backend Prolog systems that support
unbound integer arithmetic and the ``sockets`` library: ECLiPSe, SICStus
Prolog, SWI-Prolog, Trealla Prolog, and XVM.

API documentation
-----------------

Open the
`../../apis/library_index.html#s3 <../../apis/library_index.html#s3>`__
link in a web browser.

Loading
-------

To load the library, load the ``loader.lgt`` file:

::

   | ?- logtalk_load(s3(loader)).

Testing
-------

To test this library, load the ``tester.lgt`` file:

::

   | ?- logtalk_load(s3(tester)).

Current scope
-------------

- ``list_buckets/2``
- ``head_bucket/3``
- ``list_objects_v2/4``
- ``head_object/4``
- ``get_object/5``
- ``presigned_get_object/4``
- ``put_object/6``
- ``presigned_put_object/4``
- ``presigned_post_object/4``
- ``delete_object/4``
- ``copy_object/5``
- explicit session wrapper via ``s3_session(_HTTPSocket_)``
- AWS Signature Version 4 request signing for service ``s3``
- virtual-hosted and path-style endpoint generation
- direct support for custom S3-compatible endpoints such as MinIO

Basic usage
-----------

For typical file transfers, prefer the file-based arities:

- ``get_object/5`` downloads an object to a local file path and returns
  response metadata separately.
- ``put_object/6`` uploads a local file path and returns the object ETag
  and response metadata separately.

The ``presigned_get_object/4``, ``presigned_put_object/4``, and
``presigned_post_object/4`` predicates generate portable SigV4
query-string URLs for external HTTP clients. They default to
``UNSIGNED-PAYLOAD``, accept ``expires(Seconds)`` with a maximum of
``604800`` seconds, support signed payloads using
``payload_hash_mode(signed)`` plus ``payload_hash(Hash)``, and sign any
extra request headers supplied using ``headers/1``.

Result terms for the predicates returning a ``Result`` argument are:

- ``delete_object/4`` -> ``delete_result(Properties)``
- ``copy_object/5`` -> ``copy_result(ETag, Properties)``

List buckets using the default TLS-capable transport:

::

   | ?- s3_client::list_buckets(
            Buckets,
            [
                credentials(access_key_id('AKIDEXAMPLE'), secret_access_key('secret')),
                amz_date('20240627T120000Z')
            ]
        ).

Download ``report.txt`` from a custom S3-compatible endpoint into a
local file:

::

   | ?- s3_client::get_object(
           photos,
           'report.txt',
           'report.txt',
           Properties,
           [
                endpoint('http://127.0.0.1:9000'),
                addressing_style(path),
                credentials(access_key_id('minioadmin'), secret_access_key('minioadmin')),
                amz_date('20240627T120000Z')
            ]
        ).

Upload the same local ``report.txt`` file to the same custom endpoint:

::

   | ?- s3_client::put_object(
           photos,
           'report.txt',
           'report.txt',
           ETag,
           Properties,
           [
               endpoint('http://127.0.0.1:9000'),
               addressing_style(path),
               credentials(access_key_id('minioadmin'), secret_access_key('minioadmin')),
               amz_date('20240627T120000Z')
           ]
        ).

Use an explicit session handle to carry defaults:

::

   | ?- s3_session(http_process_transport)::open(
            Session,
            [
                region('eu-west-1'),
                credentials(access_key_id('AKIDEXAMPLE'), secret_access_key('secret')),
                amz_date('20240627T120000Z')
            ]
        ),
        s3_session(http_process_transport)::list_buckets(Session, Buckets, []),
        s3_session(http_process_transport)::close(Session).

Generate a presigned download URL for ``report.txt``:

::

   | ?- s3_client::presigned_get_object(
           photos,
           'report.txt',
           URL,
           [
               region('eu-west-1'),
               credentials(access_key_id('AKIDEXAMPLE'), secret_access_key('secret')),
               amz_date('20240627T120000Z'),
               expires(600)
           ]
        ).

Generate a presigned upload URL for the same ``report.txt`` object:

::

   | ?- s3_client::presigned_put_object(
           photos,
           'report.txt',
           URL,
           [
               endpoint('http://127.0.0.1:9000'),
               addressing_style(path),
               credentials(access_key_id('minioadmin'), secret_access_key('minioadmin')),
               headers([content_type-'text/plain']),
               amz_date('20240627T120000Z'),
               expires(300)
           ]
        ).

Generate a presigned POST URL for the same ``report.txt`` object:

::

   | ?- s3_client::presigned_post_object(
           photos,
           'report.txt',
           URL,
           [
               endpoint('http://127.0.0.1:9000'),
               addressing_style(path),
               credentials(access_key_id('minioadmin'), secret_access_key('minioadmin')),
               amz_date('20240627T120000Z'),
               expires(300)
           ]
        ).

Generate a presigned URL that signs a precomputed SHA-256 payload hash:

::

   | ?- s3_client::presigned_put_object(
           photos,
           'report.txt',
           URL,
           [
               region('eu-west-1'),
               credentials(access_key_id('AKIDEXAMPLE'), secret_access_key('secret')),
               amz_date('20240627T120000Z'),
               payload_hash_mode(signed),
               payload_hash('2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824')
           ]
        ).

When using signed extra headers such as ``content_type-'text/plain'``,
the eventual HTTP client must send the same headers when dereferencing
the URL.

Current limitations
-------------------

- Time signing defaults derive UTC from the local system clock plus the
  configurable ``utc_offset_seconds/1`` option. For deterministic use
  and tests, prefer passing ``amz_date/1`` or ``request_time/1``.
- Multipart uploads are not implemented yet.
- S3 Express / directory bucket APIs are outside the current scope.
