microsoft / OpenAPI.NET

The OpenAPI.NET SDK contains a useful object model for OpenAPI documents in .NET along with common serializers to extract raw OpenAPI JSON and YAML documents from the model.
MIT License
1.4k stars 230 forks source link

Components that are reference objects do not work #1506

Open darrelmiller opened 10 months ago

darrelmiller commented 10 months ago

Here is an example of a security object. Reading and writing this example fails to produce a valid OpenAPI. image

openapi: 3.0
info:
  version: 2.0.0
  title: hello world
paths:
  /:
    get:
      responses:
        '200':
          description: OK

security:
  - ReferenceObject: []
components:
  securitySchemes:
    ReferenceObject:
      $ref: 'external.yaml#/components/securitySchemes/RealObject'    
    RealObject:
      type: http
      scheme: basic

A schema component that is reference object also fails for different reasons. In the output OpoenAPI, the transitive reference is lost and the inline reference points directly to the "realObject" instead of the "referenceObject". image

openapi: 3.0
info:
  version: 2.0.0
  title: hello world
paths:
  /:
    get:
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReferenceObject'
components:
  schemas:
    ReferenceObject:
      $ref: '#/components/schemas/RealObject'
    RealObject:
      type: object
      properties:
        name: 
          type: string

Because of these bugs we need to revert PR #1491

dldl-cmd commented 3 months ago

For the first issue, with the invalid written securityScheme this can be reproduced, by the following code, which indicates that it happens while writting the document.

var openApiDocument = new OpenApiDocument
{
    Components = new OpenApiComponents
    {
        SecuritySchemes = new Dictionary<string, OpenApiSecurityScheme>
        {
            ["ReferenceObject"] = new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Id = "RealObject",
                    Type = ReferenceType.Tag,
                    ExternalResource = "external.yaml"
                }
            },
            ["RealObject"] = new OpenApiSecurityScheme
            {
                Type = SecuritySchemeType.Http,
                Scheme = "scheme"
            }
        }
    }
};

var memoryStream = new MemoryStream();
var streamWriter = new FormattingStreamWriter(memoryStream, CultureInfo.InvariantCulture);
var writer = new OpenApiYamlWriter(streamWriter);

openApiDocument.SerializeAsV3(writer);

writer.Flush();

var writtenDocument = Encoding.UTF8.GetString(memoryStream.ToArray());

The issues is, that in the serialization of an OpenApiReference a SecurityScheme is always written as

<ReferenceV3>

instead of considering the context (e.g. being in the components section of the document) where the following is expected

$ref: <ReferenceV3>

The relevant code section is this one, where I could not find and easy way to detect the context in which the element is written. https://github.com/microsoft/OpenAPI.NET/blob/6899d5f161bd0087ccea0a090cebbe4eaffdf99a/src/Microsoft.OpenApi/Models/OpenApiReference.cs#L180C1-L185C14

 public void SerializeAsV3(IOpenApiWriter writer)
 {
     Utils.CheckArgumentNull(writer);

     if (Type == ReferenceType.Tag)
     {
         // Write the string value only
         writer.WriteValue(ReferenceV3);
         return;
     }

     if (Type == ReferenceType.SecurityScheme)
     {
         // Write the string as property name
         writer.WritePropertyName(ReferenceV3);
         return;
     }

     writer.WriteStartObject();

     // $ref
     writer.WriteProperty(OpenApiConstants.DollarRef, ReferenceV3);

     writer.WriteEndObject();
 }
dldl-cmd commented 3 months ago

The second issue happens while reading the document:

var inputYaml = @"
openapi: 3.0
info:
  version: 2.0.0
  title: hello world
paths:
  /:
    get:
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReferenceObject'
components:
  schemas:
    ReferenceObject:
      $ref: '#/components/schemas/RealObject'
    RealObject:
      type: object
      properties:
        name:
          type: string
";

var reader = new OpenApiStringReader(); ;
var document = reader.Read(inputYaml, out OpenApiDiagnostic diagnostic);

var reference = document.Paths["/"].Operations[OperationType.Get].Responses["200"].Content["application/json"].Schema.Reference.ReferenceV3;
Console.WriteLine(reference);

The code above prints #/components/schemas/RealObject so the reference to ReferenceObject is lost while reading the document.

Turning of reference resolving does not create this issue. Without it the read and written document can be the same.

var reader = new OpenApiStringReader(new OpenApiReaderSettings
{
    ReferenceResolution = ReferenceResolutionSetting.DoNotResolveReferences
});