Giter VIP home page Giter VIP logo

openapi.net.csharpannotations's Introduction

Build status

C# Annotation Document Generator

Convert C# Annotations to OpenAPI.NET [Preview]

[Disclaimer: This repository is in a preview state. Expect to see some iterating as we work towards the final release candidate slated for January 2019. Feedback is welcome!]

Welcome!

This component is the first by-product of Microsoft's supported base OpenAPI.NET object model. The module is designed to convert your native annotation XML from your API code into a OpenAPI document object. All you need to do is follow a simple annotation schema for your API controller comments, and you automatically get all the benefits of the OpenAPI and its related Swagger tooling.

Overview

We've made an effort to develop an annotation model that maps very closely to the native .NET comment structure for the C# language. In general, the below image describes the general concept of how this utility can translate your annotation XML to an OpenAPI.NET document.

Convert Comments to OpenAPI

Consult our Wiki for specific guidance and examples on how to annotate your controllers.

Remarks: Throughout the README and the Wiki, we will use the following terminology:

C# Comments refers to the comments in the code

/// <summary>
/// Sample Get 1
/// </summary>
/// <group>Sample V1</group>
/// <verb>GET</verb>
/// <url>http://localhost:9000/V1/samples/{id}?queryBool={queryBool}</url>
/// <param name="sampleHeaderParam1" cref="float" in="header">Header param 1</param>
/// <param name="id" cref="string" in="path">The object id</param>
/// <param name="queryBool" required="true" cref="bool" in="query">Sample query boolean</param>
/// <response code="200"><see cref="SampleObject1"/>Sample object retrieved</response>
/// <returns>The sample object 1</returns>

Annotation or Annotation XML refers to the compiler built version of the above comments

<member name="M:Microsoft.OpenApi.CSharpComment.Reader.Tests.SampleApis.Controllers.SampleControllerV1.SampleGet1(System.String,System.Boolean)">
  <summary>
  Sample Get 1
  </summary>
  <group>Sample V1</group>
  <verb>GET</verb>
  <url>http://localhost:9000/V1/samples/{id}?queryBool={queryBool}</url>
  <param name="sampleHeaderParam1" cref="T:System.Object" in="header">Header param 1</param>
  <param name="id" cref="T:System.String" in="path">The object id</param>
  <param name="queryBool" required="true" cref="T:System.Boolean" in="query">Sample query boolean</param>
  <response code="200"><see cref="T:Microsoft.OpenApi.CSharpComment.Reader.Tests.Contracts.SampleObject1"/>Sample object retrieved</response>
  <returns>The sample object 1</returns>
</member>

This Document Generator consumes the above annotations (outputted from MSBuild.exe) to create OpenAPI.NET objects.

Mechanics

The following items are needed as input to the Document Generator:

  • Paths to the Annotation XML documentation files from your MSBuild.exe output. (List<string>)
  • Paths to the Assemblies (DLLs or EXEs) that contain the data types referenced in the comments. (List<string>)
  • Version of the OpenAPI document. (string) Note this is not the OpenAPI specification version. This corresponds to the version field of the Info object in an OpenAPI document.
  • Version of the filter set (FilterSetVersion enum)

After you've correctly annotated your C# code, you'll need to build your solution and then retrieve the output annotation XML file where MSBuild.exe aggregates the projects comments. This file is what this utility will use to convert your comments into an OpenAPI.NET object. Enable Comment Output

Simple Example Code

Here's a simple example. The OpenApiGeneratorConfig class is instantited with two lists, the document version, and the filter set version. The first list contains the paths to your Annotation XML documentation files. The second list contains the paths to the assemblies where classes referenced in the C# XML comments can be found.

For example, if you have a C# comment for a response type as follows:

/// <response code="200"><see cref="SampleObject1"/>Sample object retrieved</response>

You will need to include the path to the assembly file that contains the SampleObject1 class.

Generating your OpenAPI.NET document should look something like this:

