From 573663fc114646c85e8123f6f380ea2e92158252 Mon Sep 17 00:00:00 2001 From: Yevheniy Oliynyk Date: Sun, 25 May 2025 15:27:20 +0200 Subject: [PATCH] feat: graphql api --- README.md | 25 +++++++ .../com/crowdin/client/core/CrowdinApi.java | 38 ++++++++-- .../client/core/model/GraphQLRequest.java | 21 ++++++ .../client/core/model/GraphQLResponse.java | 11 +++ .../crowdin/client/core/CrowdinApiTest.java | 74 +++++++++++++++++++ .../resources/api/core/graphQLRequest.json | 3 + .../resources/api/core/graphQLResponse.json | 20 +++++ 7 files changed, 187 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/crowdin/client/core/model/GraphQLRequest.java create mode 100644 src/main/java/com/crowdin/client/core/model/GraphQLResponse.java create mode 100644 src/test/java/com/crowdin/client/core/CrowdinApiTest.java create mode 100644 src/test/resources/api/core/graphQLRequest.json create mode 100644 src/test/resources/api/core/graphQLResponse.json diff --git a/README.md b/README.md index b094d2bcc..03cfa5a2e 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,31 @@ public class Main { } ``` +## GraphQL API + +This library also provides possibility to use [GraphQL API](https://support.crowdin.com/developer/graphql-api/) + +```java +import com.crowdin.client.Client; +import com.crowdin.client.core.model.Credentials; +import com.crowdin.client.core.model.GraphQLRequest; + +import java.util.Map; + +public class GraphQLExample { + + public static void main(String[] args) { + Credentials credentials = new Credentials("token", "organization"); + Client client = new Client(credentials); + String query = "query { viewer { projects(first: 2) { edges { node { name } } } } }"; + GraphQLRequest request = new GraphQLRequest(query); + Map response = client.graphql(request).getData(); + System.out.println(response); + } + +} +``` + ## Seeking Assistance If you find any problems or would like to suggest a feature, please read the [How can I contribute](/CONTRIBUTING.md#how-can-i-contribute) section in our contributing guidelines. diff --git a/src/main/java/com/crowdin/client/core/CrowdinApi.java b/src/main/java/com/crowdin/client/core/CrowdinApi.java index 6c39fb929..e3f9954ad 100644 --- a/src/main/java/com/crowdin/client/core/CrowdinApi.java +++ b/src/main/java/com/crowdin/client/core/CrowdinApi.java @@ -1,11 +1,11 @@ package com.crowdin.client.core; import com.crowdin.client.core.http.HttpClient; +import com.crowdin.client.core.http.HttpRequestConfig; import com.crowdin.client.core.http.JsonTransformer; import com.crowdin.client.core.http.impl.http.ApacheHttpClient; import com.crowdin.client.core.http.impl.json.JacksonJsonTransformer; -import com.crowdin.client.core.model.ClientConfig; -import com.crowdin.client.core.model.Credentials; +import com.crowdin.client.core.model.*; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; import org.apache.http.impl.client.HttpClientBuilder; @@ -19,6 +19,7 @@ public abstract class CrowdinApi { protected final HttpClient httpClient; protected final ClientConfig clientConfig; protected final String url; + protected final String graphqlUrl; public CrowdinApi(Credentials credentials) { this(credentials, ClientConfig.builder() @@ -38,23 +39,50 @@ public CrowdinApi(Credentials credentials, ClientConfig clientConfig) { defaultHeaders.put("X-Crowdin-Integrations-User-Agent", clientConfig.getIntegrationUserAgent()); } JsonTransformer jsonTransformer = (clientConfig.getJsonTransformer() != null) - ? clientConfig.getJsonTransformer() : new JacksonJsonTransformer(); + ? clientConfig.getJsonTransformer() : new JacksonJsonTransformer(); this.httpClient = (clientConfig.getHttpClient() != null) - ? clientConfig.getHttpClient() - : new ApacheHttpClient(credentials, jsonTransformer, defaultHeaders, clientConfig.getProxy(), clientConfig.getProxyCreds(), clientConfig.getHttpTimeoutMs()); + ? clientConfig.getHttpClient() + : new ApacheHttpClient(credentials, jsonTransformer, defaultHeaders, clientConfig.getProxy(), clientConfig.getProxyCreds(), clientConfig.getHttpTimeoutMs()); this.clientConfig = clientConfig; if (credentials.getBaseUrl() != null) { if (credentials.getBaseUrl().endsWith("/")) { this.url = credentials.getBaseUrl() + "api/v2"; + this.graphqlUrl = credentials.getBaseUrl() + "api/graphql"; } else { this.url = credentials.getBaseUrl() + "/api/v2"; + this.graphqlUrl = credentials.getBaseUrl() + "/api/graphql"; } } else { if (credentials.getOrganization() != null) { this.url = "https://" + credentials.getOrganization() + ".api.crowdin.com/api/v2"; + this.graphqlUrl = "https://" + credentials.getOrganization() + ".api.crowdin.com/api/graphql"; } else { this.url = "https://api.crowdin.com/api/v2"; + this.graphqlUrl = "https://api.crowdin.com/api/graphql"; } } } + + /** + * @param request request object + * @return newly created bundle + * @see + */ + public ResponseObject> graphql(GraphQLRequest request) { + GraphQLResponse response = this.graphql(request, GraphQLResponse.class); + return ResponseObject.of(response.getData()); + } + + /** + * @param request request object + * @return newly created bundle + * @see + */ + public T graphql(GraphQLRequest request, Class clazz) { + return this.httpClient.post(this.graphqlUrl, request, new HttpRequestConfig(), clazz); + } } diff --git a/src/main/java/com/crowdin/client/core/model/GraphQLRequest.java b/src/main/java/com/crowdin/client/core/model/GraphQLRequest.java new file mode 100644 index 000000000..b47fff89f --- /dev/null +++ b/src/main/java/com/crowdin/client/core/model/GraphQLRequest.java @@ -0,0 +1,21 @@ +package com.crowdin.client.core.model; + +import lombok.Data; + +import java.util.Map; + +@Data +public class GraphQLRequest { + + private String query; + private Map variables; + private String operationName; + + public GraphQLRequest() { + } + + public GraphQLRequest(String query) { + this(); + this.query = query; + } +} diff --git a/src/main/java/com/crowdin/client/core/model/GraphQLResponse.java b/src/main/java/com/crowdin/client/core/model/GraphQLResponse.java new file mode 100644 index 000000000..599c0d144 --- /dev/null +++ b/src/main/java/com/crowdin/client/core/model/GraphQLResponse.java @@ -0,0 +1,11 @@ +package com.crowdin.client.core.model; + +import lombok.Data; + +import java.util.Map; + +@Data +public class GraphQLResponse { + + private Map data; +} diff --git a/src/test/java/com/crowdin/client/core/CrowdinApiTest.java b/src/test/java/com/crowdin/client/core/CrowdinApiTest.java new file mode 100644 index 000000000..1d7fa94f0 --- /dev/null +++ b/src/test/java/com/crowdin/client/core/CrowdinApiTest.java @@ -0,0 +1,74 @@ +package com.crowdin.client.core; + +import com.crowdin.client.core.model.GraphQLRequest; +import com.crowdin.client.framework.RequestMock; +import com.crowdin.client.framework.TestClient; +import lombok.Data; +import lombok.var; +import org.apache.http.client.methods.HttpPost; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CrowdinApiTest extends TestClient { + + @Override + public List getMocks() { + return Arrays.asList( + RequestMock.build(this.graphqlUrl, HttpPost.METHOD_NAME, "api/core/graphQLRequest.json", + "api/core/graphQLResponse.json") + ); + } + + @Test + public void graphQLTest() { + var res = this.graphql(new GraphQLRequest("query { viewer { projects(first: 2) { edges { node { name } } } } }")); + var edges = List.class.cast(Map.class.cast(Map.class.cast(Map.class.cast(res.getData().get("viewer")).get("projects"))).get("edges")); + assertEquals(2, edges.size()); + assertEquals("test1", Map.class.cast(Map.class.cast(edges.get(0)).get("node")).get("name").toString()); + assertEquals("test2", Map.class.cast(Map.class.cast(edges.get(1)).get("node")).get("name").toString()); + } + + @Test + public void graphQLCustomTypeTest() { + var res = this.graphql(new GraphQLRequest("query { viewer { projects(first: 2) { edges { node { name } } } } }"), GraphQLRes.class); + assertEquals(2, res.getData().getViewer().getProjects().getEdges().size()); + assertEquals("test1", res.getData().getViewer().getProjects().getEdges().get(0).getNode().getName()); + assertEquals("test2", res.getData().getViewer().getProjects().getEdges().get(1).getNode().getName()); + } + + @Data + public static class GraphQLRes { + + private GraphQLResData data; + + @Data + public static class GraphQLResData { + private GraphQLResViewer viewer; + } + + @Data + public static class GraphQLResViewer { + private GraphQLResProjects projects; + } + + @Data + public static class GraphQLResProjects { + private List edges; + } + + @Data + public static class GraphQLResEdge { + private GraphQLResNode node; + } + + @Data + public static class GraphQLResNode { + private String name; + } + } +} diff --git a/src/test/resources/api/core/graphQLRequest.json b/src/test/resources/api/core/graphQLRequest.json new file mode 100644 index 000000000..b12b402f5 --- /dev/null +++ b/src/test/resources/api/core/graphQLRequest.json @@ -0,0 +1,3 @@ +{ + "query": "query { viewer { projects(first: 2) { edges { node { name } } } } }" +} \ No newline at end of file diff --git a/src/test/resources/api/core/graphQLResponse.json b/src/test/resources/api/core/graphQLResponse.json new file mode 100644 index 000000000..5f00fdd18 --- /dev/null +++ b/src/test/resources/api/core/graphQLResponse.json @@ -0,0 +1,20 @@ +{ + "data": { + "viewer": { + "projects": { + "edges": [ + { + "node": { + "name": "test1" + } + }, + { + "node": { + "name": "test2" + } + } + ] + } + } + } +} \ No newline at end of file