diff --git a/core/api.cfc b/core/api.cfc index 76fc4b9..2ada13d 100644 --- a/core/api.cfc +++ b/core/api.cfc @@ -593,7 +593,7 @@ - + diff --git a/docs/3.6.0.md b/docs/3.6.0.md new file mode 100644 index 0000000..073e390 --- /dev/null +++ b/docs/3.6.0.md @@ -0,0 +1,1250 @@ +# Taffy + +The REST Web Service framework for ColdFusion and Lucee + +Download Taffy 3.6.0.zip + +- [Release Notes](https://github.com/atuttle/testing-release-automations/releases/tag/v3.6.0) +- [GitHub](https://github.com/atuttle/Taffy) +- Join us in the **#taffy** chat room on the [CFML Slack](https://cfml-slack.herokuapp.com/) + +## Quickstart: Your First API + +**Application.cfc:** + +```js +component extends="taffy.core.api" {} +``` + +**index.cfm:** + +```html + +``` + +**/resources/hello.cfc:** + +```js +component extends="taffy.core.resource" taffy_uri="/hello" { + + function get(){ + return rep( [ '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](https://twitter.com/cf_taffy/statuses/327415972581486592). + +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](https://github.com/atuttle/Taffy/wiki/Getting-Started) 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](http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=appFramework_04.html) for `/resources` so that Taffy can find your resources to initialize the routes. + +```js +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): + +```js +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()](#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](https://adamtuttle.codes/blog/2013/advanced-authentication-with-taffy-apis/). + +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. + +## Symlink Idiosyncrasies + +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: + +```xml + + CFMLServlet + /api/index.cfm/* + +``` + +This is because Tomcat doesn't support the use of _two_ wildcards in its mappings. You'll notice that installing ACF or Lucee 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) + +## Lucee Idiosyncrasies + +### Custom HTTP Status Messages + +Taffy allows for setting the HTTP status message using [.withStatus()](#withstatus), such as: + +```js +return representationOf({...}).withStatus(403, "Not Authorized"); +``` + +Lucee'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: + +```conf +org.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER=true +``` + +[Hat tip to Jean-Bernard van Zuylen](https://github.com/atuttle/Taffy/issues/121#issuecomment-23410624) + +### PUT requests & FORM scope + +Prior to versions 4.1.1.002 and 4.2 of Lucee, 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](https://github.com/atuttle/Taffy/issues/121#issuecomment-23410624) + +## IIS Idiosyncrasies + +IIS has the peculiar issue that you can't configure it (via the GUI) to allow your non-200 (e.g. Success) responses to use a custom body. If you respond with status 500, for example —in the interest of security— IIS uses a custom body, ignoring whatever response you may have provided. It is possible to configure IIS to allow your custom responses using the **Detailed Errors** setting in the **Error Pages** configuration, but this setting leaks sensitive data by default, so it is not recommended. + +Instead, you'll need to add a `web.config` file to your webroot (IIS7+), with the following contents: + +```xml + + + + + + +``` + +_Thanks to Brook Davies for providing the solution!_ + +## Override the request method with a header + +Not all HTTP clients will allow you to easily send PUT or DELETE requests (sometimes not at all). The standard method for circumventing this restriction, which Taffy supports, is by sending your request as a POST with the header `X-HTTP-METHOD-OVERRIDE` and setting its value to PUT/DELETE as needed. Taffy will detect this header and treat the request as if it were a PUT/DELETE request. + +## More Guides + +Some guides are too broad for this document. For your benefit, they are linked here: + +- [Migration Guide](https://github.com/atuttle/Taffy/wiki/Migration-Guide) - Notes on upgrading from 1.3.x to 2.x.x +- [Create a dead-simple CRUD API](https://github.com/atuttle/Taffy/wiki/So-you-want-to:-Create-a-dead-simple-CRUD-API) walks you through creating your first Taffy-powered API +- [Authentication and Security](https://github.com/atuttle/Taffy/wiki/Authentication-and-Security) +- [Bean Factories](https://github.com/atuttle/Taffy/wiki/Bean-Factories) +- [Common API HTTP Status Codes](https://github.com/atuttle/Taffy/wiki/Common-API-HTTP-Status-Codes) +- [Custom Token Regular Expressions](https://github.com/atuttle/Taffy/wiki/Custom-token-regular-expressions) +- [Exception Logging Adapters](https://github.com/atuttle/Taffy/wiki/Exception-Logging-Adapters) describes integration with BugLogHQ and Hoth, and options for emailing yourself all exceptions +- [Organizing your resources into subfolders](https://github.com/atuttle/Taffy/wiki/Organizing-your-resources-into-subfolders) explains the caveats of using subfolders within /resources +- [URL Rewrite Rule Examples](https://github.com/atuttle/Taffy/wiki/URL-Rewrite-Rule-Examples) +- [Using a Custom Serializer](https://github.com/atuttle/Taffy/wiki/Using-a-Custom-Serializer) +- [Rate Limiting your API](https://github.com/atuttle/Taffy/wiki/So-you-want-to:-Rate-Limit-access-to-your-API) +- [Require an API Key](https://github.com/atuttle/Taffy/wiki/So-you-want-to:-Require-an-API-Key) +- [Serialize data to a different data type](https://github.com/atuttle/Taffy/wiki/So-you-want-to:-Serialize-data-to-a-different-data-type) +- [Share Application variables with another application](https://github.com/atuttle/Taffy/wiki/So-you-want-to:-Share-application-variables-with-your-consumer-facing-application) +- [Support returning multiple data formats](https://github.com/atuttle/Taffy/wiki/So-you-want-to:-Support-returning-multiple-formats) +- [Allow file uploads to your API](https://github.com/atuttle/Taffy/wiki/So-you-want-to:-Upload-a-file-via-your-API) +- [Use an external bean factory to completely manage resources](https://github.com/atuttle/Taffy/wiki/So-you-want-to:-use-an-external-bean-factory-like-coldspring-to-completely-manage-resources) +- [Use an external bean factory to resolve dependencies of your resources](https://github.com/atuttle/Taffy/wiki/So-you-want-to:-Use-an-external-bean-factory-like-ColdSpring-to-resolve-dependencies-of-your-resources) +- [Use ColdSpring AOP Advice for your resources](https://github.com/atuttle/Taffy/wiki/So-you-want-to:-Use-ColdSpring-AOP-Advice-for-your-resources) +- [Use JSONUtil instead of native JSON serialization](https://github.com/atuttle/Taffy/wiki/So-you-want-to:-Use-JSONUtil-instead-of-Native-JSON-serialization) +- [Use the autowire functionality of Taffy's factory to manage resource dependencies](https://github.com/atuttle/Taffy/wiki/So-you-want-to:-Use-Taffy's-built-in-Dependency-Injection-to-resolve-dependencies-of-your-resources) +- [Write your components using CF9 script-component syntax](https://github.com/atuttle/Taffy/wiki/So-you-want-to:-Write-your-components-using-CF9--script-component-syntax) + +## 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:dashboard:hide + +If you want a resource not to be included in the dashboard, set `taffy:dashboard:hide` on the component: + +```xml + + +``` + +or + +```js +component taffy_uri="/secret-squirrel" taffy_dashboard_hide { +} +``` + +Note that while some more recent versions of Adobe ColdFusion do allow colons in component metadata property names (e.g. `taffy:uri`), some versions still do not support multiple colons, as in `taffy:dashboard:hide`. For this reason, we also support `taffy_dashboard_hide`. + +##### taffy:docs:hide + +This property works exactly like `taffy:dashboard:hide`, except it applies to the generated documentation instead of the dashboard. `taffy_docs_hide` is also supported. + +##### taffy:docs:name + +You can use this property to change the name of a resource as it appears in the generated documentation. By default Taffy will generate a name based on the folder/file structure, but sometimes these names are awkward and you want a human touch. + +##### taffy:uri + +The **taffy:uri** property applies to the `` 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: + +```xml + + +``` + +or + +```js +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: + +```xml + + + +``` + +or + +```js +function getUser( numeric userId ) taffy_verb="get" { +} +``` + +##### taffy:docs:hide + +You can prevent a resource from showing in the documentation by adding the `taffy:docs:hide` or `taffy_docs_hide` attribute to the component. + +```xml + + +``` + +or + +```js +component extends="taffy.core.resource" taffy_uri="/artist/{artistId}" taffy_docs_hide { +} +``` + +Similarly, you can hide methods and parameters by adding the attribute to the `` and `` tags. + +```xml + + + + +``` + +or + +```js +public function getUser( + required numeric userId, + string _hidden = "" taffy_docs_hide +) +taffy_docs_hide +{ +} +``` + +#### In Serializers + +Serializers are used to take the data provided by a resource and serialize it into a format usable by the web service consumer. A single Serializer 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 serializer. (The included default serializer supports only JSON.) Each `getAsX` method in your serializer 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: + +```xml + + +``` + +or + +```js +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: + +```xml + + + + + +``` + +or + +```js +function getAsJson() taffy_mime="application/json" taffy_default="true" {} + +function getAsXml() taffy_mime="application/xml" {} +``` + +### variables.framework settings + +Default values: + +```js +variables.framework = { + reloadKey = "reload", + reloadPassword = "true", + reloadOnEveryRequest = false, + + simulateKey = "sampleResponse", + simulatePassword = "true", + + endpointURLParam = "endpoint", + + serializer = "taffy.core.nativeJsonSerializer", + deserializer = "taffy.core.nativeJsonDeserializer", + + disableDashboard = false, + disabledDashboardRedirect = "", + dashboardHeaders = {}, + showDocsWhenDashboardDisabled = false, + docs = { + APIName = "", + APIVersion = "" + }, + + jsonp = false, + + csrfToken = { + cookieName = "", + headerName = "" + }, + + 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. + +#### simulateKey + +**Available in:** Taffy 3.3+
+**Type:** String
+**Default:** "sampleResponse"
+**Description:** Users can pass this attribute with a value of (simulatePassword) to indicate that they don't want to actually run the request, they want to return the simulated response. If no simulated response is available for the specified method (e.g. PUT) then a status 400 will be returned. If the password is incorrect, the request will be treated as not-simulated. + +To provide a simulated response, add an additional method to your Resource CFCs with a name using this pattern: `sampleResponse`. So for a sample GET request, the function name is `sampleGetResponse`. The method name is not case sensitive. Your new method should return the raw data (do not use `rep()`) of your sample response. This data will appear in the dashboard and generated docs, as well as being available at runtime via `simulateKey`. + +#### simulatePassword + +**Available in:** Taffy 3.3+
+**Type:** String
+**Default:** "true"
+**Description:** The password needed to allow a simulated response. See [simulateKey](#simulateKey) + +#### 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. + +#### serializer + +**Available in:** Taffy 3.0+
+**Type:** String
+**Default:** "taffy.core.nativeJsonSerializer"
+**Description:** The CFC dot-notation path, or bean name, of the Serializer that your API will use to serialize returned data for the client. + +#### deserializer + +**Available in:** Taffy 3.0+
+**Type:** String
+**Default:** "taffy.core.nativeJsonDeserializer"
+**Description:** The CFC dot-notation path, or bean name, of the [Deserializer](#custom-deserializers) that your API will use to interpret the request body sent by the client. + +#### 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. + +#### dashboardHeaders + +**Available in:** Taffy 3.0+
+**Type:** Structure
+**Default:** {}
+**Description:** A structure of custom headers to add to all requests made from the dashboard. These headers will be visible and editable in a textarea, and the value in the textarea will be added to the headers of each request before sending it to the server. + +#### showDocsWhenDashboardDisabled + +**Available in:** Taffy 3.0+
+**Type:** Boolean
+**Default:** False
+**Description:** Whether or not Taffy will display user friendly documentation when the dashboard is disabled. + +#### docs.APIName + +**Available in:** Taffy 3.0+
+**Type:** String
+**Default:** ""
+**Description:** The API name to display on the (default) documention. If not value is provided, then the default Taffy name will be displayed, along with the framework variable that needs to be set to override it. + +#### docs.APIVersion + +**Available in:** Taffy 3.0+
+**Type:** String
+**Default:** ""
+**Description:** The API version to display on the (default) documention. If not value is provided, then the default Taffy version will be displayed, along with the framework variable that needs to be set to override it. + +#### docsPath + +**Available in:** Taffy 3.1+
+**Type:** String
+**Default:** `"../dashboard/docs.cfm"`
+**Description:** Specify the template to include when loading the documentation at `?docs`. You can set this to another CFML template that inspects `application._taffy` and generates documentation however you like. For example, you could generate the JSON to power a [Swagger](http://swagger.io) interface. + +#### 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!) + +#### csrfToken + +**Available in:** Taffy 3.3+
+**Type:** Structure
+**Default:** See defaults [here](#variablesframework-settings)
+**Description:** Some Frameworks may use an anti-CSRF Cookie-to-header token technique. With this setting the dashboard will use the same technique to communicate with the secured api. Read more about it [here](https://en.wikipedia.org/wiki/Cross-site_request_forgery) + +#### csrfToken.cookieName + +**Available in:** Taffy 3.3+
+**Type:** String
+**Default:** ""
+**Description:** Specify the name of the CSRF cookie the dashboard should look after on each request (use together with csrfToken.headerName). + +#### csrfToken.headerName + +**Available in:** Taffy 3.3+
+**Type:** String
+**Default:** ""
+**Description:** Specify the name of the CSRF header variable the dashboard should send with each request (use together with csrfToken.cookieName) + +#### 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/String
+**Default:** False
+**Description:** Whether or not to allow cross-domain access to your API. Setting it to `true` adds the following headers: + +```xml + + + +``` + +The allowed verbs, of course, are the ones allowed by the requested resource, as well as OPTIONS. + +In addition, as of Taffy 3.1.0, you can set this setting to a string of allowable hosts, as a (comma, semicolon, or space) delimited list: + +```js +variables.framework.allowCrossDomain = + "http://example.com; http://foo.bar, http://google.com"; +``` + +#### 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 LUCEE USERS:** While it will not cause errors, the underlying Java code used in this feature was improperly implemented prior to Lucee 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 Lucee 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. + +#### exposeHeaders + +**Available in:** Taffy 3.3+
+**Type:** Boolean
+**Default:** False
+**Description:** When enabled, custom header names will be listed within the `Access-Control-Expose-Headers` response header. See [Mozilla](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers) for more details. + +#### 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](https://github.com/atuttle/Taffy/wiki/Exception-Logging-Adapters) for more details. + +#### exceptionLogAdapterConfig + +**Available in:** Taffy 1.2+
+**Type:** Any
+**Default:** See defaults [here](#variablesframework-settings)
+**Description:** Configuration that your chosen logging adapter requires. Can be any data type. See [Exception Logging Adapters](https://github.com/atuttle/Taffy/wiki/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. + +**NOTE FOR EXTERNAL BEAN FACTORY USERS (e.g. Coldspring, DI/1 etc)** If your external bean factory is initialized in onApplicationStart then you need to set the variables.framework.beanFactory after your bean factory has initialized, for example: + +```js +component extends="taffy.core.api" +{ + this.name = 'myapi'; + variables.framework = {}; + + function onApplicationStart(){ + application.beanFactory = createObject("coldspring.beans.DefaultXmlBeanFactory").init(); + application.beanFactory.loadBeansFromXML('/resources/coldspring.xml'); + variables.framework.beanFactory = application.beanFactory; + super.onApplicationStart(); + } +} +``` + +#### 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()](#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. + +#### getCachedResponse() + +**Parameters:** + +- **cacheKey (string)** - A unique string that represents the requested resource + +One of the [Caching Hooks](#caching-hooks) functions. The framework will call this function, only after previously calling [validCacheExists](#validcacheexists), to get the cached value for this cacheKey. Cache implementation details are up to you. + +#### getCacheKey() + +**Available in:** Taffy 3.3+
+**Parameters:** + +- **cfc (string)** - Refer to [onTaffyRequest](#ontaffyrequest) for details. +- **requestArguments (struct)** - Refer to [onTaffyRequest](#ontaffyrequest) for details. +- **matchedURI (string)** - Refer to [onTaffyRequest](#ontaffyrequest) for details. + +Override this method returning a `cacheKey` string based on the incoming arguments. It is one of the [Caching Hooks](#caching-hooks) functions, and informs the `cacheKey` argument of [validCacheExists](#validcacheexists) and [getCachedResponse](#getcachedresponse). + +#### 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: + +```js +variables.framework = { + + disableDashboard = false + + ,environments = { + production = { + disableDashboard = true + } + } +}; + +function getEnvironment(){ + if (...){ + return "production"; + } else { + return "development"; + } +} +``` + +#### getExternalBeanFactory() + +**Parameters:** _(none)_ + +Returns the external bean factory you have set into Taffy as `variables.framework.beanFactory`. Unlike `getBeanFactory()` this method always returns the external factory, even if using the `/resources` folder (which causes Taffy to use its internal Factory as well). + +#### noContent() + +**Available in:** Taffy 3.3.0+
+**Use it inside:** onTaffyRequest
+**Parameters:** _(none)_ + +Use this method inside onTaffyRequest when you want to _abort_ the request with nothing in the response body. Behaves like `noData()` except that it sets the status code to 204 and the Content-Type header to `text/plain`. You may chain calls to **withStatus** or **withHeaders**, just as if you were in a resource CFC. + +#### noData() + +**Available in:** Taffy 2.3+
+**Use it inside:** onTaffyRequest
+**Parameters:** _(none)_ + +Use this method inside onTaffyRequest when you want to _abort_ the request with nothing in the response body. You may chain a call to **withStatus** to set the response status code, just as if you were in a resource CFC. + +#### onTaffyRequest() + +**Parameters:** + +- **verb (string)** - The HTTP request verb provided by the client +- **cfc (string)** - The CFC name (minus ".cfc") that would handle the request. (Bean Name, if using an external bean factory.) +- **requestArguments (struct)** - A structure containing all of the arguments of the request, including tokens from the URI as well as any query string parameters. +- **mimeExt (string)** - The mime extension (e.g. "json" - NOT the full mime type, e.g. "application/json") +- **headers (struct)** - A structure containing each header from the request, as sent by the client. +- **methodMetadata (struct)** - A structure containing any non-taffy metadata set on the requested resource method. +- **matchedURI (string)** - The taffy:uri value, including unreplaced tokens, from the CFC that matches the request. (e.g. `/foo/{bar}`) + +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 call and return the result of either **[noData()](#nodata)** or **[representationOf()](#representationof)**. If you simply want to return with a status code of 403 (which indicates "Not Allowed") and no response body, you could do this: + +```js +return noData().withStatus(403); +``` + +Alternately, you could return some data to indicate that they owe you money or something: + +```js +return representationOf({ + 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: + +```js +function onTaffyRequest( + verb, + cfc, + requestArguments, + mimeExt, + headers, + methodMetadata, + matchedURI +) { + arguments.requestArguments.myData = "myvalue"; + return true; +} +``` + +In your resource: + +```js +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: + +```js +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: + +```js +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 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. + +#### onTaffyRequestEnd() + +**Available in:** Taffy 3.1+
+**Parameters:** + +- **verb (string)** - The HTTP request verb provided by the client +- **cfc (string)** - The CFC name (minus ".cfc") that handled the request. (Bean Name, if using an external bean factory.) +- **requestArguments (struct)** - A structure containing all of the arguments of the request, including tokens from the URI as well as any query string parameters. +- **mimeExt (string)** - The mime extension (e.g. "json" - NOT the full mime type, e.g. "application/json") +- **headers (struct)** - A structure containing each header from the request, as sent by the client. +- **methodMetadata (struct)** - A structure containing any non-taffy metadata set on the requested resource method. +- **matchedURI (string)** - The taffy:uri value, including unreplaced tokens, from the CFC that matches the request. (e.g. /foo/{bar}) +- **parsedResponse (string)** - The response, parsed in the format that the user has requested +- **originalResponse (any)** - The response in the original format, before being serialized for returning to the client +- **statusCode (any)** - The status code returned from the resource that handled the request (default 200) + +This method is optional, and allows you to add global functionality to occur after the call is complete. If you implement it, you can do things such as global logging for all calls. You do not need to return anything from this method. + +#### rep() + +**Available in:** Taffy 3.0+
+**Use it inside:** onTaffyRequest
+**Parameters:** + +- **data (any)** - The data to return to the consumer. + +This method is an alias to **representationOf** for those of us tired of typing out its whole name. :) + +#### representationOf() + +**Available in:** Taffy 2.3+
+**Use it inside:** onTaffyRequest
+**Parameters:** + +- **data (any)** - The data to return to the consumer. + +Use this method inside onTaffyRequest when you want to _abort_ the request with a response body. You may chain a call to **withStatus** to set the response status code, just as if you were in a resource CFC. Also see **representationOf** in the [Resources section](#resources) for more information. + +**Note:** While it is a best practice to use `representationOf()` or `rep()`, as of Taffy 3.3.0, if you omit it and return your raw data, it will be wrapped in a representation for you. You will not be able to set the response status or headers though, which is why this remains a best practice. However, if you intend to return status 200 with no additional headers, this is considered an officially supported approach. + +#### setCachedResponse() + +**Parameters:** + +- **cacheKey (string)** - A unique string that represents the requested resource +- **data (any)** - The data that your resource returned + +One of the [Caching Hooks](#caching-hooks) functions. The framework will call this to set or update the cached value for this cacheKey. It is not necessary to return anything. Cache implementation details are up to you. + +#### validCacheExists() + +**Parameters:** + +- **cacheKey (string)** - A unique string that represents the requested resource + +One of the [Caching Hooks](#caching-hooks) functions. The framework will call this to determine if the requested resource (GET requests only) is already in the cache **and verify that the cache is still valid**. If a value has not been cached for the provided cacheKey, or if the cache is expired, you should return **false**. Otherwise, return true. Cache implementation details are up to you. + +### Resources + +Resource CFCs extend `taffy.core.resource`. The following methods are available inside each of your Resource CFCs: + +#### addDebugData() + +**Use it inside:** responder methods inside your Resource CFCs (e.g. get, put, post, delete)
+**Parameters:** + +- **data (any)** - data to include in exception logs. + +This method saves data in a way that makes it available to [exception log adapters](https://github.com/atuttle/Taffy/wiki/Exception-Logging-Adapters), if and when exceptions occur. It's a handy way to help you understand what was going on when an exception happened. + +#### encode.string() + +**Use it inside:** responder methods inside your Resource CFCs (e.g. get, put, post, delete)
+**Parameters:** + +- **input (string)** - any string you want to make sure stays as a string in serialized output. + +This is a convenience method used to make sure that certain all-numeric inputs get serialized as a string in the output not converted to numeric output. Examples are postal codes or phone numbers. + +```js +return rep( + queryToArray(myQuery, function (row) { + row.phone = encode.string(row.phone); + return row; + }) +); +``` + +#### noContent() + +**Available in:** Taffy 3.3.0+
+**Use it inside:** responder methods inside your Resource CFCs (e.g. get, put, post, delete).
+**Parameters:** _(none)_ + +Behaves like `noData()` except that it sets the status code to 204 and the Content-Type header to `text/plain`. You may chain calls to **withStatus** or **withHeaders** if you wish. + +#### 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: + +```js +return noData().withStatus(404); +``` + +**note:** `noData()` defaults the response Content-Type to `application/json` which is invalid because the body is not valid JSON. Some clients may throw an exception as a result. Consider using `noContent()` instead. + +#### queryToArray() + +**Use it inside:** responder methods inside your Resource CFCs (e.g. get, put, post, delete).
+**Parameters:** + +- **data (query)** - The query object to be transformed +- **callback (function)** - _(optional)_ A callback function that each query row (as a structure) will be passed through before being saved to the array. + +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._ + +The callback function can be used to efficiently transform keys in the structure before it is added to the array without additional looping. For example, you can use it to format dates in a particular style. **Your callback must return a value.** + +```js +queryToArray(someQ, function (row) { + row.startDate = dateFormat(row.startDate, "yyyy-mm-dd"); + return row; +}); +``` + +When you want to return the resulting array as your API response, you must still wrap it in a [representationOf](#rep-1) call: + +```js +return rep(queryToArray(someQ)); +``` + +See the **api_callback** example for a fully functional example. + +#### queryToStruct() + +**Use it inside:** responder methods inside your Resource CFCs (e.g. get, put, post, delete).
+**Parameters:** + +- **data (query)** - The query object to be transformed +- **callback (function)** - _(optional)_ - A callback function that each data element will be passed through before being saved to the structure. + +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. + +The callback function is passed the column name and the value, and can be used to efficiently transform keys in the structure as they are read from the query without need for additional looping. For example, you can use it to format dates in a particular style. **Your callback must return a value.** + +```js +return queryToStruct(someQ, function (colName, val) { + if (colName == "startDate") { + return dateFormat(val, "yyyy-mm-dd"); + } + return val; +}); +``` + +#### rep() + +**Available in:** Taffy 3.0+
+**Use it inside:** responder methods inside your Resource CFCs (e.g. get, put, post, delete).
+**Parameters:** + +- **data (any)** - The data to return to the consumer. + +This method is an alias to **representationOf** for those of us tired of typing out its whole name. :) + +#### representationOf() + +**Use it inside:** responder methods inside your Resource CFCs (e.g. get, put, post, delete).
+**Parameters:** + +- **data (any)** - The data to return to the consumer. + +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 serializer, see [Using a custom Serializer](https://github.com/atuttle/Taffy/wiki/Using-a-Custom-Serializer). + +#### saveLog() + +**Use it inside:** anywhere inside a Resource CFC to log data using your [configured logging adapter](https://github.com/atuttle/Taffy/wiki/Exception-Logging-Adapters).
+**Parameters:** + +- **exception (struct)** - traditionally a CF exception object, but any struct may be passed. + +What you pass to this method is simply handed off to the logging adapter. You may use one of the included adapters (LogToEmail, LogToBuglogHQ, LogToLog, 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](https://github.com/atuttle/Taffy/wiki/Exception-Logging-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:** + +- **binaryData (any)** - the binary data to be streamed back to the consumer + +Use this method in place of `representationOf()` to return a stream of binary data. Useful for streaming things like dynamically generated PDFs. **Note: ** When streaming binary data as the response, you must set the mime type manually using [withMime()](#withmime). For example: + +```js +return streamBinary(local.pdf).withStatus(200).withMime("application/pdf"); +``` + +#### streamFile() + +**Use it inside:** responder methods inside your Resource CFCs (e.g. get, put, post, delete).
+**Parameters:** + +- **fileName (string)** - fully qualified file path (eg `c:\tmp\files.zip`) + +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. **Note: ** When streaming binary data as the response, you must set the mime type manually using [withMime()](#withmime). For example: + +```js +return streamFile("/foo.txt") + .andDelete(true) + .withStatus(200) + .withMime("application/pdf"); +``` + +#### streamImage() + +**Use it inside:** responder methods inside your Resource CFCs (e.g. get, put, post, delete).
+**Parameters:** + +- **binaryData (any)** - the binary, base64 encoded data of the image, to be streamed back to the consumer + +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:** + +- **headerStruct (struct)** - A structure whose keys are desired header names ("x-powered-by") and whose associated values are the values for the corresponding headers ("Taffy 1.1!"). + +This special method _**requires**_ the use of either **noData** or **representationOf**. It adds custom headers to the return. Additional use of **withStatus** optional. + +```js +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:** + +- **mime (string)** - mime type (eg. "application/pdf") to be returned with the streamed file data + +This special method _**requires**_ the use of either **streamFile** or **streamBinary**. It overrides the default mime type header for the return. + +```js +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:** + +- **statusCode (numeric)** - the [HTTP Status Code](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) to return to the consumer. +- **statusText (string)** - the Status Text to include with the HTTP Status Code. The "OK" part of "200 OK"; the "Not Found" part of "404 Not Found". + +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._ + +```js +return noData().withStatus(404, "Not Found"); +``` + +## Caching Hooks + +You may want to add custom caching to your API, over and above the caching options that are available with CFQuery. For example, you might be working with a NoSQL datastore, or you might want an out-of-process cache that is shared between app servers. In that case, Caching Hooks are for you. + +There are four methods in Application.cfc as of Taffy 3.3 for caching: [setCachedResponse](#setcachedresponse), [getCachedResponse](#getcachedresponse), [getCacheKey](#getcachekey), and [validCacheExists](#validcacheexists). Taffy will call each of them at the appropriate time to make use of your cache. The implementation details of caching are up to you, and only limited by your imagination and your ability. Some ideas: Redis, Memcached, EhCache, etc. + +**NOTE:** Because of the way certain underlying Java code was implemented in ColdFusion 8, Caching Hooks are only available on ColdFusion 9 and above. It is known to work on Lucee 4.2.0.006 and later (some earlier versions may work). + +## Custom Deserializers + +You may find that you want your API to accept more than just JSON and Form posts as request bodies. For example, some clients may want to send requests formatted as XML, or YAML. As of Taffy 3.0.0, you can provide a custom [Deserializer](#deserializer) CFC that will take the text of the request body as input and return native data that can be passed to your Resources. + +Let's explain Deserializers by looking at the default Deserializer that Taffy uses if you don't change the setting: `taffy.core.nativeJsonDeserializer`. + +```xml + + + + + + + + + + + + + + + + + + + + + +``` + +A Deserializer is a CFC that: + +- Extends `taffy.core.baseSerializer` (or another CFC that does) +- Implements 1 or more "getFrom{Something}" methods + - ... which each define a `taffy:mime` attribute that list the mime types that this method is capable of deserializing. + +The methods used for deserialization must be named with the prefix "getFrom", though what you use after the prefix does not matter. + +This particular Deserializer understands JSON, and will: + +- Throw an exception if the body is not well-formed JSON. +- Include a copy of the entire request body —after deserialization— as the `_body` key, if the body itself is not a structure (e.g. it's an array) + +The class `taffy.core.baseDeserializer` includes a method named `getFromForm` which handles Form posts, and includes a couple of helper methods: + +- throwError( string msg, numeric statusCode, optional struct headers ) — Returns a REST response with no body, the specified status code, and the message as the Status Text. +- addHeaders( struct headers ) — Adds the provided headers (whose names are the keys in the struct, and whose values are the struct values) to the response. + +To implement a custom Deserializer, set the [`variables.framework.deserializer`](#deserializer) setting to your cfc path or bean name. + +## Custom Serializers + +Serializers 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 Serializer that uses ColdFusion's **serializeJson** method to convert native data to JSON strings, and uses this Serializer by default. + +_Why would you want to create a custom Serializer, and how would you do it?_ + +### Why + +You want to create a custom Serializer if you're stymied by [the bugs in ColdFusion's serializeJson method](http://fusiongrokker.com/post/you-can-help-fix-coldfusion-json-serialization), or if you want to return something other than JSON: XML for example. Another reason to use a custom Serializer is when your api should be capable of returning more than one return format — e.g. both XML and JSON. + +### How + +A Serializer is a CFC that: + +- Extends `taffy.core.baseSerializer` +- Implements 1 or more "getAs{something}" methods + - ... that defines a `taffy:mime` attribute + +Let's take a look at **taffy.core.nativeJsonSerializer** — the default serializer that Taffy uses unless you configure something else. + +```xml + + + + + + + +``` + +- Extends `taffy.core.baseSerializer` (or another CFC that does) +- Implements 1 or more "getAs{something}" methods + - ... which each define a `taffy:mime` attribute that list the mime types to which this method is capable of serializing + +The base class is easy to explain: It makes everything about Serializers work without you having to do anything. It's the black magic. + +What's the `getAs{something}` method for? Well, it's for **get**ting the data out of your Serializer 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 Serializer 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 Serializer, you can assume that a resource has returned data and the returned value is available to the Serializer 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. + +To implement a custom Serializer, set the [`variables.framework.serializer`](#serializer) setting to your cfc path or bean name. + +### Multiple Formats + +Since a common use-case for custom Serializers is supporting multiple data formats, we'll next look at **CustomSerializer.cfc**, found in Taffy's **examples/api_twoFormats/resources** folder. + +```xml + + + + + + + + + + + + + + +``` + +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 Serializers. + +## Release Notes + +For every release after v3.3.0, release notes can be found in the [GitHub Releases](https://github.com/atuttle/taffy/releases). diff --git a/package.json b/package.json index 4455e9d..cacea1a 100644 --- a/package.json +++ b/package.json @@ -17,5 +17,5 @@ "less": "^4.1.1", "less-plugin-clean-css": "^1.5.1" }, - "version": "3.5.0" + "version": "3.6.0" }