Skip to content

Commit 70dc84b

Browse files
authored
Use item dynamic properties special handling for body parameter (#2823)
Add new connector setting _UseItemDynamicPropertiesSpecialHandling_ Remove _UseDefaultBodyNameForSinglePropertyObject_ as it was not fixing the issue as expected Special case handling when - body name is 'item' - body inner object is 'dynamicProperties' - there is only one property in inner object In that base the body will be fully flattened and we will retain the 'body' name for the parameter.
1 parent 0fa1968 commit 70dc84b

File tree

6 files changed

+158
-42
lines changed

6 files changed

+158
-42
lines changed

src/libraries/Microsoft.PowerFx.Connectors/ConnectorFunction.cs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,8 +1373,9 @@ private ConnectorParameterInternals Initialize()
13731373
string bodySchemaReferenceId = null;
13741374
bool schemaLessBody = false;
13751375
bool fatalError = false;
1376+
bool specialBodyHandling = false;
13761377
string contentType = OpenApiExtensions.ContentType_ApplicationJson;
1377-
ConnectorErrors errorsAndWarnings = new ConnectorErrors();
1378+
ConnectorErrors errorsAndWarnings = new ConnectorErrors();
13781379

13791380
foreach (OpenApiParameter parameter in Operation.Parameters)
13801381
{
@@ -1458,14 +1459,21 @@ private ConnectorParameterInternals Initialize()
14581459
foreach (KeyValuePair<string, OpenApiSchema> bodyProperty in bodySchema.Properties)
14591460
{
14601461
OpenApiSchema bodyPropertySchema = bodyProperty.Value;
1461-
string bodyPropertyName = bodyProperty.Key;
1462-
bool bodyPropertyRequired = bodySchema.Required.Contains(bodyPropertyName);
1463-
bool bodyPropertyHiddenRequired = false;
1464-
1465-
if (ConnectorSettings.UseDefaultBodyNameForSinglePropertyObject && bodySchema.Properties.Count == 1)
1462+
string bodyPropertyName = bodyProperty.Key;
1463+
bool bodyPropertyHiddenRequired = false;
1464+
1465+
// Power Apps has a special handling for the body in this case
1466+
// where it doesn't follow the swagger file
1467+
if (ConnectorSettings.UseItemDynamicPropertiesSpecialHandling &&
1468+
bodyName == "item" &&
1469+
bodyPropertyName == "dynamicProperties" &&
1470+
bodySchema.Properties.Count == 1)
14661471
{
14671472
bodyPropertyName = bodyName;
1468-
}
1473+
specialBodyHandling = true;
1474+
}
1475+
1476+
bool bodyPropertyRequired = bodySchema.Required.Contains(bodyPropertyName) || (ConnectorSettings.UseItemDynamicPropertiesSpecialHandling && requestBody.Required);
14691477

14701478
if (bodyPropertySchema.IsInternal())
14711479
{
@@ -1611,7 +1619,8 @@ private ConnectorParameterInternals Initialize()
16111619
ContentType = contentType,
16121620
BodySchemaReferenceId = bodySchemaReferenceId,
16131621
ParameterDefaultValues = parameterDefaultValues,
1614-
SchemaLessBody = schemaLessBody
1622+
SchemaLessBody = schemaLessBody,
1623+
SpecialBodyHandling = specialBodyHandling
16151624
};
16161625
}
16171626

src/libraries/Microsoft.PowerFx.Connectors/Execution/FormulaValueSerializer.cs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -108,31 +108,36 @@ private async Task WriteObjectAsync(string objectName, ISwaggerSchema schema, IE
108108
await WritePropertyAsync(
109109
nv.Name,
110110
new SwaggerSchema(
111-
type: nv.Value.Type._type.Kind switch
112-
{
113-
DKind.Number => "number",
114-
DKind.Decimal => "number",
115-
DKind.String or
116-
DKind.Date or
117-
DKind.DateTime or
118-
DKind.DateTimeNoTimeZone => "string",
119-
DKind.Boolean => "boolean",
120-
DKind.Record => "object",
121-
DKind.Table => "array",
122-
DKind.ObjNull => "null",
123-
_ => $"type: unknown_dkind {nv.Value.Type._type.Kind}"
124-
},
125-
format: GetDateFormat(nv.Value.Type._type.Kind)),
111+
type: GetType(nv.Value.Type),
112+
format: GetFormat(nv.Value.Type)),
126113
nv.Value).ConfigureAwait(false);
127114
}
128115
}
129116

130117
EndObject(objectName);
131118
}
132119

