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] Parse response before deserialization #15268

Open
wants to merge 1 commit into
base: trunk
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 26 additions & 28 deletions dotnet/src/webdriver/Response.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,21 @@

using OpenQA.Selenium.Internal;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

#nullable enable

namespace OpenQA.Selenium
{
/// <summary>
/// Handles reponses from the browser
/// Handles responses from the browser
/// </summary>
public class Response
{
private static readonly JsonSerializerOptions s_jsonSerializerOptions = new()
{
TypeInfoResolver = ResponseJsonSerializerContext.Default,
Converters = { new ResponseValueJsonConverter() } // we still need it to make `Object` as `Dictionary`
};

/// <summary>
/// Initializes a new instance of the <see cref="Response"/> class
/// </summary>
Expand Down Expand Up @@ -82,18 +76,18 @@ public Response(string? sessionId, object? value, WebDriverResult status)
/// <exception cref="JsonException">If <paramref name="value"/> is not a valid JSON object.</exception>
public static Response FromJson(string value)
{
Dictionary<string, object?> rawResponse = JsonSerializer.Deserialize<Dictionary<string, object?>>(value, s_jsonSerializerOptions)
?? throw new WebDriverException("JSON success response returned \"null\" value");
JsonObject rawResponse = JsonNode.Parse(value) as JsonObject
?? throw new WebDriverException($"JSON success response did not return a dictionary{Environment.NewLine}{value}");

object? contents;
JsonNode? contents;
string? sessionId = null;

if (rawResponse.TryGetValue("sessionId", out object? s) && s is not null)
if (rawResponse.TryGetPropertyValue("sessionId", out JsonNode? s) && s is not null)
{
sessionId = s.ToString();
}

if (rawResponse.TryGetValue("value", out object? valueObj))
if (rawResponse.TryGetPropertyValue("value", out JsonNode? valueObj))
{
contents = valueObj;
}
Expand All @@ -107,7 +101,7 @@ public static Response FromJson(string value)

// Special-case for the new session command, where the "capabilities"
// property of the response is the actual value we're interested in.
if (rawResponse.TryGetValue("capabilities", out object? capabilities))
if (rawResponse.TryGetPropertyValue("capabilities", out JsonNode? capabilities))
{
contents = capabilities;
}
Expand All @@ -117,14 +111,14 @@ public static Response FromJson(string value)
}
}

if (contents is Dictionary<string, object?> valueDictionary)
if (contents is JsonObject valueDictionary)
{
// Special case code for the new session command. If the response contains
// sessionId and capabilities properties, fix up the session ID and value members.
if (valueDictionary.TryGetValue("sessionId", out object? session))
if (valueDictionary.TryGetPropertyValue("sessionId", out JsonNode? session))
{
sessionId = session.ToString();
if (valueDictionary.TryGetValue("capabilities", out object? capabilities))
if (valueDictionary.TryGetPropertyValue("capabilities", out JsonNode? capabilities))
{
contents = capabilities;
}
Expand All @@ -135,7 +129,9 @@ public static Response FromJson(string value)
}
}

return new Response(sessionId, contents, WebDriverResult.Success);
var contentsDictionary = JsonSerializer.Deserialize(contents, ResponseJsonSerializerContext.Default.Object);

return new Response(sessionId, contentsDictionary, WebDriverResult.Success);
}

/// <summary>
Expand Down Expand Up @@ -181,29 +177,30 @@ public WebDriverResult Status
/// <exception cref="WebDriverException">If the JSON dictionary is not in the expected state, per spec.</exception>
public static Response FromErrorJson(string value)
{
Dictionary<string, object?> deserializedResponse = JsonSerializer.Deserialize<Dictionary<string, object?>>(value, s_jsonSerializerOptions)
?? throw new WebDriverException("JSON error response returned \"null\" value");
JsonObject responseObject = JsonNode.Parse(value) as JsonObject
?? throw new WebDriverException($"JSON error response did not return an object{Environment.NewLine}{value}");

if (!deserializedResponse.TryGetValue("value", out object? valueObject))
if (!responseObject.TryGetPropertyValue("value", out JsonNode? valueNode))
{
throw new WebDriverException($"The 'value' property was not found in the response:{Environment.NewLine}{value}");
throw new WebDriverException($"The 'value' property was not found in the response{Environment.NewLine}{value}");
}

if (valueObject is not Dictionary<string, object?> valueDictionary)
if (valueNode is not JsonObject valueObject)
{
throw new WebDriverException($"The 'value' property is not a dictionary of <string, object>{Environment.NewLine}{value}");
throw new WebDriverException($"The 'value' property is not a dictionary{Environment.NewLine}{value}");
}

if (!valueDictionary.TryGetValue("error", out object? errorObject))
if (!valueObject.TryGetPropertyValue("error", out JsonNode? errorObject))
{
throw new WebDriverException($"The 'value > error' property was not found in the response:{Environment.NewLine}{value}");
throw new WebDriverException($"The 'value > error' property was not found in the response{Environment.NewLine}{value}");
}

if (errorObject is not string errorString)
if (errorObject is not JsonValue errorValue || !errorValue.TryGetValue(out string? errorString))
{
throw new WebDriverException($"The 'value > error' property is not a string{Environment.NewLine}{value}");
}

var valueDictionary = JsonSerializer.Deserialize(valueObject, ResponseJsonSerializerContext.Default.Object);
WebDriverResult status = WebDriverError.ResultFromError(errorString);

return new Response(sessionId: null, valueDictionary, status);
Expand Down Expand Up @@ -243,6 +240,7 @@ public override string ToString()
}
}

[JsonSerializable(typeof(Dictionary<string, object>))]
[JsonSerializable(typeof(object))]
[JsonSourceGenerationOptions(Converters = [typeof(ResponseValueJsonConverter)])] // we still need it to make `Object` as `Dictionary`
internal sealed partial class ResponseJsonSerializerContext : JsonSerializerContext;
}
Loading