var input = new OpenApiGeneratorConfig(
    annotationXmlDocuments: new List<XDocument>()
    {
        XDocument.Load(@"C:\TestData\Annotation.xml"),
        XDocument.Load(@"C:\TestData\Contracts.xml"),
    },
    assemblyPaths: new List<string>()
    {
        @"C:\TestData\Service.dll",
        @"C:\TestData\Contract.dll"
    },
    openApiDocumentVersion: "V1",
    filterSetVersion: FilterSetVersion.V1
);

GenerationDiagnostic result;

var generator = new OpenApiGenerator();

IDictionary<DocumentVariantInfo,OpenApiDocument> openApiDocuments = generator.GenerateDocuments(
    openApiGeneratorConfig: input,
    generationDiagnostic: out result
);

In this example, the generated openApiDocuments should contain valid OpenAPI.NET document(s) for your API based on the provided annotation XMLs and contract assemblies.

Newtonsoft (JSON.NET)

C# Document Generator supports fetching Newtonsoft.Json JsonProperty and JsonIgnore attributes. If your service contracts use Newtonsoft, you will have to include the same version of Newtonsoft.Json.dll as used by service contracts in the assembly paths.

Optional Advanced Configuration

Document generator also allows you to provide an optional advanced configuration as input in OpenApiGeneratorConfig.AdvancedConfigurationXmlDocument which enables:

  • Specifying annotations that logically apply to either the entire document or certain sets of operations.
  • Generating multiple documents based on the provided variant information.

The configuration XML is handcrafted (NOT generated from Visual Studio build).

Consult our Wiki for specific guidance and examples on how to draft this XML.

VSTS Build Task

Nuget Packages

Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com.

When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

openapi.net.csharpannotations's People

Contributors

baywet avatar dralpus avatar ericatmsft avatar microsoft-github-policy-service[bot] avatar perthcharern avatar ravennasoftware avatar scott-lin avatar shwetap05 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

openapi.net.csharpannotations's Issues

Support C# comments

This issue is to track work required to support parsing /// comments from C# and generating open api document from it.

Improve parameter filter messaging for "in" attribute

Given this annotation,

/// <param name="MS-CorrelationId" cref="string" in="headerZZZ">Correlation ID for the request</param>

The error produced is,

ExceptionType: MissingInAttributeException || Message: In attribute is missing from parameter(s): "MS-CorrelationId-CorrelationId"

There are two bugs,

  1. The messaging should indicate the "in" attribute value is not supported. Ideally, it would indicate what values are supported at the same time.
  2. The parameter name in the message is wrong.

Rename of repo and files

  • Rename repo to OpenAPI.NET-CSharpAnnotations
  • Rename reader csproj to Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration
  • Rename generator file to OpenApiGenerator

Filters should detect and produce errors for all annotations instead of just the first encountered

Our filters should detect and report all issues instead of just the first. This will help customers save time by avoiding peeling the onion.

For example, when the following annotations are present, only the first produces an error.

/// <response code="200"></response>
/// <response code="400"></response>

Generation errors that result from this:

ExceptionType: MissingResponseDescriptionException || Message: Description is missing for response code: "200".

Expected:

ExceptionType: MissingResponseDescriptionException || Message: Description is missing for response code: "200".
ExceptionType: MissingResponseDescriptionException || Message: Description is missing for response code: "400".

Provide a mechanism to register generation filters

As the filter configuration stands, it is difficult to register custom filters outside the context of C# code. This becomes apparent when you start to think about, "how can our VSTS build task allow consumers to register their custom filters?". By means of text fields in the task (or perhaps by using the advanced configuration XML), users will need to specify which custom filters to add.

To this end, I'm of the opinion we need to simplify how filters are setup by doing a few pieces of work:

  1. All filters should ultimately inherit from a common interface. There may be more specific filter interfaces, but down the line, they should inherit from a common interface type.
  2. All filters should have an identifer/name.
  3. The set of filters to apply during generation should come from two sources: (a) a default set of filters that are always registered, (b) a custom set of filters that can be registered by name.

