From fbcb888a3b600e82e0a51e22d2ab8cfcff60985d Mon Sep 17 00:00:00 2001 From: Arend-Jan Kauffmann Date: Tue, 29 Oct 2024 21:27:23 +0100 Subject: [PATCH 01/12] Enhance Rest Client with error handling and cookie support Upgraded the runtime to version 14.0 and extended ID range to accommodate newly introduced components. Added a comprehensive error handling system using enums for different error scenarios and a dedicated builder for error creation. Improved HttpClient functionality by allowing the creation and management of cookies within requests and responses, enhancing flexibility. Refactored HTTP methods with improved response checking, ensuring errors are collected and processed correctly. Fix handling of secret content headers. Refactored code with new constructor functions. --- .../App/Rest Client/app.json | 4 +- .../RestClientException.Enum.al | 32 ++++ .../RestClientExceptionBuilder.Codeunit.al | 21 +++ .../HttpClientHandler.Codeunit.al | 2 +- .../Rest Client/src/HttpContent.Codeunit.al | 126 +++++--------- .../src/HttpContentImpl.Codeunit.al | 112 ++++++++++++- .../src/HttpRequestMessage.Codeunit.al | 83 +++++++++ .../src/HttpRequestMessageImpl.Codeunit.al | 90 +++++++++- .../src/HttpResponseMessage.Codeunit.al | 76 +++++++++ .../src/HttpResponseMessageImpl.Codeunit.al | 128 +++++++++++--- .../Rest Client/src/RestClient.Codeunit.al | 158 +++++++++++++++++- .../src/RestClientImpl.Codeunit.al | 119 ++++++++++--- 12 files changed, 813 insertions(+), 138 deletions(-) create mode 100644 src/System Application/App/Rest Client/src/ExceptionHandling/RestClientException.Enum.al create mode 100644 src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al diff --git a/src/System Application/App/Rest Client/app.json b/src/System Application/App/Rest Client/app.json index 91cf2833d0..39359a8134 100644 --- a/src/System Application/App/Rest Client/app.json +++ b/src/System Application/App/Rest Client/app.json @@ -34,11 +34,11 @@ "idRanges": [ { "from": 2350, - "to": 2361 + "to": 2362 } ], "target": "OnPrem", - "runtime": "12.0", + "runtime": "14.0", "resourceExposurePolicy": { "allowDebugging": true, "allowDownloadingSource": true, diff --git a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientException.Enum.al b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientException.Enum.al new file mode 100644 index 0000000000..f97c0bf79f --- /dev/null +++ b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientException.Enum.al @@ -0,0 +1,32 @@ +namespace System.RestClient; + +/// +/// This enum contains the exceptions of the Rest Client. +/// +enum 2351 "Rest Client Exception" +{ + value(100; UnknownException) + { + Caption = 'Unknown Exception'; + } + value(101; ConnectionFailed) + { + Caption = 'Connection Failed'; + } + value(102; BlockedByEnvironment) + { + Caption = 'Blocked By Environment'; + } + value(103; RequestFailed) + { + Caption = 'Request Failed'; + } + value(201; InvalidJson) + { + Caption = 'Invalid Json'; + } + value(202; InvalidXml) + { + Caption = 'Invalid Xml'; + } +} \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al new file mode 100644 index 0000000000..78361c6a63 --- /dev/null +++ b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al @@ -0,0 +1,21 @@ +namespace System.RestClient; + +codeunit 2362 "Rest Client Exception Builder" +{ + Access = Internal; + InherentEntitlements = X; + InherentPermissions = X; + + procedure CreateException(RestClientException: Enum "Rest Client Exception"; ErrorMessage: Text) Exception: ErrorInfo + begin + Exception := CreateException(RestClientException, ErrorMessage, IsCollectingErrors()); + end; + + procedure CreateException(RestClientException: Enum "Rest Client Exception"; ErrorMessage: Text; Collectible: Boolean) Exception: ErrorInfo + begin + Exception.Message := ErrorMessage; + Exception.CustomDimensions.Add('ExceptionCode', Format(RestClientException.AsInteger())); + Exception.CustomDimensions.Add('ExceptionName', RestClientException.Names.Get(RestClientException.Ordinals.IndexOf(RestClientException.AsInteger()))); + Exception.Collectible := Collectible; + end; +} \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al b/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al index c7ab685cf7..a82672eae5 100644 --- a/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al @@ -14,6 +14,6 @@ codeunit 2360 "Http Client Handler" implements "Http Client Handler" ResponseMessage: HttpResponseMessage; begin Success := HttpClient.Send(HttpRequestMessage.GetHttpRequestMessage(), ResponseMessage); - HttpResponseMessage.SetResponseMessage(ResponseMessage); + HttpResponseMessage := HttpResponseMessage.Create(ResponseMessage); end; } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al b/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al index 8295f767a9..9db74b3af2 100644 --- a/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al @@ -20,18 +20,20 @@ codeunit 2354 "Http Content" /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'text/plain'. - procedure Create(Content: Text) HttpContent: Codeunit "Http Content" + procedure Create(Content: Text): Codeunit "Http Content" begin - HttpContent := Create(Content, ''); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified SecretText content. /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'text/plain'. - procedure Create(Content: SecretText) HttpContent: Codeunit "Http Content" + procedure Create(Content: SecretText): Codeunit "Http Content" begin - HttpContent := Create(Content, ''); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified Text content and content type. @@ -39,10 +41,10 @@ codeunit 2354 "Http Content" /// The content type of the content to send to the server. /// The Http Content object. /// If the ContentType parameter is not specified, it will be set to 'text/plain'. - procedure Create(Content: Text; ContentType: Text) HttpContent: Codeunit "Http Content" + procedure Create(Content: Text; ContentType: Text): Codeunit "Http Content" begin - SetContent(Content, ContentType); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content, ContentType); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified SecretText content and content type. @@ -50,59 +52,60 @@ codeunit 2354 "Http Content" /// The content type of the content to send to the server. /// The Http Content object. /// If the ContentType parameter is not specified, it will be set to 'text/plain'. - procedure Create(Content: SecretText; ContentType: Text) HttpContent: Codeunit "Http Content" + procedure Create(Content: SecretText; ContentType: Text): Codeunit "Http Content" begin - SetContent(Content, ContentType); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content, ContentType); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified JsonObject content. /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'application/json'. - procedure Create(Content: JsonObject) HttpContent: Codeunit "Http Content" + procedure Create(Content: JsonObject): Codeunit "Http Content" begin - SetContent(Content); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified JsonArray content. /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'application/json'. - procedure Create(Content: JsonArray) HttpContent: Codeunit "Http Content" + procedure Create(Content: JsonArray): Codeunit "Http Content" begin - SetContent(Content); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified JsonToken content. /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'application/json'. - procedure Create(Content: JsonToken) HttpContent: Codeunit "Http Content" + procedure Create(Content: JsonToken): Codeunit "Http Content" begin - SetContent(Content); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified XmlDocument content. /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'text/xml'. - procedure Create(Content: XmlDocument) HttpContent: Codeunit "Http Content" + procedure Create(Content: XmlDocument): Codeunit "Http Content" begin - SetContent(Content); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified "Temp Blob" Codeunit content. /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'application/octet-stream'. - procedure Create(Content: Codeunit "Temp Blob") HttpContent: Codeunit "Http Content" + procedure Create(Content: Codeunit "Temp Blob"): Codeunit "Http Content" begin - HttpContent := Create(Content, ''); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified "Temp Blob" Codeunit content and content type. @@ -110,19 +113,20 @@ codeunit 2354 "Http Content" /// The content type of the content to send to the server. /// The Http Content object. /// If the ContentType parameter is not specified, it will be set to 'application/octet-stream'. - procedure Create(Content: Codeunit "Temp Blob"; ContentType: Text) HttpContent: Codeunit "Http Content" + procedure Create(Content: Codeunit "Temp Blob"; ContentType: Text): Codeunit "Http Content" begin - SetContent(Content, ContentType); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content, ContentType); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified InStream content. /// The content to send to the server. /// The Http Content object. /// The Content-Type header will be set to 'application/octet-stream'. - procedure Create(Content: InStream) HttpContent: Codeunit "Http Content" + procedure Create(Content: InStream): Codeunit "Http Content" begin - HttpContent := Create(Content, ''); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; /// Initializes a new instance of the Http Content class with the specified InStream content and content type. @@ -130,20 +134,20 @@ codeunit 2354 "Http Content" /// The content type of the content to send to the server. /// The Http Content object. /// If the ContentType parameter is not specified, it will be set to 'application/octet-stream'. - procedure Create(Content: InStream; ContentType: Text) HttpContent: Codeunit "Http Content" + procedure Create(Content: InStream; ContentType: Text): Codeunit "Http Content" begin - SetContent(Content, ContentType); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content, ContentType); + exit(this); end; /// Initializes a new instance of the Http Content object with the specified HttpContent object. /// The HttpContent object. /// The HttpContent object. /// The HttpContent must be properly prepared including the Content-Type header. - procedure Create(Content: HttpContent) HttpContent: Codeunit "Http Content" + procedure Create(Content: HttpContent): Codeunit "Http Content" begin - SetContent(Content); - HttpContent.SetHttpContentImpl(HttpContentImpl); + HttpContentImpl := HttpContentImpl.Create(Content); + exit(this); end; #endregion @@ -233,56 +237,4 @@ codeunit 2354 "Http Content" JsonToken := HttpContentImpl.AsJson(); end; #endregion - - #region Internal Methods - internal procedure SetContent(Content: Text; ContentType: Text) - begin - HttpContentImpl.SetContent(Content, ContentType); - end; - - internal procedure SetContent(Content: SecretText; ContentType: Text) - begin - HttpContentImpl.SetContent(Content, ContentType); - end; - - internal procedure SetContent(Content: InStream; ContentType: Text) - begin - HttpContentImpl.SetContent(Content, ContentType); - end; - - internal procedure SetContent(TempBlob: Codeunit "Temp Blob"; ContentType: Text) - begin - HttpContentImpl.SetContent(TempBlob, ContentType); - end; - - internal procedure SetContent(Content: XmlDocument) - begin - HttpContentImpl.SetContent(Content); - end; - - internal procedure SetContent(Content: JsonObject) - begin - SetContent(Content.AsToken()); - end; - - internal procedure SetContent(Content: JsonArray) - begin - SetContent(Content.AsToken()); - end; - - internal procedure SetContent(Content: JsonToken) - begin - HttpContentImpl.SetContent(Content); - end; - - internal procedure SetContent(var Value: HttpContent) - begin - HttpContentImpl.SetContent(Value); - end; - - internal procedure SetHttpContentImpl(Value: Codeunit "Http Content Impl.") - begin - HttpContentImpl := Value; - end; - #endregion } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al b/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al index b384aa28ed..670a65b3fe 100644 --- a/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al @@ -4,6 +4,7 @@ // ------------------------------------------------------------------------------------------------ namespace System.RestClient; +using System.RestClient; using System.Utilities; codeunit 2355 "Http Content Impl." @@ -21,6 +22,91 @@ codeunit 2355 "Http Content Impl." MimeTypeApplicationOctetStreamTxt: Label 'application/octet-stream', Locked = true; MimeTypeApplicationJsonTxt: Label 'application/json', Locked = true; + #region Constructors + procedure Create(Content: Text) HttpContentImpl: Codeunit "Http Content Impl." + begin + HttpContentImpl := Create(Content, ''); + end; + + procedure Create(Content: SecretText) HttpContentImpl: Codeunit "Http Content Impl." + begin + HttpContentImpl := Create(Content, ''); + end; + + procedure Create(Content: Text; ContentType: Text): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content, ContentType); + exit(this); + end; + + procedure Create(Content: SecretText; ContentType: Text): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content, ContentType); + exit(this); + end; + + procedure Create(Content: JsonObject): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content.AsToken()); + exit(this); + end; + + procedure Create(Content: JsonArray): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content.AsToken()); + exit(this); + end; + + procedure Create(Content: JsonToken): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content); + exit(this); + end; + + procedure Create(Content: XmlDocument): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content); + exit(this); + end; + + procedure Create(Content: Codeunit "Temp Blob") HttpContentImpl: Codeunit "Http Content Impl." + begin + HttpContentImpl := Create(Content, ''); + end; + + procedure Create(Content: Codeunit "Temp Blob"; ContentType: Text): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content, ContentType); + exit(this); + end; + + procedure Create(Content: InStream) HttpContent: Codeunit "Http Content Impl." + begin + HttpContent := Create(Content, ''); + end; + + procedure Create(Content: InStream; ContentType: Text): Codeunit "Http Content Impl." + begin + ClearAll(); + SetContent(Content, ContentType); + exit(this); + end; + + procedure Create(HttpContent: HttpContent): Codeunit "Http Content Impl." + begin + ClearAll(); + this.SetContent(HttpContent); + exit(this); + end; + #endregion + procedure SetContentTypeHeader(ContentType: Text) begin if ContentType = '' then @@ -48,7 +134,7 @@ codeunit 2355 "Http Content Impl." HttpContent.GetHeaders(Headers); end; - if Headers.Contains(Name) then + if Headers.Contains(Name) or Headers.ContainsSecret(Name) then Headers.Remove(Name); Headers.Add(Name, Value); @@ -63,7 +149,7 @@ codeunit 2355 "Http Content Impl." HttpContent.GetHeaders(Headers); end; - if Headers.Contains(Name) then + if Headers.Contains(Name) or Headers.ContainsSecret(Name) then Headers.Remove(Name); Headers.Add(Name, Value); @@ -104,7 +190,8 @@ codeunit 2355 "Http Content Impl." XmlReadOptions: XmlReadOptions; begin XmlReadOptions.PreserveWhitespace(false); - XmlDocument.ReadFrom(AsText(), XmlReadOptions, ReturnValue); + if not XmlDocument.ReadFrom(AsText(), XmlReadOptions, ReturnValue) then + ThrowInvalidXmlException(); end; procedure AsJson() ReturnValue: JsonToken @@ -113,7 +200,8 @@ codeunit 2355 "Http Content Impl." begin Json := AsText(); if Json <> '' then - ReturnValue.ReadFrom(AsText()); + if not ReturnValue.ReadFrom(AsText()) then + ThrowInvalidJsonException(); end; procedure SetContent(Content: Text; ContentType: Text) @@ -175,4 +263,20 @@ codeunit 2355 "Http Content Impl." begin HttpContent := Value; end; + + local procedure ThrowInvalidJsonException() + var + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + InvalidJsonMessageTxt: Label 'The content is not a valid JSON.'; + begin + Error(RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::InvalidJson, InvalidJsonMessageTxt)); + end; + + local procedure ThrowInvalidXmlException() + var + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + InvalidXmlMessageTxt: Label 'The content is not a valid XML.'; + begin + Error(RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::InvalidXml, InvalidXmlMessageTxt)); + end; } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpRequestMessage.Codeunit.al b/src/System Application/App/Rest Client/src/HttpRequestMessage.Codeunit.al index fe0fe9319b..825a646117 100644 --- a/src/System Application/App/Rest Client/src/HttpRequestMessage.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpRequestMessage.Codeunit.al @@ -13,6 +13,16 @@ codeunit 2352 "Http Request Message" var HttpRequestMessageImpl: Codeunit "Http Request Message Impl."; + /// Creates a new instance of the HttpRequestMessage object. + /// The HTTP method to use. Valid options are GET, POST, PATCH, PUT, DELETE, HEAD, OPTIONS + /// The Uri to use for the HTTP request. + /// The Http Content object to use for the HTTP request. + /// The create Http Request Message + procedure Create(Method: Enum "Http Method"; RequestUri: Text; Content: Codeunit "Http Content") HttpRequestMessage: Codeunit "Http Request Message" + begin + HttpRequestMessageImpl := HttpRequestMessageImpl.Create(Method, RequestUri, Content); + HttpRequestMessage := this; + end; /// Sets the HTTP method or the HttpRequestMessage object. /// The HTTP method to use. Valid options are GET, POST, PATCH, PUT, DELETE, HEAD, OPTIONS @@ -68,6 +78,79 @@ codeunit 2352 "Http Request Message" HttpRequestMessageImpl.SetHeader(HeaderName, HeaderValue); end; + /// Gets the values of the header with the given name from the HttpRequestMessage object. + /// The name of the header to get. + /// A list of values of the header with the given name. + /// If the header is not found, an empty list is returned. + procedure GetHeaderValues(HeaderName: Text) Values: List of [Text] + begin + Values := HttpRequestMessageImpl.GetHeaderValues(HeaderName); + end; + + /// Gets the secret values of the header with the given name from the HttpRequestMessage object. + /// The name of the header to get. + /// A list of values of the header with the given name. + /// If the header is not found, an empty list is returned. + procedure GetSecretHeaderValues(HeaderName: Text) Values: List of [SecretText] + begin + Values := HttpRequestMessageImpl.GetSecretHeaderValues(HeaderName); + end; + + /// Sets the cookie given a name and value + /// The name of the cookie to set. + /// The value of the cookie to set. + procedure SetCookie(Name: Text; Value: Text) Success: Boolean + begin + Success := HttpRequestMessageImpl.SetCookie(Name, Value); + end; + + /// Sets the cookie given a cookie object + /// The cookie object to set. + procedure SetCookie(Cookie: Cookie) Success: Boolean + begin + Success := HttpRequestMessageImpl.SetCookie(Cookie); + end; + + /// Gets the names of the cookies that are set in the HttpRequestMessage object. + /// The names of the cookies that are set in the HttpRequestMessage object. + procedure GetCookieNames() CookieNames: List of [Text] + begin + CookieNames := HttpRequestMessageImpl.GetCookieNames(); + end; + + /// Gets the cookies that are set in the HttpRequestMessage object. + /// The cookies that are set in the HttpRequestMessage object. + procedure GetCookies() Cookies: List of [Cookie] + begin + Cookies := HttpRequestMessageImpl.GetCookies(); + end; + + /// Gets the cookie with the given name from the HttpRequestMessage object. + /// The name of the cookie to get. + /// The cookie object. + /// If the cookie is not found, an empty cookie object is returned. + procedure GetCookie(Name: Text) ReturnValue: Cookie + begin + ReturnValue := HttpRequestMessageImpl.GetCookie(Name); + end; + + /// Gets the cookie with the given name from the HttpRequestMessage object. + /// The name of the cookie to get. + /// The cookie object to get. + /// True if the cookie was found, false otherwise. + procedure GetCookie(Name: Text; var Cookie: Cookie) Success: Boolean + begin + Success := HttpRequestMessageImpl.GetCookie(Name, Cookie); + end; + + /// Removes the cookie with the given name from the HttpRequestMessage object. + /// The name of the cookie to remove. + /// True if the cookie was removed, false otherwise. + procedure RemoveCookie(Name: Text) Success: Boolean + begin + Success := HttpRequestMessageImpl.RemoveCookie(Name); + end; + /// Sets the HttpRequestMessage that is represented by the HttpRequestMessage object. /// The HttpRequestMessage to set. procedure SetHttpRequestMessage(var RequestMessage: HttpRequestMessage) diff --git a/src/System Application/App/Rest Client/src/HttpRequestMessageImpl.Codeunit.al b/src/System Application/App/Rest Client/src/HttpRequestMessageImpl.Codeunit.al index e95a1f0907..1da949178b 100644 --- a/src/System Application/App/Rest Client/src/HttpRequestMessageImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpRequestMessageImpl.Codeunit.al @@ -13,6 +13,15 @@ codeunit 2353 "Http Request Message Impl." var HttpRequestMessage: HttpRequestMessage; + procedure Create(Method: Enum "Http Method"; RequestUri: Text; Content: Codeunit "Http Content"): Codeunit "Http Request Message Impl." + begin + ClearAll(); + SetHttpMethod(Method); + SetRequestUri(RequestUri); + SetContent(Content); + exit(this); + end; + procedure SetHttpMethod(Method: Text) begin HttpRequestMessage.Method := Method; @@ -43,7 +52,7 @@ codeunit 2353 "Http Request Message Impl." HttpHeaders: HttpHeaders; begin HttpRequestMessage.GetHeaders(HttpHeaders); - if HttpHeaders.Contains(HeaderName) then + if HttpHeaders.Contains(HeaderName) or HttpHeaders.ContainsSecret(HeaderName) then HttpHeaders.Remove(HeaderName); HttpHeaders.Add(HeaderName, HeaderValue); end; @@ -53,11 +62,88 @@ codeunit 2353 "Http Request Message Impl." HttpHeaders: HttpHeaders; begin HttpRequestMessage.GetHeaders(HttpHeaders); - if HttpHeaders.Contains(HeaderName) then + if HttpHeaders.Contains(HeaderName) or HttpHeaders.ContainsSecret(HeaderName) then HttpHeaders.Remove(HeaderName); HttpHeaders.Add(HeaderName, HeaderValue); end; + procedure GetHeaders() ReturnValue: HttpHeaders + begin + HttpRequestMessage.GetHeaders(ReturnValue); + end; + + procedure GetHeaderValue(HeaderName: Text) Value: Text + var + HttpHeaders: HttpHeaders; + Values: List of [Text]; + begin + HttpRequestMessage.GetHeaders(HttpHeaders); + if HttpHeaders.Contains(HeaderName) then begin + HttpHeaders.GetValues(HeaderName, Values); + if Values.Count > 0 then + Value := Values.Get(1); + end; + end; + + procedure GetHeaderValues(HeaderName: Text) Values: List of [Text] + var + HttpHeaders: HttpHeaders; + begin + HttpRequestMessage.GetHeaders(HttpHeaders); + if HttpHeaders.Contains(HeaderName) then + HttpHeaders.GetValues(HeaderName, Values); + end; + + procedure GetSecretHeaderValues(HeaderName: Text) Values: List of [SecretText] + var + HttpHeaders: HttpHeaders; + begin + HttpRequestMessage.GetHeaders(HttpHeaders); + if HttpHeaders.ContainsSecret(HeaderName) then + HttpHeaders.GetSecretValues(HeaderName, Values); + end; + + procedure SetCookie(Name: Text; Value: Text) Success: Boolean + begin + Success := HttpRequestMessage.SetCookie(Name, Value); + end; + + procedure SetCookie(Cookie: Cookie) Success: Boolean + begin + Success := HttpRequestMessage.SetCookie(Cookie); + end; + + procedure GetCookieNames() CookieNames: List of [Text] + begin + CookieNames := HttpRequestMessage.GetCookieNames(); + end; + + procedure GetCookies() Cookies: List of [Cookie] + var + CookieName: Text; + Cookie: Cookie; + begin + foreach CookieName in HttpRequestMessage.GetCookieNames() do begin + HttpRequestMessage.GetCookie(CookieName, Cookie); + Cookies.Add(Cookie); + end; + end; + + procedure GetCookie(Name: Text) ReturnValue: Cookie + begin + if HttpRequestMessage.GetCookie(Name, ReturnValue) then; + end; + + procedure GetCookie(Name: Text; var Cookie: Cookie) Success: Boolean + begin + Success := HttpRequestMessage.GetCookie(Name, Cookie); + end; + + procedure RemoveCookie(Name: Text) Success: Boolean + begin + Success := HttpRequestMessage.RemoveCookie(Name); + end; + procedure SetHttpRequestMessage(var RequestMessage: HttpRequestMessage) begin HttpRequestMessage := RequestMessage; diff --git a/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al b/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al index d53e3ca94e..0e0c027a25 100644 --- a/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al @@ -13,6 +13,17 @@ codeunit 2356 "Http Response Message" var HttpResponseMessageImpl: Codeunit "Http Response Message Impl."; + #region Constructors + /// Initializes a new instance of the HttpResponseMessage class. + /// The HTTP response message. + /// The HttpResponseMessage object. + procedure Create(HttpResponseMessage: HttpResponseMessage): Codeunit "Http Response Message" + begin + HttpResponseMessageImpl := HttpResponseMessageImpl.Create(HttpResponseMessage); + exit(this); + end; + #endregion + #region IsBlockedByEnvironment /// Sets whether the request is blocked by the environment. /// True if the request is blocked by the environment; otherwise, false. @@ -127,6 +138,47 @@ codeunit 2356 "Http Response Message" end; #endregion + #region Cookies + /// Sets the cookies in the HTTP response message. + /// The cookies to set. + procedure SetCookies(Cookies: Dictionary of [Text, Cookie]) + begin + HttpResponseMessageImpl.SetCookies(Cookies); + end; + + /// Gets the cookies in the HTTP response message. + /// The cookies in the HTTP response message. + procedure GetCookies() ReturnValue: Dictionary of [Text, Cookie] + begin + ReturnValue := HttpResponseMessageImpl.GetCookies(); + end; + + /// Gets the names of the cookies that are set in the HTTP response message. + /// The names of the cookies that are set in the HTTP response message. + procedure GetCookieNames() ReturnValue: List of [Text] + begin + ReturnValue := HttpResponseMessageImpl.GetCookieNames(); + end; + + /// Gets the cookie with the given name from the HTTP response message. + /// The name of the cookie to get. + /// The cookie object. + /// If the cookie is not found, an empty cookie object is returned. + procedure GetCookie(Name: Text) ReturnValue: Cookie + begin + ReturnValue := HttpResponseMessageImpl.GetCookie(Name); + end; + + /// Gets the cookie with the given name from the HTTP response message. + /// The name of the cookie to get. + /// The cookie object to get. + /// True if the cookie was found, false otherwise. + procedure GetCookie(Name: Text; var Cookie: Cookie) Success: Boolean + begin + Success := HttpResponseMessageImpl.GetCookie(Name, Cookie); + end; + #endregion + #region ErrorMessage /// Sets an error message when the request failed. /// The error message. @@ -141,5 +193,29 @@ codeunit 2356 "Http Response Message" begin ReturnValue := HttpResponseMessageImpl.GetErrorMessage(); end; + + /// Sets the exception information when the request failed. + /// The exception information. + procedure SetException(Exception: ErrorInfo) + begin + HttpResponseMessageImpl.SetException(Exception); + end; + + /// Gets the exception information when the request failed. + /// The exception information. + procedure GetException() Exception: ErrorInfo + begin + Exception := HttpResponseMessageImpl.GetException(); + end; + + procedure GetExceptionCode() ReturnValue: Enum "Rest Client Exception" + begin + ReturnValue := HttpResponseMessageImpl.GetExceptionCode(); + end; + + procedure GetExceptionName() ReturnValue: Text + begin + ReturnValue := HttpResponseMessageImpl.GetExceptionName(); + end; #endregion } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al b/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al index 7355f0244d..7f346d940d 100644 --- a/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al @@ -4,24 +4,34 @@ // ------------------------------------------------------------------------------------------------ namespace System.RestClient; +using System.RestClient; + codeunit 2357 "Http Response Message Impl." { Access = Internal; InherentEntitlements = X; InherentPermissions = X; + #region Constructors + procedure Create(HttpResponseMessage: HttpResponseMessage): Codeunit "Http Response Message Impl." + begin + this.SetResponseMessage(HttpResponseMessage); + exit(this); + end; + #endregion + #region IsBlockedByEnvironment var IsBlockedByEnvironment: Boolean; procedure SetIsBlockedByEnvironment(Value: Boolean) begin - IsBlockedByEnvironment := Value; + this.IsBlockedByEnvironment := Value; end; procedure GetIsBlockedByEnvironment() ReturnValue: Boolean begin - ReturnValue := IsBlockedByEnvironment; + ReturnValue := this.IsBlockedByEnvironment; end; #endregion @@ -31,13 +41,13 @@ codeunit 2357 "Http Response Message Impl." procedure SetHttpStatusCode(Value: Integer) begin - HttpStatusCode := Value; - IsSuccessStatusCode := Value in [200 .. 299]; + this.HttpStatusCode := Value; + this.IsSuccessStatusCode := Value in [200 .. 299]; end; procedure GetHttpStatusCode() ReturnValue: Integer begin - ReturnValue := HttpStatusCode + ReturnValue := this.HttpStatusCode end; #endregion @@ -47,12 +57,12 @@ codeunit 2357 "Http Response Message Impl." procedure GetIsSuccessStatusCode() Result: Boolean begin - Result := HttpResponseMessage.IsSuccessStatusCode; + Result := this.IsSuccessStatusCode; end; procedure SetIsSuccessStatusCode(Value: Boolean) begin - IsSuccessStatusCode := Value; + this.IsSuccessStatusCode := Value; end; #endregion @@ -62,12 +72,12 @@ codeunit 2357 "Http Response Message Impl." procedure SetReasonPhrase(Value: Text) begin - ReasonPhrase := Value; + this.ReasonPhrase := Value; end; procedure GetReasonPhrase() ReturnValue: Text begin - ReturnValue := HttpResponseMessage.ReasonPhrase; + ReturnValue := this.ReasonPhrase; end; #endregion @@ -77,12 +87,12 @@ codeunit 2357 "Http Response Message Impl." procedure SetContent(Content: Codeunit "Http Content") begin - HttpContent := Content; + this.HttpContent := Content; end; procedure GetContent() ReturnValue: Codeunit "Http Content" begin - ReturnValue := HttpContent; + ReturnValue := this.HttpContent; end; #endregion @@ -91,19 +101,29 @@ codeunit 2357 "Http Response Message Impl." HttpResponseMessage: HttpResponseMessage; procedure SetResponseMessage(var ResponseMessage: HttpResponseMessage) + var + Cookie: Cookie; + Cookies: Dictionary of [Text, Cookie]; + CookieName: Text; begin - HttpResponseMessage := ResponseMessage; + ClearAll(); + this.HttpResponseMessage := ResponseMessage; SetIsBlockedByEnvironment(ResponseMessage.IsBlockedByEnvironment); SetHttpStatusCode(ResponseMessage.HttpStatusCode); SetReasonPhrase(ResponseMessage.ReasonPhrase); SetIsSuccessStatusCode(ResponseMessage.IsSuccessStatusCode); SetHeaders(ResponseMessage.Headers); SetContent(HttpContent.Create(ResponseMessage.Content)); + foreach CookieName in ResponseMessage.GetCookieNames() do begin + ResponseMessage.GetCookie(CookieName, Cookie); + Cookies.Add(CookieName, Cookie); + end; + SetCookies(Cookies); end; procedure GetResponseMessage() ReturnValue: HttpResponseMessage begin - ReturnValue := HttpResponseMessage; + ReturnValue := this.HttpResponseMessage; end; #endregion @@ -113,30 +133,96 @@ codeunit 2357 "Http Response Message Impl." procedure SetHeaders(Headers: HttpHeaders) begin - HttpHeaders := Headers; + this.HttpHeaders := Headers; end; procedure GetHeaders() ReturnValue: HttpHeaders begin - ReturnValue := HttpHeaders; + ReturnValue := this.HttpHeaders; + end; + #endregion + + #region Cookies + var + Cookies: Dictionary of [Text, Cookie]; + + procedure SetCookies(Cookies: Dictionary of [Text, Cookie]) + begin + this.Cookies := Cookies; + end; + + procedure GetCookies() Cookies: Dictionary of [Text, Cookie] + begin + Cookies := this.Cookies; + end; + + procedure GetCookieNames() CookieNames: List of [Text] + begin + CookieNames := this.Cookies.Keys; + end; + + procedure GetCookie(Name: Text) Cookie: Cookie + begin + if this.Cookies.Get(Name, Cookie) then; + end; + + procedure GetCookie(Name: Text; var Cookie: Cookie) Success: Boolean + begin + Success := this.Cookies.Get(Name, Cookie); end; #endregion #region ErrorMessage var ErrorMessage: Text; + Exception: ErrorInfo; procedure SetErrorMessage(Value: Text) begin - ErrorMessage := Value; + this.ErrorMessage := Value; end; - procedure GetErrorMessage() ReturnValue: Text + procedure GetErrorMessage(): Text begin - if ErrorMessage <> '' then - ReturnValue := ErrorMessage - else - ReturnValue := GetLastErrorText(); + if this.Exception.Message <> '' then + exit(this.Exception.Message); + + if this.ErrorMessage <> '' then + exit(this.ErrorMessage); + + exit(GetLastErrorText()); end; + + procedure SetException(Exception: ErrorInfo) + begin + this.Exception := Exception; + end; + + procedure GetException() Exception: ErrorInfo + var + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + begin + if this.Exception.Message = '' then + Exception := RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::UnknownException, GetErrorMessage()) + end; + + procedure GetExceptionCode() ReturnValue: Enum "Rest Client Exception" + var + IntValue: Integer; + Execption: ErrorInfo; + begin + Exception := this.GetException(); + Evaluate(IntValue, Exception.CustomDimensions.Get('ExceptionCode')); + ReturnValue := Enum::"Rest Client Exception".FromInteger(IntValue); + end; + + procedure GetExceptionName() ReturnValue: Text + var + Exception: ErrorInfo; + begin + Exception := this.GetException(); + ReturnValue := Exception.CustomDimensions.Get('ExceptionName'); + end; + #endregion } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/RestClient.Codeunit.al b/src/System Application/App/Rest Client/src/RestClient.Codeunit.al index fcc243cd50..96efebe128 100644 --- a/src/System Application/App/Rest Client/src/RestClient.Codeunit.al +++ b/src/System Application/App/Rest Client/src/RestClient.Codeunit.al @@ -13,6 +13,47 @@ codeunit 2350 "Rest Client" var RestClientImpl: Codeunit "Rest Client Impl."; + #region Constructors + /// Initializes a new instance of the Rest Client class. + /// The Rest Client object. + /// The default Http Client Handler and anonymous Http authentication will be used. + procedure Create(): Codeunit "Rest Client" + begin + RestClientImpl := RestClientImpl.Create(); + exit(this); + end; + + /// Initializes a new instance of the Rest Client class. + /// The Http Client Handler to use. + /// The Rest Client object. + /// The anynomous Http Authentication will be used. + procedure Create(HttpClientHandler: Interface "Http Client Handler"): Codeunit "Rest Client" + begin + RestClientImpl := RestClientImpl.Create(HttpClientHandler); + exit(this); + end; + + /// Initializes a new instance of the Rest Client class. + /// The authentication to use. + /// The Rest Client object. + /// The default Http Client Handler will be used. + procedure Create(HttpAuthentication: Interface "Http Authentication"): Codeunit "Rest Client" + begin + RestClientImpl := RestClientImpl.Create(HttpAuthentication); + exit(this); + end; + + /// Initializes a new instance of the Rest Client class. + /// The Http Client Handler to use. + /// The authentication to use. + /// The Rest Client object. + procedure Create(HttpClientHandler: Interface "Http Client Handler"; HttpAuthentication: Interface "Http Authentication"): Codeunit "Rest Client" + begin + RestClientImpl := RestClientImpl.Create(HttpClientHandler, HttpAuthentication); + exit(this); + end; + #endregion + #region Initialization /// Initializes the Rest Client with the default Http Client Handler and anonymous Http authentication. procedure Initialize() @@ -135,6 +176,15 @@ codeunit 2350 "Rest Client" begin RestClientImpl.SetAuthorizationHeader(Value); end; + + /// Sets the use of response cookies in subsequent requests. + /// Use this function to enable or disable automatically attach cookies received in the response to all subsequent requests. + /// The Rest Client will be initialized if it was not initialized before. + /// If true, the client automatically attaches cookies received in the response to all subsequent requests. False to disable + procedure SetUseResponseCookies(Value: Boolean) + begin + RestClientImpl.SetUseResponseCookies(Value); + end; #endregion #region BasicMethods @@ -147,6 +197,17 @@ codeunit 2350 "Rest Client" HttpResponseMessage := Send(Enum::"Http Method"::GET, RequestUri); end; + /// Sends a GET request to the specified Uri and returns the response message. + /// The function returns true if a response was received, otherwise it returns false. + /// If a response was received, then the response message object contains information about the HTTP status. + /// The Uri the request is sent to. + /// The response message object + /// True if a response was received, otherwise false + procedure Get(RequestUri: Text; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean + begin + Success := Send(Enum::"Http Method"::GET, RequestUri, HttpResponseMessage); + end; + /// Sends a POST request to the specified Uri and returns the response message. /// The function fails with an error message if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. @@ -158,6 +219,18 @@ codeunit 2350 "Rest Client" HttpResponseMessage := Send(Enum::"Http Method"::POST, RequestUri, Content); end; + /// Sends a POST request to the specified Uri and returns the response message. + /// The function returns true if a response was received, otherwise it returns false. + /// If a response was received, then the response message object contains information about the HTTP status. + /// The Uri the request is sent to. + /// The content to send. + /// The response message object + /// True if a response was received, otherwise false + procedure Post(RequestUri: Text; Content: Codeunit "Http Content"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean + begin + Success := Send(Enum::"Http Method"::POST, RequestUri, Content, HttpResponseMessage); + end; + /// Sends a PATCH request to the specified Uri and returns the response message. /// The function fails with an error message if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. @@ -169,6 +242,18 @@ codeunit 2350 "Rest Client" HttpResponseMessage := Send(Enum::"Http Method"::PATCH, RequestUri, Content); end; + /// Sends a PATCH request to the specified Uri and returns the response message. + /// The function returns true if a response was received, otherwise it returns false. + /// If a response was received, then the response message object contains information about the HTTP status. + /// The Uri the request is sent to. + /// The content to send. + /// The response message object + /// True if a response was received, otherwise false + procedure Patch(RequestUri: Text; Content: Codeunit "Http Content"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean + begin + Success := Send(Enum::"Http Method"::PATCH, RequestUri, Content, HttpResponseMessage); + end; + /// Sends a PUT request to the specified Uri and returns the response message. /// The function fails with an error message if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. @@ -180,24 +265,58 @@ codeunit 2350 "Rest Client" HttpResponseMessage := Send(Enum::"Http Method"::PUT, RequestUri, Content); end; + /// Sends a PUT request to the specified Uri and returns the response message. + /// The function returns true if a response was received, otherwise it returns false. + /// If a response was received, then the response message object contains information about the HTTP status. + /// The Uri the request is sent to. + /// The content to send. + /// The response message object + /// True if a response was received, otherwise false + procedure Put(RequestUri: Text; Content: Codeunit "Http Content"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean + begin + Success := Send(Enum::"Http Method"::PUT, RequestUri, Content, HttpResponseMessage); + end; + /// Sends a DELETE request to the specified Uri and returns the response message. /// The function fails with an error message if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. /// The Uri the request is sent to. /// The response message object - procedure Delete(RequestUri: Text) HttpResponseMessage: Codeunit "Http Response Message"; + procedure Delete(RequestUri: Text) HttpResponseMessage: Codeunit "Http Response Message" begin HttpResponseMessage := Send(Enum::"Http Method"::DELETE, RequestUri); end; + /// Sends a DELETE request to the specified Uri and returns the response message. + /// The function returns true if a response was received, otherwise it returns false. + /// If a response was received, then the response message object contains information about the HTTP status. + /// The Uri the request is sent to. + /// The response message object + /// True if a response was received, otherwise false + procedure Delete(RequestUri: Text; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean + begin + Success := Send(Enum::"Http Method"::DELETE, RequestUri, HttpResponseMessage); + end; #endregion + [ErrorBehavior(ErrorBehavior::Collect)] + procedure demo() + var + Value: Text; + JsonToken: JsonToken; + begin + JsonToken := GetAsJson(''); + if HasCollectedErrors() then begin + + end; + end; + #region BasicMethodsAsJson /// Sends a GET request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. /// The function also fails in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. - /// In case the response contains content, then the function fails if the content is invalid JSON. + /// In case the response contains content, then the function fails with a collectible error if the content is invalid JSON. /// The Uri the request is sent to. /// The response content as JsonToken procedure GetAsJson(RequestUri: Text) JsonToken: JsonToken @@ -205,6 +324,19 @@ codeunit 2350 "Rest Client" JsonToken := RestClientImpl.GetAsJson(RequestUri); end; + /// Sends a GET request to the specified Uri and returns the response content as JsonToken. + /// The function returns true if a response was received, otherwise it returns false. + /// If a response was received, then the response content is returned as JsonToken. + /// In case the response contains no content, an empty JsonToken is returned. + /// In case the response contains content, then the function fails with a collectible errorif the content is invalid JSON. + /// The Uri the request is sent to. + /// The response content as JsonToken + /// True if a response was received, otherwise false + procedure GetAsJson(RequestUri: Text; var JsonToken: JsonToken) Success: Boolean + begin + Success := RestClientImpl.GetAsJson(RequestUri, JsonToken); + end; + /// Sends a POST request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. /// The function also fails in case the response does not contain a success status code. @@ -355,5 +487,27 @@ codeunit 2350 "Rest Client" begin HttpResponseMessage := RestClientImpl.Send(HttpRequestMessage); end; + + /// Sends a request with the specific Http method and an empty content to the specified Uri and returns the response message. + /// The function returns true if a response was received, otherwise it returns false. + /// If a response was received, then the response message object contains information about the HTTP status. + /// The HTTP method to use. + /// The Uri the request is sent to. + /// The response message object + /// True if a response was received, otherwise false + procedure Send(Method: Enum "Http Method"; RequestUri: Text; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean + begin + Success := RestClientImpl.Send(Method, RequestUri, HttpResponseMessage); + end; + + procedure Send(Method: Enum "Http Method"; RequestUri: Text; Content: Codeunit "Http Content"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean + begin + Success := RestClientImpl.Send(Method, RequestUri, Content, HttpResponseMessage); + end; + + procedure Send(var HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean + begin + Success := RestClientImpl.Send(HttpRequestMessage, HttpResponseMessage); + end; #endregion } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al b/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al index c2c30f9498..48584ea1b8 100644 --- a/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al @@ -4,6 +4,8 @@ // ------------------------------------------------------------------------------------------------ namespace System.RestClient; +using System.RestClient; + codeunit 2351 "Rest Client Impl." { Access = Internal; @@ -13,14 +15,43 @@ codeunit 2351 "Rest Client Impl." var DefaultHttpClientHandler: Codeunit "Http Client Handler"; HttpAuthenticationAnonymous: Codeunit "Http Authentication Anonymous"; + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; HttpAuthentication: Interface "Http Authentication"; HttpClientHandler: Interface "Http Client Handler"; HttpClient: HttpClient; IsInitialized: Boolean; + BlockedByEnvironmentErrorTok: Label 'BlockedByEnvironmentError', Locked = true; EnvironmentBlocksErr: Label 'Environment blocks an outgoing HTTP request to ''%1''.', Comment = '%1 = url, e.g. https://microsoft.com'; + ConnectionErrorTok: Label 'NoConnectionError', Locked = true; ConnectionErr: Label 'Connection to the remote service ''%1'' could not be established.', Comment = '%1 = url, e.g. https://microsoft.com'; + RequestFailedErrorTok: Label 'RequestFailedError', Locked = true; RequestFailedErr: Label 'The request failed: %1 %2', Comment = '%1 = HTTP status code, %2 = Reason phrase'; UserAgentLbl: Label 'Dynamics 365 Business Central - |%1| %2/%3', Locked = true, Comment = '%1 = App Publisher; %2 = App Name; %3 = App Version'; + TimeoutOutOfRangeErr: Label 'The timeout value must be greater than 0.'; + + #region Constructors + procedure Create() RestClientImpl: Codeunit "Rest Client Impl." + begin + RestClientImpl := RestClientImpl.Create(DefaultHttpClientHandler, HttpAuthenticationAnonymous); + end; + + procedure Create(HttpClientHandler: Interface "Http Client Handler") RestClientImpl: Codeunit "Rest Client Impl." + begin + RestClientImpl := RestClientImpl.Create(HttpClientHandler, HttpAuthenticationAnonymous); + end; + + procedure Create(HttpAuthentication: Interface "Http Authentication") RestClientImpl: Codeunit "Rest Client Impl." + begin + RestClientImpl := RestClientImpl.Create(DefaultHttpClientHandler, HttpAuthentication); + end; + + procedure Create(HttpClientHandler: Interface "Http Client Handler"; HttpAuthentication: Interface "Http Authentication"): Codeunit "Rest Client Impl." + begin + Initialize(HttpClientHandler, HttpAuthentication); + exit(this); + end; + + #endregion #region Initialization procedure Initialize() @@ -82,6 +113,8 @@ codeunit 2351 "Rest Client Impl." procedure SetTimeOut(TimeOut: Duration) begin CheckInitialized(); + if TimeOut <= 0 then + Error(TimeoutOutOfRangeErr); HttpClient.Timeout := TimeOut; end; @@ -112,6 +145,12 @@ codeunit 2351 "Rest Client Impl." begin SetDefaultRequestHeader('User-Agent', Value); end; + + procedure SetUseResponseCookies(Value: Boolean) + begin + CheckInitialized(); + HttpClient.UseResponseCookies(Value); + end; #endregion @@ -121,12 +160,31 @@ codeunit 2351 "Rest Client Impl." HttpResponseMessage: Codeunit "Http Response Message"; begin HttpResponseMessage := Send(Enum::"Http Method"::GET, RequestUri); - if not HttpResponseMessage.GetIsSuccessStatusCode() then - Error(HttpResponseMessage.GetErrorMessage()); + if not HttpResponseMessage.GetIsSuccessStatusCode() then begin + Error(HttpResponseMessage.GetException()); + end; JsonToken := HttpResponseMessage.GetContent().AsJson(); end; + procedure GetAsJson(RequestUri: Text; var JsonToken: JsonToken) Success: Boolean + var + HttpResponseMessage: Codeunit "Http Response Message"; + begin + Clear(JsonToken); + Success := Send(Enum::"Http Method"::GET, RequestUri, HttpResponseMessage); + if not Success then begin + exit; + end; + + if Success and HttpResponseMessage.GetIsSuccessStatusCode() then + JsonToken := HttpResponseMessage.GetContent().AsJson() + else + Error(ErrorInfo.Create(HttpResponseMessage.GetErrorMessage(), true)); + + Success := not HasCollectedErrors(); + end; + procedure PostAsJson(RequestUri: Text; Content: JsonToken) Response: JsonToken var HttpResponseMessage: Codeunit "Http Response Message"; @@ -179,15 +237,7 @@ codeunit 2351 "Rest Client Impl." var HttpRequestMessage: Codeunit "Http Request Message"; begin - CheckInitialized(); - - HttpRequestMessage.SetHttpMethod(Method); - if RequestUri.StartsWith('http://') or RequestUri.StartsWith('https://') then - HttpRequestMessage.SetRequestUri(RequestUri) - else - HttpRequestMessage.SetRequestUri(GetBaseAddress() + RequestUri); - HttpRequestMessage.SetContent(Content); - + HttpRequestMessage := CreateHttpRequestMessage(Method, RequestUri, Content); HttpResponseMessage := Send(HttpRequestMessage); end; @@ -198,6 +248,28 @@ codeunit 2351 "Rest Client Impl." if not SendRequest(HttpRequestMessage, HttpResponseMessage) then Error(HttpResponseMessage.GetErrorMessage()); end; + + procedure Send(Method: Enum "Http Method"; RequestUri: Text; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean + var + EmptyHttpContent: Codeunit "Http Content"; + begin + Success := Send(Method, RequestUri, EmptyHttpContent, HttpResponseMessage); + end; + + procedure Send(Method: Enum "Http Method"; RequestUri: Text; Content: Codeunit "Http Content"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean + var + HttpRequestMessage: Codeunit "Http Request Message"; + begin + HttpRequestMessage := CreateHttpRequestMessage(Method, RequestUri, Content); + Success := Send(HttpRequestMessage, HttpResponseMessage); + end; + + procedure Send(var HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean + begin + CheckInitialized(); + Success := SendRequest(HttpRequestMessage, HttpResponseMessage); + end; + #endregion #region Local Methods @@ -218,9 +290,14 @@ codeunit 2351 "Rest Client Impl." SetUserAgentHeader(UserAgentString); end; + local procedure CreateHttpRequestMessage(Method: Enum "Http Method"; RequestUri: Text; Content: Codeunit "Http Content") HttpRequestMessage: Codeunit "Http Request Message" + begin + if not (RequestUri.StartsWith('http://') or RequestUri.StartsWith('https://')) then + RequestUri := GetBaseAddress() + RequestUri; + HttpRequestMessage := HttpRequestMessage.Create(Method, RequestUri, Content); + end; + local procedure SendRequest(var HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message"): Boolean - var - ErrorMessage: Text; begin Clear(HttpResponseMessage); @@ -229,16 +306,20 @@ codeunit 2351 "Rest Client Impl." if not HttpClientHandler.Send(HttpClient, HttpRequestMessage, HttpResponseMessage) then begin if HttpResponseMessage.GetIsBlockedByEnvironment() then - ErrorMessage := StrSubstNo(EnvironmentBlocksErr, HttpRequestMessage.GetRequestUri()) + HttpResponseMessage.SetException( + RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::BlockedByEnvironment, + StrSubstNo(EnvironmentBlocksErr, HttpRequestMessage.GetRequestUri()))) else - ErrorMessage := StrSubstNo(ConnectionErr, HttpRequestMessage.GetRequestUri()); + HttpResponseMessage.SetException( + RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::ConnectionFailed, + StrSubstNo(ConnectionErr, HttpRequestMessage.GetRequestUri()))); exit(false); end; - if not HttpResponseMessage.GetIsSuccessStatusCode() then begin - ErrorMessage := StrSubstNo(RequestFailedErr, HttpResponseMessage.GetHttpStatusCode(), HttpResponseMessage.GetReasonPhrase()); - HttpResponseMessage.SetErrorMessage(ErrorMessage); - end; + if not HttpResponseMessage.GetIsSuccessStatusCode() then + HttpResponseMessage.SetException( + RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::RequestFailed, + StrSubstNo(RequestFailedErr, HttpResponseMessage.GetHttpStatusCode(), HttpResponseMessage.GetReasonPhrase()))); exit(true); end; From 43596fe7a0f6db6ffe0427f1ff1251df1bbd3a56 Mon Sep 17 00:00:00 2001 From: Arend-Jan Kauffmann Date: Wed, 13 Nov 2024 21:42:02 +0100 Subject: [PATCH 02/12] Refactor REST client for clearer error handling Replaced multiple overloads of HTTP request methods with simplified versions, removing boolean success indicators. Enhanced error handling by adopting collectible errors, and streamlined method implementations. Emphasized cleaner error descriptions in unsuccessful responses to improve debugging and user experience. Adjustments potentially improve maintainability and error traceability. --- .../Rest Client/src/RestClient.Codeunit.al | 143 +++--------------- .../src/RestClientImpl.Codeunit.al | 93 +++++------- 2 files changed, 55 insertions(+), 181 deletions(-) diff --git a/src/System Application/App/Rest Client/src/RestClient.Codeunit.al b/src/System Application/App/Rest Client/src/RestClient.Codeunit.al index 96efebe128..9882413460 100644 --- a/src/System Application/App/Rest Client/src/RestClient.Codeunit.al +++ b/src/System Application/App/Rest Client/src/RestClient.Codeunit.al @@ -189,7 +189,8 @@ codeunit 2350 "Rest Client" #region BasicMethods /// Sends a GET request to the specified Uri and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. + /// If a response was received, then the response message object contains information about the status. /// The Uri the request is sent to. /// The response message object procedure Get(RequestUri: Text) HttpResponseMessage: Codeunit "Http Response Message" @@ -197,19 +198,8 @@ codeunit 2350 "Rest Client" HttpResponseMessage := Send(Enum::"Http Method"::GET, RequestUri); end; - /// Sends a GET request to the specified Uri and returns the response message. - /// The function returns true if a response was received, otherwise it returns false. - /// If a response was received, then the response message object contains information about the HTTP status. - /// The Uri the request is sent to. - /// The response message object - /// True if a response was received, otherwise false - procedure Get(RequestUri: Text; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean - begin - Success := Send(Enum::"Http Method"::GET, RequestUri, HttpResponseMessage); - end; - /// Sends a POST request to the specified Uri and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. /// The Uri the request is sent to. /// The content to send. @@ -219,20 +209,8 @@ codeunit 2350 "Rest Client" HttpResponseMessage := Send(Enum::"Http Method"::POST, RequestUri, Content); end; - /// Sends a POST request to the specified Uri and returns the response message. - /// The function returns true if a response was received, otherwise it returns false. - /// If a response was received, then the response message object contains information about the HTTP status. - /// The Uri the request is sent to. - /// The content to send. - /// The response message object - /// True if a response was received, otherwise false - procedure Post(RequestUri: Text; Content: Codeunit "Http Content"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean - begin - Success := Send(Enum::"Http Method"::POST, RequestUri, Content, HttpResponseMessage); - end; - /// Sends a PATCH request to the specified Uri and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. /// The Uri the request is sent to. /// The content to send. @@ -242,20 +220,8 @@ codeunit 2350 "Rest Client" HttpResponseMessage := Send(Enum::"Http Method"::PATCH, RequestUri, Content); end; - /// Sends a PATCH request to the specified Uri and returns the response message. - /// The function returns true if a response was received, otherwise it returns false. - /// If a response was received, then the response message object contains information about the HTTP status. - /// The Uri the request is sent to. - /// The content to send. - /// The response message object - /// True if a response was received, otherwise false - procedure Patch(RequestUri: Text; Content: Codeunit "Http Content"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean - begin - Success := Send(Enum::"Http Method"::PATCH, RequestUri, Content, HttpResponseMessage); - end; - /// Sends a PUT request to the specified Uri and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. /// The Uri the request is sent to. /// The content to send. @@ -265,20 +231,8 @@ codeunit 2350 "Rest Client" HttpResponseMessage := Send(Enum::"Http Method"::PUT, RequestUri, Content); end; - /// Sends a PUT request to the specified Uri and returns the response message. - /// The function returns true if a response was received, otherwise it returns false. - /// If a response was received, then the response message object contains information about the HTTP status. - /// The Uri the request is sent to. - /// The content to send. - /// The response message object - /// True if a response was received, otherwise false - procedure Put(RequestUri: Text; Content: Codeunit "Http Content"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean - begin - Success := Send(Enum::"Http Method"::PUT, RequestUri, Content, HttpResponseMessage); - end; - /// Sends a DELETE request to the specified Uri and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. /// The Uri the request is sent to. /// The response message object @@ -286,35 +240,12 @@ codeunit 2350 "Rest Client" begin HttpResponseMessage := Send(Enum::"Http Method"::DELETE, RequestUri); end; - - /// Sends a DELETE request to the specified Uri and returns the response message. - /// The function returns true if a response was received, otherwise it returns false. - /// If a response was received, then the response message object contains information about the HTTP status. - /// The Uri the request is sent to. - /// The response message object - /// True if a response was received, otherwise false - procedure Delete(RequestUri: Text; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean - begin - Success := Send(Enum::"Http Method"::DELETE, RequestUri, HttpResponseMessage); - end; #endregion - [ErrorBehavior(ErrorBehavior::Collect)] - procedure demo() - var - Value: Text; - JsonToken: JsonToken; - begin - JsonToken := GetAsJson(''); - if HasCollectedErrors() then begin - - end; - end; - #region BasicMethodsAsJson /// Sends a GET request to the specified Uri and returns the response content as JsonToken. - /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function fails with a collectible error if the request could not be sent or a response was not received. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails with a collectible error if the content is invalid JSON. /// The Uri the request is sent to. @@ -324,22 +255,9 @@ codeunit 2350 "Rest Client" JsonToken := RestClientImpl.GetAsJson(RequestUri); end; - /// Sends a GET request to the specified Uri and returns the response content as JsonToken. - /// The function returns true if a response was received, otherwise it returns false. - /// If a response was received, then the response content is returned as JsonToken. - /// In case the response contains no content, an empty JsonToken is returned. - /// In case the response contains content, then the function fails with a collectible errorif the content is invalid JSON. - /// The Uri the request is sent to. - /// The response content as JsonToken - /// True if a response was received, otherwise false - procedure GetAsJson(RequestUri: Text; var JsonToken: JsonToken) Success: Boolean - begin - Success := RestClientImpl.GetAsJson(RequestUri, JsonToken); - end; - /// Sends a POST request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -352,7 +270,7 @@ codeunit 2350 "Rest Client" /// Sends a POST request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -365,7 +283,7 @@ codeunit 2350 "Rest Client" /// Sends a POST request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -378,7 +296,7 @@ codeunit 2350 "Rest Client" /// Sends a PATCH request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -391,7 +309,7 @@ codeunit 2350 "Rest Client" /// Sends a PATCH request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -404,7 +322,7 @@ codeunit 2350 "Rest Client" /// Sends a PATCH request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -417,7 +335,7 @@ codeunit 2350 "Rest Client" /// Sends a PUT request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -430,7 +348,7 @@ codeunit 2350 "Rest Client" /// Sends a PUT request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -443,7 +361,7 @@ codeunit 2350 "Rest Client" /// Sends a PUT request to the specified Uri and returns the response content as JsonToken. /// The function fails with an error message if the request could not be sent or a response was not received. - /// The function also fails in case the response does not contain a success status code. + /// The function also fails with a collectible error in case the response does not contain a success status code. /// In case the response contains no content, an empty JsonToken is returned. /// In case the response contains content, then the function fails if the content is invalid JSON. /// The Uri the request is sent to. @@ -457,7 +375,7 @@ codeunit 2350 "Rest Client" #region GenericSendMethods /// Sends a request with the specific Http method and an empty content to the specified Uri and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. /// The HTTP method to use. /// The Uri the request is sent to. @@ -468,7 +386,7 @@ codeunit 2350 "Rest Client" end; /// Sends a request with the specific Http method and the given content to the specified Uri and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. /// If a response was received, then the response message object contains information about the status. /// The HTTP method to use. /// The Uri the request is sent to. @@ -480,7 +398,7 @@ codeunit 2350 "Rest Client" end; /// Sends the given request message and returns the response message. - /// The function fails with an error message if the request could not be sent or a response was not received. + /// The function fails with a collectible error if the request could not be sent or a response was not received. /// The request message to send. /// The response message object procedure Send(var HttpRequestMessage: Codeunit "Http Request Message") HttpResponseMessage: Codeunit "Http Response Message" @@ -488,26 +406,5 @@ codeunit 2350 "Rest Client" HttpResponseMessage := RestClientImpl.Send(HttpRequestMessage); end; - /// Sends a request with the specific Http method and an empty content to the specified Uri and returns the response message. - /// The function returns true if a response was received, otherwise it returns false. - /// If a response was received, then the response message object contains information about the HTTP status. - /// The HTTP method to use. - /// The Uri the request is sent to. - /// The response message object - /// True if a response was received, otherwise false - procedure Send(Method: Enum "Http Method"; RequestUri: Text; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean - begin - Success := RestClientImpl.Send(Method, RequestUri, HttpResponseMessage); - end; - - procedure Send(Method: Enum "Http Method"; RequestUri: Text; Content: Codeunit "Http Content"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean - begin - Success := RestClientImpl.Send(Method, RequestUri, Content, HttpResponseMessage); - end; - - procedure Send(var HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean - begin - Success := RestClientImpl.Send(HttpRequestMessage, HttpResponseMessage); - end; #endregion } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al b/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al index 48584ea1b8..e06cd5c45e 100644 --- a/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al @@ -18,6 +18,7 @@ codeunit 2351 "Rest Client Impl." RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; HttpAuthentication: Interface "Http Authentication"; HttpClientHandler: Interface "Http Client Handler"; + HttpResponseMessage: Codeunit "Http Response Message"; HttpClient: HttpClient; IsInitialized: Boolean; BlockedByEnvironmentErrorTok: Label 'BlockedByEnvironmentError', Locked = true; @@ -59,7 +60,6 @@ codeunit 2351 "Rest Client Impl." Initialize(DefaultHttpClientHandler, HttpAuthenticationAnonymous); end; -#pragma warning disable AA0244 procedure Initialize(HttpClientHandler: Interface "Http Client Handler") begin Initialize(HttpClientHandler, HttpAuthenticationAnonymous); @@ -69,7 +69,6 @@ codeunit 2351 "Rest Client Impl." begin Initialize(DefaultHttpClientHandler, HttpAuthentication); end; -#pragma warning restore AA0244 procedure Initialize(HttpClientHandlerInstance: Interface "Http Client Handler"; HttpAuthenticationInstance: Interface "Http Authentication") begin @@ -153,36 +152,21 @@ codeunit 2351 "Rest Client Impl." end; #endregion - #region BasicMethodsAsJson procedure GetAsJson(RequestUri: Text) JsonToken: JsonToken var HttpResponseMessage: Codeunit "Http Response Message"; begin HttpResponseMessage := Send(Enum::"Http Method"::GET, RequestUri); + if HasCollectedErrors() then + exit; + if not HttpResponseMessage.GetIsSuccessStatusCode() then begin Error(HttpResponseMessage.GetException()); - end; - - JsonToken := HttpResponseMessage.GetContent().AsJson(); - end; - - procedure GetAsJson(RequestUri: Text; var JsonToken: JsonToken) Success: Boolean - var - HttpResponseMessage: Codeunit "Http Response Message"; - begin - Clear(JsonToken); - Success := Send(Enum::"Http Method"::GET, RequestUri, HttpResponseMessage); - if not Success then begin exit; end; - if Success and HttpResponseMessage.GetIsSuccessStatusCode() then - JsonToken := HttpResponseMessage.GetContent().AsJson() - else - Error(ErrorInfo.Create(HttpResponseMessage.GetErrorMessage(), true)); - - Success := not HasCollectedErrors(); + JsonToken := HttpResponseMessage.GetContent().AsJson(); end; procedure PostAsJson(RequestUri: Text; Content: JsonToken) Response: JsonToken @@ -191,9 +175,13 @@ codeunit 2351 "Rest Client Impl." HttpContent: Codeunit "Http Content"; begin HttpResponseMessage := Send(Enum::"Http Method"::POST, RequestUri, HttpContent.Create(Content)); + if HasCollectedErrors() then + exit; - if not HttpResponseMessage.GetIsSuccessStatusCode() then - Error(HttpResponseMessage.GetErrorMessage()); + if not HttpResponseMessage.GetIsSuccessStatusCode() then begin + Error(HttpResponseMessage.GetException()); + exit; + end; Response := HttpResponseMessage.GetContent().AsJson(); end; @@ -204,9 +192,13 @@ codeunit 2351 "Rest Client Impl." HttpContent: Codeunit "Http Content"; begin HttpResponseMessage := Send(Enum::"Http Method"::PATCH, RequestUri, HttpContent.Create(Content)); + if HasCollectedErrors() then + exit; - if not HttpResponseMessage.GetIsSuccessStatusCode() then - Error(HttpResponseMessage.GetErrorMessage()); + if not HttpResponseMessage.GetIsSuccessStatusCode() then begin + Error(HttpResponseMessage.GetException()); + exit; + end; Response := HttpResponseMessage.GetContent().AsJson(); end; @@ -217,9 +209,13 @@ codeunit 2351 "Rest Client Impl." HttpContent: Codeunit "Http Content"; begin HttpResponseMessage := Send(Enum::"Http Method"::PUT, RequestUri, HttpContent.Create(Content)); + if HasCollectedErrors() then + exit; - if not HttpResponseMessage.GetIsSuccessStatusCode() then - Error(HttpResponseMessage.GetErrorMessage()); + if not HttpResponseMessage.GetIsSuccessStatusCode() then begin + Error(HttpResponseMessage.GetException()); + exit; + end; Response := HttpResponseMessage.GetContent().AsJson(); end; @@ -246,28 +242,7 @@ codeunit 2351 "Rest Client Impl." CheckInitialized(); if not SendRequest(HttpRequestMessage, HttpResponseMessage) then - Error(HttpResponseMessage.GetErrorMessage()); - end; - - procedure Send(Method: Enum "Http Method"; RequestUri: Text; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean - var - EmptyHttpContent: Codeunit "Http Content"; - begin - Success := Send(Method, RequestUri, EmptyHttpContent, HttpResponseMessage); - end; - - procedure Send(Method: Enum "Http Method"; RequestUri: Text; Content: Codeunit "Http Content"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean - var - HttpRequestMessage: Codeunit "Http Request Message"; - begin - HttpRequestMessage := CreateHttpRequestMessage(Method, RequestUri, Content); - Success := Send(HttpRequestMessage, HttpResponseMessage); - end; - - procedure Send(var HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean - begin - CheckInitialized(); - Success := SendRequest(HttpRequestMessage, HttpResponseMessage); + Error(HttpResponseMessage.GetException()); end; #endregion @@ -299,28 +274,30 @@ codeunit 2351 "Rest Client Impl." local procedure SendRequest(var HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message"): Boolean begin - Clear(HttpResponseMessage); + Clear(this.HttpResponseMessage); if HttpAuthentication.IsAuthenticationRequired() then Authorize(HttpRequestMessage); - if not HttpClientHandler.Send(HttpClient, HttpRequestMessage, HttpResponseMessage) then begin - if HttpResponseMessage.GetIsBlockedByEnvironment() then - HttpResponseMessage.SetException( + if not HttpClientHandler.Send(HttpClient, HttpRequestMessage, this.HttpResponseMessage) then begin + if this.HttpResponseMessage.GetIsBlockedByEnvironment() then + this.HttpResponseMessage.SetException( RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::BlockedByEnvironment, StrSubstNo(EnvironmentBlocksErr, HttpRequestMessage.GetRequestUri()))) else - HttpResponseMessage.SetException( - RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::ConnectionFailed, + this.HttpResponseMessage.SetException( + RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::ConnectionFailed, StrSubstNo(ConnectionErr, HttpRequestMessage.GetRequestUri()))); + HttpResponseMessage := this.HttpResponseMessage; exit(false); end; - if not HttpResponseMessage.GetIsSuccessStatusCode() then - HttpResponseMessage.SetException( - RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::RequestFailed, + if not this.HttpResponseMessage.GetIsSuccessStatusCode() then + this.HttpResponseMessage.SetException( + RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::RequestFailed, StrSubstNo(RequestFailedErr, HttpResponseMessage.GetHttpStatusCode(), HttpResponseMessage.GetReasonPhrase()))); + HttpResponseMessage := this.HttpResponseMessage; exit(true); end; From 773416c3724ceab33384406bdc36774e603a758b Mon Sep 17 00:00:00 2001 From: Arend-Jan Kauffmann Date: Tue, 26 Nov 2024 22:58:00 +0100 Subject: [PATCH 03/12] Refactor HttpResponseMessage and HttpContent implementations for improved clarity and consistency --- .../src/HttpContentImpl.Codeunit.al | 47 ++++---- .../src/HttpResponseMessageImpl.Codeunit.al | 34 +++--- .../src/RestClientImpl.Codeunit.al | 113 +++++++++--------- 3 files changed, 94 insertions(+), 100 deletions(-) diff --git a/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al b/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al index 670a65b3fe..4f84260ce7 100644 --- a/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al @@ -4,7 +4,6 @@ // ------------------------------------------------------------------------------------------------ namespace System.RestClient; -using System.RestClient; using System.Utilities; codeunit 2355 "Http Content Impl." @@ -99,10 +98,10 @@ codeunit 2355 "Http Content Impl." exit(this); end; - procedure Create(HttpContent: HttpContent): Codeunit "Http Content Impl." + procedure Create(Content: HttpContent): Codeunit "Http Content Impl." begin ClearAll(); - this.SetContent(HttpContent); + SetContent(Content); exit(this); end; #endregion @@ -118,9 +117,9 @@ codeunit 2355 "Http Content Impl." var Headers: HttpHeaders; begin - if not HttpContent.GetHeaders(Headers) then begin - HttpContent.Clear(); - HttpContent.GetHeaders(Headers); + if not this.HttpContent.GetHeaders(Headers) then begin + this.HttpContent.Clear(); + this.HttpContent.GetHeaders(Headers); end; Headers.Add('Content-Encoding', ContentEncoding); end; @@ -129,9 +128,9 @@ codeunit 2355 "Http Content Impl." var Headers: HttpHeaders; begin - if not HttpContent.GetHeaders(Headers) then begin - HttpContent.Clear(); - HttpContent.GetHeaders(Headers); + if not this.HttpContent.GetHeaders(Headers) then begin + this.HttpContent.Clear(); + this.HttpContent.GetHeaders(Headers); end; if Headers.Contains(Name) or Headers.ContainsSecret(Name) then @@ -144,9 +143,9 @@ codeunit 2355 "Http Content Impl." var Headers: HttpHeaders; begin - if not HttpContent.GetHeaders(Headers) then begin - HttpContent.Clear(); - HttpContent.GetHeaders(Headers); + if not this.HttpContent.GetHeaders(Headers) then begin + this.HttpContent.Clear(); + this.HttpContent.GetHeaders(Headers); end; if Headers.Contains(Name) or Headers.ContainsSecret(Name) then @@ -157,17 +156,17 @@ codeunit 2355 "Http Content Impl." procedure GetHttpContent() ReturnValue: HttpContent begin - ReturnValue := HttpContent; + ReturnValue := this.HttpContent; end; procedure AsText() ReturnValue: Text begin - HttpContent.ReadAs(ReturnValue); + this.HttpContent.ReadAs(ReturnValue); end; procedure AsSecretText() ReturnValue: SecretText begin - HttpContent.ReadAs(ReturnValue); + this.HttpContent.ReadAs(ReturnValue); end; procedure AsBlob() ReturnValue: Codeunit "Temp Blob" @@ -175,14 +174,14 @@ codeunit 2355 "Http Content Impl." InStr: InStream; OutStr: OutStream; begin - HttpContent.ReadAs(InStr); + this.HttpContent.ReadAs(InStr); ReturnValue.CreateOutStream(OutStr); CopyStream(OutStr, InStr); end; procedure AsInStream(var InStr: InStream) begin - HttpContent.ReadAs(InStr); + this.HttpContent.ReadAs(InStr); end; procedure AsXmlDocument() ReturnValue: XmlDocument @@ -206,8 +205,8 @@ codeunit 2355 "Http Content Impl." procedure SetContent(Content: Text; ContentType: Text) begin - HttpContent.Clear(); - HttpContent.WriteFrom(Content); + this.HttpContent.Clear(); + this.HttpContent.WriteFrom(Content); if ContentType = '' then ContentType := MimeTypeTextPlainTxt; SetContentTypeHeader(ContentType); @@ -215,8 +214,8 @@ codeunit 2355 "Http Content Impl." procedure SetContent(Content: SecretText; ContentType: Text) begin - HttpContent.Clear(); - HttpContent.WriteFrom(Content); + this.HttpContent.Clear(); + this.HttpContent.WriteFrom(Content); if ContentType = '' then ContentType := MimeTypeTextPlainTxt; SetContentTypeHeader(ContentType); @@ -224,8 +223,8 @@ codeunit 2355 "Http Content Impl." procedure SetContent(Content: InStream; ContentType: Text) begin - HttpContent.Clear(); - HttpContent.WriteFrom(Content); + this.HttpContent.Clear(); + this.HttpContent.WriteFrom(Content); if ContentType = '' then ContentType := MimeTypeApplicationOctetStreamTxt; SetContentTypeHeader(ContentType); @@ -261,7 +260,7 @@ codeunit 2355 "Http Content Impl." procedure SetContent(var Value: HttpContent) begin - HttpContent := Value; + this.HttpContent := Value; end; local procedure ThrowInvalidJsonException() diff --git a/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al b/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al index 7f346d940d..a8f3a701d7 100644 --- a/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al @@ -4,8 +4,6 @@ // ------------------------------------------------------------------------------------------------ namespace System.RestClient; -using System.RestClient; - codeunit 2357 "Http Response Message Impl." { Access = Internal; @@ -13,9 +11,9 @@ codeunit 2357 "Http Response Message Impl." InherentPermissions = X; #region Constructors - procedure Create(HttpResponseMessage: HttpResponseMessage): Codeunit "Http Response Message Impl." + procedure Create(ResponseMessage: HttpResponseMessage): Codeunit "Http Response Message Impl." begin - this.SetResponseMessage(HttpResponseMessage); + SetResponseMessage(ResponseMessage); exit(this); end; #endregion @@ -144,38 +142,38 @@ codeunit 2357 "Http Response Message Impl." #region Cookies var - Cookies: Dictionary of [Text, Cookie]; + GlobalCookies: Dictionary of [Text, Cookie]; procedure SetCookies(Cookies: Dictionary of [Text, Cookie]) begin - this.Cookies := Cookies; + this.GlobalCookies := Cookies; end; procedure GetCookies() Cookies: Dictionary of [Text, Cookie] begin - Cookies := this.Cookies; + Cookies := this.GlobalCookies; end; procedure GetCookieNames() CookieNames: List of [Text] begin - CookieNames := this.Cookies.Keys; + CookieNames := this.GlobalCookies.Keys; end; procedure GetCookie(Name: Text) Cookie: Cookie begin - if this.Cookies.Get(Name, Cookie) then; + if this.GlobalCookies.Get(Name, Cookie) then; end; procedure GetCookie(Name: Text; var Cookie: Cookie) Success: Boolean begin - Success := this.Cookies.Get(Name, Cookie); + Success := this.GlobalCookies.Get(Name, Cookie); end; #endregion #region ErrorMessage var ErrorMessage: Text; - Exception: ErrorInfo; + GlobalException: ErrorInfo; procedure SetErrorMessage(Value: Text) begin @@ -184,8 +182,8 @@ codeunit 2357 "Http Response Message Impl." procedure GetErrorMessage(): Text begin - if this.Exception.Message <> '' then - exit(this.Exception.Message); + if this.GlobalException.Message <> '' then + exit(this.GlobalException.Message); if this.ErrorMessage <> '' then exit(this.ErrorMessage); @@ -195,24 +193,26 @@ codeunit 2357 "Http Response Message Impl." procedure SetException(Exception: ErrorInfo) begin - this.Exception := Exception; + this.GlobalException := Exception; end; procedure GetException() Exception: ErrorInfo var RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; begin - if this.Exception.Message = '' then + if this.GlobalException.Message = '' then Exception := RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::UnknownException, GetErrorMessage()) + else + Exception := this.GlobalException; end; procedure GetExceptionCode() ReturnValue: Enum "Rest Client Exception" var IntValue: Integer; - Execption: ErrorInfo; + Exception: ErrorInfo; begin Exception := this.GetException(); - Evaluate(IntValue, Exception.CustomDimensions.Get('ExceptionCode')); + Evaluate(IntValue, GlobalException.CustomDimensions.Get('ExceptionCode')); ReturnValue := Enum::"Rest Client Exception".FromInteger(IntValue); end; diff --git a/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al b/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al index e06cd5c45e..5dc655e16d 100644 --- a/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al @@ -4,8 +4,6 @@ // ------------------------------------------------------------------------------------------------ namespace System.RestClient; -using System.RestClient; - codeunit 2351 "Rest Client Impl." { Access = Internal; @@ -18,14 +16,10 @@ codeunit 2351 "Rest Client Impl." RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; HttpAuthentication: Interface "Http Authentication"; HttpClientHandler: Interface "Http Client Handler"; - HttpResponseMessage: Codeunit "Http Response Message"; HttpClient: HttpClient; IsInitialized: Boolean; - BlockedByEnvironmentErrorTok: Label 'BlockedByEnvironmentError', Locked = true; EnvironmentBlocksErr: Label 'Environment blocks an outgoing HTTP request to ''%1''.', Comment = '%1 = url, e.g. https://microsoft.com'; - ConnectionErrorTok: Label 'NoConnectionError', Locked = true; ConnectionErr: Label 'Connection to the remote service ''%1'' could not be established.', Comment = '%1 = url, e.g. https://microsoft.com'; - RequestFailedErrorTok: Label 'RequestFailedError', Locked = true; RequestFailedErr: Label 'The request failed: %1 %2', Comment = '%1 = HTTP status code, %2 = Reason phrase'; UserAgentLbl: Label 'Dynamics 365 Business Central - |%1| %2/%3', Locked = true, Comment = '%1 = App Publisher; %2 = App Name; %3 = App Version'; TimeoutOutOfRangeErr: Label 'The timeout value must be greater than 0.'; @@ -33,80 +27,79 @@ codeunit 2351 "Rest Client Impl." #region Constructors procedure Create() RestClientImpl: Codeunit "Rest Client Impl." begin - RestClientImpl := RestClientImpl.Create(DefaultHttpClientHandler, HttpAuthenticationAnonymous); + RestClientImpl := RestClientImpl.Create(this.DefaultHttpClientHandler, this.HttpAuthenticationAnonymous); end; - procedure Create(HttpClientHandler: Interface "Http Client Handler") RestClientImpl: Codeunit "Rest Client Impl." + procedure Create(HttpClientHandlerInstance: Interface "Http Client Handler") RestClientImpl: Codeunit "Rest Client Impl." begin - RestClientImpl := RestClientImpl.Create(HttpClientHandler, HttpAuthenticationAnonymous); + RestClientImpl := RestClientImpl.Create(HttpClientHandlerInstance, this.HttpAuthenticationAnonymous); end; - procedure Create(HttpAuthentication: Interface "Http Authentication") RestClientImpl: Codeunit "Rest Client Impl." + procedure Create(HttpAuthenticationInstance: Interface "Http Authentication") RestClientImpl: Codeunit "Rest Client Impl." begin - RestClientImpl := RestClientImpl.Create(DefaultHttpClientHandler, HttpAuthentication); + RestClientImpl := RestClientImpl.Create(this.DefaultHttpClientHandler, HttpAuthenticationInstance); end; - procedure Create(HttpClientHandler: Interface "Http Client Handler"; HttpAuthentication: Interface "Http Authentication"): Codeunit "Rest Client Impl." + procedure Create(HttpClientHandlerInstance: Interface "Http Client Handler"; HttpAuthenticationInstance: Interface "Http Authentication"): Codeunit "Rest Client Impl." begin - Initialize(HttpClientHandler, HttpAuthentication); + Initialize(HttpClientHandlerInstance, HttpAuthenticationInstance); exit(this); end; - #endregion #region Initialization procedure Initialize() begin - Initialize(DefaultHttpClientHandler, HttpAuthenticationAnonymous); + Initialize(this.DefaultHttpClientHandler, this.HttpAuthenticationAnonymous); end; - procedure Initialize(HttpClientHandler: Interface "Http Client Handler") + procedure Initialize(HttpClientHandlerInstance: Interface "Http Client Handler") begin - Initialize(HttpClientHandler, HttpAuthenticationAnonymous); + Initialize(HttpClientHandlerInstance, this.HttpAuthenticationAnonymous); end; - procedure Initialize(HttpAuthentication: Interface "Http Authentication") + procedure Initialize(HttpAuthenticationInstance: Interface "Http Authentication") begin - Initialize(DefaultHttpClientHandler, HttpAuthentication); + Initialize(this.DefaultHttpClientHandler, HttpAuthenticationInstance); end; procedure Initialize(HttpClientHandlerInstance: Interface "Http Client Handler"; HttpAuthenticationInstance: Interface "Http Authentication") begin ClearAll(); - HttpClient.Clear(); - HttpClientHandler := HttpClientHandlerInstance; - HttpAuthentication := HttpAuthenticationInstance; - IsInitialized := true; + this.HttpClient.Clear(); + this.HttpClientHandler := HttpClientHandlerInstance; + this.HttpAuthentication := HttpAuthenticationInstance; + this.IsInitialized := true; SetDefaultUserAgentHeader(); end; procedure SetDefaultRequestHeader(Name: Text; Value: Text) begin CheckInitialized(); - if HttpClient.DefaultRequestHeaders.Contains(Name) then - HttpClient.DefaultRequestHeaders.Remove(Name); - HttpClient.DefaultRequestHeaders.Add(Name, Value); + if this.HttpClient.DefaultRequestHeaders.Contains(Name) then + this.HttpClient.DefaultRequestHeaders.Remove(Name); + this.HttpClient.DefaultRequestHeaders.Add(Name, Value); end; procedure SetDefaultRequestHeader(Name: Text; Value: SecretText) begin CheckInitialized(); - if HttpClient.DefaultRequestHeaders.Contains(Name) then - HttpClient.DefaultRequestHeaders.Remove(Name); - HttpClient.DefaultRequestHeaders.Add(Name, Value); + if this.HttpClient.DefaultRequestHeaders.Contains(Name) then + this.HttpClient.DefaultRequestHeaders.Remove(Name); + this.HttpClient.DefaultRequestHeaders.Add(Name, Value); end; procedure SetBaseAddress(Url: Text) begin CheckInitialized(); - HttpClient.SetBaseAddress(Url); + this.HttpClient.SetBaseAddress(Url); end; procedure GetBaseAddress() Url: Text begin CheckInitialized(); - Url := HttpClient.GetBaseAddress; + Url := this.HttpClient.GetBaseAddress; end; procedure SetTimeOut(TimeOut: Duration) @@ -114,25 +107,25 @@ codeunit 2351 "Rest Client Impl." CheckInitialized(); if TimeOut <= 0 then Error(TimeoutOutOfRangeErr); - HttpClient.Timeout := TimeOut; + this.HttpClient.Timeout := TimeOut; end; procedure GetTimeOut() TimeOut: Duration begin CheckInitialized(); - TimeOut := HttpClient.Timeout; + TimeOut := this.HttpClient.Timeout; end; procedure AddCertificate(Certificate: Text) begin CheckInitialized(); - HttpClient.AddCertificate(Certificate); + this.HttpClient.AddCertificate(Certificate); end; procedure AddCertificate(Certificate: Text; Password: SecretText) begin CheckInitialized(); - HttpClient.AddCertificate(Certificate, Password); + this.HttpClient.AddCertificate(Certificate, Password); end; procedure SetAuthorizationHeader(Value: SecretText) @@ -148,7 +141,7 @@ codeunit 2351 "Rest Client Impl." procedure SetUseResponseCookies(Value: Boolean) begin CheckInitialized(); - HttpClient.UseResponseCookies(Value); + this.HttpClient.UseResponseCookies(Value); end; #endregion @@ -161,10 +154,11 @@ codeunit 2351 "Rest Client Impl." if HasCollectedErrors() then exit; - if not HttpResponseMessage.GetIsSuccessStatusCode() then begin + if not HttpResponseMessage.GetIsSuccessStatusCode() then Error(HttpResponseMessage.GetException()); + + if IsCollectingErrors() and HasCollectedErrors() then exit; - end; JsonToken := HttpResponseMessage.GetContent().AsJson(); end; @@ -178,10 +172,11 @@ codeunit 2351 "Rest Client Impl." if HasCollectedErrors() then exit; - if not HttpResponseMessage.GetIsSuccessStatusCode() then begin + if not HttpResponseMessage.GetIsSuccessStatusCode() then Error(HttpResponseMessage.GetException()); + + if IsCollectingErrors() and HasCollectedErrors() then exit; - end; Response := HttpResponseMessage.GetContent().AsJson(); end; @@ -195,10 +190,11 @@ codeunit 2351 "Rest Client Impl." if HasCollectedErrors() then exit; - if not HttpResponseMessage.GetIsSuccessStatusCode() then begin + if not HttpResponseMessage.GetIsSuccessStatusCode() then Error(HttpResponseMessage.GetException()); + + if IsCollectingErrors() and HasCollectedErrors() then exit; - end; Response := HttpResponseMessage.GetContent().AsJson(); end; @@ -212,10 +208,11 @@ codeunit 2351 "Rest Client Impl." if HasCollectedErrors() then exit; - if not HttpResponseMessage.GetIsSuccessStatusCode() then begin + if not HttpResponseMessage.GetIsSuccessStatusCode() then Error(HttpResponseMessage.GetException()); + + if IsCollectingErrors() and HasCollectedErrors() then exit; - end; Response := HttpResponseMessage.GetContent().AsJson(); end; @@ -250,7 +247,7 @@ codeunit 2351 "Rest Client Impl." #region Local Methods local procedure CheckInitialized() begin - if not IsInitialized then + if not this.IsInitialized then Initialize(); end; @@ -274,30 +271,28 @@ codeunit 2351 "Rest Client Impl." local procedure SendRequest(var HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message"): Boolean begin - Clear(this.HttpResponseMessage); + Clear(HttpResponseMessage); - if HttpAuthentication.IsAuthenticationRequired() then + if this.HttpAuthentication.IsAuthenticationRequired() then Authorize(HttpRequestMessage); - if not HttpClientHandler.Send(HttpClient, HttpRequestMessage, this.HttpResponseMessage) then begin - if this.HttpResponseMessage.GetIsBlockedByEnvironment() then - this.HttpResponseMessage.SetException( - RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::BlockedByEnvironment, + if not this.HttpClientHandler.Send(this.HttpClient, HttpRequestMessage, HttpResponseMessage) then begin + if HttpResponseMessage.GetIsBlockedByEnvironment() then + HttpResponseMessage.SetException( + this.RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::BlockedByEnvironment, StrSubstNo(EnvironmentBlocksErr, HttpRequestMessage.GetRequestUri()))) else - this.HttpResponseMessage.SetException( - RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::ConnectionFailed, + HttpResponseMessage.SetException( + this.RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::ConnectionFailed, StrSubstNo(ConnectionErr, HttpRequestMessage.GetRequestUri()))); - HttpResponseMessage := this.HttpResponseMessage; exit(false); end; - if not this.HttpResponseMessage.GetIsSuccessStatusCode() then - this.HttpResponseMessage.SetException( - RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::RequestFailed, + if not HttpResponseMessage.GetIsSuccessStatusCode() then + HttpResponseMessage.SetException( + this.RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::RequestFailed, StrSubstNo(RequestFailedErr, HttpResponseMessage.GetHttpStatusCode(), HttpResponseMessage.GetReasonPhrase()))); - HttpResponseMessage := this.HttpResponseMessage; exit(true); end; @@ -307,7 +302,7 @@ codeunit 2351 "Rest Client Impl." HeaderName: Text; HeaderValue: SecretText; begin - AuthorizationHeaders := HttpAuthentication.GetAuthorizationHeaders(); + AuthorizationHeaders := this.HttpAuthentication.GetAuthorizationHeaders(); foreach HeaderName in AuthorizationHeaders.Keys do begin HeaderValue := AuthorizationHeaders.Get(HeaderName); HttpRequestMessage.SetHeader(HeaderName, HeaderValue); From 36b4a10a5c872a178c6fd1d75b819e086133674a Mon Sep 17 00:00:00 2001 From: Arend-Jan Kauffmann Date: Sat, 7 Dec 2024 23:55:22 +0100 Subject: [PATCH 04/12] Refactor RestClientExceptionBuilder and HttpContent to improve code readability and maintainability - Add missing newline at the end of HttpContent.Codeunit.al - Rename GetRestClientException method to GetExceptionCode in HttpResponseMessage.Codeunit.al - Remove unused GetExceptionName method in HttpResponseMessage.Codeunit.al - Remove unused GetExceptionCode and GetExceptionName methods in HttpResponseMessageImpl.Codeunit.al - Remove unused ConnectionErr and RequestFailedErr labels in RestClientImpl.Codeunit.al - Update RestClientImpl.Codeunit.al to handle error collection more efficiently - Add new codeunit HttpExceptionTests.Codeunit.al to test different types of exceptions - Add new test to RequestMessageTests.Codeunit.al to test adding secret headers --- .../RestClientExceptionBuilder.Codeunit.al | 9 +- .../Rest Client/src/HttpContent.Codeunit.al | 18 + .../src/HttpContentImpl.Codeunit.al | 20 ++ .../src/HttpResponseMessage.Codeunit.al | 10 - .../src/HttpResponseMessageImpl.Codeunit.al | 19 -- .../src/RestClientImpl.Codeunit.al | 48 +-- .../Test/Rest Client/app.json | 2 +- .../src/HttpAuthenticationTests.Codeunit.al | 8 +- .../src/HttpExceptionTests.Codeunit.al | 290 ++++++++++++++++ .../src/RequestMessageTests.Codeunit.al | 108 +++++- .../src/RestClientTests.Codeunit.al | 318 +++++++++++++++--- .../src/TestHttpClientHandler.Codeunit.al | 51 +++ 12 files changed, 774 insertions(+), 127 deletions(-) create mode 100644 src/System Application/Test/Rest Client/src/HttpExceptionTests.Codeunit.al diff --git a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al index 78361c6a63..e0312ffdc4 100644 --- a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al +++ b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al @@ -2,7 +2,6 @@ namespace System.RestClient; codeunit 2362 "Rest Client Exception Builder" { - Access = Internal; InherentEntitlements = X; InherentPermissions = X; @@ -18,4 +17,12 @@ codeunit 2362 "Rest Client Exception Builder" Exception.CustomDimensions.Add('ExceptionName', RestClientException.Names.Get(RestClientException.Ordinals.IndexOf(RestClientException.AsInteger()))); Exception.Collectible := Collectible; end; + + procedure GetRestClientException(ErrInfo: ErrorInfo) RestClientException: Enum "Rest Client Exception" + var + ExceptionCode: Integer; + begin + Evaluate(ExceptionCode, ErrInfo.CustomDimensions.Get('ExceptionCode')); + RestClientException := Enum::"Rest Client Exception".FromInteger(ExceptionCode); + end; } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al b/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al index 9db74b3af2..508c75905a 100644 --- a/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al @@ -236,5 +236,23 @@ codeunit 2354 "Http Content" begin JsonToken := HttpContentImpl.AsJson(); end; + + /// Gets the content of the HTTP response message as a JsonObject. + /// The content of the HTTP response message as a JsonObject. + /// Returns an empty JsonObject in case there is no content. + /// Fails in case the content is not a valid JSON document. + procedure AsJsonObject() JsonObject: JsonObject + begin + JsonObject := HttpContentImpl.AsJsonObject(); + end; + + /// Gets the content of the HTTP response message as a JsonArray. + /// The content of the HTTP response message as a JsonArray. + /// Returns an empty JsonArray in case there is no content. + /// Fails in case the content is not a valid JSON document. + procedure AsJsonArray() JsonArray: JsonArray + begin + JsonArray := HttpContentImpl.AsJsonArray(); + end; #endregion } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al b/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al index 4f84260ce7..69ae541e73 100644 --- a/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al @@ -203,6 +203,26 @@ codeunit 2355 "Http Content Impl." ThrowInvalidJsonException(); end; + procedure AsJsonObject() ReturnValue: JsonObject + var + Json: Text; + begin + Json := AsText(); + if Json <> '' then + if not ReturnValue.ReadFrom(AsText()) then + ThrowInvalidJsonException(); + end; + + procedure AsJsonArray() ReturnValue: JsonArray + var + Json: Text; + begin + Json := AsText(); + if Json <> '' then + if not ReturnValue.ReadFrom(AsText()) then + ThrowInvalidJsonException(); + end; + procedure SetContent(Content: Text; ContentType: Text) begin this.HttpContent.Clear(); diff --git a/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al b/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al index 0e0c027a25..0e6532847e 100644 --- a/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al @@ -207,15 +207,5 @@ codeunit 2356 "Http Response Message" begin Exception := HttpResponseMessageImpl.GetException(); end; - - procedure GetExceptionCode() ReturnValue: Enum "Rest Client Exception" - begin - ReturnValue := HttpResponseMessageImpl.GetExceptionCode(); - end; - - procedure GetExceptionName() ReturnValue: Text - begin - ReturnValue := HttpResponseMessageImpl.GetExceptionName(); - end; #endregion } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al b/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al index a8f3a701d7..f58045ee9a 100644 --- a/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al @@ -205,24 +205,5 @@ codeunit 2357 "Http Response Message Impl." else Exception := this.GlobalException; end; - - procedure GetExceptionCode() ReturnValue: Enum "Rest Client Exception" - var - IntValue: Integer; - Exception: ErrorInfo; - begin - Exception := this.GetException(); - Evaluate(IntValue, GlobalException.CustomDimensions.Get('ExceptionCode')); - ReturnValue := Enum::"Rest Client Exception".FromInteger(IntValue); - end; - - procedure GetExceptionName() ReturnValue: Text - var - Exception: ErrorInfo; - begin - Exception := this.GetException(); - ReturnValue := Exception.CustomDimensions.Get('ExceptionName'); - end; - #endregion } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al b/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al index 5dc655e16d..183bf3ebdd 100644 --- a/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al @@ -18,9 +18,9 @@ codeunit 2351 "Rest Client Impl." HttpClientHandler: Interface "Http Client Handler"; HttpClient: HttpClient; IsInitialized: Boolean; - EnvironmentBlocksErr: Label 'Environment blocks an outgoing HTTP request to ''%1''.', Comment = '%1 = url, e.g. https://microsoft.com'; - ConnectionErr: Label 'Connection to the remote service ''%1'' could not be established.', Comment = '%1 = url, e.g. https://microsoft.com'; - RequestFailedErr: Label 'The request failed: %1 %2', Comment = '%1 = HTTP status code, %2 = Reason phrase'; + EnvironmentBlocksErr: Label 'The outgoing HTTP request to "%1" was blocked by the environment.', Comment = '%1 = url, e.g. https://microsoft.com'; + ConnectionErr: Label 'Connection to the remote service "%1" could not be established.', Comment = '%1 = url, e.g. https://microsoft.com'; + RequestFailedErr: Label 'The request to "%1" failed with status code %2 %3.', Comment = '%1 = url, %2 = HTTP status code, %3 = Reason phrase'; UserAgentLbl: Label 'Dynamics 365 Business Central - |%1| %2/%3', Locked = true, Comment = '%1 = App Publisher; %2 = App Name; %3 = App Version'; TimeoutOutOfRangeErr: Label 'The timeout value must be greater than 0.'; @@ -151,14 +151,16 @@ codeunit 2351 "Rest Client Impl." HttpResponseMessage: Codeunit "Http Response Message"; begin HttpResponseMessage := Send(Enum::"Http Method"::GET, RequestUri); - if HasCollectedErrors() then - exit; + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; if not HttpResponseMessage.GetIsSuccessStatusCode() then Error(HttpResponseMessage.GetException()); - if IsCollectingErrors() and HasCollectedErrors() then - exit; + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; JsonToken := HttpResponseMessage.GetContent().AsJson(); end; @@ -169,14 +171,16 @@ codeunit 2351 "Rest Client Impl." HttpContent: Codeunit "Http Content"; begin HttpResponseMessage := Send(Enum::"Http Method"::POST, RequestUri, HttpContent.Create(Content)); - if HasCollectedErrors() then - exit; + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; if not HttpResponseMessage.GetIsSuccessStatusCode() then Error(HttpResponseMessage.GetException()); - if IsCollectingErrors() and HasCollectedErrors() then - exit; + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; Response := HttpResponseMessage.GetContent().AsJson(); end; @@ -187,14 +191,16 @@ codeunit 2351 "Rest Client Impl." HttpContent: Codeunit "Http Content"; begin HttpResponseMessage := Send(Enum::"Http Method"::PATCH, RequestUri, HttpContent.Create(Content)); - if HasCollectedErrors() then - exit; + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; if not HttpResponseMessage.GetIsSuccessStatusCode() then Error(HttpResponseMessage.GetException()); - if IsCollectingErrors() and HasCollectedErrors() then - exit; + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; Response := HttpResponseMessage.GetContent().AsJson(); end; @@ -205,14 +211,16 @@ codeunit 2351 "Rest Client Impl." HttpContent: Codeunit "Http Content"; begin HttpResponseMessage := Send(Enum::"Http Method"::PUT, RequestUri, HttpContent.Create(Content)); - if HasCollectedErrors() then - exit; + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; if not HttpResponseMessage.GetIsSuccessStatusCode() then Error(HttpResponseMessage.GetException()); - if IsCollectingErrors() and HasCollectedErrors() then - exit; + if IsCollectingErrors() then + if HasCollectedErrors() then + exit; Response := HttpResponseMessage.GetContent().AsJson(); end; @@ -291,7 +299,7 @@ codeunit 2351 "Rest Client Impl." if not HttpResponseMessage.GetIsSuccessStatusCode() then HttpResponseMessage.SetException( this.RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::RequestFailed, - StrSubstNo(RequestFailedErr, HttpResponseMessage.GetHttpStatusCode(), HttpResponseMessage.GetReasonPhrase()))); + StrSubstNo(RequestFailedErr, HttpRequestMessage.GetRequestUri(), HttpResponseMessage.GetHttpStatusCode(), HttpResponseMessage.GetReasonPhrase()))); exit(true); end; diff --git a/src/System Application/Test/Rest Client/app.json b/src/System Application/Test/Rest Client/app.json index 2d14cf6c3f..384fd9f7ec 100644 --- a/src/System Application/Test/Rest Client/app.json +++ b/src/System Application/Test/Rest Client/app.json @@ -35,7 +35,7 @@ "idRanges": [ { "from": 134970, - "to": 134974 + "to": 134975 } ], "resourceExposurePolicy": { diff --git a/src/System Application/Test/Rest Client/src/HttpAuthenticationTests.Codeunit.al b/src/System Application/Test/Rest Client/src/HttpAuthenticationTests.Codeunit.al index 48834ba82e..4568b0ef9a 100644 --- a/src/System Application/Test/Rest Client/src/HttpAuthenticationTests.Codeunit.al +++ b/src/System Application/Test/Rest Client/src/HttpAuthenticationTests.Codeunit.al @@ -28,7 +28,7 @@ codeunit 134973 "Http Authentication Tests" // [WHEN] The authentication object is asked to return the authorization header // [THEN] The authentication object should return an empty list - Assert.AreEqual(HttpAuthenticationAnonymous.GetAuthorizationHeaders().Count, 0, 'Anonymous authentication should not return an authorization header'); + Assert.AreEqual(0, HttpAuthenticationAnonymous.GetAuthorizationHeaders().Count, 'Anonymous authentication should not return an authorization header'); end; [NonDebuggable] @@ -49,10 +49,10 @@ codeunit 134973 "Http Authentication Tests" // [WHEN] The authentication object is asked to return the authorization header // [THEN] THe authentication object should return a dictionary with one element that is a base64 encoded string AuthHeader := HttpAuthenticationBasic.GetAuthorizationHeaders(); - Assert.AreEqual(AuthHeader.Count, 1, 'Basic authentication should return one authorization header'); - Assert.AreEqual(AuthHeader.ContainsKey('Authorization'), true, 'Basic authentication should return an authorization header'); + Assert.AreEqual(1, AuthHeader.Count, 'Basic authentication should return one authorization header'); + Assert.AreEqual(true, AuthHeader.ContainsKey('Authorization'), 'Basic authentication should return an authorization header'); BasicAuthHeaderValue := AuthHeader.Get('Authorization'); - Assert.AreEqual(BasicAuthHeaderValue.Unwrap(), 'Basic VVNFUjAxOlBhc3N3b3JkMTIzIQ==', 'Basic authentication should return a base64 encoded string'); + Assert.AreEqual('Basic VVNFUjAxOlBhc3N3b3JkMTIzIQ==', BasicAuthHeaderValue.Unwrap(), 'Basic authentication should return a base64 encoded string'); end; } \ No newline at end of file diff --git a/src/System Application/Test/Rest Client/src/HttpExceptionTests.Codeunit.al b/src/System Application/Test/Rest Client/src/HttpExceptionTests.Codeunit.al new file mode 100644 index 0000000000..c2d6db39e6 --- /dev/null +++ b/src/System Application/Test/Rest Client/src/HttpExceptionTests.Codeunit.al @@ -0,0 +1,290 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ + +namespace System.Test.RestClient; + +using System.RestClient; +using System.TestLibraries.Utilities; + +codeunit 134975 "Http Exception Tests" +{ + Subtype = Test; + + var + Assert: Codeunit "Library Assert"; + HttpClientHandler: Codeunit "Test Http Client Handler"; + + [Test] + procedure TestConnectionFailed() + var + RestClient: Codeunit "Rest Client"; + HttpResponseMessage: Codeunit "Http Response Message"; + begin + // [SCENARIO] Error is raised when the connection fails + + // [GIVEN] An initialized Rest Client + HttpClientHandler.SetMockConnectionFailed(); + RestClient := RestClient.Create(HttpClientHandler); + + // [WHEN] Sending a request + asserterror HttpResponseMessage := RestClient.Get('http://www.example.com'); + + // [THEN] RestClient returns an error + Assert.ExpectedError('Connection to the remote service "http://www.example.com/" could not be established.'); + end; + + [Test] + [ErrorBehavior(ErrorBehavior::Collect)] + procedure TestConnectionFailedWithErrorCollection() + var + RestClient: Codeunit "Rest Client"; + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + HttpResponseMessage: Codeunit "Http Response Message"; + Exceptions: List of [ErrorInfo]; + Exception: ErrorInfo; + RestClientException: Enum "Rest Client Exception"; + begin + // [SCENARIO] Error is raised when the connection fails + + // [GIVEN] An initialized Rest Client + HttpClientHandler.SetMockConnectionFailed(); + RestClient := RestClient.Create(HttpClientHandler); + + // [WHEN] Sending a request + HttpResponseMessage := RestClient.Get('http://www.example.com'); + + // [THEN] RestClient returns one collectible error of type ConnectionFailed + Assert.IsTrue(HasCollectedErrors(), 'No collectible error was returned'); + Exceptions := GetCollectedErrors(true); + Assert.AreEqual(1, Exceptions.Count, 'There should be 1 exception'); + + Exception := Exceptions.Get(1); + RestClientException := RestClientExceptionBuilder.GetRestClientException(Exception); + Assert.AreEqual(Enum::"Rest Client Exception"::ConnectionFailed, RestClientException, 'The exception should be of type ConnectionFailed'); + Assert.AreEqual('ConnectionFailed', Exception.CustomDimensions.Get('ExceptionName'), 'The exception name should be "ConnectionFailed"'); + Assert.AreEqual('Connection to the remote service "http://www.example.com/" could not be established.', Exception.Message, 'The error message is incorrect'); + end; + + [Test] + procedure TestBlockedByEnvironment() + var + RestClient: Codeunit "Rest Client"; + HttpResponseMessage: Codeunit "Http Response Message"; + begin + // [SCENARIO] Error is raised when the request is blocked by the environment + + // [GIVEN] An initialized Rest Client + HttpClientHandler.SetMockIsBlockedByEnvironment(); + RestClient := RestClient.Create(HttpClientHandler); + + // [WHEN] Sending a request + asserterror HttpResponseMessage := RestClient.Get('http://www.example.com'); + + // [THEN] RestClient returns an error + Assert.ExpectedError('The outgoing HTTP request to "http://www.example.com/" was blocked by the environment.'); + end; + + [Test] + [ErrorBehavior(ErrorBehavior::Collect)] + procedure TestBlockedByEnvironmentWithErrorCollection() + var + RestClient: Codeunit "Rest Client"; + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + HttpResponseMessage: Codeunit "Http Response Message"; + Exceptions: List of [ErrorInfo]; + Exception: ErrorInfo; + RestClientException: Enum "Rest Client Exception"; + begin + // [SCENARIO] Error is raised when the request is blocked by the environment + + // [GIVEN] An initialized Rest Client + HttpClientHandler.SetMockIsBlockedByEnvironment(); + RestClient := RestClient.Create(HttpClientHandler); + + // [WHEN] Sending a request + HttpResponseMessage := RestClient.Get('http://www.example.com'); + + // [THEN] RestClient returns one collectible error of type BlockedByEnvironment + Assert.IsTrue(HasCollectedErrors(), 'No collectible error was returned'); + Exceptions := GetCollectedErrors(true); + Assert.AreEqual(1, Exceptions.Count, 'There should be 1 exception'); + + Exception := Exceptions.Get(1); + RestClientException := RestClientExceptionBuilder.GetRestClientException(Exception); + Assert.AreEqual(Enum::"Rest Client Exception"::BlockedByEnvironment, RestClientException, 'The exception should be of type BlockedByEnvironment'); + Assert.AreEqual('BlockedByEnvironment', Exception.CustomDimensions.Get('ExceptionName'), 'The exception name should be "BlockedByEnvironment"'); + Assert.AreEqual('The outgoing HTTP request to "http://www.example.com/" was blocked by the environment.', Exception.Message, 'The error message is incorrect'); + end; + + [Test] + procedure TestRequestFailedOnGenericMethod() + var + RestClient: Codeunit "Rest Client"; + HttpResponseMessage: Codeunit "Http Response Message"; + begin + // [SCENARIO] Error is raised when the request fails + + // [GIVEN] An initialized Rest Client + HttpClientHandler.SetMockRequestFailed(); + RestClient := RestClient.Create(HttpClientHandler); + + // [WHEN] Sending a request + HttpResponseMessage := RestClient.Get('http://www.example.com'); + + // [THEN] HttpResponseMessage contains an error + Assert.IsFalse(HttpResponseMessage.GetIsSuccessStatusCode(), 'The request should not be successful'); + asserterror Error(HttpResponseMessage.GetException()); + Assert.ExpectedError('The request to "http://www.example.com/" failed with status code 400 Bad Request.'); + end; + + [Test] + procedure TestRequestFailedOnTypedMethod() + var + RestClient: Codeunit "Rest Client"; + JsonToken: JsonToken; + begin + // [SCENARIO] Error is raised when the request fails + + // [GIVEN] An initialized Rest Client + HttpClientHandler.SetMockRequestFailed(); + RestClient := RestClient.Create(HttpClientHandler); + + // [WHEN] Sending a request + asserterror JsonToken := RestClient.GetAsJson('http://www.example.com'); + + // [THEN] HttpResponseMessage contains an error + Assert.ExpectedError('The request to "http://www.example.com/" failed with status code 400 Bad Request.'); + end; + + [Test] + [ErrorBehavior(ErrorBehavior::Collect)] + procedure TestRequestFailedWithErrorCollection() + var + RestClient: Codeunit "Rest Client"; + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + JsonToken: JsonToken; + Exceptions: List of [ErrorInfo]; + Exception: ErrorInfo; + RestClientException: Enum "Rest Client Exception"; + begin + // [SCENARIO] Error is raised when the request fails + + // [GIVEN] An initialized Rest Client + HttpClientHandler.SetMockRequestFailed(); + RestClient := RestClient.Create(HttpClientHandler); + + // [WHEN] Sending a request + JsonToken := RestClient.GetAsJson('http://www.example.com'); + + // [THEN] RestClient returns one collectible error of type RequestFailed + Assert.IsTrue(HasCollectedErrors(), 'No collectible error was returned'); + Exceptions := GetCollectedErrors(true); + Assert.AreEqual(1, Exceptions.Count, 'There should be 1 exception'); + + Exception := Exceptions.Get(1); + RestClientException := RestClientExceptionBuilder.GetRestClientException(Exception); + Assert.AreEqual(Enum::"Rest Client Exception"::RequestFailed, RestClientException, 'The exception should be of type RequestFailed'); + Assert.AreEqual('RequestFailed', Exception.CustomDimensions.Get('ExceptionName'), 'The exception name should be "RequestFailed"'); + Assert.AreEqual('The request to "http://www.example.com/" failed with status code 400 Bad Request.', Exception.Message, 'The error message is incorrect'); + end; + + [Test] + procedure TestInvalidJson() + var + HttpContent: Codeunit "Http Content"; + JsonToken: JsonToken; + begin + // [SCENARIO] Error is raised when trying to read as JSON from a HttpContent object with invalid JSON + + // [GIVEN] HttpContent object with invalid JSON + HttpContent := HttpContent.Create('{"key": "value"'); + + // [WHEN] Trying to read as JSON + asserterror JsonToken := HttpContent.AsJson(); + + // [THEN] HttpContent returns an error + Assert.ExpectedError('The content is not a valid JSON.'); + end; + + [Test] + [ErrorBehavior(ErrorBehavior::Collect)] + procedure TestInvalidJsonWithErrorCollection() + var + HttpContent: Codeunit "Http Content"; + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + JsonToken: JsonToken; + Exceptions: List of [ErrorInfo]; + Exception: ErrorInfo; + RestClientException: Enum "Rest Client Exception"; + begin + // [SCENARIO] Error is raised when trying to read as JSON from a HttpContent object with invalid JSON + + // [GIVEN] HttpContent object with invalid JSON + HttpContent := HttpContent.Create('{"key": "value"'); + + // [WHEN] Trying to read as JSON + JsonToken := HttpContent.AsJson(); + + // [THEN] HttpContent returns one collectible error of type InvalidJson + Assert.IsTrue(HasCollectedErrors(), 'No collectible error was returned'); + Exceptions := GetCollectedErrors(true); + Assert.AreEqual(1, Exceptions.Count, 'There should be 1 exception'); + + Exception := Exceptions.Get(1); + RestClientException := RestClientExceptionBuilder.GetRestClientException(Exception); + Assert.AreEqual(Enum::"Rest Client Exception"::InvalidJson, RestClientException, 'The exception should be of type InvalidJson'); + Assert.AreEqual('InvalidJson', Exception.CustomDimensions.Get('ExceptionName'), 'The exception name should be "InvalidJson"'); + Assert.AreEqual('The content is not a valid JSON.', Exception.Message, 'The message should be "The content is not a valid JSON."'); + end; + + [Test] + procedure TestInvalidXml() + var + HttpContent: Codeunit "Http Content"; + XmlDocument: XmlDocument; + begin + // [SCENARIO] Error is raised when trying to read as XML from a HttpContent object with invalid XML + + // [GIVEN] HttpContent object with invalid XML + HttpContent := HttpContent.Create(''); + + // [WHEN] Trying to read as XML + asserterror XmlDocument := HttpContent.AsXmlDocument(); + + // [THEN] HttpContent returns an error + Assert.ExpectedError('The content is not a valid XML.'); + end; + + [Test] + [ErrorBehavior(ErrorBehavior::Collect)] + procedure TestInvalidXmlWithErrorCollection() + var + HttpContent: Codeunit "Http Content"; + RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; + XmlDocument: XmlDocument; + Exceptions: List of [ErrorInfo]; + Exception: ErrorInfo; + RestClientException: Enum "Rest Client Exception"; + begin + // [SCENARIO] Error is raised when trying to read as XML from a HttpContent object with invalid XML + + // [GIVEN] HttpContent object with invalid XML + HttpContent := HttpContent.Create(''); + + // [WHEN] Trying to read as XML + XmlDocument := HttpContent.AsXmlDocument(); + + // [THEN] HttpContent returns one collectible error of type InvalidXml + Assert.IsTrue(HasCollectedErrors(), 'No collectible error was returned'); + Exceptions := GetCollectedErrors(true); + Assert.AreEqual(1, Exceptions.Count, 'There should be 1 exception'); + + Exception := Exceptions.Get(1); + RestClientException := RestClientExceptionBuilder.GetRestClientException(Exception); + Assert.AreEqual(Enum::"Rest Client Exception"::InvalidXml, RestClientException, 'The exception should be of type InvalidXml'); + Assert.AreEqual('InvalidXml', Exception.CustomDimensions.Get('ExceptionName'), 'The exception name should be "InvalidXml"'); + Assert.AreEqual('The content is not a valid XML.', Exception.Message, 'The message should be "The content is not a valid XML."'); + end; +} \ No newline at end of file diff --git a/src/System Application/Test/Rest Client/src/RequestMessageTests.Codeunit.al b/src/System Application/Test/Rest Client/src/RequestMessageTests.Codeunit.al index 10d54896a4..48b82fabc7 100644 --- a/src/System Application/Test/Rest Client/src/RequestMessageTests.Codeunit.al +++ b/src/System Application/Test/Rest Client/src/RequestMessageTests.Codeunit.al @@ -30,7 +30,7 @@ codeunit 134972 "Request Message Tests" HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage(); // [THEN] The request message is initialized correctly - Assert.AreEqual(HttpRequestMessage.Method(), 'PATCH', 'The request message method is not correct.'); + Assert.AreEqual('PATCH', HttpRequestMessage.Method(), 'The request message method is not correct.'); end; [Test] @@ -49,8 +49,8 @@ codeunit 134972 "Request Message Tests" HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage(); // [THEN] The request message is initialized correctly - Assert.AreEqual(HttpRequestMessage.Method(), 'GET', 'The request message method is not correct.'); - Assert.AreEqual(HttpRequestMessage.GetRequestUri(), 'https://www.microsoft.com/', 'The request message request URI is not correct.'); + Assert.AreEqual('GET', HttpRequestMessage.Method(), 'The request message method is not correct.'); + Assert.AreEqual('https://www.microsoft.com/', HttpRequestMessage.GetRequestUri(), 'The request message request URI is not correct.'); end; [Test] @@ -74,17 +74,17 @@ codeunit 134972 "Request Message Tests" HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage(); // [THEN] The request message is initialized correctly - Assert.AreEqual(HttpRequestMessage.Method(), 'POST', 'The request message method is not correct.'); - Assert.AreEqual(HttpRequestMessage.GetRequestUri(), 'https://www.microsoft.com/', 'The request message request URI is not correct.'); + Assert.AreEqual('POST', HttpRequestMessage.Method(), 'The request message method is not correct.'); + Assert.AreEqual('https://www.microsoft.com/', HttpRequestMessage.GetRequestUri(), 'The request message request URI is not correct.'); HttpRequestMessage.Content().ReadAs(ContentText); - Assert.AreEqual(ContentText, 'Hello World!', 'The request message content is not correct.'); + Assert.AreEqual('Hello World!', ContentText, 'The request message content is not correct.'); HttpRequestMessage.Content.GetHeaders(ContentHeaders); - Assert.AreEqual(ContentHeaders.Contains('Content-Type'), true, 'The content type header is missing.'); + Assert.AreEqual(true, ContentHeaders.Contains('Content-Type'), 'The content type header is missing.'); ContentHeaders.GetValues('Content-Type', ContentHeaderValues); - Assert.AreEqual(ContentHeaderValues.Get(1), 'text/plain', 'The request message content type is not correct.'); + Assert.AreEqual('text/plain', ContentHeaderValues.Get(1), 'The request message content type is not correct.'); end; [Test] @@ -110,19 +110,19 @@ codeunit 134972 "Request Message Tests" HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage(); // [THEN] The request message is initialized correctly - Assert.AreEqual(HttpRequestMessage.Method(), 'POST', 'The request message method is not correct.'); - Assert.AreEqual(HttpRequestMessage.GetRequestUri(), 'https://www.microsoft.com/', 'The request message request URI is not correct.'); + Assert.AreEqual('POST', HttpRequestMessage.Method(), 'The request message method is not correct.'); + Assert.AreEqual('https://www.microsoft.com/', HttpRequestMessage.GetRequestUri(), 'The request message request URI is not correct.'); HttpRequestMessage.Content().ReadAs(ContentText); - Assert.AreEqual(ContentJson.ReadFrom(ContentText), true, 'The request message content is not a valid JSON object.'); - Assert.AreEqual(ContentJson.Contains('value'), true, 'The request message content does not contain the expected property "value".'); - Assert.AreEqual(GetJsonToken(ContentJson, 'value').AsValue().AsText(), 'Hello World!', 'The request message content property "value" is not correct.'); + Assert.AreEqual(true, ContentJson.ReadFrom(ContentText), 'The request message content is not a valid JSON object.'); + Assert.AreEqual(true, ContentJson.Contains('value'), 'The request message content does not contain the expected property "value".'); + Assert.AreEqual('Hello World!', GetJsonToken(ContentJson, 'value').AsValue().AsText(), 'The request message content property "value" is not correct.'); HttpRequestMessage.Content.GetHeaders(ContentHeaders); - Assert.AreEqual(ContentHeaders.Contains('Content-Type'), true, 'The content type header is missing.'); + Assert.AreEqual(true, ContentHeaders.Contains('Content-Type'), 'The content type header is missing.'); ContentHeaders.GetValues('Content-Type', ContentHeaderValues); - Assert.AreEqual(ContentHeaderValues.Get(1), 'application/json', 'The request message content type is not correct.'); + Assert.AreEqual('application/json', ContentHeaderValues.Get(1), 'The request message content type is not correct.'); end; [Test] @@ -145,12 +145,86 @@ codeunit 134972 "Request Message Tests" // [THEN] The request message is initialized correctly HttpRequestMessage.GetHeaders(ContentHeaders); - Assert.AreEqual(ContentHeaders.Contains('X-Custom-Header'), true, 'The custom header is missing.'); + Assert.IsTrue(ContentHeaders.Contains('X-Custom-Header'), 'The custom header is missing.'); ContentHeaders.GetValues('X-Custom-Header', ContentHeaderValues); - Assert.AreEqual(ContentHeaderValues.Get(1), 'My Request Header', 'The custom header value is not correct.'); + Assert.AreEqual('My Request Header', ContentHeaderValues.Get(1), 'The custom header value is not correct.'); end; + [Test] + [NonDebuggable] + procedure TestAddSecretRequestHeader() + var + ALHttpRequestMessage: Codeunit "Http Request Message"; + HttpRequestMessage: HttpRequestMessage; + ContentHeaders: HttpHeaders; + SecretHeaderText: SecretText; + ContentHeaderValues: List of [SecretText]; + begin + // [GIVEN] An initialized Http Request Message + ALHttpRequestMessage.SetHttpMethod('GET'); + ALHttpRequestMessage.SetRequestUri('https://www.microsoft.com/'); + + // [GIVEN] The request message has a secret header + SecretHeaderText := SecretStrSubstNo('My Secret Request Header'); + ALHttpRequestMessage.SetHeader('X-Secret-Header', SecretHeaderText); + + // [WHEN] The request message is read + HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage(); + + // [THEN] The request message is initialized correctly + HttpRequestMessage.GetHeaders(ContentHeaders); + Assert.IsTrue(ContentHeaders.ContainsSecret('X-Secret-Header'), 'The secret header is missing.'); + + ContentHeaders.GetSecretValues('X-Secret-Header', ContentHeaderValues); + Assert.AreEqual(SecretHeaderText.Unwrap(), ContentHeaderValues.Get(1).Unwrap(), 'The secret header value is not correct.'); + end; + + [Test] + procedure TestAddCookie() + var + ALHttpRequestMessage: Codeunit "Http Request Message"; + HttpRequestMessage: HttpRequestMessage; + RequestCookie: Cookie; + begin + // [GIVEN] An initialized Http Request Message + ALHttpRequestMessage.SetHttpMethod('GET'); + ALHttpRequestMessage.SetRequestUri('https://www.microsoft.com/'); + + // [GIVEN] The request message has a cookie + ALHttpRequestMessage.SetCookie('MyCookie', 'MyCookieValue'); + + // [WHEN] The request message is read + HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage(); + + // [THEN] The request message is initialized correctly + Assert.IsTrue(HttpRequestMessage.GetCookie('MyCookie', RequestCookie), 'The cookie is missing.'); + Assert.AreEqual('MyCookieValue', RequestCookie.Value(), 'The cookie value is not correct.'); + end; + + [Test] + procedure TestRemoveCookie() + var + ALHttpRequestMessage: Codeunit "Http Request Message"; + HttpRequestMessage: HttpRequestMessage; + RequestCookie: Cookie; + begin + // [GIVEN] An initialized Http Request Message + ALHttpRequestMessage.SetHttpMethod('GET'); + ALHttpRequestMessage.SetRequestUri('https://www.microsoft.com/'); + + // [GIVEN] The request message has a cookie + ALHttpRequestMessage.SetCookie('MyCookie', 'MyCookieValue'); + ALHttpRequestMessage.RemoveCookie('MyCookie'); + + // [WHEN] The request message is read + HttpRequestMessage := ALHttpRequestMessage.GetHttpRequestMessage(); + + // [THEN] The request message is initialized correctly + Assert.IsFalse(HttpRequestMessage.GetCookie('MyCookie', RequestCookie), 'The cookie is not removed.'); + end; + + local procedure GetJsonToken(JsonObject: JsonObject; Name: Text) JsonToken: JsonToken begin JsonObject.Get(Name, JsonToken); diff --git a/src/System Application/Test/Rest Client/src/RestClientTests.Codeunit.al b/src/System Application/Test/Rest Client/src/RestClientTests.Codeunit.al index a29dde8c92..96d5048d86 100644 --- a/src/System Application/Test/Rest Client/src/RestClientTests.Codeunit.al +++ b/src/System Application/Test/Rest Client/src/RestClientTests.Codeunit.al @@ -25,17 +25,42 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test GET request - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Get method is called HttpResponseMessage := RestClient.Get('https://httpbin.org/get'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/get', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); + end; + + [Test] + procedure TestGetWithQueryParameters() + var + RestClient: Codeunit "Rest Client"; + HttpResponseMessage: Codeunit "Http Response Message"; + JsonObject: JsonObject; + begin + // [SCENARIO] Test GET request with query parameters + + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); + RestClient.Initialize(HttpClientHandler); + + // [WHEN] The Get method is called with query parameters + HttpResponseMessage := RestClient.Get('https://httpbin.org/get?param1=value1¶m2=value2'); + + // [THEN] The response contains the expected data + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); + Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); + JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); + Assert.AreEqual('value1', SelectJsonToken(JsonObject, '$.args.param1').AsValue().AsText(), 'The response should contain the expected query parameter'); + Assert.AreEqual('value2', SelectJsonToken(JsonObject, '$.args.param2').AsValue().AsText(), 'The response should contain the expected query parameter'); end; [Test] @@ -48,18 +73,19 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test POST request - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Post method is called HttpResponseMessage := RestClient.Post('https://httpbin.org/post', HttpGetContent.Create('Hello World')); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/post', 'The response should contain the expected url'); - Assert.AreEqual(GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'Hello World', 'The response should contain the expected data'); + Assert.AreEqual('https://httpbin.org/post', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); + Assert.AreEqual('Hello World', GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'The response should contain the expected data'); end; [Test] @@ -72,18 +98,19 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test PATCH request - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Patch method is called HttpResponseMessage := RestClient.Patch('https://httpbin.org/patch', HttpGetContent.Create('Hello World')); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/patch', 'The response should contain the expected url'); - Assert.AreEqual(GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'Hello World', 'The response should contain the expected data'); + Assert.AreEqual('https://httpbin.org/patch', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); + Assert.AreEqual('Hello World', GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'The response should contain the expected data'); end; [Test] @@ -96,18 +123,19 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test PUT request - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Put method is called HttpResponseMessage := RestClient.Put('https://httpbin.org/put', HttpGetContent.Create('Hello World')); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/put', 'The response should contain the expected url'); - Assert.AreEqual(GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'Hello World', 'The response should contain the expected data'); + Assert.AreEqual('https://httpbin.org/put', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); + Assert.AreEqual('Hello World', GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'The response should contain the expected data'); end; [Test] @@ -119,17 +147,18 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test DELETE request - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Delete method is called HttpResponseMessage := RestClient.Delete('https://httpbin.org/delete'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/delete', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/delete', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); end; [Test] @@ -142,6 +171,7 @@ codeunit 134971 "Rest Client Tests" // [SCENARIO] Test GET request with headers // [GIVEN] An initialized Rest Client with default request headers + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); RestClient.SetDefaultRequestHeader('X-Test-Header', 'Test'); @@ -149,11 +179,11 @@ codeunit 134971 "Rest Client Tests" HttpResponseMessage := RestClient.Get('https://httpbin.org/get'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url'); - Assert.AreEqual(SelectJsonToken('$.headers.X-Test-Header', JsonObject).AsValue().AsText(), 'Test', 'The response should contain the expected header'); + Assert.AreEqual('https://httpbin.org/get', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); + Assert.AreEqual('Test', SelectJsonToken(JsonObject, '$.headers.X-Test-Header').AsValue().AsText(), 'The response should contain the expected header'); end; [Test] @@ -166,6 +196,7 @@ codeunit 134971 "Rest Client Tests" // [SCENARIO] Test GET request with base address // [GIVEN] An initialized Rest Client with base address + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); RestClient.SetBaseAddress('https://httpbin.org'); @@ -173,10 +204,10 @@ codeunit 134971 "Rest Client Tests" HttpResponseMessage := RestClient.Get('/get'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/get', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); end; [Test] @@ -188,17 +219,18 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test GET request with default User-Agent header - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Get method is called using the default User-Agent header HttpResponseMessage := RestClient.Get('https://httpbin.org/get'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.IsTrue(SelectJsonToken('$.headers.User-Agent', JsonObject).AsValue().AsText().StartsWith('Dynamics 365 Business Central '), 'The response should contain a User-Agent header'); + Assert.IsTrue(SelectJsonToken(JsonObject, '$.headers.User-Agent').AsValue().AsText().StartsWith('Dynamics 365 Business Central '), 'The response should contain a User-Agent header'); end; [Test] @@ -211,6 +243,7 @@ codeunit 134971 "Rest Client Tests" // [SCENARIO] Test GET request with custom User-Agent header // [GIVEN] An initialized Rest Client with a customer User-Agent header + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); RestClient.SetUserAgentHeader('BC Rest Client Test'); @@ -218,10 +251,10 @@ codeunit 134971 "Rest Client Tests" HttpResponseMessage := RestClient.Get('https://httpbin.org/get'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(SelectJsonToken('$.headers.User-Agent', JsonObject).AsValue().AsText(), 'BC Rest Client Test', 'The response should contain the expected User-Agent header'); + Assert.AreEqual('BC Rest Client Test', SelectJsonToken(JsonObject, '$.headers.User-Agent').AsValue().AsText(), 'The response should contain the expected User-Agent header'); end; [Test] @@ -232,14 +265,40 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test GET request with JSON response - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The GetAsJson method is called JsonObject := RestClient.GetAsJson('https://httpbin.org/get').AsObject(); // [THEN] The response contains the expected data - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/get', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); + end; + + [Test] + [ErrorBehavior(ErrorBehavior::Collect)] + procedure TestGetAsJsonWithCollectingErrors() + var + RestClient: Codeunit "Rest Client"; + JsonToken: JsonToken; + ExceptionList: List of [ErrorInfo]; + Exception: ErrorInfo; + begin + // [SCENARIO] Test GET request with JSON response + + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); + RestClient.Initialize(HttpClientHandler); + + // [WHEN] The GetAsJson method is called + JsonToken := RestClient.GetAsJson('https://httpbin.org/xml'); + + // [THEN] The response contains the expected data + ExceptionList := GetCollectedErrors(true); + Exception := ExceptionList.Get(1); + + Assert.AreEqual('The content is not a valid JSON.', Exception.Message, 'The collected error message should be as expected'); end; [Test] @@ -252,7 +311,8 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test POST request with JSON request and response - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [GIVEN] A Json object @@ -264,10 +324,10 @@ codeunit 134971 "Rest Client Tests" JsonObject2 := RestClient.PostAsJson('https://httpbin.org/post', JsonObject1).AsObject(); // [THEN] The response contains the expected data - Assert.AreEqual(GetJsonToken(JsonObject2, 'url').AsValue().AsText(), 'https://httpbin.org/post', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/post', GetJsonToken(JsonObject2, 'url').AsValue().AsText(), 'The response should contain the expected url'); JsonObject2.ReadFrom(GetJsonToken(JsonObject2, 'data').AsValue().AsText()); - Assert.AreEqual(GetJsonToken(JsonObject2, 'name').AsValue().AsText(), 'John', 'The response should contain the expected data'); - Assert.AreEqual(GetJsonToken(JsonObject2, 'age').AsValue().AsInteger(), 30, 'The response should contain the expected data'); + Assert.AreEqual('John', GetJsonToken(JsonObject2, 'name').AsValue().AsText(), 'The response should contain the expected data'); + Assert.AreEqual(30, GetJsonToken(JsonObject2, 'age').AsValue().AsInteger(), 'The response should contain the expected data'); end; [Test] @@ -280,7 +340,8 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test PATCH request with JSON request and response - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [GIVEN] A Json object @@ -292,10 +353,10 @@ codeunit 134971 "Rest Client Tests" JsonObject2 := RestClient.PatchAsJson('https://httpbin.org/patch', JsonObject1).AsObject(); // [THEN] The response contains the expected data - Assert.AreEqual(GetJsonToken(JsonObject2, 'url').AsValue().AsText(), 'https://httpbin.org/patch', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/patch', GetJsonToken(JsonObject2, 'url').AsValue().AsText(), 'The response should contain the expected url'); JsonObject2.ReadFrom(GetJsonToken(JsonObject2, 'data').AsValue().AsText()); - Assert.AreEqual(GetJsonToken(JsonObject2, 'name').AsValue().AsText(), 'John', 'The response should contain the expected data'); - Assert.AreEqual(GetJsonToken(JsonObject2, 'age').AsValue().AsInteger(), 30, 'The response should contain the expected data'); + Assert.AreEqual('John', GetJsonToken(JsonObject2, 'name').AsValue().AsText(), 'The response should contain the expected data'); + Assert.AreEqual(30, GetJsonToken(JsonObject2, 'age').AsValue().AsInteger(), 'The response should contain the expected data'); end; [Test] @@ -308,7 +369,8 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test PUT request with JSON request and response - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [GIVEN] A Json object @@ -320,10 +382,10 @@ codeunit 134971 "Rest Client Tests" JsonObject2 := RestClient.PutAsJson('https://httpbin.org/put', JsonObject1).AsObject(); // [THEN] The response contains the expected data - Assert.AreEqual(GetJsonToken(JsonObject2, 'url').AsValue().AsText(), 'https://httpbin.org/put', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/put', GetJsonToken(JsonObject2, 'url').AsValue().AsText(), 'The response should contain the expected url'); JsonObject2.ReadFrom(GetJsonToken(JsonObject2, 'data').AsValue().AsText()); - Assert.AreEqual(GetJsonToken(JsonObject2, 'name').AsValue().AsText(), 'John', 'The response should contain the expected data'); - Assert.AreEqual(GetJsonToken(JsonObject2, 'age').AsValue().AsInteger(), 30, 'The response should contain the expected data'); + Assert.AreEqual('John', GetJsonToken(JsonObject2, 'name').AsValue().AsText(), 'The response should contain the expected data'); + Assert.AreEqual(30, GetJsonToken(JsonObject2, 'age').AsValue().AsInteger(), 'The response should contain the expected data'); end; [Test] @@ -335,17 +397,18 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test Send method without Getcontent - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Send method is called without Getcontent HttpResponseMessage := RestClient.Send(Enum::"Http Method"::GET, 'https://httpbin.org/get'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/get', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); end; [Test] @@ -358,18 +421,19 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test Send method with Getcontent - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Send method is called with Getcontent HttpResponseMessage := RestClient.Send(Enum::"Http Method"::POST, 'https://httpbin.org/post', HttpContent.Create('Hello World')); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/post', 'The response should contain the expected url'); - Assert.AreEqual(GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'Hello World', 'The response should contain the expected data'); + Assert.AreEqual('https://httpbin.org/post', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); + Assert.AreEqual('Hello World', GetJsonToken(JsonObject, 'data').AsValue().AsText(), 'The response should contain the expected data'); end; [Test] @@ -382,7 +446,8 @@ codeunit 134971 "Rest Client Tests" begin // [SCENARIO] Test Send method with request message - // [GIVEN] An uninitialized Rest Client + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); RestClient.Initialize(HttpClientHandler); // [WHEN] The Send method is called with a request message @@ -391,10 +456,10 @@ codeunit 134971 "Rest Client Tests" HttpResponseMessage := RestClient.Send(ALHttpRequestMessage); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'https://httpbin.org/get', 'The response should contain the expected url'); + Assert.AreEqual('https://httpbin.org/get', GetJsonToken(JsonObject, 'url').AsValue().AsText(), 'The response should contain the expected url'); end; [Test] @@ -417,9 +482,152 @@ codeunit 134971 "Rest Client Tests" HttpResponseMessage := RestClient.Get('https://httpbin.org/basic-auth/user01/Password123'); // [THEN] The response contains the expected data - Assert.AreEqual(HttpResponseMessage.GetHttpStatusCode(), 200, 'The response status code should be 200'); + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); + JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); + Assert.AreEqual(true, GetJsonToken(JsonObject, 'authenticated').AsValue().AsBoolean(), 'The response should contain the expected data'); + end; + + [Test] + procedure TestResponseWithCookies() + var + RestClient: Codeunit "Rest Client"; + HttpResponseMessage: Codeunit "Http Response Message"; + begin + // [SCENARIO] Test GET request with cookies + + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); + RestClient.Initialize(HttpClientHandler); + + // [WHEN] The Get method is called + HttpResponseMessage := RestClient.Get('https://postman-echo.com/get'); + + // [THEN] The response contains the expected data + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); + Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); + Assert.IsTrue(HttpResponseMessage.GetCookieNames().Contains('sails.sid'), 'The response should contain the expected cookie'); + end; + + [Test] + procedure TestRequestWithCookies() + var + RestClient: Codeunit "Rest Client"; + HttpRequestMessage: Codeunit "Http Request Message"; + HttpResponseMessage: Codeunit "Http Response Message"; + JsonObject: JsonObject; + begin + // [SCENARIO] Test GET request with cookies + + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); + RestClient.Initialize(HttpClientHandler); + + // [GIVEN] A request message with cookies + HttpRequestMessage.SetRequestUri('https://httpbin.org/cookies'); + HttpRequestMessage.SetCookie('cookie1', 'value1'); + HttpRequestMessage.SetCookie('cookie2', 'value2'); + + // [WHEN] The Send method is called with a request message + HttpResponseMessage := RestClient.Send(HttpRequestMessage); + + // [THEN] The response contains the expected data + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); + Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); + JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); + Assert.AreEqual('value1', SelectJsonToken(JsonObject, '$.cookies.cookie1').AsValue().AsText(), 'The response should contain the expected cookie1'); + Assert.AreEqual('value2', SelectJsonToken(JsonObject, '$.cookies.cookie2').AsValue().AsText(), 'The response should contain the expected cookie2'); + end; + + [Test] + procedure TestWithoutUseResponseCookies() + var + RestClient: Codeunit "Rest Client"; + HttpResponseMessage: Codeunit "Http Response Message"; + JsonObject: JsonObject; + JsonToken: JsonToken; + begin + // [SCENARIO] Test GET request without using response cookies + + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); + RestClient.Initialize(HttpClientHandler); + + // [GIVEN] Use response cookies is disabled + RestClient.SetUseResponseCookies(false); + + // [GIVEN] Specific cookies are set + HttpResponseMessage := RestClient.Get('https://httpbin.org/cookies/set?cookie1=value1'); + + // [WHEN] The cookies list is retrieved + HttpResponseMessage := RestClient.Get('https://httpbin.org/cookies'); + + // [THEN] The response contains the expected data + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); + Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); + JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); + Assert.IsFalse(JsonObject.SelectToken('$.cookies.cookie1', JsonToken), 'The response should not contain cookies'); + end; + + [Test] + procedure TestWithUseResponseCookies() + var + RestClient: Codeunit "Rest Client"; + HttpResponseMessage: Codeunit "Http Response Message"; + JsonObject: JsonObject; + begin + // [SCENARIO] Test GET request with using response cookies + + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); + RestClient.Initialize(HttpClientHandler); + + // [GIVEN] Use response cookies is enabled + RestClient.SetUseResponseCookies(true); + + // [GIVEN] Specific cookies are set + HttpResponseMessage := RestClient.Get('https://httpbin.org/cookies/set?cookie1=value1'); + + // [WHEN] The cookies list is retrieved + HttpResponseMessage := RestClient.Get('https://httpbin.org/cookies'); + + // [THEN] The response contains the expected data + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); + Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); + JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); + Assert.AreEqual('value1', SelectJsonToken(JsonObject, '$.cookies.cookie1').AsValue().AsText(), 'The response should contain the expected cookie'); + end; + + [Test] + procedure TestUseResponseCookiesWithAdditionalCookies() + var + RestClient: Codeunit "Rest Client"; + HttpRequestMessage: Codeunit "Http Request Message"; + HttpResponseMessage: Codeunit "Http Response Message"; + JsonObject: JsonObject; + begin + // [SCENARIO] Test GET request with using response cookies and additional cookies + + // [GIVEN] An initialized Rest Client + HttpClientHandler.Initialize(); + RestClient.Initialize(HttpClientHandler); + + // [GIVEN] Use response cookies is enabled + RestClient.SetUseResponseCookies(true); + + // [GIVEN] Specific cookies are set + HttpResponseMessage := RestClient.Get('https://httpbin.org/cookies/set?cookie1=value1'); + + // [WHEN] The cookies list is retrieved with additional cookies + HttpRequestMessage.SetRequestUri('https://httpbin.org/cookies'); + HttpRequestMessage.SetCookie('cookie2', 'value2'); + HttpResponseMessage := RestClient.Send(HttpRequestMessage); + + // [THEN] The response contains the expected data + Assert.AreEqual(200, HttpResponseMessage.GetHttpStatusCode(), 'The response status code should be 200'); + Assert.IsTrue(HttpResponseMessage.GetIsSuccessStatusCode(), 'GetIsSuccessStatusCode should be true'); JsonObject := HttpResponseMessage.GetContent().AsJson().AsObject(); - Assert.AreEqual(GetJsonToken(JsonObject, 'authenticated').AsValue().AsBoolean(), true, 'The response should contain the expected data'); + Assert.AreEqual('value1', SelectJsonToken(JsonObject, '$.cookies.cookie1').AsValue().AsText(), 'The response should contain the expected cookie1'); + Assert.AreEqual('value2', SelectJsonToken(JsonObject, '$.cookies.cookie2').AsValue().AsText(), 'The response should contain the expected cookie2'); end; local procedure GetJsonToken(JsonObject: JsonObject; Name: Text) JsonToken: JsonToken @@ -427,7 +635,7 @@ codeunit 134971 "Rest Client Tests" JsonObject.Get(Name, JsonToken); end; - local procedure SelectJsonToken(Path: Text; JsonObject: JsonObject) JsonToken: JsonToken + local procedure SelectJsonToken(JsonObject: JsonObject; Path: Text) JsonToken: JsonToken begin JsonObject.SelectToken(Path, JsonToken); end; diff --git a/src/System Application/Test/Rest Client/src/TestHttpClientHandler.Codeunit.al b/src/System Application/Test/Rest Client/src/TestHttpClientHandler.Codeunit.al index 379b36ccf1..4f89e806a0 100644 --- a/src/System Application/Test/Rest Client/src/TestHttpClientHandler.Codeunit.al +++ b/src/System Application/Test/Rest Client/src/TestHttpClientHandler.Codeunit.al @@ -11,11 +11,62 @@ codeunit 134974 "Test Http Client Handler" implements "Http Client Handler" { SingleInstance = true; + var + MockConnectionFailed: Boolean; + MockIsBlockedByEnvironment: Boolean; + MockRequestFailed: Boolean; + procedure Send(HttpClient: HttpClient; HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean; var ResponseMessage: HttpResponseMessage; begin + if MockConnectionFailed then begin + Success := false; + exit; + end; + + if MockIsBlockedByEnvironment then begin + Success := false; + HttpResponseMessage.SetIsBlockedByEnvironment(true); + exit; + end; + + if MockRequestFailed then begin + Success := true; + HttpResponseMessage.SetHttpStatusCode(400); + HttpResponseMessage.SetReasonPhrase('Bad Request'); + exit; + end; + Success := HttpClient.Send(HttpRequestMessage.GetHttpRequestMessage(), ResponseMessage); HttpResponseMessage.SetResponseMessage(ResponseMessage); end; + + procedure Initialize() + begin + MockConnectionFailed := false; + MockIsBlockedByEnvironment := false; + MockRequestFailed := false; + end; + + procedure SetMockConnectionFailed() + begin + MockConnectionFailed := true; + MockIsBlockedByEnvironment := false; + MockRequestFailed := false; + end; + + procedure SetMockIsBlockedByEnvironment() + begin + MockConnectionFailed := false; + MockIsBlockedByEnvironment := true; + MockRequestFailed := false; + end; + + procedure SetMockRequestFailed() + begin + MockConnectionFailed := false; + MockIsBlockedByEnvironment := false; + MockRequestFailed := true; + end; } \ No newline at end of file From 4abc3fba4b936f2367e26a47cda3339b2f3652a4 Mon Sep 17 00:00:00 2001 From: Arend-Jan Kauffmann Date: Mon, 9 Dec 2024 20:26:27 +0100 Subject: [PATCH 05/12] Adds access modifier to objects --- .../HttpAuthOAuthClientCredentials.Codeunit.al | 1 + .../Authentication/HttpAuthenticationAnonymous.Codeunit.al | 1 + .../src/Authentication/HttpAuthenticationBasic.Codeunit.al | 1 + .../src/ExceptionHandling/RestClientException.Enum.al | 4 ++++ .../ExceptionHandling/RestClientExceptionBuilder.Codeunit.al | 5 +++++ .../src/HttpClientHandler/HttpClientHandler.Codeunit.al | 1 + .../App/Rest Client/src/HttpContent.Codeunit.al | 1 + .../App/Rest Client/src/HttpContentImpl.Codeunit.al | 3 +-- .../App/Rest Client/src/HttpRequestMessage.Codeunit.al | 1 + .../App/Rest Client/src/HttpResponseMessage.Codeunit.al | 1 + .../App/Rest Client/src/RestClient.Codeunit.al | 1 + 11 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/System Application/App/Rest Client/src/Authentication/HttpAuthOAuthClientCredentials.Codeunit.al b/src/System Application/App/Rest Client/src/Authentication/HttpAuthOAuthClientCredentials.Codeunit.al index 3cccb6fb90..cd73e7752e 100644 --- a/src/System Application/App/Rest Client/src/Authentication/HttpAuthOAuthClientCredentials.Codeunit.al +++ b/src/System Application/App/Rest Client/src/Authentication/HttpAuthOAuthClientCredentials.Codeunit.al @@ -7,6 +7,7 @@ namespace System.RestClient; codeunit 2361 "HttpAuthOAuthClientCredentials" implements "Http Authentication" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; diff --git a/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationAnonymous.Codeunit.al b/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationAnonymous.Codeunit.al index abf1c542c3..3ca85bf67e 100644 --- a/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationAnonymous.Codeunit.al +++ b/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationAnonymous.Codeunit.al @@ -7,6 +7,7 @@ namespace System.RestClient; /// Implementation of the "Http Authentication" interface for a anonymous request. codeunit 2358 "Http Authentication Anonymous" implements "Http Authentication" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; diff --git a/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationBasic.Codeunit.al b/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationBasic.Codeunit.al index 552d139504..f5499c06a3 100644 --- a/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationBasic.Codeunit.al +++ b/src/System Application/App/Rest Client/src/Authentication/HttpAuthenticationBasic.Codeunit.al @@ -9,6 +9,7 @@ using System; codeunit 2359 "Http Authentication Basic" implements "Http Authentication" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; diff --git a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientException.Enum.al b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientException.Enum.al index f97c0bf79f..44d9447078 100644 --- a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientException.Enum.al +++ b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientException.Enum.al @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ namespace System.RestClient; /// diff --git a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al index e0312ffdc4..97d7ea1963 100644 --- a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al +++ b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al @@ -1,7 +1,12 @@ +// ------------------------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// ------------------------------------------------------------------------------------------------ namespace System.RestClient; codeunit 2362 "Rest Client Exception Builder" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; diff --git a/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al b/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al index a82672eae5..edd33ee443 100644 --- a/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al @@ -6,6 +6,7 @@ namespace System.RestClient; codeunit 2360 "Http Client Handler" implements "Http Client Handler" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; diff --git a/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al b/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al index 508c75905a..8c1caf0592 100644 --- a/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpContent.Codeunit.al @@ -9,6 +9,7 @@ using System.Utilities; /// Holder object for the Http Content data. codeunit 2354 "Http Content" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; diff --git a/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al b/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al index 69ae541e73..79c4d8a384 100644 --- a/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al @@ -8,11 +8,10 @@ using System.Utilities; codeunit 2355 "Http Content Impl." { + Access = Internal; InherentEntitlements = X; InherentPermissions = X; - Access = Internal; - var HttpContent: HttpContent; ContentTypeEmptyErr: Label 'The value of the Content-Type header must be specified.'; diff --git a/src/System Application/App/Rest Client/src/HttpRequestMessage.Codeunit.al b/src/System Application/App/Rest Client/src/HttpRequestMessage.Codeunit.al index 825a646117..9bbbf56dcc 100644 --- a/src/System Application/App/Rest Client/src/HttpRequestMessage.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpRequestMessage.Codeunit.al @@ -7,6 +7,7 @@ namespace System.RestClient; /// Holder object for the HTTP request data. codeunit 2352 "Http Request Message" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; diff --git a/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al b/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al index 0e6532847e..cdb1833173 100644 --- a/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpResponseMessage.Codeunit.al @@ -7,6 +7,7 @@ namespace System.RestClient; /// Holder object for the HTTP response data. codeunit 2356 "Http Response Message" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; diff --git a/src/System Application/App/Rest Client/src/RestClient.Codeunit.al b/src/System Application/App/Rest Client/src/RestClient.Codeunit.al index 9882413460..5d7f69cb08 100644 --- a/src/System Application/App/Rest Client/src/RestClient.Codeunit.al +++ b/src/System Application/App/Rest Client/src/RestClient.Codeunit.al @@ -7,6 +7,7 @@ namespace System.RestClient; /// Provides functionality to easily work with the HttpClient object. codeunit 2350 "Rest Client" { + Access = Public; InherentEntitlements = X; InherentPermissions = X; From 83e3183728075566a6113a734ec71a0a9f83187f Mon Sep 17 00:00:00 2001 From: Arend-Jan Kauffmann Date: Tue, 10 Dec 2024 09:19:02 +0100 Subject: [PATCH 06/12] Enhance Rest Client Exception handling with documentation --- .../RestClientException.Enum.al | 18 ++++++++++++++++++ .../RestClientExceptionBuilder.Codeunit.al | 9 +++++++++ 2 files changed, 27 insertions(+) diff --git a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientException.Enum.al b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientException.Enum.al index 44d9447078..78fc1c25b7 100644 --- a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientException.Enum.al +++ b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientException.Enum.al @@ -9,26 +9,44 @@ namespace System.RestClient; /// enum 2351 "Rest Client Exception" { + /// + /// Specifies that the exception is an unknown exception. + /// value(100; UnknownException) { Caption = 'Unknown Exception'; } + /// + /// Specifies that the connection failed. + /// value(101; ConnectionFailed) { Caption = 'Connection Failed'; } + /// + /// Specifies that the request is blocked by the environment. + /// value(102; BlockedByEnvironment) { Caption = 'Blocked By Environment'; } + /// + /// Specifies that the request failed. + /// value(103; RequestFailed) { Caption = 'Request Failed'; } + /// + /// Specifies that the content is not valid JSON. + /// value(201; InvalidJson) { Caption = 'Invalid Json'; } + /// + /// Specifies that the content is not valid XML. + /// value(202; InvalidXml) { Caption = 'Invalid Xml'; diff --git a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al index 97d7ea1963..6e71e2047a 100644 --- a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al +++ b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al @@ -10,11 +10,17 @@ codeunit 2362 "Rest Client Exception Builder" InherentEntitlements = X; InherentPermissions = X; + /// + /// Creates an exception with the specified error code and message. The exception is collectible if the errors are currently being collected. + /// procedure CreateException(RestClientException: Enum "Rest Client Exception"; ErrorMessage: Text) Exception: ErrorInfo begin Exception := CreateException(RestClientException, ErrorMessage, IsCollectingErrors()); end; + /// + /// Creates an exception with the specified error code, message, and collectible flag. + /// procedure CreateException(RestClientException: Enum "Rest Client Exception"; ErrorMessage: Text; Collectible: Boolean) Exception: ErrorInfo begin Exception.Message := ErrorMessage; @@ -23,6 +29,9 @@ codeunit 2362 "Rest Client Exception Builder" Exception.Collectible := Collectible; end; + /// + /// Gets the exception code from the error info. + /// procedure GetRestClientException(ErrInfo: ErrorInfo) RestClientException: Enum "Rest Client Exception" var ExceptionCode: Integer; From 1f7b39496232a5584cfc2919d4f39bd2fd5274db Mon Sep 17 00:00:00 2001 From: Arend-Jan Kauffmann Date: Tue, 10 Dec 2024 10:10:02 +0100 Subject: [PATCH 07/12] Enhance documentation for RestClientExceptionBuilder procedures --- .../RestClientExceptionBuilder.Codeunit.al | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al index 6e71e2047a..177845c2b2 100644 --- a/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al +++ b/src/System Application/App/Rest Client/src/ExceptionHandling/RestClientExceptionBuilder.Codeunit.al @@ -13,6 +13,9 @@ codeunit 2362 "Rest Client Exception Builder" /// /// Creates an exception with the specified error code and message. The exception is collectible if the errors are currently being collected. /// + /// The error code for the exception. + /// The message for the exception. + /// The exception with the specified error code and message. procedure CreateException(RestClientException: Enum "Rest Client Exception"; ErrorMessage: Text) Exception: ErrorInfo begin Exception := CreateException(RestClientException, ErrorMessage, IsCollectingErrors()); @@ -21,6 +24,10 @@ codeunit 2362 "Rest Client Exception Builder" /// /// Creates an exception with the specified error code, message, and collectible flag. /// + /// The error code for the exception. + /// The message for the exception. + /// Whether the exception is collectible. + /// The exception with the specified error code, message, and collectible flag. procedure CreateException(RestClientException: Enum "Rest Client Exception"; ErrorMessage: Text; Collectible: Boolean) Exception: ErrorInfo begin Exception.Message := ErrorMessage; @@ -32,6 +39,8 @@ codeunit 2362 "Rest Client Exception Builder" /// /// Gets the exception code from the error info. /// + /// The error info of the exception. + /// The exception code. procedure GetRestClientException(ErrInfo: ErrorInfo) RestClientException: Enum "Rest Client Exception" var ExceptionCode: Integer; From 16bdfbe835dcf60480d8d3bc581327c06a6aa8ca Mon Sep 17 00:00:00 2001 From: Jesper Schulz-Wedde Date: Tue, 10 Dec 2024 11:33:52 +0100 Subject: [PATCH 08/12] Updating ID of test. --- src/System Application/Test/Rest Client/app.json | 2 +- .../Test/Rest Client/src/HttpExceptionTests.Codeunit.al | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/System Application/Test/Rest Client/app.json b/src/System Application/Test/Rest Client/app.json index 384fd9f7ec..d6ad2f8ba4 100644 --- a/src/System Application/Test/Rest Client/app.json +++ b/src/System Application/Test/Rest Client/app.json @@ -35,7 +35,7 @@ "idRanges": [ { "from": 134970, - "to": 134975 + "to": 134977 } ], "resourceExposurePolicy": { diff --git a/src/System Application/Test/Rest Client/src/HttpExceptionTests.Codeunit.al b/src/System Application/Test/Rest Client/src/HttpExceptionTests.Codeunit.al index c2d6db39e6..5a9d00379b 100644 --- a/src/System Application/Test/Rest Client/src/HttpExceptionTests.Codeunit.al +++ b/src/System Application/Test/Rest Client/src/HttpExceptionTests.Codeunit.al @@ -8,7 +8,7 @@ namespace System.Test.RestClient; using System.RestClient; using System.TestLibraries.Utilities; -codeunit 134975 "Http Exception Tests" +codeunit 134977 "Http Exception Tests" { Subtype = Test; From 160d87fe2ee39b4af79187afe6c0d8dbd94bc1db Mon Sep 17 00:00:00 2001 From: Arend-Jan Kauffmann Date: Tue, 10 Dec 2024 11:40:44 +0100 Subject: [PATCH 09/12] Refactor HttpContentImpl to improve readability --- .../src/HttpContentImpl.Codeunit.al | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al b/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al index 79c4d8a384..fb4409d007 100644 --- a/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al @@ -197,9 +197,10 @@ codeunit 2355 "Http Content Impl." Json: Text; begin Json := AsText(); - if Json <> '' then - if not ReturnValue.ReadFrom(AsText()) then - ThrowInvalidJsonException(); + if Json = '' then + exit; + if not ReturnValue.ReadFrom(Json) then + ThrowInvalidJsonException(); end; procedure AsJsonObject() ReturnValue: JsonObject @@ -207,9 +208,10 @@ codeunit 2355 "Http Content Impl." Json: Text; begin Json := AsText(); - if Json <> '' then - if not ReturnValue.ReadFrom(AsText()) then - ThrowInvalidJsonException(); + if Json = '' then + exit; + if not ReturnValue.ReadFrom(Json) then + ThrowInvalidJsonException(); end; procedure AsJsonArray() ReturnValue: JsonArray @@ -217,9 +219,10 @@ codeunit 2355 "Http Content Impl." Json: Text; begin Json := AsText(); - if Json <> '' then - if not ReturnValue.ReadFrom(AsText()) then - ThrowInvalidJsonException(); + if Json = '' then + exit; + if not ReturnValue.ReadFrom(Json) then + ThrowInvalidJsonException(); end; procedure SetContent(Content: Text; ContentType: Text) From b7ef236c5dc239610e247471529f60c09aef6917 Mon Sep 17 00:00:00 2001 From: Arend-Jan Kauffmann Date: Wed, 11 Dec 2024 14:09:23 +0100 Subject: [PATCH 10/12] Refactor HttpClientHandler and HttpContentImpl to improve variable naming and remove the usage of this for clarity and readability. --- .../HttpClientHandler.Codeunit.al | 4 +- .../HttpClientHandler.Interface.al | 2 +- .../src/HttpContentImpl.Codeunit.al | 64 +++++++------- .../src/HttpRequestMessageImpl.Codeunit.al | 86 +++++++++---------- .../src/HttpResponseMessageImpl.Codeunit.al | 64 +++++++------- .../src/RestClientImpl.Codeunit.al | 62 ++++++------- 6 files changed, 141 insertions(+), 141 deletions(-) diff --git a/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al b/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al index edd33ee443..41f24996e3 100644 --- a/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Codeunit.al @@ -10,11 +10,11 @@ codeunit 2360 "Http Client Handler" implements "Http Client Handler" InherentEntitlements = X; InherentPermissions = X; - procedure Send(HttpClient: HttpClient; HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean; + procedure Send(CurrHttpClientInstance: HttpClient; HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean; var ResponseMessage: HttpResponseMessage; begin - Success := HttpClient.Send(HttpRequestMessage.GetHttpRequestMessage(), ResponseMessage); + Success := CurrHttpClientInstance.Send(HttpRequestMessage.GetHttpRequestMessage(), ResponseMessage); HttpResponseMessage := HttpResponseMessage.Create(ResponseMessage); end; } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Interface.al b/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Interface.al index 849a4605ea..b4d492dfd2 100644 --- a/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Interface.al +++ b/src/System Application/App/Rest Client/src/HttpClientHandler/HttpClientHandler.Interface.al @@ -6,5 +6,5 @@ namespace System.RestClient; interface "Http Client Handler" { - procedure Send(HttpClient: HttpClient; HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean; + procedure Send(CurrHttpClientInstance: HttpClient; HttpRequestMessage: Codeunit "Http Request Message"; var HttpResponseMessage: Codeunit "Http Response Message") Success: Boolean; } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al b/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al index fb4409d007..8b1488e5c5 100644 --- a/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpContentImpl.Codeunit.al @@ -13,7 +13,7 @@ codeunit 2355 "Http Content Impl." InherentPermissions = X; var - HttpContent: HttpContent; + CurrHttpContentInstance: HttpContent; ContentTypeEmptyErr: Label 'The value of the Content-Type header must be specified.'; MimeTypeTextPlainTxt: Label 'text/plain', Locked = true; MimeTypeTextXmlTxt: Label 'text/xml', Locked = true; @@ -114,58 +114,58 @@ codeunit 2355 "Http Content Impl." procedure AddContentEncoding(ContentEncoding: Text) var - Headers: HttpHeaders; + ContentHeaders: HttpHeaders; begin - if not this.HttpContent.GetHeaders(Headers) then begin - this.HttpContent.Clear(); - this.HttpContent.GetHeaders(Headers); + if not CurrHttpContentInstance.GetHeaders(ContentHeaders) then begin + CurrHttpContentInstance.Clear(); + CurrHttpContentInstance.GetHeaders(ContentHeaders); end; - Headers.Add('Content-Encoding', ContentEncoding); + ContentHeaders.Add('Content-Encoding', ContentEncoding); end; procedure SetHeader(Name: Text; Value: Text) var - Headers: HttpHeaders; + ContentHeaders: HttpHeaders; begin - if not this.HttpContent.GetHeaders(Headers) then begin - this.HttpContent.Clear(); - this.HttpContent.GetHeaders(Headers); + if not CurrHttpContentInstance.GetHeaders(ContentHeaders) then begin + CurrHttpContentInstance.Clear(); + CurrHttpContentInstance.GetHeaders(ContentHeaders); end; - if Headers.Contains(Name) or Headers.ContainsSecret(Name) then - Headers.Remove(Name); + if ContentHeaders.Contains(Name) or ContentHeaders.ContainsSecret(Name) then + ContentHeaders.Remove(Name); - Headers.Add(Name, Value); + ContentHeaders.Add(Name, Value); end; procedure SetHeader(Name: Text; Value: SecretText) var - Headers: HttpHeaders; + ContentHeaders: HttpHeaders; begin - if not this.HttpContent.GetHeaders(Headers) then begin - this.HttpContent.Clear(); - this.HttpContent.GetHeaders(Headers); + if not CurrHttpContentInstance.GetHeaders(ContentHeaders) then begin + CurrHttpContentInstance.Clear(); + CurrHttpContentInstance.GetHeaders(ContentHeaders); end; - if Headers.Contains(Name) or Headers.ContainsSecret(Name) then - Headers.Remove(Name); + if ContentHeaders.Contains(Name) or ContentHeaders.ContainsSecret(Name) then + ContentHeaders.Remove(Name); - Headers.Add(Name, Value); + ContentHeaders.Add(Name, Value); end; procedure GetHttpContent() ReturnValue: HttpContent begin - ReturnValue := this.HttpContent; + ReturnValue := CurrHttpContentInstance; end; procedure AsText() ReturnValue: Text begin - this.HttpContent.ReadAs(ReturnValue); + CurrHttpContentInstance.ReadAs(ReturnValue); end; procedure AsSecretText() ReturnValue: SecretText begin - this.HttpContent.ReadAs(ReturnValue); + CurrHttpContentInstance.ReadAs(ReturnValue); end; procedure AsBlob() ReturnValue: Codeunit "Temp Blob" @@ -173,14 +173,14 @@ codeunit 2355 "Http Content Impl." InStr: InStream; OutStr: OutStream; begin - this.HttpContent.ReadAs(InStr); + CurrHttpContentInstance.ReadAs(InStr); ReturnValue.CreateOutStream(OutStr); CopyStream(OutStr, InStr); end; procedure AsInStream(var InStr: InStream) begin - this.HttpContent.ReadAs(InStr); + CurrHttpContentInstance.ReadAs(InStr); end; procedure AsXmlDocument() ReturnValue: XmlDocument @@ -227,8 +227,8 @@ codeunit 2355 "Http Content Impl." procedure SetContent(Content: Text; ContentType: Text) begin - this.HttpContent.Clear(); - this.HttpContent.WriteFrom(Content); + CurrHttpContentInstance.Clear(); + CurrHttpContentInstance.WriteFrom(Content); if ContentType = '' then ContentType := MimeTypeTextPlainTxt; SetContentTypeHeader(ContentType); @@ -236,8 +236,8 @@ codeunit 2355 "Http Content Impl." procedure SetContent(Content: SecretText; ContentType: Text) begin - this.HttpContent.Clear(); - this.HttpContent.WriteFrom(Content); + CurrHttpContentInstance.Clear(); + CurrHttpContentInstance.WriteFrom(Content); if ContentType = '' then ContentType := MimeTypeTextPlainTxt; SetContentTypeHeader(ContentType); @@ -245,8 +245,8 @@ codeunit 2355 "Http Content Impl." procedure SetContent(Content: InStream; ContentType: Text) begin - this.HttpContent.Clear(); - this.HttpContent.WriteFrom(Content); + CurrHttpContentInstance.Clear(); + CurrHttpContentInstance.WriteFrom(Content); if ContentType = '' then ContentType := MimeTypeApplicationOctetStreamTxt; SetContentTypeHeader(ContentType); @@ -282,7 +282,7 @@ codeunit 2355 "Http Content Impl." procedure SetContent(var Value: HttpContent) begin - this.HttpContent := Value; + CurrHttpContentInstance := Value; end; local procedure ThrowInvalidJsonException() diff --git a/src/System Application/App/Rest Client/src/HttpRequestMessageImpl.Codeunit.al b/src/System Application/App/Rest Client/src/HttpRequestMessageImpl.Codeunit.al index 1da949178b..6ac1c89795 100644 --- a/src/System Application/App/Rest Client/src/HttpRequestMessageImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpRequestMessageImpl.Codeunit.al @@ -11,7 +11,7 @@ codeunit 2353 "Http Request Message Impl." InherentPermissions = X; var - HttpRequestMessage: HttpRequestMessage; + CurrHttpRequestMessageInstance: HttpRequestMessage; procedure Create(Method: Enum "Http Method"; RequestUri: Text; Content: Codeunit "Http Content"): Codeunit "Http Request Message Impl." begin @@ -24,7 +24,7 @@ codeunit 2353 "Http Request Message Impl." procedure SetHttpMethod(Method: Text) begin - HttpRequestMessage.Method := Method; + CurrHttpRequestMessageInstance.Method := Method; end; procedure SetHttpMethod(Method: Enum "Http Method") @@ -34,52 +34,52 @@ codeunit 2353 "Http Request Message Impl." procedure GetHttpMethod() ReturnValue: Text begin - ReturnValue := HttpRequestMessage.Method; + ReturnValue := CurrHttpRequestMessageInstance.Method; end; procedure SetRequestUri(Uri: Text) begin - HttpRequestMessage.SetRequestUri(Uri); + CurrHttpRequestMessageInstance.SetRequestUri(Uri); end; procedure GetRequestUri() Uri: Text begin - Uri := HttpRequestMessage.GetRequestUri(); + Uri := CurrHttpRequestMessageInstance.GetRequestUri(); end; procedure SetHeader(HeaderName: Text; HeaderValue: Text) var - HttpHeaders: HttpHeaders; + RequestHttpHeaders: HttpHeaders; begin - HttpRequestMessage.GetHeaders(HttpHeaders); - if HttpHeaders.Contains(HeaderName) or HttpHeaders.ContainsSecret(HeaderName) then - HttpHeaders.Remove(HeaderName); - HttpHeaders.Add(HeaderName, HeaderValue); + CurrHttpRequestMessageInstance.GetHeaders(RequestHttpHeaders); + if RequestHttpHeaders.Contains(HeaderName) or RequestHttpHeaders.ContainsSecret(HeaderName) then + RequestHttpHeaders.Remove(HeaderName); + RequestHttpHeaders.Add(HeaderName, HeaderValue); end; procedure SetHeader(HeaderName: Text; HeaderValue: SecretText) var - HttpHeaders: HttpHeaders; + RequestHttpHeaders: HttpHeaders; begin - HttpRequestMessage.GetHeaders(HttpHeaders); - if HttpHeaders.Contains(HeaderName) or HttpHeaders.ContainsSecret(HeaderName) then - HttpHeaders.Remove(HeaderName); - HttpHeaders.Add(HeaderName, HeaderValue); + CurrHttpRequestMessageInstance.GetHeaders(RequestHttpHeaders); + if RequestHttpHeaders.Contains(HeaderName) or RequestHttpHeaders.ContainsSecret(HeaderName) then + RequestHttpHeaders.Remove(HeaderName); + RequestHttpHeaders.Add(HeaderName, HeaderValue); end; procedure GetHeaders() ReturnValue: HttpHeaders begin - HttpRequestMessage.GetHeaders(ReturnValue); + CurrHttpRequestMessageInstance.GetHeaders(ReturnValue); end; procedure GetHeaderValue(HeaderName: Text) Value: Text var - HttpHeaders: HttpHeaders; + RequestHttpHeaders: HttpHeaders; Values: List of [Text]; begin - HttpRequestMessage.GetHeaders(HttpHeaders); - if HttpHeaders.Contains(HeaderName) then begin - HttpHeaders.GetValues(HeaderName, Values); + CurrHttpRequestMessageInstance.GetHeaders(RequestHttpHeaders); + if RequestHttpHeaders.Contains(HeaderName) then begin + RequestHttpHeaders.GetValues(HeaderName, Values); if Values.Count > 0 then Value := Values.Get(1); end; @@ -87,75 +87,75 @@ codeunit 2353 "Http Request Message Impl." procedure GetHeaderValues(HeaderName: Text) Values: List of [Text] var - HttpHeaders: HttpHeaders; + RequestHttpHeaders: HttpHeaders; begin - HttpRequestMessage.GetHeaders(HttpHeaders); - if HttpHeaders.Contains(HeaderName) then - HttpHeaders.GetValues(HeaderName, Values); + CurrHttpRequestMessageInstance.GetHeaders(RequestHttpHeaders); + if RequestHttpHeaders.Contains(HeaderName) then + RequestHttpHeaders.GetValues(HeaderName, Values); end; procedure GetSecretHeaderValues(HeaderName: Text) Values: List of [SecretText] var - HttpHeaders: HttpHeaders; + RequestHttpHeaders: HttpHeaders; begin - HttpRequestMessage.GetHeaders(HttpHeaders); - if HttpHeaders.ContainsSecret(HeaderName) then - HttpHeaders.GetSecretValues(HeaderName, Values); + CurrHttpRequestMessageInstance.GetHeaders(RequestHttpHeaders); + if RequestHttpHeaders.ContainsSecret(HeaderName) then + RequestHttpHeaders.GetSecretValues(HeaderName, Values); end; procedure SetCookie(Name: Text; Value: Text) Success: Boolean begin - Success := HttpRequestMessage.SetCookie(Name, Value); + Success := CurrHttpRequestMessageInstance.SetCookie(Name, Value); end; - procedure SetCookie(Cookie: Cookie) Success: Boolean + procedure SetCookie(TheCookie: Cookie) Success: Boolean begin - Success := HttpRequestMessage.SetCookie(Cookie); + Success := CurrHttpRequestMessageInstance.SetCookie(TheCookie); end; procedure GetCookieNames() CookieNames: List of [Text] begin - CookieNames := HttpRequestMessage.GetCookieNames(); + CookieNames := CurrHttpRequestMessageInstance.GetCookieNames(); end; procedure GetCookies() Cookies: List of [Cookie] var CookieName: Text; - Cookie: Cookie; + TheCookie: Cookie; begin - foreach CookieName in HttpRequestMessage.GetCookieNames() do begin - HttpRequestMessage.GetCookie(CookieName, Cookie); - Cookies.Add(Cookie); + foreach CookieName in CurrHttpRequestMessageInstance.GetCookieNames() do begin + CurrHttpRequestMessageInstance.GetCookie(CookieName, TheCookie); + Cookies.Add(TheCookie); end; end; procedure GetCookie(Name: Text) ReturnValue: Cookie begin - if HttpRequestMessage.GetCookie(Name, ReturnValue) then; + if CurrHttpRequestMessageInstance.GetCookie(Name, ReturnValue) then; end; - procedure GetCookie(Name: Text; var Cookie: Cookie) Success: Boolean + procedure GetCookie(Name: Text; var TheCookie: Cookie) Success: Boolean begin - Success := HttpRequestMessage.GetCookie(Name, Cookie); + Success := CurrHttpRequestMessageInstance.GetCookie(Name, TheCookie); end; procedure RemoveCookie(Name: Text) Success: Boolean begin - Success := HttpRequestMessage.RemoveCookie(Name); + Success := CurrHttpRequestMessageInstance.RemoveCookie(Name); end; procedure SetHttpRequestMessage(var RequestMessage: HttpRequestMessage) begin - HttpRequestMessage := RequestMessage; + CurrHttpRequestMessageInstance := RequestMessage; end; procedure SetContent(HttpContent: Codeunit "Http Content") begin - HttpRequestMessage.Content := HttpContent.GetHttpContent(); + CurrHttpRequestMessageInstance.Content := HttpContent.GetHttpContent(); end; procedure GetRequestMessage() ReturnValue: HttpRequestMessage begin - ReturnValue := HttpRequestMessage; + ReturnValue := CurrHttpRequestMessageInstance; end; } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al b/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al index f58045ee9a..c6117a53e1 100644 --- a/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/HttpResponseMessageImpl.Codeunit.al @@ -24,12 +24,12 @@ codeunit 2357 "Http Response Message Impl." procedure SetIsBlockedByEnvironment(Value: Boolean) begin - this.IsBlockedByEnvironment := Value; + IsBlockedByEnvironment := Value; end; procedure GetIsBlockedByEnvironment() ReturnValue: Boolean begin - ReturnValue := this.IsBlockedByEnvironment; + ReturnValue := IsBlockedByEnvironment; end; #endregion @@ -39,13 +39,13 @@ codeunit 2357 "Http Response Message Impl." procedure SetHttpStatusCode(Value: Integer) begin - this.HttpStatusCode := Value; - this.IsSuccessStatusCode := Value in [200 .. 299]; + HttpStatusCode := Value; + IsSuccessStatusCode := Value in [200 .. 299]; end; procedure GetHttpStatusCode() ReturnValue: Integer begin - ReturnValue := this.HttpStatusCode + ReturnValue := HttpStatusCode end; #endregion @@ -55,12 +55,12 @@ codeunit 2357 "Http Response Message Impl." procedure GetIsSuccessStatusCode() Result: Boolean begin - Result := this.IsSuccessStatusCode; + Result := IsSuccessStatusCode; end; procedure SetIsSuccessStatusCode(Value: Boolean) begin - this.IsSuccessStatusCode := Value; + IsSuccessStatusCode := Value; end; #endregion @@ -70,12 +70,12 @@ codeunit 2357 "Http Response Message Impl." procedure SetReasonPhrase(Value: Text) begin - this.ReasonPhrase := Value; + ReasonPhrase := Value; end; procedure GetReasonPhrase() ReturnValue: Text begin - ReturnValue := this.ReasonPhrase; + ReturnValue := ReasonPhrase; end; #endregion @@ -85,18 +85,18 @@ codeunit 2357 "Http Response Message Impl." procedure SetContent(Content: Codeunit "Http Content") begin - this.HttpContent := Content; + HttpContent := Content; end; procedure GetContent() ReturnValue: Codeunit "Http Content" begin - ReturnValue := this.HttpContent; + ReturnValue := HttpContent; end; #endregion #region HttpResponseMessage var - HttpResponseMessage: HttpResponseMessage; + CurrHttpResponseMessageInstance: HttpResponseMessage; procedure SetResponseMessage(var ResponseMessage: HttpResponseMessage) var @@ -105,7 +105,7 @@ codeunit 2357 "Http Response Message Impl." CookieName: Text; begin ClearAll(); - this.HttpResponseMessage := ResponseMessage; + CurrHttpResponseMessageInstance := ResponseMessage; SetIsBlockedByEnvironment(ResponseMessage.IsBlockedByEnvironment); SetHttpStatusCode(ResponseMessage.HttpStatusCode); SetReasonPhrase(ResponseMessage.ReasonPhrase); @@ -121,22 +121,22 @@ codeunit 2357 "Http Response Message Impl." procedure GetResponseMessage() ReturnValue: HttpResponseMessage begin - ReturnValue := this.HttpResponseMessage; + ReturnValue := CurrHttpResponseMessageInstance; end; #endregion #region HttpHeaders var - HttpHeaders: HttpHeaders; + ResponseHttpHeaders: HttpHeaders; procedure SetHeaders(Headers: HttpHeaders) begin - this.HttpHeaders := Headers; + ResponseHttpHeaders := Headers; end; procedure GetHeaders() ReturnValue: HttpHeaders begin - ReturnValue := this.HttpHeaders; + ReturnValue := ResponseHttpHeaders; end; #endregion @@ -146,27 +146,27 @@ codeunit 2357 "Http Response Message Impl." procedure SetCookies(Cookies: Dictionary of [Text, Cookie]) begin - this.GlobalCookies := Cookies; + GlobalCookies := Cookies; end; procedure GetCookies() Cookies: Dictionary of [Text, Cookie] begin - Cookies := this.GlobalCookies; + Cookies := GlobalCookies; end; procedure GetCookieNames() CookieNames: List of [Text] begin - CookieNames := this.GlobalCookies.Keys; + CookieNames := GlobalCookies.Keys; end; - procedure GetCookie(Name: Text) Cookie: Cookie + procedure GetCookie(Name: Text) TheCookie: Cookie begin - if this.GlobalCookies.Get(Name, Cookie) then; + if GlobalCookies.Get(Name, TheCookie) then; end; - procedure GetCookie(Name: Text; var Cookie: Cookie) Success: Boolean + procedure GetCookie(Name: Text; var TheCookie: Cookie) Success: Boolean begin - Success := this.GlobalCookies.Get(Name, Cookie); + Success := GlobalCookies.Get(Name, TheCookie); end; #endregion @@ -177,33 +177,33 @@ codeunit 2357 "Http Response Message Impl." procedure SetErrorMessage(Value: Text) begin - this.ErrorMessage := Value; + ErrorMessage := Value; end; procedure GetErrorMessage(): Text begin - if this.GlobalException.Message <> '' then - exit(this.GlobalException.Message); + if GlobalException.Message <> '' then + exit(GlobalException.Message); - if this.ErrorMessage <> '' then - exit(this.ErrorMessage); + if ErrorMessage <> '' then + exit(ErrorMessage); exit(GetLastErrorText()); end; procedure SetException(Exception: ErrorInfo) begin - this.GlobalException := Exception; + GlobalException := Exception; end; procedure GetException() Exception: ErrorInfo var RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; begin - if this.GlobalException.Message = '' then + if GlobalException.Message = '' then Exception := RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::UnknownException, GetErrorMessage()) else - Exception := this.GlobalException; + Exception := GlobalException; end; #endregion } \ No newline at end of file diff --git a/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al b/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al index 183bf3ebdd..6ba4f969ae 100644 --- a/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al +++ b/src/System Application/App/Rest Client/src/RestClientImpl.Codeunit.al @@ -16,7 +16,7 @@ codeunit 2351 "Rest Client Impl." RestClientExceptionBuilder: Codeunit "Rest Client Exception Builder"; HttpAuthentication: Interface "Http Authentication"; HttpClientHandler: Interface "Http Client Handler"; - HttpClient: HttpClient; + CurrHttpClientInstance: HttpClient; IsInitialized: Boolean; EnvironmentBlocksErr: Label 'The outgoing HTTP request to "%1" was blocked by the environment.', Comment = '%1 = url, e.g. https://microsoft.com'; ConnectionErr: Label 'Connection to the remote service "%1" could not be established.', Comment = '%1 = url, e.g. https://microsoft.com'; @@ -27,17 +27,17 @@ codeunit 2351 "Rest Client Impl." #region Constructors procedure Create() RestClientImpl: Codeunit "Rest Client Impl." begin - RestClientImpl := RestClientImpl.Create(this.DefaultHttpClientHandler, this.HttpAuthenticationAnonymous); + RestClientImpl := RestClientImpl.Create(DefaultHttpClientHandler, HttpAuthenticationAnonymous); end; procedure Create(HttpClientHandlerInstance: Interface "Http Client Handler") RestClientImpl: Codeunit "Rest Client Impl." begin - RestClientImpl := RestClientImpl.Create(HttpClientHandlerInstance, this.HttpAuthenticationAnonymous); + RestClientImpl := RestClientImpl.Create(HttpClientHandlerInstance, HttpAuthenticationAnonymous); end; procedure Create(HttpAuthenticationInstance: Interface "Http Authentication") RestClientImpl: Codeunit "Rest Client Impl." begin - RestClientImpl := RestClientImpl.Create(this.DefaultHttpClientHandler, HttpAuthenticationInstance); + RestClientImpl := RestClientImpl.Create(DefaultHttpClientHandler, HttpAuthenticationInstance); end; procedure Create(HttpClientHandlerInstance: Interface "Http Client Handler"; HttpAuthenticationInstance: Interface "Http Authentication"): Codeunit "Rest Client Impl." @@ -50,56 +50,56 @@ codeunit 2351 "Rest Client Impl." #region Initialization procedure Initialize() begin - Initialize(this.DefaultHttpClientHandler, this.HttpAuthenticationAnonymous); + Initialize(DefaultHttpClientHandler, HttpAuthenticationAnonymous); end; procedure Initialize(HttpClientHandlerInstance: Interface "Http Client Handler") begin - Initialize(HttpClientHandlerInstance, this.HttpAuthenticationAnonymous); + Initialize(HttpClientHandlerInstance, HttpAuthenticationAnonymous); end; procedure Initialize(HttpAuthenticationInstance: Interface "Http Authentication") begin - Initialize(this.DefaultHttpClientHandler, HttpAuthenticationInstance); + Initialize(DefaultHttpClientHandler, HttpAuthenticationInstance); end; procedure Initialize(HttpClientHandlerInstance: Interface "Http Client Handler"; HttpAuthenticationInstance: Interface "Http Authentication") begin ClearAll(); - this.HttpClient.Clear(); - this.HttpClientHandler := HttpClientHandlerInstance; - this.HttpAuthentication := HttpAuthenticationInstance; - this.IsInitialized := true; + CurrHttpClientInstance.Clear(); + HttpClientHandler := HttpClientHandlerInstance; + HttpAuthentication := HttpAuthenticationInstance; + IsInitialized := true; SetDefaultUserAgentHeader(); end; procedure SetDefaultRequestHeader(Name: Text; Value: Text) begin CheckInitialized(); - if this.HttpClient.DefaultRequestHeaders.Contains(Name) then - this.HttpClient.DefaultRequestHeaders.Remove(Name); - this.HttpClient.DefaultRequestHeaders.Add(Name, Value); + if CurrHttpClientInstance.DefaultRequestHeaders.Contains(Name) then + CurrHttpClientInstance.DefaultRequestHeaders.Remove(Name); + CurrHttpClientInstance.DefaultRequestHeaders.Add(Name, Value); end; procedure SetDefaultRequestHeader(Name: Text; Value: SecretText) begin CheckInitialized(); - if this.HttpClient.DefaultRequestHeaders.Contains(Name) then - this.HttpClient.DefaultRequestHeaders.Remove(Name); - this.HttpClient.DefaultRequestHeaders.Add(Name, Value); + if CurrHttpClientInstance.DefaultRequestHeaders.Contains(Name) then + CurrHttpClientInstance.DefaultRequestHeaders.Remove(Name); + CurrHttpClientInstance.DefaultRequestHeaders.Add(Name, Value); end; procedure SetBaseAddress(Url: Text) begin CheckInitialized(); - this.HttpClient.SetBaseAddress(Url); + CurrHttpClientInstance.SetBaseAddress(Url); end; procedure GetBaseAddress() Url: Text begin CheckInitialized(); - Url := this.HttpClient.GetBaseAddress; + Url := CurrHttpClientInstance.GetBaseAddress; end; procedure SetTimeOut(TimeOut: Duration) @@ -107,25 +107,25 @@ codeunit 2351 "Rest Client Impl." CheckInitialized(); if TimeOut <= 0 then Error(TimeoutOutOfRangeErr); - this.HttpClient.Timeout := TimeOut; + CurrHttpClientInstance.Timeout := TimeOut; end; procedure GetTimeOut() TimeOut: Duration begin CheckInitialized(); - TimeOut := this.HttpClient.Timeout; + TimeOut := CurrHttpClientInstance.Timeout; end; procedure AddCertificate(Certificate: Text) begin CheckInitialized(); - this.HttpClient.AddCertificate(Certificate); + CurrHttpClientInstance.AddCertificate(Certificate); end; procedure AddCertificate(Certificate: Text; Password: SecretText) begin CheckInitialized(); - this.HttpClient.AddCertificate(Certificate, Password); + CurrHttpClientInstance.AddCertificate(Certificate, Password); end; procedure SetAuthorizationHeader(Value: SecretText) @@ -141,7 +141,7 @@ codeunit 2351 "Rest Client Impl." procedure SetUseResponseCookies(Value: Boolean) begin CheckInitialized(); - this.HttpClient.UseResponseCookies(Value); + CurrHttpClientInstance.UseResponseCookies(Value); end; #endregion @@ -255,7 +255,7 @@ codeunit 2351 "Rest Client Impl." #region Local Methods local procedure CheckInitialized() begin - if not this.IsInitialized then + if not IsInitialized then Initialize(); end; @@ -281,24 +281,24 @@ codeunit 2351 "Rest Client Impl." begin Clear(HttpResponseMessage); - if this.HttpAuthentication.IsAuthenticationRequired() then + if HttpAuthentication.IsAuthenticationRequired() then Authorize(HttpRequestMessage); - if not this.HttpClientHandler.Send(this.HttpClient, HttpRequestMessage, HttpResponseMessage) then begin + if not HttpClientHandler.Send(CurrHttpClientInstance, HttpRequestMessage, HttpResponseMessage) then begin if HttpResponseMessage.GetIsBlockedByEnvironment() then HttpResponseMessage.SetException( - this.RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::BlockedByEnvironment, + RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::BlockedByEnvironment, StrSubstNo(EnvironmentBlocksErr, HttpRequestMessage.GetRequestUri()))) else HttpResponseMessage.SetException( - this.RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::ConnectionFailed, + RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::ConnectionFailed, StrSubstNo(ConnectionErr, HttpRequestMessage.GetRequestUri()))); exit(false); end; if not HttpResponseMessage.GetIsSuccessStatusCode() then HttpResponseMessage.SetException( - this.RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::RequestFailed, + RestClientExceptionBuilder.CreateException(Enum::"Rest Client Exception"::RequestFailed, StrSubstNo(RequestFailedErr, HttpRequestMessage.GetRequestUri(), HttpResponseMessage.GetHttpStatusCode(), HttpResponseMessage.GetReasonPhrase()))); exit(true); @@ -310,7 +310,7 @@ codeunit 2351 "Rest Client Impl." HeaderName: Text; HeaderValue: SecretText; begin - AuthorizationHeaders := this.HttpAuthentication.GetAuthorizationHeaders(); + AuthorizationHeaders := HttpAuthentication.GetAuthorizationHeaders(); foreach HeaderName in AuthorizationHeaders.Keys do begin HeaderValue := AuthorizationHeaders.Get(HeaderName); HttpRequestMessage.SetHeader(HeaderName, HeaderValue); From d222f66c55428dde136e9986d40c08e4dde47a7e Mon Sep 17 00:00:00 2001 From: Arend-Jan Kauffmann Date: Wed, 11 Dec 2024 14:11:22 +0100 Subject: [PATCH 11/12] Remove runtime version from app.json --- src/System Application/App/Rest Client/app.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/System Application/App/Rest Client/app.json b/src/System Application/App/Rest Client/app.json index 08a2384ffb..fe0060b51c 100644 --- a/src/System Application/App/Rest Client/app.json +++ b/src/System Application/App/Rest Client/app.json @@ -38,7 +38,6 @@ } ], "target": "OnPrem", - "runtime": "14.0", "resourceExposurePolicy": { "allowDebugging": true, "allowDownloadingSource": true, From 2e36862b613d5f7a35bbec090b9ce6bb6e91d830 Mon Sep 17 00:00:00 2001 From: Arend-Jan Kauffmann Date: Mon, 16 Dec 2024 10:56:18 +0100 Subject: [PATCH 12/12] Add disabled tests for Rest Client methods --- .../RestClientTests.DisabledTest.json | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/System Application/Test/DisabledTests/RestClientTests.DisabledTest.json diff --git a/src/System Application/Test/DisabledTests/RestClientTests.DisabledTest.json b/src/System Application/Test/DisabledTests/RestClientTests.DisabledTest.json new file mode 100644 index 0000000000..6c38dccb1f --- /dev/null +++ b/src/System Application/Test/DisabledTests/RestClientTests.DisabledTest.json @@ -0,0 +1,32 @@ +[ + { + "bug": "524800", + "codeunitId": 134971, + "codeunitName": "Rest Client Tests", + "method": "TestResponseWithCookies" + }, + { + "bug": "524800", + "codeunitId": 134971, + "codeunitName": "Rest Client Tests", + "method": "TestRequestWithCookies" + }, + { + "bug": "524800", + "codeunitId": 134971, + "codeunitName": "Rest Client Tests", + "method": "TestWithoutUseResponseCookies" + }, + { + "bug": "524800", + "codeunitId": 134971, + "codeunitName": "Rest Client Tests", + "method": "TestWithUseResponseCookies" + }, + { + "bug": "524800", + "codeunitId": 134971, + "codeunitName": "Rest Client Tests", + "method": "TestUseResponseCookiesWithAdditionalCookies" + } +] \ No newline at end of file