Code Monkey home page Code Monkey logo

nodes's Introduction

Nodes

A GraphQL JVM Client - Java, Kotlin, Scala, etc.

Build Status Coverage Status Download

Nodes is a GraphQL client designed for constructing queries from standard model definitions. Making this library suitable for any JVM application that wishes to interface with a GraphQL service in a familiar way - a simple, flexible, compatible, adoptable, understandable library for everyone!

The Nodes library is intended to be used in a similar fashion to other popular API interfaces so that application architecture can remain unchanged whether interfacing with a REST, SOAP, GraphQL, or any other API specification. A request entity is built for handling request specific parameters; a template is used for executing that request and mapping the response to a response entity; a response entity is used to gather the results.

Installing

Currently the library is hosted on bintray. This can be added to your installation repositories as demonstrated below.

Maven

<repositories>
    <repository>
        <id>bintray-americanexpress-maven</id>
        <url>https://dl.bintray.com/americanexpress/maven</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
      <groupId>io.aexp.nodes.graphql</groupId>
      <artifactId>nodes</artifactId>
      <version>latest</version>
    </dependency>
</dependencies>

Gradle

repositories {
    maven {
        url 'https://dl.bintray.com/americanexpress/maven/'
    }
}

dependencies {
    compile 'io.aexp.nodes.graphql:nodes:latest'
}

Replace latest with the desired version to install. The versions available for installing can be found in the git tags, using semantic versioning.

Usage

GraphQLTemplate graphQLTemplate = new GraphQLTemplate();

GraphQLRequestEntity requestEntity = GraphQLRequestEntity.Builder()
    .url("http://graphql.example.com/graphql")
    .variables(new Variable<>("timeFormat", "MM/dd/yyyy"))
    .arguments(new Arguments("path.to.argument.property",
        new Argument<>("id", "d070633a9f9")))
    .scalars(BigDecimal.class)
    .request(SampleModel.class)
    .build();
GraphQLResponseEntity<SampleModel> responseEntity = graphQLTemplate.query(requestEntity, SampleModel.class);

Annotations to configure fields

@GraphQLArgument(name="name", value="defaultVal", type="String", optional=false)

Used above property fields
name (required): GraphQL argument name
value (optional): default value to set the argument to
type (optional): how to parse the optional value. Accepts an enum of "String", "Boolean", "Integer", or "Float" - defaults to be parsed as a string.
optional (optional): set to true if the value is optional and should be left out if value is null - defaults to false.
You can specify fields arguments directly inline using this. This is good for static field arguments such as result display settings, for instance the format of a date or the locale of a message.

example:

    ...
    @GraphQLArgument(name="isPublic", value="true", type="Boolean")
    private User user;
    ...

result:

