Skip to content
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

Feat/validation rules openapi #5691

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
21 changes: 20 additions & 1 deletion src/Kiota.Builder/Plugins/PluginsGenerationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
using Kiota.Builder.Extensions;
using Kiota.Builder.OpenApiExtensions;
using Microsoft.OpenApi.ApiManifest;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Services;
using Microsoft.OpenApi.Writers;
using Microsoft.Plugins.Manifest;
using Microsoft.Plugins.Manifest.OpenApiRules;

namespace Kiota.Builder.Plugins;

Expand Down Expand Up @@ -45,6 +47,7 @@
// 1. cleanup any namings to be used later on.
Configuration.ClientClassName =
PluginNameCleanupRegex().Replace(Configuration.ClientClassName, string.Empty); //drop any special characters

// 2. write the OpenApi description
var descriptionRelativePath = $"{Configuration.ClientClassName.ToLowerInvariant()}-{DescriptionPathSuffix}";
var descriptionFullPath = Path.Combine(Configuration.OutputPath, descriptionRelativePath);
Expand All @@ -61,7 +64,23 @@
trimmedPluginDocument.SerializeAsV3(descriptionWriter);
descriptionWriter.Flush();

// 3. write the plugins
//3. validate openapi file
var ruleSet = new Microsoft.OpenApi.Validations.ValidationRuleSet
{
OpenApiServerUrlRule.ServerUrlMustBeHttps,
OpenApiAuthFlowRule.OnlyAuthorizationCodeFlowAllowed(OAIDocument.SecurityRequirements),
OpenApiCombinedAuthFlowRule.PathsCanOnlyHaveOneSecuritySchemePerOperation(OAIDocument.SecurityRequirements),
OpenApiRequestBodySchemaRule.RequestBodySchemaObjectsMustNeverBeNested,
Comment on lines +70 to +73
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there should be more validations than these.

See #5162


};
var errors = OAIDocument.Validate(ruleSet)?.ToArray();
if (errors != null && errors.Length != 0)
{
var message = string.Join(Environment.NewLine, errors.Select(static e => $"{e.Pointer}: {e.Message}"));
throw new InvalidOperationException($"OpenApi document validation failed with errors: {message}");
}

// 4. write the plugins

foreach (var pluginType in Configuration.PluginTypes)
{
Expand All @@ -79,7 +98,7 @@
pluginDocument.Write(writer);
break;
case PluginType.APIManifest:
var apiManifest = new ApiManifestDocument("application"); //TODO add application name

Check warning on line 101 in src/Kiota.Builder/Plugins/PluginsGenerationService.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)

Check warning on line 101 in src/Kiota.Builder/Plugins/PluginsGenerationService.cs

View workflow job for this annotation

GitHub Actions / Build

Complete the task associated to this 'TODO' comment. (https://rules.sonarsource.com/csharp/RSPEC-1135)
// pass empty config hash so that its not included in this manifest.
apiManifest.ApiDependencies[Configuration.ClientClassName] = Configuration.ToApiDependency(string.Empty, TreeNode?.GetRequestInfo().ToDictionary(static x => x.Key, static x => x.Value) ?? [], WorkingDirectory);
var publisherName = string.IsNullOrEmpty(OAIDocument.Info?.Contact?.Name)
Expand Down Expand Up @@ -137,7 +156,7 @@
}
return newSchema;
}
static OpenApiSchema? MergeAllOfInSchema(OpenApiSchema? schema)

Check warning on line 159 in src/Kiota.Builder/Plugins/PluginsGenerationService.cs

View workflow job for this annotation

GitHub Actions / Build

Refactor this static local function to reduce its Cognitive Complexity from 137 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)

Check warning on line 159 in src/Kiota.Builder/Plugins/PluginsGenerationService.cs

View workflow job for this annotation

GitHub Actions / Build

