Skip to content

Commit 9d56541

Browse files
committed
chore: Fix internal exception on TOC loading
1 parent cc5754a commit 9d56541

File tree

5 files changed

+152
-159
lines changed

5 files changed

+152
-159
lines changed

src/Docfx.Build/TableOfContents/TocHelper.cs

+28-19
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@
66
using Docfx.Common;
77
using Docfx.DataContracts.Common;
88
using Docfx.Plugins;
9+
using YamlDotNet.Core.Events;
10+
using YamlDotNet.Core;
11+
using Constants = Docfx.DataContracts.Common.Constants;
912

1013
namespace Docfx.Build.TableOfContents;
1114

1215
public static class TocHelper
1316
{
14-
private static readonly YamlDeserializerWithFallback _deserializer =
15-
YamlDeserializerWithFallback.Create<List<TocItemViewModel>>()
16-
.WithFallback<TocItemViewModel>();
17-
1817
internal static List<FileModel> ResolveToc(ImmutableList<FileModel> models)
1918
{
2019
var tocCache = new Dictionary<string, TocItemInfo>(FilePathComparer.OSPlatformSensitiveStringComparer);
@@ -63,21 +62,20 @@ public static TocItemViewModel LoadSingleToc(string file)
6362
var fileType = Utility.GetTocFileType(file);
6463
try
6564
{
66-
if (fileType == TocFileType.Markdown)
67-
{
68-
return new()
69-
{
70-
Items = MarkdownTocReader.LoadToc(EnvironmentContext.FileAbstractLayer.ReadAllText(file), file)
71-
};
72-
}
73-
else if (fileType == TocFileType.Yaml)
65+
switch (fileType)
7466
{
75-
return _deserializer.Deserialize(file) switch
76-
{
77-
List<TocItemViewModel> vm => new() { Items = vm },
78-
TocItemViewModel root => root,
79-
_ => throw new NotSupportedException($"{file} is not a valid TOC file."),
80-
};
67+
case TocFileType.Markdown:
68+
return new()
69+
{
70+
Items = MarkdownTocReader.LoadToc(EnvironmentContext.FileAbstractLayer.ReadAllText(file), file)
71+
};
72+
case TocFileType.Yaml:
73+
{
74+
var yaml = EnvironmentContext.FileAbstractLayer.ReadAllText(file);
75+
return DeserializeYamlToc(yaml);
76+
}
77+
default:
78+
throw new NotSupportedException($"{file} is not a valid TOC file, supported TOC files should be either \"{Constants.TableOfContents.MarkdownTocFileName}\" or \"{Constants.TableOfContents.YamlTocFileName}\".");
8179
}
8280
}
8381
catch (Exception e)
@@ -86,7 +84,18 @@ public static TocItemViewModel LoadSingleToc(string file)
8684
Logger.LogError(message, code: ErrorCodes.Toc.InvalidTocFile);
8785
throw new DocumentException(message, e);
8886
}
87+
}
88+
89+
private static TocItemViewModel DeserializeYamlToc(string yaml)
90+
{
91+
// Parse yaml content to determine TOC type (`List<TocItemViewModel>` or TocItemViewModel).
92+
var parser = new Parser(new Scanner(new StringReader(yaml), skipComments: true));
93+
bool isListItems = parser.TryConsume<StreamStart>(out var _)
94+
&& parser.TryConsume<DocumentStart>(out var _)
95+
&& parser.TryConsume<SequenceStart>(out var _);
8996

90-
throw new NotSupportedException($"{file} is not a valid TOC file, supported TOC files should be either \"{Constants.TableOfContents.MarkdownTocFileName}\" or \"{Constants.TableOfContents.YamlTocFileName}\".");
97+
return isListItems
98+
? new TocItemViewModel { Items = YamlUtility.Deserialize<List<TocItemViewModel>>(new StringReader(yaml)) }
99+
: YamlUtility.Deserialize<TocItemViewModel>(new StringReader(yaml));
91100
}
92101
}

src/Docfx.Common/YamlDeserializerWithFallback.cs

