1 /* Example of how to marshall Outcomes at namespace boundaries
2 (C) 2017-2019 Niall Douglas <http://www.nedproductions.biz/> (11 commits)
5 Boost Software License - Version 1.0 - August 17th, 2003
7 Permission is hereby granted, free of charge, to any person or organization
8 obtaining a copy of the software and accompanying documentation covered by
9 this license (the "Software") to use, reproduce, display, distribute,
10 execute, and transmit the Software, and to prepare derivative works of the
11 Software, and to permit third-parties to whom the Software is furnished to
12 do so, all subject to the following:
14 The copyright notices in the Software and this entire statement, including
15 the above license grant, this restriction and the following disclaimer,
16 must be included in all copies of the Software, in whole or in part, and
17 all derivative works of the Software, unless such copies or derivative
18 works are solely in the form of machine-executable object code generated by
19 a source language processor.
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
24 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
25 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
26 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 DEALINGS IN THE SOFTWARE.
30 #include "../../../include/boost/outcome.hpp"
31 #if __has_include("quickcpplib/string_view.hpp")
32 #include "quickcpplib/string_view.hpp"
34 #include "../../../include/boost/outcome/quickcpplib/include/quickcpplib/string_view.hpp"
36 #include <cstring> // for memcpy
37 #include <experimental/filesystem>
40 // This is some standalone library implementing high level HTTP
43 // These are the error code that this HTTP library can return
44 enum class status_code
46 success = 0, // not the HTTP success code of 200
48 // A subset of all HTTP status codes for brevity
56 // This is the error type that this HTTP library can return
59 status_code status{status_code::success};
60 std::string url{}; // The failing URL
62 // Localise a result implementation to this library, holding
63 // the logic error of incorrect observation to mean program
67 BOOST_OUTCOME_V2_NAMESPACE::result<T, failure, BOOST_OUTCOME_V2_NAMESPACE::policy::terminate>;
69 /* Performs a HTTP GET on the url, returning the body if successful,
70 a failure with status_code if unsuccessful at the HTTP level, or a
71 C++ exception throw if something catastrophic happened e.g. bad_alloc
73 result<std::string> get(std::string url);
74 } // namespace httplib
79 result<std::string> get(std::string url)
85 return failure{status_code::not_found, url};
88 } // namespace httplib
92 using QUICKCPPLIB_NAMESPACE::string_view::string_view;
93 using std::experimental::filesystem::filesystem_error;
94 using std::experimental::filesystem::path;
95 } // namespace filelib
99 using QUICKCPPLIB_NAMESPACE::string_view::string_view;
103 // You may remember this from the tutorial section on Custom Payloads
106 // Error code + paths related to a failure. Also causes ADL discovery
107 // to check this namespace.
111 path path1{}, path2{};
114 // Tell Outcome that failure_info is to be treated as a std::error_code
115 inline const std::error_code &make_error_code(const failure_info &fi) { return fi.ec; }
117 // Tell Outcome that no-value observation should throw a custom exception
118 inline void outcome_throw_as_system_error_with_payload(failure_info fi)
120 // If the error code is not filesystem related e.g. ENOMEM, throw that
121 // as a standard STL exception.
122 BOOST_OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(fi.ec);
123 // Throw the exact same filesystem_error exception which the throwing
124 // copy_file() edition does.
125 throw filesystem_error(fi.ec.message(), std::move(fi.path1), std::move(fi.path2), fi.ec);
128 // Localise a result implementation specific to this namespace.
129 template <class T> using result = BOOST_OUTCOME_V2_NAMESPACE::result<T, failure_info>;
131 // Writes a chunk of data to some file. Returns bytes written, or
132 // failure_info. Never throws exceptions.
133 result<size_t> write_file(string_view chunk) noexcept;
134 } // namespace filelib
139 result<size_t> write_file(string_view chunk) noexcept
142 return failure_info{make_error_code(std::errc::no_space_on_device), "somepath"};
144 } // namespace filelib
147 // There actually is a library for tidying HTML into XHTML called HTMLTidy
148 // See http://www.html-tidy.org/
149 // HTMLTidy is actually a great tool for dealing with 1990s-era tag soup
150 // HTML, I highly recommend it.
152 // This isn't the API for Tidy, but let's assume it's a C library returning
153 // errno domained error codes. out must be freed with free() after use.
154 extern "C" int tidy_html(char **out, size_t *outlen, const char *in, size_t inlen);
157 extern "C" int tidy_html(char **out, size_t *outlen, const char *in, size_t inlen)
160 *out = (char *) malloc(inlen + 1);
161 memcpy(*out, in, inlen + 1);
171 // This is the namespace of the application which is connecting together the httplib,
172 // filelib and tidylib libraries into a solution.
175 // Create an ADL bridge so copy/move hooks will be searched for in this namespace
176 struct error_code : public std::error_code
179 using std::error_code::error_code;
180 error_code() = default;
181 error_code(std::error_code ec)
182 : std::error_code(ec)
186 // Localise an outcome implementation for this namespace
189 BOOST_OUTCOME_V2_NAMESPACE::outcome<T, error_code /*, std::exception_ptr */>;
190 using BOOST_OUTCOME_V2_NAMESPACE::success;
194 //! [app_map_httplib1]
197 // Specialise an exception type for httplib errors
198 struct httplib_error : std::runtime_error
201 using std::runtime_error::runtime_error;
202 httplib_error(httplib::failure _failure, std::string msg)
203 : std::runtime_error(std::move(msg))
204 , failure(std::move(_failure))
208 // the original failure
209 httplib::failure failure;
212 // Type erase httplib::result<U> into a httplib_error exception ptr
213 template <class U> //
214 inline std::exception_ptr make_httplib_exception(const httplib::result<U> &src)
216 std::string str("httplib failed with error ");
217 switch(src.error().status)
219 case httplib::status_code::success:
220 str.append("success");
222 case httplib::status_code::bad_request:
223 str.append("bad request");
225 case httplib::status_code::access_denied:
226 str.append("access denied");
228 case httplib::status_code::logon_failed:
229 str.append("logon failed");
231 case httplib::status_code::forbidden:
232 str.append("forbidden");
234 case httplib::status_code::not_found:
235 str.append("not found");
237 case httplib::status_code::internal_error:
238 str.append("internal error");
241 str.append(" [url was ");
242 str.append(src.error().url);
244 return std::make_exception_ptr(httplib_error(src.error(), std::move(str)));
247 //! [app_map_httplib1]
249 //! [app_map_httplib2]
250 // Inject custom ValueOrError conversion
251 BOOST_OUTCOME_V2_NAMESPACE_BEGIN
254 // Provide custom ValueOrError conversion from
255 // httplib::result<U> into any app::outcome<T>
256 template <class T, class U> //
257 struct value_or_error<app::outcome<T>, httplib::result<U>>
259 // False to indicate that this converter wants `result`/`outcome`
260 // to NOT reject all other `result`
261 static constexpr bool enable_result_inputs = true;
262 // False to indicate that this converter wants `outcome` to NOT
263 // reject all other `outcome`
264 static constexpr bool enable_outcome_inputs = true;
266 template <class X, //
267 typename = std::enable_if_t<std::is_same<httplib::result<U>, std::decay_t<X>>::value //
268 && std::is_constructible<T, U>::value>> //
269 constexpr app::outcome<T> operator()(X &&src)
271 // Forward any successful value, else synthesise an exception ptr
272 return src.has_value() ? //
273 app::outcome<T>{std::forward<X>(src).value()} //
275 app::outcome<T>{app::make_httplib_exception(std::forward<X>(src))};
278 } // namespace convert
279 BOOST_OUTCOME_V2_NAMESPACE_END
280 //! [app_map_httplib2]
284 static outcome<int> test_value_or_error2 = BOOST_OUTCOME_V2_NAMESPACE::convert::value_or_error<outcome<int>, httplib::result<int>>{}(httplib::result<int>{5});
285 static outcome<int> test_value_or_error3(httplib::result<int>{5});
288 //! [app_map_filelib]
289 // Inject custom ValueOrError conversion
290 BOOST_OUTCOME_V2_NAMESPACE_BEGIN
293 // Provide custom ValueOrError conversion from filelib::result<U>
294 // into any app::outcome<T>
295 template <class T, class U> //
296 struct value_or_error<app::outcome<T>, filelib::result<U>>
298 // True to indicate that this converter wants `result`/`outcome`
299 // to NOT reject all other `result`
300 static constexpr bool enable_result_inputs = true;
301 // False to indicate that this converter wants `outcome` to NOT
302 // reject all other `outcome`
303 static constexpr bool enable_outcome_inputs = true;
305 template <class X, //
306 typename = std::enable_if_t<std::is_same<filelib::result<U>, std::decay_t<X>>::value //
307 && std::is_constructible<T, U>::value>> //
308 constexpr app::outcome<T> operator()(X &&src)
310 // Forward any successful value
313 return {std::forward<X>(src).value()};
316 // Synthesise a filesystem_error, exactly as if someone had
317 // called src.value()
318 auto &fi = src.error();
319 BOOST_OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(fi.ec); // might throw
320 return {std::make_exception_ptr( //
321 filelib::filesystem_error(fi.ec.message(), std::move(fi.path1), std::move(fi.path2), fi.ec))};
324 } // namespace convert
325 BOOST_OUTCOME_V2_NAMESPACE_END
326 //! [app_map_filelib]
328 //! [app_map_tidylib]
331 // Specialise an exception type for tidylib errors
332 struct tidylib_error : std::system_error
335 using std::system_error::system_error;
336 tidylib_error() = default;
337 explicit tidylib_error(int c)
338 : std::system_error(c, std::generic_category())
343 // Create a C++ invoking wrapper for the tidylib C API, modifying data with the returned data,
344 // returing a unique_ptr to release storage on scope exit.
347 template <class T> void operator()(T *p) { ::free(p); }
349 inline outcome<std::unique_ptr<char, call_free>> tidy_html(string_view &data)
353 int errcode = ::tidy_html(&out, &outlen, data.data(), data.size());
356 // If the error code matches a standard STL exception, throw as that.
357 BOOST_OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(std::error_code(errcode, std::generic_category()));
358 // Otherwise wrap the error code into a tidylib_error exception throw
359 return std::make_exception_ptr(tidylib_error(errcode));
361 // Reset input view to tidied html
362 data = string_view(out, outlen);
363 // Return a unique ptr to release storage on scope exit
364 return std::unique_ptr<char, call_free>(out);
367 //! [app_map_tidylib]
372 // A markup function to indicate when we are ValueOrError converting
373 template <class T> inline outcome<typename T::value_type> ext(T &&v)
375 return outcome<typename T::value_type>(std::move(v));
378 outcome<void> go() // NOT noexcept, this can throw STL exceptions e.g. bad_alloc
380 // Note that explicit construction is required when converting between differing types
381 // of outcome and result. This makes it explicit what you intend to do as conversion
382 // may be a lot more expensive than moves.
384 // Try to GET this URL. If an unsuccessful HTTP status is returned, serialise a string
385 // containing a description of the HTTP status code and the URL which failed, storing
386 // that into a httplib_error exception type which is stored as an exception ptr. The
387 // TRY operation below will return that exception ptr to be rethrown in the caller.
388 // Otherwise the fetched data is returned in a std::string data.
389 BOOST_OUTCOME_TRY(data, ext(httplib::get("http://www.nedproductions.biz/")));
390 string_view data_view(data);
392 // HTML tidy the fetched data. If the C library fails due to an error corresponding to
393 // a standard library exception type, throw that. Otherwise, synthesise an exception
394 // ptr of type tidylib_error which stores the error code returned in an error code with
395 // generic category (i.e. errno domain).
396 // TRY operation below will return that exception ptr to be rethrown in the caller.
397 // Otherwise the tidied data is returned into holdmem, with the string view updated to
398 // point at the tidied data.
399 BOOST_OUTCOME_TRY(holdmem, ext(tidy_html(data_view)));
401 // Write the tidied data to some file. If the write fails, synthesise a filesystem_error
402 // exception ptr exactly as if one called filelib::write_file(data_view).value().
403 BOOST_OUTCOME_TRY(written, ext(filelib::write_file(data_view)));
415 catch(const filelib::filesystem_error &e)
417 std::cerr << "Exception thrown, " << e.what() //
418 << " (path1 = " << e.path1() << ", path2 = " << e.path2() << ")" //
422 catch(const std::exception &e)
424 std::cerr << "Exception thrown, " << e.what() << std::endl;