Skip to content

Commit

Permalink
initial simpleAPI with some integer field type fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
eviltester committed Apr 6, 2024
1 parent 0aca49c commit b3ebfc0
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public static void main(String[] args) {
if(arg.toLowerCase().startsWith("-unlimitedtodos")){
// remove the limit on number of todos
logger.info("Enabling Unlimited TODO Instances");
thingifier.getDefinitionNamed("todo").setMaxInstanceLimit();
thingifier.getDefinitionNamed("todo").setNoMaxInstanceLimit();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import uk.co.compendiumdev.challenge.gui.ChallengerWebGUI;
import uk.co.compendiumdev.challenge.persistence.PersistenceLayer;
import uk.co.compendiumdev.challenge.practicemodes.mirror.MirrorRoutes;
import uk.co.compendiumdev.challenge.practicemodes.simpleapi.SimpleApiRoutes;
import uk.co.compendiumdev.challenge.practicemodes.simulation.SimulationRoutes;
import uk.co.compendiumdev.thingifier.Thingifier;
import uk.co.compendiumdev.thingifier.api.docgen.ThingifierApiDocumentationDefn;
Expand Down Expand Up @@ -84,6 +85,8 @@ public ChallengeRouteHandler configureRoutes() {
// Simulation routes should not show
new SimulationRoutes().configure();

new SimpleApiRoutes().configure();

return this;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package uk.co.compendiumdev.challenge.practicemodes.simpleapi;

import uk.co.compendiumdev.thingifier.core.domain.datapopulator.DataPopulator;
import uk.co.compendiumdev.thingifier.core.domain.definitions.ERSchema;
import uk.co.compendiumdev.thingifier.core.domain.instances.ERInstanceData;
import uk.co.compendiumdev.thingifier.core.domain.instances.EntityInstanceCollection;

import java.util.Random;

public class SimpleAPITestDataPopulator implements DataPopulator {

@Override
public void populate(final ERSchema schema, final ERInstanceData database) {

String [] types={
"book",
"book",
"dvd",
"blu-ray",
"cd",
"cd",
"dvd",
"blu-ray"};

EntityInstanceCollection items = database.getInstanceCollectionForEntityNamed("item");

Random random = new Random();
for(String type : types){
items.createManagedInstance().
setValue("type", type).
setValue("numberinstock", String.valueOf(random.nextInt(20))).
setValue("isbn13", randomIsbn(random)).
setValue("price", String.valueOf(random.nextInt(99)) + "." + String.valueOf(random.nextInt(99)) )
;
}
}

private String randomIsbn(Random random){

String isbn13 = "xxx-x-xx-xxxxxx-x";

while(isbn13.contains("x")){
isbn13 = isbn13.replaceFirst("x", String.valueOf(random.nextInt(9)));
}

return isbn13;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package uk.co.compendiumdev.challenge.practicemodes.simpleapi;

import uk.co.compendiumdev.thingifier.Thingifier;
import uk.co.compendiumdev.thingifier.api.docgen.ThingifierApiDocumentationDefn;
import uk.co.compendiumdev.thingifier.apiconfig.ThingifierApiConfig;
import uk.co.compendiumdev.thingifier.application.httprouting.ThingifierAutoDocGenRouting;
import uk.co.compendiumdev.thingifier.application.httprouting.ThingifierHttpApiRoutings;
import uk.co.compendiumdev.thingifier.core.EntityRelModel;
import uk.co.compendiumdev.thingifier.core.domain.definitions.EntityDefinition;
import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.Field;
import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.FieldType;
import uk.co.compendiumdev.thingifier.core.domain.definitions.validation.MatchesRegexValidationRule;
import uk.co.compendiumdev.thingifier.core.domain.definitions.validation.MaximumLengthValidationRule;
import uk.co.compendiumdev.thingifier.htmlgui.htmlgen.DefaultGUIHTML;
import uk.co.compendiumdev.thingifier.htmlgui.routing.DefaultGuiRoutings;

/*
The simple API is a no-auth API where anyone can amend, create, delete items.
To make this Safe all the fields will be primitives and no Strings.
*/
public class SimpleApiRoutes {

public Thingifier simplethings;
public EntityDefinition entityDefn;
private ThingifierApiDocumentationDefn apiDocDefn;
private ThingifierAutoDocGenRouting simpleApiDocsRouting;
private ThingifierHttpApiRoutings simpleApiHttpRouting;
private DefaultGuiRoutings simpleApiGuiRouting;

public SimpleApiRoutes(){
// fake the data storage
this.simplethings = new Thingifier();

simplethings.setDocumentation(
"Simple API Mode",
"A simple API, no auth protection so you can add and delete what you want in a multi-user mode.");
this.entityDefn = simplethings.defineThing("item", "items", 100);

// TODO: add descriptions on a field level to explain what they are and show this in documentation
this.entityDefn.addAsPrimaryKeyField(Field.is("id", FieldType.AUTO_INCREMENT));
this.entityDefn.addFields(
Field.is("type", FieldType.ENUM).
makeMandatory().
withExample("book").
withExample("blu-ray").
withExample("cd").
withExample("dvd"),
Field.is("isbn13", FieldType.STRING).
makeMandatory().
withValidation(new MatchesRegexValidationRule("[0-9]{3}[-]?[0-9]{1}[-]?[0-9]{2}[-]?[0-9]{6}[-]?[0-9]{1}")).
withValidation(new MaximumLengthValidationRule(17)).
withExample("123-4-56-789012-3"),
Field.is("price",FieldType.FLOAT).
makeMandatory().
withExample("97.99").
withMinimumValue(0f).
withMaximumValue(50000.0f),
Field.is("numberinstock", FieldType.INTEGER).
withDefaultValue("0").
withMaximumValue(100).
withMinimumValue(0)
);

simplethings.setDataGenerator(new SimpleAPITestDataPopulator());

simplethings.apiConfig().setFrom(new ThingifierApiConfig("/simpleapi"));
// do not convert floats to int
simplethings.apiConfig().setApiToEnforceDeclaredTypesInInput(false);

// TODO: should probably have a support multiple databases config somewhere
simplethings.getERmodel().populateDatabase(EntityRelModel.DEFAULT_DATABASE_NAME);

}

public void configure() {

DefaultGUIHTML gui = new DefaultGUIHTML();

simpleApiGuiRouting = new DefaultGuiRoutings(simplethings, gui).
configureRoutes("/simpleapi/gui");

apiDocDefn = new ThingifierApiDocumentationDefn();
apiDocDefn.setThingifier(simplethings);
apiDocDefn.setPathPrefix("/simpleapi"); // where can the API endpoints be found
simpleApiDocsRouting = new ThingifierAutoDocGenRouting(
simplethings,
apiDocDefn,
gui);

simpleApiHttpRouting = new ThingifierHttpApiRoutings(simplethings, apiDocDefn);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.Field;
import uk.co.compendiumdev.thingifier.core.domain.definitions.field.definition.FieldType;
import uk.co.compendiumdev.thingifier.core.domain.definitions.field.instance.NamedValue;
import uk.co.compendiumdev.thingifier.core.domain.definitions.relationship.RelationshipVectorDefinition;
import uk.co.compendiumdev.thingifier.core.domain.instances.InstanceFields;

Expand Down Expand Up @@ -125,7 +124,7 @@ public boolean hasMaxInstanceLimit() {
return maxInstanceCount>=0;
}

public void setMaxInstanceLimit() {
public void setNoMaxInstanceLimit() {
maxInstanceCount = EntityDefinition.NO_INSTANCE_LIMIT;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import uk.co.compendiumdev.thingifier.core.domain.definitions.field.instance.FieldValue;
import uk.co.compendiumdev.thingifier.core.domain.definitions.validation.ValidationRule;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

Expand Down Expand Up @@ -189,7 +190,7 @@ private void validateObjectValue(final FieldValue value, final ValidationReport

private void validateEnumValue(final FieldValue value, final ValidationReport report) {
if (!getExamples().contains(value.asString())) {
reportThisValueDoesNotMatchType(report, value.asString());
reportThisValueDoesNotMatchType(report, value.asString(), getExamples());
}
}

Expand All @@ -214,14 +215,23 @@ private void validateIntegerValue(final FieldValue value,
final ValidationReport report) {
try {

int intVal = value.asInteger();
// integers can come in from JSON as doubles
BigDecimal intFloatValue = new BigDecimal(value.asString());

BigDecimal fractionalPart = intFloatValue.abs().subtract(new BigDecimal(intFloatValue.abs().toBigInteger()));

if(!(fractionalPart.equals(new BigDecimal("0")) || fractionalPart.equals(new BigDecimal("0.0")))){
throw new NumberFormatException();
}

int intVal = intFloatValue.intValue();

if (!withinAllowedIntegerRange(intVal)) {
report.setValid(false);
report.addErrorMessage(
String.format(
"%s : %s is not within range for type %s (%d to %d)",
this.getName(), value.asString(),
"%s : %d is not within range for type %s (%d to %d)",
this.getName(), intVal,
type, minimumIntegerValue, maximumIntegerValue));
}
} catch (NumberFormatException e) {
Expand All @@ -232,11 +242,24 @@ private void validateIntegerValue(final FieldValue value,

private void reportThisValueDoesNotMatchType(final ValidationReport report,
final String valueString) {
reportThisValueDoesNotMatchType(report, valueString, List.of());
}

private void reportThisValueDoesNotMatchType(final ValidationReport report,
final String valueString,
final List<String> validValues) {

String reportValids = "";

if(validValues!=null && !validValues.isEmpty()){
reportValids = " - valid values are [%s]".formatted(String.join(",",validValues));
}

report.setValid(false);
report.addErrorMessage(
String.format(
"%s : %s does not match type %s",
name, valueString, type));
"%s : %s does not match type %s%s",
name, valueString, type, reportValids));
}

private void validateBooleanValue(final FieldValue value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,21 @@ public void cannotSetAValueOutwithMaxMinValueForIntegerFields(){
});
}

@Test
public void canSetIntegerField(){

EntityDefinition stringFieldEntity = new EntityDefinition("Test Session", "Test Sessions");
stringFieldEntity.addFields(Field.is("integer", FieldType.INTEGER).
withMaximumValue(100).
withMinimumValue(50)
);

EntityInstance instance = new EntityInstance(stringFieldEntity);

instance.setValue("integer", "75");

Assertions.assertEquals(instance.getFieldValue("integer").asString(),"75");
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@ public String getDescription(){
}

public void setPathPrefix(String prefix) {
pathPrefix = prefix;
String slash = "";
if(prefix!=null && !prefix.startsWith("/")){
slash = "/";
}
pathPrefix = slash + prefix;
}

public String getPathPrefix() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ public final class HttpApiRequest {
// a storage for the raw headers, which might include duplicates
private ArrayList<StringPair> headersList;

public void removePrefixFromPath(String prefix) {
if(path.startsWith(prefix)){
path = justThePath(path.replaceFirst(prefix,""));
}
}

public enum VERB{ GET, HEAD, POST, PUT, DELETE, PATCH, OPTIONS, CONNECT, TRACE}

public HttpApiRequest(final String pathInfo) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import uk.co.compendiumdev.thingifier.Thingifier;
import uk.co.compendiumdev.thingifier.api.http.bodyparser.BodyParser;
import uk.co.compendiumdev.thingifier.api.response.ApiResponse;
import uk.co.compendiumdev.thingifier.api.restapihandlers.SessionHeaderParser;
import uk.co.compendiumdev.thingifier.application.httpapimessagehooks.HttpApiRequestHook;
import uk.co.compendiumdev.thingifier.application.httpapimessagehooks.HttpApiResponseHook;
import uk.co.compendiumdev.thingifier.api.ermodelconversion.JsonThing;
Expand Down Expand Up @@ -57,6 +58,17 @@ public ThingifierHttpApi(final Thingifier aThingifier,

private HttpApiResponse handleRequest(final HttpApiRequest request, HttpVerb verb){

// if the request.url has the 'prefix' then remove the prefix and process the request
//if(request.getPath())

String prefix = thingifier.apiConfig().getApiEndPointPrefix();
if(prefix!= null && !prefix.isEmpty()){
if(prefix.startsWith("/")){
prefix = prefix.substring(1);
}
request.removePrefixFromPath(prefix);
}

// any pre-request override processing
HttpApiResponse httpResponse = runTheHttpApiRequestHooksOn(request);

Expand Down Expand Up @@ -117,7 +129,8 @@ public ApiResponse routeAndProcessRequest(final HttpApiRequest request,
ApiResponse apiResponse=null;

// if there is a session id and we have not created the erm yet, then do that now
createDatabaseBasedOnSessionHeaderUIfNecessary(request.getHeader(HTTP_SESSION_HEADER_NAME));
String databaseToUse = SessionHeaderParser.getDatabaseNameFromHeaderValue(request.getHeaders());
createDatabaseBasedOnSessionHeaderUIfNecessary(databaseToUse);

switch (verb){
case GET:
Expand Down Expand Up @@ -174,8 +187,9 @@ public HttpApiResponse query(final HttpApiRequest request, final String query) {

HttpApiResponse httpResponse = runTheHttpApiRequestHooksOn(request);

// if there is a session id and we have not created the erm yet, then do that now
createDatabaseBasedOnSessionHeaderUIfNecessary(request.getHeader(HTTP_SESSION_HEADER_NAME));
// if there is a session id and we have not created the erm yet, then do that
String databaseToUse = SessionHeaderParser.getDatabaseNameFromHeaderValue(request.getHeaders());
createDatabaseBasedOnSessionHeaderUIfNecessary(databaseToUse);

if(httpResponse==null) {
ApiResponse apiResponse = thingifier.api().get(query, request.getFilterableQueryParams(), request.getHeaders());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ private List<Map.Entry<String,String>> flattenToStringMap(final String prefixkey
if(theValue instanceof Boolean){
stringsInMap.add(new AbstractMap.SimpleEntry<String,String>(prefixkey, String.valueOf(theValue)));
}
if(theValue instanceof Integer){
stringsInMap.add(new AbstractMap.SimpleEntry<String,String>(prefixkey, String.valueOf(theValue)));
}
// todo: what else can come in?
String separator = "";
if(prefixkey!=null && prefixkey.length() > 0 && !prefixkey.endsWith(".")){
Expand Down

0 comments on commit b3ebfc0

Please sign in to comment.