Imported Upstream version 1.72.0
[platform/upstream/boost.git] / libs / outcome / doc / src / snippets / finale.cpp
1 /* Example of how to marshall Outcomes at namespace boundaries
2 (C) 2017-2019 Niall Douglas <http://www.nedproductions.biz/> (11 commits)
3
4
5 Boost Software License - Version 1.0 - August 17th, 2003
6
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:
13
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.
20
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.
28 */
29
30 #include "../../../include/boost/outcome.hpp"
31 #if __has_include("quickcpplib/string_view.hpp")
32 #include "quickcpplib/string_view.hpp"
33 #else
34 #include "../../../include/boost/outcome/quickcpplib/include/quickcpplib/string_view.hpp"
35 #endif
36 #include <cstring>  // for memcpy
37 #include <experimental/filesystem>
38
39 //! [httplib]
40 // This is some standalone library implementing high level HTTP
41 namespace httplib
42 {
43   // These are the error code that this HTTP library can return
44   enum class status_code
45   {
46     success = 0,  // not the HTTP success code of 200
47
48     // A subset of all HTTP status codes for brevity
49     bad_request = 400,
50     access_denied = 401,
51     logon_failed = 402,
52     forbidden = 403,
53     not_found = 404,
54     internal_error = 500
55   };
56   // This is the error type that this HTTP library can return
57   struct failure
58   {
59     status_code status{status_code::success};
60     std::string url{};  // The failing URL
61   };
62   // Localise a result implementation to this library, holding
63   // the logic error of incorrect observation to mean program
64   // termination.
65   template <class T>
66   using result =  //
67   BOOST_OUTCOME_V2_NAMESPACE::result<T, failure, BOOST_OUTCOME_V2_NAMESPACE::policy::terminate>;
68
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
72   */
73   result<std::string> get(std::string url);
74 }  // namespace httplib
75 //! [httplib]
76
77 namespace httplib
78 {
79   result<std::string> get(std::string url)
80   {
81     (void) url;
82 #if 1
83     return "hello world";
84 #else
85     return failure{status_code::not_found, url};
86 #endif
87   }
88 }  // namespace httplib
89
90 namespace filelib
91 {
92   using QUICKCPPLIB_NAMESPACE::string_view::string_view;
93   using std::experimental::filesystem::filesystem_error;
94   using std::experimental::filesystem::path;
95 }  // namespace filelib
96
97 namespace app
98 {
99   using QUICKCPPLIB_NAMESPACE::string_view::string_view;
100 }
101
102 //! [filelib]
103 // You may remember this from the tutorial section on Custom Payloads
104 namespace filelib
105 {
106   // Error code + paths related to a failure. Also causes ADL discovery
107   // to check this namespace.
108   struct failure_info
109   {
110     std::error_code ec;
111     path path1{}, path2{};
112   };
113
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; }
116
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)
119   {
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);
126   }
127
128   // Localise a result implementation specific to this namespace.
129   template <class T> using result = BOOST_OUTCOME_V2_NAMESPACE::result<T, failure_info>;
130
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
135 //! [filelib]
136
137 namespace filelib
138 {
139   result<size_t> write_file(string_view chunk) noexcept
140   {
141     (void) chunk;
142     return failure_info{make_error_code(std::errc::no_space_on_device), "somepath"};
143   }
144 }  // namespace filelib
145
146 //! [tidylib]
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.
151
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);
155 //! [tidylib]
156
157 extern "C" int tidy_html(char **out, size_t *outlen, const char *in, size_t inlen)
158 {
159 #if 1
160   *out = (char *) malloc(inlen + 1);
161   memcpy(*out, in, inlen + 1);
162   *outlen = inlen;
163   return 0;
164 #else
165   // return ENOMEM;
166   return EROFS;
167 #endif
168 }
169
170 //! [app]
171 // This is the namespace of the application which is connecting together the httplib,
172 // filelib and tidylib libraries into a solution.
173 namespace app
174 {
175   // Create an ADL bridge so copy/move hooks will be searched for in this namespace
176   struct error_code : public std::error_code
177   {
178     // passthrough
179     using std::error_code::error_code;
180     error_code() = default;
181     error_code(std::error_code ec)
182         : std::error_code(ec)
183     {
184     }
185   };
186   // Localise an outcome implementation for this namespace
187   template <class T>
188   using outcome =  //
189   BOOST_OUTCOME_V2_NAMESPACE::outcome<T, error_code /*, std::exception_ptr */>;
190   using BOOST_OUTCOME_V2_NAMESPACE::success;
191 }  // namespace app
192 //! [app]
193
194 //! [app_map_httplib1]
195 namespace app
196 {
197   // Specialise an exception type for httplib errors
198   struct httplib_error : std::runtime_error
199   {
200     // passthrough
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))
205     {
206     }
207
208     // the original failure
209     httplib::failure failure;
210   };
211
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)
215   {
216     std::string str("httplib failed with error ");
217     switch(src.error().status)
218     {
219     case httplib::status_code::success:
220       str.append("success");
221       break;
222     case httplib::status_code::bad_request:
223       str.append("bad request");
224       break;
225     case httplib::status_code::access_denied:
226       str.append("access denied");
227       break;
228     case httplib::status_code::logon_failed:
229       str.append("logon failed");
230       break;
231     case httplib::status_code::forbidden:
232       str.append("forbidden");
233       break;
234     case httplib::status_code::not_found:
235       str.append("not found");
236       break;
237     case httplib::status_code::internal_error:
238       str.append("internal error");
239       break;
240     }
241     str.append(" [url was ");
242     str.append(src.error().url);
243     str.append("]");
244     return std::make_exception_ptr(httplib_error(src.error(), std::move(str)));
245   }
246 }  // namespace app
247 //! [app_map_httplib1]
248
249 //! [app_map_httplib2]
250 // Inject custom ValueOrError conversion
251 BOOST_OUTCOME_V2_NAMESPACE_BEGIN
252 namespace convert
253 {
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>>
258   {
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;
265
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)
270     {
271       // Forward any successful value, else synthesise an exception ptr
272       return src.has_value() ?                              //
273              app::outcome<T>{std::forward<X>(src).value()}  //
274              :
275              app::outcome<T>{app::make_httplib_exception(std::forward<X>(src))};
276     }
277   };
278 }  // namespace convert
279 BOOST_OUTCOME_V2_NAMESPACE_END
280 //! [app_map_httplib2]
281
282 namespace app
283 {
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});
286 }  // namespace app
287
288 //! [app_map_filelib]
289 // Inject custom ValueOrError conversion
290 BOOST_OUTCOME_V2_NAMESPACE_BEGIN
291 namespace convert
292 {
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>>
297   {
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;
304
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)
309     {
310       // Forward any successful value
311       if(src.has_value())
312       {
313         return {std::forward<X>(src).value()};
314       }
315
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))};
322     }
323   };
324 }  // namespace convert
325 BOOST_OUTCOME_V2_NAMESPACE_END
326 //! [app_map_filelib]
327
328 //! [app_map_tidylib]
329 namespace app
330 {
331   // Specialise an exception type for tidylib errors
332   struct tidylib_error : std::system_error
333   {
334     // passthrough
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())
339     {
340     }
341   };
342
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.
345   struct call_free
346   {
347     template <class T> void operator()(T *p) { ::free(p); }
348   };
349   inline outcome<std::unique_ptr<char, call_free>> tidy_html(string_view &data)
350   {
351     char *out = nullptr;
352     size_t outlen = 0;
353     int errcode = ::tidy_html(&out, &outlen, data.data(), data.size());
354     if(errcode != 0)
355     {
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));
360     }
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);
365   }
366 }  // namespace app
367 //! [app_map_tidylib]
368
369 //! [app_go]
370 namespace app
371 {
372   // A markup function to indicate when we are ValueOrError converting
373   template <class T> inline outcome<typename T::value_type> ext(T &&v)
374   {  //
375     return outcome<typename T::value_type>(std::move(v));
376   }
377
378   outcome<void> go()  // NOT noexcept, this can throw STL exceptions e.g. bad_alloc
379   {
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.
383
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);
391
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)));
400
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)));
404     return success();
405   }
406 }  // namespace app
407 //! [app_go]
408
409 int main()
410 {
411   try
412   {
413     app::go().value();
414   }
415   catch(const filelib::filesystem_error &e)
416   {
417     std::cerr << "Exception thrown, " << e.what()                               //
418               << " (path1 = " << e.path1() << ", path2 = " << e.path2() << ")"  //
419               << std::endl;
420     return 1;
421   }
422   catch(const std::exception &e)
423   {
424     std::cerr << "Exception thrown, " << e.what() << std::endl;
425     return 1;
426   }
427   return 0;
428 }