Skip to content

Commit 323b0ce

Browse files
committed
chore: Fix internal exception on TOC loading
1 parent 7b7e3e7 commit 323b0ce

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);
@@ -56,21 +55,20 @@ public static TocItemViewModel LoadSingleToc(string file)
5655
var fileType = Utility.GetTocFileType(file);
5756
try
5857
{
59-
if (fileType == TocFileType.Markdown)
60-
{
61-
return new()
62-
{
63-
Items = MarkdownTocReader.LoadToc(EnvironmentContext.FileAbstractLayer.ReadAllText(file), file)
64-
};
65-
}
66-
else if (fileType == TocFileType.Yaml)
58+
switch (fileType)
6759
{
68-
return _deserializer.Deserialize(file) switch
69-
{
70-
List<TocItemViewModel> vm => new() { Items = vm },
71-
TocItemViewModel root => root,
72-
_ => throw new NotSupportedException($"{file} is not a valid TOC file."),
73-
};
60+
case TocFileType.Markdown:
61+
return new()
62+
{
63+
Items = MarkdownTocReader.LoadToc(EnvironmentContext.FileAbstractLayer.ReadAllText(file), file)
64+
};
65+
case TocFileType.Yaml:
66+
{
67+
var yaml = EnvironmentContext.FileAbstractLayer.ReadAllText(file);
68+
return DeserializeYamlToc(yaml);
69+
}
70+
default:
71+
throw new NotSupportedException($"{file} is not a valid TOC file, supported TOC files should be either \"{Constants.TableOfContents.MarkdownTocFileName}\" or \"{Constants.TableOfContents.YamlTocFileName}\".");
7472
}
7573
}
7674
catch (Exception e)
@@ -79,7 +77,18 @@ public static TocItemViewModel LoadSingleToc(string file)
7977
Logger.LogError(message, code: ErrorCodes.Toc.InvalidTocFile);
8078
throw new DocumentException(message, e);
8179
}
80+
}
81+
82+
private static TocItemViewModel DeserializeYamlToc(string yaml)
83+
{
84+
// Parse yaml content to determine TOC type (`List<TocItemViewModel>` or TocItemViewModel).
85+
var parser = new Parser(new Scanner(new StringReader(yaml), skipComments: true));
86+
bool isListItems = parser.TryConsume<StreamStart>(out var _)
87+
&& parser.TryConsume<DocumentStart>(out var _)
88+
&& parser.TryConsume<SequenceStart>(out var _);
8289

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

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
@@ -2125,13 +2125,6 @@ public static class XrefUtility
21252125
{
21262126
public static bool TryGetXrefStringValue(this Docfx.Plugins.XRefSpec spec, string key, out string value) { }
21272127
}
2128-
public class YamlDeserializerWithFallback
2129-
{
2130-
public object Deserialize(System.Func<System.IO.TextReader> reader) { }
2131-
public object Deserialize(string filePath) { }
2132-
public Docfx.Common.YamlDeserializerWithFallback WithFallback<T>() { }
2133-
public static Docfx.Common.YamlDeserializerWithFallback Create<T>() { }
2134-
}
21352128
public static class YamlMime
21362129
{
21372130
public const string ManagedReference = "YamlMime:ManagedReference";

0 commit comments

Comments
 (0)