Skip to content

Commit d91f511

Browse files
committed
feat(graphql-java-client-validator-plugin) S3508: java gql client schema sagety
1 parent 68d08fc commit d91f511

28 files changed

+856
-1
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.idea
2+
target

README.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,46 @@
1-
# graphql-java-client-validator
1+
VALSTRO GraphQL Java Client Validation Maven Plugin
2+
===================================================
3+
Currently we have scheman generated code-gen classes and their simpler POJO versions which are used in graphgl java clients.
4+
If something changes in the schema, such as renaming a variable, this will result in a runtime time error when POJO version doesn't have the same changes.
5+
`graphql-java-client-validator-plugin` is a maven plugin that aims to validate POJO classes that is used graphql java client against the schemna generated ones.
6+
7+
8+
HOW IT WORKS
9+
-------------
10+
Once maven plugin is added into the project's pom file, plugin goal will execute during the project's maven compilation phase.
11+
`graphql-java-client-validator-plugin` loads the code-gen classes from the given package. Then load the given graphql java client class and collect its methods returns, which are the POJO version of code-gen classes.
12+
First, plugin tries to find the code-gen class by using the POJO class name. If plugin cannot find any matching class name from the loaded code-gen classes, then build will fail.
13+
If it finds, then plugin checks field names and types. If there is any unmatching field name or type, build will fail.
14+
Only accaption for the unmathing filed types is, if code-gen class has variable as java wrapper classes type and POJO has it as a primitive type.
15+
16+
17+
PARAMETERS :
18+
-------------------
19+
| Name | Description | Type | Required | Default |
20+
|------| ----------- | -----| -------- |---------|
21+
| generatedPachage | Location of code-gen generated classes | String | Optional | valstro.oms.lib |
22+
| clientClasses | List of client classes that has the java classes to be validated | String [ ] | Yes | - |
23+
24+
25+
USAGE :
26+
-------
27+
```
28+
<plugin>
29+
<groupId>com.valstro.plugin</groupId>
30+
<artifactId>graphql-java-client-validator-plugin</artifactId>
31+
<version>0.0.1-SNAPSHOT</version>
32+
<executions>
33+
<execution>
34+
<goals>
35+
<goal>validate-graphql-client</goal>
36+
</goals>
37+
</execution>
38+
</executions>
39+
<configuration>
40+
<clientClasses>
41+
<clientClass>package.class_name</clientClass>
42+
<clientClass>package.class_name</clientClass>
43+
</clientClasses>
44+
</configuration>
45+
</plugin>
46+
```