Here is my proposal for the common interface:

// ------------------------------------------------------------
//  Copyright (c) Microsoft Corporation.  All rights reserved.
//  Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------

namespace Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Filters
{
    /// <summary>
    /// Defines a filter, which is responsible for transforming data from sources such as, but not limited to, C# annotations, attributes, configuration, etc in order to produce an OpenAPI document.
    /// </summary>
    public interface IFilter
    {
        /// <summary>
        /// Gets the name of this filter.
        /// </summary>
        string Name { get; };
    }
}

Add CI

Most free CIs require that we open source the project first (make github repo public).

Ones that work with C#: TravisCI, AppVeyor

Guid to uuid has min/maxLength added as properties causing code gen compile failures

A guid in params is translated to string type with format of uuid but then min and maxLength are added causing issues with autorest.

swagger: '2.0'
info:
title: Test uuid parameter
version: '1.0'
paths:
'/{id}':
parameters:
- in: path
name: id
type: string
format: uuid
minLength: 36
maxLength: 36

I suspect SchemaReferenceRegistry.cs, line 75

                    else if (input == typeof(Guid))
                    {
                        schema.Example = new OpenApiString(Guid.Empty.ToString());
                        schema.MinLength = 36;
                        schema.MaxLength = 36;
                    }

Not finding an assembly should result in error instead of warning

When assemblies cannot be found, which are required for reflection, the status associated with said issue is Warning. This should probably be changed to Error.

For example, the following exception leads to an overall Warning status:

{"ExceptionType":"FileNotFoundException","Message":"Could not load file or assembly 'file:///D:\\a\\1\\s\\Newtonsoft.Json.dll' or one of its dependencies. The system cannot find the file specified."}

Document Generation test project should not have Newtonsoft dependency

https://github.com/Microsoft/OpenAPI.NET.CSharpComment/blob/9d68a37a130d462e135f319d1ef221547967fefe/test/Microsoft.OpenApi.CSharpAnnotations.DocumentGeneration.Tests/packages.config#L6

Microsoft.OpenApi.CSharpAnnotations.NewtonsoftJsonExtensions.Tests is the project for encapsulating Newtonsoft dependency, so only it should have it.

Not sure why the dependency is needed for testing DocumentGeneration project. Something seems fishy.

Accept document version when invoking generation

Today, the Version field of Info Object is populated with the API version instead of the version of the document; this behavior is incorrect. Stamping the version on Info Object holds for V2 specification as well, since it's the "version of the application API".

I believe the document version should be input to the GenerateDocument and GenerateDocuments methods. There is no way to support a rolling build number reliably via a C# comment, so it should likely go onto the config object we have already.

InvalidResponseException should contain a actionable reason

Here are some sample InvalidResponseException from the library today.

GET /v1/services/visualStudioXml [{"ExceptionType":"InvalidResponseException","Message":"The documented response  is empty. is not valid."}]
PUT /v1/services/byod [{"ExceptionType":"InvalidResponseException","Message":"The documented response  is not valid."}]

In addition to stating the response is invalid, it should say why, so the user can take corrective action to fix.

Add a status/severity to GenerationError

I'd like to know which GenerationError is a "failure" versus "warning" when there are multiple present for a DocumentGenerationResult or OperationGenerationResult.

Do not resolve Newtonsoft.Json.dll with an "in-house" assembly

Currently, Newtonsoft.Json.dll is loaded using the method below, which uses the version that C# Comment Reader depends on and not the consumer's version.

private static Assembly ResolveNewtonsoftJsonVersion(object sender, ResolveEventArgs args)
{
    if (args?.Name != null && args.Name.Contains("Newtonsoft.Json"))
    {
        // For any assembly conflict regarding Newtonsoft.Json versions,
        // just load from the existing version of Newtonsoft.Json.
        return Assembly.LoadFrom("Newtonsoft.Json.dll");
    }

    return null;
}

The consumer should be passing all assembly paths required to do reflection in the public methods of CSharpCommentOpenApiGenerator.

