public void RouteRules() { ruleSet.Add(OpenApiPathRules.RouteSegmentsMustBePlural); ruleSet.Add(OpenApiPathRules.RouteParametersMustBePascalcased); ruleSet.Add(OpenApiPathRules.RoutesMustBeLowercased); ruleSet.Add(OpenApiPathRules.RoutesMustNotUseDelimiters); walker.Walk(document); DisplayErrors(); }
internal static async void ValidateOpenApiDocument(string openapi, LogLevel loglevel) { if (string.IsNullOrEmpty(openapi)) { throw new ArgumentNullException(nameof(openapi)); } var logger = ConfigureLoggerInstance(loglevel); var stream = await GetStream(openapi, logger); OpenApiDocument document; logger.LogTrace("Parsing the OpenApi file"); document = new OpenApiStreamReader(new OpenApiReaderSettings { RuleSet = ValidationRuleSet.GetDefaultRuleSet() } ).Read(stream, out var context); if (context.Errors.Count != 0) { foreach (var error in context.Errors) { Console.WriteLine(error.ToString()); } } var statsVisitor = new StatsVisitor(); var walker = new OpenApiWalker(statsVisitor); walker.Walk(document); logger.LogTrace("Finished walking through the OpenApi document. Generating a statistics report.."); Console.WriteLine(statsVisitor.GetStatisticsReport()); }
public void ResponseMustHaveADescription() { var openApiDocument = new OpenApiDocument(); openApiDocument.Paths.Add( "/test", new OpenApiPathItem { Operations = { [OperationType.Get] = new OpenApiOperation { Responses = { ["200"] = new OpenApiResponse() } } } }); var validator = new OpenApiValidator(); var walker = new OpenApiWalker(validator); walker.Walk(openApiDocument); validator.Exceptions.ShouldBeEquivalentTo( new List <OpenApiException> { new OpenApiException("Response must have a description") }); }
private async Task WriteIndex(string baseUrl, string graphVersion, string openApiVersion, string format, OpenApiDocument graphOpenApi, Stream stream, OpenApiStyle style) { using var sw = new StreamWriter(stream); var indexSearch = new OpenApiOperationIndex(); var walker = new OpenApiWalker(indexSearch); walker.Walk(graphOpenApi); await sw.WriteAsync("<head>" + Environment.NewLine + "<link rel='stylesheet' href='./stylesheet.css' />" + Environment.NewLine + "</head>" + Environment.NewLine + "<h1># OpenAPI Operations for Microsoft Graph</h1>" + Environment.NewLine + "<b/>" + Environment.NewLine + "<ul>" + Environment.NewLine); foreach (var item in indexSearch.Index) { var target = $"{baseUrl}/openapi?tags={item.Key.Name}&openApiVersion={openApiVersion}&graphVersion={graphVersion}&format={format}&style={style}"; await sw.WriteAsync($"<li>{item.Key.Name} [<a href='{target}'>OpenApi</a>] [<a href='/swagger/index.html#url={target}'>Swagger UI</a>]</li>{Environment.NewLine}<ul>{Environment.NewLine}"); foreach (var op in item.Value) { await sw.WriteLineAsync($"<li>{op.OperationId} [<a href='../../openapi?operationIds={op.OperationId}&openApiVersion={openApiVersion}&graphVersion={graphVersion}" + $"&format={format}&style={style}'>OpenAPI</a>]</li>"); } await sw.WriteLineAsync("</ul>"); } await sw.WriteLineAsync("</ul>"); await sw.FlushAsync(); }
public void ValidateFieldIsRequiredInInfo() { // Arrange string urlError = String.Format(SRResource.Validation_FieldIsRequired, "url", "info"); string versionError = String.Format(SRResource.Validation_FieldIsRequired, "version", "info"); IEnumerable <ValidationError> errors; OpenApiInfo info = new OpenApiInfo(); // Act var validator = new OpenApiValidator(); var walker = new OpenApiWalker(validator); walker.Walk(info); // Assert errors = validator.Errors; bool result = !errors.Any(); // Assert Assert.False(result); Assert.NotNull(errors); Assert.Equal(new[] { urlError, versionError }, errors.Select(e => e.ErrorMessage)); }
public void ServersShouldBeReferencedByIndex() { var openApiDocument = new OpenApiDocument(); openApiDocument.Info = new OpenApiInfo() { Title = "foo", Version = "1.2.2" }; openApiDocument.Servers = new List <OpenApiServer> { new OpenApiServer { Url = "http://example.org" }, new OpenApiServer { } }; var validator = new OpenApiValidator(); var walker = new OpenApiWalker(validator); walker.Walk(openApiDocument); validator.Errors.ShouldBeEquivalentTo( new List <ValidationError> { new ValidationError(ErrorReason.Required, "#/servers/1/url", String.Format(SRResource.Validation_FieldIsRequired, "url", "server")) }); }
public void ServersShouldBeReferencedByIndex() { var openApiDocument = new OpenApiDocument { Info = new OpenApiInfo() { Title = "foo", Version = "1.2.2" }, Servers = new List <OpenApiServer> { new OpenApiServer { Url = "http://example.org" }, new OpenApiServer { }, }, Paths = new OpenApiPaths() }; var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); var walker = new OpenApiWalker(validator); walker.Walk(openApiDocument); validator.Errors.Should().BeEquivalentTo( new List <OpenApiError> { new OpenApiValidatorError(nameof(OpenApiServerRules.ServerRequiredFields), "#/servers/1/url", String.Format(SRResource.Validation_FieldIsRequired, "url", "server")) }); }
private void WriteIndex(string baseUrl, string graphVersion, OpenApiDocument graphOpenApi, Stream stream) { var sw = new StreamWriter(stream); var indexSearch = new OpenApiOperationIndex(); var walker = new OpenApiWalker(indexSearch); walker.Walk(graphOpenApi); sw.AutoFlush = true; sw.WriteLine("<head>"); sw.WriteLine("<link rel='stylesheet' href='./stylesheet.css' />"); sw.WriteLine("</head>"); sw.WriteLine("<h1># OpenAPI Operations for Microsoft Graph</h1>"); sw.WriteLine("<b/>"); sw.WriteLine("<ul>"); foreach (var item in indexSearch.Index) { var target = $"{baseUrl}/openapi?tags={item.Key.Name}&openApiVersion=3&graphVersion={graphVersion}"; sw.WriteLine($"<li>{item.Key.Name} [<a href='../../openapi?tags={target}'>OpenApi</a>] [<a href='/swagger/index.html#url={target}'>Swagger UI</a>]</li>"); sw.WriteLine("<ul>"); foreach (var op in item.Value) { sw.WriteLine($"<li>{op.OperationId} [<a href='../../openapi?operationIds={op.OperationId}'>OpenAPI</a>]</li>"); } sw.WriteLine("</ul>"); } sw.WriteLine("</ul>"); sw.Dispose(); }
public void LocateTopLevelArrayItems() { var doc = new OpenApiDocument() { Servers = new List <OpenApiServer>() { new OpenApiServer(), new OpenApiServer() }, Paths = new OpenApiPaths(), Tags = new List <OpenApiTag>() { new OpenApiTag() } }; var locator = new LocatorVisitor(); var walker = new OpenApiWalker(locator); walker.Walk(doc); locator.Locations.ShouldBeEquivalentTo(new List <string> { "#/servers", "#/servers/0", "#/servers/1", "#/paths", "#/tags", "#/tags/0" }); }
internal async Task LoadAsync(OpenApiReference reference, OpenApiDocument document) { _workspace.AddDocument(reference.ExternalResource, document); document.Workspace = _workspace; // Collect remote references by walking document var referenceCollector = new OpenApiRemoteReferenceCollector(document); var collectorWalker = new OpenApiWalker(referenceCollector); collectorWalker.Walk(document); var reader = new OpenApiStreamReader(_readerSettings); // Walk references foreach (var item in referenceCollector.References) { // If not already in workspace, load it and process references if (!_workspace.Contains(item.ExternalResource)) { var input = await _loader.LoadAsync(new Uri(item.ExternalResource, UriKind.RelativeOrAbsolute)); var result = await reader.ReadAsync(input); // TODO merge _diagnositics await LoadAsync(item, result.OpenApiDocument); } } }
private static void WriteIndex(OpenApiDocument graphOpenApi, Stream stream) { var sw = new StreamWriter(stream); var indexSearch = new OpenApiOperationIndex(); var walker = new OpenApiWalker(indexSearch); walker.Walk(graphOpenApi); sw.AutoFlush = true; sw.WriteLine("<h1># OpenAPI Operations for Microsoft Graph</h1>"); sw.WriteLine("<b/>"); sw.WriteLine("<ul>"); foreach (var item in indexSearch.Index) { sw.WriteLine("<li><a href='./$openapi?tags=" + item.Key.Name + "'>" + item.Key.Name + "</a></li>"); sw.WriteLine("<ul>"); foreach (var op in item.Value) { sw.WriteLine("<li><a href='./$openapi?operationIds=" + op.OperationId + "'>" + op.OperationId + "</a></li>"); } sw.WriteLine("</ul>"); } sw.WriteLine("</ul>"); }
public void PathParameterInThePathShouldBeOk() { // Arrange IEnumerable <OpenApiError> errors; var parameter = new OpenApiParameter() { Name = "parameter1", In = ParameterLocation.Path, Required = true, Schema = new OpenApiSchema() { Type = "string", } }; // Act var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); validator.Enter("paths"); validator.Enter("/{parameter1}"); validator.Enter("get"); validator.Enter("parameters"); validator.Enter("1"); var walker = new OpenApiWalker(validator); walker.Walk(parameter); errors = validator.Errors; bool result = errors.Any(); // Assert result.Should().BeFalse(); }
public void ValidateRequiredIsTrueWhenInIsPathInParameter() { // Arrange var parameter = new OpenApiParameter() { Name = "name", In = ParameterLocation.Path }; // Act var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); validator.Enter("{name}"); var walker = new OpenApiWalker(validator); walker.Walk(parameter); var errors = validator.Errors; // Assert errors.Should().NotBeEmpty(); errors.Select(e => e.Message).Should().BeEquivalentTo(new[] { "\"required\" must be true when parameter location is \"path\"" }); }
internal static void ValidateOpenApiDocument(string input) { if (input == null) { throw new ArgumentNullException("input"); } var stream = GetStream(input); OpenApiDocument document; document = new OpenApiStreamReader(new OpenApiReaderSettings { RuleSet = ValidationRuleSet.GetDefaultRuleSet() } ).Read(stream, out var context); if (context.Errors.Count != 0) { foreach (var error in context.Errors) { Console.WriteLine(error.ToString()); } } var statsVisitor = new StatsVisitor(); var walker = new OpenApiWalker(statsVisitor); walker.Walk(document); Console.WriteLine(statsVisitor.GetStatisticsReport()); }
/// <summary> /// Update the OpenAPI document based on the style option /// </summary> /// <param name="style"></param> /// <param name="subsetOpenApiDocument"></param> /// <returns></returns> public static OpenApiDocument ApplyStyle(OpenApiStyle style, OpenApiDocument subsetOpenApiDocument) { if (style == OpenApiStyle.Plain) { return(subsetOpenApiDocument); } /* For Powershell and PowerPlatform Styles */ // Clone doc before making changes subsetOpenApiDocument = Clone(subsetOpenApiDocument); var anyOfRemover = new AnyOfRemover(); var walker = new OpenApiWalker(anyOfRemover); walker.Walk(subsetOpenApiDocument); if (style == OpenApiStyle.PowerShell) { // Format the OperationId for Powershell cmdlet names generation var operationIdFormatter = new OperationIdPowershellFormatter(); walker = new OpenApiWalker(operationIdFormatter); walker.Walk(subsetOpenApiDocument); var version = subsetOpenApiDocument.Info.Version; if (!new Regex("v\\d\\.\\d").Match(version).Success) { subsetOpenApiDocument.Info.Version = "v1.0-" + version; } } return(subsetOpenApiDocument); }
private static void RemoveContent(OpenApiDocument target) { ContentRemover contentRemover = new ContentRemover(); OpenApiWalker walker = new OpenApiWalker(contentRemover); walker.Walk(target); }
public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema() { // Arrange IEnumerable <OpenApiError> errors; var header = new OpenApiHeader() { Required = true, Example = new OpenApiInteger(55), Schema = new OpenApiSchema() { Type = "string", } }; // Act var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); var walker = new OpenApiWalker(validator); walker.Walk(header); errors = validator.Errors; bool result = !errors.Any(); // Assert result.Should().BeFalse(); errors.Select(e => e.Message).Should().BeEquivalentTo(new[] { RuleHelpers.DataTypeMismatchedErrorMessage }); errors.Select(e => e.Pointer).Should().BeEquivalentTo(new[] { "#/example", }); }
public void ValidateExampleAndDefaultShouldNotHaveDataTypeMismatchForSimpleSchema() { // Arrange IEnumerable <OpenApiError> errors; var schema = new OpenApiSchema() { Example = new OpenApiLong(55), Default = new OpenApiPassword("1234"), Type = "string", }; // Act var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); var walker = new OpenApiWalker(validator); walker.Walk(schema); errors = validator.Errors; bool result = !errors.Any(); // Assert result.Should().BeFalse(); errors.Select(e => e.Message).Should().BeEquivalentTo(new[] { RuleHelpers.DataTypeMismatchedErrorMessage, RuleHelpers.DataTypeMismatchedErrorMessage }); errors.Select(e => e.Pointer).Should().BeEquivalentTo(new[] { "#/default", "#/example", }); }
/// <summary> /// Validate element and all child elements /// </summary> /// <param name="element">Element to validate</param> /// <param name="ruleSet">Optional set of rules to use for validation</param> /// <returns>An IEnumerable of errors. This function will never return null.</returns> public static IEnumerable <ValidationError> Validate(this IOpenApiElement element, ValidationRuleSet ruleSet = null) { var validator = new OpenApiValidator(ruleSet); var walker = new OpenApiWalker(validator); walker.Walk(element); return(validator.Errors); }
private static IList <SearchResult> FindOperations(OpenApiDocument graphOpenApi, Func <OpenApiOperation, bool> predicate) { var search = new OperationSearch(predicate); var walker = new OpenApiWalker(search); walker.Walk(graphOpenApi); return(search.SearchResults); }
/// <summary> /// Walk the OpenApiDocument and resolve unresolved references /// </summary> /// <param name="useExternal">Indicates if external references should be resolved. Document needs to reference a workspace for this to be possible.</param> public IEnumerable <OpenApiError> ResolveReferences(bool useExternal = false) { var resolver = new OpenApiReferenceResolver(this, useExternal); var walker = new OpenApiWalker(resolver); walker.Walk(this); return(resolver.Errors); }
public void ValidateEnumShouldNotHaveDataTypeMismatchForSimpleSchema() { // Arrange IEnumerable <OpenApiError> errors; var schema = new OpenApiSchema() { Enum = { new OpenApiString("1"), new OpenApiObject() { ["x"] = new OpenApiInteger(2), ["y"] = new OpenApiString("20"), ["z"] = new OpenApiString("200") }, new OpenApiArray() { new OpenApiInteger(3) }, new OpenApiObject() { ["x"] = new OpenApiInteger(4), ["y"] = new OpenApiInteger(40), }, }, Type = "object", AdditionalProperties = new OpenApiSchema() { Type = "integer", } }; // Act var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); var walker = new OpenApiWalker(validator); walker.Walk(schema); errors = validator.Errors; bool result = !errors.Any(); // Assert result.Should().BeFalse(); errors.Select(e => e.Message).Should().BeEquivalentTo(new[] { RuleHelpers.DataTypeMismatchedErrorMessage, RuleHelpers.DataTypeMismatchedErrorMessage, RuleHelpers.DataTypeMismatchedErrorMessage, }); errors.Select(e => e.Pointer).Should().BeEquivalentTo(new[] { // #enum/0 is not an error since the spec allows // representing an object using a string. "#/enum/1/y", "#/enum/1/z", "#/enum/2" }); }
public static void ResolveSchemas(OpenApiComponents components, Dictionary <string, OpenApiSchema> schemas) { var visitor = new FindSchemaReferences(); visitor.Schemas = schemas; var walker = new OpenApiWalker(visitor); walker.Walk(components); }
public void LocatePathOperationContentSchema() { var doc = new OpenApiDocument { Paths = new OpenApiPaths() }; doc.Paths.Add("/test", new OpenApiPathItem() { Operations = new Dictionary <OperationType, OpenApiOperation>() { [OperationType.Get] = new OpenApiOperation() { Responses = new OpenApiResponses() { ["200"] = new OpenApiResponse() { Content = new Dictionary <string, OpenApiMediaType> { ["application/json"] = new OpenApiMediaType { Schema = new OpenApiSchema { Type = "string" } } } } } } } }); var locator = new LocatorVisitor(); var walker = new OpenApiWalker(locator); walker.Walk(doc); locator.Locations.ShouldBeEquivalentTo(new List <string> { "#/servers", "#/paths", "#/paths/~1test", "#/paths/~1test/get", "#/paths/~1test/get/responses", "#/paths/~1test/get/responses/200", "#/paths/~1test/get/responses/200/content", "#/paths/~1test/get/responses/200/content/application~1json", "#/paths/~1test/get/responses/200/content/application~1json/schema", "#/paths/~1test/get/tags", "#/tags", }); locator.Keys.ShouldBeEquivalentTo(new List <string> { "/test", "Get", "200", "application/json" }); }
private static void FixRequestBodyReferences(OpenApiDocument doc) { // Walk all unresolved parameter references // if id matches with request body Id, change type if (doc.Components?.RequestBodies != null && doc.Components?.RequestBodies.Count > 0) { var fixer = new RequestBodyReferenceFixer(doc.Components?.RequestBodies); var walker = new OpenApiWalker(fixer); walker.Walk(doc); } }
/// <summary> /// Reads the stream input and parses it into an Open API document. /// </summary> /// <param name="input">TextReader containing OpenAPI description to parse.</param> /// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param> /// <returns>Instance of newly created OpenApiDocument</returns> public OpenApiDocument Read(YamlDocument input, out OpenApiDiagnostic diagnostic) { diagnostic = new OpenApiDiagnostic(); var context = new ParsingContext(diagnostic) { ExtensionParsers = _settings.ExtensionParsers, BaseUrl = _settings.BaseUrl }; OpenApiDocument document = null; try { // Parse the OpenAPI Document document = context.Parse(input); // Resolve References if requested switch (_settings.ReferenceResolution) { case ReferenceResolutionSetting.ResolveAllReferences: throw new ArgumentException(Properties.SRResource.CannotResolveRemoteReferencesSynchronously); case ReferenceResolutionSetting.ResolveLocalReferences: var resolver = new OpenApiReferenceResolver(document); var walker = new OpenApiWalker(resolver); walker.Walk(document); foreach (var item in resolver.Errors) { diagnostic.Errors.Add(item); } break; case ReferenceResolutionSetting.DoNotResolveReferences: break; } } catch (OpenApiException ex) { diagnostic.Errors.Add(new OpenApiError(ex)); } // Validate the document if (_settings.RuleSet != null && _settings.RuleSet.Rules.Count > 0) { var errors = document.Validate(_settings.RuleSet); foreach (var item in errors) { diagnostic.Errors.Add(item); } } return(document); }
/// <summary> /// Reads the stream input and parses it into an Open API document. /// </summary> /// <param name="input">Stream containing OpenAPI description to parse.</param> /// <param name="diagnostic">Returns diagnostic object containing errors detected during parsing</param> /// <returns>Instance of newly created OpenApiDocument</returns> public OpenApiDocument Read(Stream input, out OpenApiDiagnostic diagnostic) { ParsingContext context; YamlDocument yamlDocument; diagnostic = new OpenApiDiagnostic(); // Parse the YAML/JSON try { yamlDocument = LoadYamlDocument(input); } catch (SyntaxErrorException ex) { diagnostic.Errors.Add(new OpenApiError(string.Empty, ex.Message)); return(new OpenApiDocument()); } context = new ParsingContext { ExtensionParsers = _settings.ExtensionParsers }; // Parse the OpenAPI Document var document = context.Parse(yamlDocument, diagnostic); // Resolve References if requested switch (_settings.ReferenceResolution) { case ReferenceResolutionSetting.ResolveAllReferences: throw new ArgumentException(Properties.SRResource.CannotResolveRemoteReferencesSynchronously); case ReferenceResolutionSetting.ResolveLocalReferences: var resolver = new OpenApiReferenceResolver(document); var walker = new OpenApiWalker(resolver); walker.Walk(document); break; case ReferenceResolutionSetting.DoNotResolveReferences: break; } // Validate the document var errors = document.Validate(_settings.RuleSet); foreach (var item in errors) { diagnostic.Errors.Add(new OpenApiError(item.ErrorPath, item.ErrorMessage)); } return(document); }
/// <summary> /// Update the OpenAPI document based on the style option /// </summary> /// <param name="style">The OpenApiStyle value.</param> /// <param name="subsetOpenApiDocument">The subset of an OpenAPI document.</param> /// <returns>An OpenAPI doc with the respective style applied.</returns> public static OpenApiDocument ApplyStyle(OpenApiStyle style, OpenApiDocument subsetOpenApiDocument) { if (style == OpenApiStyle.GEAutocomplete) { // Clone doc before making changes subsetOpenApiDocument = Clone(subsetOpenApiDocument); // The Content property and its schema $refs are unnecessary for autocomplete RemoveContent(subsetOpenApiDocument); } else if (style == OpenApiStyle.PowerShell || style == OpenApiStyle.PowerPlatform) { /* For Powershell and PowerPlatform Styles */ // Clone doc before making changes subsetOpenApiDocument = Clone(subsetOpenApiDocument); // Remove AnyOf var anyOfRemover = new AnyOfRemover(); var walker = new OpenApiWalker(anyOfRemover); walker.Walk(subsetOpenApiDocument); if (style == OpenApiStyle.PowerShell) { // Format the OperationId for Powershell cmdlet names generation var powershellFormatter = new PowershellFormatter(); walker = new OpenApiWalker(powershellFormatter); walker.Walk(subsetOpenApiDocument); var version = subsetOpenApiDocument.Info.Version; if (!new Regex("v\\d\\.\\d").Match(version).Success) { subsetOpenApiDocument.Info.Version = "v1.0-" + version; } // Remove the root path to make AutoREST happy subsetOpenApiDocument.Paths.Remove("/"); // Temp. fix - Escape the # character from description in // 'microsoft.graph.networkInterface' schema EscapePoundCharacter(subsetOpenApiDocument.Components); } } if (subsetOpenApiDocument.Paths == null || !subsetOpenApiDocument.Paths.Any()) { throw new ArgumentException("No paths found for the supplied parameters."); } return(subsetOpenApiDocument); }
private static void CopyReferences(OpenApiDocument target) { bool morestuff = false; do { var copy = new CopyReferences(target); var walker = new OpenApiWalker(copy); walker.Walk(target); morestuff = Add(copy.Components, target.Components); } while (morestuff); }
/// <summary> /// Checks an <see cref="OpenApiDocument"/> for internal consistency. /// </summary> /// <param name="document">The document to check.</param> /// <returns> /// The results of the validation process. /// </returns> public static OpenApiDocumentValidationResult ValidateDocument(this OpenApiDocument document) { var validator = new OpenApiValidator(new ValidationRuleSet( new[] { new ValidationRule <OpenApiSchema>(ValidateSchema), })); var walker = new OpenApiWalker(validator); walker.Walk(document); return(new OpenApiDocumentValidationResult(validator.Errors)); }