Skip to content

Commit

Permalink
Fix composed serializers
Browse files Browse the repository at this point in the history
Fix import statements

fix - rewrite to if statements
  • Loading branch information
rkodev committed Nov 4, 2024
1 parent 9340ce7 commit 6d45335
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 41 deletions.
2 changes: 1 addition & 1 deletion src/Kiota.Builder/Refiners/TypeScriptRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1462,7 +1462,7 @@ private static void AddDeserializerUsingToDiscriminatorFactoryForComposedTypePar

function.AddUsing(new CodeUsing
{
Name = modelDeserializerFunction.Parent.Name,
Name = modelDeserializerFunction.Name,
Declaration = new CodeType
{
Name = modelDeserializerFunction.Name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public override void WriteCodeElement(CodeFileDeclaration codeElement, LanguageW
.Where(static x => x is { IsExternal: false, Declaration.TypeDefinition: not null })
.GroupBy(static x =>
$"{x.Declaration!.TypeDefinition!.GetImmediateParentOfType<CodeNamespace>().Name}.{x.Declaration?.Name.ToLowerInvariant()}")
.Select(static x => x.OrderBy(static x => x.Parent?.Name).First()));
.Select(static x => x.OrderByDescending(static x => x.Alias, StringComparer.OrdinalIgnoreCase).First()));

_codeUsingWriter.WriteCodeElement(filteredUsing, ns, writer);
}
Expand Down
44 changes: 24 additions & 20 deletions src/Kiota.Builder/Writers/TypeScript/CodeFunctionWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public override void WriteCodeElement(CodeFunction codeElement, LanguageWriter w
FactoryMethodReturnType :
GetTypescriptTypeString(codeMethod.ReturnType, codeElement, inlineComposedTypeString: true);
var isVoid = "void".EqualsIgnoreCase(returnType);
CodeMethodWriter.WriteMethodDocumentationInternal(codeElement.OriginalLocalMethod, writer, isVoid, conventions);
CodeMethodWriter.WriteMethodDocumentationInternal(codeElement.GetImmediateParentOfType<CodeFile>(), codeElement.OriginalLocalMethod, writer, isVoid, conventions);
CodeMethodWriter.WriteMethodTypecheckIgnoreInternal(codeElement.OriginalLocalMethod, writer);
CodeMethodWriter.WriteMethodPrototypeInternal(codeElement.OriginalLocalMethod, writer, returnType, isVoid, conventions, true);

Expand Down Expand Up @@ -73,7 +73,7 @@ private void WriteComposedTypeDeserializer(CodeFunction codeElement, LanguageWri
if (composedType.Types.Any(x => IsPrimitiveType(x, composedType, false)))
{
var expression = string.Join(" ?? ", composedType.Types.Where(x => IsPrimitiveType(x, composedType, false)).Select(codeType => $"n.{conventions.GetDeserializationMethodName(codeType, codeElement.OriginalLocalMethod, composedType.IsCollection)}"));
writer.WriteLine($"\"\": n => {{ {composedParam.Name.ToFirstCharacterLowerCase()} = {expression}}},");
writer.WriteLine($"\"\" : n => {{ {composedParam.Name.ToFirstCharacterLowerCase()} = {expression}}},");
}
foreach (var mappedType in composedType.Types.Where(x => !IsPrimitiveType(x, composedType, false)))
{
Expand All @@ -95,12 +95,13 @@ private void WriteComposedTypeSerializer(CodeFunction codeElement, LanguageWrite
{
var paramName = composedParam.Name.ToFirstCharacterLowerCase();
writer.WriteLine($"if ({paramName} === undefined || {paramName} === null) return;");
writer.StartBlock($"switch (true) {{");
bool isFirst = true;
foreach (var type in composedType.Types.Where(x => IsPrimitiveType(x, composedType, false)))
{
WriteCaseStatementForPrimitiveTypeSerialization(type, "undefined", paramName, codeElement, writer);
var ifElse = isFirst ? "" : "else ";
WriteCaseStatementForPrimitiveTypeSerialization(type, "undefined", paramName, codeElement, writer, ifElse);
isFirst = false;
}
writer.CloseBlock();
return;
}

Expand Down Expand Up @@ -177,19 +178,18 @@ private void WriteDiscriminatorSwitchBlock(DiscriminatorInformation discriminato
writer.CloseBlock();
}

private void WriteCaseStatementForPrimitiveTypeSerialization(CodeTypeBase type, string key, string modelParamName, CodeFunction method, LanguageWriter writer)
private void WriteCaseStatementForPrimitiveTypeSerialization(CodeTypeBase type, string key, string modelParamName, CodeFunction method, LanguageWriter writer, String prefix)
{
var nodeType = conventions.GetTypeString(type, method, false);
var serializationName = GetSerializationMethodName(type, method.OriginalLocalMethod);
if (string.IsNullOrEmpty(serializationName) || string.IsNullOrEmpty(nodeType)) return;

writer.StartBlock(type.IsCollection
? $"case Array.isArray({modelParamName}) && ({modelParamName}).every(item => typeof item === '{nodeType}') :"
: $"case typeof {modelParamName} === \"{nodeType}\":");
? $"{prefix}if (Array.isArray({modelParamName}) && ({modelParamName}).every(item => typeof item === '{nodeType}')) {{"
: $"{prefix}if (typeof {modelParamName} === \"{nodeType}\" ) {{");

writer.WriteLine($"writer.{serializationName}({key}, {modelParamName} as {conventions.GetTypeString(type, method)});");
writer.WriteLine("break;");
writer.DecreaseIndent();
writer.CloseBlock();
}

private static void WriteApiConstructorBody(CodeFile parentFile, CodeMethod method, LanguageWriter writer)
Expand Down Expand Up @@ -336,7 +336,10 @@ private string FindFunctionInNameSpace(string functionName, CodeElement codeElem
var codeFunction = Array.Find(codeFunctions,
func => func.GetImmediateParentOfType<CodeNamespace>().Name == myNamespace.Name) ??
throw new InvalidOperationException($"Function {functionName} not found in namespace {myNamespace.Name}");
return conventions.GetTypeString(new CodeType { TypeDefinition = codeFunction }, codeElement, false);

var targetElement = codeElement.GetImmediateParentOfType<CodeFile>();

return GetTypescriptTypeString(new CodeType { TypeDefinition = codeFunction }, targetElement, includeCollectionInformation: false);
}

private void WriteSerializerFunction(CodeFunction codeElement, LanguageWriter writer)
Expand Down Expand Up @@ -420,30 +423,31 @@ private void WritePropertySerializationStatement(CodeProperty codeProperty, stri
private void WriteSerializationStatementForComposedTypeProperty(CodeComposedTypeBase composedType, string modelParamName, CodeFunction method, LanguageWriter writer, CodeProperty codeProperty, string? serializeName)
{
var defaultValueSuffix = GetDefaultValueLiteralForProperty(codeProperty) is string dft && !string.IsNullOrEmpty(dft) && !dft.EqualsIgnoreCase("\"null\"") ? $" ?? {dft}" : string.Empty;
writer.StartBlock("switch (true) {");
WriteComposedTypeSwitchClause(composedType, method, writer, codeProperty, modelParamName, defaultValueSuffix);
WriteComposedTypeIfClause(composedType, method, writer, codeProperty, modelParamName, defaultValueSuffix);
WriteComposedTypeDefaultClause(composedType, writer, codeProperty, modelParamName, defaultValueSuffix, serializeName);
writer.CloseBlock();
}

private void WriteComposedTypeSwitchClause(CodeComposedTypeBase composedType, CodeFunction method, LanguageWriter writer, CodeProperty codeProperty, string modelParamName, string defaultValueSuffix)
private void WriteComposedTypeIfClause(CodeComposedTypeBase composedType, CodeFunction method, LanguageWriter writer, CodeProperty codeProperty, string modelParamName, string defaultValueSuffix)
{
var codePropertyName = codeProperty.Name.ToFirstCharacterLowerCase();
var isCollectionOfEnum = IsCollectionOfEnum(codeProperty);
var spreadOperator = isCollectionOfEnum ? "..." : string.Empty;

bool isFirst = true;
foreach (var type in composedType.Types.Where(x => IsPrimitiveType(x, composedType)))
{
var isElse = isFirst ? "" : "else ";
var nodeType = conventions.GetTypeString(type, method, false);
var serializationName = GetSerializationMethodName(type, method.OriginalLocalMethod);
if (string.IsNullOrEmpty(serializationName) || string.IsNullOrEmpty(nodeType)) return;

writer.StartBlock(type.IsCollection
? $"case Array.isArray({modelParamName}.{codePropertyName}) && ({modelParamName}.{codePropertyName}).every(item => typeof item === '{nodeType}') :"
: $"case typeof {modelParamName}.{codePropertyName} === \"{nodeType}\":");
? $"{isElse}if (Array.isArray({modelParamName}.{codePropertyName}) && ({modelParamName}.{codePropertyName}).every(item => typeof item === '{nodeType}')) {{"
: $"{isElse}if ( typeof {modelParamName}.{codePropertyName} === \"{nodeType}\") {{");

writer.WriteLine($"writer.{serializationName}(\"{codeProperty.WireName}\", {spreadOperator}{modelParamName}.{codePropertyName}{defaultValueSuffix} as {nodeType});");
writer.CloseBlock("break;");
writer.CloseBlock();
isFirst = false;
}
}

Expand All @@ -453,7 +457,7 @@ private static void WriteComposedTypeDefaultClause(CodeComposedTypeBase composed
var nonPrimitiveTypes = composedType.Types.Where(x => !IsPrimitiveType(x, composedType)).ToArray();
if (nonPrimitiveTypes.Length > 0)
{
writer.StartBlock("default:");
writer.StartBlock("else {");
foreach (var groupedTypes in nonPrimitiveTypes.GroupBy(static x => x.IsCollection))
{
var collectionCodeType = (composedType.Clone() as CodeComposedTypeBase)!;
Expand All @@ -466,7 +470,7 @@ private static void WriteComposedTypeDefaultClause(CodeComposedTypeBase composed

writer.WriteLine($"writer.{writerFunction}<{propTypeName}>(\"{codeProperty.WireName}\", {modelParamName}.{codePropertyName}{defaultValueSuffix} as {propTypeName}{groupSymbol}{propertyTypes}, {serializeName});");
}
writer.CloseBlock("break;");
writer.CloseBlock();
}
}

Expand Down
13 changes: 7 additions & 6 deletions src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,20 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri
ArgumentNullException.ThrowIfNull(writer);
if (codeElement.Parent is CodeFunction) return;

var returnType = GetTypescriptTypeString(codeElement.ReturnType, codeElement, inlineComposedTypeString: true);
var codeFile = codeElement.GetImmediateParentOfType<CodeFile>();
var returnType = GetTypescriptTypeString(codeElement.ReturnType, codeFile, inlineComposedTypeString: true);
var isVoid = "void".EqualsIgnoreCase(returnType);
WriteMethodDocumentation(codeElement, writer, isVoid);
WriteMethodDocumentation(codeFile, codeElement, writer, isVoid);
WriteMethodPrototype(codeElement, writer, returnType, isVoid);
if (codeElement.Parent is CodeClass)
throw new InvalidOperationException("No method implementations are generated in typescript: either functions or constants.");
}

private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer, bool isVoid)
private void WriteMethodDocumentation(CodeFile codeFile, CodeMethod code, LanguageWriter writer, bool isVoid)
{
WriteMethodDocumentationInternal(code, writer, isVoid, conventions);
WriteMethodDocumentationInternal(codeFile, code, writer, isVoid, conventions);
}
internal static void WriteMethodDocumentationInternal(CodeMethod code, LanguageWriter writer, bool isVoid, TypeScriptConventionService typeScriptConventionService)
internal static void WriteMethodDocumentationInternal(CodeFile codeFile, CodeMethod code, LanguageWriter writer, bool isVoid, TypeScriptConventionService typeScriptConventionService)
{
var returnRemark = (isVoid, code.IsAsync) switch
{
Expand All @@ -41,7 +42,7 @@ internal static void WriteMethodDocumentationInternal(CodeMethod code, LanguageW
code.Parameters
.Where(static x => x.Documentation.DescriptionAvailable)
.OrderBy(static x => x.Name)
.Select(x => $"@param {x.Name} {x.Documentation.GetDescription(type => GetTypescriptTypeString(type, code, inlineComposedTypeString: true), ReferenceTypePrefix, ReferenceTypeSuffix, RemoveInvalidDescriptionCharacters)}")
.Select(x => $"@param {x.Name} {x.Documentation.GetDescription(type => GetTypescriptTypeString(type, codeFile, inlineComposedTypeString: true), ReferenceTypePrefix, ReferenceTypeSuffix, RemoveInvalidDescriptionCharacters)}")
.Union([returnRemark])
.Union(GetThrownExceptionsRemarks(code)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,25 @@ public override string GetAccessModifier(AccessModifier access)
public override string GetParameterSignature(CodeParameter parameter, CodeElement targetElement, LanguageWriter? writer = null)
{
ArgumentNullException.ThrowIfNull(parameter);
var paramType = GetTypescriptTypeString(parameter.Type, targetElement, includeCollectionInformation: true, inlineComposedTypeString: true);
var composedType = GetOriginalComposedType(parameter.Type);
var paramType = GetTypescriptTypeString(parameter.Type, targetElement, includeCollectionInformation: true, inlineComposedTypeString: true);

if (composedType != null && parameter.Parent is CodeMethod cm && cm.IsOfKind(CodeMethodKind.Serializer))
{
// eliminate primitive types from serializers with composed type signature
var newType = (CodeComposedTypeBase)composedType.Clone();
var nonPrimitiveTypes = composedType.Types.Where(x => !IsPrimitiveTypeOrPrimitiveCollection(x, composedType)).ToArray();
newType.SetTypes(nonPrimitiveTypes);
paramType = GetTypescriptTypeString(newType, targetElement, includeCollectionInformation: true, inlineComposedTypeString: true);
}
var isComposedOfPrimitives = composedType != null && composedType.IsComposedOfPrimitives(IsPrimitiveType);

// add a 'Parsable' suffix to composed parameters of primitives only if they are not deserialization targets
// add a 'Parsable' type to the parameter if it is composed of non-Parsable types
var parsableTypes = (
composedType != null && composedType.IsComposedOfPrimitives(IsPrimitiveTypeOrPrimitiveCollection),
parameter.Parent is CodeMethod method && method.IsOfKind(CodeMethodKind.Deserializer)
composedType != null,
parameter.Parent is CodeMethod method && (method.IsOfKind(CodeMethodKind.Deserializer, CodeMethodKind.Serializer))
) switch
{
(true, false) => string.Empty,
(true, true) => "Parsable | ",
_ => string.Empty,
};
Expand Down Expand Up @@ -232,10 +240,10 @@ TYPE_LOWERCASE_BOOLEAN or

public static bool IsPrimitiveType(CodeType codeType, CodeComposedTypeBase codeComposedTypeBase) => IsPrimitiveType(codeType, codeComposedTypeBase, true);

public static bool IsPrimitiveTypeOrPrimitiveCollection(CodeType codeType, CodeComposedTypeBase codeComposedTypeBase) => IsPrimitiveType(codeType, codeComposedTypeBase, false);

public static bool IsPrimitiveType(CodeType codeType, CodeComposedTypeBase codeComposedTypeBase, bool includeCollectionInformation) => IsPrimitiveType(GetTypescriptTypeString(codeType, codeComposedTypeBase, includeCollectionInformation));

private static bool IsPrimitiveTypeOrPrimitiveCollection(CodeType codeType, CodeComposedTypeBase codeComposedTypeBase) => IsPrimitiveType(codeType, codeComposedTypeBase, false);

internal static string RemoveInvalidDescriptionCharacters(string originalDescription) => originalDescription?.Replace("\\", "/", StringComparison.OrdinalIgnoreCase) ?? string.Empty;
public override bool WriteShortDescription(IDocumentedElement element, LanguageWriter writer, string prefix = "", string suffix = "")
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1252,10 +1252,8 @@ public async Task Writes_UnionOfPrimitiveValues_SerializerFunctionAsync()
writer.Write(serializerFunction);
var serializerFunctionStr = tw.ToString();
Assert.Contains("return", serializerFunctionStr);
Assert.Contains("switch", serializerFunctionStr);
Assert.Contains("case \"number\":", serializerFunctionStr);
Assert.Contains("case \"string\":", serializerFunctionStr);
Assert.Contains("break", serializerFunctionStr);
Assert.Contains("typeof primitives === \"number\"", serializerFunctionStr);
Assert.Contains("typeof primitives === \"string\"", serializerFunctionStr);
AssertExtensions.CurlyBracesAreClosed(serializerFunctionStr, 1);
}

Expand Down Expand Up @@ -1454,9 +1452,9 @@ public async Task Writes_CodeUnionBetweenObjectsAndPrimitiveTypes_SerializerAsyn
writer.Write(serializeFunction);
var result = tw.ToString();

Assert.Contains("case typeof parentClass.property === \"string\"", result);
Assert.Contains("typeof parentClass.property === \"string\"", result);
Assert.Contains("writer.writeStringValue(\"property\", parentClass.property as string);", result);
Assert.Contains("case typeof parentClass.property === \"number\"", result);
Assert.Contains("typeof parentClass.property === \"number\"", result);
Assert.Contains("writer.writeNumberValue(\"property\", parentClass.property as number);", result);
Assert.Contains(
"writer.writeCollectionOfObjectValues<ArrayOfObjects>(\"property\", parentClass.property as ArrayOfObjects[] | undefined | null",
Expand Down

0 comments on commit 6d45335

Please sign in to comment.