This is an automated email from the ASF dual-hosted git repository. csantanapr pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk.git
The following commit(s) were added to refs/heads/master by this push: new cfd34c0 Pretty print webaction JSON response. (#2643) cfd34c0 is described below commit cfd34c084bcd995876a3672ee715b7c814459cd4 Author: rodric rabbah <rod...@gmail.com> AuthorDate: Wed Aug 30 23:16:52 2017 -0400 Pretty print webaction JSON response. (#2643) * Pretty print webaction JSON response. Add pretty/compact printers in singleton common to controller, and use it for error responses, API response and webactions. * Tidy up a match. * Remove extra space. --- .../main/scala/whisk/core/entity/WhiskAction.scala | 7 +++--- .../src/main/scala/whisk/http/ErrorResponse.scala | 6 +++-- .../main/scala/whisk/core/controller/Actions.scala | 4 ++++ .../scala/whisk/core/controller/Activations.scala | 3 +++ .../scala/whisk/core/controller/ApiUtils.scala | 6 +++++ .../controller/AuthorizedRouteDispatcher.scala | 4 ++++ .../scala/whisk/core/controller/Entities.scala | 19 +++++++-------- .../scala/whisk/core/controller/Namespaces.scala | 3 +++ .../scala/whisk/core/controller/Packages.scala | 3 +++ .../scala/whisk/core/controller/RestAPIs.scala | 11 +++++++-- .../main/scala/whisk/core/controller/Rules.scala | 3 +++ .../scala/whisk/core/controller/Triggers.scala | 3 +++ .../scala/whisk/core/controller/WebActions.scala | 17 +++++++------- .../scala/whisk/core/invoker/InvokerReactive.scala | 2 +- .../core/controller/test/WebActionsApiTests.scala | 27 ++++++++-------------- 15 files changed, 74 insertions(+), 44 deletions(-) diff --git a/common/scala/src/main/scala/whisk/core/entity/WhiskAction.scala b/common/scala/src/main/scala/whisk/core/entity/WhiskAction.scala index 0e01369..c4e90f4 100644 --- a/common/scala/src/main/scala/whisk/core/entity/WhiskAction.scala +++ b/common/scala/src/main/scala/whisk/core/entity/WhiskAction.scala @@ -256,16 +256,15 @@ object WhiskAction implicit val ec = db.executionContext - val fa = super.get(db, doc, rev, fromCache = fromCache) + val fa = super.get(db, doc, rev, fromCache) fa.flatMap { action => action.exec match { - case exec @ CodeExecAsAttachment(_, Attached(_, _), _) => + case exec @ CodeExecAsAttachment(_, Attached(attachmentName, _), _) => val boas = new ByteArrayOutputStream() val b64s = Base64.getEncoder().wrap(boas) - val manifest = exec.manifest.attached.get - getAttachment[A](db, action.docinfo, manifest.attachmentName, b64s).map { _ => + getAttachment[A](db, action.docinfo, attachmentName, b64s).map { _ => b64s.close() val newAction = action.copy(exec = exec.inline(boas.toByteArray)) newAction.revision(action.rev) diff --git a/common/scala/src/main/scala/whisk/http/ErrorResponse.scala b/common/scala/src/main/scala/whisk/http/ErrorResponse.scala index 1687630..85f39b6 100644 --- a/common/scala/src/main/scala/whisk/http/ErrorResponse.scala +++ b/common/scala/src/main/scala/whisk/http/ErrorResponse.scala @@ -169,13 +169,15 @@ case class ErrorResponse(error: String, code: TransactionId) object ErrorResponse extends Directives with DefaultJsonProtocol { - def terminate(status: StatusCode, error: String)(implicit transid: TransactionId): StandardRoute = { + def terminate(status: StatusCode, error: String)( + implicit transid: TransactionId, jsonPrinter: JsonPrinter): StandardRoute = { terminate(status, Option(error) filter { _.trim.nonEmpty } map { e => Some(ErrorResponse(e.trim, transid)) } getOrElse None) } - def terminate(status: StatusCode, error: Option[ErrorResponse] = None, asJson: Boolean = true)(implicit transid: TransactionId): StandardRoute = { + def terminate(status: StatusCode, error: Option[ErrorResponse] = None, asJson: Boolean = true)( + implicit transid: TransactionId, jsonPrinter: JsonPrinter): StandardRoute = { val errorResponse = error getOrElse response(status) if (asJson) { complete(status, errorResponse) diff --git a/core/controller/src/main/scala/whisk/core/controller/Actions.scala b/core/controller/src/main/scala/whisk/core/controller/Actions.scala index fc042a7..ba74de8 100644 --- a/core/controller/src/main/scala/whisk/core/controller/Actions.scala +++ b/core/controller/src/main/scala/whisk/core/controller/Actions.scala @@ -87,8 +87,12 @@ trait WhiskActionsApi /** Database service to get activations. */ protected val activationStore: ActivationStore + /** Entity normalizer to JSON object. */ import RestApiCommons.emptyEntityToJsObject + /** JSON response formatter. */ + import RestApiCommons.jsonDefaultResponsePrinter + /** * Handles operations on action resources, which encompass these cases: * diff --git a/core/controller/src/main/scala/whisk/core/controller/Activations.scala b/core/controller/src/main/scala/whisk/core/controller/Activations.scala index f45accf..8c162ae 100644 --- a/core/controller/src/main/scala/whisk/core/controller/Activations.scala +++ b/core/controller/src/main/scala/whisk/core/controller/Activations.scala @@ -55,6 +55,9 @@ trait WhiskActivationsApi protected override val collection = Collection(Collection.ACTIVATIONS) + /** JSON response formatter. */ + import RestApiCommons.jsonDefaultResponsePrinter + /** Database service to GET activations. */ protected val activationStore: ActivationStore diff --git a/core/controller/src/main/scala/whisk/core/controller/ApiUtils.scala b/core/controller/src/main/scala/whisk/core/controller/ApiUtils.scala index 54f424c..a52bd24 100644 --- a/core/controller/src/main/scala/whisk/core/controller/ApiUtils.scala +++ b/core/controller/src/main/scala/whisk/core/controller/ApiUtils.scala @@ -118,6 +118,9 @@ trait ReadOps extends Directives { protected implicit val logging: Logging + /** JSON response formatter. */ + import RestApiCommons.jsonDefaultResponsePrinter + /** * Get all entities of type A from datastore that match key. Terminates HTTP request. * @@ -232,6 +235,9 @@ trait WriteOps extends Directives { protected implicit val logging: Logging + /** JSON response formatter. */ + import RestApiCommons.jsonDefaultResponsePrinter + /** * A predicate future that completes with true iff the entity should be * stored in the datastore. Future should fail otherwise with RejectPut. diff --git a/core/controller/src/main/scala/whisk/core/controller/AuthorizedRouteDispatcher.scala b/core/controller/src/main/scala/whisk/core/controller/AuthorizedRouteDispatcher.scala index d82f9e3..7812a37 100644 --- a/core/controller/src/main/scala/whisk/core/controller/AuthorizedRouteDispatcher.scala +++ b/core/controller/src/main/scala/whisk/core/controller/AuthorizedRouteDispatcher.scala @@ -43,6 +43,7 @@ import whisk.http.Messages /** A trait for routes that require entitlement checks. */ trait BasicAuthorizedRouteProvider extends Directives { + /** An execution context for futures */ protected implicit val executionContext: ExecutionContext @@ -61,6 +62,9 @@ trait BasicAuthorizedRouteProvider extends Directives { /** Route directives for API. The methods that are supported on entities. */ protected lazy val entityOps = get + /** JSON response formatter. */ + import RestApiCommons.jsonDefaultResponsePrinter + /** Checks entitlement and dispatches to handler if authorized. */ protected def authorizeAndDispatch( method: HttpMethod, diff --git a/core/controller/src/main/scala/whisk/core/controller/Entities.scala b/core/controller/src/main/scala/whisk/core/controller/Entities.scala index ebafac5..fbf75b3 100644 --- a/core/controller/src/main/scala/whisk/core/controller/Entities.scala +++ b/core/controller/src/main/scala/whisk/core/controller/Entities.scala @@ -17,18 +17,18 @@ package whisk.core.controller +import scala.concurrent.Future import scala.language.postfixOps import scala.util.Try -import scala.concurrent.Future -import akka.http.scaladsl.model.StatusCodes.RequestEntityTooLarge import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ +import akka.http.scaladsl.model.StatusCodes.RequestEntityTooLarge import akka.http.scaladsl.server.Directive0 import akka.http.scaladsl.server.Directives import akka.http.scaladsl.server.RequestContext -import akka.http.scaladsl.server.RouteResult import akka.http.scaladsl.server.Route - +import akka.http.scaladsl.server.RouteResult +import spray.json.JsonPrinter import whisk.common.TransactionId import whisk.core.entitlement.Privilege._ import whisk.core.entitlement.Privilege.Privilege @@ -41,9 +41,10 @@ import whisk.http.ErrorResponse.terminate import whisk.http.Messages protected[controller] trait ValidateRequestSize extends Directives { + protected def validateSize(check: => Option[SizeError])( - implicit tid: TransactionId) = new Directive0 { - def tapply(f: Unit => Route) = { + implicit tid: TransactionId, jsonPrinter: JsonPrinter) = new Directive0 { + override def tapply(f: Unit => Route) = { check map { case e: SizeError => terminate(RequestEntityTooLarge, Messages.entityTooBig(e)) } getOrElse f(None) @@ -100,13 +101,13 @@ trait WhiskCollectionAPI case READ => fetch(user, FullyQualifiedEntityName(resource.namespace, name), resource.env) case PUT => entity(as[LimitedWhiskEntityPut]) { e => - validateSize(e.isWithinSizeLimits)(transid) { + validateSize(e.isWithinSizeLimits)(transid, RestApiCommons.jsonDefaultResponsePrinter) { create(user, FullyQualifiedEntityName(resource.namespace, name)) } } case ACTIVATE => - extract(_.request.entity.contentLengthOption) { length => - validateSize(isWhithinRange(length.getOrElse(0)))(transid) { + extract(_.request.entity.contentLengthOption) { length => + validateSize(isWhithinRange(length.getOrElse(0)))(transid, RestApiCommons.jsonDefaultResponsePrinter) { activate(user, FullyQualifiedEntityName(resource.namespace, name), resource.env) } } diff --git a/core/controller/src/main/scala/whisk/core/controller/Namespaces.scala b/core/controller/src/main/scala/whisk/core/controller/Namespaces.scala index 86dbb8b..d14e3b7 100644 --- a/core/controller/src/main/scala/whisk/core/controller/Namespaces.scala +++ b/core/controller/src/main/scala/whisk/core/controller/Namespaces.scala @@ -56,6 +56,9 @@ trait WhiskNamespacesApi /** Database service to lookup entities in a namespace. */ protected val entityStore: EntityStore + /** JSON response formatter. */ + import RestApiCommons.jsonDefaultResponsePrinter + /** * Rest API for managing namespaces. Defines all the routes handled by this API. They are: * diff --git a/core/controller/src/main/scala/whisk/core/controller/Packages.scala b/core/controller/src/main/scala/whisk/core/controller/Packages.scala index ce6f4cf..925d5c1 100644 --- a/core/controller/src/main/scala/whisk/core/controller/Packages.scala +++ b/core/controller/src/main/scala/whisk/core/controller/Packages.scala @@ -52,6 +52,9 @@ trait WhiskPackagesApi extends WhiskCollectionAPI with ReferencedEntities { /** Must exclude any private packages when listing those in a namespace unless owned by subject. */ protected override val listRequiresPrivateEntityFilter = true + /** JSON response formatter. */ + import RestApiCommons.jsonDefaultResponsePrinter + /** * Creates or updates package/binding if it already exists. The PUT content is deserialized into a * WhiskPackagePut which is a subset of WhiskPackage (it eschews the namespace and entity name since diff --git a/core/controller/src/main/scala/whisk/core/controller/RestAPIs.scala b/core/controller/src/main/scala/whisk/core/controller/RestAPIs.scala index 57b03ea..0e374f6 100644 --- a/core/controller/src/main/scala/whisk/core/controller/RestAPIs.scala +++ b/core/controller/src/main/scala/whisk/core/controller/RestAPIs.scala @@ -24,6 +24,7 @@ import akka.http.scaladsl.model.Uri import akka.http.scaladsl.server.Directives import akka.http.scaladsl.server.Route import akka.http.scaladsl.model.headers._ +import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ import spray.json._ import spray.json.DefaultJsonProtocol._ @@ -112,6 +113,12 @@ protected[controller] object RestApiCommons { } } } + + /** Pretty print JSON response. */ + implicit val jsonPrettyResponsePrinter = PrettyPrinter + + /** Standard compact JSON printer. */ + implicit val jsonDefaultResponsePrinter = CompactPrinter } /** @@ -148,7 +155,7 @@ class RestAPIVersion(config: WhiskConfig, apiPath: String, apiVersion: String)( * Describes details of a particular API path. */ val info = (pathEndOrSingleSlash & get) { - complete(OK, JsObject( + complete(JsObject( "description" -> "OpenWhisk API".toJson, "api_version" -> SemVer(1, 0, 0).toJson, "api_version_path" -> apiVersion.toJson, @@ -156,7 +163,7 @@ class RestAPIVersion(config: WhiskConfig, apiPath: String, apiVersion: String)( "buildno" -> whiskConfig(whiskVersionBuildno).toJson, "swagger_paths" -> JsObject( "ui" -> s"/$swaggeruipath".toJson, - "api-docs" -> s"/$swaggerdocpath".toJson)).toString) + "api-docs" -> s"/$swaggerdocpath".toJson))) } def routes(implicit transid: TransactionId): Route = { diff --git a/core/controller/src/main/scala/whisk/core/controller/Rules.scala b/core/controller/src/main/scala/whisk/core/controller/Rules.scala index be0e6eb..e65c845 100644 --- a/core/controller/src/main/scala/whisk/core/controller/Rules.scala +++ b/core/controller/src/main/scala/whisk/core/controller/Rules.scala @@ -51,6 +51,9 @@ trait WhiskRulesApi extends WhiskCollectionAPI with ReferencedEntities { /** Database service to CRUD rules. */ protected val entityStore: EntityStore + /** JSON response formatter. */ + import RestApiCommons.jsonDefaultResponsePrinter + /** Path to Rules REST API. */ protected val rulesPath = "rules" diff --git a/core/controller/src/main/scala/whisk/core/controller/Triggers.scala b/core/controller/src/main/scala/whisk/core/controller/Triggers.scala index fe5304f..d77d981 100644 --- a/core/controller/src/main/scala/whisk/core/controller/Triggers.scala +++ b/core/controller/src/main/scala/whisk/core/controller/Triggers.scala @@ -76,6 +76,9 @@ trait WhiskTriggersApi extends WhiskCollectionAPI { /** Database service to get activations. */ protected val activationStore: ActivationStore + /** JSON response formatter. */ + import RestApiCommons.jsonDefaultResponsePrinter + /** Path to Triggers REST API. */ protected val triggersPath = "triggers" diff --git a/core/controller/src/main/scala/whisk/core/controller/WebActions.scala b/core/controller/src/main/scala/whisk/core/controller/WebActions.scala index e527e8b..a009ed2 100644 --- a/core/controller/src/main/scala/whisk/core/controller/WebActions.scala +++ b/core/controller/src/main/scala/whisk/core/controller/WebActions.scala @@ -49,6 +49,7 @@ import spray.json._ import spray.json.DefaultJsonProtocol._ import WhiskWebActionsApi.MediaExtension +import RestApiCommons.{ jsonPrettyResponsePrinter => jsonPrettyPrinter } import whisk.common.TransactionId import whisk.core.controller.actions.PostActionActivation @@ -178,12 +179,12 @@ protected[core] object WhiskWebActionsApi extends Directives { private def resultAsHtml(result: JsValue, transid: TransactionId, rp: WebApiDirectives) = result match { case JsString(html) => complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, html)) - case _ => terminate(BadRequest, Messages.invalidMedia(`text/html`))(transid) + case _ => terminate(BadRequest, Messages.invalidMedia(`text/html`))(transid, jsonPrettyPrinter) } private def resultAsSvg(result: JsValue, transid: TransactionId, rp: WebApiDirectives) = result match { case JsString(svg) => complete(HttpEntity(`image/svg+xml`, svg.getBytes)) - case _ => terminate(BadRequest, Messages.invalidMedia(`image/svg+xml`))(transid) + case _ => terminate(BadRequest, Messages.invalidMedia(`image/svg+xml`))(transid, jsonPrettyPrinter) } private def resultAsText(result: JsValue, transid: TransactionId, rp: WebApiDirectives) = { @@ -201,7 +202,7 @@ protected[core] object WhiskWebActionsApi extends Directives { result match { case r: JsObject => complete(OK, r) case r: JsArray => complete(OK, r) - case _ => terminate(BadRequest, Messages.invalidMedia(`application/json`))(transid) + case _ => terminate(BadRequest, Messages.invalidMedia(`application/json`))(transid, jsonPrettyPrinter) } } @@ -238,7 +239,7 @@ protected[core] object WhiskWebActionsApi extends Directives { } getOrElse { // either the result was not a JsObject or there was an exception validting the // response as an http result - terminate(BadRequest, Messages.invalidMedia(`message/http`))(transid) + terminate(BadRequest, Messages.invalidMedia(`message/http`))(transid, jsonPrettyPrinter) } } @@ -278,7 +279,7 @@ protected[core] object WhiskWebActionsApi extends Directives { } case _ => - terminate(BadRequest, Messages.httpContentTypeError)(transid) + terminate(BadRequest, Messages.httpContentTypeError)(transid, jsonPrettyPrinter) } } @@ -297,10 +298,10 @@ protected[core] object WhiskWebActionsApi extends Directives { } case Failure(RejectRequest(code, message)) => - terminate(code, message)(transid) + terminate(code, message)(transid, jsonPrettyPrinter) case _ => - terminate(BadRequest, Messages.httpContentTypeError)(transid) + terminate(BadRequest, Messages.httpContentTypeError)(transid, jsonPrettyPrinter) } } @@ -426,7 +427,7 @@ trait WhiskWebActionsApi // as the context body which may be the incoming request when the content type is JSON or formdata, or // the raw body as __ow_body (and query parameters as __ow_query) otherwise extract(_.request.entity) { e => - validateSize(isWhithinRange(e.contentLengthOption.getOrElse(0)))(transid) { + validateSize(isWhithinRange(e.contentLengthOption.getOrElse(0)))(transid, jsonPrettyPrinter) { requestMethodParamsAndPath { context => provide(fullyQualifiedActionName(actionName)) { fullActionName => onComplete(verifyWebAction(fullActionName, onBehalfOf.isDefined)) { diff --git a/core/invoker/src/main/scala/whisk/core/invoker/InvokerReactive.scala b/core/invoker/src/main/scala/whisk/core/invoker/InvokerReactive.scala index 1f0d760..1f64625 100644 --- a/core/invoker/src/main/scala/whisk/core/invoker/InvokerReactive.scala +++ b/core/invoker/src/main/scala/whisk/core/invoker/InvokerReactive.scala @@ -126,7 +126,7 @@ class InvokerReactive(config: WhiskConfig, instance: InstanceId, producer: Messa val msg = CompletionMessage(transid, res, instance) producer.send(s"completed${controllerInstance.toInt}", msg).andThen { case Success(_) => - logging.info(this, s"posted ${if (recovery) "recovery" else ""} completion of activation ${activationResult.activationId}") + logging.info(this, s"posted ${if (recovery) "recovery" else "completion"} of activation ${activationResult.activationId}") } } diff --git a/tests/src/test/scala/whisk/core/controller/test/WebActionsApiTests.scala b/tests/src/test/scala/whisk/core/controller/test/WebActionsApiTests.scala index 446d881..fac5076 100644 --- a/tests/src/test/scala/whisk/core/controller/test/WebActionsApiTests.scala +++ b/tests/src/test/scala/whisk/core/controller/test/WebActionsApiTests.scala @@ -342,24 +342,6 @@ trait WebActionsApiTests extends ControllerTestCommon with BeforeAndAfterEach wi } } - /* - * All of the verbs supported by Spray have been added to Web Actions, so comment this test out - * as it is no longer valid. - * - it should s"reject unsupported http verbs (auth? ${creds.isDefined})" in { - implicit val tid = transid() - - Seq((???, MethodNotAllowed)). - foreach { - case (m, code) => - m(s"$testRoutePath/$systemId/proxy/export_c.json") ~> Route.seal(routes(creds)) ~> check { - status should be(code) - } - } - } - * - */ - it should s"reject requests when identity, package or action lookup fail or missing annotation (auth? ${creds.isDefined})" in { implicit val tid = transid() @@ -602,6 +584,8 @@ trait WebActionsApiTests extends ControllerTestCommon with BeforeAndAfterEach wi m(s"$testRoutePath/$path") ~> Route.seal(routes(creds)) ~> check { status should be(NotFound) confirmErrorWithTid(responseAs[JsObject], Some(Messages.propertyNotFound)) + // ensure that error message is pretty printed as { error, code } + responseAs[String].lines should have size 4 } } } @@ -667,6 +651,13 @@ trait WebActionsApiTests extends ControllerTestCommon with BeforeAndAfterEach wi status should be(OK) val response = responseAs[JsObject] response shouldBe actionResult.get + + // ensure response is pretty printed + responseAs[String] shouldBe { + """{ + | "foobar": "foobar" + |}""".stripMargin + } } } } -- To stop receiving notification emails like this one, please contact ['"commits@openwhisk.apache.org" <commits@openwhisk.apache.org>'].