Relax 1.5 Feedback

Hi Luis,

We’ve been working with Relax 1.5 lately and have been very impressed with the new features. We do have some feedback and wanted to share it with the group.

Defining Endpoints in Relax.cfc

Headers - I have added headers to the config of an endpoint, as well parameters. When I select an endpoint on the right nav, the parameters load/populate, however the API headers do not. I have both global headers and enpoint specific headers defined.

We have an endpoint called /users/:id which can have both a GET and PUT method called on the resource. GET can only have parameter - essentially the ID, whereas PUT can have many parameters. We have these endpoint configured like so:

/****** Retrieve User ******/
{
pattern="/users/:id",description=“Retrieves User Profile”,methods=“GET,DELETE”
,headers=[
{name=“authorization”, required=true, type=“string”}
]
,parameters=[
{name=“apikey”, required=“true”}
]
,placeholders=[{name=“id”,description=“User ID”,required=true}]
,response=
{
schemas=[{format=“json”, description=“Success response status code 200. The following will be returned when the format requested is JSON.”, body=fileRead("#dirPath#schemas/user.json")}]
}
},

/****** Update User ******/
{
pattern="/users/:id",description=“Updates User Profile”,methods=“PUT”
,headers=[
{name=“authorization”, required=true, type=“string”}
]
,parameters=[
{name=“apikey”, required=“true”},
{name=“firstname”, required=“true”,type=“string”},
{name=“lastname”, required=“true”,type=“string”},
{name=“email”, required=“true”,type=“string”},
{name=“password”, required=“false”,type=“string”},
{name=“mobile_telephone”, required=“true”,type=“string”},
{name=“home_telephone”, required=“false”,type=“string”},
{name=“work_telephone”, required=“false”,type=“string”},
{name=“blood_type”, required=“false”,type=“int”,description=“0=unspecified,1=A+,2=A-,3=B+,4=B-,5=AB+,6=AB-,7=O+,8=O-”},
{name=“gender”, required=“false”,type=“int”,description=“0=unspecified,1=male,2=female”},
{name=“eye_color”, required=“false”,type=“int”,description=“0=unspecified,1=Amber,2=Blue,3=Brown,4=Grey,5=Green,6=Hazel,7=Red,8=Other”},
{name=“hair_color”, required=“false”,type=“int”,description=“0=unspecified,1=Black,2=Brown,3=Blonde,4=Dirty Blonde,5=Grey,6=Silver,7=White,8=Red,9=Orange,10=Blue,11=Other,12=None”},
{name=“height”, required=“false”,type=“int”,description=“Metric in cm”},
{name=“Weight”, required=“false”,type=“int”,description=“Metric in kg”}
]
,placeholders=[{name=“id”,description=“User ID”,required=true}]
,response=
{
schemas=[{format=“json”, description=“Success response status code 200. The following will be returned when the format requested is JSON.”, body=fileRead("#dirPath#schemas/user.json")}]
}
},

Creating two entries allows us to execute the endpoint easily with the required params prepopulated however it causes a few issues.

In the resource definitions list in the sidenav and the documentation it lists users/:id twice. this gets confusing because at first glance you don’t know which one is the GET and which one is the PUT. The way to resolve this would be to consolidate all of the information into one entry like so:

/****** User Endpoint******/
{
pattern="/users/:id",description=“Retrieves and updates a User profile”,methods=“GET,PUT,DELETE”
,headers=[
{name=“authorization”, required=true, type=“string”}
]
,parameters=[
{name=“apikey”, required=“true”},
{name=“firstname”, required=“true”,type=“string”},
{name=“lastname”, required=“true”,type=“string”},
{name=“email”, required=“true”,type=“string”},
{name=“password”, required=“false”,type=“string”},
{name=“mobile_telephone”, required=“true”,type=“string”},
{name=“home_telephone”, required=“false”,type=“string”},
{name=“work_telephone”, required=“false”,type=“string”},
{name=“blood_type”, required=“false”,type=“int”,description=“0=unspecified,1=A+,2=A-,3=B+,4=B-,5=AB+,6=AB-,7=O+,8=O-”},
{name=“gender”, required=“false”,type=“int”,description=“0=unspecified,1=male,2=female”},
{name=“eye_color”, required=“false”,type=“int”,description=“0=unspecified,1=Amber,2=Blue,3=Brown,4=Grey,5=Green,6=Hazel,7=Red,8=Other”},
{name=“hair_color”, required=“false”,type=“int”,description=“0=unspecified,1=Black,2=Brown,3=Blonde,4=Dirty Blonde,5=Grey,6=Silver,7=White,8=Red,9=Orange,10=Blue,11=Other,12=None”},
{name=“height”, required=“false”,type=“int”,description=“Metric in cm”},
{name=“Weight”, required=“false”,type=“int”,description=“Metric in kg”}
]
,placeholders=[{name=“id”,description=“User ID”,required=true}]
,response=
{
schemas=[{format=“json”, description=“Success response status code 200. The following will be returned when the format requested is JSON.”, body=fileRead("#dirPath#schemas/user.json")}]
}
},

I think the above (1 entry) is the preferred approach for documentation purposes however 2 entries (above above) is the preferred entry for testing purposes.

