зеркало из https://github.com/microsoft/Power-Fx.git
Add ability to override output when calling the connector (#2445)
This is useful in a situation when the output type is dynamic and is not available in the swagger --------- Co-authored-by: Mike Stall <mikestall@hotmail.com>
This commit is contained in:
Родитель
caa821f071
Коммит
d46881019d
|
@ -795,13 +795,26 @@ namespace Microsoft.PowerFx.Connectors
|
|||
/// <param name="runtimeContext">RuntimeConnectorContext.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Function result.</returns>
|
||||
public async Task<FormulaValue> InvokeAsync(FormulaValue[] arguments, BaseRuntimeConnectorContext runtimeContext, CancellationToken cancellationToken)
|
||||
public Task<FormulaValue> InvokeAsync(FormulaValue[] arguments, BaseRuntimeConnectorContext runtimeContext, CancellationToken cancellationToken)
|
||||
{
|
||||
return InvokeAsync(arguments, runtimeContext, null, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call connector function.
|
||||
/// </summary>
|
||||
/// <param name="arguments">Arguments.</param>
|
||||
/// <param name="runtimeContext">RuntimeConnectorContext.</param>
|
||||
/// <param name="outputTypeOverride">The output type that should be used during output parsing.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Function result.</returns>
|
||||
public async Task<FormulaValue> InvokeAsync(FormulaValue[] arguments, BaseRuntimeConnectorContext runtimeContext, FormulaType outputTypeOverride, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
runtimeContext.ExecutionLogger?.LogInformation($"Entering in {this.LogFunction(nameof(InvokeAsync))}, with {LogArguments(arguments)}");
|
||||
FormulaValue formulaValue = await InvokeInternalAsync(arguments, runtimeContext, cancellationToken).ConfigureAwait(false);
|
||||
FormulaValue formulaValue = await InvokeInternalAsync(arguments, runtimeContext, outputTypeOverride, cancellationToken).ConfigureAwait(false);
|
||||
runtimeContext.ExecutionLogger?.LogInformation($"Exiting {this.LogFunction(nameof(InvokeAsync))}, returning from {nameof(InvokeInternalAsync)}, with {LogFormulaValue(formulaValue)}");
|
||||
return formulaValue;
|
||||
}
|
||||
|
@ -812,7 +825,12 @@ namespace Microsoft.PowerFx.Connectors
|
|||
}
|
||||
}
|
||||
|
||||
internal async Task<FormulaValue> InvokeInternalAsync(FormulaValue[] arguments, BaseRuntimeConnectorContext runtimeContext, CancellationToken cancellationToken)
|
||||
internal Task<FormulaValue> InvokeInternalAsync(FormulaValue[] arguments, BaseRuntimeConnectorContext runtimeContext, CancellationToken cancellationToken)
|
||||
{
|
||||
return InvokeInternalAsync(arguments, runtimeContext, null, cancellationToken);
|
||||
}
|
||||
|
||||
internal async Task<FormulaValue> InvokeInternalAsync(FormulaValue[] arguments, BaseRuntimeConnectorContext runtimeContext, FormulaType outputTypeOverride, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
|
@ -832,7 +850,7 @@ namespace Microsoft.PowerFx.Connectors
|
|||
|
||||
BaseRuntimeConnectorContext context = ReturnParameterType.Binary ? runtimeContext.WithRawResults() : runtimeContext;
|
||||
ScopedHttpFunctionInvoker invoker = new ScopedHttpFunctionInvoker(DPath.Root.Append(DName.MakeValid(Namespace, out _)), Name, Namespace, new HttpFunctionInvoker(this, context), context.ThrowOnError);
|
||||
FormulaValue result = await invoker.InvokeAsync(arguments, context, cancellationToken).ConfigureAwait(false);
|
||||
FormulaValue result = await invoker.InvokeAsync(arguments, context, outputTypeOverride, cancellationToken).ConfigureAwait(false);
|
||||
FormulaValue formulaValue = await PostProcessResultAsync(result, runtimeContext, invoker, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
runtimeContext.ExecutionLogger?.LogDebug($"Exiting {this.LogFunction(nameof(InvokeInternalAsync))}, returning {LogFormulaValue(formulaValue)}");
|
||||
|
|
|
@ -376,7 +376,7 @@ namespace Microsoft.PowerFx.Connectors
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<FormulaValue> DecodeResponseAsync(HttpResponseMessage response, bool throwOnError = false)
|
||||
public async Task<FormulaValue> DecodeResponseAsync(HttpResponseMessage response, FormulaType returnTypeOverride, bool throwOnError = false)
|
||||
{
|
||||
// https://github.com/microsoft/Power-Fx/issues/2119
|
||||
// https://github.com/microsoft/Power-Fx/issues/1172
|
||||
|
@ -429,11 +429,17 @@ namespace Microsoft.PowerFx.Connectors
|
|||
// We only return UO for unknown fields (not declared in swagger file) if compatibility is SwaggerCompatibility
|
||||
bool returnUnknownRecordFieldAsUO = _function.ConnectorSettings.Compatibility == ConnectorCompatibility.SwaggerCompatibility && _function.ConnectorSettings.ReturnUnknownRecordFieldsAsUntypedObjects;
|
||||
|
||||
var typeToUse = _function.ReturnType;
|
||||
if (returnTypeOverride != null)
|
||||
{
|
||||
typeToUse = returnTypeOverride;
|
||||
}
|
||||
|
||||
return string.IsNullOrWhiteSpace(text)
|
||||
? FormulaValue.NewBlank(_function.ReturnType)
|
||||
? FormulaValue.NewBlank(typeToUse)
|
||||
: _returnRawResults
|
||||
? FormulaValue.New(text)
|
||||
: FormulaValueJSON.FromJson(text, new FormulaValueJsonSerializerSettings() { ReturnUnknownRecordFieldsAsUntypedObjects = returnUnknownRecordFieldAsUO }, _function.ReturnType);
|
||||
: FormulaValueJSON.FromJson(text, new FormulaValueJsonSerializerSettings() { ReturnUnknownRecordFieldsAsUntypedObjects = returnUnknownRecordFieldAsUO }, typeToUse);
|
||||
}
|
||||
|
||||
string reasonPhrase = string.IsNullOrEmpty(response.ReasonPhrase) ? string.Empty : $" ({response.ReasonPhrase})";
|
||||
|
@ -453,7 +459,7 @@ namespace Microsoft.PowerFx.Connectors
|
|||
_function.ReturnType);
|
||||
}
|
||||
|
||||
public async Task<FormulaValue> InvokeAsync(IConvertToUTC utcConverter, string cacheScope, FormulaValue[] args, HttpMessageInvoker localInvoker, CancellationToken cancellationToken, bool throwOnError = false)
|
||||
public async Task<FormulaValue> InvokeAsync(IConvertToUTC utcConverter, string cacheScope, FormulaValue[] args, HttpMessageInvoker localInvoker, CancellationToken cancellationToken, FormulaType expectedType, bool throwOnError = false)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
|
@ -470,17 +476,17 @@ namespace Microsoft.PowerFx.Connectors
|
|||
});
|
||||
}
|
||||
|
||||
return await ExecuteHttpRequest(cacheScope, throwOnError, request, localInvoker, cancellationToken).ConfigureAwait(false);
|
||||
return await ExecuteHttpRequest(cacheScope, throwOnError, request, localInvoker, expectedType, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<FormulaValue> InvokeAsync(string url, string cacheScope, HttpMessageInvoker localInvoker, CancellationToken cancellationToken, bool throwOnError = false)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
using HttpRequestMessage request = new HttpRequestMessage(_function.HttpMethod, new Uri(url).PathAndQuery);
|
||||
return await ExecuteHttpRequest(cacheScope, throwOnError, request, localInvoker, cancellationToken).ConfigureAwait(false);
|
||||
return await ExecuteHttpRequest(cacheScope, throwOnError, request, localInvoker, null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<FormulaValue> ExecuteHttpRequest(string cacheScope, bool throwOnError, HttpRequestMessage request, HttpMessageInvoker localInvoker, CancellationToken cancellationToken)
|
||||
private async Task<FormulaValue> ExecuteHttpRequest(string cacheScope, bool throwOnError, HttpRequestMessage request, HttpMessageInvoker localInvoker, FormulaType returnTypeOverride, CancellationToken cancellationToken)
|
||||
{
|
||||
HttpMessageInvoker client = localInvoker ?? _httpClient;
|
||||
HttpResponseMessage response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
@ -494,7 +500,7 @@ namespace Microsoft.PowerFx.Connectors
|
|||
_logger?.LogInformation($"In {nameof(HttpFunctionInvoker)}.{nameof(ExecuteHttpRequest)}, response status code: {(int)response.StatusCode} {response.StatusCode}");
|
||||
}
|
||||
|
||||
return await DecodeResponseAsync(response, throwOnError).ConfigureAwait(false);
|
||||
return await DecodeResponseAsync(response, returnTypeOverride, throwOnError).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -521,12 +527,12 @@ namespace Microsoft.PowerFx.Connectors
|
|||
|
||||
internal HttpFunctionInvoker Invoker => _invoker;
|
||||
|
||||
public async Task<FormulaValue> InvokeAsync(FormulaValue[] args, BaseRuntimeConnectorContext runtimeContext, CancellationToken cancellationToken)
|
||||
public async Task<FormulaValue> InvokeAsync(FormulaValue[] args, BaseRuntimeConnectorContext runtimeContext, FormulaType outputTypeOverride, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var localInvoker = runtimeContext.GetInvoker(this.Namespace.Name);
|
||||
return await _invoker.InvokeAsync(new ConvertToUTC(runtimeContext.TimeZoneInfo), _cacheScope, args, localInvoker, cancellationToken, _throwOnError).ConfigureAwait(false);
|
||||
return await _invoker.InvokeAsync(new ConvertToUTC(runtimeContext.TimeZoneInfo), _cacheScope, args, localInvoker, cancellationToken, outputTypeOverride, _throwOnError).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<FormulaValue> InvokeAsync(string url, BaseRuntimeConnectorContext runtimeContext, CancellationToken cancellationToken)
|
||||
|
|
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.PowerFx.Core;
|
||||
using Microsoft.PowerFx.Core.Tests;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Tests;
|
||||
using Microsoft.PowerFx.Types;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -317,6 +318,46 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
Assert.Equal("The results of a Conversation task.", connectorReturnType.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ACSL_InvokeFunctionWithOutputOverride()
|
||||
{
|
||||
// this test is asserting that we can provide the expected output type that should be used during deserialization
|
||||
// this is useful in a situation when the output type is dynamic and is not available in the swagger
|
||||
using var testConnector = new LoggingTestServer(@"Swagger\TestConnectorDateTimeFormat.json", _output);
|
||||
OpenApiDocument apiDoc = testConnector._apiDocument;
|
||||
ConsoleLogger logger = new ConsoleLogger(_output);
|
||||
|
||||
PowerFxConfig pfxConfig = new PowerFxConfig(Features.PowerFxV1);
|
||||
ConnectorFunction function = OpenApiParser.GetFunctions(new ConnectorSettings("ACSL") { Compatibility = ConnectorCompatibility.SwaggerCompatibility }, apiDoc).OrderBy(cf => cf.Name).ToList()[0];
|
||||
Assert.Equal("AnalyzeConversationTextSubmitJob", function.Name);
|
||||
Assert.Equal("![createdDateTime`'Created Date':d, displayName:s]", function.ReturnType.ToStringWithDisplayNames());
|
||||
|
||||
using var testConnector2 = new LoggingTestServer(@"Swagger\TestConnectorDateTimeFormat.json", _output);
|
||||
using var httpClient2 = new HttpClient(testConnector2);
|
||||
testConnector2.SetResponseFromFile(@"Responses\TestConnectorDateTimeFormatResponse.json");
|
||||
using PowerPlatformConnectorClient client2 = new PowerPlatformConnectorClient("https://lucgen-apim.azure-api.net", "aaa373836ffd4915bf6eefd63d164adc" /* environment Id */, "16e7c181-2f8d-4cae-b1f0-179c5c4e4d8b" /* connectionId */, () => "No Auth", httpClient2)
|
||||
{
|
||||
SessionId = "a41bd03b-6c3c-4509-a844-e8c51b61f878",
|
||||
};
|
||||
|
||||
BaseRuntimeConnectorContext context2 = new TestConnectorRuntimeContext("ACSL", client2, console: _output);
|
||||
|
||||
DType.TryParse("![createdDateTime:d, displayName:d]", out DType dtype);
|
||||
var expectedFormulaType = FormulaType.Build(dtype);
|
||||
|
||||
FormulaValue httpResult = await function.InvokeAsync(new FormulaValue[0], context2, expectedFormulaType, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
RecordValue httpResultValue = (RecordValue)httpResult;
|
||||
FormulaValue displayName = httpResultValue.GetField("displayName");
|
||||
FormulaValue createdDateTime = httpResultValue.GetField("createdDateTime");
|
||||
|
||||
Assert.NotNull(httpResult);
|
||||
Assert.True(httpResult is RecordValue);
|
||||
Assert.True(displayName is DateTimeValue);
|
||||
Assert.True(createdDateTime is DateTimeValue);
|
||||
Assert.True(function.ReturnType != expectedFormulaType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ACSL_InvokeFunction()
|
||||
{
|
||||
|
@ -871,7 +912,7 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
Assert.NotNull(returnType);
|
||||
Assert.True(returnType.FormulaType is RecordType);
|
||||
|
||||
string input = testConnector._log.ToString();
|
||||
string input = testConnector._log.ToString().Replace("\r", string.Empty);
|
||||
var version = PowerPlatformConnectorClient.Version;
|
||||
string expected = $@"POST https://tip1002-002.azure-apihub.net/invoke
|
||||
authority: tip1002-002.azure-apihub.net
|
||||
|
@ -951,7 +992,7 @@ POST https://tip1002-002.azure-apihub.net/invoke
|
|||
Assert.Equal("accountcategorycode", suggestions2.Suggestions[0].DisplayName);
|
||||
Assert.Equal("Decimal", suggestions2.Suggestions[0].Suggestion.Type.ToString());
|
||||
|
||||
string input = testConnector._log.ToString();
|
||||
string input = testConnector._log.ToString().Replace("\r", string.Empty);
|
||||
var version = PowerPlatformConnectorClient.Version;
|
||||
string expected = @$"POST https://tip1-shared.azure-apim.net/invoke
|
||||
authority: tip1-shared.azure-apim.net
|
||||
|
@ -1025,7 +1066,7 @@ POST https://tip1-shared.azure-apim.net/invoke
|
|||
runtimeContext,
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
string input = testConnector._log.ToString();
|
||||
string input = testConnector._log.ToString().Replace("\r", string.Empty);
|
||||
Assert.Equal("AdaptiveCard", (((RecordValue)result).GetField("type") as UntypedObjectValue).Impl.GetString());
|
||||
Assert.Equal(
|
||||
$@"POST https://tip1002-002.azure-apihub.net/invoke
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"displayName": "2024-05-29T17:57:00.2209666-06:00",
|
||||
"createdDateTime": "2024-05-29T17:57:00.2209666-06:00"
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"version": "v2.0",
|
||||
"title": "Azure Cognitive Service for Language",
|
||||
"description": "Azure Cognitive Service for Language, previously known as 'Text Analytics' connector detects language, sentiment and more of the text you provide.",
|
||||
"contact": {
|
||||
"name": "Microsoft",
|
||||
"url": "https://gallery.cortanaanalytics.com/MachineLearningAPI/Text-Analytics-2",
|
||||
"email": "mlapi@microsoft.com"
|
||||
},
|
||||
"x-ms-api-annotation": {
|
||||
"status": "Production"
|
||||
}
|
||||
},
|
||||
"host": "tip1-shared-002.azure-apim.net",
|
||||
"basePath": "/apim/cognitiveservicestextanalytics",
|
||||
"schemes": [
|
||||
"https"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"paths": {
|
||||
"/{connectionId}/language/analyze-conversations/jobs/": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"description": "Submit a collection of conversations for analysis. Specify one or more unique tasks to be executed..",
|
||||
"operationId": "AnalyzeConversationTextSubmitJob",
|
||||
"summary": "Async Conversation PII (text) (2022-05-15-preview)",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Analysis job status and metadata.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ConversationalPIITextJobState"
|
||||
}
|
||||
},
|
||||
"202": {
|
||||
"description": "A successful call results with an Operation-Location header used to check the status of the analysis job.",
|
||||
"headers": {
|
||||
"Operation-Location": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Error response.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ErrorResponse"
|
||||
}
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
"url": "https://docs.microsoft.com/connectors/cognitiveservicestextanalytics/#async-conversation-pii-(text)-(2022-05-15-preview)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"ConversationalPIITextJobState": {
|
||||
"type": "object",
|
||||
"description": "Contains the status of the analyze conversations job submitted along with related statistics.",
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdDateTime": {
|
||||
"format": "date-time",
|
||||
"description": "The date and time in the UTC time zone when the item was created.",
|
||||
"type": "string",
|
||||
"x-ms-summary": "Created Date"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
},
|
||||
"ErrorResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"error"
|
||||
],
|
||||
"properties": {
|
||||
"error": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": {},
|
||||
"x-ms-connector-metadata": [
|
||||
{
|
||||
"propertyName": "Website",
|
||||
"propertyValue": "https://azure.microsoft.com/services/cognitive-services/text-analytics/"
|
||||
}
|
||||
],
|
||||
"externalDocs": {
|
||||
"url": "https://docs.microsoft.com/connectors/cognitiveservicestextanalytics"
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче