Ejemplo n.º 1
0
        public void playing_with_parts()
        {
            INamespaceScope     g                      = CodeWorkspace.Create().Global;
            INamespaceScopePart gSub                   = g.CreatePart();
            INamespaceScopePart gSub2                  = gSub.CreatePart();
            ITypeScope          gSub2Type1             = gSub2.CreateType("class GSub2Type1");
            ITypeScope          gSub2Type2             = gSub2.CreateType("class GSub2Type2");
            ITypeScopePart      gSub2Type1Part1        = gSub2Type1.CreatePart();
            ITypeScopePart      gSub2Type1Part2        = gSub2Type1.CreatePart();
            IFunctionScope      gSub2Type1Part2F1      = gSub2Type1Part2.CreateFunction("void Action()");
            IFunctionScopePart  gSub2Type1Part2F1Part1 = gSub2Type1Part2F1.CreatePart();

            g.Append("g:");
            gSub.Append("gSub:");
            gSub2.Append("gSub2:");
            gSub2Type1.Append("gSub2Type1:");
            gSub2Type2.Append("gSub2Type2:");
            gSub2Type1Part1.Append("gSub2Type1Part1:");
            gSub2Type1Part2.Append("gSub2Type1Part2:");
            gSub2Type1Part2F1.Append("gSub2Type1Part2F1:");
            gSub2Type1Part2F1Part1.Append("gSub2Type1Part2F1Part1:");

            var s = g.ToString().Trim();

            s.Should().Be(@"
gSub2:
gSub:
g:
class GSub2Type1
{
gSub2Type1Part1:
gSub2Type1Part2:
gSub2Type1:
void Action()
{
gSub2Type1Part2F1Part1:
gSub2Type1Part2F1:
}
}
class GSub2Type2
{
gSub2Type2:
}".Trim().ReplaceLineEndings());
        }
        PocoJsonPropertyInfo?HandleUnionType(IPocoPropertyInfo p,
                                             IActivityMonitor monitor,
                                             JsonSerializationCodeGen jsonCodeGen,
                                             ITypeScopePart write,
                                             ITypeScopePart read,
                                             ref bool isPocoECMAScriptStandardCompliant,
                                             JsonCodeGenHandler mainHandler)
        {
            // Analyses the UnionTypes and creates the handler for each of them.
            // - Forbids ambiguous mapping for ECMAScriptStandard: all numerics are mapped to "Number" or "BigInt" (and arrays or lists are arrays).
            // - The ECMAScriptStandard projected name must be unique (and is associated to its actual handler).
            // -
            var allHandlers = new List <JsonCodeGenHandler>();
            var checkDuplicatedStandardName = new Dictionary <string, List <JsonCodeGenHandler> >();

            // Gets all the handlers and build groups of ECMAStandardJsnoName handlers (only if the Poco is still standard compliant).
            foreach (var union in p.PropertyUnionTypes)
            {
                var h = jsonCodeGen.GetHandler(union);
                if (h == null)
                {
                    return(null);
                }
                allHandlers.Add(h);
                if (isPocoECMAScriptStandardCompliant && h.HasECMAScriptStandardJsonName)
                {
                    var n = h.ECMAScriptStandardJsonName;
                    if (checkDuplicatedStandardName.TryGetValue(n.Name, out var exists))
                    {
                        exists.Add(h);
                    }
                    else
                    {
                        checkDuplicatedStandardName.Add(n.Name, new List <JsonCodeGenHandler>()
                        {
                            h
                        });
                    }
                }
            }
            allHandlers.Sort((h1, h2) => h1.TypeInfo.TypeSpecOrder.CompareTo(h2.TypeInfo.TypeSpecOrder));

            // Analyze the groups (only if the Poco is still standard compliant).
            List <JsonCodeGenHandler>?ecmaStandardReadhandlers = null;
            bool isECMAScriptStandardCompliant = isPocoECMAScriptStandardCompliant;

            if (isECMAScriptStandardCompliant)
            {
                foreach (var group in checkDuplicatedStandardName.Values)
                {
                    if (group.Count > 1)
                    {
                        int idxCanocical = group.IndexOf(h => h.ECMAScriptStandardJsonName.IsCanonical);
                        if (idxCanocical == -1)
                        {
                            monitor.Warn($"{p} UnionType '{group.Select( h => h.GenCSharpName ).Concatenate( "' ,'" )}' types mapped to the same ECMAScript standard name: '{group[0].ECMAScriptStandardJsonName.Name}' and none of them is the 'Canonical' form. De/serializing this Poco in 'ECMAScriptstandard' will throw a NotSupportedException.");
                            isECMAScriptStandardCompliant = false;
                            break;
                        }
                        var winner = group[idxCanocical];
                        monitor.Trace($"{p} UnionType '{group.Select( h => h.GenCSharpName ).Concatenate( "' ,'" )}' types will be read as {winner.GenCSharpName} in ECMAScript standard name.");
                        if (ecmaStandardReadhandlers == null)
                        {
                            ecmaStandardReadhandlers = allHandlers.Where(h => !h.HasECMAScriptStandardJsonName).ToList();
                        }
                        ecmaStandardReadhandlers.Add(winner);
                    }
                    else
                    {
                        monitor.Debug($"{p} UnionType unambiguous mapping in ECMAScript standard name from '{group[0].ECMAScriptStandardJsonName.Name}' to '{group[0].GenCSharpName}'.");
                        if (ecmaStandardReadhandlers == null)
                        {
                            ecmaStandardReadhandlers = allHandlers.Where(h => !h.HasECMAScriptStandardJsonName).ToList();
                        }
                        ecmaStandardReadhandlers.Add(group[0]);
                    }
                }
                isPocoECMAScriptStandardCompliant &= isECMAScriptStandardCompliant;
            }
            // Invariant: handlers are by design associated to different "oblivious NRT" types: switch case can be done on them.
            // That means that the actual's object type is enough to identify the exact handler (in THE CONTEXT of this property type!).
            // And the property setter controls the assignation: the set of types is controlled.
            //
            // It is tempting to simply call the generic write function but this one uses the GenericWriteHandler that is the "oblivious NRT" type:
            // even if this union exposes a ISet<string>? (nullable of non-nullable), it will be a ISet<string?> (non-nullable - since the GenericWriteHandler
            // is by design a NonNullHandler - of nullable - that is the oblivious nullability for reference type) that will be serialized.
            //
            // Actually the type name doesn't really matter, it's just a convention that a client must follow to receive or send data: here, we could
            // perfectly use the index of the type in the union types, that would be an identifier "local to this property" but this would do the job.
            //
            // What really matters is to identify the function that will read the data with the right null handling so that no nulls can be
            // injected where it should not AND to use the right function to write the data, the one that will not let unexpected nulls emitted.
            // Regarding this, using the GenericWriteHandler is definitely not right.
            //
            // That's why we generate a dedicated switch-case for writing here. If one of the handler is bound to the ObjectType (currently
            // that's true when jsonTypeInfo.IsFinal is false), we call the generic write object in the default: case.
            //
            Debug.Assert(!allHandlers.Select(h => h.TypeInfo.GenCSharpName).GroupBy(Util.FuncIdentity).Any(g => g.Count() > 1));

            var info = new PocoJsonPropertyInfo(p, allHandlers, isECMAScriptStandardCompliant ? ecmaStandardReadhandlers : null);

            _finalReadWrite.Add(() =>
            {
                var fieldName = "_v" + info.PropertyInfo.Index;
                write.Append("w.WritePropertyName( ").AppendSourceString(info.PropertyInfo.PropertyName).Append(" );").NewLine();
                if (info.IsJsonUnionType)
                {
                    write.GeneratedByComment()
                    .Append(@"switch( ").Append(fieldName).Append(" )")
                    .OpenBlock()
                    .Append("case null: ");
                    if (info.PropertyInfo.IsNullable)
                    {
                        write.Append("w.WriteNullValue();").NewLine()
                        .Append("break;");
                    }
                    else
                    {
                        write.Append(@"throw new InvalidOperationException( ""A null value appear where it should not. Writing JSON is impossible."" );");
                    }
                    write.NewLine();
                    bool hasDefaultObject = false;
                    foreach (var h in info.AllHandlers)
                    {
                        Debug.Assert(!h.IsNullable, "Union types are not nullable by design (null has been generalized).");
                        if (h.TypeInfo.IsFinal)
                        {
                            write.Append("case ").Append(h.TypeInfo.MostAbstractMapping?.GenCSharpName ?? h.TypeInfo.GenCSharpName).Append(" v: ").NewLine();
                            h.DoGenerateWrite(write, "v", handleNull: false, writeTypeName: true);
                            write.NewLine().Append("break;").NewLine();
                        }
                        else
                        {
                            hasDefaultObject = true;
                        }
                    }
                    write.Append(@"default:").NewLine();
                    if (hasDefaultObject)
                    {
                        mainHandler.ToNonNullHandler().GenerateWrite(write, fieldName);
                        write.NewLine().Append("break;");
                    }
                    else
                    {
                        write.Append(@"throw new InvalidOperationException( $""Unexpected type {").Append(fieldName).Append(@".GetType()} in union ").Append(info.PropertyInfo.ToString() !).Append(@"."" );");
                    }
                    write.CloseBlock();
                }
                else
                {
                    info.AllHandlers[0].GenerateWrite(write, fieldName);
                }

                read.Append("case ").AppendSourceString(info.PropertyInfo.PropertyName).Append(": ")
                .OpenBlock();

                if (info.IsJsonUnionType)
                {
                    read.Append("if( r.TokenType == System.Text.Json.JsonTokenType.Null )");
                    if (info.PropertyInfo.IsNullable)
                    {
                        read.OpenBlock()
                        .Append(fieldName).Append(" = null;").NewLine()
                        .Append("r.Read();")
                        .CloseBlock()
                        .Append("else")
                        .OpenBlock();
                    }
                    else
                    {
                        read.Append(" throw new System.Text.Json.JsonException(\"").Append(info.PropertyInfo.ToString() !).Append(" cannot be null.\");").NewLine();
                    }

                    if (info.IsJsonUnionType)
                    {