Create a separate project containing Newtonsoft.Json custom filter(s)

Now that we have successfully removed Newtonsoft.Json as a dependency of Microsoft.OpenApi.CSharpComment.Reader and we allow users to define custom filters, we should consider implementing a custom filter to support our existing Newtonsof.Json users who have JsonProperty attributes annotating their contracts. If we implement the custom filter for them, in a centralized manner, we will reduce the friction for them to onboard.

I'm envisioning the custom filter in a simple, lightweight project that is distinct from Microsoft.OpenApi.CSharpComment.Reader. It would need to be nugetized so users could reference it from their projects and supplied to C# Comment Reader as a dependent assembly to load.

Lastly, the nuspec for said custom filter project for target a low version of Newtonsoft.Json, so users are not forced to update to the latest unnecessarily. In theory we should require a minimum version of Newtonsoft.Json which first introduced JsonAttribute property.

Comment reader should not take open api spec version as input

Currently comment reader takes open api spec version as input and when V2 version is provided, it uses OpenAPI.Net library to serialize the V3 DOM to string, but one of the below overload method , returns a DOM which consumer of library would assume is a V2 DOM but its actually isn't. So i am suggesting Comment reader to only generate V3 and consumer can use OpenAPI.Net to convert it to V2.

OpenApiDocument GenerateDocument(
CSharpCommentOpenApiGeneratorConfig cSharpCommentOpenApiGeneratorConfig,
out GenerationDiagnostic generationDiagnostic)

Schema set to null in definitions section which violates spec

When running C# Comment reader on our SwaggerFD, it produces a document which violates the spec. I realize the below document violates the spec for other reasons (e.g. I've omitted required sections entirely), but this is done on purpose in order to highlight the issue more concisely.

Trimmed down example

{
  "swagger": "2.0",
  "definitions": {
    "Microsoft.UniversalStore.Swagger.Contracts.Metadata.ApiIdentifier": null,
    "Microsoft.UniversalStore.Swagger.Contracts.ApiMetadataStatus": null,
    "Microsoft.UniversalStore.Swagger.Contracts.ApiMetadata": null,
    "Microsoft.UniversalStore.Swagger.FrontDoor.Contracts.V1.Operation": null,
  }
}

There are three generation errors, which look related and relevant to why this is happening:

POST /api/metadata/status [{"ExceptionType":"MissingInAttributeException","Message":"In attribute is missing from parameter(s) apis, includeSwaggerDocUri"},{"ExceptionType":"FileNotFoundException","Message":"Could not load file or assembly 'file:///E:\\agent_work\\463\\s\\Newtonsoft.Json.dll' or one of its dependencies. The system cannot find the file specified."},{"ExceptionType":"FileNotFoundException","Message":"Could not load file or assembly 'file:///E:\\agent_work\\463\\s\\Newtonsoft.Json.dll' or one of its dependencies. The system cannot find the file specified."}]
POST /api/metadata [{"ExceptionType":"MissingInAttributeException","Message":"In attribute is missing from parameter(s) api, includeRawSwaggerJson"},{"ExceptionType":"FileNotFoundException","Message":"Could not load file or assembly 'file:///E:\\agent_work\\463\\s\\Newtonsoft.Json.dll' or one of its dependencies. The system cannot find the file specified."}]
GET /api/operations [{"ExceptionType":"FileNotFoundException","Message":"Could not load file or assembly 'file:///E:\\agent_work\\463\\s\\Newtonsoft.Json.dll' or one of its dependencies. The system cannot find the file specified."}]

Extract cref primitive/enum values and place them into descriptions

Take for example the following code and comments.

public enum DayOfWeek
{
    Monday,
    Tuesday,
    ...
    Sunday
}

public class FooController : Controller
{
    public const string DefaultValue = "Default";
    public const int DefaultValue2 = 10;

