From cc1491776054f68f92105227c52c13d77743a415 Mon Sep 17 00:00:00 2001 From: Arthur van de Vondervoort Date: Sun, 27 Sep 2026 19:52:17 +0200 Subject: [PATCH] docs(AC0035): add rule page for RestClientInitializeWithHttpClientHandler Co-Authored-By: Claude Fable 5.1 --- .../docs/analyzers/ApplicationCop/AC0033.md | 2 +- .../docs/analyzers/ApplicationCop/AC0035.md | 106 ++++++++++++++++++ .../docs/analyzers/ApplicationCop/_index.md | 1 + 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 content/docs/analyzers/ApplicationCop/AC0035.md diff --git a/content/docs/analyzers/ApplicationCop/AC0033.md b/content/docs/analyzers/ApplicationCop/AC0033.md index 5f37523..a288f82 100644 --- a/content/docs/analyzers/ApplicationCop/AC0033.md +++ b/content/docs/analyzers/ApplicationCop/AC0033.md @@ -63,7 +63,7 @@ codeunit 50100 "Exchange Rate Sync" RestClient := RestClient.Create(HttpHandler); {{< /highlight >}} -`Initialize()`, `Initialize(HttpAuthentication)`, `Create()` and `Create(HttpAuthentication)` all use the default handler, and so does a Rest Client that is used without being initialized. AC0033 only checks that the app contains a handler implementation. It does not check that each Rest Client receives it. +`Initialize()`, `Initialize(HttpAuthentication)`, `Create()` and `Create(HttpAuthentication)` all use the default handler, and so does a Rest Client that is used without being initialized. AC0033 only checks that the app contains a handler implementation. It does not check that each Rest Client receives it. [AC0035](../ac0035/) checks that each Rest Client variable receives it. ### When the diagnostic is reported diff --git a/content/docs/analyzers/ApplicationCop/AC0035.md b/content/docs/analyzers/ApplicationCop/AC0035.md new file mode 100644 index 0000000..e28bd0c --- /dev/null +++ b/content/docs/analyzers/ApplicationCop/AC0035.md @@ -0,0 +1,106 @@ ++++ +title = 'Rest Client is not initialized with a custom Http Client Handler' +linkTitle = 'AC0035' + +[params] + id = 'AC0035' + severity = 'Warning' + category = 'Design' + codeAction = false + ignoreObsolete = true ++++ + +`Codeunit "Rest Client"` passes every request to the `"Http Client Handler"` it was initialized with. `Initialize()`, `Create()` and the overloads that take only an `HttpAuthentication` select the System Application's own `Codeunit "Http Client Handler"`. A Rest Client that is never initialized gets the same default: the implementation initializes itself with the default handler on first use. + +The request is then sent by the System Application, not by the app that built it. In a sandbox it runs under the System Application's permission to make HTTP client requests. The outgoing web service request telemetry goes to Microsoft's telemetry instead of the app publisher's, and a test cannot replace the response. Pass the app's own `"Http Client Handler"` implementation to each Rest Client. + +### Example + +{{< highlight al "hl_lines=7" >}} +codeunit 50100 "Exchange Rate Sync" +{ + procedure GetRates(CurrencyCode: Code[10]): JsonToken + var + RestClient: Codeunit "Rest Client"; + begin + RestClient.Initialize(); // Rest Client variable 'RestClient' is not initialized with a custom Http Client Handler [AC0035] + exit(RestClient.GetAsJson('https://api.example.com/rates/' + CurrencyCode)); + end; +} +{{< /highlight >}} + +Pass the app's handler to `RestClient.Initialize`. The `"Exchange Rate Http Handler"` codeunit is the implementation shown on [AC0033](../ac0033/): + +{{< highlight al "hl_lines=6 8" >}} +codeunit 50100 "Exchange Rate Sync" +{ + procedure GetRates(CurrencyCode: Code[10]): JsonToken + var + RestClient: Codeunit "Rest Client"; + HttpHandler: Codeunit "Exchange Rate Http Handler"; + begin + RestClient.Initialize(HttpHandler); + exit(RestClient.GetAsJson('https://api.example.com/rates/' + CurrencyCode)); + end; +} +{{< /highlight >}} + +`RestClient := RestClient.Create(HttpHandler)` is equivalent. The overloads that also take an `HttpAuthentication`, `Initialize(HttpHandler, HttpAuthentication)` and `Create(HttpHandler, HttpAuthentication)`, are accepted too. + +The handler does not have to be a codeunit of the app. Any value other than the System Application's `Codeunit "Http Client Handler"` is accepted: a codeunit from a dependency, an `Interface "Http Client Handler"` variable, or a procedure that returns the interface. This covers a shared library that calls the Rest Client on behalf of the main app, which passes its own handler in: + +{{< highlight al "hl_lines=3 7" >}} +codeunit 50120 "Shared Rest Helper" +{ + procedure GetJson(HttpHandler: Interface "Http Client Handler"; Url: Text): JsonToken + var + RestClient: Codeunit "Rest Client"; + begin + RestClient.Initialize(HttpHandler); + exit(RestClient.GetAsJson(Url)); + end; +} +{{< /highlight >}} + +### When the diagnostic is reported + +- A local variable (in any procedure or trigger) or a global variable of type `Codeunit "Rest Client"` is initialized with `Initialize()`, `Initialize(HttpAuthentication)`, `Create()` or `Create(HttpAuthentication)`, with the System Application's `Codeunit "Http Client Handler"` (ID 2360), or is used without being initialized. +- The diagnostic is reported on the `Initialize` or `Create` call that selects the default handler. When there is no such call on the variable, it is reported on the variable declaration. One diagnostic is reported per variable. +- A bare `Initialize()` is reported even when the same variable is initialized with a handler elsewhere, because it re-initializes the Rest Client with the default handler. +- When the variable is passed to a procedure in the same object, the analyzer follows the call, at any depth, and accepts an initialization with a handler inside that procedure. +- The order of calls is not checked: a request sent before `Initialize(HttpHandler)` is not detected. + +AC0033 reports an object once when the app contains no `"Http Client Handler"` implementation at all. AC0035 reports each Rest Client variable that does not receive one. Both can be reported on the same app. + +### Exception + +The analyzer only follows the Rest Client within its own object. It stays silent when it cannot see where the Rest Client is initialized, so that wrapper architectures are not reported: + +- The variable is passed to a procedure of another object, of a dependency, to an interface method, to an event publisher, or to a built-in method such as `Clear`. +- The variable is assigned from anything other than its own `Create` call (another variable, a procedure return value), is assigned to another variable, or is returned with `exit`. +- Parameters and return values of type `Codeunit "Rest Client"` are not checked. A procedure that receives a Rest Client leaves initialization to its caller. +- Declared but unused variables are not reported. +- Test codeunits (`Subtype = Test` or `TestRunner`) and obsolete objects are not reported. + +When a request deliberately uses the default handler, for example while an integration is moved to the app's handler one call at a time, suppress the diagnostic on the line where it is reported: + +{{< highlight al >}} +procedure GetRates(CurrencyCode: Code[10]): JsonToken +var + RestClient: Codeunit "Rest Client"; +begin + // This endpoint still runs under the System Application's HTTP permission + // until the integration is moved to "Exchange Rate Http Handler". +#pragma warning disable AC0035 + RestClient.Initialize(); +#pragma warning restore AC0035 + exit(RestClient.GetAsJson('https://api.example.com/rates/' + CurrencyCode)); +end; +{{< /highlight >}} + +### See also + +- [Codeunit "Rest Client"](https://learn.microsoft.com/en-us/dynamics365/business-central/application/system-application/codeunit/system.restclient.rest-client) on Microsoft Learn +- [Interface "Http Client Handler"](https://learn.microsoft.com/en-us/dynamics365/business-central/application/system-application/interface/system.restclient.http-client-handler) on Microsoft Learn +- [Prefer the Rest Client module over the native HttpClient](https://github.com/ALCops/Analyzers/discussions/405) (Rule 2) on GitHub Discussions +- [AC0033](../ac0033/): Rest Client requires an Http Client Handler implementation diff --git a/content/docs/analyzers/ApplicationCop/_index.md b/content/docs/analyzers/ApplicationCop/_index.md index f4f2382..fe665ae 100644 --- a/content/docs/analyzers/ApplicationCop/_index.md +++ b/content/docs/analyzers/ApplicationCop/_index.md @@ -44,5 +44,6 @@ ApplicationCop inspects how objects are modeled: tables, fields, pages, enums, l | [AC0032](ac0032/) | Unused permission declared | Info | ✓ | ✓ | | [AC0033](ac0033/) | Rest Client requires an Http Client Handler implementation | Warning | ✓ | | | [AC0034](ac0034/) | Telemetry requires a Telemetry Logger implementation | Warning | ✓ | | +| [AC0035](ac0035/) | Rest Client is not initialized with a custom Http Client Handler | Warning | ✓ | | **Note:** Rules marked with "—" in the Enabled column are disabled by default and must be explicitly enabled in your project's ruleset file.