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

setup swagger spec and raml2swagger script #71

Merged
merged 10 commits into from
Feb 14, 2017
Merged
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
12 changes: 12 additions & 0 deletions DEVGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,15 @@ npm run docs
This task will generate all documentation to the `docs` directory. All
the source script files will be parsed via ESDoc and all RAML files
in `raml/*.raml` will create API docs.

# Generating Swagger

```
npm run raml2swagger
```

This task will process the /raml/api.v1.raml and convert it to /swagger/api.v1.yaml.

This task should be run whenever RAML files are modified.

Read more about this in [/swagger/README.md](swagger/README.md)
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"predocs-raml": "mkdir docs/raml ; exit 0",
"docs-raml": "\"node_modules/.bin/raml2html\" \"raml/api.v1.raml\" > \"docs/raml/index.html\"",
"prescaffold": "cd scaffold && npm install",
"scaffold": "node scaffold \"config/scaffold.config.js\""
"scaffold": "node scaffold \"config/scaffold.config.js\"",
"raml2swagger": "node node_modules/api-spec-converter/bin/api-spec-converter raml/api.v1.raml --from=raml --to=swagger_2 --check | node node_modules/json2yaml/CLI > swagger/api.v1.yaml"
},
"author": "",
"license": "",
Expand Down Expand Up @@ -65,6 +66,7 @@
"zone.js": "^0.6.21"
},
"devDependencies": {
"api-spec-converter": "^2.0.1",
"babel-eslint": "^6.1.2",
"babel-loader": "^6.2.5",
"babel-plugin-angular2-annotations": "^5.1.0",
Expand All @@ -91,6 +93,7 @@
"istanbul": "^0.4.5",
"jasmine-reporters": "^2.2.0",
"json-loader": "^0.5.4",
"json2yaml": "^1.1.0",
"karma": "^1.2.0",
"karma-babel-preprocessor": "^6.0.1",
"karma-chrome-launcher": "^2.0.0",
Expand Down
15 changes: 4 additions & 11 deletions raml/api.v1.raml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#%RAML 0.8
title: WebStart Example Services
version: 1
baseUri: /api/v1
baseUri: http://calproc.website/api/v1
mediaType: application/json
protocols:
- HTTP
Expand All @@ -10,23 +10,16 @@ documentation:
- title: TODO
content: Put your stuff here!

securitySchemes: []

schemas:
-
Empty: !include "api/v1/common/empty.schema.json"
Error: !include "api/v1/errors/error.schema.json"
User: !include "api/v1/users/user.schema.json"
UserCollection: !include "api/v1/users/user-collection.schema.json"
Login: !include "api/v1/login/login.schema.json"

securitySchemes:
- JWT:
type: x-JWT
describedBy:
headers:
Authorization:
description: |
JWT token containing claims for 'exp' (expiration), 'roles', and 'permissions'. TODO: define the format for each of those
required: true

traits: !include api/traits.raml
resourceTypes: !include api/types.raml

Expand Down
10 changes: 3 additions & 7 deletions raml/api/traits.raml
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
- secured:
#securedBy: [JWT]
#headers:
# Authorization:
# description: |
# JWT token containing claims for 'exp' (expiration), 'roles', and 'permissions'.
# required: true
responses:
401:
description: Authentication token is invalid or has expired. Client should display a message to the user and return them user to the login screen.
Expand All @@ -15,6 +9,8 @@
responses:
204:
description: The request was successfully handled
body:
schema: Empty

- paged:
queryParameters:
Expand Down Expand Up @@ -44,7 +40,7 @@

- mock:
responses:
299:
200:
description: This is a mock service that has not yet been implemented

- member:
Expand Down
8 changes: 8 additions & 0 deletions raml/api/v1/common/empty.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "http://kpmg/webstart/Empty",
"type": "object",
"$schema": "http://json-schema.org/draft-03/schema",
"additionalProperties": false,
"properties": {
}
}
3 changes: 1 addition & 2 deletions raml/api/v1/errors/error.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
"type": "string"
},
"errorCode": {
"type": "integer",
"format": "int32"
"type": "integer"
},
"message": {
"type": "string",
Expand Down
2 changes: 2 additions & 0 deletions raml/api/v1/login/login.raml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ post:
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJJc3N1ZXIiOiJodHRwOi8vd3d3Lnd5bnlhcmRncm91cC5jb20iLCJBdWRpZW5jZSI6IkFDQSIsIlByaW5jaXBhbCI6eyJTZXNzaW9uSWQiOiI3ZDZjN2ZjMC1lNzkzLTQyNjMtOTQ3OC01MmQzMmQyYzYzNjEiLCJVc2VyS2V5IjoiNCIsIlVzZXJOYW1lIjoia2NsaWZmZSIsIkNsYWltcyI6WyJBZG1pbiJdLCJMb2NhbGUiOiJlbi1OWiIsIlNlc3Npb25UaW1lT3V0IjoiXC9EYXRlKDE0NTA3OTQ1OTczNjIpXC8iLCJJc3N1ZWRUbyI6bnVsbCwiSWRlbnRpdHkiOnsiTmFtZSI6ImtjbGlmZmUiLCJBdXRoZW50aWNhdGlvblR5cGUiOiJXeW55YXJkIiwiSXNBdXRoZW50aWNhdGVkIjp0cnVlfX0sIkV4cGlyeSI6IlwvRGF0ZSgxKVwvIn0.0GZlnA-mdDQqSfSKvBlWsUehtVCRkNK8DA9siyeVLQ0"
}
401:
body:
schema: Empty
description: |
Invalid username or password

