diff --git a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/ExceptionCodes.java b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/ExceptionCodes.java index 80068b537543..49f78a5d72f2 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/ExceptionCodes.java +++ b/components/apimgt/org.wso2.carbon.apimgt.api/src/main/java/org/wso2/carbon/apimgt/api/ExceptionCodes.java @@ -291,7 +291,12 @@ public enum ExceptionCodes implements ErrorHandler { "GraphQL Schema cannot be empty or null"), UNSUPPORTED_GRAPHQL_FILE_EXTENSION(900802, "Unsupported GraphQL Schema File Extension", 400, "Unsupported extension. Only supported extensions are .graphql, .txt and .sdl"), - + INVALID_GRAPHQL_FILE(900803, "GraphQL filename cannot be null or invalid", 400, + "GraphQL filename cannot be null or invalid"), + GENERATE_GRAPHQL_SCHEMA_FROM_INTROSPECTION_ERROR(900804, "Error while generating GraphQL schema from introspection", + 500, "Error while generating GraphQL schema from introspection"), + RETRIEVE_GRAPHQL_SCHEMA_FROM_URL_ERROR(900805, "Error while retrieving GraphQL schema from URL", 500, + "Error while retrieving GraphQL schema from URL"), // Oauth related codes AUTH_GENERAL_ERROR(900900, "Authorization Error", 403, " Error in authorization"), diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/APIUtil.java b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/APIUtil.java index fde4b5f08aea..c12a50e6650d 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/APIUtil.java +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/java/org/wso2/carbon/apimgt/impl/utils/APIUtil.java @@ -171,6 +171,7 @@ import org.wso2.carbon.apimgt.impl.kmclient.ApacheFeignHttpClient; import org.wso2.carbon.apimgt.impl.kmclient.model.OpenIDConnectDiscoveryClient; import org.wso2.carbon.apimgt.impl.kmclient.model.OpenIdConnectConfiguration; +import org.wso2.carbon.apimgt.impl.lifecycle.LCManager; import org.wso2.carbon.apimgt.impl.notifier.Notifier; import org.wso2.carbon.apimgt.impl.notifier.events.APIPolicyEvent; import org.wso2.carbon.apimgt.impl.notifier.events.ApplicationPolicyEvent; @@ -253,6 +254,7 @@ import java.net.URLDecoder; import java.net.UnknownHostException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.rmi.RemoteException; import java.security.*; import java.security.cert.Certificate; @@ -1404,6 +1406,18 @@ public static String getOpenAPIDefinitionFilePath(String apiName, String apiVers apiName + RegistryConstants.PATH_SEPARATOR + apiVersion + RegistryConstants.PATH_SEPARATOR; } + public static String getIntrospectionQuery() throws APIManagementException { + String introspectionQueryFilePath = "graphql/introspection_query.txt"; + try (InputStream fileStream = APIUtil.class.getClassLoader().getResourceAsStream(introspectionQueryFilePath)) { + if (fileStream == null) { + throw new APIManagementException("File not found: " + introspectionQueryFilePath); + } + return IOUtils.toString(fileStream, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new APIManagementException("Error reading introspection query file", e); + } + } + public static String getRevisionPath(String apiUUID, int revisionId) { return APIConstants.API_REVISION_LOCATION + RegistryConstants.PATH_SEPARATOR + apiUUID + diff --git a/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/resources/graphql/introspection_query.txt b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/resources/graphql/introspection_query.txt new file mode 100644 index 000000000000..c31d34ce3e4e --- /dev/null +++ b/components/apimgt/org.wso2.carbon.apimgt.impl/src/main/resources/graphql/introspection_query.txt @@ -0,0 +1,96 @@ + query IntrospectionQuery { + __schema { + + queryType { name } + mutationType { name } + subscriptionType { name } + types { + ...FullType + } + directives { + name + description + + locations + args { + ...InputValue + } + } + } + } + + fragment FullType on __Type { + kind + name + description + + fields(includeDeprecated: true) { + name + description + args { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ...InputValue + } + interfaces { + ...TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ...TypeRef + } + } + + fragment InputValue on __InputValue { + name + description + type { ...TypeRef } + defaultValue + + + } + + fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } + } diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/ImportUtils.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/ImportUtils.java index 85baaa5aa087..2929ba18c73f 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/ImportUtils.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/ImportUtils.java @@ -1618,7 +1618,7 @@ public static GraphQLValidationResponseDTO retrieveValidatedGraphqlSchemaFromArc try { String schemaDefinition = loadGraphqlSDLFile(pathToArchive); GraphQLValidationResponseDTO graphQLValidationResponseDTO = PublisherCommonUtils - .validateGraphQLSchema(file.getName(), schemaDefinition); + .validateGraphQLSchema(file.getName(), schemaDefinition, null, false); if (!graphQLValidationResponseDTO.isIsValid()) { String errorMessage = "Error occurred while importing the API. Invalid GraphQL schema definition " + "found. " + graphQLValidationResponseDTO.getErrorMessage(); diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/PublisherCommonUtils.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/PublisherCommonUtils.java index 7a863624fbad..68a0a06c6288 100755 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/PublisherCommonUtils.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1.common/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/common/mappings/PublisherCommonUtils.java @@ -24,6 +24,10 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.gson.reflect.TypeToken; +import graphql.introspection.IntrospectionResultToSchema; +import graphql.language.AstPrinter; +import graphql.language.Document; import graphql.schema.GraphQLSchema; import graphql.schema.idl.SchemaParser; import graphql.schema.idl.TypeDefinitionRegistry; @@ -38,6 +42,13 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.util.EntityUtils; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; @@ -105,7 +116,10 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.net.URL; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -126,6 +140,7 @@ public class PublisherCommonUtils { private static final Log log = LogFactory.getLog(PublisherCommonUtils.class); public static final String SESSION_TIMEOUT_CONFIG_KEY = "sessionTimeOut"; + private static String graphQLIntrospectionQuery = null; /** * Update API and API definition. @@ -2000,47 +2015,58 @@ public static API addGraphQLSchema(API originalAPI, String schemaDefinition, API * Validate GraphQL Schema. * * @param filename file name of the schema - * @param schema GraphQL schema + * @param schema GraphQL schema + * @param url URL of the schema + * @param useIntrospection use introspection to obtain schema */ - public static GraphQLValidationResponseDTO validateGraphQLSchema(String filename, String schema) - throws APIManagementException { + public static GraphQLValidationResponseDTO validateGraphQLSchema(String filename, String schema, String url, + Boolean useIntrospection) throws APIManagementException { String errorMessage; GraphQLValidationResponseDTO validationResponse = new GraphQLValidationResponseDTO(); boolean isValid = false; try { - if (filename.endsWith(".graphql") || filename.endsWith(".txt") || filename.endsWith(".sdl")) { - if (schema.isEmpty()) { - throw new APIManagementException("GraphQL Schema cannot be empty or null to validate it", - ExceptionCodes.GRAPHQL_SCHEMA_CANNOT_BE_NULL); - } - SchemaParser schemaParser = new SchemaParser(); - TypeDefinitionRegistry typeRegistry = schemaParser.parse(schema); - GraphQLSchema graphQLSchema = UnExecutableSchemaGenerator.makeUnExecutableSchema(typeRegistry); - SchemaValidator schemaValidation = new SchemaValidator(); - Set validationErrors = schemaValidation.validateSchema(graphQLSchema); - - if (validationErrors.toArray().length > 0) { - errorMessage = "InValid Schema"; - validationResponse.isValid(Boolean.FALSE); - validationResponse.errorMessage(errorMessage); + if (url != null && !url.isEmpty()) { + if (useIntrospection) { + schema = generateGraphQLSchemaFromIntrospection(url); } else { - validationResponse.setIsValid(Boolean.TRUE); - GraphQLValidationResponseGraphQLInfoDTO graphQLInfo = new GraphQLValidationResponseGraphQLInfoDTO(); - GraphQLSchemaDefinition graphql = new GraphQLSchemaDefinition(); - List operationList = graphql.extractGraphQLOperationList(typeRegistry, null); - List operationArray = APIMappingUtil - .fromURITemplateListToOprationList(operationList); - graphQLInfo.setOperations(operationArray); - GraphQLSchemaDTO schemaObj = new GraphQLSchemaDTO(); - schemaObj.setSchemaDefinition(schema); - graphQLInfo.setGraphQLSchema(schemaObj); - validationResponse.setGraphQLInfo(graphQLInfo); + schema = retrieveGraphQLSchemaFromURL(url); } - } else { + } else if (filename == null) { + throw new APIManagementException("GraphQL filename cannot be null", + ExceptionCodes.INVALID_GRAPHQL_FILE); + } else if (!filename.endsWith(".graphql") && !filename.endsWith(".txt") && !filename.endsWith(".sdl")) { throw new APIManagementException("Unsupported extension type of file: " + filename, ExceptionCodes.UNSUPPORTED_GRAPHQL_FILE_EXTENSION); } + + if (schema == null || schema.isEmpty()) { + throw new APIManagementException("GraphQL Schema cannot be empty or null to validate it", + ExceptionCodes.GRAPHQL_SCHEMA_CANNOT_BE_NULL); + } + + SchemaParser schemaParser = new SchemaParser(); + TypeDefinitionRegistry typeRegistry = schemaParser.parse(schema); + GraphQLSchema graphQLSchema = UnExecutableSchemaGenerator.makeUnExecutableSchema(typeRegistry); + SchemaValidator schemaValidation = new SchemaValidator(); + Set validationErrors = schemaValidation.validateSchema(graphQLSchema); + + if (validationErrors.toArray().length > 0) { + errorMessage = "InValid Schema"; + validationResponse.isValid(Boolean.FALSE); + validationResponse.errorMessage(errorMessage); + } else { + validationResponse.setIsValid(Boolean.TRUE); + GraphQLValidationResponseGraphQLInfoDTO graphQLInfo = new GraphQLValidationResponseGraphQLInfoDTO(); + GraphQLSchemaDefinition graphql = new GraphQLSchemaDefinition(); + List operationList = graphql.extractGraphQLOperationList(typeRegistry, null); + List operationArray = APIMappingUtil.fromURITemplateListToOprationList(operationList); + graphQLInfo.setOperations(operationArray); + GraphQLSchemaDTO schemaObj = new GraphQLSchemaDTO(); + schemaObj.setSchemaDefinition(schema); + graphQLInfo.setGraphQLSchema(schemaObj); + validationResponse.setGraphQLInfo(graphQLInfo); + } isValid = validationResponse.isIsValid(); errorMessage = validationResponse.getErrorMessage(); } catch (SchemaProblem e) { @@ -2054,6 +2080,80 @@ public static GraphQLValidationResponseDTO validateGraphQLSchema(String filename return validationResponse; } + /** + * Generate the GraphQL schema by performing an introspection query on the provided endpoint. + * + * @param url The URL of the GraphQL endpoint to perform the introspection query on. + * @return The GraphQL schema as a string. + * @throws APIManagementException If an error occurs during the schema generation process + */ + public static String generateGraphQLSchemaFromIntrospection(String url) throws APIManagementException { + String schema = null; + try { + URL urlObj = new URL(url); + HttpClient httpClient = APIUtil.getHttpClient(urlObj.getPort(), urlObj.getProtocol()); + Gson gson = new Gson(); + + if (graphQLIntrospectionQuery == null || graphQLIntrospectionQuery.isEmpty()) { + graphQLIntrospectionQuery = APIUtil.getIntrospectionQuery(); + } + String requestBody = gson.toJson( + JsonParser.parseString("{\"query\": \"" + graphQLIntrospectionQuery + "\"}")); + + HttpPost httpPost = new HttpPost(url); + httpPost.setHeader("Content-Type", "application/json"); + httpPost.setEntity(new StringEntity(requestBody)); + HttpResponse response = httpClient.execute(httpPost); + + if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) { + String schemaResponse = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + Type type = new TypeToken>() { + }.getType(); + Map schemaMap = gson.fromJson(schemaResponse, type); + Document schemaDocument = new IntrospectionResultToSchema().createSchemaDefinition( + (Map) schemaMap.get("data")); + schema = AstPrinter.printAst(schemaDocument); + } else { + log.error("Unable to generate GraphQL schema from introspection. URL." + + " Returned response code: " + response.getStatusLine().getStatusCode() + + " from endpoint: " + url); + throw new APIManagementException("Error occurred while generating GraphQL schema from introspection", + ExceptionCodes.GENERATE_GRAPHQL_SCHEMA_FROM_INTROSPECTION_ERROR); + } + } catch (Exception e) { + log.error("Exception occurred while generating GraphQL schema from introspection. URL: " + url + + ". Exception: " + e.getMessage(), e); + throw new APIManagementException("Error occurred while generating GraphQL schema from introspection", + ExceptionCodes.GENERATE_GRAPHQL_SCHEMA_FROM_INTROSPECTION_ERROR); + } + return schema; + } + + /** + * Retrieve the GraphQL schema from the specified URL. + * + * @param url The URL of the GraphQL schema to retrieve. + * @return The GraphQL schema as a string. + * @throws APIManagementException If an error occurs while retrieving the schema + */ + public static String retrieveGraphQLSchemaFromURL(String url) throws APIManagementException { + String schema = null; + try { + URL urlObj = new URL(url); + HttpClient httpClient = APIUtil.getHttpClient(urlObj.getPort(), urlObj.getProtocol()); + HttpGet httpGet = new HttpGet(url); + HttpResponse response = httpClient.execute(httpGet); + + if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) { + schema = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + } + } catch (IOException e) { + throw new APIManagementException("Error occurred while retrieving GraphQL schema from schema URL", + ExceptionCodes.RETRIEVE_GRAPHQL_SCHEMA_FROM_URL_ERROR); + } + return schema; + } + /** * Update thumbnail of an API/API Product * diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApi.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApi.java index 8b3458989a54..1d9ab88e74a0 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApi.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApi.java @@ -1518,8 +1518,8 @@ public Response importAsyncAPISpecification( @Multipart(value = "file", required @ApiResponse(code = 201, message = "Created. Successful response with the newly created object as entity in the body. Location header contains URL of newly created entity. ", response = APIDTO.class), @ApiResponse(code = 400, message = "Bad Request. Invalid request or validation error.", response = ErrorDTO.class), @ApiResponse(code = 415, message = "Unsupported Media Type. The entity of the request was not in a supported format.", response = ErrorDTO.class) }) - public Response importGraphQLSchema( @ApiParam(value = "Validator for conditional requests; based on ETag. " )@HeaderParam("If-Match") String ifMatch, @Multipart(value = "type", required = false) String type, @Multipart(value = "file", required = false) InputStream fileInputStream, @Multipart(value = "file" , required = false) Attachment fileDetail, @Multipart(value = "additionalProperties", required = false) String additionalProperties) throws APIManagementException{ - return delegate.importGraphQLSchema(ifMatch, type, fileInputStream, fileDetail, additionalProperties, securityContext); + public Response importGraphQLSchema( @ApiParam(value = "Validator for conditional requests; based on ETag. " )@HeaderParam("If-Match") String ifMatch, @Multipart(value = "type", required = false) String type, @Multipart(value = "file", required = false) InputStream fileInputStream, @Multipart(value = "file" , required = false) Attachment fileDetail, @Multipart(value = "url", required = false) String url, @Multipart(value = "schema", required = false) String schema, @Multipart(value = "additionalProperties", required = false) String additionalProperties) throws APIManagementException{ + return delegate.importGraphQLSchema(ifMatch, type, fileInputStream, fileDetail, url, schema, additionalProperties, securityContext); } @POST @@ -2018,8 +2018,8 @@ public Response validateEndpoint( @NotNull @ApiParam(value = "API endpoint url", @ApiResponse(code = 200, message = "OK. API definition validation information is returned ", response = GraphQLValidationResponseDTO.class), @ApiResponse(code = 400, message = "Bad Request. Invalid request or validation error.", response = ErrorDTO.class), @ApiResponse(code = 404, message = "Not Found. The specified resource does not exist.", response = ErrorDTO.class) }) - public Response validateGraphQLSchema( @Multipart(value = "file") InputStream fileInputStream, @Multipart(value = "file" ) Attachment fileDetail) throws APIManagementException{ - return delegate.validateGraphQLSchema(fileInputStream, fileDetail, securityContext); + public Response validateGraphQLSchema( @ApiParam(value = "Specify whether to use Introspection to obtain the GraphQL Schema ", defaultValue="false") @DefaultValue("false") @QueryParam("useIntrospection") Boolean useIntrospection, @Multipart(value = "file", required = false) InputStream fileInputStream, @Multipart(value = "file" , required = false) Attachment fileDetail, @Multipart(value = "url", required = false) String url) throws APIManagementException{ + return delegate.validateGraphQLSchema(useIntrospection, fileInputStream, fileDetail, url, securityContext); } @POST diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApiService.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApiService.java index 7caffe1a712f..429dc88a01da 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApiService.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/gen/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/ApisApiService.java @@ -137,7 +137,7 @@ public interface ApisApiService { public Response getWSDLOfAPI(String apiId, String ifNoneMatch, MessageContext messageContext) throws APIManagementException; public Response importAPI(InputStream fileInputStream, Attachment fileDetail, Boolean preserveProvider, Boolean rotateRevision, Boolean overwrite, Boolean preservePortalConfigurations, String accept, MessageContext messageContext) throws APIManagementException; public Response importAsyncAPISpecification(InputStream fileInputStream, Attachment fileDetail, String url, String additionalProperties, MessageContext messageContext) throws APIManagementException; - public Response importGraphQLSchema(String ifMatch, String type, InputStream fileInputStream, Attachment fileDetail, String additionalProperties, MessageContext messageContext) throws APIManagementException; + public Response importGraphQLSchema(String ifMatch, String type, InputStream fileInputStream, Attachment fileDetail, String url, String schema, String additionalProperties, MessageContext messageContext) throws APIManagementException; public Response importOpenAPIDefinition(InputStream fileInputStream, Attachment fileDetail, String url, String additionalProperties, String inlineAPIDefinition, MessageContext messageContext) throws APIManagementException; public Response importServiceFromCatalog(String serviceKey, APIDTO APIDTO, MessageContext messageContext) throws APIManagementException; public Response importWSDLDefinition(InputStream fileInputStream, Attachment fileDetail, String url, String additionalProperties, String implementationType, MessageContext messageContext) throws APIManagementException; @@ -163,7 +163,7 @@ public interface ApisApiService { public Response validateAsyncAPISpecification(Boolean returnContent, String url, InputStream fileInputStream, Attachment fileDetail, MessageContext messageContext) throws APIManagementException; public Response validateDocument(String apiId, String name, String ifMatch, MessageContext messageContext) throws APIManagementException; public Response validateEndpoint(String endpointUrl, String apiId, MessageContext messageContext) throws APIManagementException; - public Response validateGraphQLSchema(InputStream fileInputStream, Attachment fileDetail, MessageContext messageContext) throws APIManagementException; + public Response validateGraphQLSchema(Boolean useIntrospection, InputStream fileInputStream, Attachment fileDetail, String url, MessageContext messageContext) throws APIManagementException; public Response validateOpenAPIDefinition(Boolean returnContent, String url, InputStream fileInputStream, Attachment fileDetail, String inlineAPIDefinition, MessageContext messageContext) throws APIManagementException; public Response validateWSDLDefinition(String url, InputStream fileInputStream, Attachment fileDetail, MessageContext messageContext) throws APIManagementException; } diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/impl/ApisApiServiceImpl.java b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/impl/ApisApiServiceImpl.java index da6a6d7ab74a..b6f635469173 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/impl/ApisApiServiceImpl.java +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/java/org/wso2/carbon/apimgt/rest/api/publisher/v1/impl/ApisApiServiceImpl.java @@ -3516,31 +3516,53 @@ public Response generateInternalAPIKey(String apiId, MessageContext messageConte * @param type APIType * @param fileInputStream input file * @param fileDetail file Detail + * @param url URL of the schema or endpoint + * @param schema graphQL schema definition * @param additionalProperties api object as string format * @param ifMatch If--Match header value * @param messageContext messageContext * @return Response with GraphQL API */ @Override - public Response importGraphQLSchema(String ifMatch, String type, InputStream fileInputStream, - Attachment fileDetail, String additionalProperties, MessageContext messageContext) { - APIDTO additionalPropertiesAPI = null; - String schema = ""; + public Response importGraphQLSchema(String ifMatch, String type, InputStream fileInputStream, Attachment fileDetail, + String url, String schema, String additionalProperties, MessageContext messageContext) { + APIDTO additionalPropertiesAPI = null; + String graphQLSchema = null; try { - if (fileInputStream == null || StringUtils.isBlank(additionalProperties)) { - String errorMessage = "GraphQL schema and api details cannot be empty."; + if (StringUtils.isBlank(additionalProperties)){ + String errorMessage = "Api details cannot be empty."; RestApiUtil.handleBadRequest(errorMessage, log); - } else { - schema = IOUtils.toString(fileInputStream, RestApiConstants.CHARSET); + } + else { + additionalPropertiesAPI = new ObjectMapper().readValue(additionalProperties, APIDTO.class); + } + + if (schema != null && !schema.isEmpty()){ + graphQLSchema = schema; + } + else if (fileInputStream != null && !StringUtils.isBlank(additionalProperties)){ + graphQLSchema = IOUtils.toString(fileInputStream, RestApiConstants.CHARSET); + } + else if (url != null) { + graphQLSchema = PublisherCommonUtils.retrieveGraphQLSchemaFromURL(url); + } + else { + LinkedHashMap endpoints = (LinkedHashMap) ((LinkedHashMap) additionalPropertiesAPI.getEndpointConfig()).get("production_endpoints"); + String endpointURL = endpoints.get("url"); + graphQLSchema = PublisherCommonUtils.generateGraphQLSchemaFromIntrospection(endpointURL); } - if (!StringUtils.isBlank(additionalProperties) && !StringUtils.isBlank(schema) && log.isDebugEnabled()) { + if (graphQLSchema == null || graphQLSchema.isEmpty()) { + throw new APIManagementException("GraphQL Schema cannot be empty or null to validate it", + ExceptionCodes.GRAPHQL_SCHEMA_CANNOT_BE_NULL); + } + + if (!StringUtils.isBlank(additionalProperties) && !StringUtils.isBlank(graphQLSchema) && log.isDebugEnabled()) { log.debug("Deseriallizing additionalProperties: " + additionalProperties + "/n" - + "importing schema: " + schema); + + "importing schema: " + graphQLSchema); } - additionalPropertiesAPI = new ObjectMapper().readValue(additionalProperties, APIDTO.class); APIUtil.validateCharacterLengthOfAPIParams(additionalPropertiesAPI.getName(), additionalPropertiesAPI.getVersion(), additionalPropertiesAPI.getContext(), RestApiCommonUtil.getLoggedInUsername()); @@ -3559,7 +3581,7 @@ public Response importGraphQLSchema(String ifMatch, String type, InputStream fil //adding the api API createdApi = apiProvider.addAPI(apiToAdd); - apiProvider.saveGraphqlSchemaDefinition(createdApi.getUuid(), schema, organization); + apiProvider.saveGraphqlSchemaDefinition(createdApi.getUuid(), graphQLSchema, organization); APIDTO createdApiDTO = APIMappingUtil.fromAPItoDTO(createdApi); @@ -3626,21 +3648,25 @@ public Response importGraphQLSchema(String ifMatch, String type, InputStream fil /** * Validate graphQL Schema + * @param useIntrospection use introspection or not * @param fileInputStream input file * @param fileDetail file Detail + * @param url URL of the schema or endpoint * @param messageContext messageContext * @return Validation response */ @Override - public Response validateGraphQLSchema(InputStream fileInputStream, Attachment fileDetail, - MessageContext messageContext) { - + public Response validateGraphQLSchema(Boolean useIntrospection, InputStream fileInputStream, Attachment fileDetail, + String url, MessageContext messageContext) { + String schema = null; + String filename = null; GraphQLValidationResponseDTO validationResponse = new GraphQLValidationResponseDTO(); - String filename = fileDetail.getContentDisposition().getFilename(); - try { - String schema = IOUtils.toString(fileInputStream, RestApiConstants.CHARSET); - validationResponse = PublisherCommonUtils.validateGraphQLSchema(filename, schema); + if (fileDetail != null) { + filename = fileDetail.getContentDisposition().getFilename(); + schema = IOUtils.toString(fileInputStream, RestApiConstants.CHARSET); + } + validationResponse = PublisherCommonUtils.validateGraphQLSchema(filename, schema, url, useIntrospection); } catch (IOException | APIManagementException e) { validationResponse.setIsValid(false); validationResponse.setErrorMessage(e.getMessage()); diff --git a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/resources/publisher-api.yaml b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/resources/publisher-api.yaml index 919a6d051230..a74328a4db96 100644 --- a/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/resources/publisher-api.yaml +++ b/components/apimgt/org.wso2.carbon.apimgt.rest.api.publisher.v1/src/main/resources/publisher-api.yaml @@ -2607,6 +2607,12 @@ paths: type: string description: Definition to uploads a file format: binary + url: + type: string + description: Definition url + schema: + type: string + description: Definition schema additionalProperties: type: string description: Additional attributes specified as a stringified JSON @@ -2868,17 +2874,26 @@ paths: summary: Validate a GraphQL SDL description: | This operation can be used to validate a graphQL definition and retrieve a summary. + parameters: + - name: useIntrospection + in: query + description: | + Specify whether to use Introspection to obtain the GraphQL Schema + schema: + type: boolean + default: false requestBody: content: multipart/form-data: schema: - required: - - file properties: file: type: string description: Definition to upload as a file format: binary + url: + type: string + description: Definition to upload using url required: true responses: 200: