merging Alex. Bertail's "~ work around the content-types for GET and PUT" webid
authorHenry Story <henry.story@bblfish.net>
Sun, 16 Oct 2011 20:16:40 +0200
branchwebid
changeset 78 abbf9e663c35
parent 77 03ea40f31194 (current diff)
parent 65 68e9d1a12e27 (diff)
child 79 572e802db0eb
merging Alex. Bertail's "~ work around the content-types for GET and PUT"
src/main/scala/Lang.scala
src/main/scala/WebCache.scala
src/main/scala/plan.scala
src/main/scala/rdfLanguage.scala
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/AcceptLang.scala	Sun Oct 16 20:16:40 2011 +0200
@@ -0,0 +1,13 @@
+package org.w3.readwriteweb
+
+import unfiltered.request._
+
+object AcceptLang {
+  
+  def unapply(req: HttpRequest[_]): Option[Lang] =
+    Accept(req) map Lang.apply collectFirst { case Some(lang) => lang }
+  
+  def apply(req: HttpRequest[_]): Option[Lang] =
+    unapply(req)
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/Lang.scala	Sun Oct 16 20:16:40 2011 +0200
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2011 Henry Story (bblfish.net)
+ * under the MIT licence defined
+ *    http://www.opensource.org/licenses/mit-license.html
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in the
+ * Software without restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.w3.readwriteweb
+
+import unfiltered.request._
+
+sealed trait Lang {
+  
+  def contentType = this match {
+    case RDFXML => "application/rdf+xml"
+    case TURTLE => "text/turtle"
+    case N3 => "text/n3"
+  }
+  
+  def jenaLang = this match {
+    case RDFXML => "RDF/XML-ABBREV"
+    case TURTLE => "TURTLE"
+    case N3 => "N3"
+  }
+  
+}
+
+object Lang {
+  
+  val supportedLanguages = Seq(RDFXML, TURTLE, N3)
+  val supportContentTypes = supportedLanguages map (_.contentType)
+  val supportedAsString = supportContentTypes mkString ", "
+  
+  val default = RDFXML
+  
+  def apply(contentType: String): Option[Lang] =
+    contentType match {
+      case "text/n3" => Some(N3)
+      case "text/turtle" => Some(TURTLE)
+      case "application/rdf+xml" => Some(RDFXML)
+      case _ => None
+  }
+
+  def apply(req: HttpRequest[_]): Option[Lang] =
+    RequestContentType(req) flatMap Lang.apply
+    
+  def unapply(req: HttpRequest[_]): Option[Lang] =
+    apply(req)
+
+}
+
+case object RDFXML extends Lang
+
+case object TURTLE extends Lang
+
+case object N3 extends Lang
--- a/src/main/scala/Post.scala	Sun Oct 16 20:03:39 2011 +0200
+++ b/src/main/scala/Post.scala	Sun Oct 16 20:16:40 2011 +0200
@@ -11,21 +11,21 @@
 import com.hp.hpl.jena.shared.JenaException
 
 sealed trait Post
-
 case class PostUpdate(update: UpdateRequest) extends Post
-
 case class PostRDF(model: Model) extends Post
-
 case class PostQuery(query: Query) extends Post
-
 case object PostUnknown extends Post
 
-
 import scalaz._
 import Scalaz._
 
 object Post {
   
+  val SparqlContentType = "application/sparql-query"
+  val supportContentTypes = SparqlContentType + Lang.supportContentTypes
+  val supportedAsString = supportContentTypes mkString ", "
+
+  
   val logger: Logger = LoggerFactory.getLogger(this.getClass)
 
   def parse(is: InputStream, baseURI:String): Post = {
--- a/src/main/scala/WebCache.scala	Sun Oct 16 20:03:39 2011 +0200
+++ b/src/main/scala/WebCache.scala	Sun Oct 16 20:16:40 2011 +0200
@@ -59,7 +59,7 @@
       val handler: Handler[Validation[Throwable, Model]] = request.>+>[Validation[Throwable, Model]](res =>  {
         res >:> { headers =>
           val encoding = headers("Content-Type").headOption match {
-            case Some(mime) => Lang.apply(mime)
+            case Some(mime) => Lang(mime) getOrElse Lang.default
             case None => RDFXML  // it would be better to try to do a bit of guessing in this case by looking at content
           }
           val loc = headers("Content-Location").headOption match {
--- a/src/main/scala/plan.scala	Sun Oct 16 20:03:39 2011 +0200
+++ b/src/main/scala/plan.scala	Sun Oct 16 20:16:40 2011 +0200
@@ -66,19 +66,28 @@
         case GET(_) | HEAD(_) =>
           for {
             model <- r.get() failMap { x => NotFound }
-            lang = Lang.fromRequest(req)
+            lang = AcceptLang(req) getOrElse Lang.default
           } yield {
             req match {
               case GET(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType) ~> ResponseModel(model, baseURI, lang)
               case HEAD(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType)
             }
           }
-        case PUT(_) =>
+        case PUT(_) & Lang(lang) =>
           for {
             bodyModel <- modelFromInputStream(Body.stream(req), baseURI) failMap { t => BadRequest ~> ResponseString(t.getStackTraceString) }
             _ <- r.save(bodyModel) failMap { t => InternalServerError ~> ResponseString(t.getStackTraceString) }
           } yield Created
-        case POST(_) => {
+        case PUT(_) =>
+          BadRequest ~> ResponseString("Content-Type MUST be one of: " + Lang.supportedAsString)
+        case POST(_) =>
+          req match {
+            case RequestContentType("application/sparql-query") => null
+            case RequestContentType(ct) if Lang.supportContentTypes contains ct => null
+            case _ => BadRequest ~> ResponseString("Content-Type MUST be one of: " + Post.supportedAsString)
+          }
+          
+          {
           Post.parse(Body.stream(req), baseURI) match {
             case PostUnknown => {
               logger.info("Couldn't parse the request")
@@ -104,7 +113,7 @@
             }
             case PostQuery(query) => {
               logger.info("SPARQL Query:\n" + query.toString())
-              lazy val lang = Lang.fromRequest(req)
+              lazy val lang = Lang(req) getOrElse Lang.default
               for {
                 model <- r.get() failMap { t => NotFound }
               } yield {
--- a/src/main/scala/rdfLanguage.scala	Sun Oct 16 20:03:39 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-package org.w3.readwriteweb
-
-import unfiltered.request._
-
-sealed trait Lang {
-  
-  def contentType = this match {
-    case RDFXML => "application/rdf+xml"
-    case TURTLE => "text/turtle"
-    case N3 => "text/n3"
-  }
-  
-  def jenaLang = this match {
-    case RDFXML => "RDF/XML-ABBREV"
-    case TURTLE => "TURTLE"
-    case N3 => "N3"
-  }
-  
-}
-
-object Lang {
-  
-  val default = RDFXML
-  
-  def apply: PartialFunction[String, Lang] = {
-    case "text/n3" => N3
-    case "text/turtle" => TURTLE
-    case "application/rdf+xml" => RDFXML
-  }
-
-  def fromRequest(req: HttpRequest[_]): Lang = {
-    val contentType = Accept(req).headOption
-    contentType map { Lang.apply } getOrElse RDFXML
-  }
-
-}
-
-case object RDFXML extends Lang
-
-case object TURTLE extends Lang
-
-case object N3 extends Lang
--- a/src/test/scala/CreateContentSpecs.scala	Sun Oct 16 20:03:39 2011 +0200
+++ b/src/test/scala/CreateContentSpecs.scala	Sun Oct 16 20:16:40 2011 +0200
@@ -9,7 +9,7 @@
 
   "PUTing an RDF document on Joe's URI (which does not exist yet)" should {
     "return a 201" in {
-      val httpCode = Http(uri.put(rdfxml) get_statusCode)
+      val httpCode = Http(uri.put(RDFXML, rdfxml) get_statusCode)
       httpCode must_== 201
     }
     "create a document on disk" in {
@@ -76,7 +76,7 @@
 
   """PUTting not-valid RDF to Joe's URI""" should {
     "return a 400 Bad Request" in {
-      val statusCode = Http.when(_ == 400)(uri.put("that's bouleshit") get_statusCode)
+      val statusCode = Http.when(_ == 400)(uri.put(RDFXML, "that's bouleshit") get_statusCode)
       statusCode must_== 400
     }
   }
--- a/src/test/scala/util/specs.scala	Sun Oct 16 20:03:39 2011 +0200
+++ b/src/test/scala/util/specs.scala	Sun Oct 16 20:16:40 2011 +0200
@@ -81,7 +81,7 @@
 trait SomeDataInStore extends FilesystemBased with SomeRDF with SomeURI {
   
   doBeforeSpec {
-    val httpCode = Http(uri.put(rdfxml) get_statusCode)
+    val httpCode = Http(uri.put(RDFXML, rdfxml) get_statusCode)
     httpCode must_== 201
   }
   
--- a/src/test/scala/util/utiltest.scala	Sun Oct 16 20:03:39 2011 +0200
+++ b/src/test/scala/util/utiltest.scala	Sun Oct 16 20:16:40 2011 +0200
@@ -27,40 +27,41 @@
 
 package object utiltest {
   
-  def baseURI(req:Request):String = "%s%s" format (req.host, req.path)
+  def baseURI(req: Request): String = "%s%s" format (req.host, req.path)
   
-  def beIsomorphicWith(that:Model):Matcher[Model] =
+  def beIsomorphicWith(that: Model): Matcher[Model] =
     new Matcher[Model] {
-      def apply(otherModel: => Model) =
+      def apply(otherModel:  => Model) =
         (that isIsomorphicWith otherModel,
          "Model A is isomorphic to model B",
          "%s not isomorphic with %s" format (otherModel.toString, that.toString))
   }
   
-  class RequestW(req:Request) {
+  class RequestW(req: Request) {
 
-    def as_model(base:String, lang:String = "RDF/XML-ABBREV"):Handler[Model] =
+    def as_model(base: String, lang: String = "RDF/XML-ABBREV"): Handler[Model] =
       req >> { is => modelFromInputStream(is, base, lang).toOption.get }
 
-    def post(body:String):Request =
+    def post(body: String): Request =
       (req <<< body).copy(method="POST")
       
-    def put(body:String):Request = req <<< body
+    def put(lang: Lang, body: String): Request =
+      req <:< Map("Content-Type" -> lang.contentType) <<< body
       
-    def get_statusCode:Handler[Int] = new Handler(req, (c, r, e) => c, { case t => () })
+    def get_statusCode: Handler[Int] = new Handler(req, (c, r, e) => c, { case t => () })
     
-    def get_header(header:String):Handler[String] = req >:> { _(header).head }
+    def get_header(header: String): Handler[String] = req >:> { _(header).head }
     
-    def get:Request = req.copy(method="GET")
+    def get: Request = req.copy(method="GET")
     
-    def >++ [A, B, C] (block: Request => (Handler[A], Handler[B], Handler[C])) = {
+    def >++ [A, B, C] (block:  Request => (Handler[A], Handler[B], Handler[C])) = {
       Handler(req, { (code, res, opt_ent) =>
         val (a, b, c) = block( /\ )
           (a.block(code, res, opt_ent), b.block(code,res,opt_ent), c.block(code,res,opt_ent))
       } )
     }
     
-    def >+ [A, B] (block: Request => (Handler[A], Handler[B])) = {
+    def >+ [A, B] (block:  Request => (Handler[A], Handler[B])) = {
       Handler(req, { (code, res, opt_ent) =>
         val (a, b) = block( /\ )
         (a.block(code, res, opt_ent), b.block(code,res,opt_ent))
@@ -69,7 +70,7 @@
     
   }
   
-  implicit def wrapRequest(req:Request):RequestW = new RequestW(req)
+  implicit def wrapRequest(req: Request): RequestW = new RequestW(req)