Skip to content
This repository has been archived by the owner on Dec 22, 2022. It is now read-only.

Commit

Permalink
Added a bunch of endpoints including:
Browse files Browse the repository at this point in the history
- run debug resultsonly for BusinessServices,
- WARNING, API BREAKING CHANGE: changed the run, debug and resultsonly endpoints for "all" to conform to the new endpoints for BusinessServices
- added an information/:BusinessService endpoint which provides detailed information for the named BusinessService
- the businessservices endpoint now provides a map of businessservices, where the key is the endpoint of the service and the value is the endpoint for information on the service

Changed various formatters to account for the presence of specified uitvoer.
Made Converter.contextToJson more robust by adding an "isEmpty" check on the context. An empty context returns an empty JsObject.
  • Loading branch information
NRBPerdijk committed Jan 19, 2017
1 parent 1afa4fb commit 5abd2ce
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 36 deletions.
108 changes: 86 additions & 22 deletions app/controllers/RestController.scala
Expand Up @@ -21,25 +21,86 @@ class RestController @Inject() (businessServicesService: BusinessServicesService
glossariesService: GlossariesService,
jsonConversionMapsService: JsonConversionMapsService) extends Controller {

val endpoints: String = businessServicesService.businessServiceNames.map{
case businessService:String => "/api/run/group/" + businessService
} match {
case Nil => "No endpoints available: no BusinessServices have been defined!"
case list: List[String] => list.reduceLeft((acc, next) => acc + "\n" + next)
val endpoints: Try[JsObject] = businessServicesService.businessServiceNames.map(
businessServiceName => JsObject(Map(("/api/run/group/" + businessServiceName) -> Json.toJson("/api/run/group/information/" + businessServiceName)))
) match {
case Nil => Failure(new IllegalStateException("No endpoints available: it seems no BusinessServices have been defined!"))
case jsObjectList: List[JsObject] => Success(jsObjectList.reduceLeft(_ ++ _))
}

/**
* @return a list of JsObjects where the first value is the endpoint and the second value is the information endpoint for all available BusinessServices
* or an InternalServerError(500) if no BusinessServices have been found as this suggests a configuration error.
*/
def businessservices = Action(
Ok(endpoints)
endpoints match {
case f: Failure[JsObject] => InternalServerError(f.exception.getMessage)
case s: Success[JsObject] => Ok(s.value)
}
)

/**
* provides information on verplichteInvoer, uitvoer and optioneleFacts
* @param name: the name of the BusinessService for which
* @return
*/
def information(name: String) = Action {
findBusinessService(name) match {
case f: Failure[(String, BusinessService)] => BadRequest(JsError.toJson(JsError(ValidationError(f.exception.getMessage))))
case s: Success[(String, BusinessService)] => Ok(
JsObject(Map(
"Information for Business Service " + s.value._1 ->
JsObject(Map(
"verplichteInvoer" -> contextToJson(s.value._2.verplichteInvoerFacts.map(f => f -> ("type " + f.valueType)).toMap, jsonConversionMap),
"optioneleInvoer met bijbehorende defaults" -> contextToJson(s.value._2.optioneleInvoerFacts, jsonConversionMap),
"uitvoer" -> contextToJson(s.value._2.uitvoerFacts.map(f => f -> ("type " + f.valueType)).toMap, jsonConversionMap)))
)))
}
}

/**
* Attempts to run the derivations specified by the named BusinessService with the JSON context provided.
* Will provide clear error information on all detected issues. Otherwise will provide the provided inputs and the outputs.
* @param name: the name of the BusinessService whose derivations are meant to be run, currently case sensitive
* @return the provided inputs and the specified outputs, nicely sorted.
*/
def runBusinessService(name: String) = Action(parse.json) {
request =>
findBusinessService(name) match {
case f: Failure[(String, BusinessService)] => BadRequest(JsError.toJson(JsError(ValidationError(f.exception.getMessage))))
case s: Success[(String, BusinessService)] => runBusiness(request, DebugResponseJsObject, s.value._2)
case s: Success[(String, BusinessService)] => runBusiness(request, InputsAndOutputsResponseJsObject, s.value._2)
}
}

/**
* Attempts to run the derivations specified by the named BusinessService with the JSON context provided.
* Will provide clear error information on all detected issues. Otherwise will provide the provided context, all intermediary results and the outputs.
* @param name: the name of the BusinessService whose derivations are meant to be run, currently case sensitive
* @return The inputs, intermediary results and outputs, nicely sorted.
*/
def debugBusinessService(name: String) = Action(parse.json) {
request =>
findBusinessService(name) match {
case f: Failure[(String, BusinessService)] => BadRequest(JsError.toJson(JsError(ValidationError(f.exception.getMessage))))
case s: Success[(String, BusinessService)] => runBusiness(request, CompleteResponseJsObject, s.value._2)
}
}

/**
* Attempts to run the derivations specified by the named BusinessService with the JSON context provided.
* Will provide clear error information on all detected issues. Otherwise will provide only the specified uitvoer.
* @param name: the name of the BusinessService whose derivations are meant to be run, currently case sensitive
* @return only the outputs belonging to the BusinessService
*/
def runBusinessServiceOutputsOnly(name: String) = Action(parse.json) {
request =>
findBusinessService(name) match {
case f: Failure[(String, BusinessService)] => BadRequest(JsError.toJson(JsError(ValidationError(f.exception.getMessage))))
case s: Success[(String, BusinessService)] => runBusiness(request, OutputsOnlyResponseJsObject, s.value._2)
}
}


private def findBusinessService(name: String): Try[(String, BusinessService)] = {
val matchedBusinessServices = businessServicesService.businessServices.collect{ case (naam, service) if naam == name => (naam, service)}
matchedBusinessServices match {
Expand All @@ -55,6 +116,15 @@ class RestController @Inject() (businessServicesService: BusinessServicesService
}
}

private def runBusiness(request: Request[JsValue], jsonResponseProvider: ResponseJsObject, businessService: BusinessService): Result = {
val (initialContextFragments: List[JsSuccess[Context]], conversionErrors: List[JsError]) = {
convertToIndividualContext(request.body, businessService.glossaries.foldLeft(Map.empty[String, Fact[Any]])((acc, glossary) => acc ++ glossary.facts), jsonConversionMap)
}

if (conversionErrors != List.empty) BadRequest( processConversionErrors(conversionErrors) )
else processConvertedContextBusinessService(initialContextFragments, jsonResponseProvider, businessService)
}

/**
* Provides a REST endpoint for triggering all derivations in the target project. Any fact definitions available in the target project's glossaries
* can be provided in the JSON request body like so:
Expand All @@ -70,7 +140,7 @@ class RestController @Inject() (businessServicesService: BusinessServicesService
* - A JsObject containing one JsObject: "facts", which contains the combined information of "input" and "results"
*/
def runAll = Action(parse.json) { request =>
run(request, DefaultResponseJsObject)
run(request, RunAllResponseJsObject)
}

/**
Expand All @@ -81,7 +151,7 @@ class RestController @Inject() (businessServicesService: BusinessServicesService
* - A JsObject containing two JsObject: "input" and "results", which contains only the information of "results"
*/
def runAllDebug = Action(parse.json) { request =>
run(request, DebugResponseJsObject)
run(request, DebugAllResponseJsObject)
}

/**
Expand All @@ -92,7 +162,7 @@ class RestController @Inject() (businessServicesService: BusinessServicesService
* - A JsObject containing one JsObject: "results", which contains only the information of "results"
*/
def runAllResultsOnly = Action(parse.json) { request =>
run(request, ResultsOnlyResponseJsObject)
run(request, RunAllResultsOnlyResponseJsObject)
}


Expand All @@ -103,28 +173,21 @@ class RestController @Inject() (businessServicesService: BusinessServicesService
convertToIndividualContext(request.body, glossariesService.mergedGlossaries, jsonConversionMap)

if (conversionErrors != List.empty) BadRequest( processConversionErrors(conversionErrors) )
else Ok( processConvertedContext(initialContextFragments, jsonResponseProvider) )
else Ok( processConvertedContext(initialContextFragments, Nil, jsonResponseProvider) )
}

private def runBusiness(request: Request[JsValue], jsonResponseProvider: ResponseJsObject, businessService: BusinessService): Result = {
val (initialContextFragments: List[JsSuccess[Context]], conversionErrors: List[JsError]) = {
convertToIndividualContext(request.body, businessService.glossaries.foldLeft(Map.empty[String, Fact[Any]])((acc, glossary) => acc ++ glossary.facts), jsonConversionMap)
}

if (conversionErrors != List.empty) BadRequest( processConversionErrors(conversionErrors) )
else processConvertedContextBS(initialContextFragments, jsonResponseProvider, businessService)
}

private def processConversionErrors(conversionErrors: List[JsError]): JsObject = JsError.toJson(conversionErrors.reduceLeft(_ ++ _))

private def processConvertedContext(initialContextFragments: List[JsSuccess[Context]], jsonResponse: ResponseJsObject): JsObject = {
private def processConvertedContext(initialContextFragments: List[JsSuccess[Context]], uitvoerFacts: List[Fact[Any]], jsonResponse: ResponseJsObject): JsObject = {
val initialContext: Context = initialContextFragments.foldLeft(Map.empty[Fact[Any], Any])((acc, jsSuccess) => acc ++ jsSuccess.get)
val resultContext: Context = RulesRunner.run(initialContext, derivationsService.topLevelDerivations)

jsonResponse.toJson(initialContext = initialContext, resultContext = resultContext, jsonConversionMap)
jsonResponse.toJson(initialContext = initialContext, uitvoerFacts = uitvoerFacts, resultContext = resultContext, jsonConversionMap)
}

private def processConvertedContextBS(initialContextFragments: List[JsSuccess[Context]], jsonResponse: ResponseJsObject, businessService: BusinessService): Result = {
private def processConvertedContextBusinessService(initialContextFragments: List[JsSuccess[Context]], jsonResponse: ResponseJsObject, businessService: BusinessService): Result = {
val initialContext: Context = initialContextFragments.foldLeft(Map.empty[Fact[Any], Any])((acc, jsSuccess) => acc ++ jsSuccess.get)
val resultContext: Try[Context] = businessService.run(initialContext, FactEngine.runNormalDerivations)

Expand All @@ -133,7 +196,8 @@ class RestController @Inject() (businessServicesService: BusinessServicesService
case s: Success[Context] => Ok(
jsonResponse.toJson(
initialContext = initialContext,
resultContext = s.value.filter(x => businessService.uitvoerFacts.contains(x._1)),
uitvoerFacts = businessService.uitvoerFacts,
resultContext = s.value,
jsonConversionMap
)
)
Expand Down
4 changes: 3 additions & 1 deletion app/controllers/conversion/Converter.scala
Expand Up @@ -34,5 +34,7 @@ object Converter {
(successes, errors)
}

def contextToJson(context: Context, jsonConversionMap: JsonConversionsProvider): JsObject = writes(context, jsonConversionMap)
def contextToJson(context: Context, jsonConversionMap: JsonConversionsProvider): JsObject =
if (context.isEmpty) JsObject(Map.empty[String, JsValue])
else writes(context, jsonConversionMap)
}
46 changes: 35 additions & 11 deletions app/controllers/conversion/JsResponse.scala
Expand Up @@ -2,25 +2,49 @@ package controllers.conversion

import controllers.conversion.Converter._
import org.scalarules.engine._
import org.scalarules.facts.Fact
import play.api.libs.json.JsObject

trait ResponseJsObject {
def toJson(initialContext: Context, resultContext: Context, jsonConversionMap: JsonConversionsProvider): JsObject
def toJson(initialContext: Context, uitvoerFacts: List[Fact[Any]], resultContext: Context, jsonConversionMap: JsonConversionsProvider): JsObject
}

object DebugResponseJsObject extends ResponseJsObject {
override def toJson(initialContext: Context, resultContext: Context, jsonConversionMap: JsonConversionsProvider): JsObject =
object InputsAndOutputsResponseJsObject extends ResponseJsObject {
override def toJson(initialContext: Context, uitvoerFacts: List[Fact[Any]], resultContext: Context, jsonConversionMap: JsonConversionsProvider): JsObject =
JsObject(Map(
"inputs" -> Converter.contextToJson(initialContext, jsonConversionMap),
"results" -> contextToJson(resultContext -- initialContext.keys, jsonConversionMap)))
"inputs" -> contextToJson(initialContext, jsonConversionMap),
"results" -> contextToJson(resultContext.filter(factWithValue => uitvoerFacts.contains(factWithValue._1)), jsonConversionMap)))
}

object CompleteResponseJsObject extends ResponseJsObject {
override def toJson(initialContext: Context, uitvoerFacts: List[Fact[Any]], resultContext: Context, jsonConversionMap: JsonConversionsProvider): JsObject = {
JsObject(Map(
"inputs" -> contextToJson(initialContext, jsonConversionMap),
"intermediateSteps" -> contextToJson(resultContext -- initialContext.keys -- resultContext.filter(factWithValue => (uitvoerFacts.contains(factWithValue._1))).keys, jsonConversionMap),
"results" -> contextToJson(resultContext.filter(factWithValue => uitvoerFacts.contains(factWithValue._1)), jsonConversionMap)))
}
}

object OutputsOnlyResponseJsObject extends ResponseJsObject {
override def toJson(initialContext: Context, uitvoerFacts: List[Fact[Any]], resultContext: Context, jsonConversionMap: JsonConversionsProvider): JsObject =
JsObject(Map(
"results" -> contextToJson(resultContext.filter(factWithValue => uitvoerFacts.contains(factWithValue._1)), jsonConversionMap)))
}

object RunAllResponseJsObject extends ResponseJsObject {
override def toJson(initialContext: Context, uitvoerFacts: List[Fact[Any]], resultContext: Context, jsonConversionMap: JsonConversionsProvider): JsObject =
JsObject(Map("facts" -> contextToJson(resultContext, jsonConversionMap)))
}

object DefaultResponseJsObject extends ResponseJsObject {
override def toJson(initialContext: Context, resultContext: Context, jsonConversionMap: JsonConversionsProvider): JsObject =
JsObject(Map("facts" -> Converter.contextToJson(resultContext, jsonConversionMap)))
object DebugAllResponseJsObject extends ResponseJsObject {
override def toJson(initialContext: Context, uitvoerFacts: List[Fact[Any]], resultContext: Context, jsonConversionMap: JsonConversionsProvider): JsObject =
JsObject(Map(
"inputs" -> contextToJson(initialContext, jsonConversionMap),
"results" -> contextToJson(resultContext -- initialContext.keys, jsonConversionMap)))
}

object ResultsOnlyResponseJsObject extends ResponseJsObject {
override def toJson(initialContext: Context, resultContext: Context, jsonConversionMap: JsonConversionsProvider): JsObject =
JsObject(Map("results" -> Converter.contextToJson(resultContext -- initialContext.keys, jsonConversionMap)))
object RunAllResultsOnlyResponseJsObject extends ResponseJsObject {
override def toJson(initialContext: Context, uitvoerFacts: List[Fact[Any]], resultContext: Context, jsonConversionMap: JsonConversionsProvider): JsObject =
JsObject(Map("results" -> contextToJson(resultContext -- initialContext.keys, jsonConversionMap)))
}

7 changes: 5 additions & 2 deletions conf/routes
@@ -1,10 +1,13 @@
# Routes for REST/JSON calls to run the derivations

POST /api/run/group/all controllers.RestController.runAll
POST /api/run/group/all/debug controllers.RestController.runAllDebug
POST /api/run/group/all/resultsonly controllers.RestController.runAllResultsOnly
POST /api/run/group/debug/all controllers.RestController.runAllDebug
POST /api/run/group/resultsonly/all controllers.RestController.runAllResultsOnly
POST /api/run/group/businessservices controllers.RestController.businessservices
POST /api/run/group/:name controllers.RestController.runBusinessService(name)
POST /api/run/group/debug/:name controllers.RestController.debugBusinessService(name)
POST /api/run/group/resultsonly/:name controllers.RestController.debugBusinessService(name)
POST /api/run/group/information/:name controllers.RestController.information(name)

# Routes for retrieving glossary/fact information

Expand Down

0 comments on commit 5abd2ce

Please sign in to comment.