-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Backport 2.x] Add ApiSpecFetcher for Fetching and Comparing API Spec…
…ifications (#906) * Add ApiSpecFetcher for Fetching and Comparing API Specifications (#900) * Added ApiSpecFetcher with test Signed-off-by: Junwei Dai <[email protected]> * remove duplication license Signed-off-by: Junwei Dai <[email protected]> * Add more test to pass test coverage check Signed-off-by: Junwei Dai <[email protected]> * new commit address all comments Signed-off-by: Junwei Dai <[email protected]> * new commit address all comments Signed-off-by: Junwei Dai <[email protected]> * Addressed all comments Signed-off-by: Junwei Dai <[email protected]> --------- Signed-off-by: Junwei Dai <[email protected]> Co-authored-by: Junwei Dai <[email protected]> (cherry picked from commit 57b8b59) Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * Add slf4j-api and jackson-core dependencies Signed-off-by: Daniel Widdis <[email protected]> --------- Signed-off-by: Junwei Dai <[email protected]> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Signed-off-by: Daniel Widdis <[email protected]> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Junwei Dai <[email protected]> Co-authored-by: Daniel Widdis <[email protected]>
- Loading branch information
1 parent
d1d48b1
commit c939b9c
Showing
8 changed files
with
382 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
src/main/java/org/opensearch/flowframework/exception/ApiSpecParseException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
package org.opensearch.flowframework.exception; | ||
|
||
import org.opensearch.OpenSearchException; | ||
|
||
import java.util.List; | ||
|
||
/** | ||
* Custom exception to be thrown when an error occurs during the parsing of an API specification. | ||
*/ | ||
public class ApiSpecParseException extends OpenSearchException { | ||
|
||
/** | ||
* Constructor with message. | ||
* | ||
* @param message The detail message. | ||
*/ | ||
public ApiSpecParseException(String message) { | ||
super(message); | ||
} | ||
|
||
/** | ||
* Constructor with message and cause. | ||
* | ||
* @param message The detail message. | ||
* @param cause The cause of the exception. | ||
*/ | ||
public ApiSpecParseException(String message, Throwable cause) { | ||
super(message, cause); | ||
} | ||
|
||
/** | ||
* Constructor with message and list of detailed errors. | ||
* | ||
* @param message The detail message. | ||
* @param details The list of errors encountered during the parsing process. | ||
*/ | ||
public ApiSpecParseException(String message, List<String> details) { | ||
super(message + ": " + String.join(", ", details)); | ||
} | ||
} |
120 changes: 120 additions & 0 deletions
120
src/main/java/org/opensearch/flowframework/util/ApiSpecFetcher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
package org.opensearch.flowframework.util; | ||
|
||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.opensearch.common.xcontent.XContentType; | ||
import org.opensearch.flowframework.exception.ApiSpecParseException; | ||
import org.opensearch.rest.RestRequest; | ||
|
||
import java.util.HashSet; | ||
import java.util.List; | ||
|
||
import io.swagger.v3.oas.models.OpenAPI; | ||
import io.swagger.v3.oas.models.Operation; | ||
import io.swagger.v3.oas.models.PathItem; | ||
import io.swagger.v3.oas.models.media.Content; | ||
import io.swagger.v3.oas.models.media.MediaType; | ||
import io.swagger.v3.oas.models.media.Schema; | ||
import io.swagger.v3.oas.models.parameters.RequestBody; | ||
import io.swagger.v3.parser.OpenAPIV3Parser; | ||
import io.swagger.v3.parser.core.models.ParseOptions; | ||
import io.swagger.v3.parser.core.models.SwaggerParseResult; | ||
|
||
/** | ||
* Utility class for fetching and parsing OpenAPI specifications. | ||
*/ | ||
public class ApiSpecFetcher { | ||
private static final Logger logger = LogManager.getLogger(ApiSpecFetcher.class); | ||
private static final ParseOptions PARSE_OPTIONS = new ParseOptions(); | ||
private static final OpenAPIV3Parser OPENAPI_PARSER = new OpenAPIV3Parser(); | ||
|
||
static { | ||
PARSE_OPTIONS.setResolve(true); | ||
PARSE_OPTIONS.setResolveFully(true); | ||
} | ||
|
||
/** | ||
* Parses the OpenAPI specification directly from the URI. | ||
* | ||
* @param apiSpecUri URI to the API specification (can be file path or web URI). | ||
* @return Parsed OpenAPI object. | ||
* @throws ApiSpecParseException If parsing fails. | ||
*/ | ||
public static OpenAPI fetchApiSpec(String apiSpecUri) { | ||
logger.info("Parsing API spec from URI: {}", apiSpecUri); | ||
SwaggerParseResult result = OPENAPI_PARSER.readLocation(apiSpecUri, null, PARSE_OPTIONS); | ||
OpenAPI openApi = result.getOpenAPI(); | ||
|
||
if (openApi == null) { | ||
throw new ApiSpecParseException("Unable to parse spec from URI: " + apiSpecUri, result.getMessages()); | ||
} | ||
|
||
return openApi; | ||
} | ||
|
||
/** | ||
* Compares the required fields in the API spec with the required enum parameters. | ||
* | ||
* @param requiredEnumParams List of required parameters from the enum. | ||
* @param apiSpecUri URI of the API spec to fetch and compare. | ||
* @param path The API path to check. | ||
* @param method The HTTP method (POST, GET, etc.). | ||
* @return boolean indicating if the required fields match. | ||
*/ | ||
public static boolean compareRequiredFields(List<String> requiredEnumParams, String apiSpecUri, String path, RestRequest.Method method) | ||
throws IllegalArgumentException, ApiSpecParseException { | ||
OpenAPI openAPI = fetchApiSpec(apiSpecUri); | ||
|
||
PathItem pathItem = openAPI.getPaths().get(path); | ||
Content content = getContent(method, pathItem); | ||
MediaType mediaType = content.get(XContentType.JSON.mediaTypeWithoutParameters()); | ||
if (mediaType != null) { | ||
Schema<?> schema = mediaType.getSchema(); | ||
|
||
List<String> requiredApiParams = schema.getRequired(); | ||
if (requiredApiParams != null && !requiredApiParams.isEmpty()) { | ||
return new HashSet<>(requiredEnumParams).equals(new HashSet<>(requiredApiParams)); | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
private static Content getContent(RestRequest.Method method, PathItem pathItem) throws IllegalArgumentException, ApiSpecParseException { | ||
Operation operation; | ||
switch (method) { | ||
case POST: | ||
operation = pathItem.getPost(); | ||
break; | ||
case GET: | ||
operation = pathItem.getGet(); | ||
break; | ||
case PUT: | ||
operation = pathItem.getPut(); | ||
break; | ||
case DELETE: | ||
operation = pathItem.getDelete(); | ||
break; | ||
default: | ||
throw new IllegalArgumentException("Unsupported HTTP method: " + method); | ||
} | ||
|
||
if (operation == null) { | ||
throw new IllegalArgumentException("No operation found for the specified method: " + method); | ||
} | ||
|
||
RequestBody requestBody = operation.getRequestBody(); | ||
if (requestBody == null) { | ||
throw new ApiSpecParseException("No requestBody defined for this operation."); | ||
} | ||
|
||
return requestBody.getContent(); | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
src/test/java/org/opensearch/flowframework/exception/ApiSpecParseExceptionTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
package org.opensearch.flowframework.exception; | ||
|
||
import org.opensearch.OpenSearchException; | ||
import org.opensearch.test.OpenSearchTestCase; | ||
|
||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
public class ApiSpecParseExceptionTests extends OpenSearchTestCase { | ||
|
||
public void testApiSpecParseException() { | ||
ApiSpecParseException exception = new ApiSpecParseException("API spec parsing failed"); | ||
assertTrue(exception instanceof OpenSearchException); | ||
assertEquals("API spec parsing failed", exception.getMessage()); | ||
} | ||
|
||
public void testApiSpecParseExceptionWithCause() { | ||
Throwable cause = new RuntimeException("Underlying issue"); | ||
ApiSpecParseException exception = new ApiSpecParseException("API spec parsing failed", cause); | ||
assertTrue(exception instanceof OpenSearchException); | ||
assertEquals("API spec parsing failed", exception.getMessage()); | ||
assertEquals(cause, exception.getCause()); | ||
} | ||
|
||
public void testApiSpecParseExceptionWithDetailedErrors() { | ||
String message = "API spec parsing failed"; | ||
List<String> details = Arrays.asList("Missing required field", "Invalid type"); | ||
ApiSpecParseException exception = new ApiSpecParseException(message, details); | ||
assertTrue(exception instanceof OpenSearchException); | ||
String expectedMessage = "API spec parsing failed: Missing required field, Invalid type"; | ||
assertEquals(expectedMessage, exception.getMessage()); | ||
} | ||
|
||
} |
Oops, something went wrong.