Skip to content

Commit

Permalink
Add procedures to Get/Remove for UriBuilder Query Paramaters/Flags (#…
Browse files Browse the repository at this point in the history
…2247)

<!-- Thank you for submitting a Pull Request. If you're new to
contributing to BCApps please read our pull request guideline below
* https://github.com/microsoft/BCApps/Contributing.md
-->
#### Summary <!-- Provide a general summary of your changes -->
UriBuilder procedures can only Add/Replace Query Flags/Parameters, while
the internal procedure can parse them and set them, so I want to be able
to use that to only Get/Remove from the collection.

There are different ways to implement this and many ways to test this, I
just wanted to propose this approach which reuses what's already there
for AddQueryFlag/Parameter, figuring we might need something like this
for the upcoming HttpClient mocking:
https://www.yammer.com/dynamicsnavdev/#/threads/show?threadId=3029127257333760
(why expose a special QueryParameter dictionary if we already have
Uri/UriBuilder codeunits that are supposed to expose these structures
for us?)

The new procedures added are:

```al
procedure RemoveQueryFlag(Flag: Text; DuplicateAction: Enum "Uri Query Duplicate Behaviour")
procedure RemoveQueryFlag(Flag: Text)
procedure RemoveQueryParameter(ParameterKey: Text; ParameterValue: Text; DuplicateAction: Enum "Uri Query Duplicate Behaviour")
procedure RemoveQueryParameter(ParameterKey: Text; ParameterValue: Text)
procedure RemoveQueryParameters()
procedure GetQueryFlags(): List of [Text]
procedure GetQueryParameters(): Dictionary of [Text, List of [Text]]
procedure GetQueryParameter(ParameterKey: Text): List of [Text]
```