pom.xml

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<groupId>com.valstro.plugin</groupId>
7+
<artifactId>graphql-java-client-validator-plugin</artifactId>
8+
<version>0.0.1-SNAPSHOT</version>
9+
<name>graphql-java-client-validator-plugin</name>
10+
<packaging>maven-plugin</packaging>
11+
12+
<organization>
13+
<name>Valstro</name>
14+
<url>https://www.valstro.com/</url>
15+
</organization>
16+
17+
<properties>
18+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
19+
<maven.compiler.source>17</maven.compiler.source>
20+
<maven.compiler.target>17</maven.compiler.target>
21+
<maven-plugin-api-version>3.9.2</maven-plugin-api-version>
22+
<maven-plugin-annotation-version>3.9.0</maven-plugin-annotation-version>
23+
<maven-project-version>3.0-alpha-2</maven-project-version>
24+
<maven-plugin-plugin.version>3.9.0</maven-plugin-plugin.version>
25+
<maven-site-plugin.version>3.12.1</maven-site-plugin.version>
26+
<slf4j-version>2.0.7</slf4j-version>
27+
<junit-version>5.9.3</junit-version>
28+
<maven-combat-version>3.9.2</maven-combat-version>
29+
<maven-test_harness>3.3.0</maven-test_harness>
30+
</properties>
31+
32+
<dependencies>
33+
<dependency>
34+
<groupId>org.apache.maven</groupId>
35+
<artifactId>maven-plugin-api</artifactId>
36+
<version>${maven-plugin-api-version}</version>
37+
<exclusions>
38+
<exclusion>
39+
<groupId>org.apache.maven</groupId>
40+
<artifactId>maven-project</artifactId>
41+
</exclusion>
42+
</exclusions>
43+
</dependency>
44+
<dependency>
45+
<groupId>org.apache.maven</groupId>
46+
<artifactId>maven-core</artifactId>
47+
<version>${maven-plugin-api-version}</version>
48+
</dependency>
49+
<dependency>
50+
<groupId>org.apache.maven.plugin-tools</groupId>
51+
<artifactId>maven-plugin-annotations</artifactId>
52+
<version>${maven-plugin-annotation-version}</version>
53+
<scope>provided</scope>
54+
<exclusions>
55+
<exclusion>
56+
<groupId>org.apache.maven</groupId>
57+
<artifactId>maven-project</artifactId>
58+
</exclusion>
59+
</exclusions>
60+
</dependency>
61+
<dependency>
62+
<groupId>org.apache.maven</groupId>
63+
<artifactId>maven-project</artifactId>
64+
<version>${maven-project-version}</version>
65+
<exclusions>
66+
<exclusion>
67+
<groupId>junit</groupId>
68+
<artifactId>junit</artifactId>
69+
</exclusion>
70+
</exclusions>
71+
</dependency>
72+
73+
<!-- LOG -->
74+
<dependency>
75+
<groupId>org.slf4j</groupId>
76+
<artifactId>slf4j-api</artifactId>
77+
<version>${slf4j-version}</version>
78+
</dependency>
79+
<dependency>
80+
<groupId>org.slf4j</groupId>
81+
<artifactId>slf4j-simple</artifactId>
82+
<version>${slf4j-version}</version>
83+
</dependency>
84+
85+
<!-- TESTING -->
86+
<dependency>
87+
<groupId>org.junit.jupiter</groupId>
88+
<artifactId>junit-jupiter-engine</artifactId>
89+
<version>${junit-version}</version>
90+
<scope>test</scope>
91+
</dependency>
92+
<dependency>
93+
<groupId>org.junit.vintage</groupId>
94+
<artifactId>junit-vintage-engine</artifactId>
95+
<version>${junit-version}</version>
96+
<scope>test</scope>
97+
</dependency>
98+
<dependency>
99+
<groupId>org.apache.maven.plugin-testing</groupId>
100+
<artifactId>maven-plugin-testing-harness</artifactId>
101+
<version>${maven-test_harness}</version>
102+
<scope>test</scope>
103+
</dependency>
104+
<dependency>
105+
<groupId>org.apache.maven</groupId>
106+
<artifactId>maven-compat</artifactId>
107+
<version>${maven-combat-version}</version>
108+
<scope>test</scope>
109+
<exclusions>
110+
<exclusion>
111+
<groupId>org.apache.maven</groupId>
112+
<artifactId>maven-core</artifactId>
113+
</exclusion>
114+
</exclusions>
115+
</dependency>
116+
</dependencies>
117+
118+
<build>
119+
<pluginManagement>
120+
<plugins>
121+
<plugin>
122+
<groupId>org.apache.maven.plugins</groupId>
123+
<artifactId>maven-plugin-plugin</artifactId>
124+
<version>${maven-plugin-plugin.version}</version>
125+
<executions>
126+
<execution>
127+
<id>default-descriptor</id>
128+
<phase>process-classes</phase>
129+
</execution>
130+
</executions>
131+
</plugin>
132+
<plugin>
133+
<groupId>org.apache.maven.plugins</groupId>
134+
<artifactId>maven-site-plugin</artifactId>
135+
<version>${maven-site-plugin.version}</version>
136+
</plugin>
137+
</plugins>
138+
</pluginManagement>
139+
</build>
140+
141+
<reporting>
142+
<plugins>
143+
<plugin>
144+
<groupId>org.apache.maven.plugins</groupId>
145+
<artifactId>maven-plugin-plugin</artifactId>
146+
<reportSets>
147+
<reportSet>
148+
<reports>
149+
<report>report</report>
150+
</reports>
151+
</reportSet>
152+
</reportSets>
153+
</plugin>
154+
</plugins>
155+
</reporting>
156+
</project>
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.valstro.plugin;
2+
3+
import com.valstro.plugin.util.ProjectClassLoader;
4+
import com.valstro.plugin.util.CodeGenClassHolder;
5+
import com.valstro.plugin.validator.FieldValidator;
6+
import org.apache.maven.plugin.AbstractMojo;
7+
import org.apache.maven.plugin.MojoExecutionException;
8+
import org.apache.maven.plugin.MojoFailureException;
9+
import org.apache.maven.plugin.descriptor.PluginDescriptor;
10+
import org.apache.maven.plugins.annotations.LifecyclePhase;
11+
import org.apache.maven.plugins.annotations.Mojo;
12+
import org.apache.maven.plugins.annotations.Parameter;
13+
import org.apache.maven.plugins.annotations.ResolutionScope;
14+
import org.apache.maven.project.MavenProject;
15+
import org.slf4j.Logger;
16+
import org.slf4j.LoggerFactory;
17+
18+
import java.lang.reflect.Method;
19+
import java.lang.reflect.ParameterizedType;
20+
import java.lang.reflect.Type;
21+
import java.util.Arrays;
22+
import java.util.stream.Collectors;
23+
24+
@Mojo(name = "validate-graphql-client", defaultPhase = LifecyclePhase.COMPILE, requiresDependencyResolution = ResolutionScope.COMPILE)
25+
public class GraphQLJavaClientValidator extends AbstractMojo {
26+
private static final Logger LOG = LoggerFactory.getLogger(GraphQLJavaClientValidator.class);
27+
28+
@Parameter(defaultValue = "${project}", required = true, readonly = true)
29+
private MavenProject project;
30+
31+
@Parameter(defaultValue = "${plugin}", required = true, readonly = true)
32+
private PluginDescriptor descriptor;
33+
34+
@Parameter(property = "clientClasses", required = true)
35+
private String[] clientClasses;
36+
37+
@Parameter(property = "generatedPackage", defaultValue = "com.valstro.oms.lib")
38+
private String generatedPackage;
39+
40+
private ProjectClassLoader projectClassLoader;
41+
private CodeGenClassHolder codeGenClassHolder;
42+
private FieldValidator fieldValidator;
43+
44+
@Override
45+
public void execute() throws MojoExecutionException, MojoFailureException {
46+
initialize();
47+
executeGoal();
48+
}
49+
50+
private void initialize() {
51+
this.projectClassLoader = new ProjectClassLoader(project, descriptor);
52+
53+
this.codeGenClassHolder = new CodeGenClassHolder(projectClassLoader, generatedPackage);
54+
this.fieldValidator = new FieldValidator(codeGenClassHolder);
55+
}
56+
57+
void executeGoal() throws MojoExecutionException, MojoFailureException{
58+
codeGenClassHolder.loadCodeGenClasses();
59+
LOG.info("code-gen classes has been loaded from {}", generatedPackage);
60+
61+
for (String clientClass : clientClasses) {
62+
Class<?> sourceClazz = projectClassLoader.loadClientClass(clientClass);
63+
LOG.info("Client class {} has been loaded", clientClass);
64+
65+
// collect client class returns
66+
var methods = sourceClazz.getMethods();
67+
var returnTypes = Arrays.stream(methods)
68+
.map(GraphQLJavaClientValidator::resolveMethodReturnType)
69+
.collect(Collectors.toSet());
70+
71+
for (Type returnType : returnTypes) {
72+
var clazz = ((Class<?>) returnType);
73+
var codeGenClazz = resolveCodeGenClass(clazz);
74+
75+
LOG.info("Comparing {} and {}", clazz.getName(), codeGenClazz.getName());
76+
fieldValidator.validate(clazz, codeGenClazz);
77+
}
78+
}
79+
}
80+
81+
private Class<?> resolveCodeGenClass(Class<?> clazz) throws MojoFailureException {
82+
var codeGenClazz = codeGenClassHolder.getCodeGenClassFromName(clazz.getSimpleName());
83+
84+
if (codeGenClazz == null)
85+
throw new MojoFailureException("Cannot find class " + clazz.getSimpleName() + " in " + generatedPackage);
86+
87+
return codeGenClazz;
88+
}
89+
90+
private static Type resolveMethodReturnType(Method method) {
91+
var type = method.getGenericReturnType();
92+
return (method.getGenericReturnType() instanceof ParameterizedType) ?
93+
((ParameterizedType) type).getActualTypeArguments()[0] : type;
94+
}
95+
96+
}
97+
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.valstro.plugin.util;
2+
3+
import com.google.common.reflect.ClassPath;
4+
import org.apache.maven.artifact.DependencyResolutionRequiredException;
5+
import org.apache.maven.plugin.MojoExecutionException;
6+
7+
import java.io.IOException;
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
import java.util.function.Function;
11+
import java.util.stream.Collectors;
12+
13+
public class CodeGenClassHolder {
14+
15+
private static final Map<String, Class<?>> CODE_GEN_CLASSES = new HashMap<>();
16+
17+
private final ProjectClassLoader projectClassLoader;
18+
private final String codeGenPackage;
19+
20+
public CodeGenClassHolder(ProjectClassLoader projectClassLoader, String codeGenPackage) {
21+
this.projectClassLoader = projectClassLoader;
22+
this.codeGenPackage = codeGenPackage;
23+
}
24+
25+
public void loadCodeGenClasses() throws MojoExecutionException {
26+
try {
27+
var codeGenClasses = ClassPath.from(projectClassLoader.getProjectClassLoader())
28+
.getTopLevelClasses()
29+
.stream()
30+
.filter(clazz -> clazz.getPackageName()
31+
.equalsIgnoreCase(codeGenPackage))
32+
.map(ClassPath.ClassInfo::load)
33+
.collect(Collectors.toMap(Class::getName, Function.identity()));
34+
CODE_GEN_CLASSES.putAll(codeGenClasses);
35+
} catch (IOException | DependencyResolutionRequiredException e) {
36+
throw new MojoExecutionException("Cannot load code-gen classes from " + codeGenPackage);
37+
}
38+
}
39+
40+
public Class<?> getCodeGenClassFromName(String className) {
41+
return CODE_GEN_CLASSES.get(codeGenPackage + "." + className);
42+
}
43+
44+
public boolean isExist(String className) {
45+
return CODE_GEN_CLASSES.containsKey(codeGenPackage + "." + className);
46+
}
47+
}

0 commit comments

Comments
 (0)