feat(query): Implement query endpoint
This commit is contained in:
parent
5d82987cc6
commit
aa9a870e94
9 changed files with 326 additions and 49 deletions
44
ilp-cw-api/[POST] query/GT LT -contradict.bru
Normal file
44
ilp-cw-api/[POST] query/GT LT -contradict.bru
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
meta {
|
||||||
|
name: GT LT -contradict
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{API_BASE}}/query
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"attribute": "capacity",
|
||||||
|
"operator": ">",
|
||||||
|
"value": "8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"attribute": "capacity",
|
||||||
|
"operator": "<",
|
||||||
|
"value": "8"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
res.body: length 0
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("Response body is a JSON array", function() {
|
||||||
|
expect(res.getBody()).to.be.an('array');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
53
ilp-cw-api/[POST] query/GT LT EQ.bru
Normal file
53
ilp-cw-api/[POST] query/GT LT EQ.bru
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
meta {
|
||||||
|
name: GT LT EQ
|
||||||
|
type: http
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{API_BASE}}/query
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"attribute": "capacity",
|
||||||
|
"operator": ">",
|
||||||
|
"value": "8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"attribute": "maxMoves",
|
||||||
|
"operator": "<",
|
||||||
|
"value": "2000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"attribute": "cooling",
|
||||||
|
"operator": "=",
|
||||||
|
"value": "true"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
res.body: length 1
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("Response body is a JSON array", function() {
|
||||||
|
expect(res.getBody()).to.be.an('array');
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Array is not empty and contains Strings", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(data[0]).to.be.a('string'); // data should be in string
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
48
ilp-cw-api/[POST] query/GT LT.bru
Normal file
48
ilp-cw-api/[POST] query/GT LT.bru
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
meta {
|
||||||
|
name: GT LT
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{API_BASE}}/query
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"attribute": "capacity",
|
||||||
|
"operator": ">",
|
||||||
|
"value": "8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"attribute": "maxMoves",
|
||||||
|
"operator": "<",
|
||||||
|
"value": "2000"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
res.body: length 2
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("Response body is a JSON array", function() {
|
||||||
|
expect(res.getBody()).to.be.an('array');
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Array is not empty and contains Strings", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(data[0]).to.be.a('string'); // data should be in string
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
8
ilp-cw-api/[POST] query/folder.bru
Normal file
8
ilp-cw-api/[POST] query/folder.bru
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
meta {
|
||||||
|
name: [POST] query
|
||||||
|
seq: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
auth {
|
||||||
|
mode: inherit
|
||||||
|
}
|
||||||
53
ilp-cw-api/[POST] query/three eqs.bru
Normal file
53
ilp-cw-api/[POST] query/three eqs.bru
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
meta {
|
||||||
|
name: three eqs
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{API_BASE}}/query
|
||||||
|
body: json
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"attribute": "capacity",
|
||||||
|
"operator": "=",
|
||||||
|
"value": "20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"attribute": "heating",
|
||||||
|
"operator": "=",
|
||||||
|
"value": "false"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"attribute": "cooling",
|
||||||
|
"operator": "=",
|
||||||
|
"value": "true"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
res.status: eq 200
|
||||||
|
res.body: length 1
|
||||||
|
}
|
||||||
|
|
||||||
|
tests {
|
||||||
|
test("Response body is a JSON array", function() {
|
||||||
|
expect(res.getBody()).to.be.an('array');
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Array is not empty and contains Strings", function() {
|
||||||
|
const data = res.getBody();
|
||||||
|
expect(data[0]).to.be.a('string'); // data should be in string
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
package io.github.js0ny.ilp_coursework.data;
|
package io.github.js0ny.ilp_coursework.data;
|
||||||
|
|
||||||
|
// TODO: Convert operator to Enum
|
||||||
|
// import io.github.js0ny.ilp_coursework.util.AttrOperator;
|
||||||
|
|
||||||
public record AttrComparatorDto(String attribute, String operator, String value) {
|
public record AttrComparatorDto(String attribute, String operator, String value) {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import io.github.js0ny.ilp_coursework.data.AttrComparatorDto;
|
||||||
import io.github.js0ny.ilp_coursework.data.DroneDto;
|
import io.github.js0ny.ilp_coursework.data.DroneDto;
|
||||||
import io.github.js0ny.ilp_coursework.util.AttrOperator;
|
import io.github.js0ny.ilp_coursework.util.AttrOperator;
|
||||||
|
|
||||||
|
import static io.github.js0ny.ilp_coursework.util.AttrComparator.isValueMatched;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
@ -41,10 +43,13 @@ public class DroneInfoService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an array of ids of drones with/without cooling capability
|
* Return an array of ids of drones with/without cooling capability
|
||||||
|
* <p>
|
||||||
|
* Associated service method with {@code /dronesWithCooling/{state}}
|
||||||
*
|
*
|
||||||
* @param state determines the capability filtering
|
* @param state determines the capability filtering
|
||||||
* @return if {@code state} is true, return ids of drones with cooling
|
* @return if {@code state} is true, return ids of drones with cooling
|
||||||
* capability, else without cooling
|
* capability, else without cooling
|
||||||
|
* @see io.github.js0ny.ilp_coursework.controller.DroneController#getDronesWithCoolingCapability(boolean)
|
||||||
*/
|
*/
|
||||||
public String[] dronesWithCooling(boolean state) {
|
public String[] dronesWithCooling(boolean state) {
|
||||||
URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
|
URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
|
||||||
|
|
@ -64,6 +69,8 @@ public class DroneInfoService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a {@link DroneDto}-style json data structure with the given {@code id}
|
* Return a {@link DroneDto}-style json data structure with the given {@code id}
|
||||||
|
* <p>
|
||||||
|
* Associated service method with {@code /droneDetails/{id}}
|
||||||
*
|
*
|
||||||
* @param id The id of the drone
|
* @param id The id of the drone
|
||||||
* @return drone json body of given id
|
* @return drone json body of given id
|
||||||
|
|
@ -72,6 +79,7 @@ public class DroneInfoService {
|
||||||
* @throws IllegalArgumentException when drone with given {@code id} cannot be
|
* @throws IllegalArgumentException when drone with given {@code id} cannot be
|
||||||
* found
|
* found
|
||||||
* this should lead to a 404
|
* this should lead to a 404
|
||||||
|
* @see io.github.js0ny.ilp_coursework.controller.DroneController#getDroneDetail(String)
|
||||||
*/
|
*/
|
||||||
public DroneDto droneDetail(String id) {
|
public DroneDto droneDetail(String id) {
|
||||||
URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
|
URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
|
||||||
|
|
@ -89,30 +97,68 @@ public class DroneInfoService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will result in 404
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"drone with that ID cannot be found");
|
"drone with that ID cannot be found");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return an array of ids of drones with a given attribute name and value
|
* Return an array of ids of drones with a given attribute name and value.
|
||||||
|
* <p>
|
||||||
|
* Associated service method with {@code /queryAsPath/{attrName}/{attrVal}}
|
||||||
*
|
*
|
||||||
* @param attrName the attribute name to filter on
|
* @param attrName the attribute name to filter on
|
||||||
* @param attrVal the attribute value to filter on
|
* @param attrVal the attribute value to filter on
|
||||||
* @return array of drone ids matching the attribute name and value
|
* @return array of drone ids matching the attribute name and value
|
||||||
|
* @see #dronesWithAttributeCompared(String, String, AttrOperator)
|
||||||
|
* @see io.github.js0ny.ilp_coursework.controller.DroneController#getIdByAttrMap
|
||||||
*/
|
*/
|
||||||
public String[] dronesWithAttribute(String attrName, String attrVal) {
|
public String[] dronesWithAttribute(String attrName, String attrVal) {
|
||||||
// Call the helper with EQ operator
|
// Call the helper with EQ operator
|
||||||
return dronesWithAttributeCompared(attrName, attrVal, AttrOperator.EQ);
|
return dronesWithAttributeCompared(attrName, attrVal, AttrOperator.EQ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an array of ids of drones which matches all given complex comparing rules
|
||||||
|
*
|
||||||
|
* @param attrComparators The filter rule with Name, Value and Operator
|
||||||
|
* @return array of drone ids that matches all rules
|
||||||
|
*/
|
||||||
|
public String[] dronesSatisfyingAttributes(AttrComparatorDto[] attrComparators) {
|
||||||
|
Set<String> matchingDroneIds = null;
|
||||||
|
for (var comparator : attrComparators) {
|
||||||
|
String attribute = comparator.attribute();
|
||||||
|
String operator = comparator.operator();
|
||||||
|
String value = comparator.value();
|
||||||
|
AttrOperator op = AttrOperator.fromString(operator);
|
||||||
|
String[] ids = dronesWithAttributeCompared(attribute, value, op);
|
||||||
|
if (matchingDroneIds == null) {
|
||||||
|
matchingDroneIds = new HashSet<>(Arrays.asList(ids));
|
||||||
|
} else {
|
||||||
|
matchingDroneIds.retainAll(Arrays.asList(ids));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchingDroneIds == null) {
|
||||||
|
return new String[]{};
|
||||||
|
}
|
||||||
|
return matchingDroneIds.toArray(String[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper that wraps the dynamic querying with different comparison operators
|
* Helper that wraps the dynamic querying with different comparison operators
|
||||||
|
* <p>
|
||||||
|
* This method act as a concatenation of
|
||||||
|
* {@link io.github.js0ny.ilp_coursework.util.AttrComparator#isValueMatched(JsonNode, String,
|
||||||
|
* AttrOperator)}
|
||||||
*
|
*
|
||||||
* @param attrName the attribute name to filter on
|
* @param attrName the attribute name to filter on
|
||||||
* @param attrVal the attribute value to filter on
|
* @param attrVal the attribute value to filter on
|
||||||
* @param op the comparison operator
|
* @param op the comparison operator
|
||||||
* @return array of drone ids matching the attribute name and value (filtered by
|
* @return array of drone ids matching the attribute name and value (filtered by
|
||||||
* {@code op})
|
* {@code op})
|
||||||
|
* @see io.github.js0ny.ilp_coursework.util.AttrComparator#isValueMatched(JsonNode,
|
||||||
|
* String,
|
||||||
|
* AttrOperator)
|
||||||
*/
|
*/
|
||||||
private String[] dronesWithAttributeCompared(String attrName, String attrVal, AttrOperator op) {
|
private String[] dronesWithAttributeCompared(String attrName, String attrVal, AttrOperator op) {
|
||||||
URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
|
URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
|
||||||
|
|
@ -144,49 +190,7 @@ public class DroneInfoService {
|
||||||
.toArray(String[]::new);
|
.toArray(String[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement this
|
|
||||||
public String[] dronesSatisfyingAttributes(AttrComparatorDto[] attrComparators) {
|
|
||||||
Set<String> matchingDroneIds = null;
|
|
||||||
for (var comparator : attrComparators) {
|
|
||||||
String attribute = comparator.attribute();
|
|
||||||
String operator = comparator.operator();
|
|
||||||
String value = comparator.value();
|
|
||||||
AttrOperator op = AttrOperator.fromString(operator);
|
|
||||||
String[] ids = new String[] {};// Arrays.stream
|
|
||||||
// isValueMatched(attribute, value, op);
|
|
||||||
if (matchingDroneIds == null) {
|
|
||||||
matchingDroneIds = new HashSet<>(Arrays.asList(ids));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (matchingDroneIds == null) {
|
|
||||||
return new String[] {};
|
|
||||||
}
|
|
||||||
return matchingDroneIds.toArray(String[]::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper for dynamic querying, to compare the json value with given value in
|
|
||||||
* {@code String}.
|
|
||||||
*
|
|
||||||
* @param node The {@code JsonNode} to be compared
|
|
||||||
* @param attrVal The Value passed, in {@code String}
|
|
||||||
* @return {@code true} if given values are equal, otherwise false.
|
|
||||||
*/
|
|
||||||
private boolean isValueMatched(JsonNode node, String attrVal, AttrOperator op) {
|
|
||||||
if (node.isTextual()) {
|
|
||||||
return node.asText().equals(attrVal);
|
|
||||||
} else if (node.isNumber()) {
|
|
||||||
return Double.compare(node.asDouble(), Double.parseDouble(attrVal)) == 0;
|
|
||||||
} else if (node.isBoolean()) {
|
|
||||||
return node.asText().equals(attrVal);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int[] dronesMatchesRequirements() {
|
public int[] dronesMatchesRequirements() {
|
||||||
return new int[]{};
|
return new int[]{};
|
||||||
}
|
}
|
||||||
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package io.github.js0ny.ilp_coursework.util;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparator for attribute values in {@code JsonNode}.
|
||||||
|
*
|
||||||
|
* This is a helper for dynamic querying.
|
||||||
|
*/
|
||||||
|
public class AttrComparator {
|
||||||
|
/**
|
||||||
|
* Helper for dynamic querying, to compare the json value with given value in
|
||||||
|
* {@code String}.
|
||||||
|
*
|
||||||
|
* @param node The {@code JsonNode} to be compared
|
||||||
|
* @param attrVal The Value passed, in {@code String}
|
||||||
|
* @param op The comparison operator
|
||||||
|
* @return {@code true} if given values are equal, otherwise false.
|
||||||
|
*/
|
||||||
|
public static boolean isValueMatched(JsonNode node, String attrVal, AttrOperator op) {
|
||||||
|
if (node.isTextual()) {
|
||||||
|
return compareStrings(node.asText(), attrVal, op);
|
||||||
|
} else if (node.isNumber()) {
|
||||||
|
// return Double.compare(node.asDouble(), Double.parseDouble(attrVal)) == 0;
|
||||||
|
return compareNumbers(node.decimalValue(), new BigDecimal(attrVal), op);
|
||||||
|
} else if (node.isBoolean()) {
|
||||||
|
return compareBooleans(node.asBoolean(), Boolean.parseBoolean(attrVal), op);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean compareNumbers(BigDecimal nodeVal, BigDecimal attrVal, AttrOperator op) {
|
||||||
|
int comparison = nodeVal.compareTo(attrVal);
|
||||||
|
return switch (op) {
|
||||||
|
case EQ -> comparison == 0;
|
||||||
|
case GT -> comparison > 0;
|
||||||
|
case LT -> comparison < 0;
|
||||||
|
case NE -> comparison != 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean compareStrings(String nodeVal, String attrVal, AttrOperator op) {
|
||||||
|
return switch (op) {
|
||||||
|
case EQ -> nodeVal.equals(attrVal);
|
||||||
|
default -> !nodeVal.equals(attrVal);
|
||||||
|
// case NE -> !nodeVal.equals(attrVal);
|
||||||
|
// case GT -> !nodeVal.equals(attrVal);// > 0;
|
||||||
|
// case LT -> !nodeVal.equals(attrVal);// < 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean compareBooleans(boolean nodeVal, boolean attrVal, AttrOperator op) {
|
||||||
|
return switch (op) {
|
||||||
|
case EQ -> nodeVal == attrVal;
|
||||||
|
default -> nodeVal != attrVal;
|
||||||
|
// case NE -> nodeVal != attrVal;
|
||||||
|
// case GT -> !nodeVal && attrVal; // false < true
|
||||||
|
// case LT -> nodeVal && !attrVal; // true > false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ package io.github.js0ny.ilp_coursework.util;
|
||||||
|
|
||||||
public enum AttrOperator {
|
public enum AttrOperator {
|
||||||
EQ("="),
|
EQ("="),
|
||||||
NEQ("!="),
|
NE("!="),
|
||||||
GT(">"),
|
GT(">"),
|
||||||
LT("<");
|
LT("<");
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue