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: enable null reference type support #2146

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

MaggieKimani1
Copy link
Contributor

@MaggieKimani1 MaggieKimani1 commented Feb 13, 2025

This PR:

  • Adds null reference type support by enabling the nullable feature in the core and readers project
  • Annotates all properties and method parameters/return types with ? for nullable types
  • Fixes compiler warnings such as possible dereferences of null values.

Fixes #1202

@@ -274,7 +274,7 @@
if ((version.is2_0() || version.is3_0()) && (doc.Paths == null))
{
// paths is a required field in OpenAPI 2.0 and 3.0 but optional in 3.1
RootNode.Context.Diagnostic.Errors.Add(new OpenApiError("", $"Paths is a REQUIRED field at {RootNode.Context.GetLocation()}"));
RootNode?.Context.Diagnostic.Errors.Add(new OpenApiError("", $"Paths is a REQUIRED field at {RootNode.Context.GetLocation()}"));

Check warning

Code scanning / CodeQL

Dereferenced variable may be null

Variable [this.RootNode](1) may be null at this access as suggested by [this](2) null check.

Copilot Autofix AI 10 days ago

To fix the problem, we need to ensure that RootNode is not null before accessing its Context property. The best way to do this is to add a null check for RootNode before the line where it is dereferenced. If RootNode is null, we can either log an error or handle it appropriately to avoid the NullReferenceException.

Suggested changeset 1
src/Microsoft.OpenApi/Reader/ParsingContext.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/Microsoft.OpenApi/Reader/ParsingContext.cs b/src/Microsoft.OpenApi/Reader/ParsingContext.cs
--- a/src/Microsoft.OpenApi/Reader/ParsingContext.cs
+++ b/src/Microsoft.OpenApi/Reader/ParsingContext.cs
@@ -276,3 +276,6 @@
                 // paths is a required field in OpenAPI 2.0 and 3.0 but optional in 3.1
-                RootNode?.Context.Diagnostic.Errors.Add(new OpenApiError("", $"Paths is a REQUIRED field at {RootNode.Context.GetLocation()}"));
+                if (RootNode != null)
+                {
+                    RootNode.Context.Diagnostic.Errors.Add(new OpenApiError("", $"Paths is a REQUIRED field at {RootNode.Context.GetLocation()}"));
+                }
             }
EOF
@@ -276,3 +276,6 @@
// paths is a required field in OpenAPI 2.0 and 3.0 but optional in 3.1
RootNode?.Context.Diagnostic.Errors.Add(new OpenApiError("", $"Paths is a REQUIRED field at {RootNode.Context.GetLocation()}"));
if (RootNode != null)
{
RootNode.Context.Diagnostic.Errors.Add(new OpenApiError("", $"Paths is a REQUIRED field at {RootNode.Context.GetLocation()}"));
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
}
else if (_artifactsRegistry.TryGetValue(uri, out var artifact))
{
return (T)(object)artifact;

Check warning

Code scanning / CodeQL

Useless upcast

There is no need to upcast from [Stream](1) to [Object](2) - the conversion can be done implicitly.
Comment on lines +92 to +100
if ((!childItem.Properties?.ContainsKey(discriminatorName) ?? false) &&
(!childItem.Required?.Contains(discriminatorName) ?? false))
{
return ValidateChildSchemaAgainstDiscriminator(childItem, discriminatorName);
}
else
{
return true;
}

Check notice

Code scanning / CodeQL

Missed ternary opportunity

Both branches of this 'if' statement return - consider using '?' to express intent better.
@MaggieKimani1 MaggieKimani1 changed the title feat: enable NRT feat: enable null reference type support Feb 14, 2025
@MaggieKimani1 MaggieKimani1 marked this pull request as ready for review February 14, 2025 13:04
Copy link
Member

@baywet baywet left a comment

Choose a reason for hiding this comment

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

Great work here!
I've only done a partial review at this point starting with the interfaces, since changes there will require reviewing implementations again.

@@ -19,7 +19,7 @@ internal interface IOpenApiVersionService
/// <param name="summary">The summary of the reference.</param>
/// <param name="description">A reference description</param>
/// <returns>The <see cref="OpenApiReference"/> object or null.</returns>
OpenApiReference ConvertToOpenApiReference(string reference, ReferenceType? type, string summary = null, string description = null);
OpenApiReference? ConvertToOpenApiReference(string reference, ReferenceType? type, string? summary = null, string? description = null);
Copy link
Member

Choose a reason for hiding this comment

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

why do we accept a nullable reference type here? shouldn't it be required?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It can be null in the case of an externally referenced file:

// Either this is an external reference as an entire file
// or a simple string-style reference for tag and security scheme.
if (type == null)
{
// "$ref": "Pet.json"
return new()
{
ExternalResource = segments[0]
};
}

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure I understand this case? "it's something from another file but we don't know what it is" ??? how is that possible since the reference itself will be in a "known section" (i.e. the reference will be where a schema is, etc...)

@@ -27,16 +27,16 @@ protected OpenApiExtensibleDictionary():this(null) { }
/// <param name="dictionary">The generic dictionary.</param>
/// <param name="extensions">The dictionary of <see cref="IOpenApiExtension"/>.</param>
protected OpenApiExtensibleDictionary(
Copy link
Member

Choose a reason for hiding this comment

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

wouldn't it be better to make the dictionary parameter non nullable and optional with a default value of [] (if that's possible) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah that's possible. However I didn't want to tamper with/change model data as it might have side effects during serialization


/// <summary>
/// External resource in the reference.
/// It maybe:
/// 1. a absolute/relative file path, for example: ../commons/pet.json
/// 2. a Url, for example: http://localhost/pet.json
/// </summary>
public string ExternalResource { get; init; }

public string? ExternalResource { get; init; }
Copy link
Member

Choose a reason for hiding this comment

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

leaving the comment here because I can't below.
I'm not sure Type should be nullable

@@ -136,7 +140,7 @@ public void SerializeAsV2(IOpenApiWriter writer)

foreach (var example in Content
.Where(mediaTypePair => mediaTypePair.Value.Examples != null && mediaTypePair.Value.Examples.Any())
.SelectMany(mediaTypePair => mediaTypePair.Value.Examples))
.SelectMany(mediaTypePair => mediaTypePair.Value.Examples!))
Copy link
Member

Choose a reason for hiding this comment

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

don't use bang operators , they usually lead to bugs.
Add an OfType() after the select many instead, not only it'll filter the null values at runtime, it'll fix the compiler complaining about nullable values.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using OfType() still doesn't work. There's still a possible null reference return at the SelectMany():

foreach (var example in Content
    .Where(mediaTypePair => mediaTypePair.Value.Examples is not null && mediaTypePair.Value.Examples.Any())
    .SelectMany(mediaTypePair => mediaTypePair.Value.Examples)
    .OfType<KeyValuePair<string, IOpenApiExample>>())
{
    writer.WritePropertyName(example.Key);
    example.Value.SerializeAsV2(writer);
}

Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
56.6% Coverage on New Code (required ≥ 80%)
30.6% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Enable null reference types on next major version
2 participants