merging Alex. Bertail's "~ work around the content-types for GET and PUT"
--- /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)