зеркало из https://github.com/microsoft/Power-Fx.git
Introduce IsType, AsType and ParseJSON overloads with custom types (#2569)
Support IsType, AsType and ParseJSON functions with custom type as input parameter ``` >> IsType(ParseJSON("42"), Number) true >> AsType(ParseJSON("42"), Number) 42 >> ParseJSON("42", Number) 42 >> AsType(ParseJSON("{ ""a"": 42 }"), Type({a: Number})) {a : 42} ``` --------- Co-authored-by: Luc Genetier <69138830+LucGenetier@users.noreply.github.com>
This commit is contained in:
Родитель
930ca694c5
Коммит
251e918f32
|
@ -861,7 +861,7 @@ namespace Microsoft.PowerFx.Connectors
|
|||
{
|
||||
ExpressionError er = null;
|
||||
|
||||
if (result is ErrorValue ev && (er = ev.Errors.FirstOrDefault(e => e.Kind == ErrorKind.Network || e.Kind == ErrorKind.InvalidJSON)) != null)
|
||||
if (result is ErrorValue ev && (er = ev.Errors.FirstOrDefault(e => e.Kind == ErrorKind.Network || e.Kind == ErrorKind.InvalidJSON || e.Kind == ErrorKind.InvalidArgument)) != null)
|
||||
{
|
||||
runtimeContext.ExecutionLogger?.LogError($"{this.LogFunction(nameof(PostProcessResultAsync))}, ErrorValue is returned with {er.Message}");
|
||||
ExpressionError newError = er is HttpExpressionError her
|
||||
|
|
|
@ -78,6 +78,11 @@ namespace Microsoft.PowerFx.Core.Binding
|
|||
/// </summary>
|
||||
TypeName,
|
||||
|
||||
Lim
|
||||
/// <summary>
|
||||
/// This BindKind applies to globally defined NamedTypes is not used for any data.
|
||||
/// </summary>
|
||||
NamedType,
|
||||
|
||||
Lim,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ using Microsoft.PowerFx.Core.Functions.Delegation;
|
|||
using Microsoft.PowerFx.Core.Functions.Delegation.DelegationStrategies;
|
||||
using Microsoft.PowerFx.Core.Glue;
|
||||
using Microsoft.PowerFx.Core.Localization;
|
||||
using Microsoft.PowerFx.Core.Syntax.Visitors;
|
||||
using Microsoft.PowerFx.Core.Texl;
|
||||
using Microsoft.PowerFx.Core.Texl.Builtins;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.Types.Enums;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
|
@ -2643,6 +2645,30 @@ namespace Microsoft.PowerFx.Core.Binding
|
|||
_txb.SetType(node, DType.ObjNull);
|
||||
}
|
||||
|
||||
public override void Visit(TypeLiteralNode node)
|
||||
{
|
||||
AssertValid();
|
||||
Contracts.AssertValue(node);
|
||||
|
||||
if (_nameResolver == null)
|
||||
{
|
||||
_txb.SetType(node, DType.Unknown);
|
||||
return;
|
||||
}
|
||||
|
||||
var type = DTypeVisitor.Run(node.TypeRoot, _nameResolver);
|
||||
|
||||
if (type.IsValid)
|
||||
{
|
||||
_txb.SetType(node, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
_txb.SetType(node, DType.Error);
|
||||
_txb.ErrorContainer.Error(node, TexlStrings.ErrTypeLiteral_InvalidTypeDefinition, node.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public override void Visit(BoolLitNode node)
|
||||
{
|
||||
AssertValid();
|
||||
|
@ -4296,6 +4322,34 @@ namespace Microsoft.PowerFx.Core.Binding
|
|||
|
||||
_txb.SetInfo(node, new CallInfo(maybeFunc, node, null, default, false, _currentScope.Nest));
|
||||
_txb.SetType(node, maybeFunc.ReturnType);
|
||||
}
|
||||
|
||||
// checks if the call node best matches function overloads with UntypedObject/JSON
|
||||
private bool MatchOverloadWithUntypedOrJSONConversionFunctions(CallNode node, TexlFunction maybeFunc)
|
||||
{
|
||||
Contracts.AssertValue(node);
|
||||
Contracts.AssertValue(maybeFunc);
|
||||
Contracts.Assert(maybeFunc.HasTypeArgs);
|
||||
|
||||
if (maybeFunc.Name == AsTypeFunction.AsTypeInvariantFunctionName &&
|
||||
_txb.GetType(node.Args.Children[0]) == DType.UntypedObject)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (maybeFunc.Name == IsTypeFunction_UO.IsTypeInvariantFunctionName &&
|
||||
_txb.GetType(node.Args.Children[0]) == DType.UntypedObject)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (maybeFunc.Name == ParseJSONFunction.ParseJSONInvariantFunctionName &&
|
||||
node.Args.Count > 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool PreVisit(CallNode node)
|
||||
|
@ -4346,7 +4400,10 @@ namespace Microsoft.PowerFx.Core.Binding
|
|||
|
||||
// If there are no overloads with lambdas or identifiers, we can continue the visitation and
|
||||
// yield to the normal overload resolution.
|
||||
var overloadsWithLambdasOrIdentifiers = overloads.Where(func => func.HasLambdas || func.HasColumnIdentifiers);
|
||||
var overloadsWithLambdasOrIdentifiers = overloads.Where(func => func.HasLambdas || func.HasColumnIdentifiers);
|
||||
|
||||
var overloadsWithTypeArgs = overloads.Where(func => func.HasTypeArgs);
|
||||
|
||||
if (!overloadsWithLambdasOrIdentifiers.Any())
|
||||
{
|
||||
// We may still need a scope to determine inline-record types
|
||||
|
@ -4377,6 +4434,25 @@ namespace Microsoft.PowerFx.Core.Binding
|
|||
startArg++;
|
||||
}
|
||||
|
||||
if (overloadsWithTypeArgs.Any() && node.Args.Count > 1)
|
||||
{
|
||||
var nodeInp = node.Args.Children[0];
|
||||
nodeInp.Accept(this);
|
||||
|
||||
Contracts.Assert(overloadsWithTypeArgs.Count() == 1);
|
||||
|
||||
var functionWithTypeArg = overloadsWithTypeArgs.First();
|
||||
|
||||
if (MatchOverloadWithUntypedOrJSONConversionFunctions(node, functionWithTypeArg))
|
||||
{
|
||||
PreVisitTypeArgAndProccesCallNode(node, functionWithTypeArg);
|
||||
FinalizeCall(node);
|
||||
return false;
|
||||
}
|
||||
|
||||
startArg++;
|
||||
}
|
||||
|
||||
PreVisitHeadNode(node);
|
||||
PreVisitBottomUp(node, startArg, maybeScope);
|
||||
FinalizeCall(node);
|
||||
|
@ -5034,6 +5110,96 @@ namespace Microsoft.PowerFx.Core.Binding
|
|||
}
|
||||
|
||||
_txb.SetType(node, returnType);
|
||||
}
|
||||
|
||||
// Method to previsit type arg of callnode if it is determined as untyped/json conversion function
|
||||
private void PreVisitTypeArgAndProccesCallNode(CallNode node, TexlFunction func)
|
||||
{
|
||||
AssertValid();
|
||||
Contracts.AssertValue(node);
|
||||
Contracts.AssertValue(func);
|
||||
Contracts.Assert(func.HasTypeArgs);
|
||||
|
||||
var args = node.Args.Children.ToArray();
|
||||
var argCount = args.Count();
|
||||
|
||||
Contracts.AssertValue(_txb.GetType(args[0]));
|
||||
|
||||
if (argCount < func.MinArity || argCount > func.MaxArity)
|
||||
{
|
||||
ArityError(func.MinArity, func.MaxArity, node, argCount, _txb.ErrorContainer);
|
||||
_txb.SetInfo(node, new CallInfo(func, node));
|
||||
_txb.SetType(node, DType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
Contracts.Assert(argCount > 1);
|
||||
Contracts.AssertValue(args[1]);
|
||||
|
||||
if (args[1] is FirstNameNode typeName)
|
||||
{
|
||||
if (_nameResolver.LookupType(typeName.Ident.Name, out var typeArgType))
|
||||
{
|
||||
_txb.SetType(typeName, typeArgType._type);
|
||||
_txb.SetInfo(typeName, FirstNameInfo.Create(typeName, new NameLookupInfo(BindKind.NamedType, typeArgType._type, DPath.Root, 0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
_txb.ErrorContainer.Error(args[1], TexlStrings.ErrInvalidName, typeName.Ident.Name.Value);
|
||||
_txb.SetType(args[1], DType.Error);
|
||||
_txb.SetInfo(typeName, FirstNameInfo.Create(typeName, new NameLookupInfo(BindKind.NamedType, DType.Error, DPath.Root, 0)));
|
||||
|
||||
_txb.ErrorContainer.Error(node, TexlStrings.ErrInvalidArgumentExpectedType, typeName.Ident.Name.Value);
|
||||
_txb.SetInfo(node, new CallInfo(func, node));
|
||||
_txb.SetType(node, DType.Error);
|
||||
}
|
||||
}
|
||||
else if (args[1] is TypeLiteralNode typeLiteral)
|
||||
{
|
||||
typeLiteral.Accept(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
_txb.ErrorContainer.Error(args[1], TexlStrings.ErrInvalidArgumentExpectedType, args[1]);
|
||||
_txb.SetType(args[1], DType.Error);
|
||||
}
|
||||
|
||||
PostVisit(node.Args);
|
||||
|
||||
var info = _txb.GetInfo(node);
|
||||
|
||||
// If PreVisit resulted in errors for the node (and a non-null CallInfo),
|
||||
// we're done -- we have a match and appropriate errors logged already.
|
||||
if (_txb.ErrorContainer.HasErrors(node) || _txb.ErrorContainer.HasErrors(node.Head.Token))
|
||||
{
|
||||
Contracts.Assert(info != null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Contracts.AssertNull(info);
|
||||
|
||||
_txb.SetInfo(node, new CallInfo(func, node));
|
||||
|
||||
var returnType = func.ReturnType;
|
||||
var argTypes = args.Select(_txb.GetType).ToArray();
|
||||
bool fArgsValid;
|
||||
var checkErrorContainer = new ErrorContainer();
|
||||
|
||||
// Typecheck the invocation and infer the return type.
|
||||
fArgsValid = func.HandleCheckInvocation(_txb, args, argTypes, checkErrorContainer, out returnType, out var _);
|
||||
|
||||
if (checkErrorContainer?.HasErrors() == true)
|
||||
{
|
||||
_txb.ErrorContainer.MergeErrors(checkErrorContainer.GetErrors());
|
||||
}
|
||||
|
||||
if (!fArgsValid)
|
||||
{
|
||||
_txb.ErrorContainer.Error(DocumentErrorSeverity.Severe, node.Head.Token, TexlStrings.ErrInvalidArgs_Func, func.Name);
|
||||
}
|
||||
|
||||
_txb.SetType(node, returnType);
|
||||
}
|
||||
|
||||
private void PreVisitBottomUp(CallNode node, int argCountVisited, Scope scopeNew = null)
|
||||
|
|
|
@ -388,6 +388,10 @@ namespace Microsoft.PowerFx.Core.Functions
|
|||
|
||||
public bool IsDeprecatedOrInternalFunction => this is IHasUnsupportedFunctions sdf && (sdf.IsDeprecated || sdf.IsInternal);
|
||||
|
||||
// This property is true for a function if and only if there is an argIndex such that func.ArgIsType(argIndex) == true
|
||||
// Eg: for example TypedParseJSON.ArgIsType(1) == true and hence TypedParseJSON.HasTypeArg is true
|
||||
public virtual bool HasTypeArgs => false;
|
||||
|
||||
public TexlFunction(
|
||||
DPath theNamespace,
|
||||
string name,
|
||||
|
@ -514,6 +518,11 @@ namespace Microsoft.PowerFx.Core.Functions
|
|||
return SupportsParamCoercion && (argIndex <= MinArity || argIndex <= MaxArity);
|
||||
}
|
||||
|
||||
public virtual bool ArgIsType(int argIndex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckTypesCore(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary<TexlNode, DType> nodeToCoercedTypeMap)
|
||||
{
|
||||
Contracts.AssertValue(args);
|
||||
|
@ -531,8 +540,8 @@ namespace Microsoft.PowerFx.Core.Functions
|
|||
// Type check the args
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
// Identifiers don't have a type
|
||||
if (ParameterCanBeIdentifier(args[i], i, context.Features))
|
||||
// Identifiers don't have a type and type arguments need not be type checked
|
||||
if (ParameterCanBeIdentifier(args[i], i, context.Features) || ArgIsType(i))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
@ -567,7 +576,7 @@ namespace Microsoft.PowerFx.Core.Functions
|
|||
for (var i = count; i < args.Length; i++)
|
||||
{
|
||||
// Identifiers don't have a type
|
||||
if (ParameterCanBeIdentifier(args[i], i, context.Features))
|
||||
if (ParameterCanBeIdentifier(args[i], i, context.Features) || ArgIsType(i))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -80,6 +80,14 @@ namespace Microsoft.PowerFx.Core.IR
|
|||
Contracts.AssertValue(context);
|
||||
|
||||
return MaybeInjectCoercion(node, new TextLiteralNode(context.GetIRContext(node), node.Value), context);
|
||||
}
|
||||
|
||||
public override IntermediateNode Visit(TypeLiteralNode node, IRTranslatorContext context)
|
||||
{
|
||||
Contracts.AssertValue(node);
|
||||
Contracts.AssertValue(context);
|
||||
|
||||
return new TextLiteralNode(IRContext.NotInSource(FormulaType.String), context.Binding.GetType(node).ToString());
|
||||
}
|
||||
|
||||
public override IntermediateNode Visit(NumLitNode node, IRTranslatorContext context)
|
||||
|
@ -672,6 +680,11 @@ namespace Microsoft.PowerFx.Core.IR
|
|||
|
||||
result = new ResolvedObjectNode(context.GetIRContext(node), info.Data);
|
||||
break;
|
||||
}
|
||||
|
||||
case BindKind.NamedType:
|
||||
{
|
||||
return new TextLiteralNode(IRContext.NotInSource(FormulaType.String), context.Binding.GetType(node).ToString());
|
||||
}
|
||||
|
||||
default:
|
||||
|
|
|
@ -466,10 +466,18 @@ namespace Microsoft.PowerFx.Core.Localization
|
|||
public static StringGetter IsTypeArg1 = (b) => StringResources.Get("IsTypeArg1", b);
|
||||
public static StringGetter IsTypeArg2 = (b) => StringResources.Get("IsTypeArg2", b);
|
||||
|
||||
public static StringGetter AboutIsTypeUO = (b) => StringResources.Get("AboutIsTypeUO", b);
|
||||
public static StringGetter IsTypeUOArg1 = (b) => StringResources.Get("IsTypeUOArg1", b);
|
||||
public static StringGetter IsTypeUOArg2 = (b) => StringResources.Get("IsTypeUOArg2", b);
|
||||
|
||||
public static StringGetter AboutAsType = (b) => StringResources.Get("AboutAsType", b);
|
||||
public static StringGetter AsTypeArg1 = (b) => StringResources.Get("AsTypeArg1", b);
|
||||
public static StringGetter AsTypeArg2 = (b) => StringResources.Get("AsTypeArg2", b);
|
||||
|
||||
public static StringGetter AboutAsTypeUO = (b) => StringResources.Get("AboutAsTypeUO", b);
|
||||
public static StringGetter AsTypeUOArg1 = (b) => StringResources.Get("AsTypeUOArg1", b);
|
||||
public static StringGetter AsTypeUOArg2 = (b) => StringResources.Get("AsTypeUOArg2", b);
|
||||
|
||||
public static StringGetter AboutWith = (b) => StringResources.Get("AboutWith", b);
|
||||
public static StringGetter WithArg1 = (b) => StringResources.Get("WithArg1", b);
|
||||
public static StringGetter WithArg2 = (b) => StringResources.Get("WithArg2", b);
|
||||
|
@ -482,6 +490,10 @@ namespace Microsoft.PowerFx.Core.Localization
|
|||
public static StringGetter AboutParseJSON = (b) => StringResources.Get("AboutParseJSON", b);
|
||||
public static StringGetter ParseJSONArg1 = (b) => StringResources.Get("ParseJSONArg1", b);
|
||||
|
||||
public static StringGetter AboutTypedParseJSON = (b) => StringResources.Get("AboutTypedParseJSON", b);
|
||||
public static StringGetter TypedParseJSONArg1 = (b) => StringResources.Get("TypedParseJSONArg1", b);
|
||||
public static StringGetter TypedParseJSONArg2 = (b) => StringResources.Get("TypedParseJSONArg2", b);
|
||||
|
||||
public static StringGetter AboutFileInfo = (b) => StringResources.Get("AboutFileInfo", b);
|
||||
public static StringGetter FileInfoArg1 = (b) => StringResources.Get("FileInfoArg1", b);
|
||||
|
||||
|
@ -829,5 +841,7 @@ namespace Microsoft.PowerFx.Core.Localization
|
|||
public static ErrorResourceKey ErrSummarizeDataSourceScopeNotSupported = new ErrorResourceKey("ErrSummarizeDataSourceScopeNotSupported");
|
||||
|
||||
public static ErrorResourceKey ErrInvalidDataSourceForFunction = new ErrorResourceKey("ErrInvalidDataSourceForFunction");
|
||||
public static ErrorResourceKey ErrInvalidArgumentExpectedType = new ErrorResourceKey("ErrInvalidArgumentExpectedType");
|
||||
public static ErrorResourceKey ErrUnsupportedTypeInTypeArgument = new ErrorResourceKey("ErrUnsupportedTypeInTypeArgument");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ namespace Microsoft.PowerFx
|
|||
(NumberIsFloat ? TexlParser.Flags.NumberIsFloat : 0) |
|
||||
(DisableReservedKeywords ? TexlParser.Flags.DisableReservedKeywords : 0) |
|
||||
(TextFirst ? TexlParser.Flags.TextFirst : 0) |
|
||||
(AllowParseAsTypeLiteral ? TexlParser.Flags.AllowTypeLiteral : 0) |
|
||||
(features.PowerFxV1CompatibilityRules ? TexlParser.Flags.PFxV1 : 0);
|
||||
|
||||
var result = TexlParser.ParseScript(script, features, Culture, flags);
|
||||
|
|
|
@ -320,14 +320,13 @@ namespace Microsoft.PowerFx.Core.Parser
|
|||
var result = ParseExpr(Precedence.None);
|
||||
if (result is TypeLiteralNode typeLiteralNode)
|
||||
{
|
||||
if (typeLiteralNode.IsValid(out var errors))
|
||||
if (typeLiteralNode.IsValid(out _))
|
||||
{
|
||||
definedTypes.Add(new DefinedType(thisIdentifier.As<IdentToken>(), typeLiteralNode, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
definedTypes.Add(new DefinedType(thisIdentifier.As<IdentToken>(), typeLiteralNode, false));
|
||||
CollectionUtils.Add(ref _errors, errors);
|
||||
}
|
||||
|
||||
continue;
|
||||
|
@ -1189,7 +1188,14 @@ namespace Microsoft.PowerFx.Core.Parser
|
|||
{
|
||||
if (ident.Token.As<IdentToken>().Name.Value == "Type" && _flagsMode.Peek().HasFlag(Flags.AllowTypeLiteral))
|
||||
{
|
||||
return ParseTypeLiteral();
|
||||
var typeLiteralNode = ParseTypeLiteral();
|
||||
|
||||
if (!typeLiteralNode.IsValid(out var err))
|
||||
{
|
||||
CollectionUtils.Add(ref _errors, err);
|
||||
}
|
||||
|
||||
return typeLiteralNode;
|
||||
}
|
||||
|
||||
trivia = ParseTrivia();
|
||||
|
|
|
@ -325,6 +325,11 @@ namespace Microsoft.PowerFx.Syntax
|
|||
public override void Visit(SelfNode node)
|
||||
{
|
||||
SetCurrentNodeAsResult(node);
|
||||
}
|
||||
|
||||
public override void Visit(TypeLiteralNode node)
|
||||
{
|
||||
SetCurrentNodeAsResult(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -259,7 +259,10 @@ namespace Microsoft.PowerFx.Core.Texl
|
|||
public static readonly TexlFunction UTCToday = _featureGateFunctions.Add(new UTCTodayFunction());
|
||||
public static readonly TexlFunction BooleanL = _featureGateFunctions.Add(new BooleanLFunction());
|
||||
public static readonly TexlFunction BooleanL_T = _featureGateFunctions.Add(new BooleanLFunction_T());
|
||||
public static readonly TexlFunction Summarize = _featureGateFunctions.Add(new SummarizeFunction());
|
||||
public static readonly TexlFunction Summarize = _featureGateFunctions.Add(new SummarizeFunction());
|
||||
public static readonly TexlFunction AsType_UO = _featureGateFunctions.Add(new AsTypeFunction_UO());
|
||||
public static readonly TexlFunction IsType_UO = _featureGateFunctions.Add(new IsTypeFunction_UO());
|
||||
public static readonly TexlFunction TypedParseJSON = _featureGateFunctions.Add(new TypedParseJSONFunction());
|
||||
|
||||
// Slow API, only use for backward compatibility
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
|
|
@ -141,6 +141,20 @@ namespace Microsoft.PowerFx.Core.Texl.Builtins
|
|||
// AsType should always be marked as valid regardless of it being async and impure.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// AsType(UntypedObject:O, Type:U): ?
|
||||
internal class AsTypeFunction_UO : UntypedOrJSONConversionFunction
|
||||
{
|
||||
public AsTypeFunction_UO()
|
||||
: base(AsTypeFunction.AsTypeInvariantFunctionName, TexlStrings.AboutAsTypeUO, DType.Error, 2, DType.UntypedObject, DType.Error)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<TexlStrings.StringGetter[]> GetSignatures()
|
||||
{
|
||||
yield return new[] { TexlStrings.AsTypeUOArg1, TexlStrings.AsTypeUOArg2 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.PowerFx.Core.App.ErrorContainers;
|
||||
using Microsoft.PowerFx.Core.Functions;
|
||||
using Microsoft.PowerFx.Core.Localization;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
using Microsoft.PowerFx.Syntax;
|
||||
|
||||
#pragma warning disable SA1402 // File may only contain a single type
|
||||
#pragma warning disable SA1649 // File name should match first type name
|
||||
|
||||
namespace Microsoft.PowerFx.Core.Texl.Builtins
|
||||
{
|
||||
// IsType(UntypedObject:O, Type:U): Boolean
|
||||
internal class IsTypeFunction_UO : UntypedOrJSONConversionFunction
|
||||
{
|
||||
public const string IsTypeInvariantFunctionName = "IsType";
|
||||
|
||||
public IsTypeFunction_UO()
|
||||
: base(IsTypeInvariantFunctionName, TexlStrings.AboutIsTypeUO, DType.Boolean, 2, DType.UntypedObject, DType.Error)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<TexlStrings.StringGetter[]> GetSignatures()
|
||||
{
|
||||
yield return new[] { TexlStrings.IsTypeUOArg1, TexlStrings.IsTypeUOArg2 };
|
||||
}
|
||||
|
||||
public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary<TexlNode, DType> nodeToCoercedTypeMap)
|
||||
{
|
||||
Contracts.AssertValue(args);
|
||||
Contracts.AssertAllValues(args);
|
||||
Contracts.AssertValue(argTypes);
|
||||
Contracts.Assert(args.Length == 2);
|
||||
Contracts.Assert(argTypes.Length == 2);
|
||||
Contracts.AssertValue(errors);
|
||||
|
||||
if (!base.CheckTypes(context, args, argTypes, errors, out returnType, out nodeToCoercedTypeMap))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
returnType = DType.Boolean;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore SA1402 // File may only contain a single type
|
||||
#pragma warning restore SA1649 // File name should match first type name
|
|
@ -2,9 +2,16 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.PowerFx.Core.App.ErrorContainers;
|
||||
using Microsoft.PowerFx.Core.Binding;
|
||||
using Microsoft.PowerFx.Core.Errors;
|
||||
using Microsoft.PowerFx.Core.Functions;
|
||||
using Microsoft.PowerFx.Core.Localization;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
using Microsoft.PowerFx.Syntax;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Core.Texl.Builtins
|
||||
{
|
||||
|
@ -45,4 +52,18 @@ namespace Microsoft.PowerFx.Core.Texl.Builtins
|
|||
yield return new[] { TexlStrings.IndexArg1, TexlStrings.IndexArg2 };
|
||||
}
|
||||
}
|
||||
|
||||
// ParseJSON(JsonString:s, Type:U): ?
|
||||
internal class TypedParseJSONFunction : UntypedOrJSONConversionFunction
|
||||
{
|
||||
public TypedParseJSONFunction()
|
||||
: base(ParseJSONFunction.ParseJSONInvariantFunctionName, TexlStrings.AboutTypedParseJSON, DType.Error, 2, DType.String, DType.Error)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<TexlStrings.StringGetter[]> GetSignatures()
|
||||
{
|
||||
yield return new[] { TexlStrings.TypedParseJSONArg1, TexlStrings.TypedParseJSONArg2 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.PowerFx.Core.App.ErrorContainers;
|
||||
using Microsoft.PowerFx.Core.Binding;
|
||||
using Microsoft.PowerFx.Core.Errors;
|
||||
using Microsoft.PowerFx.Core.Functions;
|
||||
using Microsoft.PowerFx.Core.IR;
|
||||
using Microsoft.PowerFx.Core.Localization;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
using Microsoft.PowerFx.Syntax;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Core.Texl.Builtins
|
||||
{
|
||||
internal abstract class UntypedOrJSONConversionFunction : BuiltinFunction
|
||||
{
|
||||
public override bool IsSelfContained => true;
|
||||
|
||||
public override bool SupportsParamCoercion => false;
|
||||
|
||||
public override bool HasTypeArgs => true;
|
||||
|
||||
public override bool ArgIsType(int argIndex)
|
||||
{
|
||||
Contracts.Assert(HasTypeArgs);
|
||||
return argIndex == 1;
|
||||
}
|
||||
|
||||
private static readonly ISet<DType> SupportedJSONTypes = new HashSet<DType> { DType.Boolean, DType.Number, DType.Decimal, DType.Date, DType.DateTime, DType.DateTimeNoTimeZone, DType.Time, DType.String, DType.Guid, DType.Hyperlink, DType.UntypedObject };
|
||||
|
||||
public UntypedOrJSONConversionFunction(string name, TexlStrings.StringGetter description, DType returnType, int arityMax, params DType[] paramTypes)
|
||||
: base(name, description, FunctionCategories.Text, returnType, 0, 2, arityMax, paramTypes)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool CheckTypes(CheckTypesContext context, TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary<TexlNode, DType> nodeToCoercedTypeMap)
|
||||
{
|
||||
Contracts.AssertValue(args);
|
||||
Contracts.AssertAllValues(args);
|
||||
Contracts.AssertValue(argTypes);
|
||||
Contracts.Assert(args.Length >= 2 && args.Length <= MaxArity);
|
||||
Contracts.Assert(argTypes.Length >= 2 && argTypes.Length <= MaxArity);
|
||||
Contracts.AssertValue(errors);
|
||||
|
||||
if (!base.CheckTypes(context, args, argTypes, errors, out returnType, out nodeToCoercedTypeMap))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
returnType = argTypes[1];
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void CheckSemantics(TexlBinding binding, TexlNode[] args, DType[] argTypes, IErrorContainer errors)
|
||||
{
|
||||
Contracts.AssertValue(args);
|
||||
Contracts.AssertAllValues(args);
|
||||
Contracts.AssertValue(argTypes);
|
||||
Contracts.Assert(args.Length >= 2 && args.Length <= MaxArity);
|
||||
Contracts.Assert(argTypes.Length >= 2 && argTypes.Length <= MaxArity);
|
||||
Contracts.AssertValue(errors);
|
||||
|
||||
base.CheckSemantics(binding, args, argTypes, errors);
|
||||
|
||||
CheckTypeArgHasSupportedTypes(args[1], argTypes[1], errors);
|
||||
}
|
||||
|
||||
private void CheckTypeArgHasSupportedTypes(TexlNode typeArg, DType type, IErrorContainer errors)
|
||||
{
|
||||
// Dataverse types may contain fields with ExpandInfo that may have self / mutually recursive reference
|
||||
// we allow these in type check phase by ignoring validation of types in such fields.
|
||||
if (type.HasExpandInfo)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((type.IsRecordNonObjNull || type.IsTableNonObjNull) && type.TypeTree != null)
|
||||
{
|
||||
type.TypeTree.ToList().ForEach(t => CheckTypeArgHasSupportedTypes(typeArg, t.Value, errors));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SupportedJSONTypes.Contains(type))
|
||||
{
|
||||
errors.EnsureError(DocumentErrorSeverity.Severe, typeArg, TexlStrings.ErrUnsupportedTypeInTypeArgument, type.Kind);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,9 @@ namespace Microsoft.PowerFx
|
|||
{
|
||||
config.AddFunction(new ParseJSONFunctionImpl());
|
||||
config.AddFunction(new JsonFunctionImpl());
|
||||
config.AddFunction(new AsTypeFunction_UOImpl());
|
||||
config.AddFunction(new IsTypeFunction_UOImpl());
|
||||
config.AddFunction(new TypedParseJSONFunctionImpl());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Microsoft.PowerFx.Core.IR;
|
||||
|
@ -17,6 +18,13 @@ namespace Microsoft.PowerFx.Types
|
|||
public bool NumberIsFloat { get; init; } = false;
|
||||
|
||||
public bool ReturnUnknownRecordFieldsAsUntypedObjects { get; init; } = false;
|
||||
|
||||
// JSON value input of type object may contain fields with values that not present in the target schema.
|
||||
// This attribute controls if the result such conversion should be valid or an error.
|
||||
// This is false (i.e we dont allow additional field in input) when used for functions like AsType_UO, IsType_UO and TypedParseJSON
|
||||
public bool AllowUnknownRecordFields { get; init; } = true;
|
||||
|
||||
public TimeZoneInfo ResultTimeZone { get; init; } = TimeZoneInfo.Utc;
|
||||
}
|
||||
|
||||
internal class FormulaValueJsonSerializerWorkingData
|
||||
|
@ -58,7 +66,7 @@ namespace Microsoft.PowerFx.Types
|
|||
{
|
||||
Message = $"{pfxje.GetType().Name} {pfxje.Message}",
|
||||
Span = new Syntax.Span(0, 0),
|
||||
Kind = ErrorKind.InvalidJSON
|
||||
Kind = ErrorKind.InvalidArgument
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +84,7 @@ namespace Microsoft.PowerFx.Types
|
|||
|
||||
public static FormulaValue FromJson(JsonElement element, FormulaValueJsonSerializerSettings settings, FormulaType formulaType = null)
|
||||
{
|
||||
return FromJson(element, settings, new FormulaValueJsonSerializerWorkingData(), formulaType);
|
||||
return FromJson(element, settings, new FormulaValueJsonSerializerWorkingData(), formulaType);
|
||||
}
|
||||
|
||||
internal static FormulaValue FromJson(JsonElement element, FormulaValueJsonSerializerSettings settings, FormulaValueJsonSerializerWorkingData data, FormulaType formulaType = null)
|
||||
|
@ -126,18 +134,29 @@ namespace Microsoft.PowerFx.Types
|
|||
dt2 = dt2.ToUniversalTime();
|
||||
}
|
||||
|
||||
if (dt2.Kind == DateTimeKind.Unspecified)
|
||||
if (settings.ResultTimeZone == TimeZoneInfo.Utc && dt2.Kind == DateTimeKind.Unspecified)
|
||||
{
|
||||
dt2 = new DateTime(dt2.Ticks, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
return DateTimeValue.New(dt2);
|
||||
return DateTimeValue.New(DateTimeValue.GetConvertedDateTimeValue(dt2, settings.ResultTimeZone));
|
||||
}
|
||||
else if (formulaType is DateTimeNoTimeZoneType)
|
||||
{
|
||||
DateTime dt3 = element.GetDateTime();
|
||||
return DateTimeValue.New(TimeZoneInfo.ConvertTimeToUtc(dt3));
|
||||
}
|
||||
else if (formulaType is TimeType)
|
||||
{
|
||||
var timeString = element.GetString();
|
||||
if (TimeSpan.TryParseExact(timeString, @"hh\:mm\:ss\.FFFFFFF", CultureInfo.InvariantCulture, TimeSpanStyles.None, out var res) ||
|
||||
TimeSpan.TryParseExact(timeString, @"hh\:mm\:ss", CultureInfo.InvariantCulture, TimeSpanStyles.None, out res))
|
||||
{
|
||||
return TimeValue.New(res);
|
||||
}
|
||||
|
||||
throw new PowerFxJsonException($"Time '{timeString}' could not be parsed", data.Path);
|
||||
}
|
||||
else if (formulaType is BlobType)
|
||||
{
|
||||
return FormulaValue.NewBlob(element.GetBytesFromBase64());
|
||||
|
@ -226,6 +245,11 @@ namespace Microsoft.PowerFx.Types
|
|||
|
||||
if (recordType?.TryGetFieldType(name, out fieldType) == false)
|
||||
{
|
||||
if (!settings.AllowUnknownRecordFields)
|
||||
{
|
||||
throw new PowerFxJsonException($"Unexpected field '{name}' found in JSONObject", $"{data.Path}/{name}");
|
||||
}
|
||||
|
||||
// if we expect a record type and the field is unknown, let's ignore it like in Power Apps
|
||||
if (!settings.ReturnUnknownRecordFieldsAsUntypedObjects)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerFx.Core.Functions;
|
||||
using Microsoft.PowerFx.Core.IR;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
using Microsoft.PowerFx.Functions;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Core.Texl.Builtins
|
||||
{
|
||||
internal class AsTypeFunction_UOImpl : AsTypeFunction_UO, IAsyncTexlFunction4
|
||||
{
|
||||
public async Task<FormulaValue> InvokeAsync(TimeZoneInfo timezoneInfo, FormulaType ft, FormulaValue[] args, CancellationToken cancellationToken)
|
||||
{
|
||||
Contracts.Assert(args.Length == 2);
|
||||
|
||||
var irContext = IRContext.NotInSource(ft);
|
||||
var typeString = (StringValue)args[1];
|
||||
|
||||
try
|
||||
{
|
||||
return JSONFunctionUtils.ConvertUntypedObjectToFormulaValue(irContext, args[0], typeString, timezoneInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new ErrorValue(irContext, new ExpressionError()
|
||||
{
|
||||
Message = $"{e.GetType().Name} {e.Message}",
|
||||
Span = irContext.SourceContext,
|
||||
Kind = ErrorKind.InvalidArgument
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerFx.Core.Functions;
|
||||
using Microsoft.PowerFx.Core.IR;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
using Microsoft.PowerFx.Functions;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Core.Texl.Builtins
|
||||
{
|
||||
internal class IsTypeFunction_UOImpl : IsTypeFunction_UO, IAsyncTexlFunction4
|
||||
{
|
||||
public async Task<FormulaValue> InvokeAsync(TimeZoneInfo timezoneInfo, FormulaType ft, FormulaValue[] args, CancellationToken cancellationToken)
|
||||
{
|
||||
Contracts.Assert(args.Length == 2);
|
||||
|
||||
var irContext = IRContext.NotInSource(FormulaType.UntypedObject);
|
||||
var typeString = (StringValue)args[1];
|
||||
|
||||
try
|
||||
{
|
||||
var fv = JSONFunctionUtils.ConvertUntypedObjectToFormulaValue(irContext, args[0], typeString, timezoneInfo);
|
||||
if (fv is BlankValue || fv is ErrorValue)
|
||||
{
|
||||
return fv;
|
||||
}
|
||||
else
|
||||
{
|
||||
return BooleanValue.New(true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return BooleanValue.New(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.PowerFx.Core.IR;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Functions;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Core.Utils
|
||||
{
|
||||
internal class JSONFunctionUtils
|
||||
{
|
||||
public static FormulaValue ConvertUntypedObjectToFormulaValue(IRContext irContext, FormulaValue input, StringValue typeString, TimeZoneInfo timeZoneInfo)
|
||||
{
|
||||
if (input is BlankValue || input is ErrorValue)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
if (!DType.TryParse(typeString.Value, out DType dtype))
|
||||
{
|
||||
// This should never happen, as the typeString will be created by the IR
|
||||
return new ErrorValue(irContext, new ExpressionError()
|
||||
{
|
||||
Message = $"Internal error: Unable to parse type argument",
|
||||
Span = irContext.SourceContext,
|
||||
Kind = ErrorKind.Internal
|
||||
});
|
||||
}
|
||||
|
||||
var untypedObjectValue = (UntypedObjectValue)input;
|
||||
var uo = untypedObjectValue.Impl;
|
||||
var jsElement = ((JsonUntypedObject)uo)._element;
|
||||
|
||||
var settings = new FormulaValueJsonSerializerSettings { AllowUnknownRecordFields = false, ResultTimeZone = timeZoneInfo };
|
||||
|
||||
return FormulaValueJSON.FromJson(jsElement, settings, FormulaType.Build(dtype));
|
||||
}
|
||||
|
||||
public static FormulaValue ConvertJSONStringToFormulaValue(IRContext irContext, FormulaValue input, StringValue typeString, TimeZoneInfo timeZoneInfo)
|
||||
{
|
||||
if (input is BlankValue || input is ErrorValue)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
if (input is not StringValue)
|
||||
{
|
||||
return new ErrorValue(irContext, new ExpressionError()
|
||||
{
|
||||
Message = "Runtime type mismatch",
|
||||
Span = irContext.SourceContext,
|
||||
Kind = ErrorKind.InvalidArgument
|
||||
});
|
||||
}
|
||||
|
||||
if (!DType.TryParse(typeString.Value, out DType dtype))
|
||||
{
|
||||
// This should never happen, as the typeString will be created by the IR
|
||||
return new ErrorValue(irContext, new ExpressionError()
|
||||
{
|
||||
Message = $"Internal error: Unable to parse type argument",
|
||||
Span = irContext.SourceContext,
|
||||
Kind = ErrorKind.Internal
|
||||
});
|
||||
}
|
||||
|
||||
var json = ((StringValue)input).Value;
|
||||
var settings = new FormulaValueJsonSerializerSettings { AllowUnknownRecordFields = false, ResultTimeZone = timeZoneInfo };
|
||||
|
||||
return FormulaValueJSON.FromJson(json, settings, FormulaType.Build(dtype));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -61,7 +61,7 @@ namespace Microsoft.PowerFx.Core.Texl.Builtins
|
|||
{
|
||||
Message = $"The Json could not be parsed: {ex.Message}",
|
||||
Span = irContext.SourceContext,
|
||||
Kind = ErrorKind.InvalidArgument
|
||||
Kind = ErrorKind.InvalidJSON
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerFx.Core.Functions;
|
||||
using Microsoft.PowerFx.Core.IR;
|
||||
using Microsoft.PowerFx.Core.Types;
|
||||
using Microsoft.PowerFx.Core.Utils;
|
||||
using Microsoft.PowerFx.Functions;
|
||||
using Microsoft.PowerFx.Types;
|
||||
|
||||
namespace Microsoft.PowerFx.Core.Texl.Builtins
|
||||
{
|
||||
internal class TypedParseJSONFunctionImpl : TypedParseJSONFunction, IAsyncTexlFunction4
|
||||
{
|
||||
public async Task<FormulaValue> InvokeAsync(TimeZoneInfo timezoneInfo, FormulaType ft, FormulaValue[] args, CancellationToken cancellationToken)
|
||||
{
|
||||
Contracts.Assert(args.Length == 2);
|
||||
|
||||
var irContext = IRContext.NotInSource(ft);
|
||||
var typeString = (StringValue)args[1];
|
||||
|
||||
return JSONFunctionUtils.ConvertJSONStringToFormulaValue(irContext, args[0], typeString, timezoneInfo);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1807,13 +1807,31 @@
|
|||
<value>typeTable</value>
|
||||
<comment>function_parameter - Second argument of the IsType function - The Entity table representing the type that we wish to compare the value to. For example, if the author has a CDS data source named Account, that table could be passed here (eg IsType(myVal, Account)).</comment>
|
||||
</data>
|
||||
<data name="AboutIsTypeUO" xml:space="preserve">
|
||||
<value>Returns true if the provided untyped value is of the given type.</value>
|
||||
<comment>Description of the 'IsType' function.</comment>
|
||||
</data>
|
||||
<data name="IsTypeUOArg1" xml:space="preserve">
|
||||
<value>untypedValue</value>
|
||||
<comment>function_parameter - First argument of the IsTypeUO function - The untyped value to be inspected.</comment>
|
||||
</data>
|
||||
<data name="IsTypeUOArg2" xml:space="preserve">
|
||||
<value>type</value>
|
||||
<comment>function_parameter - Second argument of the IsTypeUO function - Type value - either a typeliteral or global named type.</comment>
|
||||
</data>
|
||||
<data name="AboutIsType_untypedValue" xml:space="preserve">
|
||||
<value>The untyped value to check if it can be casted as specified type.</value>
|
||||
</data>
|
||||
<data name="AboutIsType_type" xml:space="preserve">
|
||||
<value>The type that we wish to check the untypedValue.</value>
|
||||
</data>
|
||||
<data name="AboutAsType" xml:space="preserve">
|
||||
<value>Uses the provided value as the given type.</value>
|
||||
<comment>Description of the 'AsType' function.</comment>
|
||||
</data>
|
||||
<data name="AsTypeArg1" xml:space="preserve">
|
||||
<value>value</value>
|
||||
<comment>function_parameter - First argument of the IsType function - The polymorphic value to be used as the new type.</comment>
|
||||
<comment>function_parameter - First argument of the AsType function - The polymorphic value to be used as the new type.</comment>
|
||||
</data>
|
||||
<data name="AsTypeArg2" xml:space="preserve">
|
||||
<value>typeTable</value>
|
||||
|
@ -1825,6 +1843,24 @@
|
|||
<data name="AboutAsType_typeTable" xml:space="preserve">
|
||||
<value>The entity table representing the type we wish the value to be used as.</value>
|
||||
</data>
|
||||
<data name="AboutAsTypeUO" xml:space="preserve">
|
||||
<value>Checks and uses the provided Untyped value as the given type.</value>
|
||||
<comment>Description of the 'AsType' function.</comment>
|
||||
</data>
|
||||
<data name="AsTypeUOArg1" xml:space="preserve">
|
||||
<value>untypedValue</value>
|
||||
<comment>function_parameter - First argument of the AsType function - untyped value to be used as the new type.</comment>
|
||||
</data>
|
||||
<data name="AsTypeUOArg2" xml:space="preserve">
|
||||
<value>type</value>
|
||||
<comment>function_parameter - Second argument of the AsType function - The type that we wish the value to be used as.</comment>
|
||||
</data>
|
||||
<data name="AboutAsType_untypedValue" xml:space="preserve">
|
||||
<value>The untyped value to cast as a new type.</value>
|
||||
</data>
|
||||
<data name="AboutAsType_type" xml:space="preserve">
|
||||
<value>The type that we wish the value to be used as.</value>
|
||||
</data>
|
||||
<data name="AboutWith" xml:space="preserve">
|
||||
<value>Executes the formula provided as second parameter using the scope provided by the first.</value>
|
||||
<comment>Description of the 'With' function.</comment>
|
||||
|
@ -2673,6 +2709,24 @@
|
|||
<data name="AboutParseJSON_input" xml:space="preserve">
|
||||
<value>A JSON string to process.</value>
|
||||
</data>
|
||||
<data name="AboutTypedParseJSON" xml:space="preserve">
|
||||
<value>Converts a JSON string into a typed object.</value>
|
||||
<comment>Description of 'ParseJSON' function overload that converts a string input to typed object.</comment>
|
||||
</data>
|
||||
<data name="TypedParseJSONArg1" xml:space="preserve">
|
||||
<value>string</value>
|
||||
<comment>function_parameter - First argument of the ParseJSON function - String type.</comment>
|
||||
</data>
|
||||
<data name="AboutParseJSON_string" xml:space="preserve">
|
||||
<value>A JSON string to convert into typed object.</value>
|
||||
</data>
|
||||
<data name="TypedParseJSONArg2" xml:space="preserve">
|
||||
<value>type</value>
|
||||
<comment>function_parameter - Second argument of the typed ParseJSON function overload - Inlined type definition or NamedType.</comment>
|
||||
</data>
|
||||
<data name="AboutParseJSON_type" xml:space="preserve">
|
||||
<value>An inlined type definiton or a globally defined named type.</value>
|
||||
</data>
|
||||
<data name="AboutIndex" xml:space="preserve">
|
||||
<value>Returns the record in a table at a given index.</value>
|
||||
<comment>Description of 'Index' function.</comment>
|
||||
|
@ -4694,4 +4748,12 @@
|
|||
<value>The specified data source cannot be used with this function.</value>
|
||||
<comment>Error Message.</comment>
|
||||
</data>
|
||||
<data name="ErrInvalidArgumentExpectedType" xml:space="preserve">
|
||||
<value>Invalid argument '{0}'. Expected valid type name or type literal.</value>
|
||||
<comment>Error Message shown to user when a value other name or type literal is passed into AsType, IsType and ParseJSON functions.</comment>
|
||||
</data>
|
||||
<data name="ErrUnsupportedTypeInTypeArgument" xml:space="preserve">
|
||||
<value>Unsupported untyped/JSON conversion type '{0}' in argument.</value>
|
||||
<comment>Error Message shown to user when a unsupported type is passed in type argument of AsType, IsType and ParseJSON functions.</comment>
|
||||
</data>
|
||||
</root>
|
|
@ -610,7 +610,7 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
FormulaValue httpResult = await function.InvokeAsync(new FormulaValue[] { kind, analysisInputParam, parametersParam }, context, CancellationToken.None);
|
||||
|
||||
ErrorValue ev = Assert.IsType<ErrorValue>(httpResult);
|
||||
Assert.Equal(ErrorKind.InvalidJSON, ev.Errors.First().Kind);
|
||||
Assert.Equal(ErrorKind.InvalidArgument, ev.Errors.First().Kind);
|
||||
Assert.Equal("ACSL.ConversationAnalysisAnalyzeConversationConversation failed: PowerFxJsonException Expecting Table but received a Number, in result/prediction/intents", ev.Errors.First().Message);
|
||||
}
|
||||
|
||||
|
@ -675,7 +675,7 @@ namespace Microsoft.PowerFx.Connectors.Tests
|
|||
FormulaValue httpResult = await function.InvokeAsync(new FormulaValue[] { kind, analysisInputParam, parametersParam }, context, CancellationToken.None);
|
||||
|
||||
ErrorValue ev = Assert.IsType<ErrorValue>(httpResult);
|
||||
Assert.Equal(ErrorKind.InvalidJSON, ev.Errors.First().Kind);
|
||||
Assert.Equal(ErrorKind.InvalidArgument, ev.Errors.First().Kind);
|
||||
Assert.Equal("ACSL.ConversationAnalysisAnalyzeConversationConversation failed: PowerFxJsonException Expecting String but received a Table with 2 elements, in result/prediction/entities/category", ev.Errors.First().Message);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
#SETUP: AllowTypeLiteral, TimeZoneInfo("Pacific Standard Time")
|
||||
|
||||
// Primitives
|
||||
>> AsType(ParseJSON("987654321"), Number)
|
||||
987654321
|
||||
|
||||
>> AsType(ParseJSON("98765.4321"), Decimal)
|
||||
98765.4321
|
||||
|
||||
>> AsType(ParseJSON("12345678901234.567890123456"), Decimal)
|
||||
12345678901234.567890123456
|
||||
|
||||
>> AsType(ParseJSON("-1.3"), Decimal)
|
||||
-1.3
|
||||
|
||||
>> AsType(ParseJSON("2e3"), Decimal)
|
||||
2000
|
||||
|
||||
>> AsType(ParseJSON("98765.4321"), Number) > AsType(ParseJSON("98765.4320"), Number)
|
||||
true
|
||||
|
||||
>> AsType(ParseJSON("""AstypeFunction"""), Type(Text))
|
||||
"AstypeFunction"
|
||||
|
||||
>> AsType(ParseJSON("""1984-01-01"""), Date)
|
||||
Date(1984,1,1)
|
||||
|
||||
>> AsType(ParseJSON("""1900-12-31T23:59:59.999Z"""), DateTime)
|
||||
DateTime(1900,12,31,15,59,59,999)
|
||||
|
||||
>> AsType(ParseJSON("""1900-12-31T23:59:59.999"""), DateTime)
|
||||
DateTime(1900,12,31,23,59,59,999)
|
||||
|
||||
>> AsType(ParseJSON("""1900-12-31T23:59:59.999+00:00"""), DateTime)
|
||||
DateTime(1900,12,31,15,59,59,999)
|
||||
|
||||
>> AsType(ParseJSON("""1900-12-31T23:59:59.999-08:00"""), DateTime)
|
||||
DateTime(1900,12,31,23,59,59,999)
|
||||
|
||||
>> AsType(ParseJSON("""1900-12-31"""), DateTime)
|
||||
DateTime(1900,12,31,0,0,0,0)
|
||||
|
||||
>> AsType(ParseJSON("""1900-12-31T23:59:59.999"""), Date)
|
||||
Date(1900,12,31)
|
||||
|
||||
>> AsType(ParseJSON("""1900-12-31T00:00:00.000Z"""), Date)
|
||||
Date(1900,12,31)
|
||||
|
||||
>> AsType(ParseJSON("""11:59:59.999"""), Time)
|
||||
Time(11,59,59,999)
|
||||
|
||||
>> AsType(ParseJSON("""00:00:00"""), Time)
|
||||
Time(0,0,0,0)
|
||||
|
||||
>> AsType(ParseJSON("""12:34:56.789"""), Time) = TimeValue(ParseJSON("""12:34:56.789"""))
|
||||
true
|
||||
|
||||
>> AsType(ParseJSON("""12:34:56.789"""), Time) = TimeValue(ParseJSON("""12:34:56.7891"""))
|
||||
false
|
||||
|
||||
>> AsType(ParseJSON("""1900-12-31T00:00:00.000Z"""), DateTimeTZInd)
|
||||
DateTime(1900,12,31,0,0,0,0)
|
||||
|
||||
>> AsType(ParseJSON("""1900-12-31T00:00:00.000-08:00"""), DateTimeTZInd)
|
||||
DateTime(1900,12,31,8,0,0,0)
|
||||
|
||||
>> DateTimeValue(AsType(ParseJSON("""1900-12-31T00:00:00.000Z"""), UntypedObject))
|
||||
DateTime(1900,12,30,16,0,0,0)
|
||||
|
||||
>> DateValue(AsType(ParseJSON("""1900-12-31T00:00:00.000Z"""), UntypedObject))
|
||||
Date(1900,12,30)
|
||||
|
||||
>> Value(AsType(ParseJSON("42"), UntypedObject))
|
||||
42
|
||||
|
||||
>> Value(AsType(ParseJSON("true"), UntypedObject))
|
||||
1
|
||||
|
||||
>> If(AsType(ParseJSON("false"), Boolean), "MyFalse", "MyTrue")
|
||||
"MyTrue"
|
||||
|
||||
>> AsType(ParseJSON("42"), Number) = 42
|
||||
true
|
||||
|
||||
>> UniChar(AsType(ParseJSON("66"), Number))
|
||||
"B"
|
||||
|
||||
// record
|
||||
>> AsType(ParseJSON("{""foo"": true, ""bar"": 1.1}"), Type({foo: Boolean, bar: Number}))
|
||||
{bar:1.1,foo:true}
|
||||
|
||||
// record missing field
|
||||
>> AsType(ParseJSON("{""Name"": ""SpongeBob"", ""Age"": 1}"), Type({Name: Text, Age: Number, Aquatic: Boolean})).Name
|
||||
"SpongeBob"
|
||||
|
||||
// Deeply nested record with table
|
||||
>> AsType(ParseJSON("{""a"": {""b"" : { ""c"" : [1, 2, 3, 4]}}}"), Type({a: {b: {c: [Number]}}}))
|
||||
{a:{b:{c:Table({Value:1},{Value:2},{Value:3},{Value:4})}}}
|
||||
|
||||
// Table
|
||||
>> AsType(ParseJSON("[{""a"": ""Hello"", ""b"": ""2012-01-02""}, {""a"": ""Hi"", ""b"": ""2012-01-03""}]"), Type([{a: Text, b: Date}]))
|
||||
Table({a:"Hello",b:Date(2012,1,2)},{a:"Hi",b:Date(2012,1,3)})
|
||||
|
||||
>> AsType(ParseJSON("[{""a"": [{""z"": true}, {""z"": false}]}, {""a"": [{""z"": false}, {""z"": true}]}]"), Type([{a: [{z: Boolean}]}]))
|
||||
Table({a:Table({z:true},{z:false})},{a:Table({z:false},{z:true})})
|
||||
|
||||
// Negative tests
|
||||
>> AsType(ParseJSON("5"), Text)
|
||||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
|
||||
>> AsType(ParseJSON("""42"""), Number)
|
||||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
|
||||
>> AsType(ParseJSON("""1900-12-31T24:59:59.1002Z"""), DateTime)
|
||||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
|
||||
>> AsType(ParseJSON("""24:59:59.12345678"""), Time)
|
||||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
|
||||
>> AsType(ParseJSON("1"), 1)
|
||||
Errors: Error 23-24: Invalid argument '1'. Expected valid type name or type literal.
|
||||
|
||||
>> AsType(ParseJSON("5"), Type(5))
|
||||
Errors: Error 28-29: Type literal declaration is invalid. The expression '5' cannot be used in a type definition.|Error 27-28: Type literal declaration is invalid. The expression 'Type(5)' cannot be used in a type definition.
|
||||
|
||||
>> AsType(ParseJSON("true"), UnKnown)
|
||||
Errors: Error 26-33: Name isn't valid. 'UnKnown' isn't recognized.|Error 0-34: Invalid argument 'UnKnown'. Expected valid type name or type literal.
|
||||
|
||||
>> AsType(ParseJSON("fasle"), Boolean)
|
||||
Error({Kind:ErrorKind.InvalidJSON})
|
||||
|
||||
>> AsType(ParseJSON("true"), Void)
|
||||
Errors: Error 26-30: Unsupported untyped/JSON conversion type 'Void' in argument.
|
||||
|
||||
>> AsType(ParseJSON("{""a"": 5, ""b"":true}"), Type({a: Number}))
|
||||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
|
||||
>> AsType(ParseJSON("{""a"": 5}"), Type({a: Number}), "Hello")
|
||||
Errors: Error 0-59: Invalid number of arguments: received 3, expected 2.
|
||||
|
||||
>> AsType(ParseJSON("true"), None)
|
||||
Errors: Error 26-30: Unsupported untyped/JSON conversion type 'ObjNull' in argument.
|
||||
|
||||
>> AsType(ParseJSON("null"), None)
|
||||
Errors: Error 26-30: Unsupported untyped/JSON conversion type 'ObjNull' in argument.
|
||||
|
||||
>> AsType(ParseJSON("{}"), Type({a: Text, b: [Color]}))
|
||||
Errors: Error 28-29: Unsupported untyped/JSON conversion type 'Color' in argument.
|
||||
|
||||
>> AsType(If(1/0 > 1, ParseJSON("42")), Number)
|
||||
Error({Kind:ErrorKind.Div0})
|
||||
|
||||
>> AsType(ParseJSON(Blank()), Number)
|
||||
Blank()
|
||||
|
||||
>> AsType(ParseJSON("42"), Blank())
|
||||
Errors: Error 24-31: Invalid argument 'Blank()'. Expected valid type name or type literal.
|
||||
|
||||
>> AsType(ParseJSON("42"), 1/0)
|
||||
Errors: Error 25-26: Invalid argument '1 / 0'. Expected valid type name or type literal.
|
|
@ -258,10 +258,10 @@ Errors: Error 0-4: The function 'Text' has some invalid arguments.|Warning 45-85
|
|||
// badly formed numbers
|
||||
|
||||
>> Text( ParseJSON("123456789d12") )
|
||||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
Error({Kind:ErrorKind.InvalidJSON})
|
||||
|
||||
>> Text( ParseJSON("--123456789") )
|
||||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
Error({Kind:ErrorKind.InvalidJSON})
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -283,7 +283,7 @@ Errors: Error 0-4: The function 'Text' has some invalid arguments.|Warning 45-85
|
|||
// badly formed numbers
|
||||
|
||||
>> Text( ParseJSON("123456789d12") )
|
||||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
Error({Kind:ErrorKind.InvalidJSON})
|
||||
|
||||
>> Text( ParseJSON("--123456789") )
|
||||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
Error({Kind:ErrorKind.InvalidJSON})
|
||||
|
|
|
@ -413,7 +413,7 @@ Error({Kind:ErrorKind.Div0})
|
|||
Error({Kind:ErrorKind.Numeric})
|
||||
|
||||
>> ParseJSON("not a JSON string")
|
||||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
Error({Kind:ErrorKind.InvalidJSON})
|
||||
|
||||
>> ParseJSON(If(1/0<2,"not a JSON string"))
|
||||
Error({Kind:ErrorKind.Div0})
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
#SETUP: AllowTypeLiteral, TimeZoneInfo("Pacific Standard Time")
|
||||
|
||||
// Primitives
|
||||
>> IsType(ParseJSON("987654321"), Number)
|
||||
true
|
||||
|
||||
>> AsType(ParseJSON("12345678901234.567890123456"), Decimal)
|
||||
12345678901234.567890123456
|
||||
|
||||
>> IsType(ParseJSON("98765.4321"), Decimal)
|
||||
true
|
||||
|
||||
>> IsType(ParseJSON("-1.3"), Decimal)
|
||||
true
|
||||
|
||||
>> IsType(ParseJSON("2e3"), Decimal)
|
||||
true
|
||||
|
||||
>> IsType(ParseJSON("987654321"), Text)
|
||||
false
|
||||
|
||||
>> IsType(ParseJSON("987654321"), Date)
|
||||
false
|
||||
|
||||
>> If(IsType(ParseJSON("""1900-12-31T23:59:59.999Z"""), DateTime), "ValidDate")
|
||||
"ValidDate"
|
||||
|
||||
>> If(IsType(ParseJSON("""1900-12-31T23:59:59.999Z"""), Text), "ValidText")
|
||||
"ValidText"
|
||||
|
||||
>> IsType(ParseJSON("""IstypeFunction"""), Type(Text))
|
||||
true
|
||||
|
||||
>> IsType(ParseJSON("""1984-01-01"""), Date)
|
||||
true
|
||||
|
||||
>> IsType(ParseJSON("""1900-12-31"""), DateTime)
|
||||
true
|
||||
|
||||
>> IsType(ParseJSON("""1900-12-31T23:59:59.999"""), Date)
|
||||
true
|
||||
|
||||
>> IsType(ParseJSON("""11:59:59.999"""), Time)
|
||||
true
|
||||
|
||||
>> IsType(ParseJSON("""00:00:00"""), Time)
|
||||
true
|
||||
|
||||
>> IsType(ParseJSON("""1900-12-31T00:00:00.000Z"""), DateTimeTZInd)
|
||||
true
|
||||
|
||||
>> IsType(ParseJSON("""true"""), Boolean)
|
||||
false
|
||||
|
||||
>> IsType(ParseJSON("true"), Boolean)
|
||||
true
|
||||
|
||||
// record
|
||||
>> IsType(ParseJSON("{""foo"": true, ""bar"": 1.1}"), Type({foo: Boolean, bar: Number}))
|
||||
true
|
||||
|
||||
// record missing field
|
||||
>> IsType(ParseJSON("{""Name"": ""SpongeBob"", ""Age"": 1}"), Type({Name: Text, Age: Number, Aquatic: Boolean}))
|
||||
true
|
||||
|
||||
// record with additional field
|
||||
>> IsType(ParseJSON("{""a"": 5, ""b"":true}"), Type({a: Number}))
|
||||
false
|
||||
|
||||
// record with incorrect field type
|
||||
>> IsType(ParseJSON("{""a"": 5}"), Type({a: Text}))
|
||||
false
|
||||
|
||||
// Deeply nested record with table
|
||||
>> IsType(ParseJSON("{""a"": {""b"" : { ""c"" : [1, 2, 3, 4]}}}"), Type({a: {b: {c: [Number]}}}))
|
||||
true
|
||||
|
||||
// Table
|
||||
>> IsType(ParseJSON("[{""a"": ""Hello"", ""b"": ""2012-01-02""}, {""a"": ""Hi"", ""b"": ""2012-01-03""}]"), Type([{a: Text, b: Date}]))
|
||||
true
|
||||
|
||||
>> IsType(ParseJSON("[{""a"": ""Hello"", ""b"": ""2012-01-02""}]"), Type([{a: Text, b: Text}]))
|
||||
true
|
||||
|
||||
>> IsType(ParseJSON("[{""a"": ""Hello"", ""b"": ""2012-01-02""}]"), Type([{a: Text, b: Number}]))
|
||||
false
|
||||
|
||||
>> IsType(ParseJSON("[{""a"": [{""z"": true}, {""z"": false}]}, {""a"": [{""z"": false}, {""z"": true}]}]"), Type([{a: [{z: Boolean}]}]))
|
||||
true
|
||||
|
||||
>> IsType(ParseJSON("""1900-12-31T24:59:59.1002Z"""), DateTime)
|
||||
false
|
||||
|
||||
>> IsType(ParseJSON("""24:59:59.12345678"""), Time)
|
||||
false
|
||||
|
||||
>> IsType(ParseJSON("1"), 1)
|
||||
Errors: Error 23-24: Invalid argument '1'. Expected valid type name or type literal.
|
||||
|
||||
>> IsType(ParseJSON("5"), Type(5))
|
||||
Errors: Error 28-29: Type literal declaration is invalid. The expression '5' cannot be used in a type definition.|Error 27-28: Type literal declaration is invalid. The expression 'Type(5)' cannot be used in a type definition.
|
||||
|
||||
>> IsType(ParseJSON("true"), UnKnown)
|
||||
Errors: Error 26-33: Name isn't valid. 'UnKnown' isn't recognized.|Error 0-34: Invalid argument 'UnKnown'. Expected valid type name or type literal.
|
||||
|
||||
>> IsType(ParseJSON("fasle"), Boolean)
|
||||
Error({Kind:ErrorKind.InvalidJSON})
|
||||
|
||||
>> IsType(ParseJSON("true"), Void)
|
||||
Errors: Error 26-30: Unsupported untyped/JSON conversion type 'Void' in argument.
|
||||
|
||||
>> IsType(ParseJSON("{""a"": 5}"), Type({a: Number}), "Hello")
|
||||
Errors: Error 0-59: Invalid number of arguments: received 3, expected 2.
|
||||
|
||||
>> IsType(ParseJSON("true"), None)
|
||||
Errors: Error 26-30: Unsupported untyped/JSON conversion type 'ObjNull' in argument.
|
||||
|
||||
>> IsType(ParseJSON("{}"), Type({a: Text, b: [Color]}))
|
||||
Errors: Error 28-29: Unsupported untyped/JSON conversion type 'Color' in argument.
|
||||
|
||||
>> IsType(If(1/0 > 1, ParseJSON("42")), Number)
|
||||
Error({Kind:ErrorKind.Div0})
|
||||
|
||||
>> IsType(ParseJSON(Blank()), Number)
|
||||
Blank()
|
||||
|
||||
>> IsType(ParseJSON("42"), Blank())
|
||||
Errors: Error 24-31: Invalid argument 'Blank()'. Expected valid type name or type literal.
|
||||
|
||||
>> IsType(ParseJSON("42"), 1/0)
|
||||
Errors: Error 25-26: Invalid argument '1 / 0'. Expected valid type name or type literal.
|
|
@ -54,7 +54,7 @@ Blank()
|
|||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
|
||||
>> Value(ParseJSON("This "" Is , "" Invalid ").a)
|
||||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
Error({Kind:ErrorKind.InvalidJSON})
|
||||
|
||||
// Text tests
|
||||
>> Text(Index(ParseJSON("[""s""]"), 1))
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
#SETUP: AllowTypeLiteral, TimeZoneInfo("Pacific Standard Time")
|
||||
|
||||
// Primitives
|
||||
>> ParseJSON("5", Number)
|
||||
5
|
||||
|
||||
>> ParseJSON("98765.4321", Number)
|
||||
98765.4321
|
||||
|
||||
>> ParseJSON("98765.4321", Decimal)
|
||||
98765.4321
|
||||
|
||||
>> ParseJSON("12345678901234.567890123456", Decimal)
|
||||
12345678901234.567890123456
|
||||
|
||||
>> ParseJSON("-1.3", Decimal)
|
||||
-1.3
|
||||
|
||||
>> ParseJSON("2e3", Decimal)
|
||||
2000
|
||||
|
||||
>> ParseJSON("""HelloWorld""", Text)
|
||||
"HelloWorld"
|
||||
|
||||
>> ParseJSON("""HelloWorld""", Type(Text))
|
||||
"HelloWorld"
|
||||
|
||||
>> ParseJSON("""1984-01-01""", Date)
|
||||
Date(1984,1,1)
|
||||
|
||||
>> ParseJSON("""2008-01-01T12:12:12.100Z""", DateTime)
|
||||
DateTime(2008,1,1,4,12,12,100)
|
||||
|
||||
>> ParseJSON("""2008-01-01T12:12:12.100""", DateTime)
|
||||
DateTime(2008,1,1,12,12,12,100)
|
||||
|
||||
>> ParseJSON("""2008-01-01T12:12:12.100-08:00""", DateTime)
|
||||
DateTime(2008,1,1,12,12,12,100)
|
||||
|
||||
>> ParseJSON("""1900-12-31""", DateTime)
|
||||
DateTime(1900,12,31,0,0,0,0)
|
||||
|
||||
>> ParseJSON("""1900-12-31T23:59:59.999""", Date)
|
||||
Date(1900,12,31)
|
||||
|
||||
>> ParseJSON("""11:59:59.999""", Time)
|
||||
Time(11,59,59,999)
|
||||
|
||||
>> ParseJSON("""00:00:00""", Time)
|
||||
Time(0,0,0,0)
|
||||
|
||||
>> ParseJSON("""12:34:56.789""", Time) = TimeValue(ParseJSON("""12:34:56.789"""))
|
||||
true
|
||||
|
||||
>> ParseJSON("""12:34:56.789""", Time) = TimeValue(ParseJSON("""12:34:56.7891"""))
|
||||
false
|
||||
|
||||
>> ParseJSON("""1900-12-31T00:00:00.000Z""", DateTimeTZInd)
|
||||
DateTime(1900,12,31,0,0,0,0)
|
||||
|
||||
>> ParseJSON("""1900-12-31T00:00:00.000-08:00""", DateTimeTZInd)
|
||||
DateTime(1900,12,31,8,0,0,0)
|
||||
|
||||
>> DateTimeValue(ParseJSON("""1900-12-31T00:00:00.000Z""", UntypedObject))
|
||||
DateTime(1900,12,30,16,0,0,0)
|
||||
|
||||
>> DateValue(ParseJSON("""1900-12-31T00:00:00.000Z""", UntypedObject))
|
||||
Date(1900,12,30)
|
||||
|
||||
>> Value(ParseJSON("42", UntypedObject))
|
||||
42
|
||||
|
||||
>> Value(ParseJSON("true", UntypedObject))
|
||||
1
|
||||
|
||||
>> ParseJSON("true", Boolean)
|
||||
true
|
||||
|
||||
>> If(ParseJSON("false", Boolean), "No", "Yes")
|
||||
"Yes"
|
||||
|
||||
>> ParseJSON("555", Number) = 555
|
||||
true
|
||||
|
||||
>> 2 < ParseJSON("1", Number)
|
||||
false
|
||||
|
||||
>> UniChar(ParseJSON("65", Number))
|
||||
"A"
|
||||
|
||||
// record
|
||||
>> ParseJSON("{""foo"": true}", Type({foo: Boolean}))
|
||||
{foo:true}
|
||||
|
||||
// record missing field
|
||||
>> ParseJSON("{""Name"": ""SpongeBob"", ""Age"": 1}", Type({Name: Text, Age: Number, Aquatic: Boolean})).Name
|
||||
"SpongeBob"
|
||||
|
||||
// Deeply nested record with table
|
||||
>> ParseJSON("{""a"": {""b"" : { ""c"" : [1, 2, 3, 4]}}}", Type({a: {b: {c: [Number]}}}))
|
||||
{a:{b:{c:Table({Value:1},{Value:2},{Value:3},{Value:4})}}}
|
||||
|
||||
// Table
|
||||
>> ParseJSON("[{""a"": ""Hello"", ""b"": ""2012-01-02""}, {""a"": ""Hi"", ""b"": ""2012-01-03""}]", Type([{a: Text, b: Date}]))
|
||||
Table({a:"Hello",b:Date(2012,1,2)},{a:"Hi",b:Date(2012,1,3)})
|
||||
|
||||
>> ParseJSON("[{""a"": [{""z"": true}, {""z"": false}]}, {""a"": [{""z"": false}, {""z"": true}]}]", Type([{a: [{z: Boolean}]}]))
|
||||
Table({a:Table({z:true},{z:false})},{a:Table({z:false},{z:true})})
|
||||
|
||||
// Negative tests
|
||||
>> ParseJSON("5", Text)
|
||||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
|
||||
>> ParseJSON("""42""", Number)
|
||||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
|
||||
>> ParseJSON("""24:59:59.12345678""", Time)
|
||||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
|
||||
>> ParseJSON("1", 1)
|
||||
Errors: Error 15-16: Invalid argument '1'. Expected valid type name or type literal.
|
||||
|
||||
>> ParseJSON("""RED""", Color)
|
||||
Errors: Error 21-26: Unsupported untyped/JSON conversion type 'Color' in argument.
|
||||
|
||||
>> ParseJSON("5", Type(5))
|
||||
Errors: Error 20-21: Type literal declaration is invalid. The expression '5' cannot be used in a type definition.|Error 19-20: Type literal declaration is invalid. The expression 'Type(5)' cannot be used in a type definition.
|
||||
|
||||
>> ParseJSON("true", UnKnown)
|
||||
Errors: Error 18-25: Name isn't valid. 'UnKnown' isn't recognized.|Error 0-26: Invalid argument 'UnKnown'. Expected valid type name or type literal.
|
||||
|
||||
>> ParseJSON("fasle", Boolean)
|
||||
Error({Kind:ErrorKind.InvalidJSON})
|
||||
|
||||
>> ParseJSON("true", Void)
|
||||
Errors: Error 18-22: Unsupported untyped/JSON conversion type 'Void' in argument.
|
||||
|
||||
>> ParseJSON("{""a"": 5, ""b"":true}", Type({a: Number}))
|
||||
Error({Kind:ErrorKind.InvalidArgument})
|
||||
|
||||
>> ParseJSON("{""a"": 5}", Type({a: Number}), "Hello")
|
||||
Errors: Error 0-51: Invalid number of arguments: received 3, expected 2.
|
||||
|
||||
>> ParseJSON("true", None)
|
||||
Errors: Error 18-22: Unsupported untyped/JSON conversion type 'ObjNull' in argument.
|
||||
|
||||
>> ParseJSON("null", None)
|
||||
Errors: Error 18-22: Unsupported untyped/JSON conversion type 'ObjNull' in argument.
|
||||
|
||||
>> ParseJSON("{}", Type({a: Text, b: [Color]}))
|
||||
Errors: Error 20-21: Unsupported untyped/JSON conversion type 'Color' in argument.
|
||||
|
||||
>> ParseJSON(If(1/0 > 1, "42"), Number)
|
||||
Error({Kind:ErrorKind.Div0})
|
||||
|
||||
>> ParseJSON(Blank(), Number)
|
||||
Blank()
|
||||
|
||||
>> ParseJSON("42", Blank())
|
||||
Errors: Error 16-23: Invalid argument 'Blank()'. Expected valid type name or type literal.
|
||||
|
||||
>> ParseJSON("42", 1/0)
|
||||
Errors: Error 17-18: Invalid argument '1 / 0'. Expected valid type name or type literal.
|
|
@ -72,6 +72,7 @@
|
|||
"IsMatch",
|
||||
"IsNumeric",
|
||||
"IsToday",
|
||||
"IsType",
|
||||
"JSON",
|
||||
"Language",
|
||||
"Last",
|
||||
|
|
|
@ -32,6 +32,11 @@ namespace Microsoft.PowerFx.Interpreter.Tests.Helpers
|
|||
parserOptions.TextFirst = true;
|
||||
}
|
||||
|
||||
if (flags.HasFlag(TexlParser.Flags.AllowTypeLiteral))
|
||||
{
|
||||
parserOptions.AllowParseAsTypeLiteral = true;
|
||||
}
|
||||
|
||||
return parserOptions;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,203 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.PowerFx.Core.Functions;
|
||||
using Microsoft.PowerFx.Core.IR;
|
||||
using Microsoft.PowerFx.Core.Localization;
|
||||
using Microsoft.PowerFx.Core.Tests;
|
||||
using Microsoft.PowerFx.Core.Texl;
|
||||
using Microsoft.PowerFx.Core.Texl.Builtins;
|
||||
using Microsoft.PowerFx.Functions;
|
||||
using Microsoft.PowerFx.Types;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.PowerFx.Json.Tests
|
||||
{
|
||||
public class AsTypeIsTypeParseJSONTests
|
||||
{
|
||||
private static readonly ParserOptions ParseType = new ParserOptions
|
||||
{
|
||||
AllowParseAsTypeLiteral = true,
|
||||
};
|
||||
|
||||
private RecalcEngine SetupEngine()
|
||||
{
|
||||
var config = new PowerFxConfig();
|
||||
config.EnableJsonFunctions();
|
||||
return new RecalcEngine(config);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrimitivesTest()
|
||||
{
|
||||
var engine = SetupEngine();
|
||||
|
||||
// custom-type type alias
|
||||
engine.AddUserDefinitions("T = Type(Number);");
|
||||
|
||||
// Positive tests
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"42\"", "Number", 42D);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"17.29\"", "Number", 17.29D);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"\"\"HelloWorld\"\"\"", "Text", "HelloWorld");
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"\"\"2000-01-01\"\"\"", "Date", new DateTime(2000, 1, 1));
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"\"\"2000-01-01T00:00:01.100\"\"\"", "DateTime", new DateTime(2000, 1, 1, 0, 0, 1, 100));
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"true\"", "Boolean", true);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"false\"", "Boolean", false);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"1234.56789\"", "Decimal", 1234.56789m);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"42\"", "T", 42D);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"\"\"Power Fx\"\"\"", "Type(Text)", "Power Fx", options: ParseType);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"\"\"2000-01-01T00:00:01.100Z\"\"\"", "DateTimeTZInd", new DateTime(2000, 1, 1, 0, 0, 1, 100));
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"\"\"11:59:59\"\"\"", "Time", new TimeSpan(11, 59, 59));
|
||||
|
||||
// Negative tests - Coercions not allowed
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"42\"", "Text", string.Empty, false);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"\"\"42\"\"\"", "Number", string.Empty, false);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"\"\"42\"\"\"", "Decimal", string.Empty, false);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"0\"", "Boolean", false, false);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"true\"", "Number", false, false);
|
||||
|
||||
// Negative tests - types not supported in FromJSON converter
|
||||
CheckIsTypeAsTypeParseJSONCompileErrors(engine, "\"42\"", "None", TexlStrings.ErrUnsupportedTypeInTypeArgument.Key);
|
||||
CheckIsTypeAsTypeParseJSONCompileErrors(engine, "\"\"\"RED\"\"\"", "Color", TexlStrings.ErrUnsupportedTypeInTypeArgument.Key);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"\"\"abcd-efgh-1234-ijkl\"\"\"", "GUID", string.Empty, false);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"\"\"foo/bar/uri\"\"\"", "Hyperlink", string.Empty, false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecordsTest()
|
||||
{
|
||||
var engine = SetupEngine();
|
||||
|
||||
engine.AddUserDefinitions("T = Type({a: Number});");
|
||||
|
||||
dynamic obj1 = new ExpandoObject();
|
||||
obj1.a = 5D;
|
||||
|
||||
dynamic obj2 = new ExpandoObject();
|
||||
obj2.a = new ExpandoObject();
|
||||
obj2.a.b = new ExpandoObject();
|
||||
obj2.a.b.c = false;
|
||||
|
||||
dynamic obj3 = new ExpandoObject();
|
||||
obj3.a = new object[] { 1m, 2m, 3m, 4m };
|
||||
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"{\"\"a\"\": 5}\"", "T", obj1);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"{\"\"a\"\": 5}\"", "Type({a: Number})", obj1, options: ParseType);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"{\"\"a\"\": { \"\"b\"\": {\"\"c\"\": false}}}\"", "Type({a: {b: {c: Boolean }}})", obj2, options: ParseType);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"{\"\"a\"\": [1, 2, 3, 4]}\"", "Type({a: [Decimal]})", obj3, options: ParseType);
|
||||
|
||||
// Negative Tests
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"{\"\"a\"\": 5}\"", "Type({a: Text})", obj1, isValid: false, options: ParseType);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"{\"\"a\"\": 5, \"\"b\"\": 6}\"", "Type({a: Number})", obj1, false, options: ParseType);
|
||||
CheckIsTypeAsTypeParseJSONCompileErrors(engine, "\"{\"\"a\"\": \"\"foo/bar/uri\"\"}\"", "Type({a: Void})", TexlStrings.ErrUnsupportedTypeInTypeArgument.Key, options: ParseType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TablesTest()
|
||||
{
|
||||
var engine = SetupEngine();
|
||||
|
||||
engine.AddUserDefinitions("T = Type([{a: Number}]);");
|
||||
|
||||
var t1 = new object[] { 5D };
|
||||
var t2 = new object[] { 1m, 2m, 3m, 4m };
|
||||
var t3a = new object[] { true, true, false, true };
|
||||
var t3 = new object[] { t3a };
|
||||
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"[{\"\"a\"\": 5}]\"", "T", t1);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"[{\"\"a\"\": 5}]\"", "Type([{a: Number}])", t1, options: ParseType);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"[{\"\"a\"\": [true, true, false, true]}]\"", "Type([{a: [Boolean]}])", t3, options: ParseType);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"[1, 2, 3, 4]\"", "Type([Decimal])", t2, options: ParseType);
|
||||
|
||||
// Negative tests
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"[{\"\"a\"\": 5, \"\"b\"\": 6}]\"", "Type([{a: Number}])", t1, false, options: ParseType);
|
||||
CheckIsTypeAsTypeParseJSON(engine, "\"[1, 2, 3, 4]\"", "Type([Text])", t2, false, options: ParseType);
|
||||
CheckIsTypeAsTypeParseJSONCompileErrors(engine, "\"[\"\"foo/bar/uri\"\"]\"", "Type([Color])", TexlStrings.ErrUnsupportedTypeInTypeArgument.Key, options: ParseType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("\"42\"", "SomeType", true, "ErrInvalidName")]
|
||||
[InlineData("\"42\"", "Type(5)", true, "ErrTypeLiteral_InvalidTypeDefinition")]
|
||||
[InlineData("\"42\"", "Text(42)", true, "ErrInvalidArgumentExpectedType")]
|
||||
[InlineData("\"\"\"Hello\"\"\"", "\"Hello\"", true, "ErrInvalidArgumentExpectedType")]
|
||||
[InlineData("\"{}\"", "Type([{a: 42}])", true, "ErrTypeLiteral_InvalidTypeDefinition")]
|
||||
[InlineData("AsType(ParseJSON(\"42\"))", "", false, "ErrBadArity")]
|
||||
[InlineData("IsType(ParseJSON(\"42\"))", "", false, "ErrBadArity")]
|
||||
[InlineData("AsType(ParseJSON(\"42\"), Number , Text(5))", "", false, "ErrBadArity")]
|
||||
[InlineData("IsType(ParseJSON(\"42\"), Number, 5)", "", false, "ErrBadArity")]
|
||||
[InlineData("AsType(ParseJSON(\"123\"), 1)", "", false, "ErrInvalidArgumentExpectedType")]
|
||||
public void TestCompileErrors(string expression, string type, bool testAllFunctions, string expectedError)
|
||||
{
|
||||
var engine = SetupEngine();
|
||||
|
||||
if (testAllFunctions)
|
||||
{
|
||||
CheckIsTypeAsTypeParseJSONCompileErrors(engine, expression, type, expectedError, ParseType);
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = engine.Check(expression, options: ParseType);
|
||||
Assert.False(result.IsSuccess);
|
||||
Assert.Contains(result.Errors, e => e.MessageKey == expectedError);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestFunctionsWithTypeArgs()
|
||||
{
|
||||
var expectedFunctions = new HashSet<string> { "AsType", "IsType", "ParseJSON" };
|
||||
var functionWithTypeArgs = BuiltinFunctionsCore.TestOnly_AllBuiltinFunctions.Where(f => f.HasTypeArgs);
|
||||
|
||||
Assert.All(functionWithTypeArgs, f => Assert.Contains(f.Name, expectedFunctions));
|
||||
Assert.All(functionWithTypeArgs, f => Assert.Contains(Enumerable.Range(0, f.MaxArity), i => f.ArgIsType(i)));
|
||||
|
||||
var functionsWithoutTypeArgs = BuiltinFunctionsCore.TestOnly_AllBuiltinFunctions.Where(f => !f.HasTypeArgs);
|
||||
Assert.All(functionsWithoutTypeArgs, f => Assert.DoesNotContain(Enumerable.Range(0, Math.Min(f.MaxArity, 5)), i => f.ArgIsType(i)));
|
||||
}
|
||||
|
||||
private void CheckIsTypeAsTypeParseJSON(RecalcEngine engine, string json, string type, object expectedValue, bool isValid = true, ParserOptions options = null)
|
||||
{
|
||||
var result = engine.Eval($"AsType(ParseJSON({json}), {type})", options: options);
|
||||
CheckResult(expectedValue, result, isValid);
|
||||
|
||||
result = engine.Eval($"ParseJSON({json}, {type})", options: options);
|
||||
CheckResult(expectedValue, result, isValid);
|
||||
|
||||
result = engine.Eval($"IsType(ParseJSON({json}), {type})", options: options);
|
||||
Assert.Equal(isValid, result.ToObject());
|
||||
}
|
||||
|
||||
private void CheckResult(object expectedValue, FormulaValue resultValue, bool isValid)
|
||||
{
|
||||
if (isValid)
|
||||
{
|
||||
Assert.Equal(expectedValue, resultValue.ToObject());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.True(resultValue is ErrorValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckIsTypeAsTypeParseJSONCompileErrors(RecalcEngine engine, string json, string type, string expectedError, ParserOptions options = null)
|
||||
{
|
||||
var result = engine.Check($"AsType(ParseJSON({json}), {type})", options: options);
|
||||
Assert.False(result.IsSuccess);
|
||||
Assert.Contains(result.Errors, e => e.MessageKey == expectedError);
|
||||
|
||||
result = engine.Check($"ParseJSON({json}, {type})", options: options);
|
||||
Assert.False(result.IsSuccess);
|
||||
Assert.Contains(result.Errors, e => e.MessageKey == expectedError);
|
||||
|
||||
result = engine.Check($"IsType(ParseJSON({json}), {type})", options: options);
|
||||
Assert.False(result.IsSuccess);
|
||||
Assert.Contains(result.Errors, e => e.MessageKey == expectedError);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче