Skip to content

Commit

Permalink
Parse JSON XContent in the RestHelloAction (opensearch-project#192)
Browse files Browse the repository at this point in the history
* Parse JSON XContent in the RestHelloAction

Signed-off-by: Daniel Widdis <[email protected]>

* Autofix newline and whitespace issues with spotless

Signed-off-by: Daniel Widdis <[email protected]>

* Null check

Signed-off-by: Daniel Widdis <[email protected]>

Signed-off-by: Daniel Widdis <[email protected]>
  • Loading branch information
dbwiddis authored and kokibas committed Mar 17, 2023
1 parent 24ef74e commit b495129
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 122 deletions.
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");
}
} 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"));
}
}

0 comments on commit b495129

Please sign in to comment.