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

Make GraphQL scalar parsing compatible with variables #4522

Merged
merged 4 commits into from
Oct 12, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Make GraphQL scalar parsing compatible with variables
Our current graphql scalar parsing interacts poorly with the variables
support in the library.  Revise the parsing so it works corectly.

Signed-off-by: Danno Ferrin <danno.ferrin@gmail.com>
  • Loading branch information
shemnon committed Oct 12, 2022
commit fd6e07346bb592b4cb548f426d087e6248824d7b
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package org.hyperledger.besu.ethereum.api.graphql.internal;

import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity;

import graphql.language.IntValue;
Expand All @@ -28,156 +27,228 @@
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
import org.apache.tuweni.units.bigints.UInt256Value;

public class Scalars {

private static final Coercing<Object, Object> ADDRESS_COERCING =
new Coercing<Object, Object>() {
private Scalars() {}

private static final Coercing<Address, String> ADDRESS_COERCING =
new Coercing<Address, String>() {
Address convertImpl(final Object input) {
if (input instanceof Address) {
return (Address) input;
} else if (input instanceof Bytes) {
if (((Bytes) input).size() <= 20) {
return Address.wrap((Bytes) input);
} else {
return null;
}
} else if (input instanceof StringValue) {
String inputValue = ((StringValue) input).getValue();
if (inputValue != null) {
return Address.fromHexStringStrict(inputValue);
} else {
return null;
}
} else if (input instanceof String) {
return Address.fromHexStringStrict((String) input);
} else {
return null;
}
}

@Override
public String serialize(final Object input) throws CoercingSerializeException {
if (input instanceof Address) {
return input.toString();
Address result = convertImpl(input);
if (result != null) {
return result.toHexString();
} else {
throw new CoercingSerializeException("Unable to serialize " + input + " as an Address");
}
throw new CoercingSerializeException("Unable to serialize " + input + " as an Address");
}

@Override
public String parseValue(final Object input) throws CoercingParseValueException {
if (input instanceof Address) {
return input.toString();
public Address parseValue(final Object input) throws CoercingParseValueException {
Address result = convertImpl(input);
if (result != null) {
return result;
} else {
throw new CoercingParseValueException(
"Unable to parse variable value " + input + " as an Address");
}
throw new CoercingParseValueException(
"Unable to parse variable value " + input + " as an Address");
}

@Override
public Address parseLiteral(final Object input) throws CoercingParseLiteralException {
if (!(input instanceof StringValue)) {
throw new CoercingParseLiteralException("Value is not any Address : '" + input + "'");
}
String inputValue = ((StringValue) input).getValue();
try {
return Address.fromHexStringStrict(inputValue);
} catch (final IllegalArgumentException e) {
Address result = convertImpl(input);
if (result != null) {
return result;
} else {
throw new CoercingParseLiteralException("Value is not any Address : '" + input + "'");
}
}
};

private static final Coercing<Object, Object> BIG_INT_COERCING =
new Coercing<Object, Object>() {
private static final Coercing<String, String> BIG_INT_COERCING =
new Coercing<String, String>() {

String convertImpl(final Object input) {
if (input instanceof String) {
return (String) input;
} else if (input instanceof Bytes) {
return ((Bytes) input).toShortHexString();
} else if (input instanceof StringValue) {
return ((StringValue) input).getValue();
} else if (input instanceof IntValue) {
return UInt256.valueOf(((IntValue) input).getValue()).toShortHexString();
} else {
return null;
}
}

@Override
public String serialize(final Object input) throws CoercingSerializeException {
if (input instanceof UInt256Value) {
return ((UInt256Value) input).toShortHexString();
var result = convertImpl(input);
if (result != null) {
return result;
} else {
throw new CoercingSerializeException("Unable to serialize " + input + " as an BigInt");
}
throw new CoercingSerializeException("Unable to serialize " + input + " as an BigInt");
}

@Override
public String parseValue(final Object input) throws CoercingParseValueException {
if (input instanceof UInt256Value) {
return ((UInt256Value) input).toShortHexString();
var result = convertImpl(input);
if (result != null) {
return result;
} else {
throw new CoercingParseValueException(
"Unable to parse variable value " + input + " as an BigInt");
}
throw new CoercingParseValueException(
"Unable to parse variable value " + input + " as an BigInt");
}

@Override
public UInt256 parseLiteral(final Object input) throws CoercingParseLiteralException {
try {
if (input instanceof StringValue) {
return UInt256.fromHexString(((StringValue) input).getValue());
} else if (input instanceof IntValue) {
return UInt256.valueOf(((IntValue) input).getValue());
}
} catch (final IllegalArgumentException e) {
// fall through
public String parseLiteral(final Object input) throws CoercingParseLiteralException {
var result = convertImpl(input);
if (result != null) {
return result;
} else {
throw new CoercingParseLiteralException("Value is not any BigInt : '" + input + "'");
}
throw new CoercingParseLiteralException("Value is not any BigInt : '" + input + "'");
}
};

private static final Coercing<Object, Object> BYTES_COERCING =
new Coercing<Object, Object>() {
private static final Coercing<Bytes, String> BYTES_COERCING =
new Coercing<Bytes, String>() {

Bytes convertImpl(final Object input) {
if (input instanceof Bytes) {
return (Bytes) input;
} else if (input instanceof StringValue) {
return convertImpl(((StringValue) input).getValue());
} else if (input instanceof String) {
if (!Quantity.isValid((String) input)) {
throw new CoercingParseLiteralException(
"Bytes value '" + input + "' is not prefixed with 0x");
}
return Bytes.fromHexStringLenient((String) input);
} else {
return null;
}
}

@Override
public String serialize(final Object input) throws CoercingSerializeException {
if (input instanceof Bytes) {
return input.toString();
var result = convertImpl(input);
if (result != null) {
return result.toHexString();
} else {
throw new CoercingSerializeException("Unable to serialize " + input + " as an Bytes");
}
throw new CoercingSerializeException("Unable to serialize " + input + " as an Bytes");
}

@Override
public String parseValue(final Object input) throws CoercingParseValueException {
if (input instanceof Bytes) {
return input.toString();
public Bytes parseValue(final Object input) throws CoercingParseValueException {
var result = convertImpl(input);
if (result != null) {
return result;
} else {
throw new CoercingParseValueException(
"Unable to parse variable value " + input + " as an Bytes");
}
throw new CoercingParseValueException(
"Unable to parse variable value " + input + " as an Bytes");
}

@Override
public Bytes parseLiteral(final Object input) throws CoercingParseLiteralException {
if (!(input instanceof StringValue)) {
throw new CoercingParseLiteralException("Value is not any Bytes : '" + input + "'");
}
String inputValue = ((StringValue) input).getValue();
if (!Quantity.isValid(inputValue)) {
throw new CoercingParseLiteralException(
"Bytes value '" + inputValue + "' is not prefixed with 0x");
}
try {
return Bytes.fromHexStringLenient(inputValue);
} catch (final IllegalArgumentException e) {
var result = convertImpl(input);
if (result != null) {
return result;
} else {
throw new CoercingParseLiteralException("Value is not any Bytes : '" + input + "'");
}
}
};

private static final Coercing<Object, Object> BYTES32_COERCING =
new Coercing<Object, Object>() {
private static final Coercing<Bytes32, String> BYTES32_COERCING =
new Coercing<Bytes32, String>() {

Bytes32 convertImpl(final Object input) {
if (input instanceof Bytes32) {
return (Bytes32) input;
} else if (input instanceof Bytes) {
if (((Bytes) input).size() <= 32) {
return Bytes32.leftPad((Bytes) input);
} else {
return null;
}
} else if (input instanceof StringValue) {
return convertImpl((((StringValue) input).getValue()));
} else if (input instanceof String) {
if (!Quantity.isValid((String) input)) {
throw new CoercingParseLiteralException(
"Bytes32 value '" + input + "' is not prefixed with 0x");
} else {
return Bytes32.fromHexStringLenient((String) input);
}
} else {
return null;
}
}

@Override
public String serialize(final Object input) throws CoercingSerializeException {
if (input instanceof Hash) {
return ((Hash) input).toString();
}
if (input instanceof Bytes32) {
return input.toString();
var result = convertImpl(input);
if (result == null) {
throw new CoercingSerializeException("Unable to serialize " + input + " as an Bytes32");
} else {
return result.toHexString();
}
throw new CoercingSerializeException("Unable to serialize " + input + " as an Bytes32");
}

@Override
public String parseValue(final Object input) throws CoercingParseValueException {
if (input instanceof Bytes32) {
return input.toString();
public Bytes32 parseValue(final Object input) throws CoercingParseValueException {
var result = convertImpl(input);
if (result == null) {
throw new CoercingParseValueException(
"Unable to parse variable value " + input + " as an Bytes32");
} else {
return result;
}
throw new CoercingParseValueException(
"Unable to parse variable value " + input + " as an Bytes32");
}

@Override
public Bytes32 parseLiteral(final Object input) throws CoercingParseLiteralException {
if (!(input instanceof StringValue)) {
throw new CoercingParseLiteralException("Value is not any Bytes32 : '" + input + "'");
}
String inputValue = ((StringValue) input).getValue();
if (!Quantity.isValid(inputValue)) {
throw new CoercingParseLiteralException(
"Bytes32 value '" + inputValue + "' is not prefixed with 0x");
}
try {
return Bytes32.fromHexStringLenient(inputValue);
} catch (final IllegalArgumentException e) {
var result = convertImpl(input);
if (result == null) {
throw new CoercingParseLiteralException("Value is not any Bytes32 : '" + input + "'");
} else {
return result;
}
}
};

private static final Coercing<Object, Object> LONG_COERCING =
new Coercing<Object, Object>() {
private static final Coercing<Number, Number> LONG_COERCING =
new Coercing<Number, Number>() {
@Override
public Number serialize(final Object input) throws CoercingSerializeException {
if (input instanceof Number) {
Expand Down Expand Up @@ -210,7 +281,7 @@ public Number parseValue(final Object input) throws CoercingParseValueException
}

@Override
public Object parseLiteral(final Object input) throws CoercingParseLiteralException {
public Number parseLiteral(final Object input) throws CoercingParseLiteralException {
try {
if (input instanceof IntValue) {
return ((IntValue) input).getValue().longValue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ public static Collection<String> specs() {
specs.add("graphql_tooComplex");
specs.add("graphql_tooComplexSchema");

specs.add("graphql_variable_address");
specs.add("graphql_variable_bytes");
specs.add("graphql_variable_bytes32");
specs.add("graphql_variable_Long");

return specs;
}

Expand All @@ -128,7 +133,13 @@ private void graphQLCall(final String name) throws IOException {
EthGraphQLHttpBySpecTest.class.getResource(testSpecFile), Charsets.UTF_8);
final JsonObject spec = new JsonObject(json);
final String rawRequestBody = spec.getString("request");
final RequestBody requestBody = RequestBody.create(rawRequestBody, GRAPHQL);
final String rawVariables = spec.getString("variables");
final RequestBody requestBody =
rawVariables == null
? RequestBody.create(rawRequestBody, GRAPHQL)
: RequestBody.create(
"{ \"query\":\"" + rawRequestBody + "\", \"variables\": " + rawVariables + "}",
JSON);
final Request request = new Request.Builder().post(requestBody).url(baseUrl).build();

importBlocks(1, BLOCKS.size());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"variables": "{ \"address\": \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\"}",
"request": "query getAddressBalance($address :Address!) { pending { account(address:$address) { balance} } }",
"response": {
"data": {
"pending": {
"account": {
"balance": "0x140"
}
}
}
},
"statusCode": 200
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"variables" : "{ \"data\": \"0xf86d0485174876e800830222e0945aae326516b4f8fe08074b7e972e40a713048d62880de0b6b3a7640000801ba05d4e7998757264daab67df2ce6f7e7a0ae36910778a406ca73898c9899a32b9ea0674700d5c3d1d27f2e6b4469957dfd1a1c49bf92383d80717afc84eb05695d5b\"}",
"request" : "mutation postTransaction($data: Bytes!) { sendRawTransaction(data: $data) }",
"response":{
"data" : {
"sendRawTransaction" : "0xbaabcc1bd699e7378451e4ce5969edb9bdcae76cb79bdacae793525c31e423c7"
}
},
"statusCode": 200
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"variables": "{ \"hash\": \"0xc8df1f061abb4d0c107b2b1a794ade8780b3120e681f723fe55a7be586d95ba6\"}",
"request": "query getBlock($hash :Bytes32!) { block(hash:$hash) { number } }",
"response": {
"data": {
"block": {
"number": 30
}
}
},
"statusCode": 200
}
Loading