Refactor this static local function to reduce its Cognitive Complexity from 137 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)
{
if (schema?.AllOf is not { Count: > 0 }) return schema;
var newSchema = new OpenApiSchema();
Expand Down
103 changes: 100 additions & 3 deletions tests/Kiota.Builder.Tests/Plugins/PluginsGenerationServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public async Task GeneratesManifestAsync(string inputPluginName, string expected
version: 1.0
description: test description we've created
servers:
- url: http://localhost/
- url: https://localhost/
description: There's no place like home
paths:
/test:
Expand Down Expand Up @@ -118,6 +118,103 @@ public async Task GeneratesManifestAsync(string inputPluginName, string expected
Assert.Empty(resultingManifest.Problems);// no problems are expected with names
Assert.Equal("test description we've created", resultingManifest.Document.DescriptionForHuman);// description is pulled from info
}

[Theory]
[InlineData("client")]
public async Task GenerateManifestAsyncFailsOnInvalidOpenApiFile(string inputPluginName)
{
var simpleDescriptionContent = @"openapi: 3.0.0
info:
title: test
version: 1.0
description: test description we've created
servers:
- url: http://localhost/
description: There's no place like home
paths:
/test:
get:
summary: summary for test path
description: description for test path
security:
- OAuth2: [read]
OpenID: []
responses:
'200':
description: test
/test/{id}:
get:
summary: Summary for test path with id that is longer than 50 characters
description: description for test path with id
operationId: test.WithId
security:
- BasicAuth: []
parameters:
- name: id
in: path
required: true
description: The id of the test
schema:
type: integer
format: int32
responses:
'200':
description: test
components:
securitySchemes:
BasicAuth:
type: http
scheme: basic

BearerAuth:
type: http
scheme: bearer

ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key

OpenID:
type: openIdConnect
openIdConnectUrl: https://example.com/.well-known/openid-configuration

OAuth2:
type: oauth2
flows:
implicit:
authorizationUrl: https://example.com/api/oauth/dialog
scopes:
write: modify pets in your account
read: read your pets";
var workingDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
var simpleDescriptionPath = Path.Combine(workingDirectory) + "description.yaml";
await File.WriteAllTextAsync(simpleDescriptionPath, simpleDescriptionContent);
var mockLogger = new Mock<ILogger<PluginsGenerationService>>();
var openAPIDocumentDS = new OpenApiDocumentDownloadService(_httpClient, mockLogger.Object);
var outputDirectory = Path.Combine(workingDirectory, "output");
var generationConfiguration = new GenerationConfiguration
{
OutputPath = outputDirectory,
OpenAPIFilePath = "openapiPath",
PluginTypes = [PluginType.APIPlugin, PluginType.APIManifest, PluginType.OpenAI],
ClientClassName = inputPluginName,
ApiRootUrl = "http://localhost/", //Kiota builder would set this for us
};
var (openAPIDocumentStream, _) = await openAPIDocumentDS.LoadStreamAsync(simpleDescriptionPath, generationConfiguration, null, false);
var openApiDocument = await openAPIDocumentDS.GetDocumentFromStreamAsync(openAPIDocumentStream, generationConfiguration);
KiotaBuilder.CleanupOperationIdForPlugins(openApiDocument);
var urlTreeNode = OpenApiUrlTreeNode.Create(openApiDocument, Constants.DefaultOpenApiLabel);

var pluginsGenerationService = new PluginsGenerationService(openApiDocument, urlTreeNode, generationConfiguration, workingDirectory);

var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () => await pluginsGenerationService.GenerateManifestAsync());
Assert.Contains("OpenApi document validation failed", exception.Message);
Assert.Contains("Server URL must use HTTPS protocol", exception.Message);
Assert.Contains("Only Authorization Code flow is allowed for OAuth2", exception.Message);
Assert.Contains("Operation cannot have more than one security scheme", exception.Message);
}

private const string ManifestFileName = "client-apiplugin.json";
private const string OpenAIPluginFileName = "openai-plugins.json";
private const string OpenApiFileName = "client-openapi.yml";
Expand All @@ -130,7 +227,7 @@ public async Task ThrowsOnEmptyPathsAfterFilteringAsync()
title: test
version: 1.0
servers:
- url: http://localhost/
- url: https://localhost/
description: There's no place like home
paths:
/test:
Expand Down Expand Up @@ -165,7 +262,7 @@ public async Task GeneratesManifestAndCleansUpInputDescriptionAsync()
version: 1.0
x-test-root-extension: test
servers:
- url: http://localhost/
- url: https://localhost/
description: There's no place like home
paths:
/test:
Expand Down
Loading