From 38c7f8edc94457f3b57c992c0cbddc99a5ff9954 Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 24 Dec 2024 22:39:09 +0100 Subject: [PATCH] Hello libcode-seafire-resources --- .editorconfig | 17 ++ .gitattributes | 1 + .gitea/workflows/on-push.yaml | 24 ++ .gitignore | 31 ++ LICENSE | 31 ++ README.md | 21 ++ build/.gitignore | 4 + build/bootstrap.build | 7 + build/export.build | 6 + build/root.build | 16 + buildfile | 5 + code/seafire/resources/.gitignore | 9 + code/seafire/resources/buildfile | 67 +++++ code/seafire/resources/concepts.hxx | 33 ++ code/seafire/resources/handle-create.hxx | 32 ++ code/seafire/resources/handle-create.txx | 34 +++ code/seafire/resources/handle-get.hxx | 42 +++ code/seafire/resources/handle-get.txx | 47 +++ code/seafire/resources/handle-update.hxx | 34 +++ code/seafire/resources/handle-update.txx | 45 +++ code/seafire/resources/handle.hxx | 29 ++ code/seafire/resources/handle.txx | 129 ++++++++ code/seafire/resources/metadata.hxx | 104 +++++++ code/seafire/resources/negotiate.hxx | 28 ++ code/seafire/resources/negotiate.txx | 31 ++ code/seafire/resources/preconditions.hxx | 67 +++++ code/seafire/resources/preconditions.txx | 151 ++++++++++ code/seafire/resources/resource-handler.hxx | 66 ++++ code/seafire/resources/resource-handler.txx | 36 +++ code/seafire/resources/traits.hxx | 316 ++++++++++++++++++++ code/seafire/resources/version.hxx.in | 37 +++ manifest | 16 + repositories.manifest | 23 ++ tests/.gitignore | 8 + tests/build/.gitignore | 4 + tests/build/bootstrap.build | 5 + tests/build/root.build | 16 + tests/buildfile | 1 + 38 files changed, 1573 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitea/workflows/on-push.yaml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build/.gitignore create mode 100644 build/bootstrap.build create mode 100644 build/export.build create mode 100644 build/root.build create mode 100644 buildfile create mode 100644 code/seafire/resources/.gitignore create mode 100644 code/seafire/resources/buildfile create mode 100644 code/seafire/resources/concepts.hxx create mode 100644 code/seafire/resources/handle-create.hxx create mode 100644 code/seafire/resources/handle-create.txx create mode 100644 code/seafire/resources/handle-get.hxx create mode 100644 code/seafire/resources/handle-get.txx create mode 100644 code/seafire/resources/handle-update.hxx create mode 100644 code/seafire/resources/handle-update.txx create mode 100644 code/seafire/resources/handle.hxx create mode 100644 code/seafire/resources/handle.txx create mode 100644 code/seafire/resources/metadata.hxx create mode 100644 code/seafire/resources/negotiate.hxx create mode 100644 code/seafire/resources/negotiate.txx create mode 100644 code/seafire/resources/preconditions.hxx create mode 100644 code/seafire/resources/preconditions.txx create mode 100644 code/seafire/resources/resource-handler.hxx create mode 100644 code/seafire/resources/resource-handler.txx create mode 100644 code/seafire/resources/traits.hxx create mode 100644 code/seafire/resources/version.hxx.in create mode 100644 manifest create mode 100644 repositories.manifest create mode 100644 tests/.gitignore create mode 100644 tests/build/.gitignore create mode 100644 tests/build/bootstrap.build create mode 100644 tests/build/root.build create mode 100644 tests/buildfile diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ec9f3de --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.gitea/workflows/on-push.yaml b/.gitea/workflows/on-push.yaml new file mode 100644 index 0000000..23a9e11 --- /dev/null +++ b/.gitea/workflows/on-push.yaml @@ -0,0 +1,24 @@ +name: on-push +on: [push] + +jobs: + build-and-test: + runs-on: linux + container: code.helloryan.se/infra/buildenv/cxx-amd64-fedora-40:latest + volumes: + - /build + steps: + - name: Clone repository + uses: actions/checkout@v3 + - name: Authenticate + run: | + git config unset http.https://code.helloryan.se/.extraheader + echo "${{ secrets.NETRC }}" >> ~/.netrc + - name: Initialize + run: | + bpkg create -d /build cc config.cc.coptions="-Wall -Werror" + bdep init -A /build + - name: Build + run: b + - name: Test + run: b test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c96e1ec --- /dev/null +++ b/.gitignore @@ -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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dfc745b --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a0d5729 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# libcode-seafire-resources + +![Build status](https://code.helloryan.se/code/libcode-seafire-resources/actions/workflows/on-push.yaml/badge.svg) + +## Requirements + +None, other than a modern C++-compiler. + +## Building + +See the wiki, https://code.helloryan.se/code/wiki/wiki/Build-Instructions, for +build instructions. + +## Contact + +Please report bugs and issues by sending an e-mail to: ryan@helloryan.se. + +## Contributing + +Please send an e-mail to ryan@helloryan.se to request an account and +write-access to the libcode-seafire-resources repository. diff --git a/build/.gitignore b/build/.gitignore new file mode 100644 index 0000000..974e01d --- /dev/null +++ b/build/.gitignore @@ -0,0 +1,4 @@ +/config.build +/root/ +/bootstrap/ +build/ diff --git a/build/bootstrap.build b/build/bootstrap.build new file mode 100644 index 0000000..564e08c --- /dev/null +++ b/build/bootstrap.build @@ -0,0 +1,7 @@ +project = libcode-seafire-resources + +using version +using config +using test +using install +using dist diff --git a/build/export.build b/build/export.build new file mode 100644 index 0000000..74bfccb --- /dev/null +++ b/build/export.build @@ -0,0 +1,6 @@ +$out_root/ +{ + include code/seafire/resources/ +} + +export $out_root/code/seafire/resources/$import.target diff --git a/build/root.build b/build/root.build new file mode 100644 index 0000000..21e0a2e --- /dev/null +++ b/build/root.build @@ -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 diff --git a/buildfile b/buildfile new file mode 100644 index 0000000..bbe185e --- /dev/null +++ b/buildfile @@ -0,0 +1,5 @@ +./: {code/ tests/} doc{README.md} legal{LICENSE} manifest + +# Don't install tests. +# +tests/: install = false diff --git a/code/seafire/resources/.gitignore b/code/seafire/resources/.gitignore new file mode 100644 index 0000000..b1ed0e0 --- /dev/null +++ b/code/seafire/resources/.gitignore @@ -0,0 +1,9 @@ +# Generated version header. +# +version.hxx + +# Unit test executables and Testscript output directories +# (can be symlinks). +# +*.test +test-*.test diff --git a/code/seafire/resources/buildfile b/code/seafire/resources/buildfile new file mode 100644 index 0000000..3850674 --- /dev/null +++ b/code/seafire/resources/buildfile @@ -0,0 +1,67 @@ +intf_libs = # Interface dependencies. +impl_libs = # Implementation dependencies. + +import intf_libs =+ libcode-seafire-common%lib{code-seafire-common} +import intf_libs =+ libcode-seafire-protocol%lib{code-seafire-protocol} +import intf_libs =+ libcode-seafire-server%lib{code-seafire-server} +import intf_libs =+ libcode-seafire-representation%lib{code-seafire-representation} + +./: lib{code-seafire-resources}: libul{code-seafire-resources} + +libul{code-seafire-resources}: {hxx ixx txx cxx}{** -**.test... -version} \ + {hxx }{ version} + +libul{code-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{code-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{code-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{code-seafire-resources}: bin.lib.version = "-$version.project_id" +else + lib{code-seafire-resources}: bin.lib.version = "-$version.major.$version.minor" + +# Install into the code/seafire/resources/ subdirectory of, say, /usr/include/ +# recreating subdirectories. +# +{hxx ixx txx}{*}: +{ + install = include/code/seafire/resources/ + install.subdirs = true +} diff --git a/code/seafire/resources/concepts.hxx b/code/seafire/resources/concepts.hxx new file mode 100644 index 0000000..cc46cef --- /dev/null +++ b/code/seafire/resources/concepts.hxx @@ -0,0 +1,33 @@ +#ifndef code__seafire__resources__concepts_hxx_ +#define code__seafire__resources__concepts_hxx_ + +#include + +namespace code::seafire::resources +{ + + // resources. + // + + template + concept GettableResource = traits::is_gettable_resource_v; + + template + concept UpdatableResource = traits::is_updatable_resource_v; + + template + concept CreatableResource = traits::is_creatable_resource_v; + + template + concept ErasableResource = traits::is_erasable_resource_v; + + template + concept Resource + = traits::is_gettable_resource_v + || traits::is_updatable_resource_v + || traits::is_creatable_resource_v + || traits::is_erasable_resource_v; + +} // namespace code::seafire::resources + +#endif diff --git a/code/seafire/resources/handle-create.hxx b/code/seafire/resources/handle-create.hxx new file mode 100644 index 0000000..e1fa89f --- /dev/null +++ b/code/seafire/resources/handle-create.hxx @@ -0,0 +1,32 @@ +#ifndef code__seafire__resources__handle_create_hxx_ +#define code__seafire__resources__handle_create_hxx_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace code::seafire::resources +{ + + template + void + handle_create(server::request_t& req, + server::response_t& res, + R const& resource, + std::optional const& accepted_type); + +} // namespace code::seafire::resources + +#include + +#endif diff --git a/code/seafire/resources/handle-create.txx b/code/seafire/resources/handle-create.txx new file mode 100644 index 0000000..72d01ad --- /dev/null +++ b/code/seafire/resources/handle-create.txx @@ -0,0 +1,34 @@ +namespace code::seafire::resources +{ + + template + void + handle_create(server::request_t& req, + server::response_t& res, + R const& resource, + std::optional const& accepted_type) + { + using resource_traits = traits::resource_traits>; + using input_representation_type = typename resource_traits::create_input_representation_type; + + using t = traits::resource_traits; + + if constexpr (representation::traits::is_variant_input_representation_v) { + res.send(server::common_error_t::not_implemented); + } + else if constexpr (representation::Representation) { + auto content_type = get(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(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 code::seafire::resources diff --git a/code/seafire/resources/handle-get.hxx b/code/seafire/resources/handle-get.hxx new file mode 100644 index 0000000..4cb1583 --- /dev/null +++ b/code/seafire/resources/handle-get.hxx @@ -0,0 +1,42 @@ +#ifndef code__seafire__resources__handle_get_hxx_ +#define code__seafire__resources__handle_get_hxx_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace code::seafire::resources +{ + + enum class get_kind_t { + get, + head, + created_resource, + updated_resource + }; + + template + void + handle_get(server::request_t& req, + server::response_t& res, + R const& resource, + std::optional const& accepted_type, + get_kind_t kind); + +} // namespace code::seafire::resources + +#include + +#endif diff --git a/code/seafire/resources/handle-get.txx b/code/seafire/resources/handle-get.txx new file mode 100644 index 0000000..04f8957 --- /dev/null +++ b/code/seafire/resources/handle-get.txx @@ -0,0 +1,47 @@ +namespace code::seafire::resources +{ + + template + void + handle_get(server::request_t& req, + server::response_t& res, + R const& resource, + std::optional const& accepted_type, + get_kind_t kind) + { + auto rep = common::invoke(resource, req, &R::get); + + if (!rep) { + res.send(server::common_error_t::not_found); + return; + } + + 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(res, *opt_val); + + if (auto opt_val = get_last_modified(resource); opt_val) + set(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(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 code::seafire::resources diff --git a/code/seafire/resources/handle-update.hxx b/code/seafire/resources/handle-update.hxx new file mode 100644 index 0000000..c1dd278 --- /dev/null +++ b/code/seafire/resources/handle-update.hxx @@ -0,0 +1,34 @@ +#ifndef code__seafire__resources__handle_update_hxx_ +#define code__seafire__resources__handle_update_hxx_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace code::seafire::resources +{ + + template + void + handle_update(server::request_t& req, + server::response_t& res, + R const& resource, + std::optional const& accepted_type); + +} // namespace code::seafire::resources + +#include + +#endif diff --git a/code/seafire/resources/handle-update.txx b/code/seafire/resources/handle-update.txx new file mode 100644 index 0000000..f4b54dc --- /dev/null +++ b/code/seafire/resources/handle-update.txx @@ -0,0 +1,45 @@ +namespace code::seafire::resources +{ + + template + void + handle_update(server::request_t& req, + server::response_t& res, + R const& resource, + std::optional const& accepted_type) + { + using resource_traits = traits::resource_traits>; + using input_representation_type = typename resource_traits::update_input_representation_type; + + auto content_type = get(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; + + if constexpr (GettableResource) { + 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) { + 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 code::seafire::resources diff --git a/code/seafire/resources/handle.hxx b/code/seafire/resources/handle.hxx new file mode 100644 index 0000000..f236f52 --- /dev/null +++ b/code/seafire/resources/handle.hxx @@ -0,0 +1,29 @@ +#ifndef code__seafire__resources__handle_hxx_ +#define code__seafire__resources__handle_hxx_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace code::seafire::resources +{ + + void + handle(server::request_t& req, + server::response_t& res, + Resource auto const& r); + +} // namespace code::seafire::resources + +#include + +#endif diff --git a/code/seafire/resources/handle.txx b/code/seafire/resources/handle.txx new file mode 100644 index 0000000..806ba37 --- /dev/null +++ b/code/seafire/resources/handle.txx @@ -0,0 +1,129 @@ +namespace code::seafire::resources +{ + + void + handle(server::request_t& req, + server::response_t& res, + Resource auto const& r) + { + using resource_traits = traits::resource_traits>; + + auto const not_allowed = [&req, &res] + { + set(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( + req, + res, + [&req, &res, &r](std::optional 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 (representation::Representation) { + negotiate( + req, + res, + [&req, &res, &r](std::optional const& accepted_type) + { + handle_update(req, res, r, accepted_type); + } + ); + } + else { + negotiate::get_representation_type>( + req, + res, + [&req, &res, &r](std::optional 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 (representation::Representation) { + negotiate( + req, + res, + [&req, &res, &r](std::optional 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::get_representation_type; + //using resource_traits = traits::resource_traits< + negotiate( + req, + res, + [&req, &res, &r](std::optional const& accepted_type) + { + handle_create(req, res, r, accepted_type); + } + ); + } + } + else { + not_allowed(); + } + } + + // DELETE + // + else if (method == "DELETE") { + #if 0 + if constexpr (resource_traits::is_erasable) { + handle_erase(tx, r); + return; + } + else { + not_allowed(); + } + #endif + } + + // method not implemented. + // + else { + res.send(server::common_error_t::not_implemented); + } + } + +} // namespace code::seafire::resources diff --git a/code/seafire/resources/metadata.hxx b/code/seafire/resources/metadata.hxx new file mode 100644 index 0000000..2aa6e9c --- /dev/null +++ b/code/seafire/resources/metadata.hxx @@ -0,0 +1,104 @@ +#ifndef code__seafire__resources__metadata_hxx_ +#define code__seafire__resources__metadata_hxx_ + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +namespace code::seafire::resources +{ + + // location + // + template + std::optional + get_location(R const& res) + { + using local_traits = traits::resource_traits; + + if constexpr (local_traits::has_location) + return res.location(); + + return std::nullopt; + } + + + // etag + // + template + std::optional + get_etag(BR const& rep) + { + using local_traits = representation::traits::representation_traits
; + + if constexpr (local_traits::has_entity_tag_t) + return rep.etag(); + + return std::nullopt; + } + + inline + std::optional + get_etag(representation::representation_t const& rep) + { + return rep.etag(); + } + + template + std::optional + get_etag(R const& res) + { + using local_traits = traits::resource_traits; + + if constexpr (local_traits::has_entity_tag) + return res.etag(); + + return std::nullopt; + } + + // last-modified + // + template + std::optional + get_last_modified(BR const& rep) + { + using local_traits = representation::traits::representation_traits
; + + if constexpr (local_traits::has_last_modified) + return rep.last_modified(); + + return std::nullopt; + } + + inline + std::optional + get_last_modified(representation::representation_t const& rep) + { + return rep.last_modified(); + } + + template + std::optional + get_last_modified(R const& res) + { + using local_traits = traits::resource_traits; + + if constexpr (local_traits::has_last_modified) + return res.last_modified(); + + return std::nullopt; + } + +} // namespace code::seafire::resources + +#endif diff --git a/code/seafire/resources/negotiate.hxx b/code/seafire/resources/negotiate.hxx new file mode 100644 index 0000000..6ada8e2 --- /dev/null +++ b/code/seafire/resources/negotiate.hxx @@ -0,0 +1,28 @@ +#ifndef code__seafire__resources__negotiate_hxx_ +#define code__seafire__resources__negotiate_hxx_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace code::seafire::resources +{ + +// fixme: implement concept for handler. + + template + void + negotiate(server::request_t&, server::response_t&, Handler&&); + +} // namespace code::seafire::resources + +#include + +#endif diff --git a/code/seafire/resources/negotiate.txx b/code/seafire/resources/negotiate.txx new file mode 100644 index 0000000..9a82b91 --- /dev/null +++ b/code/seafire/resources/negotiate.txx @@ -0,0 +1,31 @@ +namespace code::seafire::resources +{ + + template + void + negotiate(server::request_t& req, server::response_t& res, Handler&& handler) + { + using representation_traits = representation::traits::representation_traits; + + if constexpr (representation_traits::is_content_negotiable) { + using protocol::rfc7231::accept_t; + + if (auto opt_accept = get(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 code::seafire::resources diff --git a/code/seafire/resources/preconditions.hxx b/code/seafire/resources/preconditions.hxx new file mode 100644 index 0000000..3681829 --- /dev/null +++ b/code/seafire/resources/preconditions.hxx @@ -0,0 +1,67 @@ +#ifndef code__seafire__resources__preconditions_hxx_ +#define code__seafire__resources__preconditions_hxx_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace code::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 code::seafire::resources + +#include + +#endif diff --git a/code/seafire/resources/preconditions.txx b/code/seafire/resources/preconditions.txx new file mode 100644 index 0000000..54934f9 --- /dev/null +++ b/code/seafire/resources/preconditions.txx @@ -0,0 +1,151 @@ +namespace code::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; + + if (auto if_match = get(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(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(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(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>; + + if constexpr (representation_traits::has_entity_tag) { + auto etag = [&r] + { + if constexpr (common::traits::is_optional_v) { + return r.etag(); + } + else { + return std::optional{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>; + + 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>; + + if constexpr (representation_traits::has_last_modified) { + auto last_modified = r.last_modified(); + + if (last_modified >= if_modified_since) + return true; + } + + return false; + } + +} // namespace code::seafire::resources diff --git a/code/seafire/resources/resource-handler.hxx b/code/seafire/resources/resource-handler.hxx new file mode 100644 index 0000000..8fce5ee --- /dev/null +++ b/code/seafire/resources/resource-handler.hxx @@ -0,0 +1,66 @@ +#ifndef code__seafire__resources__resource_handler_hxx_ +#define code__seafire__resources__resource_handler_hxx_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace code::seafire::resources +{ + + template + 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 factory_args_; + }; + + template> + H use_resource(F factory, FactoryArgs&&... args); + +} // namespace code::seafire::resources + +#include + +#endif diff --git a/code/seafire/resources/resource-handler.txx b/code/seafire/resources/resource-handler.txx new file mode 100644 index 0000000..205bd2e --- /dev/null +++ b/code/seafire/resources/resource-handler.txx @@ -0,0 +1,36 @@ +namespace code::seafire::resources +{ + + template + resource_handler_t:: + resource_handler_t(factory_type factory, FactoryArgs&&... args) + : factory_{std::move(factory)}, + factory_args_{std::forward(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::make, f, req, res, std::forward(args)...); + }; + + auto r = std::apply(factory_invoker, factory_args_); + handle(req, res, r); + } + + template + H + use_resource(F factory, FactoryArgs&&... args) + { + return H{std::move(factory), std::forward(args)...}; + } + +} // namespace code::seafire::resources diff --git a/code/seafire/resources/traits.hxx b/code/seafire/resources/traits.hxx new file mode 100644 index 0000000..8256a9c --- /dev/null +++ b/code/seafire/resources/traits.hxx @@ -0,0 +1,316 @@ +#ifndef code__seafire__resources__traits_hxx_ +#define code__seafire__resources__traits_hxx_ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace code::seafire::resources::traits +{ + + // ============================================================================= + // resources. + // + + // is_gettable_resource + // + + template> + struct is_gettable_resource : std::false_type {}; + + template + struct is_gettable_resource< + R, + std::void_t< + decltype(&R::get) + > + > : std::bool_constant< + std::is_same_v< + void, + typename common::traits::function_traits::return_type + > + || + representation::traits::is_optional_representation_v< + typename common::traits::function_traits::return_type + > + > { + }; + + template + inline constexpr bool is_gettable_resource_v{is_gettable_resource::value}; + + template> + struct get_traits { + using result_type = void; + }; + + template + struct get_traits { + using function_traits = common::traits::function_traits; + using result_type = common::traits::remove_optional_t; + }; + + // is_updatable_resource + // + + template> + struct is_updatable_resource : std::false_type {}; + + template + 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. + representation::traits::is_input_representation_v< + typename common::traits::first_arg::type + > + && + ( + // Check if return value of R::update() is void. + std::is_same_v< + void, + typename common::traits::function_traits::return_type + > + || + // Check if return value of R::update() is a gettable resource. + is_gettable_resource_v< + typename common::traits::function_traits::return_type + > + || + // Check if return value of R::update() is a representation. + representation::traits::is_representation_v< + typename common::traits::function_traits::return_type + > + ) + > { + }; + + template + inline constexpr bool is_updatable_resource_v{is_updatable_resource::value}; + + template> + struct update_traits { + using input_representation_type = void; + using result_type = void; + }; + + template + struct update_traits { + using function_traits = common::traits::function_traits; + using input_representation_type = common::traits::first_arg_t; + using result_type = typename function_traits::return_type; + }; + + // is_creatable_resource + // + + template> + struct is_creatable_resource : std::false_type {}; + + template + struct is_creatable_resource< + R, + std::void_t< + decltype(&R::create) + > + > : std::bool_constant< + representation::traits::is_input_representation_v< + common::traits::first_arg_t + > + && + ( + // Check if return value of R::create() is void. + std::is_same_v< + void, + typename common::traits::function_traits::return_type + > + || + // Check if return value of R::create() is a gettable resource. + is_gettable_resource_v< + typename common::traits::function_traits::return_type + > + || + // Check if return value of R::create() is a representation. + representation::traits::is_representation_v< + typename common::traits::function_traits::return_type + > + ) + > { + }; + + template + inline constexpr bool is_creatable_resource_v{is_creatable_resource::value}; + + template> + struct create_traits { + using input_representation_type = void; + using result_type = void; + }; + + template + struct create_traits { + using function_traits = common::traits::function_traits; + using input_representation_type = common::traits::first_arg_t; + using result_type = typename function_traits::return_type; + }; + + // is_erasable_resource + // + + template> + struct is_erasable_resource : std::false_type {}; + + template + struct is_erasable_resource< + R, + std::void_t< + decltype(std::declval().erase()) + > + > : std::true_type{ + }; + + template + constexpr bool is_erasable_resource_v{is_erasable_resource::value}; + + // is_resource + // + + template + struct is_resource : std::bool_constant< + is_gettable_resource_v || + is_updatable_resource_v || + is_creatable_resource_v || + is_erasable_resource_v + > { + }; + + template + constexpr bool is_resource_v{is_resource::value}; + + // has_location + // + + template> + struct has_location : std::false_type {}; + + template + struct has_location< + R, + std::void_t< + decltype( + uri::uri_t{ + std::declval().location() + } + ) + > + > : std::true_type{ + }; + + template + constexpr bool has_location_v{has_location::value}; + + // resource_traits + // + + template + struct resource_traits { + using resource_type = R; + + // properties + // + + static constexpr bool has_entity_tag{ + representation::traits::has_entity_tag_v + }; + + static constexpr bool has_last_modified{ + representation::traits::has_last_modified_v + }; + + static constexpr bool has_location{ + has_location_v + }; + + 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 + }; + + using get_traits = traits::get_traits; + 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 + }; + + using update_traits = traits::update_traits; + using update_input_representation_type = typename update_traits::input_representation_type; + using update_input_representation_type_traits = representation::traits::input_representation_traits; + using update_result_type = typename update_traits::result_type; + + // CREATE + // + static constexpr bool is_creatable{ + is_creatable_resource_v + }; + + using create_traits = traits::create_traits; + using create_input_representation_type = typename create_traits::input_representation_type; + using create_input_representation_type_traits = representation::traits::input_representation_traits; + using create_result_type = typename create_traits::result_type; + + // ERASE + // + static constexpr bool is_erasable{ + is_erasable_resource_v + }; + }; + +} // namespace code::seafire::resources::traits + +#endif diff --git a/code/seafire/resources/version.hxx.in b/code/seafire/resources/version.hxx.in new file mode 100644 index 0000000..a88e4a1 --- /dev/null +++ b/code/seafire/resources/version.hxx.in @@ -0,0 +1,37 @@ +#ifndef code__seafire__resources__version_hxx_ +#define code__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 LIBCODE_SEAFIRE_RESOURCES_VERSION $libcode_seafire_resources.version.project_number$ULL +#define LIBCODE_SEAFIRE_RESOURCES_VERSION_STR "$libcode_seafire_resources.version.project$" +#define LIBCODE_SEAFIRE_RESOURCES_VERSION_ID "$libcode_seafire_resources.version.project_id$" +#define LIBCODE_SEAFIRE_RESOURCES_VERSION_FULL "$libcode_seafire_resources.version$" + +#define LIBCODE_SEAFIRE_RESOURCES_VERSION_MAJOR $libcode_seafire_resources.version.major$ +#define LIBCODE_SEAFIRE_RESOURCES_VERSION_MINOR $libcode_seafire_resources.version.minor$ +#define LIBCODE_SEAFIRE_RESOURCES_VERSION_PATCH $libcode_seafire_resources.version.patch$ + +#define LIBCODE_SEAFIRE_RESOURCES_PRE_RELEASE $libcode_seafire_resources.version.pre_release$ + +#define LIBCODE_SEAFIRE_RESOURCES_SNAPSHOT_SN $libcode_seafire_resources.version.snapshot_sn$ULL +#define LIBCODE_SEAFIRE_RESOURCES_SNAPSHOT_ID "$libcode_seafire_resources.version.snapshot_id$" + +#endif diff --git a/manifest b/manifest new file mode 100644 index 0000000..fa68662 --- /dev/null +++ b/manifest @@ -0,0 +1,16 @@ +: 1 +name: libcode-seafire-resources +version: 0.1.0-a.0.z +language: c++ +summary: libcode-seafire-resources C++ library +license: BSD-4-Clause +description-file: README.md +url: https://helloryan.se/code/ +email: ryan@helloryan.se +depends: * build2 >= 0.17.0 +depends: * bpkg >= 0.17.0 +depends: libasio ^1.29.0 +depends: libcode-seafire-common ^0.1.0- +depends: libcode-seafire-protocol ^0.1.0- +depends: libcode-seafire-server ^0.1.0- +depends: libcode-seafire-representation ^0.1.0- diff --git a/repositories.manifest b/repositories.manifest new file mode 100644 index 0000000..4e671d3 --- /dev/null +++ b/repositories.manifest @@ -0,0 +1,23 @@ +: 1 +summary: libcode-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/code/libcode-seafire-common.git##HEAD + +: +role: prerequisite +location: https://code.helloryan.se/code/libcode-seafire-protocol.git##HEAD + +: +role: prerequisite +location: https://code.helloryan.se/code/libcode-seafire-server.git##HEAD + +: +role: prerequisite +location: https://code.helloryan.se/code/libcode-seafire-representation.git##HEAD diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..662178d --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,8 @@ +# Test executables. +# +driver + +# Testscript output directories (can be symlinks). +# +test +test-* diff --git a/tests/build/.gitignore b/tests/build/.gitignore new file mode 100644 index 0000000..974e01d --- /dev/null +++ b/tests/build/.gitignore @@ -0,0 +1,4 @@ +/config.build +/root/ +/bootstrap/ +build/ diff --git a/tests/build/bootstrap.build b/tests/build/bootstrap.build new file mode 100644 index 0000000..a07b5ea --- /dev/null +++ b/tests/build/bootstrap.build @@ -0,0 +1,5 @@ +project = # Unnamed tests subproject. + +using config +using test +using dist diff --git a/tests/build/root.build b/tests/build/root.build new file mode 100644 index 0000000..a67b2fe --- /dev/null +++ b/tests/build/root.build @@ -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 diff --git a/tests/buildfile b/tests/buildfile new file mode 100644 index 0000000..aeeab15 --- /dev/null +++ b/tests/buildfile @@ -0,0 +1 @@ +./: {*/ -build/}