{"id":109023,"date":"2023-11-16T07:00:00","date_gmt":"2023-11-16T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109023"},"modified":"2023-11-16T08:25:49","modified_gmt":"2023-11-16T16:25:49","slug":"20231116-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20231116-00\/?p=109023","title":{"rendered":"What happened to the custom exception description I threw from a C++\/WinRT IAsyncAction?"},"content":{"rendered":"<p>A customer designed one of their methods such that it returns the answer on success, and on failure, it throws a Windows Runtime exception whose custom description is a JSON payload that describes what went wrong.<\/p>\n<pre>winrt::IAsyncAction GetItemNameAsync()\r\n{\r\n    auto lifetime = get_strong();\r\n    auto result = co_await m_httpClient.TryGetStringAsync(m_uri);\r\n    if (!result.Succeeded()) {\r\n        \/\/ Network request failed. Return an empty object.\r\n        throw winrt::hresult_error(result.ExtendedError(),\r\n            L\"{ }\");\r\n    }\r\n\r\n    auto o = winrt::JsonObject::Parse(result.Value());\r\n\r\n    auto name = o.TryLookup(L\"name\");\r\n    if (!name) {\r\n        \/\/ If there is no name, then fail and include\r\n        \/\/ the JSON response (which will contain error details).\r\n        throw winrt::hresult_error(E_FAIL, result.Value());\r\n    }\r\n    return name;\r\n}\r\n<\/pre>\n<p>They found that sometimes the custom description they attached to the Windows Runtime exception was being truncated.<\/p>\n<pre>\/\/ consumer\r\n\r\ntry {\r\n    auto name = co_await GetItemNameAsync(itemId);\r\n    \u27e6 use the name \u27e7\r\n} catch (winrt::hresult_error const&amp; e) {\r\n    \/\/ Sometimes this parse fails with bad JSON.\r\n    auto o = winrt::JsonObject::Parse(e.message());\r\n    \u27e6 study the JSON to see what went wrong \u27e7\r\n}\r\n<\/pre>\n<p>They wondered if maybe they should throw some other type of exception to encode the JSON information.<\/p>\n<p>We learned a little while ago that <a title=\"How come my custom exception message is lost when it is thrown from a IAsyncAction^?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20231102-00\/?p=108956\"> the custom description associated with a Windows Runtime exception is a courtesy and may not survive transport across the ABI<\/a>. In particular, the custom description is not intended for programmatic use. Its intended use is to inform the developer of additional information that may help diagnose the problem. For example, for an <code>E_INVALIDARG<\/code> failure, it may contain the name of the invalid parameter. But it&#8217;s not intended as a reliable side channel.<\/p>\n<p>The <code>Ro\u00adOriginate\u00adError<\/code> function take a description for an about-to-be-returned failure and saves it in the per-thread side channel data. However, it saves only the first 250-ish characters of the message. I don&#8217;t know why it sets an artificial limit, but I have some ideas. For one thing, the description is already advisory anyway, and it&#8217;s supposed to be a brief message to help debug the problem. You weren&#8217;t expected to be passing large amounts of information, and perhaps the fixed limit is to prevent problems if somebody decides to pass a 1 gigabyte string as an &#8220;error description&#8221;.<\/p>\n<p>At any rate, you can&#8217;t put functionally significant information in the error description because it may not even survive at all.<\/p>\n<p>What the component should do is report the JSON error message as part of a compound return value.<\/p>\n<pre>namespace Contoso\r\n{\r\n    runtimeclass ItemNameResult\r\n    {\r\n        Boolean Succeeded { get; };\r\n        String Name { get; };\r\n        String ErrorJson { get; };\r\n    }\r\n}\r\n<\/pre>\n<p>We can then use this <code>Item\u00adName\u00adResult<\/code> as the return value of <code>Get\u00adItem\u00adName\u00adAsync<\/code>.<\/p>\n<pre>namespace winrt::Contoso::implementation::ItemNameResult\r\n{\r\n    struct ItemNameResult : ItemNameResultT&lt;ItemNameResult&gt;\r\n    {\r\n        static auto CreateName(hstring const&amp; name)\r\n        {\r\n            return make&lt;ItemNameResult&gt;(true, name, L\"\");\r\n        }\r\n        static auto CreateError(hstring const&amp; errorJson)\r\n        {\r\n            return make&lt;ItemNameResult&gt;(false, L\"\", errorJson);\r\n        }\r\n\r\n        auto Succeeded() { return m_succeeded; }\r\n        auto Name() { return m_name; }\r\n        auto ErrorJson() { return m_errorJson; }\r\n\r\n    private:\r\n        ItemNameResult(bool succeeeded,\r\n                       hstring const&amp; name,\r\n                       hstring const&amp; errorJson) :\r\n            m_succeeded(succeeded), m_name(name), m_errorJson(errorJson) {}\r\n\r\n        bool m_succeeded;\r\n        hstring m_name;\r\n        hstring m_errorJson;\r\n    };\r\n}\r\n\r\nwinrt::IAsyncOperation&lt;Contoso::ItemNameResult&gt; GetItemNameAsync()\r\n{\r\n    auto lifetime = get_strong();\r\n    auto result = co_await m_httpClient.TryGetStringAsync(m_uri);\r\n    if (!result.Succeeded()) {\r\n        \/\/ Network request failed.\r\n        <span style=\"border: solid 1px currentcolor;\">return ItemNameResult::CreateError(L\"{ }\");<\/span>\r\n    }\r\n\r\n    auto o = winrt::JsonObject::Parse(result.Value());\r\n\r\n    auto name = o.TryLookup(L\"name\");\r\n    if (!name) {\r\n        \/\/ If there is no name, then fail and include\r\n        \/\/ the JSON response (which will contain error details).\r\n        <span style=\"border: solid 1px currentcolor;\">return ItemNameResult::CreateError(result.Value());<\/span>\r\n    }\r\n    <span style=\"border: solid 1px currentcolor;\">return ItemNameResult::CreateName(name);<\/span>\r\n}\r\n\r\n\/\/ consumer\r\n\r\nauto result = co_await GetItemNameAsync(itemId);\r\n<span style=\"border: solid 1px currentcolor; border-bottom: none;\">if (result.Succeeded()) {     <\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">    auto name = result.Name();<\/span>\r\n    \u27e6 use the name \u27e7\r\n} else {\r\n    auto o = winrt::JsonObject::Parse(<span style=\"border: solid 1px currentcolor;\">result.ErrorJson()<\/span>);\r\n    \u27e6 study the JSON to see what went wrong \u27e7\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>The description is just a courtesy and is not part of the API contract.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-109023","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>The description is just a courtesy and is not part of the API contract.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109023","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=109023"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109023\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=109023"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109023"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109023"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}