One entry simplifies the documentation and resource listings however it assumes that the parameters are used across all 3 methods, which is not the case. Is there a way that we can specify that a group of HTTP methods belong to the single endpoint, yet have different parameter configurations for different methods? I see that you’ve done this in the description field - i.e The user firstname. Only used on PUT and POST operations. I guess this is fine however it seems to be mixing the object definition with the method functionality. Just wondering if there is a better way of achieving clean documentation without sacrificing the testing functionality of Relax.

I also have a couple of other suggestions that I want to run by the group.

  1. Add a default method to an endpoint config. This is more for testing purposes so that when you load a resource in RelaxURL it defaults to say POST instead of GET. Currently everything defaults to GET
  2. The same applies for the format. Would it make sense to add format restrictions to an endpoint? i.e format=“JSON,JSONT”. It is quite possible in an API that the return format is not consistent across all API endpoints. i.e endpoint 1 only returns XML whereas endpoint2 returns JSON, JSONT,XML. Furthermore, it would be great to have a global setting to populate the return format drop down, as well as be able to had endpoint specific settings for populating the default. Again some of these suggestions are to help make testing easier, whereas some are to get granular in the documentation
  3. How do you define defaults for parameters and headers - I tried adding default=“foobars” to the below globalHeader config and this blows a CF error. What is the proper syntax to define a default?

this.globalHeaders = [
// Sample global header, Available keys: name,description,required,default,type
{name=“authorization”,description=“The apikey needed for request authentication.”,required=true,default=“foobars”}
];

  1. I noticed this in the the route generation -

// Throw exception 406 when an invalid extension is detected?
setThrowOnInvalidExtension( false );

Is this part of CB 3.5?

This is all I have right now. I welcome any feedback or suggestions from the list on the above. Loving Relax Louis. We use it daily in our development and testing.

Thanks very much.

Nolan

Nolan, this is some great feedback and I want to introduce some new features in the next release version 1.7 that will help out on some of the issues. The version is almost complete (Now that you gave me more work) and it is in the development branch.

  1. We now have switched over to a programmatic DSL: http://wiki.coldbox.org/wiki/Projects:Relax.cfm#Programmatic_DSL
    You can use both approaches, but I like the programmatic since ColdFusion’s implicit structs/arrays are quirky.

This solves visibility issue and much more reusability.

As for having two resources with the same pattern but different configurations, that is tricky, since you are mostly splitting them because of documentation purposes, but in all reality you can have two patterns in ColdBox, because you can do split actions, but the split throws exceptions. So that’s tricky, do not know what to do yet on that.

Add a default method to an endpoint config.
How about this:
resource("/my/pattern")
.methods(“GET,PUT,POST”)
.defaultMethod(“GET”)

Then I can use that to select the right method from the RelaxURL

The same applies for the format.
How about this:
resource("/my/pattern")
.methods(“GET,PUT,POST”)
.defaultFormat(“json”)

  1. Fixed via programmatic DSL

  2. I noticed this in the the route generation -

// Throw exception 406 when an invalid extension is detected?
setThrowOnInvalidExtension( false );

Is this part of CB 3.5?

Yes, that’s part of 3.1 actually.

Luis F. Majano
CEO
Ortus Solutions, Corp
www.ortussolutions.com

ColdBox Platform: http://www.coldbox.org
Linked In: http://www.linkedin.com/pub/3/731/483
Blog: http://www.luismajano.com
IECFUG Manager: http://www.iecfug.com

Social: twitter.com/lmajano facebook.com/lmajano

HI Luis,

Thanks for your feedback. The programmatic DSL looks awesome. Wish we had known about that before writing it out the old way!! haha. that’s okay.

Your suggestions below look great for the default method and format.

Regarding #1 it would be ideal to keep it in one pattern. Is it possible to add an optional argument in the setParams() to specify which methods the params apply to?

i.e .setParams(params=[],method=“GET,PUT”);

Another feature that would be great is the ability for a schema to inherit another schema - sort of like the setTokens in the MailService. For instance I have a User Object schema and I also have a Group Object schema. Within my Group schema there is a Contacts element which contains an array of Users. Perhaps this could be acheived by using the same syntax as the mail service?

i.e

{
“id” : 132,
“name” : “Family”,
“notes” : “My I.C.E. contacts”
“default” : true,
“autodial_option” : 3,
“autodial_id” : 5,
“pin_enabled” : false,
“group_telephones” : [@Telephone@],
“contacts” : [@User@]
}

One last thing… what is the difference between samples and schemas?

Thanks!

Nolan

Ok, the defaultMethod(), defaultFormat() or implicit struct approach is now in development branch. Also, the RelaxURL now switches the methods and formats accordingly.

One last thing… what is the difference between samples and schemas?
Samples are just response samples you would like to put out other, or plain old docs.

Luis F. Majano
CEO
Ortus Solutions, Corp
www.ortussolutions.com

ColdBox Platform: http://www.coldbox.org
Linked In: http://www.linkedin.com/pub/3/731/483
Blog: http://www.luismajano.com
IECFUG Manager: http://www.iecfug.com

Social: twitter.com/lmajano facebook.com/lmajano