#### Work Item(s) <!-- Add the issue number here after the #. The issue
needs to be open and approved. Submitting PRs with no linked issues or
unapproved issues is highly discouraged. -->
Fixes #2248 
Fixes
[AB#555623](https://dynamicssmb2.visualstudio.com/1fcb79e7-ab07-432a-a3c6-6cf5a88ba4a5/_workitems/edit/555623)
  • Loading branch information
bjarkihall authored Nov 15, 2024
1 parent 3105c1e commit 582548b
Show file tree
Hide file tree
Showing 3 changed files with 385 additions and 22 deletions.
95 changes: 95 additions & 0 deletions src/System Application/App/URI/src/UriBuilder.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,101 @@ codeunit 3061 "Uri Builder"
UriBuilderImpl.AddODataQueryParameter(ParameterKey, ParameterValue);
end;

/// <summary>
/// Removes a flag from the query string of this UriBuilder. In case the same query flag exists already, the action in <paramref name="DuplicateAction"/> is taken.
/// </summary>
/// <param name="Flag">A flag to Remove from the query string of this UriBuilder. This value will be encoded before being Removed to the URI query string. Cannot be empty.</param>
/// <param name="DuplicateAction">Specifies which action to take if the flag specified already exist.</param>
/// <error>If the provided <paramref name="Flag"/> is empty.</error>
/// <error>If the provided <paramref name="DuplicateAction"/> is <c>"Throw Error"</c> and the flag does not exist in the URI.</error>
/// <error>If the provided <paramref name="DuplicateAction"/> is not a valid value for the enum.</error>
/// <remarks>This function could alter the order of the existing query string parts. For example, if the previous URL was "https://microsoft.com?foo=bar&amp;john=doe" and the new flag is "contoso", the result could be "https://microsoft.com?john=doe&amp;foo=bar&amp;contoso".</remarks>
procedure RemoveQueryFlag(Flag: Text; DuplicateAction: Enum "Uri Query Duplicate Behaviour")
begin
UriBuilderImpl.AddQueryFlag(Flag, DuplicateAction, true);
end;

/// <summary>
/// Removes a flag from the query string of this UriBuilder. In case the same query flag exists already, only one occurrence is kept.
/// </summary>
/// <param name="Flag">A flag to Remove from the query string of this UriBuilder. This value will be encoded before being Removed to the URI query string. Cannot be empty.</param>
/// <error>If the provided <paramref name="Flag"/> is empty.</error>
/// <remarks>This function could alter the order of the existing query string parts. For example, if the previous URL was "https://microsoft.com?foo=bar&amp;john=doe" and the new flag is "contoso", the result could be "https://microsoft.com?john=doe&amp;foo=bar&amp;contoso".</remarks>
procedure RemoveQueryFlag(Flag: Text)
begin
UriBuilderImpl.AddQueryFlag(Flag, Enum::"Uri Query Duplicate Behaviour"::"Overwrite All Matching", true);
end;

/// <summary>
/// Removes a parameter key-value pair from the query string of this UriBuilder (in the form <c>ParameterKey=ParameterValue</c>). In case the same query key exists already, the action in <paramref name="DuplicateAction"/> is taken.
/// </summary>
/// <param name="ParameterKey">The key for the new query parameter. This value will be encoded before being Removed to the URI query string. Cannot be empty.</param>
/// <param name="ParameterValue">The value for the new query parameter. This value will be encoded before being Removed to the URI query string. Can be empty.</param>
/// <param name="DuplicateAction">Specifies which action to take if the ParameterKey specified already exist.</param>
/// <error>If the provided <paramref name="ParameterKey"/> is empty.</error>
/// <error>If the provided <paramref name="DuplicateAction"/> is <c>"Throw Error"</c>.</error>
/// <error>If the provided <paramref name="DuplicateAction"/> is not a valid value for the enum.</error>
/// <remarks>This function could alter the order of the existing query string parts. For example, if the previous URL was "https://microsoft.com?foo=bar&amp;john=doe" and the new flag is "contoso=42", the result could be "https://microsoft.com?john=doe&amp;foo=bar&amp;contoso=42".</remarks>
procedure RemoveQueryParameter(ParameterKey: Text; ParameterValue: Text; DuplicateAction: Enum "Uri Query Duplicate Behaviour")
begin
UriBuilderImpl.RemoveQueryParameter(ParameterKey, ParameterValue, DuplicateAction);
end;

/// <summary>
/// Removes a parameter key-value pair from the query string of this UriBuilder (in the form <c>ParameterKey=ParameterValue</c>). In case the same query key exists already, its value is overwritten.
/// </summary>
/// <param name="ParameterKey">The key for the new query parameter. This value will be encoded before being Removed to the URI query string. Cannot be empty.</param>
/// <param name="ParameterValue">The value for the new query parameter. This value will be encoded before being Removed to the URI query string. Can be empty.</param>
/// <error>If the provided <paramref name="ParameterKey"/> is empty.</error>
/// <remarks>This function could alter the order of the existing query string parts. For example, if the previous URL was "https://microsoft.com?foo=bar&amp;john=doe" and the new flag is "contoso=42", the result could be "https://microsoft.com?john=doe&amp;foo=bar&amp;contoso=42".</remarks>
procedure RemoveQueryParameter(ParameterKey: Text; ParameterValue: Text)
begin
UriBuilderImpl.RemoveQueryParameter(ParameterKey, ParameterValue, Enum::"Uri Query Duplicate Behaviour"::"Overwrite All Matching");
end;

/// <summary>
/// Removes a parameter key-value pair from the query string of this UriBuilder (in the form <c>ParameterKey=ParameterValue</c>). In case the same query key exists already, the action in <paramref name="DuplicateAction"/> is taken.
/// </summary>
/// <param name="ParameterKey">The key for the new query parameter. This value will be encoded before being Removed to the URI query string. Cannot be empty.</param>
/// <param name="DuplicateAction">Specifies which action to take if the ParameterKey specified already exist.</param>
/// <error>If the provided <paramref name="ParameterKey"/> is empty.</error>
/// <error>If the provided <paramref name="DuplicateAction"/> is <c>"Throw Error"</c>.</error>
/// <error>If the provided <paramref name="DuplicateAction"/> is not a valid value for the enum.</error>
/// <remarks>This function could alter the order of the existing query string parts. For example, if the previous URL was "https://microsoft.com?foo=bar&amp;john=doe" and the new flag is "contoso=42", the result could be "https://microsoft.com?john=doe&amp;foo=bar&amp;contoso=42".</remarks>
procedure RemoveQueryParameters()
begin
UriBuilderImpl.RemoveQueryParameters();
end;

/// <summary>
/// Gets the flags in the query string of this UriBuilder.
/// </summary>
/// <returns>A list of flags in the query string of this UriBuilder.</returns>
procedure GetQueryFlags(): List of [Text]
begin
exit(UriBuilderImpl.GetQueryFlags());
end;

/// <summary>
/// Gets the parameters in the query string of this UriBuilder.
/// </summary>
/// <returns>A dictionary of parameters in the query string of this UriBuilder.</returns>
procedure GetQueryParameters(): Dictionary of [Text, List of [Text]]
begin
exit(UriBuilderImpl.GetQueryParameters());
end;

/// <summary>
/// Gets the value of the specified query parameter.
/// </summary>
/// <param name="ParameterKey">The key of the query parameter to get the value of.</param>
/// <returns>The value of the specified query parameter.</returns>
procedure GetQueryParameter(ParameterKey: Text): List of [Text]
begin
exit(UriBuilderImpl.GetQueryParameter(ParameterKey));
end;


var
UriBuilderImpl: Codeunit "Uri Builder Impl.";
}
113 changes: 96 additions & 17 deletions src/System Application/App/URI/src/UriBuilderImpl.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ codeunit 3062 "Uri Builder Impl."
end;

