Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
ObjectRequestHandler.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Framework_WebService_Server_ObjectRequestHandler_h_
5#define _Stroika_Framework_WebService_Server_ObjectRequestHandler_h_ 1
6
7#include "Stroika/Frameworks/StroikaPreComp.h"
8
9#include <tuple>
10
11#include "Stroika/Foundation/Common/Concepts.h"
12#include "Stroika/Foundation/Containers/Sequence.h"
14#include "Stroika/Foundation/DataExchange/ObjectVariantMapper.h"
16
17#include "Stroika/Frameworks/WebServer/RequestHandler.h"
18
19#include "Stroika/Frameworks/WebService/Server/Basic.h"
20#include "Stroika/Frameworks/WebService/Server/VariantValue.h"
21
22/*
23 * \note Code-Status: <a href="Code-Status.md#Alpha">Alpha</a>
24 */
25
26namespace Stroika::Frameworks::WebService::Server::ObjectRequestHandler {
27
28 using namespace Stroika::Foundation;
29
31 using Common::Case;
33 using Common::Select_t;
38 using IO::Network::URI;
39 using Memory::BLOB;
41
42 using WebServer::Request;
43 using WebServer::Response;
44
45 /**
46 * \brief not directly instantiated, but to receive context arguments in callbacks.
47 *
48 * \note data (like request) etc only valid until end of call - don't copy/save
49 */
50 struct Context {
51 /**
52 * \note - the size of the fMatchedURLArgs is a function of the regexp matched in the Route
53 * rule, and has nothing todo with the data in the request (the value depends on the
54 * data but not the size). So you can index fMatchedURLArgs[2] - and have it checked
55 * with assertions safely.
56 */
58 Request& fRequest;
59 Response& fResponse;
60
61 /**
62 * \note since Context is not copyable, you must explicitly call .ToString() on it to use it with _f strings (std::format).
63 */
64 nonvirtual String ToString () const;
65 };
66 static_assert (not copyable<Context>);
67 static_assert (not movable<Context>);
68
69 /**
70 * \brief Options for ObjectRequestHandler - mostly the ObjectVariantMapper, but also a few others depending on situation
71 */
72 struct Options {
73
74 /**
75 */
76 ObjectVariantMapper fObjectMapper;
77
78 /**
79 * if set specified, any web-method not in the set will be rejected.
80 * \note These are compared case-sensitive, and are typically upper case.
81 */
82 optional<Set<String>> fAllowedMethods; // e.g. GET
83
84 /**
85 * This is the default media type for the content type of the result message. If missing, it will be inferred based on data type produced.
86 * regardless, it maybe overriden based on (eventually) http accept headers.
87 */
88 optional<InternetMediaType> fDefaultResultMediaType;
89
90 /**
91 */
93
94 /**
95 * Sometimes you will want to treat the body as the sole input object for a webservice call. Sometimes
96 * best to treat it as an array of parameters. If treated as an array of parameters (possible from mix of sources with fExtra... above)
97 * then need their names and ordering to map to the arguments to the callback function.
98 *
99 * \see PickOutNamedArguments
100 */
101 optional<Iterable<String>> fTreatBodyAsListOfArguments;
102
103 /**
104 */
105 optional<DataExchange::Variant::JSON::Writer::Options> fJSONWriterOptions;
106
107 /**
108 */
109 nonvirtual String ToString () const;
110 };
111 static_assert (copyable<Options>);
112
113 /**
114 * \brief ObjectRequestHandler::Factory is a way to construct a WebServer::RequestHandler from an ObjectVariantMapper object and a lambda taking in/out params of objects.
115 *
116 *
117 * A WebServer::RequestHandler gets handed a Message& (Request& + Response&). And its expected to read from the request and
118 * write to the response.
119 *
120 * ObjectRequestHandler::Factory, takes a lambda, with some in object parameters, and producing an (optional) output object
121 * and creates a WebServer::RequestHandler that handles the middle-layer, translating the Requst to object paramters, calls
122 * the argument lambda function, and then writes the result (translated back) to the webserver Response&.
123 *
124 * \par Example Usage
125 * \code
126 * Route{"api/objs/?"_RegEx,
127 * ObjectRequestHandler::Factory{
128 * {kMapper},
129 * [] () -> Sequence<GUID> {
130 * return Sequence<GUID>{};
131 * }}}
132 * \endcode
133 *
134 * \par Example Usage (access request context with extra fields, but just declaring it as a parameter)
135 * \code
136 * Route{"api/(v1/)?recordings/(.+)"_RegEx,
137 * ObjectRequestHandler::Factory{{kMapper}, [this] (const ObjectRequestHandler::Context& c) -> Recording {
138 * String id = c.fMatchedURLArgs[1];
139 * return fWSImpl_->recordings_GET (id);
140 * }}}
141 * \endcode
142 *
143 * \par Example Usage (from WebService sample)
144 * \code
145 * Route{HTTP::MethodsRegEx::kPost, "plus"_RegEx,
146 * ObjectRequestHandler::Factory{kBinaryOpObjRequestOptions_,
147 * [this] (Number arg1, Number arg2) { return fWSImpl_->plus (arg1, arg2); }}},
148 * \endcode
149 *
150 * \todo check acceptsContentType and return result as JSON, binary json, or xml (etc) accordingly - take OPTIONS param saying default
151 */
152 template <typename RETURN_TYPE, typename... ARG_TYPES>
153 class Factory {
154 public:
155 static_assert (not is_reference_v<RETURN_TYPE>);
156 //static_assert (conjunction<((not is_reference_v<ARG_TYPES>) ...)>, ""); // todo something close to this
157
158 public:
159 /**
160 * \brief Build Frameworks::WebServer::RequestHandler out of ObjectVariantMapper, a few options/clues, and a object-based Route callback function
161 */
162 template <qCompilerAndStdLib_ConstraintDiffersInTemplateRedeclaration_BWA (invocable<ARG_TYPES...>) CALLBACK_FUNCTION>
163 Factory (const Options& options, CALLBACK_FUNCTION&& highLevelHandler);
164
165 public:
166 /**
167 * This is the whole point of this class - to produce a RequestHandler that can be used in a Stroika WebServer Route.
168 */
169 nonvirtual operator Frameworks::WebServer::RequestHandler () const;
170
171 public:
172 /**
173 * This is 1/2 the guts of the RequestHandler - taking the request calling the handler with it, and producing
174 * the 'RESULT_TYPE' object.
175 *
176 * Note this is broken out as a callable method so it can be used from a straight custom WebServer::RequestHandler
177 * and just parts of the functionality used.
178 *
179 * \par Example Usage
180 * \code
181 * , Route{IO::Network::HTTP::MethodsRegEx::kPost, "api/(v1/)?recordings/?"_RegEx,
182 * [this] (Message& m) {
183 * // use ObjectRequestHandler::Factory indirectly so can support POST raw data and arguments as query-args!
184 * ObjectRequestHandler::Factory f{{kMapper}, [this] (const Recording& r) { return fWSImpl_->recordings_POST (r); }};
185 * Recording arg = [&] () {
186 * InternetMediaType requestCt =
187 * Memory::ValueOfOrThrow (m.request ().contentType (), ClientErrorException{"missing request content type"sv});
188 * auto ctChecker = InternetMediaTypeRegistry::sThe.load ();
189 * if (ctChecker.IsA (InternetMediaTypes::kJSON, requestCt)) {
190 * return Recording{kMapper.ToObject<Recording> (m.rwRequest ().GetBodyVariantValue ())};
191 * }
192 * else if (ctChecker.IsA (InternetMediaTypes::kAudio, requestCt)) {
193 * auto r = Recording{.fData = make_tuple (requestCt, m.rwRequest ().GetBody ())};
194 * // also can grab some parameters, like user, etc from query args - @todo
195 * return r;
196 * }
197 * else {
198 * Throw (ClientErrorException{"unsupported request content type"sv});
199 * }
200 * }();
201 * auto rr = f.ApplyHandler (arg);
202 * f.SendResponse (m.request (), m.rwResponse (), rr);
203 * }},
204 * \endcode
205 */
206 nonvirtual RETURN_TYPE ApplyHandler (const Context& context) const;
207
208 public:
209 /**
210 */
211 nonvirtual RETURN_TYPE ApplyObjectHandler (ARG_TYPES... args) const;
212
213 public:
214 /**
215 */
216 template <typename T>
217 nonvirtual T ConvertArg2Object (const VariantValue& v) const;
218
219 private:
220 // use tuple_cat to put all the args together (but in a tuple) and then apply on the function to expand the args to call f
221 // \pre nArgs in variantValueArgs >= count(ARGS_TYPE...) - @todo consider if this should be requirement - depends on behavior of extract code...--LGP 2025-02-25
222 template <typename RET = RETURN_TYPE>
223 nonvirtual tuple<> mkArgsTuple_ (const Context& context, const Iterable<VariantValue>& variantValueArgs,
224 [[maybe_unused]] const function<RET ()>& f) const;
225 template <typename SINGLE_ARG>
226 nonvirtual tuple<SINGLE_ARG> mkArgsTuple_ (const Context& context, const Iterable<VariantValue>& variantValueArgs,
227 [[maybe_unused]] const function<RETURN_TYPE (SINGLE_ARG)>& f) const;
228 template <typename ARG_FIRST, typename... REST_ARG_TYPES>
229 nonvirtual auto mkArgsTuple_ (const Context& context, const Iterable<VariantValue>& variantValueArgs,
230 [[maybe_unused]] const function<RETURN_TYPE (ARG_FIRST, REST_ARG_TYPES...)>& f) const
231 -> decltype (tuple_cat (make_tuple (declval<remove_cvref_t<ARG_FIRST>> ()), make_tuple (declval<REST_ARG_TYPES...> ())));
232
233 public:
234 /**
235 * Given the packaged up response 'r' - send it as a result, in the appropriate format (based on request accept headers etc)
236 *
237 * \see also SendStringResponse, if the response should be sent as a string
238 */
239 template <same_as<RETURN_TYPE> RT>
240 nonvirtual void SendResponse (const Request& request, Response& response, const RT& r) const;
241 nonvirtual void SendResponse (const Request& request, Response& response) const
242 requires (same_as<RETURN_TYPE, void>);
243
244 public:
245 /**
246 * Send the given response, but assure content type is 'text' or textish, and don't encode 'r' as JSON.
247 * This is primarily useful for a POST method, where you want to return the ID, not "ID".
248 */
249 template <same_as<RETURN_TYPE> RT>
250 nonvirtual void SendStringResponse (const Request& request, Response& response, const RT& r) const;
251
252 private:
253 function<RETURN_TYPE (ARG_TYPES...)> fHighLevelHandler_;
254 Options fOptions_;
255 };
256
257 namespace Private_ {
258 // just to shorten stuff below...
259 template <typename CALLBACK_FUNCTION, size_t i>
260 using CBArg_t_ = remove_cvref_t<typename FunctionTraits<CALLBACK_FUNCTION>::template ArgOrVoid_t<i>>;
261 }
262
263 // hopefully adequate approach for now, but there must be some way to generalize this - perhaps with folds?
264 // --LGP 2024-11-10
265 template <typename CALLBACK_FUNCTION>
266 Factory (const Options&, CALLBACK_FUNCTION&&) -> Factory<invoke_result_t<CALLBACK_FUNCTION>>;
267 template <typename CALLBACK_FUNCTION>
268 Factory (const Options&, CALLBACK_FUNCTION&&)
269 -> Factory<invoke_result_t<CALLBACK_FUNCTION, Private_::CBArg_t_<CALLBACK_FUNCTION, 0>>, Private_::CBArg_t_<CALLBACK_FUNCTION, 0>>;
270 template <typename CALLBACK_FUNCTION>
271 Factory (const Options&, CALLBACK_FUNCTION&&)
272 -> Factory<invoke_result_t<CALLBACK_FUNCTION, Private_::CBArg_t_<CALLBACK_FUNCTION, 0>, Private_::CBArg_t_<CALLBACK_FUNCTION, 1>>,
273 Private_::CBArg_t_<CALLBACK_FUNCTION, 0>, Private_::CBArg_t_<CALLBACK_FUNCTION, 1>>;
274 template <typename CALLBACK_FUNCTION>
275 Factory (const Options&, CALLBACK_FUNCTION&&)
276 -> Factory<invoke_result_t<CALLBACK_FUNCTION, Private_::CBArg_t_<CALLBACK_FUNCTION, 0>, Private_::CBArg_t_<CALLBACK_FUNCTION, 1>, Private_::CBArg_t_<CALLBACK_FUNCTION, 2>>,
277 Private_::CBArg_t_<CALLBACK_FUNCTION, 0>, Private_::CBArg_t_<CALLBACK_FUNCTION, 1>, Private_::CBArg_t_<CALLBACK_FUNCTION, 2>>;
278 template <typename CALLBACK_FUNCTION>
279 Factory (const Options&, CALLBACK_FUNCTION&&)
280 -> Factory<invoke_result_t<CALLBACK_FUNCTION, Private_::CBArg_t_<CALLBACK_FUNCTION, 0>, Private_::CBArg_t_<CALLBACK_FUNCTION, 1>,
281 Private_::CBArg_t_<CALLBACK_FUNCTION, 2>, Private_::CBArg_t_<CALLBACK_FUNCTION, 3>>,
282 Private_::CBArg_t_<CALLBACK_FUNCTION, 0>, Private_::CBArg_t_<CALLBACK_FUNCTION, 1>,
283 Private_::CBArg_t_<CALLBACK_FUNCTION, 2>, Private_::CBArg_t_<CALLBACK_FUNCTION, 3>>;
284
285}
286
287/*
288 ********************************************************************************
289 ***************************** Implementation Details ***************************
290 ********************************************************************************
291 */
292#include "ObjectRequestHandler.inl"
293
294#endif /*_Stroika_Framework_WebService_Server_ObjectRequestHandler_h_*/
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Definition Sequence.h:187
ObjectVariantMapper can be used to map C++ types to and from variant-union types, which can be transp...
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
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
nonvirtual RETURN_TYPE ApplyHandler(const Context &context) const
Extract the number of arguments, return type, and each individual argument type from a lambda or simp...
Definition Concepts.h:95
ConnectionManager::Options specify things like default headers, caching policies, binding flags (not ...
not directly instantiated, but to receive context arguments in callbacks.
Options for ObjectRequestHandler - mostly the ObjectVariantMapper, but also a few others depending on...