133-
private static string GetDateFormat(DKind kind)
120+
internal static string GetType(FormulaType type)
134121
{
135-
return kind switch
122+
return type._type.Kind switch
123+
{
124+
DKind.Number => "number",
125+
DKind.Decimal => "number",
126+
DKind.String or
127+
DKind.Date or
128+
DKind.DateTime or
129+
DKind.DateTimeNoTimeZone => "string",
130+
DKind.Boolean => "boolean",
131+
DKind.Record => "object",
132+
DKind.Table => "array",
133+
DKind.ObjNull => "null",
134+
_ => $"type: unknown_dkind {type._type.Kind}"
135+
};
136+
}
137+
138+
internal static string GetFormat(FormulaType type)
139+
{
140+
return type._type.Kind switch
136141
{
137142
DKind.Date => "date",
138143
DKind.DateTime => "date-time",

src/libraries/Microsoft.PowerFx.Connectors/Execution/HttpFunctionInvoker.cs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,28 @@ public async Task<HttpRequestMessage> BuildRequest(FormulaValue[] args, IConvert
5757
// Header names are not case sensitive.
5858
// From RFC 2616 - "Hypertext Transfer Protocol -- HTTP/1.1", Section 4.2, "Message Headers"
5959
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
60-
Dictionary<string, (ISwaggerSchema, FormulaValue)> bodyParts = new ();
60+
Dictionary<string, (ISwaggerSchema, FormulaValue)> bodyParts = new ();
6161
Dictionary<string, FormulaValue> incomingParameters = ConvertToNamedParameters(args);
6262
string contentType = null;
6363

6464
foreach (KeyValuePair<ConnectorParameter, FormulaValue> param in _function._internals.OpenApiBodyParameters)
65-
{
65+
{
6666
if (incomingParameters.TryGetValue(param.Key.Name, out var paramValue))
6767
{
68-
bodyParts.Add(param.Key.Name, (param.Key.Schema, paramValue));
68+
if (_function._internals.SpecialBodyHandling && paramValue is RecordValue rv)
69+
{
70+
foreach (NamedValue field in rv.Fields)
71+
{
72+
string type = FormulaValueSerializer.GetType(field.Value.Type);
73+
string format = FormulaValueSerializer.GetFormat(field.Value.Type);
74+
75+
bodyParts.Add(field.Name, (new SwaggerSchema(type, format), field.Value));
76+
}
77+
}
78+
else
79+
{
80+
bodyParts.Add(param.Key.Name, (param.Key.Schema, paramValue));
81+
}
6982
}
7083
else if (param.Key.Schema.Default != null && param.Value != null)
7184
{
@@ -200,6 +213,7 @@ public Dictionary<string, FormulaValue> ConvertToNamedParameters(FormulaValue[]
200213
// Parameter names are case sensitive.
201214

202215
Dictionary<string, FormulaValue> map = new ();
216+
bool specialBodyHandling = _function._internals.SpecialBodyHandling;
203217

204218
// Seed with default values. This will get overwritten if provided.
205219
foreach (KeyValuePair<string, (bool required, FormulaValue fValue, DType dType)> kv in _function._internals.ParameterDefaultValues)
@@ -217,9 +231,9 @@ public Dictionary<string, FormulaValue> ConvertToNamedParameters(FormulaValue[]
217231
{
218232
string parameterName = _function.RequiredParameters[i].Name;
219233
FormulaValue paramValue = args[i];
220-
234+
221235
// Objects are always flattenned
222-
if (paramValue is RecordValue record && !_function.RequiredParameters[i].IsBodyParameter)
236+
if (paramValue is RecordValue record && (specialBodyHandling || !_function.RequiredParameters[i].IsBodyParameter))
223237
{
224238
foreach (NamedValue field in record.Fields)
225239
{

src/libraries/Microsoft.PowerFx.Connectors/Internal/ConnectorParameterInternals.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,7 @@ internal class ConnectorParameterInternals
2020
internal string BodySchemaReferenceId { get; init; }
2121

2222
internal Dictionary<string, (bool, FormulaValue, DType)> ParameterDefaultValues { get; init; }
23+
24+
internal bool SpecialBodyHandling { get; init; }
2325
}
2426
}

src/libraries/Microsoft.PowerFx.Connectors/Public/ConnectorSettings.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,17 @@ public bool ExposeInternalParamsWithoutDefaultValue
9292
/// This flag will force all enums to be returns as FormulaType.String or FormulaType.Decimal regardless of x-ms-enum-*.
9393
/// This flag is only in effect when SupportXMsEnumValues is true.
9494
/// </summary>
95-
public bool ReturnEnumsAsPrimitive { get; init; } = false;
96-
95+
public bool ReturnEnumsAsPrimitive { get; init; } = false;
96+
9797
/// <summary>
98-
/// In Power Apps, when a body parameter is used it's flattened and we create one parameter for each
99-
/// body object property. With that logic each parameter name will be the object property name.
100-
/// When set, this setting will use the real body name specified in the swagger instead of the property name
101-
/// of the object, provided there is only one property.
98+
/// This flag enables some special handling for the body parameter, when
99+
/// - body name is 'item'
100+
/// - body inner object is 'dynamicProperties'
101+
/// - there is only one property in inner object
102+
/// In that base the body will be fully flattened and we will retain the 'body' name for the parameter.
102103
/// </summary>
103-
public bool UseDefaultBodyNameForSinglePropertyObject { get; init; } = false;
104-
104+
public bool UseItemDynamicPropertiesSpecialHandling { get; init; } = false;
105+
105106
public ConnectorCompatibility Compatibility { get; init; } = ConnectorCompatibility.Default;
106107
}
107108

src/tests/Microsoft.PowerFx.Connectors.Tests.Shared/PowerPlatformConnectorTests.cs

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2402,20 +2402,105 @@ public async Task SQL_ExecuteStoredProc_Scoped()
24022402
[Theory]
24032403
[InlineData(true)]
24042404
[InlineData(false)]
2405-
public void ExchangeOnlineTest2(bool useDefaultBodyNameForSinglePropertyObject)
2405+
public async Task ExchangeOnlineTest2(bool useItemDynamicPropertiesSpecialHandling)
24062406
{
2407+
bool live = false;
24072408
using var testConnector = new LoggingTestServer(@"Swagger\ExcelOnlineBusiness.swagger.json", _output);
24082409
List<ConnectorFunction> functions = OpenApiParser.GetFunctions(
2409-
new ConnectorSettings("Excel")
2410+
new ConnectorSettings("ExcelOnline")
24102411
{
24112412
Compatibility = ConnectorCompatibility.Default,
2412-
UseDefaultBodyNameForSinglePropertyObject = useDefaultBodyNameForSinglePropertyObject
2413+
UseItemDynamicPropertiesSpecialHandling = useItemDynamicPropertiesSpecialHandling
24132414
},
24142415
testConnector._apiDocument).ToList();
24152416

24162417
ConnectorFunction patchItem = functions.First(f => f.Name == "PatchItem");
2418+
ConnectorParameter itemparam = useItemDynamicPropertiesSpecialHandling ? patchItem.RequiredParameters[6] : patchItem.OptionalParameters[2];
24172419

2418-
Assert.Equal(!useDefaultBodyNameForSinglePropertyObject ? "dynamicProperties" : "item", patchItem.OptionalParameters[2].Name);
2420+
Assert.Equal(!useItemDynamicPropertiesSpecialHandling ? "dynamicProperties" : "item", itemparam.Name);
2421+
2422+
FormulaValue[] parameters = new FormulaValue[7];
2423+
parameters[0] = FormulaValue.New("b!IbvdIRe4LEGypNQpzV_eHMlG3PtubVREtOzk7doKeFvkIs8VRqloT4mtkIOb6aTB");
2424+
parameters[1] = FormulaValue.New("013DZ3QDGY2Y23HOQN5BC2HUMJWD7G4UPL");
2425+
parameters[2] = FormulaValue.New("{E5A21CC6-3B17-48DE-84D7-0326A06B38F4}");
2426+
parameters[3] = FormulaValue.New("035fd7a2-34d6-4a6f-a885-a646b1398012");
2427+
parameters[4] = FormulaValue.New("me");
2428+
parameters[5] = FormulaValue.New("__PowerAppsId__");
2429+
2430+
parameters[6] = useItemDynamicPropertiesSpecialHandling
2431+
2432+
? // Required parameter
2433+
RecordValue.NewRecordFromFields(
2434+
new NamedValue("item", RecordValue.NewRecordFromFields(
2435+
new NamedValue("Column1", FormulaValue.New(171)))))
2436+
2437+
: // Optional parameters
2438+
RecordValue.NewRecordFromFields(
2439+
new NamedValue("dynamicProperties", RecordValue.NewRecordFromFields(
2440+
new NamedValue("Column1", FormulaValue.New(171)))));
2441+
2442+
using var httpClient = live ? new HttpClient() : new HttpClient(testConnector);
2443+
2444+
if (!live)
2445+
{
2446+
string output = @"{
2447+
""@odata.context"": ""https://excelonline-wcus.azconn-wcus-001.p.azurewebsites.net/$metadata#drives('b%21IbvdIRe4LEGypNQpzV_eHMlG3PtubVREtOzk7doKeFvkIs8VRqloT4mtkIOb6aTB')/Files('013DZ3QDGY2Y23HOQN5BC2HUMJWD7G4UPL')/Tables('%7BE5A21CC6-3B17-48DE-84D7-0326A06B38F4%7D')/items/$entity"",
2448+
""@odata.etag"": """",
2449+
""ItemInternalId"": ""035fd7a2-34d6-4a6f-a885-a646b1398012"",
2450+
""Column1"": ""171"",
2451+
""Column2"": ""Customer1"",
2452+
""Column3"": """",
2453+
""__PowerAppsId__"": ""035fd7a2-34d6-4a6f-a885-a646b1398012""
2454+
}";
2455+
testConnector.SetResponse(output, HttpStatusCode.OK);
2456+
}
2457+
2458+
string jwt = "eyJ0e...";
2459+
using PowerPlatformConnectorClient client = new PowerPlatformConnectorClient("https://49970107-0806-e5a7-be5e-7c60e2750f01.12.common.firstrelease.azure-apihub.net", "49970107-0806-e5a7-be5e-7c60e2750f01", "e24a1ac719284479a4817a0c5bb6ef58", () => jwt, httpClient)
2460+
{
2461+
SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878",
2462+
};
2463+
2464+
BaseRuntimeConnectorContext context = new TestConnectorRuntimeContext("ExcelOnline", client, console: _output);
2465+
FormulaValue result = await patchItem.InvokeAsync(parameters, context, CancellationToken.None);
2466+
2467+
// Can't test the result as it's ![] and is an empty RecordValue
2468+
2469+
if (live)
2470+
{
2471+
return;
2472+
}
2473+
2474+
string version = PowerPlatformConnectorClient.Version;
2475+
string expected = useItemDynamicPropertiesSpecialHandling
2476+
? $@"POST https://49970107-0806-e5a7-be5e-7c60e2750f01.12.common.firstrelease.azure-apihub.net/invoke
2477+
authority: 49970107-0806-e5a7-be5e-7c60e2750f01.12.common.firstrelease.azure-apihub.net
2478+
Authorization: Bearer {jwt}
2479+
path: /invoke
2480+
scheme: https
2481+
x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/49970107-0806-e5a7-be5e-7c60e2750f01
2482+
x-ms-client-session-id: a41bd03b-6c3c-4509-a844-e8c51b61f878
2483+
x-ms-request-method: PATCH
2484+
x-ms-request-url: /apim/excelonlinebusiness/e24a1ac719284479a4817a0c5bb6ef58/drives/b%21IbvdIRe4LEGypNQpzV_eHMlG3PtubVREtOzk7doKeFvkIs8VRqloT4mtkIOb6aTB/files/013DZ3QDGY2Y23HOQN5BC2HUMJWD7G4UPL/tables/%7BE5A21CC6-3B17-48DE-84D7-0326A06B38F4%7D/items/035fd7a2-34d6-4a6f-a885-a646b1398012?source=me&idColumn=__PowerAppsId__
2485+
x-ms-user-agent: PowerFx/{version}
2486+
[content-header] Content-Type: application/json; charset=utf-8
2487+
[body] {{""Column1"":171}}
2488+
"
2489+
: $@"POST https://49970107-0806-e5a7-be5e-7c60e2750f01.12.common.firstrelease.azure-apihub.net/invoke
2490+
authority: 49970107-0806-e5a7-be5e-7c60e2750f01.12.common.firstrelease.azure-apihub.net
2491+
Authorization: Bearer {jwt}
2492+
path: /invoke
2493+
scheme: https
2494+
x-ms-client-environment-id: /providers/Microsoft.PowerApps/environments/49970107-0806-e5a7-be5e-7c60e2750f01
2495+
x-ms-client-session-id: a41bd03b-6c3c-4509-a844-e8c51b61f878
2496+
x-ms-request-method: PATCH
2497+
x-ms-request-url: /apim/excelonlinebusiness/e24a1ac719284479a4817a0c5bb6ef58/drives/b%21IbvdIRe4LEGypNQpzV_eHMlG3PtubVREtOzk7doKeFvkIs8VRqloT4mtkIOb6aTB/files/013DZ3QDGY2Y23HOQN5BC2HUMJWD7G4UPL/tables/%7BE5A21CC6-3B17-48DE-84D7-0326A06B38F4%7D/items/035fd7a2-34d6-4a6f-a885-a646b1398012?source=me&idColumn=__PowerAppsId__
2498+
x-ms-user-agent: PowerFx/{version}
2499+
[content-header] Content-Type: application/json; charset=utf-8
2500+
[body] {{""dynamicProperties"":{{""Column1"":171}}}}
2501+
";
2502+
2503+
Assert.Equal<object>(expected, testConnector._log.ToString());
24192504
}
24202505

24212506
public class HttpLogger : HttpClient

0 commit comments

Comments
 (0)