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

[dotnet] Json serializer gen context for SM output #14481

Merged

Conversation

nvborisenko
Copy link
Member

@nvborisenko nvborisenko commented Sep 10, 2024

User description

Description

Use generated json serializer context for Selenium Manager json output.

Motivation and Context

To be friendly with AOT.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • I have read the contributing document.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

PR Type

enhancement


Description

  • Enhanced JSON deserialization by replacing JsonNode with a strongly typed SeleniumManagerResponse class, improving type safety and clarity.
  • Introduced JsonSerializerOptions with a custom serializer context to support AOT (Ahead Of Time) compilation.
  • Updated the RunCommand method to utilize the new structured response class, ensuring more reliable data handling.
  • Added internal classes SeleniumManagerResponse, LogEntryResponse, and ResultResponse to represent the JSON structure.

Changes walkthrough 📝

Relevant files
Enhancement
SeleniumManager.cs
Implement strong typing for JSON deserialization in SeleniumManager

dotnet/src/webdriver/SeleniumManager.cs

  • Replaced JsonNode with a strongly typed SeleniumManagerResponse class.
  • Introduced JsonSerializerOptions with a custom serializer context.
  • Updated the RunCommand method to use the new response class.
  • Added internal classes for structured JSON deserialization.
  • +50/-17 

    💡 PR-Agent usage:
    Comment /help on the PR to get a list of all available PR-Agent tools and their descriptions

    Copy link
    Contributor

    PR Reviewer Guide 🔍

    ⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Key issues to review

    Error Handling
    The error handling in the RunCommand method could be improved. The catch block for JSON deserialization throws a new exception, potentially losing the original exception details.

    Null Reference
    There's a potential for null reference exception when accessing jsonResponse.Logs. Although there's a null check, it's good practice to use the null-conditional operator.

    Copy link
    Contributor

    PR Code Suggestions ✨

    CategorySuggestion                                                                                                                                    Score
    Best practice
    Use a dictionary to map log levels to logging actions for more efficient and maintainable code

    Consider using a Dictionary<string, Action> to map log levels to logging actions,
    reducing the need for multiple if-else checks.

    dotnet/src/webdriver/SeleniumManager.cs [197-217]

    -switch (entry.Level)
    +private static readonly Dictionary<string, Action<ILogger, string>> LogActions = new()
     {
    -    case "WARN":
    -        if (_logger.IsEnabled(LogEventLevel.Warn))
    -        {
    -            _logger.Warn(entry.Message);
    -        }
    -        break;
    -    case "DEBUG":
    -        if (_logger.IsEnabled(LogEventLevel.Debug))
    -        {
    -            _logger.Debug(entry.Message);
    -        }
    -        break;
    -    case "INFO":
    -        if (_logger.IsEnabled(LogEventLevel.Info))
    -        {
    -            _logger.Info(entry.Message);
    -        }
    -        break;
    +    ["WARN"] = (logger, message) => { if (logger.IsEnabled(LogEventLevel.Warn)) logger.Warn(message); },
    +    ["DEBUG"] = (logger, message) => { if (logger.IsEnabled(LogEventLevel.Debug)) logger.Debug(message); },
    +    ["INFO"] = (logger, message) => { if (logger.IsEnabled(LogEventLevel.Info)) logger.Info(message); }
    +};
    +
    +// In the loop:
    +if (LogActions.TryGetValue(entry.Level, out var logAction))
    +{
    +    logAction(_logger, entry.Message);
     }
     
    • Apply this suggestion
    Suggestion importance[1-10]: 8

    Why: This suggestion enhances code maintainability and efficiency by reducing repetitive conditional checks, making it a valuable improvement.

    8
    Use nameof operator in JsonPropertyName attributes for better maintainability

    Consider using nameof operator for property names in JsonPropertyName attributes to
    improve maintainability and refactoring support.

    dotnet/src/webdriver/SeleniumManager.cs [240-244]

    -[JsonPropertyName("driver_path")]
    +[JsonPropertyName(nameof(DriverPath).ToLower())]
     public string DriverPath { get; set; }
     
    -[JsonPropertyName("browser_path")]
    +[JsonPropertyName(nameof(BrowserPath).ToLower())]
     public string BrowserPath { get; set; }
     
    • Apply this suggestion
    Suggestion importance[1-10]: 6

    Why: Using the nameof operator improves maintainability and refactoring support, but the improvement is minor and not critical.

    6
    Enhancement
    Replace switch statement with switch expression for more concise log level handling

    Consider using a switch expression instead of a switch statement for more concise
    and readable code when handling log levels.

    dotnet/src/webdriver/SeleniumManager.cs [197-217]

    -switch (entry.Level)
    +_ = entry.Level switch
     {
    -    case "WARN":
    -        if (_logger.IsEnabled(LogEventLevel.Warn))
    -        {
    -            _logger.Warn(entry.Message);
    -        }
    -        break;
    -    case "DEBUG":
    -        if (_logger.IsEnabled(LogEventLevel.Debug))
    -        {
    -            _logger.Debug(entry.Message);
    -        }
    -        break;
    -    case "INFO":
    -        if (_logger.IsEnabled(LogEventLevel.Info))
    -        {
    -            _logger.Info(entry.Message);
    -        }
    -        break;
    -}
    +    "WARN" when _logger.IsEnabled(LogEventLevel.Warn) => _logger.Warn(entry.Message),
    +    "DEBUG" when _logger.IsEnabled(LogEventLevel.Debug) => _logger.Debug(entry.Message),
    +    "INFO" when _logger.IsEnabled(LogEventLevel.Info) => _logger.Info(entry.Message),
    +    _ => null
    +};
     
    • Apply this suggestion
    Suggestion importance[1-10]: 7

    Why: The suggestion to use a switch expression improves code readability and conciseness, making it easier to maintain. However, it does not address any critical issues or bugs.

    7
    Performance
    Make SeleniumManagerResponse and its nested classes sealed to prevent inheritance and potentially improve performance

    Consider making the SeleniumManagerResponse and its nested classes sealed to prevent
    inheritance and potentially improve performance.

    dotnet/src/webdriver/SeleniumManager.cs [225-245]

    -internal class SeleniumManagerResponse
    +internal sealed class SeleniumManagerResponse
     {
         public IReadOnlyList<LogEntryResponse> Logs { get; set; }
     
         public ResultResponse Result { get; set; }
     
    -    public class LogEntryResponse
    +    public sealed class LogEntryResponse
         {
             public string Level { get; set; }
     
             public string Message { get; set; }
         }
     
    -    public class ResultResponse
    +    public sealed class ResultResponse
         {
             [JsonPropertyName("driver_path")]
             public string DriverPath { get; set; }
     
             [JsonPropertyName("browser_path")]
             public string BrowserPath { get; set; }
         }
     }
     
    • Apply this suggestion
    Suggestion importance[1-10]: 5

    Why: Sealing classes can prevent unintended inheritance and slightly improve performance, but the impact is minimal in this context.

    5

    @nvborisenko
    Copy link
    Member Author

    Blocked by bazel-contrib/rules_dotnet#443

    @purkhusid
    Copy link
    Contributor

    This has been fixed in rules_dotnet. I tested it out on this branch and you can fix it with the following patch:

    diff --git a/MODULE.bazel b/MODULE.bazel
    index 348c66a2bf..ea0a6a3aa2 100644
    --- a/MODULE.bazel
    +++ b/MODULE.bazel
    @@ -26,6 +26,11 @@ bazel_dep(name = "rules_pkg", version = "0.10.1")
     bazel_dep(name = "rules_python", version = "0.33.0")
     bazel_dep(name = "rules_proto", version = "6.0.0")
     bazel_dep(name = "rules_ruby", version = "0.11.0")
    +git_override(
    +    module_name = "rules_dotnet",
    +    commit = "cb4545964decdca5d4cc984e170635a2dc3bc893",
    +    remote = "https://github.com/bazelbuild/rules_dotnet",
    +)
     
     linter = use_extension("@apple_rules_lint//lint:extensions.bzl", "linter")
     linter.configure(
    diff --git a/dotnet/paket.dependencies b/dotnet/paket.dependencies
    index 35689c8116..e7e982d52f 100644
    --- a/dotnet/paket.dependencies
    +++ b/dotnet/paket.dependencies
    @@ -24,5 +24,6 @@ nuget System.Diagnostics.Tools 4.3.0
     nuget System.Drawing.Common 7.0.0
     nuget System.Runtime 4.3.1
     nuget System.Runtime.InteropServices 4.3.0
    -nuget System.Text.Json 8.0.4
    +nuget System.Text.Json 8.0.0
    +nuget System.Text.Encodings.Web 
     nuget Runfiles 0.12.0
    diff --git a/dotnet/src/webdriver/BUILD.bazel b/dotnet/src/webdriver/BUILD.bazel
    index 1f23755fa1..00db006f6e 100644
    --- a/dotnet/src/webdriver/BUILD.bazel
    +++ b/dotnet/src/webdriver/BUILD.bazel
    @@ -56,6 +56,7 @@ csharp_library(
             framework("nuget", "System.Threading.Tasks.Extensions"),
             framework("nuget", "System.Memory"),
             framework("nuget", "System.Text.Json"),
    +        framework("nuget", "System.Text.Encodings.Web"),
         ],
     )
     
    
    

    I'll be cutting a new rules_dotnet release soon and then the override in MODULE.bazel can be removed

    @nvborisenko
    Copy link
    Member Author

    That's great! I verified locally, and seems it works smoothly:

    image

    So, just waiting official release. Thank you @purkhusid!

    @nvborisenko
    Copy link
    Member Author

    Finally it is happening, thanks all participants!

    @nvborisenko nvborisenko merged commit 40bfc5e into SeleniumHQ:trunk Oct 7, 2024
    8 of 10 checks passed
    @nvborisenko nvborisenko deleted the dotnet-aot-seleniummanager branch October 7, 2024 18:26
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    2 participants