Monday, December 17, 2012

A swagger plugin for soapUI – with a little help from Groovy

Swagger is a simple vocabulary for providing metadata for an HTTP API (ie REST) for which it can be used to generate code, documentation, tests, etc. In this article I'll show you how to create a soapUI plugin in java and groovy that allows you to import JSON-based swagger definitions into soapUI for immediate ad-hoc, functional, performance and security testing of these described APIs.

Background

I first heard about swagger in one of Kin Lane's presentations at apidays.io earlier this year. Basically it’s a metadata vocabulary for describing HTTP/REST APIs; which operations there are, what path and parameters they have, which methods to use, dataypes (using JSON Schema!) etc. To be honest I was a bit surprised that someone had put quite an effort into something that seems to solve a problem that has already been solved (ie with WADL/XML Schema). But on the other hand, re-inventing the wheel often gives new insights and perspectives on its target domain and in this specific case the heavy use of JSON makes swagger easily accessible from most popular scripting platforms (note: swagger works swell with XML too). Also, the folks at wordnik have made a fantastic effort by providing an ecosystem (available at github) containing tools and libraries to make your adoption of swagger as easy as possible.

Since soapUI is specifically geared at reading API definitions (WSDL and WADL as for now) for generating API tests, adding support for swagger seemed like a natural step forward – and a good way to learn a little more about swagger itself and groovy as soapUI-plugin language.

The plugin project

I initially opted for creating the plugin entirely in groovy, primarily because JSON processing in Java is such a pain – unfortunately though I had to make some exceptions on my way forward, as you will see below. Using the gmaven plugin and related groovy project archetypes it was straight-forward to create an empty groovy project, now all I had to do was:
  • Create the action class to import a swagger definition
  • Add a META-INF/actions.xml file which soapUI needs to map the action into the correct menu
  • Make some adjustments to the pom.xml (dependencies and resource inclusions)
  • Write some basic junit tests to make sure things are working

The entire project in eclipse looks as follows:


(You can download the plugin source and jar file from the bottom of this article)

Let’s walk through these one by one.

The AddSwaggerAction class and soapui-actions.xml file

The AddSwaggerAction class is what is triggered by the associated menu item (the reason it is implemented in Java and not in Groovy will be evident a bit further down). It extends AbstractSoapUIAction and is parameterized with the type of soapUI object it will be associated with; WsdlProject (ie the project node in soapUI).

public class AddSwaggerAction extends AbstractSoapUIAction
{
 private XFormDialog dialog;

 public AddSwaggerAction()
 {
  super( "Add Swagger", "Imports a swagger definition" );
 }
The constructor calls the super-class with the actions name and description – the XFormDialog is from soapUIs internal dialog-framework (more to come on that).
The perform method is called when the corresponding action is triggered via the menu:

public void perform( WsdlProject project, Object param )
{
 // initialize form
 if( dialog == null )
 {
  dialog = ADialogBuilder.buildDialog( Form.class );
 }
 else
 {
  dialog.setValue( Form.SWAGGERURL, "" );
 }

 while( dialog.show() )
 {
  try
  {
   // get the specified URL
   String url = dialog.getValue( Form.SWAGGERURL ).trim();
   if( StringUtils.hasContent( url ) )
   {
    // expand any property-expansions
    String expUrl = PathUtils.expandPath( url, project );

    // if this is a file - convert it to a file URL
    if( new File( expUrl ).exists() )
     expUrl = new File( expUrl ).toURI().toURL().toString();

    // create the importer and import!
    SwaggerImporter importer = new SwaggerImporter( project );
    RestService[] services = importer.importSwagger( expUrl );

    // select the first imported REST Service (since a swagger definition can 
    // define multiple APIs
    if( services != null && services.length > 0 )
     UISupport.select( services[0] );

    break;
   }
  }
  catch( Exception ex )
  {
   UISupport.showErrorMessage( ex );
  }
 }
}

@AForm( name = "Add Swagger Definition", description = "Creates a Rest Service from the specified Swagger definition" )
public interface Form
{
 @AField( description = "Location or URL of swagger definition", type = AFieldType.FILE )
 public final static String SWAGGERURL = "Swagger Definition";
}

The key to understanding this method is the annotated Form interface at the end; it defines a form containing one field – the URL or filename of the swagger definition. As you can see the type of the field is set to AFieldType.FILE – which ultimately was the reason for this class being in Java - I couldn’t get the groovy compiler to work with this construct where the annotation attribute uses an enumeration; the class generated by the groovy pre-compiler failed to compile for some reason (after banging my head against that wall for 2hours I gave up and went back to Java…). If you have a solution to this please let me know :-)

