Hello libart-seafire-resources
All checks were successful
on-push / build-and-test (push) Successful in 59s

This commit is contained in:
2025-10-18 00:45:23 +02:00
commit 977d921a3c
41 changed files with 1704 additions and 0 deletions

17
.editorconfig Normal file
View File

@@ -0,0 +1,17 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
indent_size = 4
max_line_length = off
trim_trailing_whitespace = false
[*.yaml]
indent_size = 2

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto

View File

@@ -0,0 +1,31 @@
name: on-push
on:
push:
tags-ignore:
- '*'
branches:
- '**'
jobs:
build-and-test:
runs-on: linux
container: code.helloryan.se/art/infra/buildenv/x86_64-fedora_42-unified:latest
volumes:
- /build
steps:
- name: Configure repository access
run: |
git config --global http.$GITHUB_SERVER_URL/.extraheader "Authorization: token ${{ secrets.ACT_RUNNER_TOKEN }}"
- name: Configure build directory
run: |
bpkg create -d /build cc config.cxx=clang++ config.cc.coptions="-Wall -Werror -Wno-unknown-pragmas"
- name: Build package
run: |
cd /build
bpkg build --yes --trust-yes $GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git##$GITHUB_SHA
- name: Test package
run: |
cd /build
b test

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
patreon: helloryan

31
.gitignore vendored Normal file
View File

@@ -0,0 +1,31 @@
.bdep/
# Local default options files.
#
.build2/local/
# Compiler/linker output.
#
*.d
*.t
*.i
*.i.*
*.ii
*.ii.*
*.o
*.obj
*.gcm
*.pcm
*.ifc
*.so
*.dylib
*.dll
*.a
*.lib
*.exp
*.pdb
*.ilk
*.exe
*.exe.dlls/
*.exe.manifest
*.pc

31
LICENSE Normal file
View File

@@ -0,0 +1,31 @@
Copyright © 2024 Ryan. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must
display the following acknowledgement:
This product includes software developed by Ryan, http://helloryan.se/.
4. Neither the name(s) of the copyright holder(s) nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

14
README.md Normal file
View File