procedure AddQueryFlag(Flag: Text; DuplicateAction: Enum "Uri Query Duplicate Behaviour")
begin
AddQueryFlag(Flag, DuplicateAction, false);
end;

procedure AddQueryFlag(Flag: Text; DuplicateAction: Enum "Uri Query Duplicate Behaviour"; ShouldRemove: Boolean)
var
KeysWithValueList: Dictionary of [Text, List of [Text]];
Flags: List of [Text];
Expand All @@ -94,23 +99,28 @@ codeunit 3062 "Uri Builder Impl."

QueryString := GetQuery();
ParseParametersAndFlags(QueryString, KeysWithValueList, Flags);
ProcessNewFlag(Flags, Flag, DuplicateAction);
ProcessNewFlag(Flags, Flag, DuplicateAction, ShouldRemove);
QueryString := CreateNewQueryString(KeysWithValueList, Flags, false);

SetQuery(QueryString);
end;

procedure AddQueryParameter(ParameterKey: Text; ParameterValue: Text; DuplicateAction: Enum "Uri Query Duplicate Behaviour")
begin
AddQueryParameterInternal(ParameterKey, ParameterValue, DuplicateAction, false);
AddQueryParameterInternal(ParameterKey, ParameterValue, DuplicateAction, false, false);
end;

procedure AddODataQueryParameter(ParameterKey: Text; ParameterValue: Text)
begin
AddQueryParameterInternal(ParameterKey, ParameterValue, Enum::"Uri Query Duplicate Behaviour"::"Overwrite All Matching", true);
AddQueryParameterInternal(ParameterKey, ParameterValue, Enum::"Uri Query Duplicate Behaviour"::"Overwrite All Matching", true, false);
end;

procedure RemoveQueryParameter(ParameterKey: Text; ParameterValue: Text; DuplicateAction: Enum "Uri Query Duplicate Behaviour")
begin
AddQueryParameterInternal(ParameterKey, ParameterValue, DuplicateAction, false, true);
end;

local procedure AddQueryParameterInternal(ParameterKey: Text; ParameterValue: Text; DuplicateAction: Enum "Uri Query Duplicate Behaviour"; UseODataEncoding: Boolean)
local procedure AddQueryParameterInternal(ParameterKey: Text; ParameterValue: Text; DuplicateAction: Enum "Uri Query Duplicate Behaviour"; UseODataEncoding: Boolean; ShouldRemove: Boolean)
var
KeysWithValueList: Dictionary of [Text, List of [Text]];
Flags: List of [Text];
Expand All @@ -121,7 +131,7 @@ codeunit 3062 "Uri Builder Impl."

QueryString := GetQuery();
ParseParametersAndFlags(QueryString, KeysWithValueList, Flags);
ProcessNewParameter(KeysWithValueList, ParameterKey, ParameterValue, DuplicateAction);
ProcessNewParameter(KeysWithValueList, ParameterKey, ParameterValue, DuplicateAction, ShouldRemove);
QueryString := CreateNewQueryString(KeysWithValueList, Flags, UseODataEncoding);

SetQuery(QueryString);
Expand Down Expand Up @@ -157,48 +167,75 @@ codeunit 3062 "Uri Builder Impl."
end;
end;

local procedure ProcessNewParameter(var KeysWithValueList: Dictionary of [Text, List of [Text]]; QueryKey: Text; QueryValue: Text; DuplicateAction: Enum "Uri Query Duplicate Behaviour")
local procedure ProcessNewParameter(var KeysWithValueList: Dictionary of [Text, List of [Text]]; QueryKey: Text; QueryValue: Text; DuplicateAction: Enum "Uri Query Duplicate Behaviour"; ShouldRemove: Boolean)
var
Values: List of [Text];
begin
if not KeysWithValueList.ContainsKey(QueryKey) then begin
if ShouldRemove then begin
if DuplicateAction = DuplicateAction::"Throw Error" then
Error(ParameterNotFoundErr);
exit;
end;
Values.Add(QueryValue);
KeysWithValueList.Add(QueryKey, Values);
exit;
end;

KeysWithValueList.Get(QueryKey, Values);

if ShouldRemove then
if Values.Contains(QueryValue) then begin
Values.Remove(QueryValue); // Remove first-occurrence from List
KeysWithValueList.Set(QueryKey, Values);
end;