The Form interface is used to generate the dialog class (by the ADialogBuilder) which is shown when the action is triggered. The specified URL is checked for a file-name – and the actual import is delegated to the SwaggerImporter class (see below).

The actions.xml file is equally simple:

<?xml version="1.0" encoding="UTF-8"?>
<tns:soapui-actions xmlns:tns="http://eviware.com/soapui/config">

<tns:action id="AddSwaggerAction" actionClass="com.smartbear.restplugin.AddSwaggerAction"/>

<tns:actionGroup id="EnabledWsdlProjectActions">
   <tns:actionMapping actionId="AddSwaggerAction" position="AFTER" positionRef="AddWadlAction"/>
</tns:actionGroup>

</tns:soapui-actions>

The AddSwaggerAction action is defined for our AddSwaggerAction class and mapped to the EnabledWsdlProjectActions group (which is used to build the right-click menu for an enabled Project) – it is positioned after the “Add WADL” action (see below for a screenshot).

The SwaggerImporter

This is the “powerhouse” of the plugin – it reads the specified swagger definition and creates the corresponding REST Service(s) in the specified soapUI Project. The main reason for using groovy was the JsonSlurper introduced in Groovy 1.8 which makes JSON parsing and processing so much easier. The “heart” of this class is the importApiDeclarations method:

public RestService importApiDeclarations( def url ) {

 // load the specified document
 def doc = loadJsonDoc( url )
    
 // create the RestService
 RestService restService = createRestService( doc.basePath, url )  

 // loop apis in document
 doc.apis.each {
  it

  // add a resource for this api
  RestResource resource = restService.addNewResource( it.path, it.path )
  resource.description = it.description

  // check for format template parameter - add at resource level so all methods will inherit
  if( it.path.contains( "{format}" )) {
   RestParameter p = resource.params.addProperty( "format" )
   p.setStyle( ParameterStyle.TEMPLATE )
   p.required = true
   p.options = {"json"}
   p.defaultValue = "json"
  }

  // loop all operations - import as methods
  it.operations.each {
   it

   RestMethod method = resource.addNewMethod( it.nickname )
   method.method = RequestMethod.valueOf( it.httpMethod )
   method.description = it.description

   // loop parameters and add accordingly
   it.parameters.each {
    it

    // ignore body parameters
    if( it.paramType != "body" ) {

     RestParameter p = method.params.addProperty( it.name )
     def paramType = it.paramType.toUpperCase()
     if( paramType == "PATH" )
        paramType = "TEMPLATE"

     p.style = ParameterStyle.valueOf( paramType )
     p.required = it.required
    }
   }

   // add a default request for the generated method
   method.addNewRequest( "Request 1" )
  }
 }

 return restService
}

As you can see it basically traverses the API definitions in the specified JSON document and creates the corresponding soapUI objects – fortunately the mapping between swagger and WADL (which is used to model the REST object model in soapUI) is straight-forward;


The Unit Tests

A simple GroovyTestCase with two tests to validate the base functionality of the SwaggerImporter:

void testImportResourceListing() {
 def project = new WsdlProject();

 SwaggerImporter importer = new SwaggerImporter( project )

 RestService [] result = importer.importSwagger( "http://petstore.swagger.wordnik.com/api/api-docs.json" )

 assertEquals( 2, result.length )

 RestService service = result[0]
 assertEquals( "http://petstore.swagger.wordnik.com", service.endpoints[0])
 assertEquals( "/api", service.basePath, )
 assertEquals( 6, service.resourceList.size())

 service = result[1]
 assertEquals( "http://petstore.swagger.wordnik.com", service.endpoints[0])
 assertEquals( "/api", service.basePath, )
 assertEquals( 3, service.resourceList.size())
}

void testImportApi() {
 def project = new WsdlProject();
 SwaggerImporter importer = new SwaggerImporter( project )

 RestService [] result = importer.importSwagger( "http://petstore.swagger.wordnik.com/api/api-docs.json/user" )

 assertEquals( 1, result.length )

 RestService service = result[0]
 assertEquals( "http://petstore.swagger.wordnik.com", service.endpoints[0])
 assertEquals( "/api", service.basePath, )
 assertEquals( 6, service.resourceList.size())
}
Both tests create an empty soapUI Project and import a swagger definition into it; the first test calls the sample swagger resource listing provided on the swagger website and validates that it imports two RestService objects, the second calls one of the API Declaration directly and does corresponding validations (since the resource listing is optional a user might provide this directly).

