All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cj.restspecs.server.RestSpecServer.scala Maven / Gradle / Ivy

Go to download

A test-friendly mechanism for expressing RESTful http contracts. Specification server.

There is a newer version: 10.0.1
Show newest version
/**
 * 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*}") ) } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy