aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/org/traccar/BaseProtocolDecoder.java27
-rw-r--r--src/org/traccar/ExtendedObjectDecoder.java5
-rw-r--r--src/org/traccar/web/WebServer.java2
-rw-r--r--swagger.json1042
-rw-r--r--test/org/traccar/protocol/UproProtocolDecoderTest.java3
-rwxr-xr-xtools/swagger2html.py354
6 files changed, 751 insertions, 682 deletions
diff --git a/src/org/traccar/BaseProtocolDecoder.java b/src/org/traccar/BaseProtocolDecoder.java
index ea0905bf2..8748a9be6 100644
--- a/src/org/traccar/BaseProtocolDecoder.java
+++ b/src/org/traccar/BaseProtocolDecoder.java
@@ -23,6 +23,7 @@ import org.traccar.model.Position;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
+import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@@ -165,13 +166,31 @@ public abstract class BaseProtocolDecoder extends ExtendedObjectDecoder {
}
@Override
- protected void onMessageEvent(Channel channel, SocketAddress remoteAddress, Object msg) {
+ protected void onMessageEvent(
+ Channel channel, SocketAddress remoteAddress, Object originalMessage, Object decodedMessage) {
if (Context.getStatisticsManager() != null) {
Context.getStatisticsManager().registerMessageReceived();
}
- DeviceSession deviceSession = getDeviceSession(channel, remoteAddress);
- if (deviceSession != null) {
- Context.getConnectionManager().updateDevice(deviceSession.getDeviceId(), Device.STATUS_ONLINE, new Date());
+ Position position = null;
+ if (decodedMessage != null) {
+ if (decodedMessage instanceof Position) {
+ position = (Position) decodedMessage;
+ } else if (decodedMessage instanceof Collection) {
+ Collection positions = (Collection) decodedMessage;
+ if (!positions.isEmpty()) {
+ position = (Position) positions.iterator().next();
+ }
+ }
+ }
+ if (position != null) {
+ Context.getConnectionManager().updateDevice(
+ position.getDeviceId(), Device.STATUS_ONLINE, new Date());
+ } else {
+ DeviceSession deviceSession = getDeviceSession(channel, remoteAddress);
+ if (deviceSession != null) {
+ Context.getConnectionManager().updateDevice(
+ deviceSession.getDeviceId(), Device.STATUS_ONLINE, new Date());
+ }
}
}
diff --git a/src/org/traccar/ExtendedObjectDecoder.java b/src/org/traccar/ExtendedObjectDecoder.java
index ec03afa60..268e6f688 100644
--- a/src/org/traccar/ExtendedObjectDecoder.java
+++ b/src/org/traccar/ExtendedObjectDecoder.java
@@ -56,7 +56,7 @@ public abstract class ExtendedObjectDecoder implements ChannelUpstreamHandler {
MessageEvent e = (MessageEvent) evt;
Object originalMessage = e.getMessage();
Object decodedMessage = decode(e.getChannel(), e.getRemoteAddress(), originalMessage);
- onMessageEvent(e.getChannel(), e.getRemoteAddress(), originalMessage); // call after decode
+ onMessageEvent(e.getChannel(), e.getRemoteAddress(), originalMessage, decodedMessage);
if (originalMessage == decodedMessage) {
ctx.sendUpstream(evt);
} else {
@@ -77,7 +77,8 @@ public abstract class ExtendedObjectDecoder implements ChannelUpstreamHandler {
}
}
- protected void onMessageEvent(Channel channel, SocketAddress remoteAddress, Object msg) {
+ protected void onMessageEvent(
+ Channel channel, SocketAddress remoteAddress, Object originalMessage, Object decodedMessage) {
}
protected Object handleEmptyMessage(Channel channel, SocketAddress remoteAddress, Object msg) {
diff --git a/src/org/traccar/web/WebServer.java b/src/org/traccar/web/WebServer.java
index 8201f8d16..4dd37f4cc 100644
--- a/src/org/traccar/web/WebServer.java
+++ b/src/org/traccar/web/WebServer.java
@@ -102,7 +102,7 @@ public class WebServer {
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setResourceBase(config.getString("web.path"));
if (config.getBoolean("web.debug")) {
- resourceHandler.setWelcomeFiles(new String[] {"debug.html"});
+ resourceHandler.setWelcomeFiles(new String[] {"debug.html", "index.html"});
resourceHandler.setMinMemoryMappedContentLength(-1); // avoid locking files on Windows
} else {
resourceHandler.setWelcomeFiles(new String[] {"release.html", "index.html"});
diff --git a/swagger.json b/swagger.json
index 25b76963c..0f298f161 100644
--- a/swagger.json
+++ b/swagger.json
@@ -1,23 +1,29 @@
{
"swagger": "2.0",
"info": {
- "version": "3.8",
+ "version": "3.9",
"title": "traccar"
},
- "host": "traccar.org",
+ "host": "demo.traccar.org",
"basePath": "/api",
"schemes": [
"http"
],
+ "security": [
+ {
+ "basicAuth": []
+ }
+ ],
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
"paths": {
"/commands": {
"post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Dispatch commands to device",
"parameters": [
{
"name": "body",
@@ -31,70 +37,53 @@
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/Command"
}
+ },
+ "400": {
+ "description": "Could happen when dispatching to a device that is offline, the user doesn't have permission or an incorrect command _type_ for the device"
}
}
}
},
"/devices": {
"get": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Fetch a list of Devices",
+ "description": "Without any params, returns a list of the user's devices",
"parameters": [
{
- "name": "all",
- "in": "query",
- "required": true,
- "type": "boolean"
+ "$ref": "#/parameters/all"
},
{
- "name": "userId",
- "in": "query",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/userId"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Device"
}
}
+ },
+ "400": {
+ "description": "No permission"
}
}
},
"post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Create a Device",
"parameters": [
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/Device"
- }
+ "$ref": "#/parameters/Device"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/Device"
}
@@ -104,32 +93,18 @@
},
"/devices/{id}": {
"put": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Update a Device",
"parameters": [
{
- "name": "id",
- "in": "path",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/entityId"
},
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/Device"
- }
+ "$ref": "#/parameters/Device"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/Device"
}
@@ -137,42 +112,25 @@
}
},
"delete": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Update a Device",
"parameters": [
{
- "name": "id",
- "in": "path",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/entityId"
}
],
"responses": {
"204": {
- "description": "No Content",
- "headers": {}
+ "description": "No Content"
}
}
}
},
"/devices/{id}/distance": {
"put": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Update the distance counter of the Device",
"parameters": [
{
- "name": "id",
- "in": "path",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/entityId"
},
{
"name": "body",
@@ -185,34 +143,22 @@
],
"responses": {
"204": {
- "description": "No Content",
- "headers": {}
+ "description": "No Content"
}
}
}
},
"/devices/geofences": {
"post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Link a Geofence to a Device",
"parameters": [
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/DeviceGeofence"
- }
+ "$ref": "#/parameters/DeviceGeofence"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/DeviceGeofence"
}
@@ -220,56 +166,34 @@
}
},
"delete": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Remove a Geofence from a Device",
"parameters": [
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/DeviceGeofence"
- }
+ "$ref": "#/parameters/DeviceGeofence"
}
],
"responses": {
"204": {
- "description": "No Content",
- "headers": {}
+ "description": "No Content"
}
}
}
},
"/groups": {
"get": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Fetch a list of Groups",
+ "description": "Without any params, returns a list of the Groups the user belongs to",
"parameters": [
{
- "name": "all",
- "in": "query",
- "required": true,
- "type": "boolean"
+ "$ref": "#/parameters/all"
},
{
- "name": "userId",
- "in": "query",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/userId"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"type": "array",
"items": {
@@ -280,61 +204,39 @@
}
},
"post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Create a Group",
"parameters": [
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/Group"
- }
+ "$ref": "#/parameters/Group"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/Group"
}
+ },
+ "400": {
+ "description": "No permission"
}
}
}
},
"/groups/{id}": {
"put": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Update a Group",
"parameters": [
{
- "name": "id",
- "in": "path",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/entityId"
},
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/Group"
- }
+ "$ref": "#/parameters/Group"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/Group"
}
@@ -342,50 +244,30 @@
}
},
"delete": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Delete a Group",
"parameters": [
{
- "name": "id",
- "in": "path",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/entityId"
}
],
"responses": {
"204": {
- "description": "No Content",
- "headers": {}
+ "description": "No Content"
}
}
}
},
"/groups/geofences": {
"post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Link a Geofence to a Group",
"parameters": [
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GroupGeofence"
- }
+ "$ref": "#/parameters/GroupGeofence"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/GroupGeofence"
}
@@ -393,105 +275,64 @@
}
},
"delete": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Remove a Geofence from a Group",
"parameters": [
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GroupGeofence"
- }
+ "$ref": "#/parameters/GroupGeofence"
}
],
"responses": {
"204": {
- "description": "No Content",
- "headers": {}
+ "description": "No Content"
}
}
}
},
"/permissions/devices": {
"post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Link a Device to a User",
"parameters": [
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/DevicePermission"
- }
+ "$ref": "#/parameters/DevicePermission"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/DevicePermission"
}
+ },
+ "400": {
+ "description": "No permission"
}
}
},
"delete": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Remove a Device from a User",
"parameters": [
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/DevicePermission"
- }
+ "$ref": "#/parameters/DevicePermission"
}
],
"responses": {
"204": {
- "description": "No Content",
- "headers": {}
+ "description": "No Content"
}
}
}
},
"/permissions/groups": {
"post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Link a Group to a User",
"parameters": [
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GroupPermission"
- }
+ "$ref": "#/parameters/GroupPermission"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/GroupPermission"
}
@@ -499,52 +340,30 @@
}
},
"delete": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Remove a Group from a User",
"parameters": [
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GroupPermission"
- }
+ "$ref": "#/parameters/GroupPermission"
}
],
"responses": {
"204": {
- "description": "No Content",
- "headers": {}
+ "description": "No Content"
}
}
}
},
"/permissions/geofences": {
"post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Link a Geofence to a User",
"parameters": [
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GeofencePermission"
- }
+ "$ref": "#/parameters/GeofencePermission"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/GeofencePermission"
}
@@ -552,26 +371,15 @@
}
},
"delete": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Remove a Geofence from a User",
"parameters": [
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/GeofencePermission"
- }
+ "$ref": "#/parameters/GeofencePermission"
}
],
"responses": {
"204": {
- "description": "No Content",
- "headers": {}
+ "description": "No Content"
}
}
}
@@ -579,13 +387,7 @@
"/positions": {
"get": {
"summary" : "Fetches a list of Positions",
- "description" : "Without any params, it returns a list of last known positions for all the user's devices",
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "description" : "Without any params, it returns a list of last known positions for all the user's Devices. _from_ and _to_ fields are not required with _id_",
"parameters": [
{
"name": "deviceId",
@@ -597,17 +399,20 @@
{
"name": "from",
"in": "query",
- "description": "Not required with _id_",
+ "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`",
"required": false,
- "type": "string"
+ "type": "string",
+ "format": "date-time"
},
{
"name": "to",
"in": "query",
- "description": "Not required with _id_",
+ "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`",
"required": false,
- "type": "string"
- }, {
+ "type": "string",
+ "format": "date-time"
+ },
+ {
"name" : "id",
"in" : "query",
"description" : "To fetch one or more positions. Multiple params can be passed like `id=31&id=42`",
@@ -631,17 +436,10 @@
},
"/server": {
"get": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "parameters": [],
+ "summary": "Fetch Server information",
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/Server"
}
@@ -649,12 +447,7 @@
}
},
"put": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Update Server information",
"parameters": [
{
"name": "body",
@@ -668,7 +461,6 @@
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/Server"
}
@@ -678,34 +470,27 @@
},
"/session": {
"get": {
+ "summary": "Fetch Session information",
"consumes": [
"application/x-www-form-urlencoded"
],
- "produces": [
- "application/json"
- ],
- "parameters": [],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/User"
}
},
"404": {
- "description": "Not Found",
- "headers": {}
+ "description": "Not Found"
}
}
},
"post": {
+ "summary": "Create a new Session",
"consumes": [
"application/x-www-form-urlencoded"
],
- "produces": [
- "application/json"
- ],
"parameters": [
{
"name": "email",
@@ -717,82 +502,63 @@
"name": "password",
"in": "formData",
"required": true,
- "type": "string"
+ "type": "string",
+ "format": "password"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/User"
}
},
"401": {
- "description": "Unauthorized",
- "headers": {}
+ "description": "Unauthorized"
}
}
},
"delete": {
+ "summary": "Close the Session",
"consumes": [
"application/x-www-form-urlencoded"
],
- "produces": [
- "application/json"
- ],
"parameters": [],
"responses": {
"204": {
- "description": "No Content",
- "headers": {}
+ "description": "No Content"
}
}
}
},
"/users": {
"get": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
- "parameters": [],
+ "summary": "Fetch a list of Users",
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/User"
}
}
+ },
+ "400": {
+ "description": "No Permission"
}
}
},
"post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Create a User",
"parameters": [
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/User"
- }
+ "$ref": "#/parameters/User"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/User"
}
@@ -802,32 +568,18 @@
},
"/users/{id}": {
"put": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Update a User",
"parameters": [
{
- "name": "id",
- "in": "path",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/entityId"
},
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/User"
- }
+ "$ref": "#/parameters/User"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/User"
}
@@ -835,54 +587,37 @@
}
},
"delete": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Delete a User",
"parameters": [
{
- "name": "id",
- "in": "path",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/entityId"
}
],
"responses": {
"204": {
- "description": "No Content",
- "headers": {}
+ "description": "No Content"
}
}
}
},
"/users/notifications": {
"get": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Fetch a list of Notification types",
+ "description": "Without params, it returns a list of the user's enabled Notifications",
"parameters": [
{
"name": "all",
"in": "query",
- "required": true,
+ "description": "To fetch a list of all available Notifications",
"type": "boolean"
},
{
- "name": "userId",
- "in": "query",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/userId"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"type": "array",
"items": {
@@ -893,12 +628,7 @@
}
},
"post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Set or unset a Notification",
"parameters": [
{
"name": "body",
@@ -912,7 +642,6 @@
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/Notification"
}
@@ -922,12 +651,7 @@
},
"/commandtypes": {
"get": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Fetch a list of available Commands for the Device",
"parameters": [
{
"name": "deviceId",
@@ -939,49 +663,37 @@
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/CommandType"
}
}
+ },
+ "400": {
+ "description": "Could happen when trying to fetch from an offline device or the user does not have permission"
}
}
}
},
"/geofences": {
"get": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Fetch a list of Geofences",
+ "description": "Without params, it returns a list of Geofences the user has access to",
"parameters": [
{
- "name": "all",
- "in": "query",
- "required": true,
- "type": "boolean"
+ "$ref": "#/parameters/all"
},
{
- "name": "userId",
- "in": "query",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/userId"
},
{
"name": "groupId",
"in": "query",
- "required": true,
"type": "integer"
},
{
- "name": "deviceId",
- "in": "query",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/deviceId"
},
{
"name": "refresh",
@@ -993,7 +705,6 @@
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"type": "array",
"items": {
@@ -1004,26 +715,15 @@
}
},
"post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Create a Geofence",
"parameters": [
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/Geofence"
- }
+ "$ref": "#/parameters/Geofence"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/Geofence"
}
@@ -1033,32 +733,18 @@
},
"/geofences/{id}": {
"put": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Update a Geofence",
"parameters": [
{
- "name": "id",
- "in": "path",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/entityId"
},
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/Geofence"
- }
+ "$ref": "#/parameters/Geofence"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/Geofence"
}
@@ -1066,48 +752,29 @@
}
},
"delete": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Delete a Geofence",
"parameters": [
{
- "name": "id",
- "in": "path",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/entityId"
}
],
"responses": {
"204": {
- "description": "No Content",
- "headers": {}
+ "description": "No Content"
}
}
}
},
"/events/{id}": {
"get": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
"parameters": [
{
- "name": "id",
- "in": "path",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/entityId"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/Event"
}
@@ -1117,50 +784,25 @@
},
"/reports/route": {
"get": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Fetch a list of Positions within the time period for the Devices or Groups",
+ "description": "At least one _deviceId_ or one _groupId_ must be passed",
"parameters": [
{
- "name": "deviceId",
- "in": "query",
- "description": "at least one deviceId or one groupId must be passed",
- "required": true,
- "type": "array",
- "items": {
- "type": "integer"
- }
+ "$ref": "#/parameters/deviceIdArray"
},
{
- "name": "groupId",
- "in": "query",
- "description": "at least one deviceId or one groupId must be passed",
- "required": true,
- "type": "array",
- "items": {
- "type": "integer"
- }
+ "$ref": "#/parameters/groupIdArray"
},
{
- "name": "from",
- "in": "query",
- "required": true,
- "type": "string"
+ "$ref": "#/parameters/fromTime"
},
{
- "name": "to",
- "in": "query",
- "required": true,
- "type": "string"
+ "$ref": "#/parameters/toTime"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"type": "array",
"items": {
@@ -1173,60 +815,34 @@
},
"/reports/events": {
"get": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Fetch a list of Events within the time period for the Devices or Groups",
+ "description": "At least one _deviceId_ or one _groupId_ must be passed",
"parameters": [
{
- "name": "deviceId",
- "in": "query",
- "description": "at least one deviceId or one groupId must be passed",
- "required": true,
- "type": "array",
- "items": {
- "type": "integer"
- }
+ "$ref": "#/parameters/deviceIdArray"
},
{
- "name": "groupId",
- "in": "query",
- "description": "at least one deviceId or one groupId must be passed",
- "required": true,
- "type": "array",
- "items": {
- "type": "integer"
- }
+ "$ref": "#/parameters/groupIdArray"
},
{
"name": "type",
"in": "query",
"description": "% can be used to return events of all types",
- "required": true,
"type": "array",
"items": {
"type": "string"
}
},
{
- "name": "from",
- "in": "query",
- "required": true,
- "type": "string"
+ "$ref": "#/parameters/fromTime"
},
{
- "name": "to",
- "in": "query",
- "required": true,
- "type": "string"
+ "$ref": "#/parameters/toTime"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"type": "array",
"items": {
@@ -1239,50 +855,25 @@
},
"/reports/summary": {
"get": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Fetch a list of ReportSummary within the time period for the Devices or Groups",
+ "description": "At least one _deviceId_ or one _groupId_ must be passed",
"parameters": [
{
- "name": "deviceId",
- "in": "query",
- "description": "at least one deviceId or one groupId must be passed",
- "required": true,
- "type": "array",
- "items": {
- "type": "integer"
- }
+ "$ref": "#/parameters/deviceIdArray"
},
{
- "name": "groupId",
- "in": "query",
- "description": "at least one deviceId or one groupId must be passed",
- "required": true,
- "type": "array",
- "items": {
- "type": "integer"
- }
+ "$ref": "#/parameters/groupIdArray"
},
{
- "name": "from",
- "in": "query",
- "required": true,
- "type": "string"
+ "$ref": "#/parameters/fromTime"
},
{
- "name": "to",
- "in": "query",
- "required": true,
- "type": "string"
+ "$ref": "#/parameters/toTime"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"type": "array",
"items": {
@@ -1295,50 +886,25 @@
},
"/reports/trips": {
"get": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Fetch a list of ReportTrips within the time period for the Devices or Groups",
+ "description": "At least one _deviceId_ or one _groupId_ must be passed",
"parameters": [
{
- "name": "deviceId",
- "in": "query",
- "description": "at least one deviceId or one groupId must be passed",
- "required": true,
- "type": "array",
- "items": {
- "type": "integer"
- }
+ "$ref": "#/parameters/deviceIdArray"
},
{
- "name": "groupId",
- "in": "query",
- "description": "at least one deviceId or one groupId must be passed",
- "required": true,
- "type": "array",
- "items": {
- "type": "integer"
- }
+ "$ref": "#/parameters/groupIdArray"
},
{
- "name": "from",
- "in": "query",
- "required": true,
- "type": "string"
+ "$ref": "#/parameters/fromTime"
},
{
- "name": "to",
- "in": "query",
- "required": true,
- "type": "string"
+ "$ref": "#/parameters/toTime"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"type": "array",
"items": {
@@ -1351,30 +917,18 @@
},
"/statistics": {
"get": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Fetch server Statistics",
"parameters": [
{
- "name": "from",
- "in": "query",
- "required": true,
- "type": "string"
+ "$ref": "#/parameters/fromTime"
},
{
- "name": "to",
- "in": "query",
- "required": true,
- "type": "string"
+ "$ref": "#/parameters/toTime"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"type": "array",
"items": {
@@ -1387,24 +941,16 @@
},
"/attributes/aliases": {
"get": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Fetch a list of AttributeAlias",
+ "description": "Without params, it returns a list of AttributeAlias from all the user's Devices",
"parameters": [
{
- "name": "deviceId",
- "in": "query",
- "required": false,
- "type": "integer"
+ "$ref": "#/parameters/deviceId"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"type": "array",
"items": {
@@ -1415,26 +961,15 @@
}
},
"post": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Set an AttributeAlias",
"parameters": [
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/AttributeAlias"
- }
+ "$ref": "#/parameters/AttributeAlias"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/AttributeAlias"
}
@@ -1444,32 +979,18 @@
},
"/attributes/aliases/{id}": {
"put": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Update an AttributeAlias",
"parameters": [
{
- "name": "id",
- "in": "path",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/entityId"
},
{
- "name": "body",
- "in": "body",
- "required": true,
- "schema": {
- "$ref": "#/definitions/AttributeAlias"
- }
+ "$ref": "#/parameters/AttributeAlias"
}
],
"responses": {
"200": {
"description": "OK",
- "headers": {},
"schema": {
"$ref": "#/definitions/AttributeAlias"
}
@@ -1477,24 +998,15 @@
}
},
"delete": {
- "consumes": [
- "application/json"
- ],
- "produces": [
- "application/json"
- ],
+ "summary": "Delete an AttributeAlias",
"parameters": [
{
- "name": "id",
- "in": "path",
- "required": true,
- "type": "integer"
+ "$ref": "#/parameters/entityId"
}
],
"responses": {
"204": {
- "description": "No Content",
- "headers": {}
+ "description": "No Content"
}
}
}
@@ -1513,10 +1025,19 @@
"type": "string"
},
"deviceTime": {
- "type": "string"
+ "type": "string",
+ "format": "date-time",
+ "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`"
},
"fixTime": {
- "type": "string"
+ "type": "string",
+ "format": "date-time",
+ "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`"
+ },
+ "serverTime": {
+ "type": "string",
+ "format": "date-time",
+ "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`"
},
"outdated": {
"type": "boolean"
@@ -1534,7 +1055,8 @@
"type": "number"
},
"speed": {
- "type": "number"
+ "type": "number",
+ "description": "in knots"
},
"course": {
"type": "number"
@@ -1593,7 +1115,9 @@
"type": "boolean"
},
"expirationTime": {
- "type": "string"
+ "type": "string",
+ "format": "date-time",
+ "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`"
},
"deviceLimit": {
"type": "integer"
@@ -1680,7 +1204,9 @@
"type": "string"
},
"lastUpdate": {
- "type": "string"
+ "type": "string",
+ "format": "date-time",
+ "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`"
},
"positionId": {
"type": "integer"
@@ -1700,7 +1226,12 @@
"category": {
"type": "string"
},
- "geofenceIds": {},
+ "geofenceIds": {
+ "type": "array",
+ "items": {
+ "type": "integer"
+ }
+ },
"attributes": {}
}
},
@@ -1815,7 +1346,9 @@
"type": "string"
},
"serverTime": {
- "type": "string"
+ "type": "string",
+ "format": "date-time",
+ "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`"
},
"deviceId": {
"type": "integer"
@@ -1838,13 +1371,16 @@
"type": "string"
},
"maxSpeed": {
- "type": "number"
+ "type": "number",
+ "description": "in knots"
},
"averageSpeed": {
- "type": "number"
+ "type": "number",
+ "description": "in knots"
},
"distance": {
- "type": "number"
+ "type": "number",
+ "description": "in meters"
},
"engineHours": {
"type": "integer"
@@ -1860,19 +1396,24 @@
"type": "string"
},
"maxSpeed": {
- "type": "number"
+ "type": "number",
+ "description": "in knots"
},
"averageSpeed": {
- "type": "number"
+ "type": "number",
+ "description": "in knots"
},
"distance": {
- "type": "number"
+ "type": "number",
+ "description": "in meters"
},
"duration": {
"type": "integer"
},
"startTime": {
- "type": "string"
+ "type": "string",
+ "format": "date-time",
+ "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`"
},
"startAddress": {
"type": "string"
@@ -1884,7 +1425,9 @@
"type": "number"
},
"endTime": {
- "type": "string"
+ "type": "string",
+ "format": "date-time",
+ "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`"
},
"endAddress": {
"type": "string"
@@ -1900,7 +1443,9 @@
"Statistics": {
"properties": {
"captureTime": {
- "type": "string"
+ "type": "string",
+ "format": "date-time",
+ "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`"
},
"activeUsers": {
"type": "integer"
@@ -1941,9 +1486,156 @@
"type": "integer"
},
"totalDistance": {
- "type": "number"
+ "type": "number",
+ "description": "in meters"
}
}
}
+ },
+ "parameters": {
+ "entityId": {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "type": "integer"
+ },
+ "all": {
+ "name": "all",
+ "in": "query",
+ "description": "Can only be used by admin users to fetch all entities",
+ "type": "boolean"
+ },
+ "userId": {
+ "name": "userId",
+ "in": "query",
+ "description": "Standard users can use this only with their own _userId_",
+ "type": "integer"
+ },
+ "deviceId": {
+ "name": "deviceId",
+ "in": "query",
+ "description": "Standard users can use this only with _userId_s, they have access to",
+ "type": "integer"
+ },
+ "Device": {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/Device"
+ }
+ },
+ "DeviceGeofence": {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/DeviceGeofence"
+ }
+ },
+ "Group": {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/Group"
+ }
+ },
+ "GroupGeofence": {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/GroupGeofence"
+ }
+ },
+ "DevicePermission": {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/DevicePermission"
+ }
+ },
+ "GroupPermission": {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/GroupPermission"
+ }
+ },
+ "GeofencePermission": {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/GeofencePermission"
+ }
+ },
+ "User": {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/User"
+ }
+ },
+ "Geofence": {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/Geofence"
+ }
+ },
+ "AttributeAlias": {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/AttributeAlias"
+ }
+ },
+ "deviceIdArray": {
+ "name": "deviceId",
+ "in": "query",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "collectionFormat": "multi"
+ },
+ "groupIdArray": {
+ "name": "groupId",
+ "in": "query",
+ "type": "array",
+ "items": {
+ "type": "integer"
+ },
+ "collectionFormat": "multi"
+ },
+ "fromTime": {
+ "name": "from",
+ "in": "query",
+ "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`",
+ "required": true,
+ "type": "string",
+ "format": "date-time"
+ },
+ "toTime": {
+ "name": "to",
+ "in": "query",
+ "description": "in IS0 8601 format. eg. `1963-11-22T18:30:00Z`",
+ "required": true,
+ "type": "string",
+ "format": "date-time"
+ }
+ },
+ "securityDefinitions": {
+ "basicAuth": {
+ "type": "basic",
+ "description": "Basic HTTP authorization with _email_ and _password_"
+ }
}
}
diff --git a/test/org/traccar/protocol/UproProtocolDecoderTest.java b/test/org/traccar/protocol/UproProtocolDecoderTest.java
index 3af62da08..270caeab5 100644
--- a/test/org/traccar/protocol/UproProtocolDecoderTest.java
+++ b/test/org/traccar/protocol/UproProtocolDecoderTest.java
@@ -11,6 +11,9 @@ public class UproProtocolDecoderTest extends ProtocolTest {
UproProtocolDecoder decoder = new UproProtocolDecoder(new UproProtocol());
verifyPosition(decoder, binary(
+ "2a4d473230313836383530303032303030343836372c414226413035303032343138313438373536303636303131373732323030303031313132313626583331302c3236302c34383837322c353639312c37333b34383837322c3732322c38363b34383837322c353639332c38383b34383837322c323336332c39303b34383837322c323336322c393726423030303030303030303026573030264e3230265a31342659313430303323"));
+
+ verifyPosition(decoder, binary(
"2a4d473230303639333530323030303033353537332c42412641303834313237333332363334353230373033383933373630303030303235313131362642303130303030303030302647303036323030264d393930264e3235264f3035303026433030313a363b363926510411058c0c125c0d0a2fff4237ee614d66454c140826555f50000000000300000000000000000026543139333723"));
verifyPosition(decoder, buffer(
diff --git a/tools/swagger2html.py b/tools/swagger2html.py
new file mode 100755
index 000000000..a3488835c
--- /dev/null
+++ b/tools/swagger2html.py
@@ -0,0 +1,354 @@
+#!/usr/bin/python
+
+import sys, argparse, json, re
+
+def handleException(etype, e=None):
+ if etype == 'KeyError':
+ print "Error: Required property {} not found".format(e)
+ elif etype == 'IOError':
+ print "Error ({}): {}".format(e.errno, e.strerror)
+ elif etype == 'ValueError':
+ print "Error: Unable to parse input as JSON"
+ elif etype == 'Custom':
+ print e
+ sys.exit(1)
+
+def get_json(filename):
+ try:
+ with open(filename) as json_file:
+ json_data = json.load(json_file)
+ return json_data
+ except IOError as e:
+ handleException('IOError', e)
+ except ValueError:
+ handleException('ValueError')
+ except:
+ print "Unexpected error: {}".format(sys.exc_info()[0])
+ raise
+
+def write_file(filename, body):
+ try:
+ with open(filename, 'w') as md_file:
+ md_file.write(body)
+ except IOError as e:
+ handleException('IOError', e)
+
+def make_header(json_data):
+ try:
+ if not 'swagger' in json_data:
+ raise KeyError
+ info = json_data['info']
+ md = "<h1>{}</h1>\n".format(info['title'])
+ md += "<p>Version: {}</p>\n".format(info['version'])
+ if 'license' in info:
+ md += "<p>License: "
+ license = info['license']
+ if 'url' in license:
+ md += '<a href="{}">{}</a>'.format(license['url'], license['name'])
+ else:
+ md += license['name']
+ md += '</p>\n'
+ if 'contact' in info:
+ contact = info['contact']
+ if 'name' in contact or 'email' in contact:
+ md += '<p>Contact: '
+ if not 'name' in contact:
+ md += '<a href="mailto:{0}">{0}</a>'.format(contact['email'])
+ elif not 'email' in contact:
+ md += contact['name']
+ else:
+ md += '{0} &lt;<a href="mailto:{1}"&gt;{1}</a>'.format(contact['name'], contact['email'])
+ md += ' \n'
+ if 'url' in contact:
+ md += "<p>Website: {}</p>\n".format(contact['url'])
+ if 'termsOfService' in info:
+ md += '<p>Terms of Service: {}</p>\n'.format(info['termsOfService'])
+ if 'host' in json_data:
+ md += '<p>Base URL: '
+ base = json_data['host']
+ if 'basePath' in json_data:
+ base += json_data['basePath']
+ else:
+ base += '/'
+ if 'schemes' in json_data:
+ md += (', ').join(map(
+ lambda x: '<a href="{0}://{1}">{0}://{1}</a>'.format(x, base),
+ json_data['schemes']
+ ))
+ else:
+ md += '<a href="{0}">{0}</a>'.format(base)
+ md += '</p>\n'
+ if 'description' in info:
+ md += '<p>Description: {}</p>\n'.format(info['description'])
+ md += '\n'
+ return md
+ except KeyErrori as e:
+ handleException('KeyError', e)
+
+def make_ref(ref):
+ href = ref.split('/')[1:]
+ return '<a href="#{}">{}</a>'.format('_'.join(href), href[-1])
+
+def get_ref(ref, raw):
+ keys = ref.split('/')
+ return raw[keys[1]][keys[2]]
+
+def make_html(s):
+ reg = re.compile(r"[*_]{3}(.+?)[*_]{3}")
+ s = reg.sub(r"<strong><em>\1</em></strong>", s)
+ reg = re.compile(r"[*_]{2}(.+?)[*_]{2}")
+ s = reg.sub(r"<strong>\1</strong>", s)
+ reg = re.compile(r"[*_](.+?)[*_]")
+ s = reg.sub(r"<em>\1</em>", s)
+ reg = re.compile(r"\`(.+?)\`")
+ s = reg.sub(r"<code>\1</code>", s)
+ return s
+
+def make_table(data):
+ md = '<table class="table-bordered">\n'
+ md += ' <thead>\n'
+ md += ' <tr>\n'
+ for col in data[0]:
+ md += ' <td>{}</td>\n'.format(col)
+ md += ' </tr>\n'
+ md += ' </thead>\n'
+ md += ' <tbody>\n'
+ for row in data[1:]:
+ md += ' <tr>\n'
+ for cell in row:
+ md += ' <td>{}</td>\n'.format(cell)
+ md += ' </tr>\n'
+ md += ' </tbody>\n'
+ md += '</table>\n'
+ return md
+
+def make_params_table(itemsraw, raw):
+ items = []
+ for item in itemsraw:
+ if '$ref' in item:
+ items.append(get_ref(item['$ref'], raw))
+ else:
+ items.append(item)
+ try:
+ fields = list(set([]).union(*map(lambda x: x.keys(), items)))
+ row = [ 'Name', 'ParamType' ]
+ if 'description' in fields:
+ row.append('Description')
+ if 'required' in fields:
+ row.append('Required')
+ if 'type' in fields:
+ row.append('DataType')
+ if 'schema' in fields:
+ row.append('Schema')
+ table = [ row ]
+ for item in items:
+ row = [ "<em>{}</em>".format(item['name']), item['in'] ]
+ if 'description' in fields:
+ if 'description' in item:
+ row.append(make_html(item['description']))
+ else:
+ row.append('')
+ if 'required' in fields:
+ required = 'False'
+ if 'required' in item and item['required']:
+ required = "<strong>True</strong>"
+ row.append(required)
+ if 'type' in fields:
+ type = ''
+ if 'type' in item:
+ if item['type'] == 'array':
+ type = "[ <em>{}</em> ]".format(item['items']['type'])
+ else:
+ type = item['type']
+ if 'format' in item:
+ type += " ({})".format(item['format'])
+ type = "<em>{}</em>".format(type)
+ row.append(type)
+ if 'schema' in fields:
+ if 'schema' in item:
+ if '$ref' in item['schema']:
+ row.append(make_ref(item['schema']['$ref']))
+ else:
+ row.append('')
+ table.append(row)
+ return make_table(table)
+ except KeyError as e:
+ handleException('KeyError', e)
+
+def make_responses_table(responses):
+ try:
+ fields = list(
+ set([]).union(*map(lambda x: x.keys(),
+ map(lambda x: responses[x], responses.keys())
+ ))
+ )
+ row = [ 'Status Code', 'Description' ]
+ if 'headers' in fields:
+ row.append('Headers')
+ if 'schema' in fields:
+ row.append('Schema')
+ if 'examples' in fields:
+ row.append('Examples')
+ table = [ row ]
+ for key in sorted(responses):
+ response = responses[key]
+ row = [ "<em>{}</em>".format(key), make_html(response['description']) ]
+ if 'headers' in fields:
+ header = ''
+ if 'headers' in response:
+ hrow = []
+ for header, h_obj in response['headers'].iteritems():
+ hrow += "{} ({})".format(header, h_obj['type'])
+ if 'description' in h_obj:
+ hrow += ": {}".format(h_obj['description'])
+ header = ' \n'.join(hrow)
+ row.append(header)
+ if 'schema' in fields:
+ schema = ''
+ if 'schema' in response:
+ if '$ref' in response['schema']:
+ schema += make_ref(response['schema']['$ref'])
+ if 'type' in response['schema']:
+ if response['schema']['type'] == 'array':
+ if '$ref' in response['schema']['items']:
+ schema += make_ref(response['schema']['items']['$ref'])
+ schema = "[ {} ]".format(schema)
+ row.append(schema)
+ if 'examples' in fields:
+ if 'examples' in response:
+ row.append(response['examples'])
+ else:
+ row.append('')
+ table.append(row)
+ return make_table(table)
+ except KeyError as e:
+ handleException('KeyError', e)
+
+def make_paths(sections, json_data):
+ md = '<h2><a name="paths"></a>Paths</h2>\n'
+ for key in sorted(sections):
+ md += '<h3><a name="paths_{0}"></a>{0}</h3>\n'.format(key)
+ for section in sections[key]:
+ md += '<h4><a name="{}"></a><code>{}</code></h4>\n'.format(
+ section['href'], section['title']
+ )
+ operation = section['operation']
+ if 'summary' in operation:
+ md += '<p>Summary: {}</p>\n'.format(make_html(operation['summary']))
+ if 'description' in operation:
+ md += '<p>Description: {}</p>\n'.format(make_html(operation['description']))
+ md += '<h5>Parameters</h5>\n'
+ if 'parameters' in operation and len(operation['parameters']) > 0:
+ md += make_params_table(operation['parameters'], json_data)
+ else:
+ md += "<p><em>None</em></p>\n"
+ md += '<h5>Responses</h5>\n'
+ md += make_responses_table(operation['responses'])
+ md += '\n'
+ md += '\n'
+ return md
+
+def make_contents(path_section, json_data):
+ md = '<h3>Contents</h3>\n'
+ md += '<ul>\n'
+ md += ' <li><a href="#paths">Paths</a>\n'
+ md += ' <ul>\n'
+ for key in sorted(path_section):
+ md += ' <li><a href="#paths_{0}">{0}</a>\n'.format(key)
+ md += ' <ul>\n'
+ for section in path_section[key]:
+ md += ' <li><a href="#{}">{}</a></li>\n'.format(
+ section['href'], section['title']
+ )
+ md += ' </ul>\n'
+ md += ' </li>\n'
+ md += ' </ul>\n'
+ md += ' </li>\n'
+ md += ' <li><a href="#definitions">Models</a>\n'
+ md += ' <ul>\n'
+ for key in sorted(json_data['definitions']):
+ md += ' <li><a href="#definitions_{0}">{0}</a></li>\n'.format(key)
+ md += ' </ul>\n'
+ md += ' </li>\n'
+ md += '</ul>\n'
+ return md
+
+def make_definitions(json_data):
+ md = '<h2><a name="definitions"></a>Models</h2>\n'
+ for name in sorted(json_data['definitions']):
+ md += '<h3><a name="definitions_{0}"></a>{0}</h3>\n'.format(name)
+ model = json_data['definitions'][name]
+ if 'properties' in model:
+ fields = list(
+ set(['type']).union(
+ *map(lambda x: x.keys(),
+ map(lambda x: model['properties'][x], model['properties'].keys())
+ )
+ )
+ )
+ row = [ 'Property', 'Type' ]
+ if 'description' in fields:
+ row.append('Description')
+ table = [ row ]
+ for key in sorted(model['properties']):
+ property = model['properties'][key]
+ row = [ "<em>{}</em>".format(key) ]
+ if 'type' in property:
+ type = property['type']
+ if 'format' in property:
+ type += " ({})".format(property['format'])
+ row.append("<em>{}</em>".format(type))
+ elif '$ref' in property:
+ row.append(make_ref(property['$ref']))
+ else:
+ row.append('')
+ if 'description' in fields:
+ if 'description' in property:
+ row.append(make_html(property['description']))
+ else:
+ row.append('')
+ table.append(row)
+ md += make_table(table)
+ return md
+
+def make_markdown(json_data):
+ path_sections = {}
+ for endpoint in json_data['paths']:
+ path_split = endpoint.split('/')
+ path_key = path_split[1]
+ if not path_key in path_sections:
+ path_sections[path_key] = []
+ for method, operation in json_data['paths'][endpoint].iteritems():
+ if 'operationId' in operation:
+ link = operation['operationId']
+ else:
+ link = ''.join([
+ c for c in endpoint if c not in ['/', '{', '}']
+ ])
+ path_sections[path_key].append({
+ 'title': '{} {}'.format(method.upper(), endpoint),
+ 'href': 'paths_{}_{}'.format(link, method.upper()),
+ 'operation': operation
+ })
+ md = make_header(json_data)
+ md += make_contents(path_sections, json_data)
+ md += make_paths(path_sections, json_data)
+ md += make_definitions(json_data)
+ return md
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('SPECFILE', help="path to swagger.json file")
+ parser.add_argument('OUTFILE', help="path to output HTML file")
+ args = parser.parse_args()
+
+ marked_down = make_markdown(get_json(args.SPECFILE))
+
+ if args.OUTFILE:
+ write_file(args.OUTFILE, marked_down)
+ print " success: {}".format(args.OUTFILE)
+ else:
+ print marked_down
+
+if __name__ == '__main__':
+ main()