Skip to content

Commit fff2ec8

Browse files
authored
Add capabilities for merging CSDLs (#155)
* Update to the latest markdowndeep drop * Add capability to merge EntityFramework objects together * Updates to merge logic * Fix up bindingParameter/this confusion. * Improvements for publishing schema * Updated dedupe logic * Fix typo * Handle some additional docs scenarios * Make orphan pages an option
1 parent 1a99665 commit fff2ec8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1459
-297
lines changed

ApiDocs.Console/CommandLineOptions.cs

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ class CommandLineOptions
4949
public const string VerbPublishMetadata = "publish-edmx";
5050

5151
public const string VerbGenerateDocs = "generate-docs";
52-
53-
[VerbOption(VerbPrint, HelpText="Print files, resources, and methods discovered in the documentation.")]
52+
53+
[VerbOption(VerbPrint, HelpText = "Print files, resources, and methods discovered in the documentation.")]
5454
public PrintOptions PrintVerbOptions { get; set; }
5555

5656
[VerbOption(VerbCheckLinks, HelpText = "Verify links in the documentation aren't broken.")]
57-
public BasicCheckOptions CheckLinksVerb { get; set; }
57+
public CheckLinkOptions CheckLinksVerb { get; set; }
5858

5959
[VerbOption(VerbDocs, HelpText = "Check for errors in the documentation (resources + examples).")]
6060
public BasicCheckOptions CheckDocsVerb { get; set; }
@@ -65,19 +65,19 @@ class CommandLineOptions
6565
[VerbOption(VerbService, HelpText = "Check for errors between the documentation and service.")]
6666
public CheckServiceOptions CheckServiceVerb { get; set; }
6767

68-
[VerbOption(VerbPublish, HelpText="Publish a version of the documentation, optionally converting it into other formats.")]
68+
[VerbOption(VerbPublish, HelpText = "Publish a version of the documentation, optionally converting it into other formats.")]
6969
public PublishOptions PublishVerb { get; set; }
7070

71-
[VerbOption(VerbPublishMetadata, HelpText="Publish or update metadata based on information in the docset.")]
71+
[VerbOption(VerbPublishMetadata, HelpText = "Publish or update metadata based on information in the docset.")]
7272
public PublishMetadataOptions EdmxPublishVerb { get; set; }
7373

74-
[VerbOption(VerbMetadata, HelpText="Check service CSDL metadata against documentation.")]
74+
[VerbOption(VerbMetadata, HelpText = "Check service CSDL metadata against documentation.")]
7575
public CheckMetadataOptions CheckMetadataVerb { get; set; }
7676

77-
[VerbOption(VerbGenerateDocs, HelpText="Generate documentation from an CSDL model")]
77+
[VerbOption(VerbGenerateDocs, HelpText = "Generate documentation from an CSDL model")]
7878
public GenerateDocsOptions GenerateDocsVerb { get; set; }
7979

80-
[VerbOption(VerbAbout, HelpText="Print about information for this application.")]
80+
[VerbOption(VerbAbout, HelpText = "Print about information for this application.")]
8181
public BaseOptions AboutVerb { get; set; }
8282

8383
[HelpVerbOption]
@@ -90,7 +90,7 @@ public string GetUsage(string verb)
9090
class BaseOptions
9191
{
9292

93-
[Option("log", HelpText="Write the console output to file.")]
93+
[Option("log", HelpText = "Write the console output to file.")]
9494
public string LogFile { get; set; }
9595

9696
[Option("ignore-warnings", HelpText = "Ignore warnings as errors for pass rate.")]
@@ -99,10 +99,10 @@ class BaseOptions
9999
[Option("silence-warnings", HelpText = "Don't print warnings to the screen or consider them errors")]
100100
public bool SilenceWarnings { get; set; }
101101

102-
[Option("appveyor-url", HelpText="Specify the AppVeyor Build Worker API URL for output integration")]
102+
[Option("appveyor-url", HelpText = "Specify the AppVeyor Build Worker API URL for output integration")]
103103
public string AppVeyorServiceUrl { get; set; }
104104

105-
[Option("ignore-errors", HelpText="Prevent errors from generating a non-zero return code.")]
105+
[Option("ignore-errors", HelpText = "Prevent errors from generating a non-zero return code.")]
106106
public bool IgnoreErrors { get; set; }
107107

108108
[Option("parameters", HelpText = "Specify additional page variables that are used by the publishing engine. URL encoded: key=value&key2=value2.")]
@@ -131,7 +131,7 @@ public Dictionary<string, string> PageParameterDict {
131131
}
132132

133133
#if DEBUG
134-
[Option("debug", HelpText="Launch the debugger before doing anything interesting")]
134+
[Option("debug", HelpText = "Launch the debugger before doing anything interesting")]
135135
public bool AttachDebugger { get; set; }
136136
#endif
137137

@@ -183,16 +183,16 @@ class CheckMetadataOptions : DocSetOptions
183183

184184
class PrintOptions : DocSetOptions
185185
{
186-
[Option("files", HelpText="Print the files discovered as part of the documentation")]
186+
[Option("files", HelpText = "Print the files discovered as part of the documentation")]
187187
public bool PrintFiles { get; set; }
188-
189-
[Option("resources", HelpText="Print the resources discovered in the documentation")]
188+
189+
[Option("resources", HelpText = "Print the resources discovered in the documentation")]
190190
public bool PrintResources { get; set; }
191-
192-
[Option("methods", HelpText="Print the methods discovered in the documentation.")]
191+
192+
[Option("methods", HelpText = "Print the methods discovered in the documentation.")]
193193
public bool PrintMethods { get; set; }
194194

195-
[Option("accounts", HelpText="Print the list of accounts discovered in the documentation.")]
195+
[Option("accounts", HelpText = "Print the list of accounts discovered in the documentation.")]
196196
public bool PrintAccounts { get; set; }
197197

198198
public override bool HasRequiredProperties(out string[] missingArguments)
@@ -212,6 +212,11 @@ public override bool HasRequiredProperties(out string[] missingArguments)
212212
}
213213
}
214214

215+
class CheckLinkOptions : BasicCheckOptions {
216+
[Option("orphan-page-warning", HelpText="Print a warning for each page without any incoming links.")]
217+
public bool IncludeOrphanPageWarning { get; set; }
218+
}
219+
215220
class BasicCheckOptions : DocSetOptions
216221
{
217222
[Option('m', "method", HelpText = "Name of the method to test. If omitted, all defined methods are tested.", MutuallyExclusiveSet="fileOrMethod")]
@@ -392,6 +397,9 @@ class PublishMetadataOptions : DocSetOptions
392397
[Option("source", HelpText="Source metadata input file.")]
393398
public string SourceMetadataPath { get; set; }
394399

400+
[Option("merge-with", HelpText= "Specify a second metadata input file to merge with the first.")]
401+
public string SecondSourceMetadataPath { get; set; }
402+
395403
[Option("format", DefaultValue=MetadataFormat.Default, HelpText="Specify the input and output formats for metadata.")]
396404
public MetadataFormat DataFormat { get; set; }
397405

@@ -428,6 +436,7 @@ public CsdlWriterOptions GetOptions()
428436
Sort = SortOutput,
429437
OutputDirectoryPath = OutputDirectory,
430438
SourceMetadataPath = SourceMetadataPath,
439+
MergeWithMetadataPath = SecondSourceMetadataPath,
431440
Namespaces = Namespaces?.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries),
432441
TransformOutput = TransformOutput,
433442
DocumentationSetPath = DocumentationSetPath,

ApiDocs.Console/Program.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,13 @@ private static async Task RunInvokedMethodAsync(CommandLineOptions origCommandLi
181181
await PrintDocInformationAsync((PrintOptions)options);
182182
break;
183183
case CommandLineOptions.VerbCheckLinks:
184-
returnSuccess = await CheckLinksAsync((BasicCheckOptions)options);
184+
returnSuccess = await CheckLinksAsync((CheckLinkOptions)options);
185185
break;
186186
case CommandLineOptions.VerbDocs:
187187
returnSuccess = await CheckDocsAsync((BasicCheckOptions)options);
188188
break;
189189
case CommandLineOptions.VerbCheckAll:
190-
returnSuccess = await CheckDocsAllAsync((BasicCheckOptions)options);
190+
returnSuccess = await CheckDocsAllAsync((CheckLinkOptions)options);
191191
break;
192192
case CommandLineOptions.VerbService:
193193
returnSuccess = await CheckServiceAsync((CheckServiceOptions)options);
@@ -220,7 +220,7 @@ private static async Task RunInvokedMethodAsync(CommandLineOptions origCommandLi
220220
/// </summary>
221221
/// <param name="options"></param>
222222
/// <returns></returns>
223-
private static async Task<bool> CheckDocsAllAsync(BasicCheckOptions options)
223+
private static async Task<bool> CheckDocsAllAsync(CheckLinkOptions options)
224224
{
225225
var docset = await GetDocSetAsync(options);
226226

@@ -468,7 +468,7 @@ private static async Task PrintAccountsAsync(PrintOptions options, DocSet docset
468468
/// </summary>
469469
/// <param name="options"></param>
470470
/// <param name="docs"></param>
471-
private static async Task<bool> CheckLinksAsync(BasicCheckOptions options, DocSet docs = null)
471+
private static async Task<bool> CheckLinksAsync(CheckLinkOptions options, DocSet docs = null)
472472
{
473473
const string testName = "Check-links";
474474
var docset = docs ?? await GetDocSetAsync(options);
@@ -487,7 +487,7 @@ private static async Task<bool> CheckLinksAsync(BasicCheckOptions options, DocSe
487487
TestReport.StartTest(testName);
488488

489489
ValidationError[] errors;
490-
docset.ValidateLinks(options.EnableVerboseOutput, interestingFiles, out errors, options.RequireFilenameCaseMatch);
490+
docset.ValidateLinks(options.EnableVerboseOutput, interestingFiles, out errors, options.RequireFilenameCaseMatch, options.IncludeOrphanPageWarning);
491491

492492
foreach (var error in errors)
493493
{
@@ -616,7 +616,7 @@ private static async Task<CheckResults> CheckExamplesAsync(BasicCheckOptions opt
616616
if (example.Language != CodeLanguage.Json)
617617
continue;
618618

619-
var testName = string.Format("check-example: {0}", example.Metadata.MethodName, example.Metadata.ResourceType);
619+
var testName = string.Format("check-example: {0}", example.Metadata.MethodName?.FirstOrDefault(), example.Metadata.ResourceType);
620620
TestReport.StartTest(testName, doc.DisplayName);
621621

622622
ValidationError[] errors;
@@ -650,7 +650,7 @@ private static async Task<CheckResults> CheckMethodsAsync(BasicCheckOptions opti
650650

651651
if (string.IsNullOrEmpty(method.ExpectedResponse))
652652
{
653-
await TestReport.FinishTestAsync(testName, TestOutcome.Failed, "Null response where one was expected.", printFailuresOnly: options.PrintFailuresOnly);
653+
await TestReport.FinishTestAsync(testName, TestOutcome.Failed, "No response was paired with this request.", printFailuresOnly: options.PrintFailuresOnly);
654654
results.FailureCount++;
655655
continue;
656656
}

ApiDocs.Publishing/ApiDocs.Publishing.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
<ItemGroup>
5050
<Compile Include="CSDL\CsdlExtensionMethods.cs" />
5151
<Compile Include="CSDL\CsdlWriter.cs" />
52+
<Compile Include="CSDL\ObjectGraphMerger.cs" />
5253
<Compile Include="CSDL\MethodCollection.cs" />
5354
<Compile Include="Html\ApiDocsConditionalTag.cs" />
5455
<Compile Include="Html\DocumentPublisherHtml.cs" />

ApiDocs.Publishing/CSDL/CsdlExtensionMethods.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,15 @@ namespace ApiDocs.Publishing.CSDL
2727
{
2828
using ApiDocs.Validation;
2929
using System;
30+
using System.Collections.Generic;
31+
using System.Reflection;
32+
using System.Text;
3033
using Validation.Http;
34+
using Validation.OData;
35+
using Validation.OData.Transformation;
36+
using Validation.Utility;
37+
using System.Linq;
38+
3139
internal static class CsdlExtensionMethods
3240
{
3341

@@ -67,5 +75,43 @@ internal static void AppendWithCondition(this System.Text.StringBuilder sb, bool
6775
sb.Append(text);
6876
}
6977
}
78+
79+
/// <summary>
80+
/// Merge two EntityFramework instances together into the first framework
81+
/// </summary>
82+
/// <param name="framework1"></param>
83+
/// <param name="framework2"></param>
84+
internal static EntityFramework MergeWith(this EntityFramework framework1, EntityFramework framework2)
85+
{
86+
ObjectGraphMerger<EntityFramework> merger = new ObjectGraphMerger<EntityFramework>(framework1, framework2);
87+
var edmx = merger.Merge();
88+
89+
// Clean up bindingParameters on actions and methods to be consistently the same
90+
foreach(var schema in edmx.DataServices.Schemas)
91+
{
92+
foreach (var action in schema.Actions)
93+
{
94+
foreach(var param in action.Parameters.Where(x => x.Name == "bindingParameter" || x.Name == "this")) {
95+
param.Name = "bindingParameter";
96+
param.Nullable = null;
97+
}
98+
}
99+
foreach(var func in schema.Functions)
100+
{
101+
foreach (var param in func.Parameters.Where(x => x.Name == "bindingParameter" || x.Name == "this")) {
102+
param.Name = "bindingParameter";
103+
param.Nullable = null;
104+
}
105+
}
106+
}
107+
108+
return edmx;
109+
110+
111+
}
112+
113+
114+
115+
70116
}
71117
}

ApiDocs.Publishing/CSDL/CsdlWriter.cs

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,20 @@ public CsdlWriter(DocSet docs, CsdlWriterOptions options)
6161

6262
public override async Task PublishToFolderAsync(string outputFolder)
6363
{
64-
string outputFilenameSuffix = null;
64+
string outputFilenameSuffix = "";
6565

6666
// Step 1: Generate an EntityFramework OM from the documentation and/or template file
6767
EntityFramework framework = CreateEntityFrameworkFromDocs();
6868
if (null == framework)
6969
return;
7070

71+
if (!string.IsNullOrEmpty(options.MergeWithMetadataPath))
72+
{
73+
EntityFramework secondFramework = CreateEntityFrameworkFromDocs(options.MergeWithMetadataPath, generateFromDocs: false);
74+
framework = framework.MergeWith(secondFramework);
75+
outputFilenameSuffix += "-merged";
76+
}
77+
7178
// Step 1a: Apply an transformations that may be defined in the documentation
7279
if (!string.IsNullOrEmpty(options.TransformOutput))
7380
{
@@ -81,7 +88,7 @@ public override async Task PublishToFolderAsync(string outputFolder)
8188
framework.ApplyTransformation(transformations.SchemaChanges, versionsToPublish);
8289
if (!string.IsNullOrEmpty(options.Version))
8390
{
84-
outputFilenameSuffix = $"-{options.Version}";
91+
outputFilenameSuffix += $"-{options.Version}";
8592
}
8693
}
8794

@@ -141,19 +148,21 @@ private string GenerateOutputFileFullName(string templateFilename, string output
141148
return outputFullName;
142149
}
143150

144-
private EntityFramework CreateEntityFrameworkFromDocs()
151+
private EntityFramework CreateEntityFrameworkFromDocs(string sourcePath = null, bool? generateFromDocs = null)
145152
{
153+
sourcePath = sourcePath ?? options.SourceMetadataPath;
154+
146155
EntityFramework edmx = new EntityFramework();
147-
if (!string.IsNullOrEmpty(options.SourceMetadataPath))
156+
if (!string.IsNullOrEmpty(sourcePath))
148157
{
149158
try
150159
{
151-
if (!System.IO.File.Exists(options.SourceMetadataPath))
160+
if (!System.IO.File.Exists(sourcePath))
152161
{
153-
throw new System.IO.FileNotFoundException($"Unable to locate source file: {options.SourceMetadataPath}");
162+
throw new System.IO.FileNotFoundException($"Unable to locate source file: {sourcePath}");
154163
}
155164

156-
using (System.IO.FileStream stream = new System.IO.FileStream(options.SourceMetadataPath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
165+
using (System.IO.FileStream stream = new System.IO.FileStream(sourcePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
157166
{
158167
if (options.Formats.HasFlag(MetadataFormat.EdmxInput))
159168
{
@@ -193,10 +202,10 @@ private EntityFramework CreateEntityFrameworkFromDocs()
193202
}
194203
}
195204

196-
bool generateNewElements = !options.SkipMetadataGeneration;
205+
bool generateNewElements = (generateFromDocs == null && !options.SkipMetadataGeneration) || (generateFromDocs.HasValue && generateFromDocs.Value) ;
197206

198207
// Add resources
199-
if (Documents.Files.Any())
208+
if (generateNewElements && Documents.Files.Any())
200209
{
201210
foreach (var resource in Documents.Resources)
202211
{
@@ -523,7 +532,7 @@ private Dictionary<string, MethodCollection> GetUniqueRequestPaths(string baseUr
523532
Dictionary<string, MethodCollection> uniqueRequestPaths = new Dictionary<string, MethodCollection>();
524533
foreach (var m in Documents.Methods)
525534
{
526-
if (m.ExpectedResponseMetadata.ExpectError)
535+
if (m.ExpectedResponseMetadata != null && m.ExpectedResponseMetadata.ExpectError)
527536
{
528537
// Ignore thigns that are expected to error
529538
continue;
@@ -544,7 +553,7 @@ private Dictionary<string, MethodCollection> GetUniqueRequestPaths(string baseUr
544553
}
545554
uniqueRequestPaths[path].Add(m);
546555

547-
Console.WriteLine("{0} :: {1} --> {2}", path, m.RequestMetadata.ResourceType, m.ExpectedResponseMetadata.ResourceType);
556+
Console.WriteLine("{0} :: {1} --> {2}", path, m.RequestMetadata.ResourceType, m.ExpectedResponseMetadata?.ResourceType);
548557
}
549558
cachedUniqueRequestPaths = uniqueRequestPaths;
550559
}
@@ -792,6 +801,7 @@ public class CsdlWriterOptions
792801
{
793802
public string OutputDirectoryPath { get; set; }
794803
public string SourceMetadataPath { get; set; }
804+
public string MergeWithMetadataPath { get; set; }
795805
public MetadataFormat Formats { get; set; }
796806
public string[] Namespaces { get; set; }
797807
public bool Sort { get; set; }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+


ApiDocs.Validation.UnitTests/ApiDocs.Validation.UnitTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
<Compile Include="JsonRewriteTests.cs" />
5454
<Compile Include="MultipartMimeTests.cs" />
5555
<Compile Include="NullableTests.cs" />
56+
<Compile Include="ObjectGraphMergerTests.cs" />
5657
<Compile Include="Properties\Resources.Designer.cs">
5758
<AutoGen>True</AutoGen>
5859
<DesignTime>True</DesignTime>

0 commit comments

Comments
 (0)