Expand Down
25 changes: 3 additions & 22 deletions raml/api/v1/users/user.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@
"properties": {
"userId": {
"description": "The identifier of the object",
"type": "string",
"x-kpmg-dotnet-attributes": [
"//[CustomAttribute]",
"//[CustomAttribute2(param=\"value\")]"
]
"type": "string"
},
"firstName": {
"description": "The users first name",
Expand All @@ -37,24 +33,9 @@
"format": "uri"
},
"role": {
"description": "The associated user role",
"description": "The associated user role. 0 = Standard user, 1 = Account admin, 2 = System admin",
"enum": [0,1,2],
"options": [
{
"value": 0,
"label": "Standard"
},
{
"value": 1,
"label": "AccountAdmin"
},
{
"value": 2,
"label": "SystemAdmin"
}
],
"type": "integer",
"x-kpmg-enumName": "UserRole"
"type": "integer"
}
}
}
8 changes: 5 additions & 3 deletions server/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
module.exports = function(app) {

let osprey = require('osprey'),
url = require('url'),
path = require('path'),
ospreyMock = require('osprey-mock-service'),
fs = require('fs'),
Expand All @@ -21,7 +22,8 @@ module.exports = function(app) {

return Promise.all(files.map(file => {
return raml.loadFile(path.join(dir, file)).then(raml => {
console.log(`registering ${file} on ${raml.baseUri}`);
let baseUri = url.parse(raml.baseUri).path;
console.log(`registering ${file} on ${baseUri}`);
try {
app.use(raml.baseUri, osprey.server(raml));
try {
Expand All @@ -33,8 +35,8 @@ module.exports = function(app) {
console.log(e);
}
finally {
app.use(raml.baseUri, ospreyMock(raml));
console.log(`registered ${file} on ${raml.baseUri}`);
app.use(baseUri, ospreyMock(raml));
console.log(`registered ${file} on ${baseUri}`);
}
}
catch (e) {
Expand Down
25 changes: 25 additions & 0 deletions swagger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Note on Swagger & RAML

KPMG has been an early adopter in the area of documenting API specs in standardized, machine-readable, and easily editable formats. This has significantly enhanced our development process and drastically reduced integration issues.

After using Swagger on a few projects, we found that the process of writing an maintaining Swagger specs was cumbersome, tedious, and error prone because the Swagger syntax is not tailored towards the software engineering best practices of composability and reuse.

A few years ago, we switched to recommending [RAML](http://raml.org) instead of Swagger as the API definition syntax on our projects.

# What we do with RAML

Leveraging RAML, our "accelerator" (code our teams can fork from when creating new projects for clients) provides several useful capabilities out of the box.

1. Documentation generation. Human-friendly API docs are generated as static HTML files based on RAML specs.
1. Client-side API wrapper. As part of the UI build process, a JavaScript module is generated with function, parameter, and attribute names based on the documentation. This makes it simpler for UI developers to code against the defined REST APIs.
1. Static mocks. One of our goals in project management is ensuring that nobody on the team is ever blocked from making forward progress. A common issue we encounter is backend development and UI development not being 100% aligned on schedules. Some things are simply harder to do on one side or the other. To enable different sides of the stack to progress independently, our Express server identifies calls to APIs that have not yet been implemented and, instead of returning a 404, returns a response based on the *example* defined for that endpoint in the RAML spec. UI development can move forward coding against services and the example data they return. As individual services are implemented for real, the UI that's been built against these mocks often works perfect without any additional integration work being needed.
1. Static schema validation. As part of the build process, RAML files are validated for compliance with the RAML spec. Additionally, examples for request/response bodies in the RAML files are validated against the corresponding JSON schemas in the RAML files. This prevents the common problem of schemas and examples not being kept up to date with each other.
1. Runtime validation proxy. When running the Express server in non-production mode, all request & response bodies will be validated against the schemas defined for their endpoints in RAML. This enables us to quickly find and identify issues where the client or server is not sending data in the expected structure.
1. Server-side codegen. While not demonstrated in this prototype, we also have acclerators for doing codegen of Java & C# service interfaces & DTO classes. These help expedite service implementation and further help ensure that the implementation of the interface matches the spec. Additionally, because these are strongly typed languages, many types of breaking changes to RAML specs will result in the Java/C# projects failing to build - a good thing because it forces the team to have a discussion & remediation rather than let the issue linger.

# For this prototype

Each of these capabilities could certainly be implemented with Swagger as well. However, in the interest of time, we have decided to sitck with using RAML as the "source of truth" for this project and to leverage our existing RAML-based versions of these capabilities. To meet the requirement of providing a Swagger spec, we have created a script that converts RAML to Swagger and validates the result against the Swagger schema.

The `api.v1.yaml` file in this folder is a Swagger spec generated from `./raml/api.v1.raml`
by running `npm run raml2swagger`.
Loading