query {
    ...
    user(isPublic: true) {
        ...

@GraphQLArguments({@GraphQLArgument})

Used above property fields
Annotation for allowing mutliple default argument descriptions on one field

example:

    ...
    @GraphQLArguments({
        @GraphQLArgument(name="isPublic", value="true", type="Boolean"),
        @GraphQLArgument(name="username")
    })
    private User user;
    ...

result:

query {
    ...
    user(isPublic: true, username: null) {
        ...

@GraphQLIgnore

Used above property fields
Annotation to ignore the field when constructing the schema

example:

    ...
    @GraphQLIgnore
    private User user;
    ...

result:

query {
    ...

@GraphQLProperty(name="name", arguments={@GraphQLArgument(...)})

Used above property and class fields
name (required): GraphQL schema field name, the property's field name will be used as the alias
arguments (optional): arguments for the specified graphQL schema.
When used above property fields the annotation simply aliases that field, but when used above class fields it will replace the defined class name.

example:

    ...
    @GraphQLProperty(name="myFavoriteUser", arguments={
        @GraphQLArgument(name="username", value="amex")
    })
    private User user;
    ...

result:

query {
    ...
    myFavoriteUser: user(username: "amex") {
        ...

@GraphQLVariable(name="name", scalar="Float!")

Used above property fields
name (required): GraphQL variable name
type (required): GraphQL scalar type. Including optional and required parsing (!)
This is good for sharing the same variables across multiple input parameters in a query.

example:

    ...
    @GraphQLVariable(name="isPublic", scalar="Boolean")
    private User user;
    ...

result:

query($isPublic: Boolean) {
    ...
    user(isPublic: $isPublic) {
        ...

@GraphQLVariables({@GraphQLVariable})

Used above property fields
Annotation for allowing mutliple variables for a given field.

example:

    ...
    @GraphQLVariables({
        @GraphQLVariable(name="isPublic", scalar="Boolean"),
        @GraphQLVariable(name="username", scalar="String!")
    })
    private User user;
    ...

result:

query($isPublic: Boolean, $username: String!) {
    ...
    user(isPublic: $isPublic, username: $username) {
        ...

Terminology

All language found in this library is aimed to align with the language used in the GraphQL specification. An example of this is GraphQL vs GraphQl, where in this library the GraphQL specification is favored over the Java standard.


Contributing

We welcome Your interest in the American Express Open Source Community on Github. Any Contributor to any Open Source Project managed by the American Express Open Source Community must accept and sign an Agreement indicating agreement to the terms below. Except for the rights granted in this Agreement to American Express and to recipients of software distributed by American Express, You reserve all right, title, and interest, if any, in and to Your Contributions. Please fill out the Agreement.

Please feel free to open pull requests and see CONTRIBUTING.md for commit formatting details.

License

Any contributions made under this project will be governed by the Apache License 2.0.

Code of Conduct

This project adheres to the American Express Community Guidelines. By participating, you are expected to honor these guidelines.

nodes's People

Contributors

bpillai avatar chemdrew avatar fupduck avatar magp3 avatar mkalen avatar mrchass avatar realityforge avatar schawla80 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nodes's Issues

cache requests

Requests are being built through reflection on every transaction. Adding the ability to cache the requests upon completion and using already built requests in future transactions could improve the performance

how to map a list of fields via this library

I was testing out this library via the Spotify test API : https://spotify-graphql-server.herokuapp.com/graphql

The query would be

query ($byName: String) {
  queryArtists(byName: $byName) {
    image
    albums {
      image
      name
      id
      tracks {
        preview_url
        name
        id
      }
    }
    name
    id
  }
}

with variables byName : "Red Hot Chili Peppers"

Debugging shows it sending the right query and data back but it fails to Deserliase into a structure

Exception in thread "main" GraphQLException{message='OK', status='200', description='Unrecognized field "queryArtists" (class com.atlassian.model.Query), not marked as ignorable (0 known properties: ])
 at [Source: (String)"{"queryArtists":[{"image":"https://i.scdn.co/image/5b2072e522bf3324019a8c2dc3db20116dff0b87","albums":[{"image":"https://i.scdn.co/image/8deed63f89e0a215e90de1ee5809780921a47747","name":"The Getaway","id":"43otFXrY0bgaq5fB3GrZj6"},{"image":"https://i.scdn.co/image/66ab66e38e40a9202e8417a0b59ad86a210637a7","name":"I'm With You","id":"5wZtSIvijWCMc1vlPFqAyB"},{"image":"https://i.scdn.co/image/fd62b1f4f697284a024784706949bff3a6e1a27e","name":"Stadium Arcadium","id":"7xl50xr9NDkd3i2kBbzsNZ"},{"image"[truncated 3654 chars]; line: 1, column: 18] (through reference chain: io.aexp.nodes.graphql.Wrapper["data"]->com.atlassian.model.Query["queryArtists"])', errors=null}
	at io.aexp.nodes.graphql.Fetch.send(Fetch.java:96)
	at io.aexp.nodes.graphql.GraphQLTemplate.execute(GraphQLTemplate.java:99)
	at io.aexp.nodes.graphql.GraphQLTemplate.query(GraphQLTemplate.java:58)
	at com.atlassian.Main.main(Main.java:23)

My code is this

public static void main(String[] args) throws MalformedURLException {
        GraphQLTemplate graphQLTemplate = new GraphQLTemplate();

        GraphQLRequestEntity requestEntity = GraphQLRequestEntity.Builder()
                .url("https://spotify-graphql-server.herokuapp.com/graphql")
                .variables(new Variable("byName", "Red Hot Chili Peppers"))
                .scalars(BigDecimal.class)
                .request(Query.class)
                .build();
        GraphQLResponseEntity<Query> responseEntity = graphQLTemplate.query(requestEntity, Query.class);
        System.out.println(responseEntity);
    }

My model classes are

public class Query {

    @GraphQLVariable(name = "byName", scalar = "String")
    List<Artist> queryArtists;
}

public class Artist {
    String name;
    String id;
    String image;
    List<Album> albums;
}


public class Album {
    String name;
    String id;
    String image;
    //List<Track> tracks;
}

public class Track {
    String name;
    //List<Artist> artists;
    String preview_url;
    String id;
}

I guess I am unsure about where the entry point for queries are. The samples in the library only every enter a single value field (eg User via github /user) and not a list field like above

Am I off track on how this gets set up?

Array of Objects/InputObjects inside of an InputObject

E.g.

{
  currency: "USD",
  totalAmount: 10.00,
  items: [{
    name: "test1",
    currency: "USD",
    price: 5.00
  }, {
    name: "test2",
    currency: "USD",
    price: 5.00
  }]
}

I've tried it with the code below but items are not generating properly.

InputObject item1 = new InputObject.Builder<>()
        .put("name", "test1")
        .put("currency", "USD")
        .put("price", 5.00)
        .build();
InputObject item2 = new InputObject.Builder<>()
        .put("name", "test2")
        .put("currency", "USD")
        .put("price", 5.00)
        .build();

List<InputObject> items = new ArrayList<>();
items.add(item1);
items.add(item2);

InputObject input = new InputObject.Builder<>()
        .put("currency", "USD")
        .put("totalAmount", 10.00)
        .put("items", items)
        .build();

Actual Output:

mutation { createPayment (input:{currency:"USD",totalAmount:10.00,items:[io.aexp.nodes.graphql.InputObject@74a0c8e4,io.aexp.nodes.graphql.InputObject@82a0c0a5]}) { paymentId } }

Expected Output:

mutation { createPayment (input:{currency:"USD",totalAmount:10.00,items:[{name:"test1",currency:"USD",price:5.00},{name:"test2",currency:"USD",price:5.00}]}) { paymentId } }

Thanks.

Variables not taken into account - Need help (or bug)

Hi,
My variables don't seem to be taken into account when executing the query

List<Variable> myVars = new ArrayList<Variable>();
myVars.add(new Variable<String>("name", "text"));
myVars.add(new Variable<String>("language", "en"));

GraphQLRequestEntity requestEntity = GraphQLRequestEntity.Builder()
			.url(TestGlobalConfiguration.getGraphQlUrl())
			.variables(myVars).request(Jcr.class)
			.build();

GraphQLTemplate graphQLTemplate = new GraphQLTemplate();
GraphQLResponseEntity<Jcr> responseEntity = graphQLTemplate.query(requestEntity, Jcr.class);

In the Model I have defined, one of the property has the variables definition:

@GraphQLVariables({@GraphQLVariable(name = "name", scalar = "String"),
@GraphQLVariable(name = "language", scalar = "String")})
private Property property;

However when executing the request I can see the query is:

query ($name:String,$language:String){ jcr { nodeByPath { marketingFactory { optimizationTestVariant { path name property (name:$name,language:$language) { value } uuid } personalizedVariant { path name property (name:$name,language:$language) { value } uuid } } name uuid } } }

Which obviously returns:
Validation error of type VariableTypeMismatch: Variable type doesn't match since $name is not the expected "text"

I used to use GraphQLArguments, which worked fine but when you have the same arguments at two different places it is recommended to use Variables.
What am I doing wrong?

Invalid charset during deserialization on JVM with default encoding other than UTF-8

io.aexp.nodes.graphql.Fetch uses a Java InputStreamReader without explicit charset. If Nodes is running under a JVM with default encoding other than UTF-8 (i.e. under Windows), the response deserialization will not handle the JSON strings in the UTF-8 server response correctly since the deserialization is based on the stream reader with JVM default charset (!= UTF-8 i.e. different from actual response). Thus any string values with extended characters will contain invalid/mis-encoded values. E.g. "Martin Kalรฉn" will be deserialized as "Martin Kalรƒยฉn".

How to deserialize a list?

I've tried to run simple GraphQL query returning a list:
query { allTeams { name } }
Here is the DTO class that I use to build a GraphQLRequestEntity:

@GraphQLProperty(name = "allTeams")
public class AllTeamsQuery {
    String name;
    @GraphQLIgnore
    @GraphQLProperty(name="allTeams")
    List<TeamQuery> allTeams;
    ... getters and setters

The GraphQL query is good and run successfully against the server. However the result is not deserialized properly: Cannot deserialize instance of io.superherosample.model.AllTeamsQuery out of START_ARRAY token which seems to be a Jackson problem.

I guess ST is wrong in my DTO class. I've tried many options and I'm totally blocked. It would be nice that the documentation provides some examples on how to fetch lists.

gradle pull

Can anyone help me with gradle pull request. My gralde projects is not dowloading the nodes jar file.
I added below entry in my gradle build file. Is this the right version?

compile 'io.aexp.nodes.graphql:nodes:0.1.0'

I am not finding anything in Maven repo also.

graphqlproperty annotation is being mixed and shuffled around with the java field in the request

`@GraphQLProperty(name = "business")
public class MerchantFinancingLoanSearchBusiness {

String legalBusinessName;

@GraphQLProperty(name = "federalTaxId")
String tin;

@GraphQLProperty(name = "businessName")
String DBA;`

gives you:

business { DBA : businessNametin : federalTaxIdlegalBusinessName }

Here is the request builder

GraphQLRequestEntity requestEntity = GraphQLRequestEntity.Builder().headers(uriHeaders) .request(MerchantFinancingLoanSearchLoanAccounts.class) .arguments((new Arguments("findLoanAccounts", new Argument("start", Integer.parseInt(loanAccountsSearchRequest.getOffset())), new Argument("end", Integer.parseInt(loanAccountsSearchRequest.getLimit())), new Argument("criteria", criteriaInput)))) .url(configData.getProperty("loanAccountssearch.url", "")).build();

Could GraphQLRequestEntity make setRequestMethod as public method?

Our team use this framework to compose query of GraphQL API call. I saw setRequestMethod method in GraphQLRequestEntity is access only package and GraphQLTemplate call it and change request method. For our program, we use Feign+Hystrix as http call. That means we only use Nodes to compose query.
For my understanding, it is not enough to change setRequestMethod as public method. All initial process finish in GraphQLRequestEntity constructor method.
could expose set up requestMethod (query or mutate) in builder?

Array of Objects/InputObjects inside of an InputObject

E.g.

{
  currency: "USD",
  totalAmount: 10.00,
  items: [{
    name: "test1",
    currency: "USD",
    price: 5.00
  }, {
    name: "test2",
    currency: "USD",
    price: 5.00
  }]
}

I've tried the code below but items are not being generated properly.

InputObject item1 = new InputObject.Builder<>()
        .put("name", "test1")
        .put("currency", "USD")
        .put("price", 5.00)
        .build();
InputObject item2 = new InputObject.Builder<>()
        .put("name", "test2")
        .put("currency", "USD")
        .put("price", 5.00)
        .build();

List<InputObject> items = new ArrayList<>();
items.add(item1);
items.add(item2);

InputObject input = new InputObject.Builder<>()
        .put("currency", "USD")
        .put("totalAmount", 10.00)
        .put("items", items)
        .build();

Actual Output:

mutation { createPayment (input:{currency:"USD",totalAmount:10.00,items:[io.aexp.nodes.graphql.InputObject@74a0c8e4,io.aexp.nodes.graphql.InputObject@82a0c0a5]}) { paymentId } }

Expected Output:

mutation { createPayment (input:{currency:"USD",totalAmount:10.00,items:[{name:"test1",currency:"USD",price:5.00},{name:"test2",currency:"USD",price:5.00}]}) { paymentId } }

Thanks.

Exception when use gzip compression

I use io.aexp.nodes.graphql.nodes version: 0.5.0 for graphQL requests(java client).
While I send requests without header Accept-Encoding = gzip it works fine. But if I add this header graphQLTemplate throws next exception:
Exception in thread "main" GraphQLException{message='OK', status='200', description='Illegal character ((CTRL-CHAR, code 31)): only regular white space (\r, \n, \t) is allowed between tokens at [Source: (BufferedReader); line: 1, column: 2]', errors=null} at io.aexp.nodes.graphql.Fetch.send(Fetch.java:96) at io.aexp.nodes.graphql.GraphQLTemplate.execute(GraphQLTemplate.java:99) at io.aexp.nodes.graphql.GraphQLTemplate.query(GraphQLTemplate.java:58)

How I have to use gzip compression?

Exception Cannot deserialize instance of `Class` out of START_ARRAY token

Hi,

I am trying to use Nodes to query my GraphQL server; this is the query,

GraphQLTemplate graphQLTemplate = new GraphQLTemplate();

GraphQLRequestEntity requestEntity = GraphQLRequestEntity.Builder()
            .url("http://localhost:32806/graphql")
            .arguments(new Arguments("dishes", new Argument<String>("chef_id", "chef_1")), new Arguments("dishes", new Argument<String>("name", "Pizza")))
            .request(Dish.class)
            .build();
System.out.println(requestEntity.getRequest());
GraphQLResponseEntity<Dish> responseEntity = graphQLTemplate.query(requestEntity, Dish.class);

This is the model,

@GraphQLProperty(name="dishes", arguments = {@GraphQLArgument(name = "chef_id"), @GraphQLArgument(name = "dish_id")})
public class Dish
{
   
    private String chef_id;
    private String dish_id;
    private String name;
    private String description;

...getters and setters...
}

This is the schema,

dishes(chef_id: String! dish_id: String name: String limit: Int = 100 offset: Int = 0 columnToSort: SortDishColumn = NAME order: OrderBy = DESCENDING): [Dish]!

The query that Nodes generates is,

query { dishes (chef_id:"chef_1", name:"Pizza") { chef_id name description dish_id } }

The query does work when used with graphiql, and it is exactly what I am expecting from it. However, the code throws an exception every time,

Exception in thread "main" GraphQLException{message='null', status='200', description='Cannot deserialize instance of `org.[... my addition].models.Dish` out of START_ARRAY token
 at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: io.aexp.nodes.graphql.Wrapper["data"])', errors=null}
	at io.aexp.nodes.graphql.Fetch.send(Fetch.java:84)
	at io.aexp.nodes.graphql.GraphQLTemplate.execute(GraphQLTemplate.java:83)
	at io.aexp.nodes.graphql.GraphQLTemplate.query(GraphQLTemplate.java:42)

Any idea of what I am doing wrong?

Thank you for your help.

Externalize Fetching

I'm interested in this project but have a lot invested in RestTemplate and proprietary infra below that. I would need to be able to provide my own Fetch implementation for this to be usable.

How to response pack as class

mutation {
createUser(
userData: {
firstName: "Bullhorn", lastName: "Pegas"

}) {

user{

firstName
lastName
}
}
}

  • How to write this client in java? I am facing I couldn't pack the response user object. My code is below given

InputObject inputObject = new InputObject.Builder<>().put("firstName", "myname").put("lastName", "Sujith")
.build();
GraphQLRequestEntity build = GraphQLRequestEntity.Builder().url("http://192.168.0.182:8000/graphql/")
.requestMethod(GraphQLMethod.MUTATE)
.arguments(new Arguments("createUser", new Argument<>("userData", inputObject)))
.request(TestClass.class).build();
GraphQLTemplate graphQLTemplate = new GraphQLTemplate();
System.err.println("Sending request " + build.getRequest());
GraphQLResponseEntity execute = graphQLTemplate.mutate(build, TestResponse.class);

import io.aexp.nodes.graphql.annotations.GraphQLArgument;
import io.aexp.nodes.graphql.annotations.GraphQLProperty;
@getter
@Setter
@GraphQLProperty(arguments = {
@GraphQLArgument(name = "firstName"),
@GraphQLArgument(name = "lastName")
},name = "createUser")
public class TestResponse {
private String firstName;
private String lastName;
}

import io.aexp.nodes.graphql.annotations.GraphQLArgument;
import io.aexp.nodes.graphql.annotations.GraphQLProperty;

@GraphQLProperty(name = "createUser", arguments = { @GraphQLArgument(name = "userData") })
@getter
@Setter
public class TestClass {

private String firstName;
private String lastName;

}

HTTP to HTTPS

Enhancement: consider supporting HttpsURLConnection
javax.net.ssl.HttpsURLConnection
in order to request secure https API endpoints.

Mutation query returning Boolean throws Exception

I have a mutation query which returns a Boolean. The following POJO does not work:

@GraphQLProperty(name = "updateHealth", arguments = {@GraphQLArgument(name = "request")})
`public class UpdateHealth {
private boolean isSuccess;

public boolean isSuccess() {
	return isSuccess;
}

public void setSuccess(boolean isSuccess) {
	this.isSuccess = isSuccess;
}

}`

I get the following error: Validation error of type SubSelectionNotAllowed: Sub selection not allowed on leaf type Boolean of field updateHealth @ 'updateHealth''.

It generates the following query: query { updateHealth (request:{apiStatusDetails:[{apiPath:"/v1/sampleextensionapi/health",httpMethod:"GET",status:AVAILABLE},{apiPath:"/v1/sampleextensionapi/sampleresource/hello",httpMethod:"GET",status:AVAILABLE}],extName:"hello-world",status:AVAILABLE}) { isSuccess } }

On the graphql console, when I run the query WITHOUT {isSuccess} at the end, it works. How do I do it programmatically? Here is how I am setting things up:

`List apiDetails = new ArrayList();

for (ApiStatusDetails detail : response.getApiStatusDetails()) {
InputObject apiDetail = new InputObject.Builder().put("apiPath", detail.getApiPath())
.put("httpMethod", detail.getHttpMethod()).put("status", detail.getStatus()).build();
apiDetails.add(apiDetail);
}

InputObject request = new InputObject.Builder().put("extName", response.getExtensionName())
.put("status", response.getStatus()).put("apiStatusDetails", apiDetails).build();

GraphQLRequestEntity requestEntity = GraphQLRequestEntity.Builder()
.url("http://localhost:8443/v1/console/graphql")
.arguments(new Arguments("updateHealth", new Argument<>("request", request)))
.request(UpdateHealth.class)
.build();
System.out.println(requestEntity.getRequest());

GraphQLResponseEntity responseEntity = graphQLTemplate.mutate(requestEntity, UpdateHealth.class);`

Custom Error type

GraphQL servers can format the errors any way they want, defined here

Currently io.aexp.nodes.graphql.internal.Error is very rigid and only matches the default message and locations fields. It would be nice to make this class extensible so users can match the server's error format.

Support for Union

Do we have any support for using the Union in the response?

This is the sample query I am trying to fetch the response for .

searchABCs (pageSize:100,pageNum:1,query:"{}")
  { 
    totalCount
    results{
      ... on ABC {
        id
        name
        value
        abcInnerItem{
          id
          name 
        }
      }
    }
  } 


This is already tracked here:
#62

Please let me know if there is an alternative way of getting the response for this.

0.5.0 release?

Hey everyone,
sorry, but I couldn't find another way of contacting you than opening an issue.
Is there any plan for releasing a next version soon? Especially, I'm looking at the changes from PR #75 which I see as basically mandatory for any HTTP connection.
Cheers,
Ronny

Support InlineFragments

Currently there doesn't appear to be a way to support inline fragments, I am looking to do something like:

query {
  someInterface {
    ... on SomeType {
      field
    }
  }
}

I may have missed how to do this, I tried decorating a field with @GraphQLProperty but it gave me back something that looked far more like a field alias than an Inline Fragment.

Thoughts?

If this is missing and there is consensus on how to do this, I'd be happy to put together a PR!

Null Pointer When Adding Arguments

Context:

I'm trying to create a small demo project using this project to see if this fits our needs. It is nothing fancy just a bare bones app. The app just assembles the request object and prints it to the console.

Issue:

When trying to assemble add the arguments to the builder using the "GraphQLRequestEntity.Builder.arguments()" method I get a null pointer exception. The stack trace looks like this:

 java.lang.NullPointerException
        at io.aexp.nodes.graphql.GraphQLRequestEntity.setArguments(GraphQLRequestEntity.java:119)
        at io.aexp.nodes.graphql.GraphQLRequestEntity.<init>(GraphQLRequestEntity.java:62)
        at io.aexp.nodes.graphql.GraphQLRequestEntity$RequestBuilder.build(GraphQLRequestEntity.java:379)
        at App.main(App.java:36) 

I have included what I think are the correct annotations on the class I'm trying to query for. Below is the main method for my "app" and the first few lines of the model I'm using.
Is there something I'm doing wrong? Thanks in advance.

Code

Main class

`public class App {
public String getGreeting() {
return "Hello world.";
}

public static void main(String[] args) {
    try{
    System.out.println(new App().getGreeting());
    GraphQLTemplate template = new GraphQLTemplate();
    Argument<Fudge> ag= new Argument("thing", "d070633a9f9"); //<---- this throws a warning aobut argumant being a generic an i need to do a checked cast but it gets mad when I do that
    Arguments arguments= new Arguments("thing1.thing2.Fudge",ag);
    GraphQLRequestEntity requestEntity = GraphQLRequestEntity.Builder()
    .url("http://graphql.example.com/graphql")
    .arguments(arguments)//<---- this seems to be what fails. If i comment it out It works but I have no arguments
    .request(Fudge.class)
    .build();
System.out.println(requestEntity.getRequest());
System.out.println("no errors, this time");
}
    catch(Exception ex){ex.printStackTrace();
    }
}

}`

Model

@GraphQLProperty(name="fudge" 
    , arguments={
        @GraphQLArgument(name="id", type="String")
        ,@GraphQLArgument(name="thing", type="String")
        ,@GraphQLArgument(name="bar", type="String") 
        }
    )
public class Fudge {

    String id;
    String subjectLine;
    String body;
}
// everything below here is just getters and setters

@GraphQLVariables isn't a class level annotation

Unlike GraphQLProperty, GraphQLVariable(s) isn't class level -- I'm not sure how I'm suppose to use them to achieve my query/mutation.

I'm trying to build the following mutation

mutation Create2FA($inputDescriptor: Input2FA!) {
   Create2FA(inputDescriptor: $inputDescriptor) {
       TwoFAID
       Description
       ...
   }
}

Or A more general usage example for Queries:

query FindProducts($filter: InputFilter!) {
     FindProductsWithFilter(filter: $filter) {
        SKU
        Description
     }
}

Expected Uses

I was anticipating a model definition like (but illegal to put variables @ class level):

@GraphQLProperty(name = "Create2FA")
@GraphQLVariables({
    @GraphQLVariable(name = "TwoFA", scalar = "CreateTwoFA!")
})
public class MutationCreate2FA extends TwoFAModel {
}

Available Uses

These are my (legal) options but all provide the wrong result.

Test Case

package privoro.aid.authenticator.graphql;

import io.aexp.nodes.graphql.GraphQLRequestEntity;
import io.aexp.nodes.graphql.Variable;
import org.junit.Test;
import org.privoro.aid.authenticator.graphql.InputCreate2FA;
import org.privoro.aid.authenticator.graphql.MutationCreate2FA;

import java.net.MalformedURLException;

import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;

public class TestGraphQLRequestEntity {
    private String EXAMPLE_URL = "https://graphql.example.com";
    @Test
    public void mutationWithVariables() throws MalformedURLException {
        InputCreate2FA input = new InputCreate2FA();
        input.setUserID("user-id");
        input.setDeviceID("device-id");
        input.setRelyingPartyID("relying-party-id");
        input.setDescription("description");

        GraphQLRequestEntity requestEntity = GraphQLRequestEntity.Builder()
            .url(EXAMPLE_URL)
            .variables(new Variable<InputCreate2FA>("TwoFA", input))
            .request(MutationCreate2FA.class)
            .build();
        System.out.println("Request Entity:" + requestEntity.toString());
        assertThat(requestEntity.toString(), containsString("'query ($TwoFA: CreateTwoFA!) { Create2FA (TwoFA: $TwoFA) {"));
    }
}

Input variable model

package org.privoro.aid.authenticator.graphql;

public class InputCreate2FA {
    public String UserID;
    public String RelyingPartyID;
    public String Description;
    public String DeviceID;
}

Result Model

package org.privoro.aid.authenticator.graphql;

public class TwoFA {
    public String UserID;
    public String RelyingPartyID;
    public String Description;
    public String Status;
    public String Reason;
    public String CreatedTimestamp;
    public String ExpiresAt;
    public String TwoFAID;
}
1 Property and Values set inside the class on the result model property

MutationCreate2FA

public class MutationCreate2FA {
@GraphQLProperty(name = "Create2FA")
@GraphQLVariables({
    @GraphQLVariable(name = "TwoFA", scalar = "CreateTwoFA!")
})
    public TwoFAModel twoFa;
}

Output:

query ($TwoFA:CreateTwoFA!){ twoFA : Create2FA(TwoFA:$TwoFA) { Status RelyingPartyID Description CreatedTimestamp UserID TwoFAID ExpiresAt Reason } } 
2 Property on the class but variables on a result model property

MutationCreate2FA

@GraphQLProperty(name = "Create2FA")
public class MutationCreate2FA {
@GraphQLVariables({
    @GraphQLVariable(name = "TwoFA", scalar = "CreateTwoFA!")
})
    public TwoFAModel twoFa;
}

Output:

query ($TwoFA:CreateTwoFA!){ Create2FA { twoFA (TwoFA:$TwoFA) { Status RelyingPartyID Description CreatedTimestamp UserID TwoFAID ExpiresAt Reason } } } 
3 Variables only specified when building request

MutationCreate2FA

@GraphQLProperty(name = "Create2FA", arguments = {
    @GraphQLArgument(name = "TwoFA", value = "$TwoFA")
})
public class MutationCreate2FA extends TwoFA {
}

Output (close, but the builder doesn't inject the variables):

query { Create2FA (TwoFA:$TwoFA) { Status RelyingPartyID Description CreatedTimestamp UserID TwoFAID ExpiresAt Reason } } 

Preferred usage

If I step back from the docs a little and imagine how I might specify query's and mutations with variables I come up with:

@GraphQLOperation(name = "Create2FAMutation", type="mutation", variables = {
   @GraphQLVariable(name = "inputCreate2FA", scalar = "Create2FA!")
}, properties = {
   @GraphQLProperty(name = "Create2FA", arguments = {
      @GraphQLArgument(name = "TwoFA", variable = "inputCreate2FA")
   })
})
class MutationCreate2FA extends TwoFA {

}

Output

mutation Create2FAMutation ($inputCreate2FA: Create2FA!) { Create2FA (TwoFA:$inputCreate2FA) { Status RelyingPartyID Description CreatedTimestamp UserID TwoFAID ExpiresAt Reason } } 

I think this preferred usage can be provided and be backwards compatible w/ current usage.

I've introduced an annotation @GraphQLOperation which is optional (if you don't need variables) in your query/mutation. This would remove the need for @GraphQLVariables (altogether) and @GraphQLVariable would only be used in top level operation annotations (not on models or on model properties).

I've also added an optional variable parameter to the @GraphQLArgument which associates the argument to a specified variable. The addition allows properties to be defined as arguments that can be bound to variables later which reduces unwanted coupling from the model to the operation.

Query for same field multiple times

query {
    myUser: user(username: "visa") { ... }
    myFavoriteUser: user(username: "amex") { ... }
}

If i were to have a query like the one above, how is it possible to implement it with Nodes?

Testing Input Object .

Currently in the client since we have protected access over the methods in InputObject it becomes impossible to test an argument that has an input object as the value .

Support ability to make a dynamic request

Seems like there are only two ways to build a request, one thru a predefined class and the other thru a raw query string.

Could you add support for java.util.Map requests?

Such as:

GraphQLTemplate graphQLTemplate = new GraphQLTemplate();

Map<String, Property> request = new HashMap<>();
request.put("someField", Property.Builder().arguments(..).build());

GraphQLRequestEntity requestEntity = GraphQLRequestEntity.Builder()
    .url("http://graphql.example.com/graphql");
    .variables(new Variable("timeFormat", "MM/dd/yyyy"))
    .arguments(new Arguments("path.to.argument.property",
        new Argument("id", "d070633a9f9")))
    .request(request)
    .build();
GraphQLResponseEntity<Map> responseEntity = graphQLTemplate.query(requestEntity, Map.class);

Cannot deserialize value of type `java.time.LocalDateTime` from String \"2020-01-30T13:42:34.400\

@chemdrew @realityforge
Hi,

Im trying to use the GraphQL API and we have the need to deserialize a field that is in String to LocalDateTime and in that case ,we get the below exception

"message" : "Servlet.service() for servlet [dispatcherServlet] in context with path [] threw 
  exception [Request processing failed; nested exception is GraphQLException{message='null', 
 status='200', description='Cannot deserialize value of type `java.time.LocalDateTime` from 
String \"2020-01-30T13:42:34.400\": Failed to deserialize java.time.LocalDateTime: 
(java.time.format.DateTimeParseException) Text '2020-01-30T13:42:34.400' could not be 
parsed at index 10\n at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: 
  io.aexp.nodes.graphql.Wrapper[\"data\"]- 
   >com.ing.diba.scalable.domain.inbound.PortFolioResponse[\"realTimeValuation\"]- 
    >com.ing.diba.scalable.domain.inbound.RealTimeValuationResponse[\"dateTime\"])', 
     errors=null}] 

The code to invoke is

public GraphQLResponseEntity<EntityResponse> getEntity(Long 
 entityId) {
    GraphQLResponseEntity<EntityResponse> responseEntity = new 


    GraphQLRequestEntity requestEntity = null;
    try {
        requestEntity = GraphQLRequestEntity.Builder()
                .url(portFolioOverViewProperties.getConnectionUrl())
                .request(Entity.class)
                .arguments(new Arguments("entityOverview", new Argument("id", entityId)))
                .build();
    } catch (MalformedURLException e) {
        log.error("MalFormed URL {}", e);
        throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, 
    e.getMessage(), e);
    }
    responseEntity = graphQLTemplate.query(requestEntity, Entity.class);
    return responseEntity;
}

On debugging we found that the exception occurs from the deserialize method of
LocalDateTimeDeserializer class of jacksonjar at highlighted line

   public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws 
         IOException {
    if (parser.hasTokenId(6)) {
        String string = parser.getText().trim();
        if (string.length() == 0) {
            return !this.isLenient() ? (LocalDateTime)this._failForNotLenient(parser, context, 
   JsonToken.VALUE_STRING) : null;
        } else {
            try {
                if (this._formatter == DEFAULT_FORMATTER && string.length() > 10 && 
  string.charAt(10) == 'T') {
                    return string.endsWith("Z") ? LocalDateTime.ofInstant(Instant.parse(string), 
  ZoneOffset.UTC) : LocalDateTime.parse(string, DEFAULT_FORMATTER);
                } **else {
                 return LocalDateTime.parse(string, this._formatter);
                }**
            } catch (DateTimeException var12) {
                return (LocalDateTime)this._handleDateTimeException(context, var12, string);
            }
        }

But if we parse it locally we don't get the exception

   LocalDateTime responseDateTime = LocalDateTime.parse(realTimeValuation.getDateTime());

Can you let me know what is causing this issue? and so its possible to resolve it in our application

Static fields in model class

I reuse custom types in my model class, for example a class called CreditCardNumber. Static fields in this class are also send with the request. On server side, the validation failed because of that static field is not defined in the schema.

public class CreditCardNumber {
    private static final Pattern REGEX = Pattern.compile("\\d{12;19}");
    private String number;
    ...
}

Because I share my custom types across different projects, I'm not able to annote the static field with @GraphQLIgnore.

How should I handle this? Is it a bug, that static fields are in included? May Mixins as used by Jackson be a possible solution?

Facing issues while querying an API with certain fields but getting an array in response.

Hi I am trying to query an API which is of the following format.
query{ requestallstudentdata{ id name hobby } }
In response we get something like following:
`{
"data": {
"requestallstudentdata": [
{
"id": 1,
"name":"xyz",
"hobby":"fxdu"

  },
  {
  "id": 2,
    "name":"xYz",
    "hobby":"fdu"
  }
]

}
}`

How to handle such requests.Please assist.
Currently I have the following classes.

I am getting the following response whenever I am trying to send a list.

GraphQLException{message='OK', status='200', description='Can not deserialize instance of com.graphql.model.foo.StudentDetails out of START_ARRAY token
 at [Source: N/A; line: -1, column: -1] (through reference chain: io.aexp.nodes.graphql.Wrapper["data"])', errors=null}

Arguments on aliases

Hi, I'm trying to send a request on a model where I have at the same level two same nodes with identical names and arguments. So I used alias graphQLPropertyName. Problem is I cannot inject the arguments on request. In the given example I want to do:

GraphQLTemplate graphQLTemplate = new GraphQLTemplate();

GraphQLRequestEntity requestEntity = GraphQLRequestEntity.Builder()
    .url("http://graphql.example.com/graphql");
    .variables(new Variable("timeFormat", "MM/dd/yyyy"))
    .arguments(new Arguments("path.to.argument.property",
        new Argument("id", "toto")))
.arguments(new Arguments("path.to.argument.propertyWithDifferentAlias",
        new Argument("id", "titi")))
    .request(SampleModel.class)
    .build();
GraphQLResponseEntity<SampleModel> responseEntity = graphQLTemplate.query(requestEntity, SampleModel.class);

However the API does not validate my query (while it has the proper syntax). Here is a simplified error message:

Unrecognized field "propertyWithDifferentAlias" (class SampleModel), not marked as ignorable ( known properties: "property])

How can I inject "id" "titi" to my aliased property?

Customizable object mapper

I think, it would be great to be able to customize the objectmapper instance used in Fetch class. In my case i was missing jackson's java time module to use java time datatypes in my response entities.

Because Fetch class has a final class declaration i can't see any easy way to modify/replace its object mapper instance. This could be useful also in spring (boot) projects, where the environment provides a preconfigured object mapper and an easy way to provide a custom one.

For the moment i helped myself by using @JsonDeserialize annotation for java time properties of my response entities.

Unrecognized field error when attempting to capture References (`refs`) from GitHub GraphQL API v4

Requirement:
I am attempting to capture the branches of a GitHub repository using the GitHub GraphQL API v4. The following is an example GraphQL query for capturing the branches of repository wso2/docker-ei.

{
  organization(login: "wso2") {
    name
    url
    repository(name: "docker-ei") {
      refs(first: 10, refPrefix: "refs/heads/") {
        edges {
          node {
            name
          }
        }
      }
    }
  }
}

Current Status:
For this purpose, we need to capture the Git References (refs). I have managed to build up the Java implementation for building the following query, to capture the Git References (en route to creating the final query).

{
  organization(login: "wso2") {
    name
    repository(name: "docker-ei") {
      issue(number: 32) {
        title
      }
      refs(first: 6, refPrefix: "refs/heads/") {
        totalCount
      }
      name
    }
    url
  }
}

I have created three relevant Java classes (DTOs) Organization.java, Repository.java and Refs.java in order to build up the above query. The content of these classes are as follows:

Organization.java

@GraphQLProperty(name = "organization",
        arguments = {@GraphQLArgument(name = "login", type = "String")}
)
public class Organization {
    private String name;
    private String url;
    @GraphQLArguments({
            @GraphQLArgument(name = "name", type = "String")
    })
    private Repository repository;

    public String getName() {
        return name;
    }

    public String getUrl() {
        return url;
    }

    public Repository getRepository() {
        return repository;
    }
}

Repository.java

@GraphQLProperty(name = "repository")
public class Repository {
    private String name;

    @GraphQLArguments({
            @GraphQLArgument(name = "number", type = "Int")
    })
    private Issue issue;

    @GraphQLArguments({
            @GraphQLArgument(name = "first", type = "Int"),
            @GraphQLArgument(name = "refPrefix", type = "String")
    })
    private Refs refs;

    public String getName() {
        return name;
    }

    public Issue getIssue() {
        return issue;
    }

    public Refs getReferences() {
        return refs;
    }
}

Refs.java

@GraphQLProperty(name = "refs")
public class Refs {
    private int totalCount;

    public int getTotalCount() {
        return totalCount;
    }
}

The following code snippet depicts how I am executing the query:

        GraphQLRequestEntity requestEntity = null;

        try {
            requestEntity = GraphQLRequestEntity.Builder()
                    .headers(headers)
                    .url("https://api.github.com/graphql")
                    .request(Organization.class)
                    .arguments(
                            new Arguments("organization", new Argument("login", "wso2")),
                            new Arguments("organization.repository", new Argument("name", "docker-ei")),
                            new Arguments("organization.repository.issue", new Argument("number", 32)),
                            new Arguments("organization.repository.refs", new Argument("first", 6), new Argument("refPrefix", "refs/heads/"))
                    )
                    .build();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        GraphQLTemplate graphQLTemplate = new GraphQLTemplate();

        GraphQLResponseEntity<Organization> responseEntity = graphQLTemplate.query(requestEntity, Organization.class);

Issue:
But I am experiencing the following exception when executing the query (although refs is a valid field under repository):

Exception in thread "main" GraphQLException{message='OK', status='200', description='Unrecognized field "refs" (class org.wso2.carbon.wum.git.Repository), not marked as ignorable (2 known properties: "issue", "name"])
 at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: io.aexp.nodes.graphql.Wrapper["data"]->org.wso2.carbon.wum.git.Organization["repository"]->org.wso2.carbon.wum.git.Repository["refs"])', errors=null}
	at io.aexp.nodes.graphql.Fetch.send(Fetch.java:84)
	at io.aexp.nodes.graphql.GraphQLTemplate.execute(GraphQLTemplate.java:83)
	at io.aexp.nodes.graphql.GraphQLTemplate.query(GraphQLTemplate.java:42)
	at org.wso2.carbon.wum.git.GraphQLRequestSender.main(GraphQLRequestSender.java:44)

During debugging it was identified that the GraphQL query is accurately created and when executed using the GitHub GraphQL v4 explorer, the desired results can be obtained.

Any help or suggestions in relation with fixing this issue, are highly appreciated.

StackOverflowError on enum members in model

If a model contains one or more enum members, GraphQLRequestEntity will loop in the getChildren method, resulting in java.lang.StackOverflowError. Only happens if the enum has at least two contant values.

Need Help

Looking for some help.. I am using this library into my project. and getting error while deserializing the response.
My query :
query somethingHere {
courses {
id
}
}
Response :
{
"data": {
"courses": [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
}
]
}
}

I have defined the class like this

@Setter
@getter
@GraphQLProperty(name = "courses")
public class Course {
int id;
}


@getter
@Setter
public class CourseRes{

private List courses;
}


try{

  final GraphQLRequestEntity requestEntity = GraphQLRequestEntity.Builder()
      .url("https://nimbus-service.herokuapp.com/courses")
      .request(Course.class)
      .build();
  GraphQLResponseEntity<CourseRes> tmp = new GraphQLTemplate().query(requestEntity, CourseRes.class);
  }catch(Exception e) {
  //throw new VirtualAssistantException(e.getMessage(), e);
  e.printStackTrace();
}

}

Getting below exception
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "courses" (class CourseRes), not marked as ignorable (0 known properties: ])
at [Source: (String)"{"courses":[{"id":1},{"id":2},{"id":3}]}"; line: 1, column: 13] (through reference chain: io.aexp.nodes.graphql.Wrapper["data"]->CourseRes["courses"])

Thanks

Not possible to call API with optional and mutually exclusive arguments

When using Nodes to call an API where arguments are mutually exclusive, there is currently no way to leave out either argument even if value is not set or explicitly set to null in annotation. This is due to two issues:
a) null values are treated as String("null") instead of null values internally, while parsing value from GraphQLArgument annotation
and
b) the GraphQLArgument annotation has no notion of "!"/optional argument or not, meaning that request generation can effectively never leave out any arguments.

An example is an electricity consumption API where parameters first or last is used to get the first or last N entries from a series of consumption values. The API returns nothing on the following conditions:
consumption(resolution: HOURLY, first: null, last: 100) or consumption(resolution: HOURLY, first: 100, last: null)
The query must be either of:
consumption(resolution: HOURLY, last: 100) or consumption(resolution: HOURLY, first: 100)

GraphQLTemplate possibly not thread safe

We made our GraphQLTemplate a singleton spring bean and noticed some erratic behavior (seemingly confusing the response returned from the server).

The docs suggest newing the template up each time so this is our bad, but can we ensure it is thread safe instead? It feels like it fits better with the other XYZTemplate tools you find in spring-land

A template has a handle on a Fetch:
https://github.com/americanexpress/nodes/blob/master/nodes/src/main/java/io/aexp/nodes/graphql/Fetch.java#L54

Which has objectMapper and simpleModule fields that are altered every time send() is called. I don't have a smoking gun but my hunch is there are some issues stemming from that - two threads have handles on the sample template/fetch but the mapper/modules are being swapped around underneath.

support subscriptions

To keep in parity with the GraphQL specification it would be great to add support for subscriptions

Support for recursive relationships

Hi! Thank you for developing this client!

Is there any way to use nodes with recursive relationships? For instance, is it possible to generate the following query

{
  query {
    cars(id: 1) {
      id
      owner {
        cars {
          id
        }
      }
    }
  }
}

to list all cars owned by the owner of this car?

Currently the query-generation results in a stack overflow

GraphQLException in Kotlin sample

When running Main.kt in the kotlin-sample my coworker and I are both seeing the following error:

Exception in thread "main" GraphQLException{message='OK', status='200', description='Cannot construct instance of `models.User` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: io.aexp.nodes.graphql.Wrapper["data"])', errors=null}

README does not inform the user which repository this project is available from

Not familiar with the Java eco system, it was non obvious to me to try jcenter to find the dependency. It should probably be mentioned in the read me and included in the maven install examples.

Maven

<repositories>
    <repository>
            <id>jcenter - amex</id>
            <url>https://dl.bintray.com/americanexpress/maven/</url>
    </repository>
</repositories>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.