@@ -0,0 +1,14 @@
# libart-seafire-resources
![Build badge](https://code.helloryan.se/art/libart-seafire-resources/actions/workflows/on-push.yaml/badge.svg)
libart-seafire-resources is part of the Seafire HTTP/1.1 library for C++.
## Dedication
This project is dedicated to the memory of Sefyr.
## Sponsorship
You can sponsor the development of this project via Patreon. Read more
over at https://patreon.com/helloryan.

9
art/seafire/resources/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
# Generated version header.
#
version.hxx
# Unit test executables and Testscript output directories
# (can be symlinks).
#
*.test
test-*.test

View File

@@ -0,0 +1,67 @@
intf_libs = # Interface dependencies.
impl_libs = # Implementation dependencies.
import intf_libs =+ libart-seafire-common%lib{art-seafire-common}
import intf_libs =+ libart-seafire-protocol%lib{art-seafire-protocol}
import intf_libs =+ libart-seafire-server%lib{art-seafire-server}
import intf_libs =+ libart-seafire-representation%lib{art-seafire-representation}
./: lib{art-seafire-resources}: libul{art-seafire-resources}
libul{art-seafire-resources}: {hxx ixx txx cxx}{** -**.test... -version} \
{hxx }{ version}
libul{art-seafire-resources}: $impl_libs $intf_libs
# Unit tests.
#
exe{*.test}:
{
test = true
install = false
}
for t: cxx{**.test...}
{
d = $directory($t)
n = $name($t)...
./: $d/exe{$n}: $t $d/{hxx ixx txx}{+$n} $d/testscript{+$n}
$d/exe{$n}: libul{art-seafire-resources}: bin.whole = false
}
hxx{version}: in{version} $src_root/manifest
{
dist = true
clean = ($src_root != $out_root)
}
# Build options.
#
cxx.poptions =+ "-I$out_root" "-I$src_root"
# Export options.
#
lib{art-seafire-resources}:
{
cxx.export.poptions = "-I$out_root" "-I$src_root"
cxx.export.libs = $intf_libs
}
# For pre-releases use the complete version to make sure they cannot
# be used in place of another pre-release or the final version. See
# the version module for details on the version.* variable values.
#
if $version.pre_release
lib{art-seafire-resources}: bin.lib.version = "-$version.project_id"
else
lib{art-seafire-resources}: bin.lib.version = "-$version.major.$version.minor"
# Install into the art/seafire/resources/ subdirectory of, say, /usr/include/
# recreating subdirectories.
#
{hxx ixx txx}{*}:
{
install = include/art/seafire/resources/
install.subdirs = true
}

View File

@@ -0,0 +1,33 @@
#ifndef art__seafire__resources__concepts_hxx_
#define art__seafire__resources__concepts_hxx_
#include <art/seafire/resources/traits.hxx>
namespace art::seafire::resources
{
// resources.
//
template<typename R>
concept GettableResource = traits::is_gettable_resource_v<R>;
template<typename R>
concept UpdatableResource = traits::is_updatable_resource_v<R>;
template<typename R>
concept CreatableResource = traits::is_creatable_resource_v<R>;
template<typename R>
concept ErasableResource = traits::is_erasable_resource_v<R>;
template<typename R>
concept Resource
= traits::is_gettable_resource_v<R>
|| traits::is_updatable_resource_v<R>
|| traits::is_creatable_resource_v<R>
|| traits::is_erasable_resource_v<R>;
} // namespace art::seafire::resources
#endif

View File

@@ -0,0 +1,32 @@
#ifndef art__seafire__resources__handle_create_hxx_
#define art__seafire__resources__handle_create_hxx_
#include <art/seafire/common/invoke.hxx>
#include <art/seafire/protocol/media-type.hxx>
#include <art/seafire/protocol/rfc7231/content-type.hxx>
#include <art/seafire/protocol/rfc7231/location.hxx>
#include <art/seafire/protocol/rfc7232/etag.hxx>
#include <art/seafire/protocol/rfc7232/last-modified.hxx>
#include <art/seafire/representation/representation.hxx>
#include <art/seafire/resources/concepts.hxx>
#include <art/seafire/resources/handle-get.hxx>
#include <art/seafire/resources/metadata.hxx>
#include <art/seafire/resources/preconditions.hxx>
#include <art/seafire/server/request.hxx>
#include <art/seafire/server/response.hxx>
namespace art::seafire::resources
{
template<CreatableResource R>
void
handle_create(server::request_t& req,
server::response_t& res,
R const& resource,
std::optional<protocol::media_type_t> const& accepted_type);
} // namespace art::seafire::resources
#include <art/seafire/resources/handle-create.txx>
#endif

View File

@@ -0,0 +1,40 @@
namespace art::seafire::resources
{
template<CreatableResource R>
void
handle_create(server::request_t& req,
server::response_t& res,
R const& resource,
std::optional<protocol::media_type_t> const& accepted_type)
{
using resource_traits = traits::resource_traits<std::decay_t<R>>;
using input_representation_type = typename resource_traits::create_input_representation_type;
using t = traits::resource_traits<R>;
if constexpr (std::is_same_v<void, typename t::create_result_type>) {
auto content_type = get<protocol::rfc7231::content_type_t>(req);
auto input = input_representation_type::read_from(content_type, req.content());
common::invoke(resource, req, &R::create, input);
res.send(204);
}
else if constexpr (representation::traits::is_variant_input_representation_v<input_representation_type>) {
res.send(server::common_error_t::not_implemented);
}
else if constexpr (representation::Representation<typename t::create_result_type>) {
auto content_type = get<protocol::rfc7231::content_type_t>(req);
auto input = input_representation_type::read_from(content_type, req.content());
auto result = common::invoke(resource, req, &R::create, input);
auto selected_rep = representation::select(result, accepted_type);
representation::send(req, res, 201, selected_rep, true);
}
else {
auto content_type = get<protocol::rfc7231::content_type_t>(req);
auto input = input_representation_type::read_from(content_type, req.content());
auto result = common::invoke(resource, req, &R::create, input);
handle_get(req, res, result, accepted_type, get_kind_t::created_resource);
}
}
} // namespace art::seafire::resources

View File

@@ -0,0 +1,34 @@
#ifndef art__seafire__resources__handle_erase_hxx_
#define art__seafire__resources__handle_erase_hxx_
#include <art/seafire/common/invoke.hxx>
#include <art/seafire/protocol/media-type.hxx>
#include <art/seafire/protocol/rfc7231/content-type.hxx>
#include <art/seafire/protocol/rfc7231/location.hxx>
#include <art/seafire/protocol/rfc7232/etag.hxx>
#include <art/seafire/protocol/rfc7232/last-modified.hxx>
#include <art/seafire/representation/representation.hxx>
#include <art/seafire/representation/select.hxx>
#include <art/seafire/representation/send.hxx>
#include <art/seafire/resources/concepts.hxx>
#include <art/seafire/resources/metadata.hxx>
#include <art/seafire/resources/preconditions.hxx>
#include <art/seafire/server/transaction.hxx>
#include <optional>
namespace art::seafire::resources
{
template<ErasableResource R>
void
handle_erase(server::request_t&,
server::response_t&,
R const&,
std::optional<protocol::media_type_t> const&);
} // namespace art::seafire::resources
#include <art/seafire/resources/handle-erase.txx>
#endif

View File

@@ -0,0 +1,25 @@
namespace art::seafire::resources
{
template<ErasableResource R>
void
handle_erase(server::request_t& req,
server::response_t& res,
R const& resource,
std::optional<protocol::media_type_t> const& accepted_type)
{
using t = traits::resource_traits<R>;
if constexpr (representation::Representation<typename t::erase_result_type>) {
auto result = common::invoke(resource, req, &R::erase);
auto selected_rep = representation::select(result, accepted_type);
representation::send(req, res, 200, selected_rep, true);
}
else {
common::invoke(resource, req, &R::erase);
res.send(204);
}
}
} // namespace art::seafire::resources

View File

@@ -0,0 +1,42 @@
#ifndef art__seafire__resources__handle_get_hxx_
#define art__seafire__resources__handle_get_hxx_
#include <art/seafire/common/invoke.hxx>
#include <art/seafire/protocol/media-type.hxx>
#include <art/seafire/protocol/rfc7231/content-type.hxx>
#include <art/seafire/protocol/rfc7231/location.hxx>
#include <art/seafire/protocol/rfc7232/etag.hxx>
#include <art/seafire/protocol/rfc7232/last-modified.hxx>
#include <art/seafire/representation/representation.hxx>
#include <art/seafire/representation/select.hxx>
#include <art/seafire/representation/send.hxx>
#include <art/seafire/resources/concepts.hxx>
#include <art/seafire/resources/metadata.hxx>
#include <art/seafire/resources/preconditions.hxx>
#include <art/seafire/server/transaction.hxx>
#include <optional>
namespace art::seafire::resources
{
enum class get_kind_t {
get,
head,
created_resource,
updated_resource
};
template<GettableResource R>
void
handle_get(server::request_t& req,
server::response_t& res,
R const& resource,
std::optional<protocol::media_type_t> const& accepted_type,
get_kind_t kind);
} // namespace art::seafire::resources
#include <art/seafire/resources/handle-get.txx>
#endif

View File

@@ -0,0 +1,41 @@
namespace art::seafire::resources
{
template<GettableResource R>
void
handle_get(server::request_t& req,
server::response_t& res,
R const& resource,
std::optional<protocol::media_type_t> const& accepted_type,
get_kind_t kind)
{
auto rep = common::invoke(resource, req, &R::get);
auto selected_rep = representation::select(rep, accepted_type);
if (!check_preconditions(req, res, selected_rep))
return;
namespace rfc7231 = protocol::rfc7231;
namespace rfc7232 = protocol::rfc7232;
if (auto opt_val = get_etag(resource); opt_val)
set<rfc7232::etag_t>(res, *opt_val);
if (auto opt_val = get_last_modified(resource); opt_val)
set<rfc7232::last_modified_t>(res, *opt_val);
if (kind == get_kind_t::created_resource || kind == get_kind_t::updated_resource) {
if (auto opt_val = get_location(resource); opt_val)
set<rfc7231::location_t>(res, *opt_val);
}
protocol::status_code_t status{200};
if (kind == get_kind_t::created_resource)
status = 201;
bool const send_content = kind != get_kind_t::head;
representation::send(req, res, status, selected_rep, send_content);
}
} // namespace art::seafire::resources

View File

@@ -0,0 +1,34 @@
#ifndef art__seafire__resources__handle_update_hxx_
#define art__seafire__resources__handle_update_hxx_
#include <art/seafire/common/invoke.hxx>
#include <art/seafire/protocol/media-type.hxx>
#include <art/seafire/protocol/rfc7231/content-type.hxx>
#include <art/seafire/protocol/rfc7231/location.hxx>
#include <art/seafire/protocol/rfc7232/etag.hxx>
#include <art/seafire/protocol/rfc7232/last-modified.hxx>
#include <art/seafire/representation/representation.hxx>
#include <art/seafire/resources/concepts.hxx>
#include <art/seafire/resources/handle-get.hxx>
#include <art/seafire/resources/metadata.hxx>
#include <art/seafire/resources/preconditions.hxx>
#include <art/seafire/server/request.hxx>
#include <art/seafire/server/response.hxx>
#include <optional>
namespace art::seafire::resources
{
template<UpdatableResource R>
void
handle_update(server::request_t& req,
server::response_t& res,
R const& resource,
std::optional<protocol::media_type_t> const& accepted_type);
} // namespace art::seafire::resources
#include <art/seafire/resources/handle-update.txx>
#endif

View File

@@ -0,0 +1,64 @@
namespace art::seafire::resources
{
template<UpdatableResource R>
void
handle_update(server::request_t& req,
server::response_t& res,
R const& resource,
std::optional<protocol::media_type_t> const& accepted_type)
{
using resource_traits = traits::resource_traits<std::decay_t<R>>;
using input_representation_type = typename resource_traits::update_input_representation_type;
if constexpr (std::is_same_v<void, input_representation_type>) {
using t = traits::resource_traits<R>;
if constexpr (GettableResource<typename t::update_result_type>) {
auto result = common::invoke(resource, req, &R::update);
handle_get(req, res, result, accepted_type, get_kind_t::updated_resource);
}
else if constexpr (representation::Representation<typename t::update_result_type>) {
auto result = common::invoke(resource, req, &R::update);
auto selected_rep = representation::select(result, accepted_type);
representation::send(req, res, 200, selected_rep, true);
}
else {
common::invoke(resource, req, &R::update);
res.send(204);
}
}
else {
auto content_type = get<protocol::rfc7231::content_type_t>(req);
if (!content_type) {
res.send(400);
return;
}
if (!input_representation_type::can_accept_input(*content_type)) {
res.send(400);
return;
}
auto input = input_representation_type::read_from(content_type, req.content());
using t = traits::resource_traits<R>;
if constexpr (GettableResource<typename t::update_result_type>) {
auto result = common::invoke(resource, req, &R::update, input);
handle_get(req, res, result, accepted_type, get_kind_t::updated_resource);
}
else if constexpr (representation::Representation<typename t::update_result_type>) {
auto result = common::invoke(resource, req, &R::update, input);
auto selected_rep = representation::select(result, accepted_type);
representation::send(req, res, 200, selected_rep, true);
}
else {
common::invoke(resource, req, &R::update, input);
res.send(204);
}
}
}
} // namespace art::seafire::resources

View File

@@ -0,0 +1,30 @@
#ifndef art__seafire__resources__handle_hxx_
#define art__seafire__resources__handle_hxx_
#include <art/seafire/protocol/rfc7231/allow.hxx>
#include <art/seafire/resources/concepts.hxx>
#include <art/seafire/resources/handle-create.hxx>
#include <art/seafire/resources/handle-erase.hxx>
#include <art/seafire/resources/handle-get.hxx>
#include <art/seafire/resources/handle-update.hxx>
#include <art/seafire/resources/negotiate.hxx>
#include <art/seafire/resources/traits.hxx>
#include <art/seafire/server/request.hxx>
#include <art/seafire/server/response.hxx>
#include <sstream>
#include <type_traits>
namespace art::seafire::resources
{
void
handle(server::request_t& req,
server::response_t& res,
Resource auto const& r);
} // namespace art::seafire::resources
#include <art/seafire/resources/handle.txx>
#endif

View File

@@ -0,0 +1,157 @@
namespace art::seafire::resources
{
void
handle(server::request_t& req,
server::response_t& res,
Resource auto const& r)
{
using resource_traits = traits::resource_traits<std::decay_t<decltype(r)>>;
auto const not_allowed = [&res]
{
set<protocol::rfc7231::allow_t>(res, resource_traits::allowed_methods());
res.send(server::common_error_t::method_not_allowed);
};
auto const& method = req.get_message().method();
// GET
//
if (method == "GET" || method == "HEAD") {
if constexpr (resource_traits::is_gettable) {
negotiate<typename resource_traits::get_representation_type>(
req,
res,
[&req, &res, &r](std::optional<protocol::media_type_t> const& accepted_type)
{
handle_get(
req,
res,
r,
accepted_type,
req.get_message().method() == "HEAD" ? get_kind_t::head : get_kind_t::get
);
}
);
return;
}
else {
not_allowed();
}
}
// PUT (update)
//
else if (method == "PUT") {
if constexpr (resource_traits::is_updatable) {
if constexpr (std::is_same_v<void, typename resource_traits::update_result_type>) {
handle_update(req, res, r, std::nullopt);
}
else if constexpr (representation::Representation<typename resource_traits::update_result_type>) {
negotiate<typename resource_traits::update_result_type>(
req,
res,
[&req, &res, &r](std::optional<protocol::media_type_t> const& accepted_type)
{
handle_update(req, res, r, accepted_type);
}
);
}
else {
negotiate<typename traits::resource_traits<typename resource_traits::update_result_type>::get_representation_type>(
req,
res,
[&req, &res, &r](std::optional<protocol::media_type_t> const& accepted_type)
{
handle_update(req, res, r, accepted_type);
}
);
}
}
else {
not_allowed();
}
}
// POST (create)
//
else if (method == "POST") {
if constexpr (resource_traits::is_creatable) {
if constexpr (std::is_same_v<void, typename resource_traits::update_result_type>) {
handle_create(req, res, r, std::nullopt);
}
else if constexpr (representation::Representation<typename resource_traits::create_result_type>) {
negotiate<typename resource_traits::create_result_type>(
req,
res,
[&req, &res, &r](std::optional<protocol::media_type_t> const& accepted_type)
{
handle_create(req, res, r, accepted_type);
}
);
}
else {
using one = typename resource_traits::create_result_type;
using two = typename traits::resource_traits<one>::get_representation_type;
//using resource_traits = traits::resource_traits<
negotiate<two>(
req,
res,
[&req, &res, &r](std::optional<protocol::media_type_t> const& accepted_type)
{
handle_create(req, res, r, accepted_type);
}
);
}
}
else {
not_allowed();
}
}
// DELETE
//
else if (method == "DELETE") {
if constexpr (resource_traits::is_erasable) {
if constexpr (std::is_same_v<void, typename resource_traits::erase_result_type>) {
handle_erase(req, res, r, std::nullopt);
}
else if constexpr (representation::Representation<typename resource_traits::erase_result_type>) {
negotiate<typename resource_traits::erase_result_type>(
req,
res,
[&req, &res, &r](std::optional<protocol::media_type_t> const& accepted_type)
{
handle_erase(req, res, r, accepted_type);
}
);
}
else {
using one = typename resource_traits::erase_result_type;
using two = typename traits::resource_traits<one>::get_representation_type;
//using resource_traits = traits::resource_traits<
negotiate<two>(
req,
res,
[&req, &res, &r](std::optional<protocol::media_type_t> const& accepted_type)
{
handle_erase(req, res, r, accepted_type);
}
);
}
}
else {
not_allowed();
}
}
// method not implemented.
//
else {
res.send(server::common_error_t::not_implemented);
}
}
} // namespace art::seafire::resources

View File

@@ -0,0 +1,104 @@
#ifndef art__seafire__resources__metadata_hxx_
#define art__seafire__resources__metadata_hxx_
#include <art/seafire/protocol/media-type.hxx>
#include <art/seafire/protocol/rfc7232/entity-tag.hxx>
#include <art/seafire/resources/concepts.hxx>
#include <art/seafire/resources/traits.hxx>
#include <art/seafire/representation/concepts.hxx>
#include <art/seafire/representation/representation.hxx>
#include <art/seafire/representation/traits.hxx>
#include <art/uri/uri.hxx>
#include <chrono>
#include <optional>
namespace art::seafire::resources
{
// location
//
template<Resource R>
std::optional<uri::uri_t>
get_location(R const& res)
{
using local_traits = traits::resource_traits<R>;
if constexpr (local_traits::has_location)
return res.location();
return std::nullopt;
}
// etag
//
template<representation::BasicRepresentation BR>
std::optional<protocol::rfc7232::entity_tag_t>
get_etag(BR const& rep)
{
using local_traits = representation::traits::representation_traits<BR>;
if constexpr (local_traits::has_entity_tag_t)
return rep.etag();
return std::nullopt;
}
inline
std::optional<protocol::rfc7232::entity_tag_t>
get_etag(representation::representation_t const& rep)
{
return rep.etag();
}
template<Resource R>
std::optional<protocol::rfc7232::entity_tag_t>
get_etag(R const& res)
{
using local_traits = traits::resource_traits<R>;
if constexpr (local_traits::has_entity_tag)
return res.etag();
return std::nullopt;
}
// last-modified
//
template<representation::BasicRepresentation BR>
std::optional<std::chrono::system_clock::time_point>
get_last_modified(BR const& rep)
{
using local_traits = representation::traits::representation_traits<BR>;
if constexpr (local_traits::has_last_modified)
return rep.last_modified();
return std::nullopt;
}
inline
std::optional<std::chrono::system_clock::time_point>
get_last_modified(representation::representation_t const& rep)
{
return rep.last_modified();
}
template<Resource R>
std::optional<std::chrono::system_clock::time_point>
get_last_modified(R const& res)
{
using local_traits = traits::resource_traits<R>;
if constexpr (local_traits::has_last_modified)
return res.last_modified();
return std::nullopt;
}
} // namespace art::seafire::resources
#endif

View File

@@ -0,0 +1,28 @@
#ifndef art__seafire__resources__negotiate_hxx_
#define art__seafire__resources__negotiate_hxx_
#include <art/seafire/protocol/media-type.hxx>
#include <art/seafire/protocol/rfc7231/accept.hxx>
#include <art/seafire/representation/concepts.hxx>
#include <art/seafire/representation/traits.hxx>
#include <art/seafire/resources/concepts.hxx>
#include <art/seafire/resources/traits.hxx>
#include <art/seafire/server/request.hxx>
#include <art/seafire/server/response.hxx>
#include <optional>
namespace art::seafire::resources
{
// fixme: implement concept for handler.
template<representation::Representation R, typename Handler>
void
negotiate(server::request_t&, server::response_t&, Handler&&);
} // namespace art::seafire::resources
#include <art/seafire/resources/negotiate.txx>
#endif

View File

@@ -0,0 +1,31 @@
namespace art::seafire::resources
{
template<representation::Representation R, typename Handler>
void
negotiate(server::request_t& req, server::response_t& res, Handler&& handler)
{
using representation_traits = representation::traits::representation_traits<R>;
if constexpr (representation_traits::is_content_negotiable) {
using protocol::rfc7231::accept_t;
if (auto opt_accept = get<accept_t>(req); opt_accept) {
auto const accept = *opt_accept;
for (auto const& accepted_type : accept) {
if (R::is_accepted(accepted_type)) {
handler(accepted_type);
return;
}
}
res.send(server::common_error_t::not_acceptable);
return;
}
}
handler(std::nullopt);
}
} // namespace art::seafire::resources

View File

@@ -0,0 +1,67 @@
#ifndef art__seafire__resources__preconditions_hxx_
#define art__seafire__resources__preconditions_hxx_
#include <art/seafire/protocol/rfc7232/if-match.hxx>
#include <art/seafire/protocol/rfc7232/if-modified-since.hxx>
#include <art/seafire/protocol/rfc7232/if-none-match.hxx>
#include <art/seafire/protocol/rfc7232/if-unmodified-since.hxx>
#include <art/seafire/resources/concepts.hxx>
#include <art/seafire/resources/traits.hxx>
#include <art/seafire/server/request.hxx>
#include <art/seafire/server/response.hxx>
#include <art/seafire/representation/concepts.hxx>
#include <art/seafire/representation/traits.hxx>
#include <chrono>
namespace art::seafire::resources
{
bool
check_preconditions(
server::request_t& req,
server::response_t& res,
representation::BasicRepresentation auto const& r);
// If-Match
//
bool
check_if_match(
server::request_t& req,
server::response_t& res,
representation::BasicRepresentation auto const& r,
protocol::rfc7232::if_match_t const& if_match);
// If-Unmodified-Since
//
bool
check_if_unmodified_since(
server::request_t& req,
server::response_t& res,
representation::BasicRepresentation auto const& representation,
std::chrono::system_clock::time_point const& if_unmodified_since);
// If-None-Match
//
bool
check_if_none_match(
server::request_t& req,
server::response_t& res,
representation::BasicRepresentation auto const& r,
protocol::rfc7232::if_none_match_t const& if_none_match);
// If-Modified-Since
//
bool
check_if_modified_since(
server::request_t& req,
server::response_t& res,
representation::BasicRepresentation auto const& r,
std::chrono::system_clock::time_point const& if_modified_since);
} // namespace art::seafire::resources
#include <art/seafire/resources/preconditions.txx>
#endif

View File

@@ -0,0 +1,151 @@
namespace art::seafire::resources
{
bool
check_preconditions(
server::request_t& req,
server::response_t& res,
representation::BasicRepresentation auto const& r)
{
namespace rfc7232 = protocol::rfc7232;
using representation_traits = representation::traits::representation_traits<decltype(r)>;
if (auto if_match = get<rfc7232::if_match_t>(req); if_match) {
if constexpr (representation_traits::has_entity_tag) {
bool match = check_if_match(req, res, r, *if_match);
if (!match) {
res.send(server::common_error_t::precondition_failed);
return false;
}
}
else {
res.send(server::common_error_t::precondition_failed);
return false;
}
}
else if (auto if_unmodified_since = get<rfc7232::if_unmodified_since_t>(req); if_unmodified_since) {
bool match = check_if_unmodified_since(req, res, r, *if_unmodified_since);
if (!match) {
res.send(server::common_error_t::precondition_failed);
return false;
}
}
if (auto if_none_match = get<rfc7232::if_none_match_t>(req); if_none_match) {
bool match = false;
if (!match) {
if (req.get_message().method() == "GET" || req.get_message().method() == "HEAD")
res.send(server::common_error_t::not_modified);
else
res.send(server::common_error_t::precondition_failed);
return false;
}
}
else if (auto if_modified_since = get<rfc7232::if_modified_since_t>(req); if_modified_since) {
bool match = false;
if (!match) {
res.send(server::common_error_t::precondition_failed);
return false;
}
}
return true;
}
// If-Match
//
bool
check_if_match(
server::request_t& req,
server::response_t& res,
representation::BasicRepresentation auto const& r,
protocol::rfc7232::if_match_t const& if_match)
{
using representation_traits = representation::traits::representation_traits<std::decay_t<decltype(r)>>;
if constexpr (representation_traits::has_entity_tag) {
auto etag = [&r]
{
if constexpr (common::traits::is_optional_v<decltype(r.etag())>) {
return r.etag();
}
else {
return std::optional<protocol::rfc7232::entity_tag_t>{r.etag()};
}
}();
if (!etag)
return false;
if (if_match.is_anything())
return true;
for (auto const& j : if_match.tags()) {
if (strong_compare(*etag, j))
return true;
}
}
return false;
}
// If-Unmodified-Since
//
bool
check_if_unmodified_since(
server::request_t& req,
server::response_t& res,
representation::BasicRepresentation auto const& r,
std::chrono::system_clock::time_point const& if_unmodified_since)
{
using representation_traits = representation::traits::representation_traits<std::decay_t<decltype(r)>>;
if constexpr (representation_traits::has_last_modified) {
auto last_modified = r.last_modified();
if (last_modified <= if_unmodified_since)
return true;
}
return false;
}
// If-None-Match
//
bool
check_if_none_match(
server::request_t& req,
server::response_t& res,
representation::BasicRepresentation auto const& r,
protocol::rfc7232::if_none_match_t const& if_none_match)
{
return false;
}
// If-Modified-Since
//
bool
check_if_modified_since(
server::request_t& req,
server::response_t& res,
representation::BasicRepresentation auto const& r,
std::chrono::system_clock::time_point const& if_modified_since)
{
using representation_traits = representation::traits::representation_traits<std::decay_t<decltype(r)>>;
if constexpr (representation_traits::has_last_modified) {
auto last_modified = r.last_modified();
if (last_modified >= if_modified_since)
return true;
}
return false;
}
} // namespace art::seafire::resources

View File

@@ -0,0 +1,66 @@
#ifndef art__seafire__resources__resource_handler_hxx_
#define art__seafire__resources__resource_handler_hxx_
#include <art/seafire/common/invoke.hxx>
#include <art/seafire/protocol/media-type.hxx>
#include <art/seafire/protocol/rfc7231/accept.hxx>
#include <art/seafire/protocol/rfc7231/content-type.hxx>
#include <art/seafire/protocol/rfc7232/entity-tag.hxx>
#include <art/seafire/protocol/rfc7232/etag.hxx>
#include <art/seafire/protocol/rfc7232/last-modified.hxx>
#include <art/seafire/resources/concepts.hxx>
#include <art/seafire/resources/handle.hxx>
#include <art/seafire/resources/traits.hxx>
#include <art/seafire/server/request.hxx>
#include <art/seafire/server/response.hxx>
#include <optional>
#include <string>
#include <tuple>
#include <vector>
namespace art::seafire::resources
{
template<Resource R,
typename F, // TODO: Implement factory concept
typename... FactoryArgs>
class resource_handler_t {
public:
using resource_type = R;
using factory_type = F;
explicit
resource_handler_t(factory_type factory, FactoryArgs&&... args);
factory_type const&
factory() const
{
return factory_;
}
void
on_request(server::request_t& req, server::response_t& res) const;
void
operator()(server::request_t& req, server::response_t& res) const
{
on_request(req, res);
}
private:
factory_type factory_;
std::tuple<FactoryArgs...> factory_args_;
};
template<Resource R,
typename F,
typename... FactoryArgs,
typename H = resource_handler_t<R, F, FactoryArgs...>>
H use_resource(F factory, FactoryArgs&&... args);
} // namespace art::seafire::resources
#include <art/seafire/resources/resource-handler.txx>
#endif

View File

@@ -0,0 +1,36 @@
namespace art::seafire::resources
{
template<Resource R, typename F, typename... FactoryArgs >
resource_handler_t<R, F, FactoryArgs...>::
resource_handler_t(factory_type factory, FactoryArgs&&... args)
: factory_{std::move(factory)},
factory_args_{std::forward<FactoryArgs>(args)...}
{}
template< Resource R, typename F, typename... FactoryArgs >
void
resource_handler_t< R, F, FactoryArgs... >::
on_request(server::request_t& req,
server::response_t& res) const
{
auto factory_invoker = [f = factory(), &req, &res](auto... args)
{
return common::invoke(req, &F::template factory<resource_type>::make, f, req, res, std::forward<decltype(args)>(args)...);
};
auto r = std::apply(factory_invoker, factory_args_);
handle(req, res, r);
}
template<Resource R,
typename F,
typename... FactoryArgs,
typename H>
H
use_resource(F factory, FactoryArgs&&... args)
{
return H{std::move(factory), std::forward<FactoryArgs>(args)...};
}
} // namespace art::seafire::resources

View File

@@ -0,0 +1,339 @@
#ifndef art__seafire__resources__traits_hxx_
#define art__seafire__resources__traits_hxx_
#include <art/seafire/common/traits.hxx>
#include <art/seafire/protocol/media-type.hxx>
#include <art/seafire/protocol/rfc7232/entity-tag.hxx>
#include <art/seafire/protocol/token.hxx>
#include <art/seafire/representation/traits.hxx>
#include <art/uri/uri.hxx>
#include <chrono>
#include <cstdint>
#include <iostream>
#include <optional>
#include <tuple>
#include <type_traits>
#include <variant>
#include <vector>
namespace art::seafire::resources::traits
{
// =============================================================================
// resources.
//
// is_gettable_resource<R>
//
template<typename, typename = std::void_t<>>
struct is_gettable_resource : std::false_type {};
template<typename R>
struct is_gettable_resource<
R,
std::void_t<
decltype(&R::get)
>
> : std::bool_constant<
std::is_same_v<
void,
typename common::traits::function_traits<decltype(&R::get)>::return_type
>
||
representation::traits::is_representation_v<
typename common::traits::function_traits<decltype(&R::get)>::return_type
>
> {
};
template<typename R>
inline constexpr bool is_gettable_resource_v{is_gettable_resource<R>::value};
template<typename R, bool = is_gettable_resource_v<R>>
struct get_traits {
using result_type = void;
};
template<typename R>
struct get_traits<R, true> {
using function_traits = common::traits::function_traits<decltype(&R::get)>;
using result_type = common::traits::remove_optional_t<typename function_traits::return_type>;
};
// is_updatable_resource<R>
//
template<typename, typename = std::void_t<>>
struct is_updatable_resource : std::false_type {};
template<typename R>
struct is_updatable_resource<
R,
std::void_t<
decltype(&R::update)
>
> : std::bool_constant<
// Check if the first arg to R::update() is an input representation.
//
(
std::is_same_v<
void,
typename common::traits::function_arg_n<decltype(&R::update), 0>::type
>
||
representation::traits::is_input_representation_v<
typename common::traits::function_arg_n<decltype(&R::update), 0>::type
>
)
&&
(
// Check if return value of R::update() is void.
std::is_same_v<
void,
typename common::traits::function_traits<decltype(&R::update)>::return_type
>
||
// Check if return value of R::update() is a gettable resource.
is_gettable_resource_v<
typename common::traits::function_traits<decltype(&R::update)>::return_type
>
||
// Check if return value of R::update() is a representation.
representation::traits::is_representation_v<
typename common::traits::function_traits<decltype(&R::update)>::return_type
>
)
> {
};
template<typename R>
inline constexpr bool is_updatable_resource_v{is_updatable_resource<R>::value};
template<typename R, bool = is_updatable_resource_v<R>>
struct update_traits {
using input_representation_type = void;
using result_type = void;
};
template<typename R>
struct update_traits<R, true> {
using function_traits = common::traits::function_traits<decltype(&R::update)>;
using input_representation_type = common::traits::function_arg_n<decltype(&R::update), 0>::type;
using result_type = typename function_traits::return_type;
};
// is_creatable_resource<R>
//
template<typename, typename = std::void_t<>>
struct is_creatable_resource : std::false_type {};
template<typename R>
struct is_creatable_resource<
R,
std::void_t<
decltype(&R::create)
>
> : std::bool_constant<
representation::traits::is_input_representation_v<
typename common::traits::function_arg_n<decltype(&R::create), 0>::type
>
&&
(
// Check if return value of R::create() is void.
std::is_same_v<
void,
typename common::traits::function_traits<decltype(&R::create)>::return_type
>
||
// Check if return value of R::create() is a gettable resource.
is_gettable_resource_v<
typename common::traits::function_traits<decltype(&R::create)>::return_type
>
||
// Check if return value of R::create() is a representation.
representation::traits::is_representation_v<
typename common::traits::function_traits<decltype(&R::create)>::return_type
>
)
> {
};
template<typename R>
inline constexpr bool is_creatable_resource_v{is_creatable_resource<R>::value};
template<typename R, bool = is_creatable_resource_v<R>>
struct create_traits {
using input_representation_type = void;
using result_type = void;
};
template<typename R>
struct create_traits<R, true> {
using function_traits = common::traits::function_traits<decltype(&R::create)>;
using input_representation_type = common::traits::function_arg_n<decltype(&R::create), 0>::type;
using result_type = typename function_traits::return_type;
};
// is_erasable_resource<R>
//
template<typename, typename = std::void_t<>>
struct is_erasable_resource : std::false_type {};
template<typename R>
struct is_erasable_resource<
R,
std::void_t<
decltype(std::declval<R const>().erase())
>
> : std::true_type{
};
template<typename R>
constexpr bool is_erasable_resource_v{is_erasable_resource<R>::value};
template<typename R, bool = is_erasable_resource_v<R>>
struct erase_traits {
using result_type = void;
};
template<typename R>
struct erase_traits<R, true> {
using function_traits = common::traits::function_traits<decltype(&R::erase)>;
using result_type = typename function_traits::return_type;
};
// is_resource
//
template<typename R>
struct is_resource : std::bool_constant<
is_gettable_resource_v<R> ||
is_updatable_resource_v<R> ||
is_creatable_resource_v<R> ||
is_erasable_resource_v<R>
> {
};
template<typename R>
constexpr bool is_resource_v{is_resource<R>::value};
// has_location
//
template<typename, typename = std::void_t<>>
struct has_location : std::false_type {};
template<typename R>
struct has_location<
R,
std::void_t<
decltype(
uri::uri_t{
std::declval<R const>().location()
}
)
>
> : std::true_type{
};
template<typename R>
constexpr bool has_location_v{has_location<R>::value};
// resource_traits
//
template<typename R>
struct resource_traits {
using resource_type = R;
// properties
//
static constexpr bool has_entity_tag{
representation::traits::has_entity_tag_v<resource_type>
};
static constexpr bool has_last_modified{
representation::traits::has_last_modified_v<resource_type>
};
static constexpr bool has_location{
has_location_v<resource_type>
};
static
protocol::tokens_t
allowed_methods()
{
protocol::tokens_t methods;
if constexpr (is_gettable) {
methods.emplace_back("GET");
methods.emplace_back("HEAD");
}
if constexpr (is_updatable)
methods.emplace_back("PUT");
if constexpr (is_creatable)
methods.emplace_back("POST");
if constexpr (is_erasable)
methods.emplace_back("DELETE");
return methods;
}
// GET
//
static constexpr bool is_gettable{
is_gettable_resource_v<resource_type>
};
using get_traits = traits::get_traits<resource_type>;
using get_representation_type = typename get_traits::result_type;
using get_representation_type_traits = representation::traits::representation_traits<
get_representation_type
>;
// UPDATE
//
static constexpr bool is_updatable{
is_updatable_resource_v<resource_type>
};
using update_traits = traits::update_traits<resource_type>;
using update_input_representation_type = typename update_traits::input_representation_type;
using update_input_representation_type_traits = representation::traits::input_representation_traits<update_input_representation_type>;
using update_result_type = typename update_traits::result_type;
// CREATE
//
static constexpr bool is_creatable{
is_creatable_resource_v<resource_type>
};
using create_traits = traits::create_traits<resource_type>;
using create_input_representation_type = typename create_traits::input_representation_type;
using create_input_representation_type_traits = representation::traits::input_representation_traits<create_input_representation_type>;
using create_result_type = typename create_traits::result_type;
// ERASE
//
static constexpr bool is_erasable{
is_erasable_resource_v<resource_type>
};
using erase_traits = traits::erase_traits<resource_type>;
using erase_result_type = typename erase_traits::result_type;
};
} // namespace art::seafire::resources::traits
#endif

View File

@@ -0,0 +1,37 @@
#ifndef art__seafire__resources__version_hxx_
#define art__seafire__resources__version_hxx_
// The numeric version format is AAAAABBBBBCCCCCDDDE where:
//
// AAAAA - major version number
// BBBBB - minor version number
// CCCCC - bugfix version number
// DDD - alpha / beta (DDD + 500) version number
// E - final (0) / snapshot (1)
//
// When DDDE is not 0, 1 is subtracted from AAAAABBBBBCCCCC. For example:
//
// Version AAAAABBBBBCCCCCDDDE
//
// 0.1.0 0000000001000000000
// 0.1.2 0000000001000020000
// 1.2.3 0000100002000030000
// 2.2.0-a.1 0000200001999990010
// 3.0.0-b.2 0000299999999995020
// 2.2.0-a.1.z 0000200001999990011
//
#define LIBART_SEAFIRE_RESOURCES_VERSION $libart_seafire_resources.version.project_number$ULL
#define LIBART_SEAFIRE_RESOURCES_VERSION_STR "$libart_seafire_resources.version.project$"
#define LIBART_SEAFIRE_RESOURCES_VERSION_ID "$libart_seafire_resources.version.project_id$"
#define LIBART_SEAFIRE_RESOURCES_VERSION_FULL "$libart_seafire_resources.version$"
#define LIBART_SEAFIRE_RESOURCES_VERSION_MAJOR $libart_seafire_resources.version.major$
#define LIBART_SEAFIRE_RESOURCES_VERSION_MINOR $libart_seafire_resources.version.minor$
#define LIBART_SEAFIRE_RESOURCES_VERSION_PATCH $libart_seafire_resources.version.patch$
#define LIBART_SEAFIRE_RESOURCES_PRE_RELEASE $libart_seafire_resources.version.pre_release$
#define LIBART_SEAFIRE_RESOURCES_SNAPSHOT_SN $libart_seafire_resources.version.snapshot_sn$ULL
#define LIBART_SEAFIRE_RESOURCES_SNAPSHOT_ID "$libart_seafire_resources.version.snapshot_id$"
#endif

4
build/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

7
build/bootstrap.build Normal file
View File

@@ -0,0 +1,7 @@
project = libart-seafire-resources
using version
using config
using test
using install
using dist

6
build/export.build Normal file
View File

@@ -0,0 +1,6 @@
$out_root/
{
include art/seafire/resources/
}
export $out_root/art/seafire/resources/$import.target

16
build/root.build Normal file
View File

@@ -0,0 +1,16 @@
# Uncomment to suppress warnings coming from external libraries.
#
#cxx.internal.scope = current
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

5
buildfile Normal file
View File

@@ -0,0 +1,5 @@
./: {art/ tests/} doc{README.md} legal{LICENSE} manifest
# Don't install tests.
#
tests/: install = false

16
manifest Normal file
View File

@@ -0,0 +1,16 @@
: 1
name: libart-seafire-resources
version: 0.1.0-a.0.z
language: c++
summary: Seafire Resources C++ library
license: BSD-4-Clause
description-file: README.md
url: https://art.helloryan.se/
email: art@helloryan.se
depends: * build2 >= 0.17.0
depends: * bpkg >= 0.17.0
depends: libasio ^1.29.0
depends: libart-seafire-common ^0.1.0-
depends: libart-seafire-protocol ^0.1.0-
depends: libart-seafire-server ^0.1.0-
depends: libart-seafire-representation ^0.1.0-

23
repositories.manifest Normal file
View File

@@ -0,0 +1,23 @@
: 1
summary: Seafire-Resources project repository
:
role: prerequisite
location: https://pkg.cppget.org/1/beta
trust: 70:64:FE:E4:E0:F3:60:F1:B4:51:E1:FA:12:5C:E0:B3:DB:DF:96:33:39:B9:2E:E5:C2:68:63:4C:A6:47:39:43
:
role: prerequisite
location: https://code.helloryan.se/art/libart-seafire-common.git##HEAD
:
role: prerequisite
location: https://code.helloryan.se/art/libart-seafire-protocol.git##HEAD
:
role: prerequisite
location: https://code.helloryan.se/art/libart-seafire-server.git##HEAD
:
role: prerequisite
location: https://code.helloryan.se/art/libart-seafire-representation.git##HEAD

8
tests/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
# Test executables.
#
driver
# Testscript output directories (can be symlinks).
#
test
test-*

4
tests/build/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/config.build
/root/
/bootstrap/
build/

View File

@@ -0,0 +1,5 @@
project = # Unnamed tests subproject.
using config
using test
using dist

16
tests/build/root.build Normal file
View File

@@ -0,0 +1,16 @@
cxx.std = latest
using cxx
hxx{*}: extension = hxx
ixx{*}: extension = ixx
txx{*}: extension = txx
cxx{*}: extension = cxx
# Every exe{} in this subproject is by default a test.
#
exe{*}: test = true
# The test target for cross-testing (running tests under Wine, etc).
#
test.target = $cxx.target

1
tests/buildfile Normal file
View File

@@ -0,0 +1 @@
./: {*/ -build/}