case DuplicateAction of
DuplicateAction::"Overwrite All Matching":
begin
Clear(Values);
Values.Add(QueryValue);
KeysWithValueList.Remove(QueryKey);
KeysWithValueList.Add(QueryKey, Values);
if not ShouldRemove then
Values.Add(QueryValue);
KeysWithValueList.Set(QueryKey, Values);
end;
DuplicateAction::Skip:
; // Do nothing
DuplicateAction::"Keep All":
Values.Add(QueryValue);
begin
if not ShouldRemove then
Values.Add(QueryValue)
else
if Values.Contains(QueryValue) then
Values.Remove(QueryValue);
KeysWithValueList.Set(QueryKey, Values);
end;
DuplicateAction::"Throw Error":
Error(DuplicateParameterErr);
if not ShouldRemove then
Error(DuplicateParameterErr);
else // In case the duplicate action is invalid, it's safer to error out than to have a malformed URL
Error(DuplicateParameterErr);
end;
end;

local procedure ProcessNewFlag(var Flags: List of [Text]; Flag: Text; DuplicateAction: Enum "Uri Query Duplicate Behaviour")
local procedure ProcessNewFlag(var Flags: List of [Text]; Flag: Text; DuplicateAction: Enum "Uri Query Duplicate Behaviour"; ShouldRemove: Boolean)
var
FlagsSizeBeforeRemove: Integer;
begin
if not Flags.Contains(Flag) then begin
Flags.Add(Flag);
if not ShouldRemove then
Flags.Add(Flag)
else
if DuplicateAction = DuplicateAction::"Throw Error" then
Error(FlagNotFoundErr);
exit;
end;

if ShouldRemove then
Flags.Remove(Flag); // Remove first occurence from List

case DuplicateAction of
DuplicateAction::Skip:
;
; // Do nothing
DuplicateAction::"Overwrite All Matching":
begin
// If multiple matching flags exist, we need to keep only one
Expand All @@ -208,17 +245,57 @@ codeunit 3062 "Uri Builder Impl."
if Flags.Remove(Flag) then;
until Flags.Count >= FlagsSizeBeforeRemove;

Flags.Add(Flag);
if not ShouldRemove then
Flags.Add(Flag);
end;
DuplicateAction::"Keep All":
Flags.Add(Flag);
if not ShouldRemove then
Flags.Add(Flag);
DuplicateAction::"Throw Error":
Error(DuplicateFlagErr);
if not ShouldRemove then
Error(DuplicateFlagErr);
else // In case the duplicate action is invalid, it's safer to error out than to have a malformed URL
Error(DuplicateFlagErr);
end;
end;

procedure RemoveQueryParameters()
begin
SetQuery('');
end;

procedure GetQueryFlags(): List of [Text]
var
KeysWithValueList: Dictionary of [Text, List of [Text]];
Flags: List of [Text];
QueryString: Text;
begin
QueryString := GetQuery();
ParseParametersAndFlags(QueryString, KeysWithValueList, Flags);
exit(Flags);
end;

procedure GetQueryParameters(): Dictionary of [Text, List of [Text]]
var
KeysWithValueList: Dictionary of [Text, List of [Text]];
Flags: List of [Text];
QueryString: Text;
begin
QueryString := GetQuery();
ParseParametersAndFlags(QueryString, KeysWithValueList, Flags);
exit(KeysWithValueList);
end;

procedure GetQueryParameter(ParameterKey: Text): List of [Text]
var
KeysWithValueList: Dictionary of [Text, List of [Text]];
begin
KeysWithValueList := GetQueryParameters();
if not KeysWithValueList.ContainsKey(ParameterKey) then
exit;
exit(KeysWithValueList.Get(ParameterKey));
end;

local procedure CreateNewQueryString(KeysWithValueList: Dictionary of [Text, List of [Text]]; Flags: List of [Text]; UseODataEncoding: Boolean) FinalQuery: Text
var
Uri: Codeunit Uri;
Expand Down Expand Up @@ -261,5 +338,7 @@ codeunit 3062 "Uri Builder Impl."
QueryParameterKeyCannotBeEmptyErr: Label 'The query parameter key cannot be empty.';
DuplicateFlagErr: Label 'The provided query flag is already present in the URI.';
DuplicateParameterErr: Label 'The provided query parameter is already present in the URI.';
FlagNotFoundErr: Label 'The provided query flag is not present in the URI.';
ParameterNotFoundErr: Label 'The provided query parameter is not present in the URI.';
UriBuilder: DotNet UriBuilder;
}
Loading

0 comments on commit 582548b

Please sign in to comment.