Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swagger support #799

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>

<build>
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/spark/ResponseTransformerRouteImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package spark;

import spark.swagger.RouteDocumentation;

/**
* A ResponseTransformerRouteImpl is built up by a path (for url-matching) and the
* implementation of the 'render' method. ResponseTransformerRoute instead of
Expand Down Expand Up @@ -49,6 +51,7 @@ public Object handle(Request request, Response response) throws Exception {
};
}


protected ResponseTransformerRouteImpl(String path, String acceptType, Route route) {
super(path, acceptType, route);
}
Expand Down
76 changes: 65 additions & 11 deletions src/main/java/spark/Routable.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package spark;

import spark.route.HttpMethod;
import spark.swagger.RouteDocumentation;
import spark.utils.SparkUtils;

/**
Expand All @@ -32,13 +33,16 @@ abstract class Routable {
*/
protected abstract void addRoute(String httpMethod, RouteImpl route);

protected abstract void addRoute(String httpMethod, RouteDocumentation documentation, RouteImpl route);

/**
* Adds a filter
*
* @param httpMethod the HTTP method
* @param filter the route implementation
*/
protected abstract void addFilter(String httpMethod, FilterImpl filter);
protected abstract void addFilter(String httpMethod, RouteDocumentation documentation, FilterImpl filter);

/////////////////////////////
// Default implementations //
Expand All @@ -53,6 +57,10 @@ public void get(final String path, final Route route) {
addRoute(HttpMethod.get.name(), RouteImpl.create(path, route));
}

public void get(final String path, final RouteDocumentation documentation, final Route route) {
addRoute(HttpMethod.get.name(), documentation, RouteImpl.create(path, route));
}

/**
* Map the route for HTTP POST requests
*
Expand All @@ -62,6 +70,15 @@ public void get(final String path, final Route route) {
public void post(String path, Route route) {
addRoute(HttpMethod.post.name(), RouteImpl.create(path, route));
}
/**
* Map the route for HTTP POST requests
*
* @param path the path
* @param route The route
*/
public void post(String path, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.post.name(), documentation, RouteImpl.create(path, route));
}

/**
* Map the route for HTTP PUT requests
Expand Down Expand Up @@ -102,6 +119,15 @@ public void delete(String path, Route route) {
public void head(String path, Route route) {
addRoute(HttpMethod.head.name(), RouteImpl.create(path, route));
}
public void head(String path, String accept, Route route) {
addRoute(HttpMethod.head.name(), RouteImpl.create(path, accept, route));
}
public void head(String path, String accept, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.head.name(), documentation, RouteImpl.create(path, accept, route));
}
public void head(String path, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.head.name(), documentation, RouteImpl.create(path, route));
}

/**
* Map the route for HTTP TRACE requests
Expand Down Expand Up @@ -168,6 +194,10 @@ public void get(String path, String acceptType, Route route) {
addRoute(HttpMethod.get.name(), RouteImpl.create(path, acceptType, route));
}

public void get(String path, String acceptType, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.get.name(), documentation, RouteImpl.create(path, acceptType, route));
}

/**
* Map the route for HTTP POST requests
*
Expand All @@ -178,6 +208,9 @@ public void get(String path, String acceptType, Route route) {
public void post(String path, String acceptType, Route route) {
addRoute(HttpMethod.post.name(), RouteImpl.create(path, acceptType, route));
}
public void post(String path, String acceptType, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.post.name(), documentation, RouteImpl.create(path, acceptType, route));
}

/**
* Map the route for HTTP PUT requests
Expand All @@ -189,6 +222,12 @@ public void post(String path, String acceptType, Route route) {
public void put(String path, String acceptType, Route route) {
addRoute(HttpMethod.put.name(), RouteImpl.create(path, acceptType, route));
}
public void put(String path, String acceptType, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.put.name(), documentation, RouteImpl.create(path, acceptType, route));
}
public void put(String path, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.put.name(), documentation, RouteImpl.create(path, route));
}

/**
* Map the route for HTTP PATCH requests
Expand All @@ -200,7 +239,12 @@ public void put(String path, String acceptType, Route route) {
public void patch(String path, String acceptType, Route route) {
addRoute(HttpMethod.patch.name(), RouteImpl.create(path, acceptType, route));
}

public void patch(String path, String acceptType, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.patch.name(), documentation, RouteImpl.create(path, acceptType, route));
}
public void patch(String path, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.patch.name(), documentation, RouteImpl.create(path, route));
}
/**
* Map the route for HTTP DELETE requests
*
Expand All @@ -211,16 +255,11 @@ public void patch(String path, String acceptType, Route route) {
public void delete(String path, String acceptType, Route route) {
addRoute(HttpMethod.delete.name(), RouteImpl.create(path, acceptType, route));
}

/**
* Map the route for HTTP HEAD requests
*
* @param path the path
* @param acceptType the accept type
* @param route The route
*/
public void head(String path, String acceptType, Route route) {
addRoute(HttpMethod.head.name(), RouteImpl.create(path, acceptType, route));
public void delete(String path, String acceptType, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.delete.name(), documentation, RouteImpl.create(path, acceptType, route));
}
public void delete(String path, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.delete.name(), documentation, RouteImpl.create(path, route));
}