    /// <param name="stringCriteria" required="false" cref="string" in="query">
    /// Optional string criteria of resource to query. Default is <see cref="DefaultValue"/>.
    /// </param>
    /// <param name="dayOfWeek" required="false" cref="DayOfWeek" in="query">
    /// Optional day of week of resource to query. Default is <see cref="DayOfWeek.Tuesday"/>.
    /// </param>
    /// <param name="numberCriteria" required="false" cref="int" in="query">
    /// Optional number criteria of resource to query. Default is <see cref="DefaultValue2"/>.
    /// </param>
    public void Get(
        string stringCriteria = DefaultValue,
        DayOfWeek dayOfWeek = DayOfWeek.Tuesday,
        int numberCriteria = DefaultValue2 )
    {
        ...
    }
}

I would love to see the descriptions in the resulting OpenAPI document to be:

Optional string criteria of resource to query. Default is 'Default'.
Optional day of week of resource to query. Default is 'Tuesday'.
Optional number criteria of resource to query. Default is '10'.

Today, the descriptions are filled with the literal cref text, so for the examples above, they would be something like:

Optional string criteria of resource to query. Default is 'DefaultValue'.
Optional day of week of resource to query. Default is 'DayOfWeek.Tuesday'.
Optional number criteria of resource to query. Default is 'DefaultValue2'.

Naturally, we cannot do this convenience for complex referenced types, but for simple types like primitives and enums, we should be able to. It would keep documentation in sync with the code values, which is a plus for readers of the OpenAPI documents since they don't have to refer back to code.

Complete CI Integration

Now that we tried out AppVeyor, we need the following:

  • build failure should block check-in.
  • after build, artifact should be available.
  • after build, nuget package should be created.

optional at this point

  • after build, nuget package is uploaded automatically to some private feed.

Add a filter for detecting and handling global serialization settings

Some services/applications set global serialization settings instead of specifying attributes on every contract class property. For applications that do this, their properties in the resulting OpenAPI Document model may have incorrect casing. One such example is our FD application.

Make C# Comment Reader actually implement the IOpenApiReader

Make C# Comment Reader actually implement the IOpenApiReader.

We need to wrap the different inputs into an object and use the interface IOpenApiReader<TInput, TDiagnostic>

SerializedOverallGenerationResult can go into TDiagnostic for now. The construct is a bit awkward since IOpenApiReader assumes that there's one document returned. We will have to fix that as well.

A parameter object with "in": "path" must have "required": true

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#fixed-fields-10

Field Name Type Description
in string REQUIRED. The location of the parameter. Possible values are "query", "header", "path" or "cookie".
required boolean Determines whether this parameter is mandatory. If the parameter location is "path", this property is REQUIRED and its value MUST be true. Otherwise, the property MAY be included and its default value is false.

We need to add "required" : true to every parameter that has "in": "path"

AppBase as a possible parameter to App Domain Creator

Issue discovered by @scott-lin. Thanks!

When the library is invoked from a PowerShell context, the AppDomain's "AppBase" path is set to the location of PowerShell.exe on the machine. This causes an issue when calling Generate document method, given that a new app domain is spun off.

The caller (PowerShell in this case) will try to load the Reader dll again in the new app domain, but it will be looking in the wrong folder (i.e. the AppBase aforementioned) instead of the folder where the DLLs actually are.

The PowerShell script itself can workaround this issue by controlling the AppBase manually, but we can also allow the user to specify the path specifically in AppDomainCreator. This can ensure the caller looks at the right place for the reader Dll.

Edit by Scott-Lin: fixing a few comments to make the issue accurate

Provide an annotation for hiding operations and properties in models

We have a model in our FD code which is incorrect in the resulting document because there's no way to hide specific properties.

For example, the following model is incorrect compared to the behavior of our actual code; StatusCode and Headers are not serialized, but this model indicates they are.

{
  "Description": "string",
  "ErrorCode": "string",
  "Details": [
    "string"
  ],
  "Headers": {
    "additionalProp1": "string",
    "additionalProp2": "string",
    "additionalProp3": "string"
  },
  "Source": "string",
  "StatusCode": "Continue"
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.