Building and deploying

Building with maven is obviously easy – the maven pom.xml adds a bunch of soapUI dependencies (this will be much easier when the soapUI codebase itself makes the move to maven 3 in the near future) and a resource declaration, apart from that it’s a standard groovy/maven pom. Running mvn clean install churns a short while and creates the desired plugin jar in the target folder:

The plugin jar contains the compiled classes and resources (open it in a zip-file utility to see for yourself). All we need to do now is place this jar in a “plugins” folder under our soapUI\bin folder and start soapUI – the soapUI log tab at the bottom of the main soapUI window shows that the plugin gets loaded ok:


Swagger Action!

Now that soapUI is running and the plugin is installed let’s start by creating an empty project and right clicking the project node:

Select the “Add Swagger” menu option to bring up the dialog we created in the Action class:


Start by specifying the sample api-docs.json definition that we used in the unit-test (at http://petstore.swagger.wordnik.com/api/api-docs.json) – press OK and soapUI will work a little while resulting in:


Awesomely cool – now you can invoke the APIs methods, create tests, etc.. (head over to soapui.org read more about how to get started with REST Testing in soapUI).  

Since this isn’t a “real” service you might be tempted to try one of those instead; Singly creates swagger definitions for its APIs, if you head over to their API Explorer at https://singly.com/explore you’ll eventually get something like:


Press the Raw link highlighted in the image above to get to the /profile swagger definition:


Now paste the URL of the definition into the “Add Swagger” dialog in your soapUI Project and you’ll get a corresponding REST Service created with a sample request that you can submit with the access_token given in the Singly API Explorer:


WADL for imported swaggers

Since soapUI internally models REST services in alignment with the WADL specification, you can actually get a WADL for the imported swagger definition, just double-click the REST Service node and go to the WADL Content tab:

This WADL can be exported, used for code generation, etc. What fun!

Final thoughts – next steps

As noted above there are some things missing in regard to the swagger import;
  • No support for models and datatypes (swagger uses JSON Schema to define these)
  • No support for swagger definitions in XML – only JSON for now
  • Very limited error-handling
  • The unit tests are dependent on the online swagger examples – they will fail if these change/move/etc.

So if you feel the urge to improve the importer or plugin – please do, and please contribute your changes back to me so I can put them in this distributable!

And as mentioned earlier – if you have suggestions on how to come around my gripes with the groovy compiler and enumerations for annotations attributes – let me know!

Resources and downloads

If you want to know more about swagger just head over to  http://swagger.wordnik.com/, the importer was created for the specifications at https://github.com/wordnik/swagger-core/wiki/Resource-Listing and https://github.com/wordnik/swagger-core/wiki/API-Declaration.

If you want to know more about how to create soapUI extensions/plugins head over to http://www.soapui.org/Developers-Corner/extending-soapui.html on soapUI.org or read a previous post on the subject at http://blog.smartbear.com/software-quality/bid/170458/Creating-your-own-TestSteps-in-soapUI.

If you don’t have soapUI installed (shame on you!) you can download it from www.soapui.org.

You can download the source for the entire above project here and the built jar here – just put this in the soapui\bin\plugins folder as shown above to start using it (it requires soapUI 4.5.X or later)!
The project is also on github: https://github.com/olensmar/soapui-swagger-plugin

If you have any questions or comments please don’t hesitate to ask below - thanks for your time!

/Ole



2 comments:

  1. Hello
    Thanks for this useful plugin . Would you be providing Updates to the swagger API ? I would like if the api extended to incorporate updates to the swagger so that when a new parameter is added etc , I can give the updated swagger and the resources are automatically updated.
    Thanks and Regards
    Smitha

    ReplyDelete
  2. Hi Smitha,

    Thanks for the reques! I'm currently working on an "Export Swagger" action to create a Swagger definition from any REST service defined in SoapUI. I'll tackle your request next - please add a corresponding issue at https://github.com/olensmar/soapui-swagger-plugin/issues

    thanks!

    /Ole

    ReplyDelete