/**
Expand All @@ -233,6 +272,12 @@ public void head(String path, String acceptType, Route route) {
public void trace(String path, String acceptType, Route route) {
addRoute(HttpMethod.trace.name(), RouteImpl.create(path, acceptType, route));
}
public void trace(String path, String acceptType, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.trace.name(), documentation, RouteImpl.create(path, acceptType, route));
}
public void trace(String path, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.trace.name(), documentation, RouteImpl.create(path, route));
}

/**
* Map the route for HTTP CONNECT requests
Expand All @@ -244,6 +289,9 @@ public void trace(String path, String acceptType, Route route) {
public void connect(String path, String acceptType, Route route) {
addRoute(HttpMethod.connect.name(), RouteImpl.create(path, acceptType, route));
}
public void connect(String path, String acceptType, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.connect.name(), documentation, RouteImpl.create(path, acceptType, route));
}

/**
* Map the route for HTTP OPTIONS requests
Expand All @@ -255,6 +303,12 @@ public void connect(String path, String acceptType, Route route) {
public void options(String path, String acceptType, Route route) {
addRoute(HttpMethod.options.name(), RouteImpl.create(path, acceptType, route));
}
public void options(String path, String acceptType, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.options.name(), documentation, RouteImpl.create(path, acceptType, route));
}
public void options(String path, RouteDocumentation documentation, Route route) {
addRoute(HttpMethod.options.name(), documentation, RouteImpl.create(path, route));
}


/**
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/spark/Route.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package spark;

import spark.swagger.RouteDocumentation;

/**
* Created by Per Wendel on 2014-05-10.
*/
Expand All @@ -15,5 +17,4 @@ public interface Route {
* @throws java.lang.Exception implementation can choose to throw exception
*/
Object handle(Request request, Response response) throws Exception;

}
2 changes: 1 addition & 1 deletion src/main/java/spark/RouteImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package spark;


import spark.swagger.RouteDocumentation;
import spark.utils.Wrapper;

/**
Expand Down Expand Up @@ -142,5 +143,4 @@ String getPath() {
public Object delegate() {
return this.delegate;
}

}
102 changes: 90 additions & 12 deletions src/main/java/spark/Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,35 @@
*/
package spark;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import spark.embeddedserver.EmbeddedServer;
import spark.embeddedserver.EmbeddedServers;
import spark.embeddedserver.jetty.websocket.WebSocketHandlerClassWrapper;
import spark.embeddedserver.jetty.websocket.WebSocketHandlerInstanceWrapper;
import spark.embeddedserver.jetty.websocket.WebSocketHandlerWrapper;
import spark.route.RouteEntry;
import spark.route.Routes;
import spark.route.ServletRoutes;
import spark.ssl.SslStores;
import spark.staticfiles.MimeType;
import spark.staticfiles.StaticFilesConfiguration;
import spark.swagger.Payload;
import spark.swagger.RouteDocumentation;
import spark.swagger.RouteMethod;
import spark.swagger.SwaggerDoc;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;

