Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
ObjectRequestHandler.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
9#include "Stroika/Foundation/IO/Network/HTTP/ClientErrorException.h"
10
11#include "Stroika/Frameworks/WebService/Server/Basic.h"
12
13namespace Stroika::Frameworks::WebService::Server::ObjectRequestHandler {
14
15 /*
16 ********************************************************************************
17 ************************ ObjectRequestHandler::Factory *************************
18 ********************************************************************************
19 */
20 template <typename RETURN_TYPE, typename... ARG_TYPES>
21 template <qCompilerAndStdLib_ConstraintDiffersInTemplateRedeclaration_BWA (invocable<ARG_TYPES...>) CALLBACK_FUNCTION>
22 inline Factory<RETURN_TYPE, ARG_TYPES...>::Factory (const Options& options, CALLBACK_FUNCTION&& highLevelHandler)
23 : fHighLevelHandler_{highLevelHandler}
24 , fOptions_{options}
25 {
26 }
27 template <typename RETURN_TYPE, typename... ARG_TYPES>
28 inline Factory<RETURN_TYPE, ARG_TYPES...>::operator Frameworks::WebServer::RequestHandler () const
29 {
30 using namespace Characters::Literals;
31 using namespace DataExchange;
33 return [*this] (Message& m, [[maybe_unused]] const Sequence<String>& matchedArgs) {
35 Stroika_Foundation_Debug_OptionalizeTraceArgs ("ObjectRequestHandler::Factory handler", "m->request = {}, RETURN_TYPE={}"_f,
36 m.request ().ToString (), type_index{typeid (RETURN_TYPE)})};
37 Request& req = m.rwRequest ();
38 Response& resp = m.rwResponse ();
39 Context context{.fMatchedURLArgs = matchedArgs, .fRequest = req, .fResponse = resp};
40 if constexpr (same_as<RETURN_TYPE, void>) {
41 ApplyHandler (context);
42 SendResponse (req, resp);
43 }
44 else {
45 SendResponse (req, resp, ApplyHandler (context));
46 }
47 };
48 }
49 template <typename RETURN_TYPE, typename... ARG_TYPES>
50 template <typename RET>
51 inline tuple<> Factory<RETURN_TYPE, ARG_TYPES...>::mkArgsTuple_ ([[maybe_unused]] const Context& context,
52 [[maybe_unused]] const Iterable<VariantValue>& variantValueArgs,
53 [[maybe_unused]] const function<RET ()>& f) const
54 {
55 return make_tuple ();
56 }
57 template <typename RETURN_TYPE, typename... ARG_TYPES>
58 template <typename SINGLE_ARG>
59 tuple<SINGLE_ARG> Factory<RETURN_TYPE, ARG_TYPES...>::mkArgsTuple_ (const Context& context,
60 [[maybe_unused]] const Iterable<VariantValue>& variantValueArgs,
61 [[maybe_unused]] const function<RETURN_TYPE (SINGLE_ARG)>& f) const
62 {
63 if constexpr (same_as<remove_cvref_t<SINGLE_ARG>, Context>) {
64 return make_tuple (context);
65 }
66 else {
67 Require (variantValueArgs.size () >= 1);
68 return make_tuple (ConvertArg2Object<SINGLE_ARG> (variantValueArgs.Nth (0)));
69 }
70 }
71 template <typename RETURN_TYPE, typename... ARG_TYPES>
72 template <typename ARG_FIRST, typename... REST_ARG_TYPES>
73 auto Factory<RETURN_TYPE, ARG_TYPES...>::mkArgsTuple_ (const Context& context, const Iterable<VariantValue>& variantValueArgs,
74 [[maybe_unused]] const function<RETURN_TYPE (ARG_FIRST, REST_ARG_TYPES...)>& f) const
75 -> decltype (tuple_cat (make_tuple (declval<remove_cvref_t<ARG_FIRST>> ()), make_tuple (declval<REST_ARG_TYPES...> ())))
76 {
77 [[maybe_unused]] constexpr size_t kTotalArgsRemaining_ = sizeof...(REST_ARG_TYPES) + 1; // +1 cuz still processing ARG_FIRST here
78 Require (variantValueArgs.size () >= kTotalArgsRemaining_ - 1); // -1 cuz one may be context
79 if constexpr (same_as<remove_cvref_t<ARG_FIRST>, Context>) {
80 return tuple_cat (mkArgsTuple_ (context, Iterable<VariantValue>{}, function<RETURN_TYPE (ARG_FIRST)>{}),
81 mkArgsTuple_ (context, variantValueArgs, function<RETURN_TYPE (REST_ARG_TYPES...)>{}));
82 }
83 else {
84 Require (variantValueArgs.size () >= 1); // must consume one here (@todo consider if we should require or silently trim/ignore missing data) --LGP 2025-02-25
85 return tuple_cat (mkArgsTuple_ (context, variantValueArgs.Take (1), function<RETURN_TYPE (ARG_FIRST)>{}),
86 mkArgsTuple_ (context, variantValueArgs.Skip (1), function<RETURN_TYPE (REST_ARG_TYPES...)>{}));
87 }
88 }
89 template <typename RETURN_TYPE, typename... ARG_TYPES>
91 {
93 using Server::VariantValue::PickOutNamedArguments;
94 if (fOptions_.fAllowedMethods) {
95 ExpectedMethod (context.fRequest, *fOptions_.fAllowedMethods);
96 }
97 Iterable<VariantValue> variantValueArgs = [&] () {
98 VariantValue argVV = fOptions_.fExtractVariantValueFromRequest (context.fRequest);
99 if (fOptions_.fTreatBodyAsListOfArguments) {
100 Iterable<VariantValue> variantValueArgs = PickOutNamedArguments (*fOptions_.fTreatBodyAsListOfArguments, argVV);
101 Require (variantValueArgs.size () >= sizeof...(ARG_TYPES) - 1); // assuming one is context, else >= sizeof(argstype)
102 return variantValueArgs;
103 }
104 else {
105 Iterable<VariantValue> variantValueArgs{argVV};
106 Require (variantValueArgs.size () == 1);
107 return variantValueArgs;
108 }
109 }();
110
111 // exceptions parsing args mean ill-formatted arguments to the webservice, so treat as client errors
112 auto&& args = ClientErrorException::TreatExceptionsAsClientError (
113 [&, this] () { return mkArgsTuple_ (context, variantValueArgs, fHighLevelHandler_); });
114 if constexpr (same_as<RETURN_TYPE, void>) {
115 apply (fHighLevelHandler_, args);
116 }
117 else {
118 return apply (fHighLevelHandler_, args);
119 }
120 }
121 template <typename RETURN_TYPE, typename... ARG_TYPES>
122 inline RETURN_TYPE Factory<RETURN_TYPE, ARG_TYPES...>::ApplyObjectHandler (ARG_TYPES... args) const
123 {
124 if constexpr (same_as<RETURN_TYPE, void>) {
125 fHighLevelHandler_ (args...);
126 }
127 else {
128 return fHighLevelHandler_ (args...);
129 }
130 }
131 template <typename RETURN_TYPE, typename... ARG_TYPES>
132 template <typename T>
133 inline T Factory<RETURN_TYPE, ARG_TYPES...>::ConvertArg2Object (const VariantValue& v) const
134 {
135 return fOptions_.fObjectMapper.ToObject<T> (v);
136 }
137 template <typename RETURN_TYPE, typename... ARG_TYPES>
138 template <same_as<RETURN_TYPE> RT>
139 inline void Factory<RETURN_TYPE, ARG_TYPES...>::SendResponse ([[maybe_unused]] const Request& request, Response& response, const RT& r) const
140 {
141 using namespace DataExchange;
142 // note maybe_unused on request wrong but tmphack to quiet til we check accept headers
143 if (not response.contentType ().has_value ()) {
144 // @todo check accept headers for the default...
145 response.contentType = fOptions_.fDefaultResultMediaType.value_or (InternetMediaTypes::kJSON);
146 }
147 auto ct = Memory::ValueOf (response.contentType ());
148
149 // @todo check accepts content type - and convert result (to JSON or binary json, xml etc)
150 VariantValue vv2Write = fOptions_.fObjectMapper.FromObject (r);
151 if (InternetMediaTypeRegistry::sThe->IsA (InternetMediaTypes::kJSON, ct)) {
152 using Variant::JSON::Writer;
153 response.write (Writer{fOptions_.fJSONWriterOptions.value_or (Writer::Options{})}.WriteAsString (vv2Write));
154 }
155 else if (InternetMediaTypeRegistry::sThe->IsA (InternetMediaTypes::kText_PLAIN, ct)) {
156 response.write (vv2Write.As<String> ()); // may throw if cannot convert to String, like accept: text/plain on content that was a map - should throw!
157 }
158 else {
159 RequireNotReached (); // that type not yet supported... - @todo binary json, xml, etc...
160 }
161 }
162 template <typename RETURN_TYPE, typename... ARG_TYPES>
163 inline void Factory<RETURN_TYPE, ARG_TYPES...>::SendResponse ([[maybe_unused]] const Request& request, [[maybe_unused]] Response& response) const
164 requires (same_as<RETURN_TYPE, void>)
165 {
166 // @todo - not sure anything todo here???
167 }
168 template <typename RETURN_TYPE, typename... ARG_TYPES>
169 template <same_as<RETURN_TYPE> RT>
170 inline void Factory<RETURN_TYPE, ARG_TYPES...>::SendStringResponse ([[maybe_unused]] const Request& request, Response& response, const RT& r) const
171 {
172 // @todo maybe respect accept headers - to a degree?
173 using namespace DataExchange;
174 response.contentType = fOptions_.fDefaultResultMediaType.value_or (InternetMediaTypes::kText_PLAIN);
175 if constexpr (Characters::IConvertibleToString<RT>) {
176 response.write (r);
177 }
178 else {
179 response.write (r.template As<String> ()); // e.g. for Common::GUID, or URL, etc...
180 }
181 }
182
183}
#define RequireNotReached()
Definition Assertions.h:385
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:270
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Definition Sequence.h:187
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
ClientErrorException is to capture exceptions caused by a bad (e.g ill-formed) request.
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
nonvirtual size_t size() const
Returns the number of items contained.
Definition Iterable.inl:300
this represents a HTTP request object for the WebServer module
ObjectRequestHandler::Factory is a way to construct a WebServer::RequestHandler from an ObjectVariant...
nonvirtual void SendStringResponse(const Request &request, Response &response, const RT &r) const
nonvirtual void SendResponse(const Request &request, Response &response, const RT &r) const
Factory(const Options &options, CALLBACK_FUNCTION &&highLevelHandler)
Build Frameworks::WebServer::RequestHandler out of ObjectVariantMapper, a few options/clues,...
nonvirtual RETURN_TYPE ApplyHandler(const Context &context) const
not directly instantiated, but to receive context arguments in callbacks.
Options for ObjectRequestHandler - mostly the ObjectVariantMapper, but also a few others depending on...