Taffy

The REST Web Service Framework for ColdFusion and Railo

Download Taffy 2.2.3.zip

Quickstart: Your First API

Application.cfc:

component extends="taffy.core.api" {}

index.cfm:

<!-- this space left intentionally blank -->

/resources/hello.cfc:

component extends="taffy.core.resource" taffy_uri="/hello" {

    function get(){
        return representationOf(['hello','world']);
    }

}

Congratulations, you've just written your first API. Didn't think it could be so simple, did you? Indeed, you can fit an entire Taffy API into a single tweet.

Point your browser to the empty index.cfm file you created, and have a look at the dashboard. Click the "hello" row to expand it, and click the Send button to make a REST request. The results of your request appear below the request parameters.

The response includes a status code and status text, as well as other headers, and the response body (if applicable). Taffy also shows you the amount of time the request took.

Getting Started, with more details

Here's a Getting Started guide that goes into a little bit more detail, if the above example wasn't enough.

Installation Options

Global /taffy Mapping

Best practice would be to keep Taffy out of your web root. This is also (almost) the easiest install method. Instead of putting taffy in the web root, use a global mapping (set in the CF Administrator) pointing /taffy to wherever you have it saved.

/taffy in Web Root

Just put the /taffy folder at the root of your domain. Boom, done.

Sub-folder

If you aren't allowed to create a global mapping, or for some other reason want to, you can install Taffy as a sub-folder of your API. (NB: This would also allow you to use multiple versions of Taffy on the same CF Instance.)

Using sub-folders requires the use of Application-Specific Mappings (introduced in Adobe ColdFusion 8). Start by creating this directory structure:

/path-to-your-api
├── Application.cfc
├── index.cfm
├─┬ /taffy
│ ├── /bonus
│ ├── /core
│ └── /dashboard
└─┬ /resources
  ├── ...
  └── someResource.cfc

You can see that the taffy folder is a sibling to Application.cfc. This allows Application.cfc to use relative paths to extend taffy.core.api.

Next, if your Application.cfc and /resources/ folder aren't in the web-root (e.g. they're inside something like /api/) then you'll need to add an application-specific mapping for /resources so that Taffy can find your resources to initialize the routes.

this.mappings['/resources'] = expandPath('./resources');

You'll also need to add a mapping for /taffy so that the resources can extend taffy.core.resource (since the taffy folder isn't a child of the resources folder):

this.mappings['/taffy'] = expandPath('./taffy');

Authentication and Security

If your authentication and security requirements are simple, you may find the combination of SSL and HTTP Basic Auth to be sufficient. I would advise you not to use Basic Auth without SSL, as it is then easily sniffed. If you're utilizing Basic Auth, Taffy 1.3 added getBasicAuthCredentials().

If you're after something a bit more complex, but still short of OAuth, I've written an article covering Advanced Authentication with Taffy.

Implementing OAuth is something I would like to document, but it is a fairly large topic and I haven't had the time yet. Pull requests welcome.

Adobe ColdFusion (and possibly other engines) historically have been horrible about symlinks inside the web root; and will probably continue to be horrible about them in the future. It's best just to avoid them. They'll cause issues with more than just Taffy.

Tomcat, JBoss (and other app server) Idiosyncrasies

404 when your API is in a subdirectory

It's a known issue, and one we have no control over via code, that on vanilla Tomcat (as opposed to the modified version included in Adobe CF10+) you'll probably find that you get a 404 error if you try to move your api into a subdirectory. The 404 will complain that the URI is not found, such as:

/api/index.cfm/myResource

... Of course this is not found, because "index.cfm/myResource" isn't a file.

All hope is not lost, however!

The Fix: In your web.xml, you need to add an additional servlet mapping:

<servlet-mapping>
  <servlet-name>CFMLServlet</servlet-name>
  <url-pattern>/api/index.cfm/*</url-pattern>
</servlet-mapping>

This is because Tomcat doesn't support the use of two wildcards in its mappings. You'll notice that installing ACF or Railo in Tomcat you'll get a web.xml with mappings that have a url-pattern of index.cfm/*, but unfortunately because of this limitation, you can't change that to */index.cfm/*.

In the xml above you can see that I only have 1 wildcard, but to compensate I've specified the entire path to index.cfm, so that only 1 is needed. If you need to have multiple API's, you'll need a mapping for each index.cfm, and specify the full path of each. (Note that I used /api/index.cfm because it matched my example of a 404 for /api/index.cfm/myResource... yours should match the location of your index.cfm)

Railo Idiosyncrasies

Custom HTTP Status Messages

Taffy allows for setting the HTTP status message using .withStatus(), such as:

return representationOf({...}).withStatus(403, "Not Authorized");

Railo's default setup on Tomcat doesn't allow changing the Status Text header value; so you might use .withStatus(403, "Account Past Due") but Tomcat changes this back to 403 Not Authorized.

You can either work around this by passing status information in the response body, or alternately changing catalina.properties (Tomcat config) to include the line:

org.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER=true

Hat tip to Jean-Bernard van Zuylen

PUT requests & FORM scope

Prior to versions 4.1.1.002 and 4.2 of Railo, a PUT request would populate the FORM scope. This probably doesn't cause any issues with API's, but did cause a pair of unit tests to fail. (put_body_is_mime_content, and put_body_is_url_encoded_params) This is safe to ignore. Also, upgrade, man!

Hat tip to Jean-Bernard van Zuylen

Some guides are too broad for this document. For your benefit, they are linked here:

Configuration Reference

Configuration via Metadata

Metadata is used to apply configuration in a concise and elegant manor, where necessity and possibility intersect.

In Resources

Resources are CFCs that interpret a request and provide or update the requested data.

taffy:uri

The taffy:uri property applies to the <cfcomponent> tag or the component{} definition. It configures which URIs the CFC will respond to. There is no limit to the number of tokens that can be used in a single URI, as long as they are not consecutive and are delimited by at least one character (like a slash). Examples:

<cfcomponent taffy:uri="/artist/{artistId}">
</cfcomponent>

or

component taffy_uri="/artist/{artistId}" {
}

The following example is invalid, because the tokens are not separated by (at least) a slash:

taffy:uri="/artist/{artistId}{artistName}"
Tokens

Tokens in URIs define the parts of the URI that are dynamic. They are identified by curly-brackets: {tokenName} and are passed by name to all functions that handle the request. The {foo} token's value will be passed to the foo argument of the associated method.

URI Matching Order

As of Taffy 1.3, URIs are processed into a sorted array on API startup, and searched in order for every request, such that /artists/list will match before /artists/{id}. You should design your API URI's accordingly. Pay special attention to placement and possible values for URI tokens. If a possible value is the same as a static URI, consider changing URI formatting. For example, if we had an artist with id "list", then having the static URI /artists/list would prevent the /artists/{id} URI from ever matching when the intention is to get the individual artist record for artist with id "list."

taffy:verb

By convention, resources will automatically map the 4 primary HTTP REST verbs -- GET, PUT, POST, DELETE -- to CFC methods with the same name. For extended verbs -- OPTIONS, HEAD -- or if you want to map one of the primary verbs to a method with a different name, you can use the taffy:verb metadata property on the method to specify the verb it should respond to. Examples:

<cffunction name="getUser" taffy:verb="get">
    <cfargument name="userId" type="numeric" />
</cffunction>

or

function getUser( numeric userId ) taffy_verb="get" {
}

In Representation Classes

Representation classes are used to take the data provided by a resource and serialize it into a format usable by the web service consumer. A single Representation Class is capable of serializing native data objects (strings, numbers, queries, structures, arrays, etc) into 1 or more formats. Typical formats include JSON, XML, or YAML, but are not limited.

taffy:mime

By convention, the mime-types supported by your API are determined by the method names in your default representation class. (The included default representation class supports only JSON.) Each getAsX method in your representation class describes a new mime type, defined in two parts: The X portion of the method name determines the extension of the mime type -- which can be appended to the URI as if it were a file, as in: /artists/42.json which would use getAsJson. Taffy will also return a content-type header with the content type that you supply in the taffy:mime metadata property of the getAsX method. Typically, this is something like "application/json" or "application/xml". Examples:

<cffunction name="getAsJson" taffy:mime="application/json">
</cffunction>

or

function getAsJson() taffy_mime="application/json" {
}
taffy:default

When your API supports more than one data format (i.e. json and xml), you must set one as the default. You do this with the taffy:default property, which expects a boolean value of either TRUE or FALSE. The default is FALSE, and you never need to include taffy:default="false". You only need to include taffy:default="true" on one method -- the one that should be the default. Examples:

<cffunction name="getAsJson" taffy:mime="application/json" taffy:default="true">
</cffunction>

<cffunction name="getAsXml" taffy:mime="application/xml">
</cffunction>

or

function getAsJson() taffy_mime="application/json" taffy_default="true"
{
}

function getAsXml() taffy_mime="application/xml"
{
}

variables.framework settings

Default values:

variables.framework = {
    reloadKey = "reload",
    reloadPassword = "true",
    reloadOnEveryRequest = false,

    endpointURLParam = "endpoint",

    representationClass = "taffy.core.nativeJsonRepresentation",

    dashboardKey = "dashboard",
    disableDashboard = false,
    disabledDashboardRedirect = "",

    jsonp = false,

    unhandledPaths = "/flex2gateway",
    allowCrossDomain = false,
    globalHeaders = structNew(),
    debugKey = "debug",

    useEtags = false,

    returnExceptionsAsJson = true,
    exceptionLogAdapter = "taffy.bonus.LogToScreen",
    exceptionLogAdapterConfig = {},

    beanFactory = "",

    environments = {}
};

reloadKey

Available in: Taffy 1.2+
Type: String
Default: "reload"
Description: Name of the url parameter that requests the framework to be reloaded. Used in combination with the reload password (see: reloadPassword), the framework will re-initialize itself. During re-initialization, all configuration settings are re-applied and all cached objects are cleared and reloaded. If the value of the key does not match the reload password, a reload will not be performed. This allows you to set a secret password to restrict control of reloading your API to trusted parties.

reloadPassword

Available in: Taffy 1.2+
Type: String
Default: "true"
Description: Accepted value of the url parameter that requests the framework to be reloaded. Used in combination with the reload key (see: reloadKey), the framework will re-initialize itself. During re-initialization, all configuration settings are re-applied and all cached objects are cleared and reloaded. If the value of the key does not match the reload password, a reload will not be performed. This allows you to set a secret password to restrict control of reloading your API to trusted parties.

reloadOnEveryRequest

Available in: Taffy 1.2+
Type: Boolean
Default: False
Description: Flag that indicates whether Taffy should reload cached values and configuration on every request. Useful in development; set to FALSE in production.

endpointURLParam

Available in: Taffy 1.3+
Type: String
Default: "endpoint"
Description: The query-string parameter name that can optionally be used to specify URI. Until now, URI formatting has been required to be index.cfm/URI; this parameter allows you to use index.cfm?endpoint=/URI. This setting (endpointURLParam) allows you to change the default parameter name of "endpoint" to something custom.

representationClass

Available in: Taffy 1.2+
Type: String
Default: "taffy.core.nativeJsonRepresentation"
Description: The CFC dot-notation path, or bean name, of the representation class that your API will use to serialize returned data for the client.

dashboardKey

Available in: Taffy 1.2+
Deprecated in: Taffy 1.3+
Type: String
Default: "dashboard" Description: Name of the url parameter that displays the dashboard. The dashboard displays resources that your API is aware of, generates documentation about your API based on hint attributes, and contains a mock client to make testing your API easy.

disableDashboard

Available in: Taffy 1.2+
Type: Boolean
Default: False
Description: Whether or not Taffy will allow the dashboard to be displayed. If set to true, the dashboard key is simply ignored. You may wish to disable the dashboard in production, depending on whether or not you want customers/clients to be able to see it.

disabledDashboardRedirect

Available in: Taffy 1.3+
Type: String
Default: ""
Description: URL to which Taffy should redirect (302) the client/browser if the dashboard is disabled. If the dashboard is disabled and this value is blank, a simple 403 Forbidden response is sent instead.

jsonp

Available in: Taffy 2.0+
Type: Boolean/String
Default: false
Description: When false, JSONP is disabled. To enable it, change the value to a string, such as "callback". The value you specify will be the query parameter in which Taffy expects to find the JSONP callback name. Note: JSONP only works for GET requests (by design!)

unhandledPaths

Available in: Taffy 1.2+
Type: String (Comma-delimited list)
Default: "/flex2gateway"
Description: Set a list of paths (usually subfolders of the API) that you do not want Taffy to interfere with. Unless listed here, Taffy takes over the request lifecycle and does not execute the requested ColdFusion template.

allowCrossDomain

Available in: Taffy 1.2+
Type: Boolean
Default: False
Description: Whether or not to allow cross-domain access to your API. Turning this on adds the following headers:

<cfheader name="Access-Control-Allow-Origin" value="*" />
<cfheader name="Access-Control-Allow-Methods" value="#allowedVerbs#" />
<cfheader name="Access-Control-Allow-Headers" value="Content-Type" />

The allowed verbs, of course, are the ones allowed by the requested resource, as well as OPTIONS.

globalHeaders

Available in: Taffy 1.2+
Type: Structure
Default: {}
Description: A structure where each key is the name of a header you want to return, such as "X-MY-HEADER" and the structure value is the header value.

Global headers are static. You set them on application initialization and they do not change. If you need dynamic headers, you can add them to each response at runtime using withHeaders().

debugKey

Available in: Taffy 1.2+
Type: String
Default: "debug"
Description: Name of the url parameter that enables CF Debug Output.

useEtags

Available in: Taffy 1.3+
Type: Boolean
Default: False
Description: Enable the use of HTTP ETags for caching purposes. Taffy will automatically handle both sending the server ETag value and detecting client supplied ETags (via the If-None-Match header) for you; simply turn this setting on.

NOTE FOR RAILO USERS: While it will not cause errors, the underlying Java code used in this feature was improperly implemented prior to Railo 4.0.? and this could result in your result data being sent as if it were changed when it in fact has not. (I'm not sure which Railo point release will include the fix. The latest as of this writing is version 4.0.2, and does not include it.) Adobe ColdFusion is unaffected.

returnExceptionsAsJson

Available in: Taffy 1.2+
Type: Boolean
Default: true
Description: When an error occurs that is not otherwise handled, this option tells Taffy to attempt to format the error information as JSON and return that (regardless of the requested return format). As of Taffy 2.1 this also includes a structured stack trace with file names and line numbers.

exceptionLogAdapter

Available in: Taffy 1.2+
Type: String
Default: "taffy.bonus.LogToEmail"
Description: CFC dot-notation path to the exception logging adapter you want to use. Default adapter simply emails all exceptions. See Exception Logging Adapters for more details.

exceptionLogAdapterConfig

Available in: Taffy 1.2+
Type: Any
Default: See defaults here
Description: Configuration that your chosen logging adapter requires. Can be any data type. See Exception Logging Adapters for more details.

beanFactory

Available in: Taffy 1.2+
Type: Object Instance
Default: ""
Description: Already instantiated and cached (e.g. in Application scope) object instance of your external bean factory. Not required in order to use Taffy's built-in factory.

environments

Available in: Taffy 1.3+
Type: Structure
Default: {}
Description: Environment-specific overrides to any framework settings. Applied after general variables.framework settings, and after configureTaffy() has been called. See getEnvironment() for more details.

Index of API Methods

Application.cfc

getPath()

Parameters: (none)

This method is provided as an extension point. On Adobe ColdFusion ("ACF") 9, installed in standard entire-server mode, no change should be necessary. However, if ACF is installed on another JEE app server (i.e. Tomcat, Glassfish, etc), or on JRun but using an EAR/WAR setup, then you may need to override this method to make Taffy work on your server.

getBasicAuthCredentials()

Parameters: (none)

Added in Taffy 1.3. This method returns a structure with two keys: username, and password. When the client does not provide HTTP Basic Auth credentials, the username and password keys will be blank. When the client does provide them, the values will be available in these keys.

This method is only available inside your Application.cfc. If, for example, you need access to the username in a resource, you can use this method inside onTaffyRequest and add the username to the requestArguments structure.

getBeanFactory()

Parameters: (none)

Returns whatever bean factory you may have set into Taffy, if any. If using the /resources folder, it returns Taffy's built-in factory.

newRepresentation()

Use it inside: onTaffyRequest
Parameters:

Use this method inside onTaffyRequest when you want to abort the request with some specific message or data. Use the setData method on the returned representation class to put your data/message into it before returning it.

getEnvironment()

Parameters: (none)

Taffy calls this method during initialization to determine in which configured environment, if any, it is executing. Overriding it is optional. Use whatever methodology you like to determine the result (e.g. hostname, reading a file or registry setting, etc.), and simply return, as string, the name of the current environment.

The returned value will be used to load environment-specific configuration. For example, if you have the following code in your Application.cfc, then the dashboard will be disabled in production:

variables.framework = {

    disableDashboard = false

    ,environments = {
        production = {
            disableDashboard = true
        }
    }
};

function getEnvironment(){
    if (...){
        return "production";
    }else{
        return "development";
    }

}

onTaffyRequest()

Parameters:

This method is optional, and allows you to inspect and potentially abort an API request in a way that adheres to the HTTP specification and REST best practices. If you choose not to override it (by implementing it in your Application.cfc), it will always return true, allowing the request to continue. If you implement it, you can check for things like an API key, or whether or not the customer has paid for your service, and return something other than the data that they are requesting.

If you do not return TRUE, allowing the request to continue as normal, then Taffy expects you to return a representation (either the default class, or a custom one) that it should immediately return to the consumer, serialized to the appropriate format. If you simply want to return with a status code of 403 (which indicates "Not Allowed"), you could do this:

return newRepresentation().noData().withStatus(403);

Alternately, you could return some data to indicate that they owe you money or something:

return newRepresentation()
      .setData({error="Your account is past due. Please email accounts payable."})
      .withStatus(403);

The options here are limited only by your imagination.

You can add data to the requestArguments structure and this will be passed on to any resource that handles the request. Simply add a key to the structure:

function onTaffyRequest(verb, cfc, requestArguments, mimeExt, headers){
  arguments.requestArguments.myData = "myvalue";
  return true;
}

In your resource:

function get(myData){
  //arguments.myData => "myValue"
}

You can use the method metadata for anything you see fit; but the original use case was for role-based security. Consider the following resource method:

public function getData( id ) taffy_method="get" role="datareader" { ... }

Taffy doesn't do anything with the role metadata on this method other than expose it to you in onTaffyRequest. So let's use the user's API key to find out what roles they have, and verify that the method's required role is among them. This is a snippet from your Application.cfc:

function onTaffyRequest(verb, cfc, requestArgs, mime, head, methodMetadata){

    local.user = (...); //get user from api key...

    if (structKeyExists(methodMetadata, "role")){
        for (var availableRole in local.user.roles){
            if (availableRole == methodMetadata.role) { return true; }
        }
        return newRepresentation().noData().withStatus(403, "Not Authorized");
    }else{
        //no role required on the method, so allow anyone to use it
        return true;
    }
}

Here, if the user doesn't have the datareader role assigned, they'll get a 403 response.

Resources

Resource CFCs extend taffy.core.resource. The following methods are available inside each of your Resource CFCs:

noData()

Use it inside: responder methods inside your Resource CFCs (e.g. get, put, post, delete).
Parameters: (none)

This method allows you to specify that there is no data to be returned for the current request. Generally, you would use it in conjunction with the withStatus method to set a specific return status for the request. For example, if the requested resource doesn't exist, you could return a 404 error like so:

return noData().withStatus(404);

queryToArray()

Use it inside: responder methods inside your Resource CFCs (e.g. get, put, post, delete).
Parameters:

This method transforms a ColdFusion query object into an array of structures. It was added because ColdFusion's serializeJSON functionality uses an ...eccentric... format for queries. queryToArray returns the format most people expect: a vanilla array of structures with named keys. To be fair the ACF serialization format uses less data as long as there is more than 1 row in the query, but it doesn't matter that you do a better job if nobody understands your output. queryToArray also preserves query column name case, which serializeJSON does not.

queryToStruct()

Use it inside: responder methods inside your Resource CFCs (e.g. get, put, post, delete).
Parameters:

This method transforms a ColdFusion query object into a structure. If there is more than one row in the query, only the first row is used. This is a sort of shorthand syntax for queryToArray(qry)[1], when you know there will only be one record and you want the structure, but not wrapped in an array. Like queryToArray, queryToStruct preserves query column casing in the structure key names.

representationOf()

Use it inside: responder methods inside your Resource CFCs (e.g. get, put, post, delete).
Parameters:

Data can be of any type, including complex data types like queries, structures, and arrays, as long as the serializer knows how to serialize them. For more information on using a custom representation class, see Using a custom Representation Class.

saveLog()

Use it inside: anywhere inside a Resource CFC to log data using your configured logging adapter.
Parameters:

What you pass to this method is simply handed off to the logging adapter. You may use one of the included adapters (LogToEmail, LogToBuglogHQ, or LogToHoth), or a custom logging adapter. If you write a custom logging adapter, it should implement the taffy.bonus.ILogAdapter interface.

If you don't configure a logging adapter, the default is LogToEmail, but the default from and to email addresses are not useful. See Exception Log Adapters for more information on configuring logging adapters.

streamBinary()

Use it inside: responder methods inside your Resource CFCs (e.g. get, put, post, delete).
Parameters:

Use this method in place of representationOf() to return a stream of binary data. Useful for streaming things like dynamically generated PDFs.

streamFile()

Use it inside: responder methods inside your Resource CFCs (e.g. get, put, post, delete).
Parameters:

Use this method in place of representationOf() to stream a file from disk (or VFS). Optionally append .andDelete( true ) to delete the file once streaming is complete.

return streamFile( '/foo.txt' ).andDelete( true );

streamImage()

Use it inside: responder methods inside your Resource CFCs (e.g. get, put, post, delete).
Parameters:

Use this method in place of representationOf() to stream an image from disk (or VFS).

withHeaders()

Use it inside: responder methods inside your Resource CFCs (e.g. get, put, post, delete).
Parameters:

This special method requires the use of either noData or representationOf. It adds custom headers to the return. Additional use of withStatus optional.

return representationOf(myData).withHeaders({"X-POWERED-BY"="Taffy 2.0!"});

withMime()

Use it inside: responder methods inside your Resource CFCs (e.g. get, put, post, delete).
Parameters:

This special method requires the use of either streamFile or streamBinary. It overrides the default mime type header for the return.

return streamFile('kittens/cuteness.pdf').withMime('application/pdf');

withStatus()

Use it inside: responder methods inside your Resource CFCs (e.g. get, put, post, delete).
Parameters:

This special method requires the use of either noData or representationOf. It sets the HTTP Status Code of the return. Additional use of withHeaders optional.

If you do not specify a return status code, Taffy will always return status code 200 (OK) by default.

return noData().withStatus(404, "Not Found");

Custom Representation Classes

Representation Classes have the job of converting the native data that your resources return (queries, structures, arrays, and so on...) into —usually— a string that can be sent to the client and used there. It's also possible to send images and binary files like PDFs and ZIPs, but that's a topic that has its own guide. Taffy comes with a representation class that uses ColdFusion's serializeJson method to convert native data to JSON strings, and uses this class by default.

Why would you want to create a custom representation class, and how would you do it?

Why

You want to create a Custom Representation Class ("CRC") if you're stymied by the bugs in ColdFusion's serializeJson method, or if you want to return something other than JSON: XML for example. Another reason to use a CRC is when your api should be capable of returning more than one return format — e.g. both XML and JSON.

How

A Custom Representation Class is a CFC that:

Let's take a look at taffy.core.nativeJsonRepresentation — the default representation class that Taffy uses unless you configure something else.

<cfcomponent extends="taffy.core.baseRepresentation" output="false">

    <cffunction
        name="getAsJson"
        output="false"
        taffy:mime="application/json"
        taffy:default="true"
        hint="serializes data as JSON">
            <cfreturn serializeJSON(variables.data) />
    </cffunction>

</cfcomponent>

The base class is easy to explain: It makes everything about CRC's work without you having to do anything. It's the black magic.

What's the getAs{something} method for? Well, it's for getting the data out of your CRC in a format that the user is requesting. The client can specify its desired return format in one of two ways. An HTTP Accept header, or by appending .{format} to the URL.

The former looks like: Accept: application/json, and the latter looks like: http://example.com/api/v1/index.cfm/users.json. You may be familiar or comfortable with either, but the point is that they both mean the same thing. Taffy looks at the methods in your CRC at startup to determine what types your API will support. It needs both the format-extension (.json, via getAsJson) and the mime type (application/json, via taffy:mime) in order to function properly.

When writing your own CRC, you can assume that a resource has returned data and the returned value is available to the CRC as variables.data. This is why the default class returns serializeJson(variables.data). If your method is getAsXML then it needs to be able to convert the data into an XML string.

Multiple Formats

Since a common use-case for CRCs is supporting multiple data formats, we'll next look at CustomRepresentationClass.cfc, found in Taffy's examples/api_twoFormats/resources folder.

<cfcomponent extends="taffy.core.baseRepresentation">

    <cfset variables.jsonUtil = application.jsonUtil />
    <cfset variables.AnythingToXML = application.AnythingToXML />

    <cffunction name="getAsJSON" taffy:mime="application/json" taffy:default="true" output="false">
        <cfreturn variables.jsonUtil.serializeJson(variables.data) />
    </cffunction>

    <cffunction name="getAsXML" taffy:mime="application/xml" output="false">
        <cfreturn variables.AnythingToXML.ToXML(variables.data) />
    </cffunction>

</cfcomponent>

Here we see two getters: getAsJSON with taffy:mime="application/json" and getAsXML with taffy:mime="application/xml". Each is using a 3rd party library (available in the example folder) to do the serialization work. It's assuming that these libraries are available in Application scope (check Application.cfc for the implementation). This is generally regarded as a bad practice ("tight coupling"), and Taffy does not require that your code be structured this way. It's only included this way to keep the example as simple and focused as possible.

When a request is made that wants XML back, Taffy calls the getAsXML method to get the XML string. When a request is made for JSON data, Taffy calls getAsJSON.

And that —in a nutshell— is Custom Representation Classes.