-
-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove unreferenced schema, add --trim-unused-schema
& --keep-schema
#199
Merged
christianhelle
merged 6 commits into
christianhelle:main
from
kirides:feat/remove_unused_schema
Nov 5, 2023
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
8187a2e
first implementation of schema removal
kirides e2cd780
Add more elaborate test Spec, fix wrong Schema being processed
kirides 236502b
formatting, _doc -> document
kirides 077e975
add more parameters to test case, ups coverage
kirides 378f49e
improve recursion protection
kirides bd91fce
Update Readme, add `--keep-schema` to keep schema from getting stipped
kirides File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
using System.Text.RegularExpressions; | ||
|
||
using NJsonSchema; | ||
|
||
using NSwag; | ||
|
||
namespace Refitter.Core; | ||
|
||
public class SchemaCleaner | ||
{ | ||
private readonly OpenApiDocument document; | ||
private readonly string[] keepSchemaPatterns; | ||
|
||
public SchemaCleaner(OpenApiDocument document, string[] keepSchemaPatterns) | ||
{ | ||
this.document = document; | ||
this.keepSchemaPatterns = keepSchemaPatterns; | ||
} | ||
|
||
public void RemoveUnreferencedSchema() | ||
{ | ||
var usage = FindUsedSchema(document); | ||
|
||
var unused = document.Components.Schemas.Where(s => !usage.Contains(s.Key)) | ||
.ToArray(); | ||
|
||
foreach (var unusedSchema in unused) | ||
{ | ||
document.Components.Schemas.Remove(unusedSchema); | ||
} | ||
} | ||
|
||
HashSet<string> FindUsedSchema(OpenApiDocument doc) | ||
{ | ||
var toProcess = new Stack<JsonSchema>(); | ||
var schemaIdLookup = document.Components.Schemas | ||
.ToDictionary(x => x.Value, | ||
x => x.Key); | ||
|
||
var keepSchemaRegexes = keepSchemaPatterns | ||
.Select(x => new Regex(x, RegexOptions.Compiled)) | ||
.ToArray(); | ||
|
||
if (doc.Components?.Schemas != null) | ||
{ | ||
foreach (var kvp in doc.Components.Schemas) | ||
{ | ||
var schema = kvp.Key; | ||
if (keepSchemaRegexes.Any(x => x.IsMatch(schema))) | ||
{ | ||
TryPush(kvp.Value, toProcess); | ||
} | ||
} | ||
} | ||
|
||
foreach (var kvp in doc.Paths) | ||
{ | ||
var pathItem = kvp.Value; | ||
foreach (JsonSchema? schema in GetSchemaForPath(pathItem)) | ||
{ | ||
TryPush(schema, toProcess); | ||
} | ||
} | ||
|
||
var seenIds = new HashSet<string>(); | ||
var seen = new HashSet<JsonSchema>(); | ||
while (toProcess.Count > 0) | ||
{ | ||
var schema = toProcess.Pop(); | ||
if (!seen.Add(schema.ActualSchema)) | ||
{ | ||
continue; | ||
} | ||
|
||
// NOTE: NSwag schema stuff seems weird, with all their "Actual..." | ||
if (schemaIdLookup.TryGetValue(schema.ActualSchema, out var refId)) | ||
{ | ||
if (!seenIds.Add(refId)) | ||
{ | ||
// prevent recursion | ||
continue; | ||
} | ||
} | ||
|
||
foreach (var subSchema in EnumerateSchema(schema.ActualSchema)) | ||
{ | ||
TryPush(subSchema, toProcess); | ||
} | ||
} | ||
|
||
return seenIds; | ||
} | ||
|
||
IEnumerable<JsonSchema?> GetSchemaForPath(OpenApiPathItem pathItem) | ||
{ | ||
foreach (var p in pathItem.Parameters) | ||
{ | ||
yield return p; | ||
} | ||
|
||
foreach (var op in pathItem.Values) | ||
{ | ||
if (op.RequestBody != null) | ||
{ | ||
var body = op.RequestBody; | ||
foreach (var kvpBody in body.Content) | ||
{ | ||
var content = kvpBody.Value; | ||
yield return content.Schema; | ||
} | ||
} | ||
|
||
foreach (var p in op.ActualParameters) | ||
{ | ||
yield return p; | ||
} | ||
|
||
foreach (var resp in op.ActualResponses.Select(x => x.Value)) | ||
{ | ||
foreach (var header in resp.Headers.Select(x => x.Value)) | ||
{ | ||
yield return header; | ||
} | ||
|
||
foreach (var mediaType in resp.Content.Select(x => x.Value)) | ||
{ | ||
yield return mediaType.Schema; | ||
} | ||
} | ||
} | ||
} | ||
|
||
private void TryPush(JsonSchema? schema, Stack<JsonSchema> stack) | ||
{ | ||
if (schema == null) | ||
{ | ||
return; | ||
} | ||
|
||
stack.Push(schema); | ||
} | ||
|
||
IEnumerable<JsonSchema> EnumerateSchema(JsonSchema? schema) | ||
{ | ||
if (schema is null) | ||
{ | ||
return Enumerable.Empty<JsonSchema>(); | ||
} | ||
|
||
return EnumerateInternal(schema) | ||
.Where(x => x != null) | ||
.Select(x => x!); | ||
|
||
static IEnumerable<JsonSchema?> EnumerateInternal(JsonSchema schema) | ||
{ | ||
// schema = schema.ActualSchema; | ||
yield return schema.AdditionalItemsSchema; | ||
yield return schema.AdditionalPropertiesSchema; | ||
if (schema.AllInheritedSchemas != null) | ||
{ | ||
foreach (JsonSchema s in schema.AllInheritedSchemas) | ||
{ | ||
yield return s; | ||
} | ||
} | ||
|
||
if (schema.Items != null) | ||
{ | ||
foreach (JsonSchema s in schema.Items) | ||
{ | ||
yield return s; | ||
} | ||
} | ||
|
||
yield return schema.Not; | ||
|
||
|
||
foreach (var subSchema in schema.AllOf) | ||
{ | ||
yield return subSchema; | ||
} | ||
|
||
foreach (var subSchema in schema.AnyOf) | ||
{ | ||
yield return subSchema; | ||
} | ||
|
||
foreach (var subSchema in schema.OneOf) | ||
{ | ||
yield return subSchema; | ||
} | ||
|
||
foreach (var kvp in schema.Properties) | ||
{ | ||
var subSchema = kvp.Value; | ||
yield return subSchema; | ||
} | ||
|
||
foreach (var kvp in schema.Definitions) | ||
{ | ||
var subSchema = kvp.Value; | ||
yield return subSchema; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kirides can you please add an empty line between the constructor and the
RemoveUnreferencedSchema()
method declarationThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done