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

Add @Namespace for GraphQLApi and GraphQLClientApi #2184

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

RoMiRoSSaN
Copy link
Contributor

This PR is only open to see what changes may be needed to changes suggestion by @t1 from this discussion #2173.
For viewing only, not for merge.

@RoMiRoSSaN RoMiRoSSaN mentioned this pull request Sep 6, 2024
Copy link
Collaborator

@t1 t1 left a comment

Choose a reason for hiding this comment

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

I like it!

Most of the changes are not from the @Name@Namespace change, but from using "namespace" instead of "group" and the fact that they can be nested.

request.append(method.getName());
if (method.hasValueParameters())

request.append(method.getOperationName()); // operation name
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
request.append(method.getOperationName()); // operation name
request.append(method.getOperationName());

Comment on lines 35 to 40
if (!namespaces.isEmpty()) {
namespaces.forEach(value -> {
request.append(" { ");
request.append(value);
});
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (!namespaces.isEmpty()) {
namespaces.forEach(value -> {
request.append(" { ");
request.append(value);
});
}
namespaces.forEach(namespace -> request.append(" { ").append(namespace)});

}

public String getOperationName() {
return namespaces.stream().map(this::prettyString).collect(joining()) + prettyString(getName());
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure about this. Why do we do this? And if we wouldn't uppercase the very first character, most changes to the tests would become unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For example

@Namespace("first/second/third/someMynotherName")
class Api {
   @Query("findAll")
   ////
}

If don create first letter to upper case, operation will firstsecondthirdsomeMynotherNamefindAll. it is unreadable.
And create it only findAll incorrect, becouse in code may be other findAll method. And if we will call just findAll, using some router (with analytics), it can break call analytics.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Okay, I think I get it: if the user wants to send multiple operations in one request, their names have to be distinct. But with the typesafe client, you can send multiple request only with a @Multiple annotation. The operation name is only scoped within a query string, not globally. I think we can safely skip this. Try to write a test to prove me wrong 😜
BTW, this code does uppercase the very first letter: FirstSecondThirdSomeMynotherName ... but the F could also remain f 😁

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When the Quarkus tests failed, I saw that Quarkus also checks the operation name.
And maybe it’s worth making sure that the operation name is formed like this:

  1. If there is @Name or @Namespace, capitalize the first letters.
  2. If not, leave it as is.

As a result, the quarkus build will not crash, and there will be a minimum changes in the tests.
As for @Multiple, I'll take a look today.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm obviously biased by the Java naming convention that methods start with a lowercase character. That's why I'd like to stick with that, also for operation names. I.e., also if there is a @Namespace annotation, the very first character should be lowercase. E.g., @Namespace("users") ... @Query List<User> all() {... would result in an operation name usersAll.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just referred to examples from the graphql documentation https://graphql.org/learn/queries/#operation-name
But of course I can change it to your example, no problem.

@@ -34,15 +36,19 @@ public QueryBuilder(MethodInfo method) {
public String build() {
StringBuilder request = new StringBuilder(method.getOperationTypeAsString());
request.append(" ");
request.append(method.getName()); // operationName

request.append(method.getOperationName()); // operation name
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
request.append(method.getOperationName()); // operation name
request.append(method.getOperationName());

Comment on lines 46 to 51
List<String> namespaces = method.getNamespaces();
if (!namespaces.isEmpty()) {
namespaces.forEach(value -> {
request.append(" { ");
request.append(value);
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as in the QueryBuilder.

I really look forward to get rid of this kind of code duplication when @mskacelik finishes his work.

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Experimental("Experimental namespaces on api class. Separate namespaces via '/' (example - admin/users)")
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would move the second sentence into Javadoc. And add a hint that the class name is being used as the default value.

return value.substring(0, 1).toUpperCase() + value.substring(1);
}

private void addNamespacedRootObject(GraphQLObjectType.Builder mutationBuilder,
Copy link
Collaborator

Choose a reason for hiding this comment

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

The parameters can also be for queries, can't they? Maybe better rename it to, e.g., operationBuilder/namespaceOperations/operationType?


GraphQLObjectType namedType = createNamedType(rootName, group, operations);
if (!groupContainer.getOperations().isEmpty() || !graphQLFieldDefinitions.isEmpty()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (!groupContainer.getOperations().isEmpty() || !graphQLFieldDefinitions.isEmpty()) {
if (groupContainer.getOperations().isEmpty() && graphQLFieldDefinitions.isEmpty()) {
return List.of();
}

Comment on lines 464 to +467
objectTypeBuilder = objectTypeBuilder.field(graphQLFieldDefinition);
}

objectTypeBuilder.fields(graphQLFieldDefinitions);
Copy link
Collaborator

Choose a reason for hiding this comment

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

isn't the second one unnecessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

here passed nested objects.
@Namespace("admin"), @Namespace("admin/users"). Each contains some operations. Here builds admin definition (with its opeartions), and in the next adds definitions 'users' and etc..


@GraphQLApi
@Namespace("first/second")
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is so much nicer! 😁

@RoMiRoSSaN
Copy link
Contributor Author

RoMiRoSSaN commented Sep 9, 2024

I don't like it in this form yet. I think it would be better to roll back the changes. And expand the existing code. To enable work with Namespace, you need to add a config to switch to working with them. And by default, use what is there. With this approach, Quarkus will not break. And it will be possible to refine it (add additional configs, checks) without any problems.

I will rewrite it

Roman Lovakov added 2 commits September 12, 2024 00:52
# Conflicts:
#	client/model-builder/src/main/java/io/smallrye/graphql/client/model/helper/OperationModel.java
@t1
Copy link
Collaborator

t1 commented Sep 12, 2024

I just wanted to repeat it: thanks, @RoMiRoSSaN. You're doing a great job!

@RoMiRoSSaN
Copy link
Contributor Author

RoMiRoSSaN commented Sep 12, 2024

Hi, @t1

Summary at this moment

  1. You can use @Name or @Namepace on the server and client code.
  2. If both annotations are used - an error.
  3. Needs simple changes in quarkus (add namesapced operations here)

I only have a question about the operation names. Quarkus tests fails on the check operation name. How should it be done?
Doing it by name is not correct if there are namespaces. I made the first letter capitalize. I had to make some minor changes to the tests. Now I think it makes sense to put this back for situations where there are no namespaces. And Quarkus tests will not fail, and there are fewer internal tests to edit.

@Namespace("fisrt/second")
@GraphQLClientApi
interface Api {
   @Query
   String get();  // Operation name will be 'FirstSecondGet'
}

@Name("fisrt")
@GraphQLClientApi
interface Api {
   @Query
   String get();  // Operation name will be 'FirstGet'
}

@GraphQLClientApi
interface Api {
   @Query
   String get();  // Operation name will be 'get'
}

And I'm waiting for your questions and suggestions

@RoMiRoSSaN RoMiRoSSaN changed the title [DRAFT] Replace using @Name to @Namespace on GraphQLApi and GraphQLClientApi Add @Namespace for GraphQLApi and GraphQLClientApi Sep 12, 2024
@RoMiRoSSaN RoMiRoSSaN marked this pull request as ready for review September 12, 2024 12:49
@RoMiRoSSaN
Copy link
Contributor Author

@jmartisk Please, look at this later too. When you have time

@t1
Copy link
Collaborator

t1 commented Sep 17, 2024

If we would follow my comment above, the names would be firstSecondGet, firstGet, and get.

@@ -185,7 +195,8 @@ public <T> T build(Class<T> apiClass) {
endpoint,
websocketUrl, executeSingleOperationsOverWebsocket, httpClient, webClient, subprotocols,
websocketInitializationTimeout,
allowUnexpectedResponseFields);
allowUnexpectedResponseFields,
namespaceAnnotation != 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 would you pass a useNamespace boolean when you have already looked at the annotation and can now already just pass the information about the particular namespace as a List<String>? That would avoid having to use reflection later on.


if (nameAnnotation != null && namespaceAnnotation != null) {
throw new RuntimeException("You can only use one of the annotations - @Name or @Namespace " +
"over the GraphQLClientApi interface. Please, fix next interface: " + apiClass.getName());
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"over the GraphQLClientApi interface. Please, fix next interface: " + apiClass.getName());
"over the GraphQLClientApi interface. Please, fix the interface: " + apiClass.getName());

if (!errors.isEmpty()) {
throw new RuntimeException("Subscriptions can't be nested. " +
"Move your subscriptions to another @GraphQLApi class, not marked @Namespace or @Name. " +
"Check next places: " + String.join("; ", errors));
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"Check next places: " + String.join("; ", errors));
"Check these places: " + String.join("; ", errors));

@RoMiRoSSaN
Copy link
Contributor Author

RoMiRoSSaN commented Sep 19, 2024

@jmartisk
Replaced the string with an array of strings. As a result, it was possible to remove the code for groups and other things without any problems.
Good point about useNamespace, I've rollback it.
Managed to maintain backward compatibility with @Name
I checked these changes in quarkus - minor edits will be required.

It might be worth adding a check that the strings in @Name and @Namespace match some pattern (letters and strings, no spaces or symbols), and throw an exception. Or is it redundant? And fix docs.

.collect(Collectors.toList());
if (!errorInterfaces.isEmpty()) {
throw new RuntimeException("You can only use one of the annotations - @Name or @Namespace " +
"over the GraphQLClientApi interface. Please, fix next interfaces: " +
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"over the GraphQLClientApi interface. Please, fix next interfaces: " +
"over the GraphQLClientApi interface. Please, fix the following interfaces: " +

@@ -75,6 +80,19 @@ private ClientModels generateClientModels() {
return clientModels;
}

private void validateAnnotations(Collection<AnnotationInstance> graphQLApiAnnotations) {
Copy link
Member

Choose a reason for hiding this comment

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

Could we name this validateNamespaceAnnotations to make it more clear what it does?

@jmartisk
Copy link
Member

Have you got a branch with the planned Quarkus-side changes too so that I can have a look?

@RoMiRoSSaN
Copy link
Contributor Author

@jmartisk Hi. Here are the changes with comments. They are minimal. The main thing is to remove group processing.
Just in case - works without adding @Namesapce to the index and processing operations. I checked this in the native assembly. But most likely you should add these lines (subtleties of native assemblies, probably)

quarkusio/quarkus@bda9a2f

@jmartisk
Copy link
Member

Yeah let's put the @Namespace annotation into the index to be sure,
and also the classes.addAll(getOperationClassNames(schema.getAllOperations())) call - I'm not sure what exactly you checked, but it should be necessary, at minimum in cases where GraphQL APIs work with non-scalar classes, I think. We need to make sure that the classes are registered for reflection.
If you could update your Quarkus commit per these comments, I would cherry-pick it and create a Quarkus branch that contains it, so we can later switch the CI (the "Quarkus tests" job) to run against that branch and get a full test run

@RoMiRoSSaN
Copy link
Contributor Author

@jmartisk
Copy link
Member

jmartisk commented Sep 23, 2024

@RoMiRoSSaN I took the liberty to copy it into my own branch: https://github.com/jmartisk/quarkus/tree/smallrye-graphql-2.11 this branch will serve to capture any necessary changes that we will do in Quarkus along with upgrading to SmallRye GraphQL 2.11 (when it's released). I've also pushed a commit here to change the CI build so that the Quarkus part of the CI runs with a snapshot from that branch - so it should reflect the Quarkus-side changes. Let's see what the CI says now (it should fully pass now)

@RoMiRoSSaN
Copy link
Contributor Author

RoMiRoSSaN commented Sep 23, 2024

Everything seems to be fine.
Please, if you are ready to accept this, look carefully at the changes again, I am ready to accept all comments.
And may be need to add more tests in quarkus for this

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.

3 participants