import static java.util.Objects.requireNonNull;
import static spark.globalstate.ServletFlag.isRunningFromServlet;
Expand Down Expand Up @@ -88,6 +96,7 @@ public final class Service extends Routable {
public final StaticFiles staticFiles;

private final StaticFilesConfiguration staticFilesConfiguration;
private SwaggerDoc documentation = new SwaggerDoc();

/**
* Creates a new Service (a Spark instance). This should be used instead of the static API if the user wants
Expand All @@ -110,6 +119,15 @@ private Service() {
}
}

public SwaggerDoc getDocumentation() {
return documentation("/swagger.json");
}

public SwaggerDoc documentation(String path) {
documentation.setSwaggerPath(path);
return documentation;
}

/**
* Set the IP address that Spark should listen on. If not called the default
* address is '0.0.0.0'. This has to be called before any route mapping is
Expand Down Expand Up @@ -447,18 +465,35 @@ public String getPaths() {
@Override
public void addRoute(String httpMethod, RouteImpl route) {
init();
routes.add(httpMethod + " '" + getPaths() + route.getPath() + "'", route.getAcceptType(), route);
routes.add(httpMethod + " '" + getPaths() + route.getPath() + "'", route.getAcceptType(), null, route);
}
public void addRoute(String httpMethod, RouteDocumentation documentation, RouteImpl route) {
init();
routes.add(httpMethod + " '" + getPaths() + route.getPath() + "'", route.getAcceptType(), documentation, route);
}

@Override
public void addFilter(String httpMethod, FilterImpl filter) {
init();
routes.add(httpMethod + " '" + getPaths() + filter.getPath() + "'", filter.getAcceptType(), filter);
routes.add(httpMethod + " '" + getPaths() + filter.getPath() + "'", filter.getAcceptType(), null, filter);
}
@Override
public void addFilter(String httpMethod, RouteDocumentation documentation, FilterImpl filter) {
init();
routes.add(httpMethod + " '" + getPaths() + filter.getPath() + "'", filter.getAcceptType(), documentation, filter);
}

private void initSwagger() {
get(documentation.swaggerPath(), new RouteDocumentation("swagger").summary("Swagger JSON v2 Documentation endpoint").produces(new Payload().json()),
(req, resp) -> {
resp.header("Content-Type", "application/json");
return getSwaggerV2JSON();
}
);
}

public synchronized void init() {
if (!initialized) {

initializeRouteMatcher();

if (!isRunningFromServlet()) {
Expand Down Expand Up @@ -493,6 +528,7 @@ public synchronized void init() {
}).start();
}
initialized = true;
initSwagger();
}
}

Expand Down Expand Up @@ -578,6 +614,48 @@ public HaltException halt(int status, String body) {
throw new HaltException(status, body);
}

public Routes getRoutes() {
return routes;
}

public String getSwaggerV2JSON() {
Gson gson = new GsonBuilder()
.enableComplexMapKeySerialization()
// .setDateFormat(DateFormat.LONG)
// .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
.setPrettyPrinting()
.setVersion(1.0)
.setExclusionStrategies(new ExclusionStrategy[] {
new ExclusionStrategy() {

@Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
return (fieldAttributes.getName().equals("enabled"));
}

@Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
}})
.create();

synchronized(Service.class) {
if (documentation.getPaths().isEmpty()) {
documentation.calculateDefinitions(this.routes);
for (RouteEntry route : this.routes.getRoutes()) {
String path = RouteDocumentation.swaggerPath(route.getPath());
if (!documentation.getPaths().containsKey(path)) {
documentation.getPaths().put(path, new RouteMethod());
}
documentation.getPaths().get(path)
.put(route.getHttpMethod().name(), route.getDocumentation());
}
}
}
return gson.toJson(this.documentation);
}

/**
* Provides static files utility methods.
*/
Expand Down
Loading