-62
This file was deleted.
+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Text;
5+
using Docfx.Common;
6+
using Docfx.DataContracts.Common;
7+
using FluentAssertions;
8+
using Xunit;
9+
10+
namespace Docfx.Build.TableOfContents.Tests;
11+
12+
public class TocHelperTest
13+
{
14+
[Fact]
15+
public void TestItemDeserialization()
16+
{
17+
// Arrange
18+
var item = new TocItemViewModel
19+
{
20+
Items =
21+
[
22+
new TocItemViewModel { Uid = "item1" },
23+
new TocItemViewModel { Uid = "item2" }
24+
],
25+
};
26+
27+
var yaml = ToYaml(item);
28+
var filePath = Path.Combine(Path.GetTempPath(), "toc.yml");
29+
File.WriteAllText(filePath, yaml, new UTF8Encoding(false));
30+
31+
try
32+
{
33+
// Act
34+
var result = TocHelper.LoadSingleToc(filePath);
35+
36+
// Assert
37+
result.Should().BeEquivalentTo(item);
38+
}
39+
finally
40+
{
41+
File.Delete(filePath);
42+
}
43+
}
44+
45+
[Fact]
46+
public void TestListDeserialization()
47+
{
48+
// Arrange
49+
var items = new TocItemViewModel[]
50+
{
51+
new TocItemViewModel { Uid = "item1" },
52+
new TocItemViewModel { Uid = "item2" },
53+
};
54+
55+
var yaml = ToYaml(items);
56+
var filePath = Path.Combine(Path.GetTempPath(), "toc.yml");
57+
File.WriteAllText(filePath, yaml);
58+
59+
try
60+
{
61+
// Act
62+
var result = TocHelper.LoadSingleToc(filePath);
63+
64+
// Assert
65+
result.Uid.Should().BeNull();
66+
result.Href.Should().BeNull();
67+
result.Items.Should().BeEquivalentTo(items);
68+
}
69+
finally
70+
{
71+
File.Delete(filePath);
72+
}
73+
}
74+
75+
[Fact]
76+
public void TestItemDeserializationWithEncoding()
77+
{
78+
// Arrange
79+
var item = new TocItemViewModel
80+
{
81+
Items =
82+
[
83+
new TocItemViewModel { Uid = "item1" },
84+
new TocItemViewModel { Uid = "item2" }
85+
],
86+
};
87+
88+
var yaml = ToYaml(item);
89+
90+
foreach (var encoding in Encodings)
91+
{
92+
var filePath = Path.Combine(Path.GetTempPath(), "toc.yml");
93+
File.WriteAllText(filePath, yaml, encoding);
94+
95+
try
96+
{
97+
// Act
98+
var result = TocHelper.LoadSingleToc(filePath);
99+
100+
// Assert
101+
result.Should().BeEquivalentTo(item);
102+
}
103+
finally
104+
{
105+
File.Delete(filePath);
106+
}
107+
}
108+
}
109+
110+
private static readonly Encoding[] Encodings =
111+
[
112+
new UTF8Encoding(false),
113+
new UTF8Encoding(true),
114+
Encoding.Unicode,
115+
Encoding.BigEndianUnicode,
116+
];
117+
118+
private static string ToYaml<T>(T model)
119+
{
120+
using StringWriter sw = new StringWriter();
121+
YamlUtility.Serialize(sw, model);
122+
return sw.ToString();
123+
}
124+
}

test/Docfx.Common.Tests/YamlDeserializerWithFallbackTest.cs

-71
This file was deleted.

test/docfx.Tests/Api.verified.cs

-7
Original file line numberDiff line numberDiff line change
@@ -2123,13 +2123,6 @@ public static class XrefUtility
21232123
{
21242124
public static bool TryGetXrefStringValue(this Docfx.Plugins.XRefSpec spec, string key, out string value) { }
21252125
}
2126-
public class YamlDeserializerWithFallback
2127-
{
2128-
public object Deserialize(System.Func<System.IO.TextReader> reader) { }
2129-
public object Deserialize(string filePath) { }
2130-
public Docfx.Common.YamlDeserializerWithFallback WithFallback<T>() { }
2131-
public static Docfx.Common.YamlDeserializerWithFallback Create<T>() { }
2132-
}
21332126
public static class YamlMime
21342127
{
21352128
public const string ManagedReference = "YamlMime:ManagedReference";

0 commit comments

Comments
 (0)