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

Parse JSON XContent in the RestHelloAction #192

Merged
merged 3 commits into from
Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@
*/
package org.opensearch.sdk.sample.helloworld.rest;

import org.opensearch.OpenSearchParseException;
import org.opensearch.common.xcontent.NamedXContentRegistry;
import org.opensearch.common.xcontent.XContentType;
import org.opensearch.extensions.rest.ExtensionRestRequest;
import org.opensearch.extensions.rest.ExtensionRestResponse;
import org.opensearch.rest.RestHandler.Route;
import org.opensearch.rest.RestRequest.Method;
import org.opensearch.sdk.ExtensionRestHandler;

import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
Expand All @@ -26,7 +30,6 @@
import static org.opensearch.rest.RestStatus.BAD_REQUEST;
import static org.opensearch.rest.RestStatus.NOT_ACCEPTABLE;
import static org.opensearch.rest.RestStatus.NOT_FOUND;
import static org.opensearch.rest.RestStatus.NOT_MODIFIED;
import static org.opensearch.rest.RestStatus.OK;

/**
Expand All @@ -35,13 +38,15 @@
public class RestHelloAction implements ExtensionRestHandler {

private static final String GREETING = "Hello, %s!";
private String worldName = "World";
private static final String DEFAULT_NAME = "World";

private String worldName = DEFAULT_NAME;
private List<String> worldAdjectives = new ArrayList<>();
private Random rand = new Random();

@Override
public List<Route> routes() {
return List.of(new Route(GET, "/hello"), new Route(POST, "/hello"), new Route(DELETE, "/hello"), new Route(PUT, "/hello/{name}"));
return List.of(new Route(GET, "/hello"), new Route(POST, "/hello"), new Route(PUT, "/hello/{name}"), new Route(DELETE, "/goodbye"));
}

@Override
Expand All @@ -52,10 +57,10 @@ public ExtensionRestResponse handleRequest(ExtensionRestRequest request) {
return handleGetRequest(request);
} else if (Method.POST.equals(method)) {
return handlePostRequest(request);
} else if (Method.DELETE.equals(method)) {
return handleDeleteRequest(request);
} else if (Method.PUT.equals(method)) {
return handlePutRequest(request);
} else if (Method.DELETE.equals(method)) {
return handleDeleteRequest(request);
}
return handleBadRequest(request);
}
Expand All @@ -75,42 +80,23 @@ private ExtensionRestResponse handlePostRequest(ExtensionRestRequest request) {
// Plain text
adjective = request.content().utf8ToString();
} else if (contentType.equals(XContentType.JSON)) {
adjective = parseJsonAdjective(request.content().utf8ToString());
try {
adjective = request.contentParser(NamedXContentRegistry.EMPTY).mapStrings().get("adjective");
} catch (IOException | OpenSearchParseException e) {
return new ExtensionRestResponse(request, BAD_REQUEST, "Unable to parse adjective from JSON");
}
dbwiddis marked this conversation as resolved.
Show resolved Hide resolved
} else {
return new ExtensionRestResponse(request, NOT_ACCEPTABLE, "Only text and JSON content types are supported");
}
if (!adjective.isBlank()) {
worldAdjectives.add(adjective);
if (adjective != null && !adjective.isBlank()) {
worldAdjectives.add(adjective.trim());
return new ExtensionRestResponse(request, OK, "Added " + adjective + " to words that describe the world!");
}
return new ExtensionRestResponse(request, BAD_REQUEST, "No adjective included with POST request");
}
return new ExtensionRestResponse(request, BAD_REQUEST, "No content included with POST request");
}

private ExtensionRestResponse handleDeleteRequest(ExtensionRestRequest request) {
if (request.hasContent()) {
String adjective = "";
XContentType contentType = request.getXContentType();
if (contentType == null) {
// Plain text
adjective = request.content().utf8ToString();
} else if (contentType.equals(XContentType.JSON)) {
adjective = parseJsonAdjective(request.content().utf8ToString());
} else {
return new ExtensionRestResponse(request, NOT_ACCEPTABLE, "Only text and JSON content types are supported");
}
if (!adjective.isBlank()) {
if (worldAdjectives.remove(adjective)) {
return new ExtensionRestResponse(request, OK, "Goodbye, " + adjective + " world!");
}
return new ExtensionRestResponse(request, NOT_MODIFIED, "");
}
return new ExtensionRestResponse(request, BAD_REQUEST, "No adjective included with DELETE request");
}
return new ExtensionRestResponse(request, BAD_REQUEST, "No content included with DELETE request");
}

private ExtensionRestResponse handlePutRequest(ExtensionRestRequest request) {
String name = request.param("name");
try {
Expand All @@ -121,25 +107,13 @@ private ExtensionRestResponse handlePutRequest(ExtensionRestRequest request) {
return new ExtensionRestResponse(request, OK, "Updated the world's name to " + worldName);
}

private ExtensionRestResponse handleBadRequest(ExtensionRestRequest request) {
return new ExtensionRestResponse(request, NOT_FOUND, "Extension REST action improperly configured to handle " + request.toString());
private ExtensionRestResponse handleDeleteRequest(ExtensionRestRequest request) {
this.worldName = DEFAULT_NAME;
this.worldAdjectives.clear();
return new ExtensionRestResponse(request, OK, "Goodbye, cruel world! Restored default values.");
}

private String parseJsonAdjective(String json) {
// TODO: Once CreateComponents has an XContentRegistry available we can parse from there
// For now we just hack our way into the result.
boolean foundLabel = false;
boolean foundColon = false;
for (String s : json.split("\"")) {
if (!foundLabel) {
foundLabel = "adjective".equals(s);
} else if (!foundColon) {
foundColon = s.contains(":");
} else {
// This is the adjective!
return s;
}
}
return "";
private ExtensionRestResponse handleBadRequest(ExtensionRestRequest request) {
return new ExtensionRestResponse(request, NOT_FOUND, "Extension REST action improperly configured to handle " + request.toString());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"openapi": "3.1.0",
"openapi": "3.0.3",
"info": {
"title": "Hello World",
"description": "This is a sample Hello World extension.",
Expand Down Expand Up @@ -48,38 +48,36 @@
"tags": [
"hello"
],
"summary": "Adds an adjective to a list",
"summary": "Adds a descriptive world adjective to a list",
"description": "Adds an adjective to a list from which a random element will be prepended to the world name",
"operationId": "",
"responses": {
"200": {
"description": "Successful operation"
},
"400": {
"description": "Syntax Error in request"
},
"404": {
"description": "Improper REST action configuration"
},
"406": {
"description": "Content format not text or JSON"
"requestBody": {
"description": "An adjective in plain text or JSON",
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"adjective": {
"type": "string",
"example": "wonderful"
}
}
}
},
"text/plain": {
"schema": {
"type": "string",
"example": "wonderful"
}
}
}
}
},
"delete": {
"tags": [
"hello"
],
"summary": "Removes an adjective from the list",
"description": "Removes an adjective from the list from which a random element will be prepended to the world name",
"operationId": "",
},
"responses": {
"200": {
"description": "Successful operation"
},
"304": {
"description": "Adjective not in the list, no action taken"
},
"400": {
"description": "Syntax Error in request"
},
Expand All @@ -97,8 +95,8 @@
"tags": [
"hello"
],
"summary": "Update world name",
"description": "Rename the world",
"summary": "Rename the world",
"description": "Update the world to a custom name",
"parameters": [
{
"name": "name",
Expand Down Expand Up @@ -131,6 +129,21 @@
}
}
}
},
"/goodbye": {
"delete": {
"tags": [
"hello"
],
"summary": "Restores the world to default",
"description": "Removes all adjectives and the custom world name",
"operationId": "",
"responses": {
"200": {
"description": "Successful operation"
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
openapi: 3.1.0
openapi: 3.0.3
info:
title: Hello World
description: This is a sample Hello World extension.
Expand Down Expand Up @@ -30,12 +30,27 @@ paths:
description: Improper REST action configuration
post:
tags:
- hello
summary: Adds an adjective to a list
- hello
summary: Adds a descriptive world adjective to a list
description: >-
Adds an adjective to a list from which a random element will be
prepended to the world name
operationId: ''
requestBody:
description: An adjective in plain text or JSON
required: true
content:
application/json:
schema:
type: object
properties:
adjective:
type: string
example: wonderful
text/plain:
schema:
type: string
example: wonderful
responses:
'200':
description: Successful operation
Expand All @@ -45,31 +60,12 @@ paths:
description: Improper REST action configuration
'406':
description: Content format not text or JSON
delete:
tags:
- hello
summary: Removes an adjective from the list
description: >-
Removes an adjective from the list from which a random element
will be prepended to the world name
operationId: ''
responses:
'200':
description: Successful operation
'304':
description: Adjective not in the list, no action taken
'400':
description: Syntax Error in request
'404':
description: Improper REST action configuration
'406':
description: Content format not text or JSON
/hello/{name}:
put:
tags:
- hello
summary: Update world name
description: Rename the world
summary: Rename the world
description: Update the world to a custom name
parameters:
- name: name
in: path
Expand All @@ -89,3 +85,14 @@ paths:
description: Syntax Error in URI
'404':
description: Improper REST action configuration
/goodbye:
delete:
tags:
- hello
summary: Restores the world to default
description: >-
Removes all adjectives and the custom world name
operationId: ''
responses:
'200':
description: Successful operation
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ public void testRoutes() {
assertEquals("/hello", routes.get(0).getPath());
assertEquals(Method.POST, routes.get(1).getMethod());
assertEquals("/hello", routes.get(1).getPath());
assertEquals(Method.DELETE, routes.get(2).getMethod());
assertEquals("/hello", routes.get(2).getPath());
assertEquals(Method.PUT, routes.get(3).getMethod());
assertEquals("/hello/{name}", routes.get(3).getPath());
assertEquals(Method.PUT, routes.get(2).getMethod());
assertEquals("/hello/{name}", routes.get(2).getPath());
assertEquals(Method.DELETE, routes.get(3).getMethod());
assertEquals("/goodbye", routes.get(3).getPath());
}

@Test
Expand All @@ -79,14 +79,7 @@ public void testHandleRequest() {
new BytesArray("{\"adjective\":\"testable\"}"),
token
);
ExtensionRestRequest deleteRequest = new ExtensionRestRequest(
Method.DELETE,
"/hello",
params,
null,
new BytesArray("testable"),
token
);
ExtensionRestRequest deleteRequest = new ExtensionRestRequest(Method.DELETE, "/goodbye", params, null, new BytesArray(""), token);
ExtensionRestRequest badRequest = new ExtensionRestRequest(
Method.PUT,
"/hello/Bad%Request",
Expand All @@ -95,7 +88,7 @@ public void testHandleRequest() {
new BytesArray(""),
token
);
ExtensionRestRequest unhandledRequest = new ExtensionRestRequest(Method.HEAD, "/goodbye", params, null, new BytesArray(""), token);
ExtensionRestRequest unhandledRequest = new ExtensionRestRequest(Method.HEAD, "/hi", params, null, new BytesArray(""), token);

// Initial default response
RestResponse response = restHelloAction.handleRequest(getRequest);
Expand Down Expand Up @@ -130,22 +123,18 @@ public void testHandleRequest() {
responseStr = new String(BytesReference.toBytes(response.content()), StandardCharsets.UTF_8);
assertEquals("Hello, testable Passing Test!", responseStr);

// Remove the adjective
// Remove the name and adjective
response = restHelloAction.handleRequest(deleteRequest);
assertEquals(RestStatus.OK, response.status());
assertEquals(BytesRestResponse.TEXT_CONTENT_TYPE, response.contentType());
responseStr = new String(BytesReference.toBytes(response.content()), StandardCharsets.UTF_8);
assertTrue(responseStr.contains("testable"));
assertTrue(responseStr.contains("Goodbye, cruel world!"));

response = restHelloAction.handleRequest(getRequest);
assertEquals(RestStatus.OK, response.status());
assertEquals(BytesRestResponse.TEXT_CONTENT_TYPE, response.contentType());
responseStr = new String(BytesReference.toBytes(response.content()), StandardCharsets.UTF_8);
assertEquals("Hello, Passing Test!", responseStr);

// Try to remove nonexistent adjective
response = restHelloAction.handleRequest(deleteRequest);
assertEquals(RestStatus.NOT_MODIFIED, response.status());
assertEquals("Hello, World!", responseStr);

// Unparseable
response = restHelloAction.handleRequest(badRequest);
Expand All @@ -159,6 +148,6 @@ public void testHandleRequest() {
assertEquals(RestStatus.NOT_FOUND, response.status());
assertEquals(BytesRestResponse.TEXT_CONTENT_TYPE, response.contentType());
responseStr = new String(BytesReference.toBytes(response.content()), StandardCharsets.UTF_8);
assertTrue(responseStr.contains("/goodbye"));
assertTrue(responseStr.contains("/hi"));
}
}