com.cj.restspecs.server.RestSpecServer.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rest-specs-server Show documentation
Show all versions of rest-specs-server Show documentation
A test-friendly mechanism for expressing RESTful http contracts. Specification server.
/**
* Copyright (C) Commission Junction Inc.
*
* This file is part of rest-specs.
*
* rest-specs is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* rest-specs is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with rest-specs; see the file COPYING. If not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* Linking this library statically or dynamically with other modules is
* making a combined work based on this library. Thus, the terms and
* conditions of the GNU General Public License cover the whole
* combination.
*
* As a special exception, the copyright holders of this library give you
* permission to link this library with independent modules to produce an
* executable, regardless of the license terms of these independent
* modules, and to copy and distribute the resulting executable under
* terms of your choice, provided that you also meet, for each linked
* independent module, the terms and conditions of the license of that
* module. An independent module is a module which is not derived from
* or based on this library. If you modify this library, you may extend
* this exception to your version of the library, but you are not
* obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
package com.cj.restspecs.server
import org.httpobjects.jetty.HttpObjectsJettyHandler
import org.httpobjects.HttpObject
import org.httpobjects.Request
import org.httpobjects.DSL._
import java.io.{File => Path}
import cj.restspecs.core.RestSpec
import cj.restspecs.core.io.FilesystemLoader
import org.httpobjects.Response
import org.httpobjects.ResponseCode
import scala.collection.JavaConversions._
import org.httpobjects.header.GenericHeaderField
import java.io.FileInputStream
import java.net.URL
object RestSpecServer {
private def findSpecs(dir: Path): Seq[Path] = {
val results = dir.listFiles().toSeq.map { child =>
if (child.isDirectory) {
findSpecs(child)
}
else if (child.getName.endsWith("spec.json")) {
Seq(child)
} else {
Seq()
}
}
results.flatten
}
private def relativePath(a: Path, b: Path) = {
val aStr = a.getAbsolutePath
val bStr = b.getAbsolutePath
if (aStr.startsWith(bStr)) aStr.substring(bStr.length()) else aStr
}
private def stripVars(t: String) = t.replaceAll("\\{[a-z|A-Z]*\\}", "VARIABLE")
def main(args: Array[String]) {
val currentDirectory = new Path(System.getProperty("user.dir"))
val pathOption = if (args.length > 0) Some(new Path(args(0))) else None
val rootPath = pathOption.getOrElse(currentDirectory)
val loader = new FilesystemLoader(rootPath)
val specFiles = findSpecs(rootPath)
case class SpecWithFilesystemLocation(filesystemLocation: String, spec: RestSpec)
val specFilePathsAndSpecs = specFiles.map { pathToFile =>
val specPath = relativePath(pathToFile, rootPath)
SpecWithFilesystemLocation(specPath, new RestSpec(specPath, loader))
}
val specs = specFilePathsAndSpecs.map(_.spec)
println(s"Serving ${specs.length} specs")
def createHelpPage(httpMethod: String, req: Request) = {
val query = req.query.toString
val path = req.path.toString
val lines = specs.map { spec =>
val relativePathToSpec = specFilePathsAndSpecs.find(_.spec eq spec)
val specPath = stripVars(spec.path)
val sampleLink = if (spec.request.method == "GET") {
" " + spec.path + ""
} else {
spec.path
}
s"""${spec.name} """ + spec.request.method + sampleLink
}
val htmlContent = s"""
NO SPEC FOUND MATCHING $httpMethod $path $query\n
Specs I know about are:
""" + lines.mkString("- ", "
- ", "
") + """
"""
INTERNAL_SERVER_ERROR(Html(htmlContent))
}
def respond(httpMethod: String, req: Request) = {
val query = req.query.toString
val path = req.path.toString
val methodAndPathAndQueryMatches = specs.filter { candidate =>
val p = new URL("http://" + stripVars(candidate.path())).getPath
val pathsMatch = path == p
val queriesMatch = stripVars(query) == stripVars(candidate.queryString)
val methodsMatch = candidate.request.method == httpMethod
methodsMatch && pathsMatch && queriesMatch
}
val numMatches = methodAndPathAndQueryMatches.length
if (methodAndPathAndQueryMatches.length > 1) println(s"WARNING: There are $numMatches matches for $path")
val maybeSpec = methodAndPathAndQueryMatches.headOption
maybeSpec match {
case None => createHelpPage(httpMethod, req)
case Some(spec) =>
val headers = spec.response().header().fieldNames().flatMap { name =>
spec.response().header().fieldsNamed(name).map { value =>
new GenericHeaderField(name, value)
}
}
val representation = spec.response().representation() match {
case null => null
case specRepresentation => {
val contentType = spec.response().header().fieldNames().find(_ == "Content-Type").getOrElse("")
Bytes(contentType, specRepresentation.data())
}
}
new Response(ResponseCode.forCode(spec.response().statusCode()), representation, headers: _*)
}
}
class RequestGlue(pathMapping: String) extends HttpObject(pathMapping) {
override def get(req: Request) = respond("GET", req)
override def post(req: Request) = respond("POST", req)
override def put(req: Request) = respond("PUT", req)
override def delete(req: Request) = respond("DELETE", req)
}
HttpObjectsJettyHandler.launchServer(9933,
new RequestGlue("/"),
new HttpObject("/_specs/{specPath*}") {
override def get(req: Request) = {
val relativePath = req.path().valueFor("specPath")
val localPath = new Path(rootPath, relativePath)
if (localPath.exists()) {
OK(Bytes("application/json", new FileInputStream(localPath)))
} else {
NOT_FOUND
}
}
},
new RequestGlue("/{resource*}")
)
}
}