234 attempt to fix js error Uncaught SyntaxError: Unexpected identifier 'hidden'
This commit is contained in:
+1390
-1360
File diff suppressed because it is too large
Load Diff
@@ -1,57 +1,47 @@
|
||||
<cfcomponent output="false" hint="a helper class to decode input data">
|
||||
component output="false" hint="a helper class to decode input data" {
|
||||
|
||||
<!--- the most basic of input handlers, available in all APIs --->
|
||||
<cffunction name="getFromForm" output="false" taffy:mime="application/x-www-form-urlencoded" hint="get data from form post">
|
||||
<cfargument name="body" hint="the textual request body" />
|
||||
<cfset var response = {} />
|
||||
<cfset var pairs = listToArray(arguments.body, "&") />
|
||||
<cfset var pair = "" />
|
||||
<cfset var kv = [] />
|
||||
<cfset var ix = 0 />
|
||||
<cfset var k = "" />
|
||||
<cfset var v = "" />
|
||||
public function getFromForm(body) output="false" taffy_mime="application/x-www-form-urlencoded" hint="get data from form post" {
|
||||
var response = {};
|
||||
var pairs = listToArray(arguments.body, "&");
|
||||
var pair = "";
|
||||
var kv = [];
|
||||
var ix = 0;
|
||||
var k = "";
|
||||
var v = "";
|
||||
|
||||
<cfif not find('=', arguments.body)>
|
||||
<cfset throwError(400, "You've indicated that you're sending form-encoded data but it doesn't appear to be valid. Aborting request.") />
|
||||
</cfif>
|
||||
if (!find('=', arguments.body)) {
|
||||
throwError(400, "You've indicated that you're sending form-encoded data but it doesn't appear to be valid. Aborting request.");
|
||||
}
|
||||
|
||||
<cfloop from="1" to="#arrayLen(pairs)#" index="ix">
|
||||
<cfset pair = pairs[ix] />
|
||||
<cfset kv = listToArray(pair, "=", true) />
|
||||
<cfset k = kv[1] />
|
||||
<cfset v = urlDecode( kv[2] ) />
|
||||
<cfif structKeyExists( response, k )>
|
||||
<cfset response[k] = listAppend(response[k], v)>
|
||||
<cfelse>
|
||||
<cfset response[k] = v>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
for (ix = 1; ix <= arrayLen(pairs); ix++) {
|
||||
pair = pairs[ix];
|
||||
kv = listToArray(pair, "=", true);
|
||||
k = kv[1];
|
||||
v = urlDecode(kv[2]);
|
||||
if (structKeyExists(response, k)) {
|
||||
response[k] = listAppend(response[k], v);
|
||||
} else {
|
||||
response[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
<cfreturn response />
|
||||
</cffunction>
|
||||
return response;
|
||||
}
|
||||
|
||||
<!--- ============================ --->
|
||||
<!--- Helpers --->
|
||||
<!--- ============================ --->
|
||||
private void function throwError(numeric statusCode=500, required string msg, struct headers={}) output="false" {
|
||||
cfcontent(reset="true");
|
||||
addHeaders(arguments.headers);
|
||||
cfheader(statuscode=arguments.statusCode, statustext=arguments.msg);
|
||||
abort;
|
||||
}
|
||||
|
||||
<cffunction name="throwError" access="private" output="false" returntype="void">
|
||||
<cfargument name="statusCode" type="numeric" default="500" />
|
||||
<cfargument name="msg" type="string" required="true" hint="message to return to api consumer" />
|
||||
<cfargument name="headers" type="struct" required="false" default="#structNew()#" />
|
||||
<cfcontent reset="true" />
|
||||
<cfset addHeaders(arguments.headers) />
|
||||
<cfheader statuscode="#arguments.statusCode#" statustext="#arguments.msg#" />
|
||||
<cfabort />
|
||||
</cffunction>
|
||||
private void function addHeaders(required struct headers) output="false" {
|
||||
var h = '';
|
||||
if (!structIsEmpty(arguments.headers)) {
|
||||
for (h in arguments.headers) {
|
||||
cfheader(name=h, value=arguments.headers[h]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<cffunction name="addHeaders" access="private" output="false" returntype="void">
|
||||
<cfargument name="headers" type="struct" required="true" />
|
||||
<cfset var h = '' />
|
||||
<cfif !structIsEmpty(arguments.headers)>
|
||||
<cfloop list="#structKeyList(arguments.headers)#" index="h">
|
||||
<cfheader name="#h#" value="#arguments.headers[h]#" />
|
||||
</cfloop>
|
||||
</cfif>
|
||||
</cffunction>
|
||||
|
||||
</cfcomponent>
|
||||
}
|
||||
|
||||
+173
-177
@@ -1,199 +1,195 @@
|
||||
<cfcomponent output="false" hint="a helper class to represent easily serializable data">
|
||||
component output="false" hint="a helper class to represent easily serializable data" {
|
||||
|
||||
<cfset variables.data = "" />
|
||||
<cfset variables.fileName = "" />
|
||||
<cfset variables.fileMime = "" />
|
||||
<cfset variables.statusCode = 200 />
|
||||
<cfset variables.statusText = "OK" />
|
||||
<cfset variables.miscHeaders = StructNew() />
|
||||
<cfset variables.deleteFile = false />
|
||||
<!--- 1= textual, 2= filename, 3= file data --->
|
||||
<cfset variables.type = 1 />
|
||||
<cfset variables.types = StructNew() />
|
||||
<cfset variables.types[1] = "textual" />
|
||||
<cfset variables.types[2] = "filename" />
|
||||
<cfset variables.types[3] = "filedata" />
|
||||
<cfset variables.types[4] = "imagedata" />
|
||||
<cfset variables.statusTexts = StructNew() />
|
||||
<cfset variables.statusTexts[100] = "Continue" />
|
||||
<cfset variables.statusTexts[101] = "Switching Protocols" />
|
||||
<cfset variables.statusTexts[102] = "Processing" />
|
||||
<cfset variables.statusTexts[200] = "OK" />
|
||||
<cfset variables.statusTexts[201] = "Created" />
|
||||
<cfset variables.statusTexts[202] = "Accepted" />
|
||||
<cfset variables.statusTexts[203] = "Non-authoritative Information" />
|
||||
<cfset variables.statusTexts[204] = "No Content" />
|
||||
<cfset variables.statusTexts[205] = "Reset Content" />
|
||||
<cfset variables.statusTexts[206] = "Partial Content" />
|
||||
<cfset variables.statusTexts[207] = "Multi-Status" />
|
||||
<cfset variables.statusTexts[208] = "Already Reported" />
|
||||
<cfset variables.statusTexts[226] = "IM Used" />
|
||||
<cfset variables.statusTexts[300] = "Multiple Choices" />
|
||||
<cfset variables.statusTexts[301] = "Moved Permanently" />
|
||||
<cfset variables.statusTexts[302] = "Found" />
|
||||
<cfset variables.statusTexts[303] = "See Other" />
|
||||
<cfset variables.statusTexts[304] = "Not Modified" />
|
||||
<cfset variables.statusTexts[305] = "Use Proxy" />
|
||||
<cfset variables.statusTexts[307] = "Temporary Redirect" />
|
||||
<cfset variables.statusTexts[308] = "Permanent Redirect" />
|
||||
<cfset variables.statusTexts[400] = "Bad Request" />
|
||||
<cfset variables.statusTexts[401] = "Unauthorized" />
|
||||
<cfset variables.statusTexts[402] = "Payment Required" />
|
||||
<cfset variables.statusTexts[403] = "Forbidden" />
|
||||
<cfset variables.statusTexts[404] = "Not Found" />
|
||||
<cfset variables.statusTexts[405] = "Method Not Allowed" />
|
||||
<cfset variables.statusTexts[406] = "Not Acceptable" />
|
||||
<cfset variables.statusTexts[407] = "Proxy Authentication Required" />
|
||||
<cfset variables.statusTexts[408] = "Request Timeout" />
|
||||
<cfset variables.statusTexts[409] = "Conflict" />
|
||||
<cfset variables.statusTexts[410] = "Gone" />
|
||||
<cfset variables.statusTexts[411] = "Length Required" />
|
||||
<cfset variables.statusTexts[412] = "Precondition Failed" />
|
||||
<cfset variables.statusTexts[413] = "Payload Too Large" />
|
||||
<cfset variables.statusTexts[414] = "Request-URI Too Long" />
|
||||
<cfset variables.statusTexts[415] = "Unsupported Media Type" />
|
||||
<cfset variables.statusTexts[416] = "Requested Range Not Satisfiable" />
|
||||
<cfset variables.statusTexts[417] = "Expectation Failed" />
|
||||
<cfset variables.statusTexts[418] = "I'm a teapot" />
|
||||
<cfset variables.statusTexts[421] = "Misdirected Request" />
|
||||
<cfset variables.statusTexts[422] = "Unprocessable Entity" />
|
||||
<cfset variables.statusTexts[423] = "Locked" />
|
||||
<cfset variables.statusTexts[424] = "Failed Dependency" />
|
||||
<cfset variables.statusTexts[426] = "Upgrade Required" />
|
||||
<cfset variables.statusTexts[428] = "Precondition Required" />
|
||||
<cfset variables.statusTexts[429] = "Too Many Requests" />
|
||||
<cfset variables.statusTexts[431] = "Request Header Fields Too Large" />
|
||||
<cfset variables.statusTexts[444] = "Connection Closed Without Response" />
|
||||
<cfset variables.statusTexts[451] = "Unavailable For Legal Reasons" />
|
||||
<cfset variables.statusTexts[499] = "Client Closed Request" />
|
||||
<cfset variables.statusTexts[500] = "Internal Server Error" />
|
||||
<cfset variables.statusTexts[501] = "Not Implemented" />
|
||||
<cfset variables.statusTexts[502] = "Bad Gateway" />
|
||||
<cfset variables.statusTexts[503] = "Service Unavailable" />
|
||||
<cfset variables.statusTexts[504] = "Gateway Timeout" />
|
||||
<cfset variables.statusTexts[505] = "HTTP Version Not Supported" />
|
||||
<cfset variables.statusTexts[506] = "Variant Also Negotiates" />
|
||||
<cfset variables.statusTexts[507] = "Insufficient Storage" />
|
||||
<cfset variables.statusTexts[508] = "Loop Detected" />
|
||||
<cfset variables.statusTexts[510] = "Not Extended" />
|
||||
<cfset variables.statusTexts[511] = "Network Authentication Required" />
|
||||
<cfset variables.statusTexts[599] = "Network Connect Timeout Error" />
|
||||
variables.data = "";
|
||||
variables.fileName = "";
|
||||
variables.fileMime = "";
|
||||
variables.statusCode = 200;
|
||||
variables.statusText = "OK";
|
||||
variables.miscHeaders = {};
|
||||
variables.deleteFile = false;
|
||||
variables.noDataSends204NoContent = false;
|
||||
// 1= textual, 2= filename, 3= file data
|
||||
variables.type = 1;
|
||||
variables.types = {};
|
||||
variables.types[1] = "textual";
|
||||
variables.types[2] = "filename";
|
||||
variables.types[3] = "filedata";
|
||||
variables.types[4] = "imagedata";
|
||||
variables.statusTexts = {};
|
||||
variables.statusTexts[100] = "Continue";
|
||||
variables.statusTexts[101] = "Switching Protocols";
|
||||
variables.statusTexts[102] = "Processing";
|
||||
variables.statusTexts[200] = "OK";
|
||||
variables.statusTexts[201] = "Created";
|
||||
variables.statusTexts[202] = "Accepted";
|
||||
variables.statusTexts[203] = "Non-authoritative Information";
|
||||
variables.statusTexts[204] = "No Content";
|
||||
variables.statusTexts[205] = "Reset Content";
|
||||
variables.statusTexts[206] = "Partial Content";
|
||||
variables.statusTexts[207] = "Multi-Status";
|
||||
variables.statusTexts[208] = "Already Reported";
|
||||
variables.statusTexts[226] = "IM Used";
|
||||
variables.statusTexts[300] = "Multiple Choices";
|
||||
variables.statusTexts[301] = "Moved Permanently";
|
||||
variables.statusTexts[302] = "Found";
|
||||
variables.statusTexts[303] = "See Other";
|
||||
variables.statusTexts[304] = "Not Modified";
|
||||
variables.statusTexts[305] = "Use Proxy";
|
||||
variables.statusTexts[307] = "Temporary Redirect";
|
||||
variables.statusTexts[308] = "Permanent Redirect";
|
||||
variables.statusTexts[400] = "Bad Request";
|
||||
variables.statusTexts[401] = "Unauthorized";
|
||||
variables.statusTexts[402] = "Payment Required";
|
||||
variables.statusTexts[403] = "Forbidden";
|
||||
variables.statusTexts[404] = "Not Found";
|
||||
variables.statusTexts[405] = "Method Not Allowed";
|
||||
variables.statusTexts[406] = "Not Acceptable";
|
||||
variables.statusTexts[407] = "Proxy Authentication Required";
|
||||
variables.statusTexts[408] = "Request Timeout";
|
||||
variables.statusTexts[409] = "Conflict";
|
||||
variables.statusTexts[410] = "Gone";
|
||||
variables.statusTexts[411] = "Length Required";
|
||||
variables.statusTexts[412] = "Precondition Failed";
|
||||
variables.statusTexts[413] = "Payload Too Large";
|
||||
variables.statusTexts[414] = "Request-URI Too Long";
|
||||
variables.statusTexts[415] = "Unsupported Media Type";
|
||||
variables.statusTexts[416] = "Requested Range Not Satisfiable";
|
||||
variables.statusTexts[417] = "Expectation Failed";
|
||||
variables.statusTexts[418] = "I'm a teapot";
|
||||
variables.statusTexts[421] = "Misdirected Request";
|
||||
variables.statusTexts[422] = "Unprocessable Entity";
|
||||
variables.statusTexts[423] = "Locked";
|
||||
variables.statusTexts[424] = "Failed Dependency";
|
||||
variables.statusTexts[426] = "Upgrade Required";
|
||||
variables.statusTexts[428] = "Precondition Required";
|
||||
variables.statusTexts[429] = "Too Many Requests";
|
||||
variables.statusTexts[431] = "Request Header Fields Too Large";
|
||||
variables.statusTexts[444] = "Connection Closed Without Response";
|
||||
variables.statusTexts[451] = "Unavailable For Legal Reasons";
|
||||
variables.statusTexts[499] = "Client Closed Request";
|
||||
variables.statusTexts[500] = "Internal Server Error";
|
||||
variables.statusTexts[501] = "Not Implemented";
|
||||
variables.statusTexts[502] = "Bad Gateway";
|
||||
variables.statusTexts[503] = "Service Unavailable";
|
||||
variables.statusTexts[504] = "Gateway Timeout";
|
||||
variables.statusTexts[505] = "HTTP Version Not Supported";
|
||||
variables.statusTexts[506] = "Variant Also Negotiates";
|
||||
variables.statusTexts[507] = "Insufficient Storage";
|
||||
variables.statusTexts[508] = "Loop Detected";
|
||||
variables.statusTexts[510] = "Not Extended";
|
||||
variables.statusTexts[511] = "Network Authentication Required";
|
||||
variables.statusTexts[599] = "Network Connect Timeout Error";
|
||||
|
||||
<cffunction name="getType" acces="public" output="false">
|
||||
<cfreturn variables.types[variables.type] />
|
||||
</cffunction>
|
||||
public function getType() output="false" {
|
||||
return variables.types[variables.type];
|
||||
}
|
||||
|
||||
<cffunction name="setData" access="public" output="false" hint="setter for the data to be returned">
|
||||
<cfargument name="data" required="true" hint="the simple or complex data that you want to return to the api consumer" />
|
||||
<cfset variables.type = 1 />
|
||||
<cfset variables.data = arguments.data />
|
||||
<cfreturn this />
|
||||
</cffunction>
|
||||
public function setData(required data) output="false" hint="setter for the data to be returned" {
|
||||
variables.type = 1;
|
||||
variables.data = arguments.data;
|
||||
return this;
|
||||
}
|
||||
|
||||
<cffunction name="getData" access="public" output="false" hint="mostly for testability, returns the native data embedded in the representation instance">
|
||||
<cfreturn variables.data />
|
||||
</cffunction>
|
||||
public function getData() output="false" hint="mostly for testability, returns the native data embedded in the representation instance" {
|
||||
return variables.data;
|
||||
}
|
||||
|
||||
<cffunction name="noData" access="public" output="false" hint="returns empty representation instance">
|
||||
<cfif application._taffy.settings.noDataSends204NoContent>
|
||||
<cfreturn this.noContent() />
|
||||
<cfelse>
|
||||
<cfreturn this />
|
||||
</cfif>
|
||||
</cffunction>
|
||||
public function setNoDataSends204NoContent(required boolean value) output="false" {
|
||||
variables.noDataSends204NoContent = arguments.value;
|
||||
return this;
|
||||
}
|
||||
|
||||
<cffunction name="noContent" access="public" output="false" hint="returns empty representation instance">
|
||||
<!--- According to issue #365 https://github.com/atuttle/Taffy/issues/365
|
||||
noContent() returns with HTTP status code 204 and Content-Type as text/plain (omitting this header is difficult and maybe not recommanded)
|
||||
noData() is kept 'as is' for backward compatibility with existing implementations
|
||||
--->
|
||||
<cfreturn this.withStatus(204).withHeaders({"Content-Type"="text/plain"}) />
|
||||
</cffunction>
|
||||
public function noData() output="false" hint="returns empty representation instance" {
|
||||
if (variables.noDataSends204NoContent) {
|
||||
return this.noContent();
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
<cffunction name="setFileName" access="public" output="false" hint="Pass in a file-name (fully qualified, e.g. c:\temp\img.jpg) to have Taffy stream this file back to the client">
|
||||
<cfargument name="file" type="string" required="true" />
|
||||
<cfset variables.type = 2 />
|
||||
<cfset variables.fileName = arguments.file />
|
||||
<cfreturn this />
|
||||
</cffunction>
|
||||
public function noContent() output="false" hint="returns empty representation instance" {
|
||||
// According to issue #365 https://github.com/atuttle/Taffy/issues/365
|
||||
// noContent() returns with HTTP status code 204 and Content-Type as text/plain (omitting this header is difficult and maybe not recommanded)
|
||||
// noData() is kept 'as is' for backward compatibility with existing implementations
|
||||
return this.withStatus(204).withHeaders({"Content-Type"="text/plain"});
|
||||
}
|
||||
|
||||
<cffunction name="getFileName" access="public" output="false">
|
||||
<cfreturn variables.fileName />
|
||||
</cffunction>
|
||||
public function setFileName(required string file) output="false" hint="Pass in a file-name (fully qualified, e.g. c:\temp\img.jpg) to have Taffy stream this file back to the client" {
|
||||
variables.type = 2;
|
||||
variables.fileName = arguments.file;
|
||||
return this;
|
||||
}
|
||||
|
||||
<cffunction name="setFileData" access="public" output="false" hint="Pass in file data (eg a generated PDF object) - NOT a Filename! - to have Taffy stream the content back to the client">
|
||||
<cfargument name="data" required="true" />
|
||||
<cfset variables.type = 3 />
|
||||
<cfset variables.fileData = arguments.data />
|
||||
<cfreturn this />
|
||||
</cffunction>
|
||||
public function getFileName() output="false" {
|
||||
return variables.fileName;
|
||||
}
|
||||
|
||||
<cffunction name="getFileData" access="public" output="false">
|
||||
<cfreturn variables.fileData />
|
||||
</cffunction>
|
||||
public function setFileData(required data) output="false" hint="Pass in file data (eg a generated PDF object) - NOT a Filename! - to have Taffy stream the content back to the client" {
|
||||
variables.type = 3;
|
||||
variables.fileData = arguments.data;
|
||||
return this;
|
||||
}
|
||||
|
||||
<cffunction name="setImageData" access="public" output="false" hint="Pass in image data (eg a generated image object) - NOT a Filename! - to have Taffy stream the content back to the client">
|
||||
<cfargument name="data" required="true" />
|
||||
<cfset variables.type = 4 />
|
||||
<cfif not isBinary(arguments.data)>
|
||||
<cfset arguments.data = toBinary(toBase64(arguments.data)) />
|
||||
</cfif>
|
||||
<cfset variables.fileData = arguments.data />
|
||||
<cfreturn this />
|
||||
</cffunction>
|
||||
public function getFileData() output="false" {
|
||||
return variables.fileData;
|
||||
}
|
||||
|
||||
<cffunction name="getImageData" access="public" output="false">
|
||||
<cfreturn getFileData() />
|
||||
</cffunction>
|
||||
public function setImageData(required data) output="false" hint="Pass in image data (eg a generated image object) - NOT a Filename! - to have Taffy stream the content back to the client" {
|
||||
variables.type = 4;
|
||||
if (!isBinary(arguments.data)) {
|
||||
arguments.data = toBinary(toBase64(arguments.data));
|
||||
}
|
||||
variables.fileData = arguments.data;
|
||||
return this;
|
||||
}
|
||||
|
||||
<cffunction name="withMime" access="public" output="false" hint="Use this method in conjunction with streamFile and streamBinary in your resources to set the mime type of the file being returned. Ex: return streamFile('kittens/cuteness.jpg').withMime('image/jpeg');">
|
||||
<cfargument name="mime" type="string" required="true" />
|
||||
<cfset variables.fileMime = arguments.mime />
|
||||
<cfreturn this />
|
||||
</cffunction>
|
||||
public function getImageData() output="false" {
|
||||
return getFileData();
|
||||
}
|
||||
|
||||
<cffunction name="getFileMime" access="public" output="false">
|
||||
<cfreturn variables.fileMime />
|
||||
</cffunction>
|
||||
public function withMime(required string mime) output="false" hint="Use this method in conjunction with streamFile and streamBinary in your resources to set the mime type of the file being returned. Ex: return streamFile('kittens/cuteness.jpg').withMime('image/jpeg');" {
|
||||
variables.fileMime = arguments.mime;
|
||||
return this;
|
||||
}
|
||||
|
||||
<cffunction name="withStatus" access="public" output="false" hint="used to set the http response code for the response">
|
||||
<cfargument name="statusCode" type="numeric" required="true" hint="eg 200" />
|
||||
<cfargument name="statusText" type="string" required="false" default="" />
|
||||
<cfset variables.statusCode = arguments.statusCode />
|
||||
<cfif len(arguments.statusText)>
|
||||
<cfset variables.statusText = arguments.statusText />
|
||||
<cfelseif StructKeyExists(variables.statusTexts, arguments.statusCode)>
|
||||
<cfset variables.statusText = variables.statusTexts[arguments.statusCode] />
|
||||
</cfif>
|
||||
<cfreturn this />
|
||||
</cffunction>
|
||||
public function getFileMime() output="false" {
|
||||
return variables.fileMime;
|
||||
}
|
||||
|
||||
<cffunction name="getStatus" access="public" output="false" returnType="numeric">
|
||||
<cfreturn variables.statusCode />
|
||||
</cffunction>
|
||||
public function withStatus(required numeric statusCode, string statusText="") output="false" hint="used to set the http response code for the response" {
|
||||
variables.statusCode = arguments.statusCode;
|
||||
if (len(arguments.statusText)) {
|
||||
variables.statusText = arguments.statusText;
|
||||
} else if (structKeyExists(variables.statusTexts, arguments.statusCode)) {
|
||||
variables.statusText = variables.statusTexts[arguments.statusCode];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
<cffunction name="getStatusText" access="public" output="false" returnType="string">
|
||||
<cfreturn variables.statusText />
|
||||
</cffunction>
|
||||
public numeric function getStatus() output="false" {
|
||||
return variables.statusCode;
|
||||
}
|
||||
|
||||
<cffunction name="withHeaders" access="public" output="false" hint="used to set custom headers for the response">
|
||||
<cfargument name="headerStruct" type="struct" required="true" />
|
||||
<cfset variables.miscHeaders = arguments.headerStruct />
|
||||
<cfreturn this />
|
||||
</cffunction>
|
||||
public string function getStatusText() output="false" {
|
||||
return variables.statusText;
|
||||
}
|
||||
|
||||
<cffunction name="getHeaders" access="public" output="false" returntype="Struct">
|
||||
<cfreturn variables.miscHeaders />
|
||||
</cffunction>
|
||||
public function withHeaders(required struct headerStruct) output="false" hint="used to set custom headers for the response" {
|
||||
variables.miscHeaders = arguments.headerStruct;
|
||||
return this;
|
||||
}
|
||||
|
||||
<cffunction name="andDelete" access="public" output="false" hint="used to delete the streamed file">
|
||||
<cfargument name="doDeleteFile" type="boolean" required="true" />
|
||||
<cfset variables.deleteFile = arguments.doDeleteFile />
|
||||
<cfreturn this />
|
||||
</cffunction>
|
||||
public struct function getHeaders() output="false" {
|
||||
return variables.miscHeaders;
|
||||
}
|
||||
|
||||
<cffunction name="getDeleteFile" access="public" output="false" returntype="boolean">
|
||||
<cfreturn variables.deleteFile />
|
||||
</cffunction>
|
||||
public function andDelete(required boolean doDeleteFile) output="false" hint="used to delete the streamed file" {
|
||||
variables.deleteFile = arguments.doDeleteFile;
|
||||
return this;
|
||||
}
|
||||
|
||||
</cfcomponent>
|
||||
public boolean function getDeleteFile() output="false" {
|
||||
return variables.deleteFile;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+166
-174
@@ -1,159 +1,138 @@
|
||||
<cfcomponent output="false">
|
||||
<cfscript>
|
||||
//bean cache
|
||||
this.beans = structNew();
|
||||
this.transients = structNew();
|
||||
//functionality
|
||||
</cfscript>
|
||||
component output="false" {
|
||||
|
||||
<cffunction name="init" output="false">
|
||||
<cfargument name="externalBeanFactory">
|
||||
<cfscript>
|
||||
if (structKeyExists(arguments, "externalBeanFactory")) {
|
||||
this.externalBeanFactory = arguments.externalBeanFactory;
|
||||
}
|
||||
return this;
|
||||
</cfscript>
|
||||
</cffunction>
|
||||
// bean cache
|
||||
this.beans = {};
|
||||
this.transients = {};
|
||||
|
||||
<cfscript>
|
||||
// Proxy to beanExists to provide similar interface to ColdSpring
|
||||
function containsBean(beanName){
|
||||
return beanExists(arguments.beanName);
|
||||
public function init(externalBeanFactory) {
|
||||
if (structKeyExists(arguments, "externalBeanFactory")) {
|
||||
this.externalBeanFactory = arguments.externalBeanFactory;
|
||||
}
|
||||
function transientExists(beanName){
|
||||
return structKeyExists(this.transients, arguments.beanName);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Proxy to beanExists to provide similar interface to ColdSpring
|
||||
public function containsBean(beanName) {
|
||||
return beanExists(arguments.beanName);
|
||||
}
|
||||
|
||||
public function transientExists(beanName) {
|
||||
return structKeyExists(this.transients, arguments.beanName);
|
||||
}
|
||||
|
||||
public function getBean(beanName) {
|
||||
var b = 0;
|
||||
var meta = 0;
|
||||
if (beanExists(arguments.beanName, false, false)) {
|
||||
return this.beans[arguments.beanName];
|
||||
} else if (transientExists(arguments.beanName)) {
|
||||
b = createObject('component', this.transients[arguments.beanName]);
|
||||
meta = getMetadata(b);
|
||||
_recurse_ResolveDependencies(b, meta);
|
||||
return b;
|
||||
} else if (externalBeanExists(arguments.beanName)) {
|
||||
return this.externalBeanFactory.getBean(arguments.beanName);
|
||||
} else {
|
||||
throwError(message="Bean name '#arguments.beanName#' not found.", type="Taffy.Factory.BeanNotFound");
|
||||
}
|
||||
function getBean(beanName){
|
||||
var b = 0;
|
||||
var meta = 0;
|
||||
if (beanExists(arguments.beanName, false, false)){
|
||||
return this.beans[arguments.beanName];
|
||||
}else if (transientExists(arguments.beanName)){
|
||||
b = createObject('component', this.transients[arguments.beanName]);
|
||||
meta = getMetadata(b);
|
||||
_recurse_ResolveDependencies(b, meta);
|
||||
return b;
|
||||
}else if (externalBeanExists(arguments.beanName)){
|
||||
return this.externalBeanFactory.getBean(arguments.beanName);
|
||||
}else{
|
||||
throwError(message="Bean name '#arguments.beanName#' not found.", type="Taffy.Factory.BeanNotFound");
|
||||
}
|
||||
|
||||
public function getBeanList() {
|
||||
var combined = structKeyList(this.beans);
|
||||
var trans = structKeyList(this.transients);
|
||||
if (len(combined) and len(trans)) {
|
||||
combined = combined & ",";
|
||||
}
|
||||
combined = combined & trans;
|
||||
return combined;
|
||||
}
|
||||
|
||||
public function beanExists(required beanName, includeTransients=true, includeExternal=false) output="false" {
|
||||
return structKeyExists(this.beans, arguments.beanName) or (arguments.includeTransients and transientExists(arguments.beanName)) or
|
||||
(arguments.includeExternal and externalBeanExists(arguments.beanName));
|
||||
}
|
||||
|
||||
private boolean function externalBeanExists(required beanName) output="false" {
|
||||
return structKeyExists(this, "externalBeanFactory") and this.externalBeanFactory.containsBean(arguments.beanName);
|
||||
}
|
||||
|
||||
public void function loadBeansFromPath(
|
||||
required string beanPath hint="Absolute path to folder containing beans",
|
||||
string resourcesPath="resources",
|
||||
string resourcesBasePath="",
|
||||
boolean isFullReload=false,
|
||||
taffyRef={}
|
||||
) output="false" {
|
||||
var local = {};
|
||||
// cache all of the beans
|
||||
if (isFullReload) {
|
||||
this.beans = {};
|
||||
arguments.taffyRef.status.skippedResources = []; // empty out the array on factory reloads
|
||||
arguments.taffyRef.beanList = "";
|
||||
}
|
||||
// if the folder doesn't exist, do nothing
|
||||
if (!directoryExists(arguments.beanPath)) {
|
||||
return;
|
||||
}
|
||||
// get list of beans to load
|
||||
local.beanQuery = directoryList(arguments.beanPath, true, "query", "*.cfc");
|
||||
for (var row in local.beanQuery) {
|
||||
local.beanName = filePathToBeanName(row.directory, row.name, arguments.resourcesBasePath);
|
||||
local.beanPath = filePathToBeanPath(row.directory, row.name, arguments.resourcesPath, arguments.resourcesBasePath);
|
||||
try {
|
||||
local.objBean = createObject("component", local.beanPath);
|
||||
if (isSerializerInstance(local.objBean)) {
|
||||
this.transients[local.beanName] = local.beanPath;
|
||||
} else {
|
||||
this.beans[local.beanName] = local.objBean;
|
||||
}
|
||||
} catch (any e) {
|
||||
// skip cfc's with errors, but save info about them for display in the dashboard
|
||||
local.err = {};
|
||||
local.err.resource = local.beanName;
|
||||
local.err.exception = e;
|
||||
arrayAppend(arguments.taffyRef.status.skippedResources, local.err);
|
||||
}
|
||||
}
|
||||
function getBeanList(){
|
||||
var combined = structKeyList(this.beans);
|
||||
var trans = structKeyList(this.transients);
|
||||
if (len(combined) and len(trans)){
|
||||
combined = combined & ",";
|
||||
}
|
||||
combined = combined & trans;
|
||||
return combined;
|
||||
// resolve dependencies
|
||||
for (local.b in this.beans) {
|
||||
local.bean = this.beans[local.b];
|
||||
local.beanMeta = getMetadata(local.bean);
|
||||
_recurse_ResolveDependencies(local.bean, local.beanMeta);
|
||||
}
|
||||
</cfscript>
|
||||
<cffunction name="beanExists" output="false">
|
||||
<cfargument required="true" name="beanName">
|
||||
<cfargument name="includeTransients" default="true">
|
||||
<cfargument name="includeExternal" default="false">
|
||||
<cfscript>
|
||||
return structKeyExists(this.beans, arguments.beanName) or (arguments.includeTransients and transientExists(arguments.beanName)) or
|
||||
(arguments.includeExternal and externalBeanExists(arguments.beanName));
|
||||
</cfscript>
|
||||
</cffunction>
|
||||
<cffunction name="externalBeanExists" access="private" output="false" returnType="boolean">
|
||||
<cfargument required="true" name="beanName">
|
||||
<cfscript>
|
||||
return structKeyExists(this, "externalBeanFactory") and this.externalBeanFactory.containsBean(arguments.beanName);
|
||||
</cfscript>
|
||||
</cffunction>
|
||||
<cffunction name="loadBeansFromPath" access="public" output="false" returnType="void">
|
||||
<cfargument name="beanPath" type="string" required="true" hint="Absolute path to folder containing beans" />
|
||||
<cfargument name="resourcesPath" type="string" default="resources" />
|
||||
<cfargument name="resourcesBasePath" type="string" default="" />
|
||||
<cfargument name="isFullReload" type="boolean" default="false" />
|
||||
<cfargument name="taffyRef" type="any" required="false" default="#structNew()#" />
|
||||
<cfset var local = StructNew() />
|
||||
<!--- cache all of the beans --->
|
||||
<cfif isFullReload>
|
||||
<cfset this.beans = structNew() />
|
||||
<cfset arguments.taffyRef.status.skippedResources = arrayNew(1) /> <!--- empty out the array on factory reloads --->
|
||||
<cfset arguments.taffyRef.beanList = "" />
|
||||
</cfif>
|
||||
<!--- if the folder doesn't exist, do nothing --->
|
||||
<cfif not directoryExists(arguments.beanPath)>
|
||||
<cfreturn />
|
||||
</cfif>
|
||||
<!--- get list of beans to load --->
|
||||
<cfdirectory action="list" directory="#arguments.beanPath#" filter="*.cfc" name="local.beanQuery" recurse="true" />
|
||||
<cfloop query="local.beanQuery">
|
||||
<cfset local.beanName = filePathToBeanName(local.beanQuery.directory, local.beanquery.name, arguments.resourcesBasePath) />
|
||||
<cfset local.beanPath = filePathToBeanPath(local.beanQuery.directory, local.beanquery.name, arguments.resourcesPath, arguments.resourcesBasePath) />
|
||||
<cftry>
|
||||
<cfset local.objBean = createObject("component", local.beanPath) />
|
||||
<cfif isInstanceOf(local.objBean, "taffy.core.baseSerializer")>
|
||||
<cfset this.transients[local.beanName] = local.beanPath />
|
||||
<cfelse>
|
||||
<cfset this.beans[local.beanName] = local.objBean />
|
||||
</cfif>
|
||||
<cfcatch>
|
||||
<!--- skip cfc's with errors, but save info about them for display in the dashboard --->
|
||||
<cfset local.err = structNew() />
|
||||
<cfset local.err.resource = local.beanName />
|
||||
<cfset local.err.exception = cfcatch />
|
||||
<cfset arrayAppend(arguments.taffyRef.status.skippedResources, local.err) />
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
</cfloop>
|
||||
<!--- resolve dependencies --->
|
||||
<cfloop list="#structKeyList(this.beans)#" index="local.b">
|
||||
<cfset local.bean = this.beans[local.b] />
|
||||
<cfset local.beanMeta = getMetadata(local.bean) />
|
||||
<cfset _recurse_ResolveDependencies(local.bean, local.beanMeta) />
|
||||
</cfloop>
|
||||
</cffunction>
|
||||
}
|
||||
|
||||
<cffunction name="filePathToBeanPath" access="private">
|
||||
<cfargument name="path" />
|
||||
<cfargument name="filename" />
|
||||
<cfargument name="resourcesPath" />
|
||||
<cfargument name="resourcesBasePath" />
|
||||
<cfset var beanPath = "" />
|
||||
<cfif len(resourcesBasePath) eq 0>
|
||||
<cfset arguments.resourcesBasePath = "!@$%^&*()" />
|
||||
</cfif>
|
||||
<cfset beanPath =
|
||||
private function filePathToBeanPath(path, filename, resourcesPath, resourcesBasePath) {
|
||||
var beanPath = "";
|
||||
if (len(resourcesBasePath) eq 0) {
|
||||
arguments.resourcesBasePath = "!@$%^&*()";
|
||||
}
|
||||
beanPath =
|
||||
resourcesPath
|
||||
&
|
||||
"."
|
||||
&
|
||||
replaceList(
|
||||
& "."
|
||||
& replaceList(
|
||||
replace(path, resourcesBasePath, ""),
|
||||
"/,\",
|
||||
".,."
|
||||
)
|
||||
&
|
||||
"."
|
||||
&
|
||||
replace(
|
||||
& "."
|
||||
& replace(
|
||||
filename,
|
||||
".cfc",
|
||||
""
|
||||
)
|
||||
/>
|
||||
<cfset beanPath = replace(beanPath, "..", ".", "ALL") />
|
||||
<cfif left(beanPath, 1) eq ".">
|
||||
<cfset beanPath = right(beanPath, len(beanPath)-1) />
|
||||
</cfif>
|
||||
<cfreturn beanPath />
|
||||
</cffunction>
|
||||
);
|
||||
beanPath = replace(beanPath, "..", ".", "ALL");
|
||||
if (left(beanPath, 1) eq ".") {
|
||||
beanPath = right(beanPath, len(beanPath)-1);
|
||||
}
|
||||
return beanPath;
|
||||
}
|
||||
|
||||
<cffunction name="filePathToBeanName" access="private">
|
||||
<cfargument name="path" />
|
||||
<cfargument name="filename" />
|
||||
<cfargument name="basepath" />
|
||||
<cfif len(basepath) eq 0>
|
||||
<cfset arguments.basePath = "!@$%^&*()" />
|
||||
</cfif>
|
||||
<cfreturn
|
||||
private function filePathToBeanName(path, filename, basepath) {
|
||||
if (len(basepath) eq 0) {
|
||||
arguments.basePath = "!@$%^&*()";
|
||||
}
|
||||
return
|
||||
replaceList(
|
||||
replace(path, basepath, ""),
|
||||
"/,\",
|
||||
@@ -163,40 +142,53 @@
|
||||
filename,
|
||||
".cfc",
|
||||
""
|
||||
)
|
||||
/>
|
||||
</cffunction>
|
||||
);
|
||||
}
|
||||
|
||||
<cffunction name="_recurse_ResolveDependencies" access="private">
|
||||
<cfargument name="bean" required="true" />
|
||||
<cfargument name="metaData" type="struct" required="true" />
|
||||
<cfset var local = structNew() />
|
||||
<cfif structKeyExists(arguments.metaData, "functions") and isArray(arguments.metaData.functions)>
|
||||
<cfloop from="1" to="#arrayLen(arguments.metaData.functions)#" index="local.f">
|
||||
<cfset local.fname = arguments.metaData.functions[local.f].name />
|
||||
<cfif len(local.fname) gt 3>
|
||||
<cfset local.propName = right(local.fname, len(local.fname)-3) />
|
||||
<cfif left(local.fname, 3) eq "set" and beanExists(local.propName, true, true)>
|
||||
<cfset evaluate("arguments.bean.#local.fname#(getBean('#local.propName#'))") />
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
<cfif structKeyExists(arguments.metaData, "properties") and isArray(arguments.metaData.properties)>
|
||||
<cfloop from="1" to="#arrayLen(arguments.metaData.properties)#" index="local.p">
|
||||
<cfset local.propName = arguments.metaData.properties[local.p].name />
|
||||
<cfif beanExists(local.propName, true, true)>
|
||||
<cfset arguments.bean[local.propName] = getBean(local.propName) />
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
<cfif structKeyExists(arguments.metaData, "extends") and isStruct(arguments.metaData.extends)>
|
||||
<cfset _recurse_ResolveDependencies(arguments.bean, arguments.metaData.extends) />
|
||||
</cfif>
|
||||
</cffunction>
|
||||
<!--- proxy function for CF8 compatibility --->
|
||||
<cffunction name="throwError">
|
||||
<cfthrow attributecollection="#arguments#" />
|
||||
</cffunction>
|
||||
private function _recurse_ResolveDependencies(required bean, required struct metaData) {
|
||||
var local = {};
|
||||
if (structKeyExists(arguments.metaData, "functions") and isArray(arguments.metaData.functions)) {
|
||||
for (local.f = 1; local.f <= arrayLen(arguments.metaData.functions); local.f++) {
|
||||
local.fname = arguments.metaData.functions[local.f].name;
|
||||
if (len(local.fname) gt 3) {
|
||||
local.propName = right(local.fname, len(local.fname)-3);
|
||||
if (left(local.fname, 3) eq "set" and beanExists(local.propName, true, true)) {
|
||||
invoke(arguments.bean, local.fname, [getBean(local.propName)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (structKeyExists(arguments.metaData, "properties") and isArray(arguments.metaData.properties)) {
|
||||
for (local.p = 1; local.p <= arrayLen(arguments.metaData.properties); local.p++) {
|
||||
local.propName = arguments.metaData.properties[local.p].name;
|
||||
if (beanExists(local.propName, true, true)) {
|
||||
arguments.bean[local.propName] = getBean(local.propName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (structKeyExists(arguments.metaData, "extends") and isStruct(arguments.metaData.extends)) {
|
||||
_recurse_ResolveDependencies(arguments.bean, arguments.metaData.extends);
|
||||
}
|
||||
}
|
||||
|
||||
</cfcomponent>
|
||||
/**
|
||||
* Walks the inheritance chain to check if obj extends baseSerializer.
|
||||
* Replaces isInstanceOf() which fails on Lucee when extends resolves
|
||||
* to a relative path instead of a mapping-prefixed path.
|
||||
*/
|
||||
private boolean function isSerializerInstance(required any obj) output="false" {
|
||||
if (!isObject(arguments.obj)) return false;
|
||||
var meta = getMetadata(arguments.obj);
|
||||
while (structKeyExists(meta, "name")) {
|
||||
if (listLast(meta.name, ".") == "baseSerializer") return true;
|
||||
if (!structKeyExists(meta, "extends")) break;
|
||||
meta = meta.extends;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function throwError() {
|
||||
cfthrow(attributecollection=arguments);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
<cfcomponent extends="taffy.core.baseDeserializer">
|
||||
component extends="baseDeserializer" {
|
||||
|
||||
<cffunction name="getFromJson" output="false" taffy:mime="application/json,text/json" hint="get data from json">
|
||||
<cfargument name="body" hint="the textual request body" />
|
||||
<cfset var data = 0 />
|
||||
<cfset var response = {} />
|
||||
public function getFromJson(body) output="false" taffy_mime="application/json,text/json" hint="get data from json" {
|
||||
var data = 0;
|
||||
var response = {};
|
||||
|
||||
<cfif not isJson(arguments.body)>
|
||||
<cfset throwError(msg="Input JSON is not well formed", statusCode="400") />
|
||||
</cfif>
|
||||
<cfset data = deserializeJSON(arguments.body) />
|
||||
<cfif not isStruct(data)>
|
||||
<cfset response['_body'] = data />
|
||||
<cfelse>
|
||||
<cfset response = data />
|
||||
</cfif>
|
||||
if (!isJson(arguments.body)) {
|
||||
throwError(msg="Input JSON is not well formed", statusCode="400");
|
||||
}
|
||||
data = deserializeJSON(arguments.body);
|
||||
if (!isStruct(data)) {
|
||||
response['_body'] = data;
|
||||
} else {
|
||||
response = data;
|
||||
}
|
||||
|
||||
<cfreturn response />
|
||||
</cffunction>
|
||||
return response;
|
||||
}
|
||||
|
||||
</cfcomponent>
|
||||
}
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
<cfcomponent extends="taffy.core.baseSerializer" output="false" hint="Serializer that uses CFML server's json serialization functionality to return json data">
|
||||
component extends="baseSerializer" output="false" hint="Serializer that uses CFML server's json serialization functionality to return json data" {
|
||||
|
||||
<cffunction
|
||||
name="getAsJson"
|
||||
output="false"
|
||||
taffy:mime="application/json;text/json"
|
||||
taffy:default="true"
|
||||
hint="serializes data as JSON">
|
||||
<cfreturn rereplace(replace(serializeJSON(variables.data), chr(2), '', 'ALL'), '"\\u0002', '"', 'ALL') />
|
||||
</cffunction>
|
||||
public function getAsJson() output="false" taffy_mime="application/json;text/json" taffy_default="true" hint="serializes data as JSON" {
|
||||
return rereplace(replace(serializeJSON(variables.data), chr(2), '', 'ALL'), '"\\u0002', '"', 'ALL');
|
||||
}
|
||||
|
||||
</cfcomponent>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
component output="false" hint="Generates an OpenAPI 3.1 document from Taffy runtime metadata." {
|
||||
|
||||
public struct function generate()
|
||||
hint="Returns an OpenAPI 3.1 struct describing all registered resources." {
|
||||
|
||||
var settings = application._taffy.settings;
|
||||
var endpoints = application._taffy.endpoints;
|
||||
var matchOrder = application._taffy.uriMatchOrder;
|
||||
var openapiCfg = structKeyExists(settings, "openapi") ? settings.openapi : {};
|
||||
var mediaTypes = collectResponseMediaTypes();
|
||||
|
||||
var doc = {
|
||||
"openapi": "3.1.0",
|
||||
"info": buildInfo(settings, openapiCfg),
|
||||
"paths": {}
|
||||
};
|
||||
|
||||
var servers = buildServers(openapiCfg);
|
||||
if (arrayLen(servers)) {
|
||||
doc["servers"] = servers;
|
||||
}
|
||||
|
||||
for (var i = 1; i <= arrayLen(matchOrder); i++) {
|
||||
var endpoint = endpoints[matchOrder[i]];
|
||||
var bean = application._taffy.factory.getBean(endpoint.beanName);
|
||||
var beanMeta = getMetaData(bean);
|
||||
|
||||
if (hasAnyKey(beanMeta, "taffy_docs_hide,taffy:docs:hide,taffy_dashboard_hide,taffy:dashboard:hide")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var funcs = collectFunctions(beanMeta);
|
||||
var pathItem = {};
|
||||
var validVerbs = "get,put,post,delete,options,head,patch,trace";
|
||||
|
||||
for (var verb in endpoint.methods) {
|
||||
if (!listFindNoCase(validVerbs, verb)) continue;
|
||||
var funcName = endpoint.methods[verb];
|
||||
if (!structKeyExists(funcs, funcName)) continue;
|
||||
var func = funcs[funcName];
|
||||
if (hasAnyKey(func, "taffy_docs_hide,taffy:docs:hide,taffy_dashboard_hide,taffy:dashboard:hide")) continue;
|
||||
pathItem[lcase(verb)] = buildOperation(verb, func, endpoint, beanMeta, mediaTypes);
|
||||
}
|
||||
|
||||
if (!structIsEmpty(pathItem)) {
|
||||
doc.paths[normalizePathTemplate(endpoint.srcUri, endpoint.tokens)] = pathItem;
|
||||
}
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
private struct function buildInfo(required struct settings, required struct openapiCfg) {
|
||||
var info = {
|
||||
"title": arguments.settings.docs.APIName,
|
||||
"version": arguments.settings.docs.APIVersion
|
||||
};
|
||||
if (structKeyExists(arguments.openapiCfg, "description")) info["description"] = arguments.openapiCfg.description;
|
||||
if (structKeyExists(arguments.openapiCfg, "contact")) info["contact"] = arguments.openapiCfg.contact;
|
||||
if (structKeyExists(arguments.openapiCfg, "license")) info["license"] = arguments.openapiCfg.license;
|
||||
return info;
|
||||
}
|
||||
|
||||
private array function buildServers(required struct openapiCfg) {
|
||||
if (structKeyExists(arguments.openapiCfg, "servers") && isArray(arguments.openapiCfg.servers)) {
|
||||
return arguments.openapiCfg.servers;
|
||||
}
|
||||
var host = cgi.http_host;
|
||||
if (!len(host)) return [];
|
||||
var isHttps = cgi.server_port == 443 || (structKeyExists(cgi, "https") && cgi.https == "on");
|
||||
var scheme = isHttps ? "https" : "http";
|
||||
var scriptDir = getDirectoryFromPath(cgi.script_name);
|
||||
if (right(scriptDir, 1) == "/" && len(scriptDir) > 1) {
|
||||
scriptDir = left(scriptDir, len(scriptDir) - 1);
|
||||
}
|
||||
return [ { "url": scheme & "://" & host & scriptDir } ];
|
||||
}
|
||||
|
||||
private array function collectResponseMediaTypes()
|
||||
hint="Unique MIME type strings Taffy can return, derived from registered serializers." {
|
||||
var mimes = {};
|
||||
for (var ext in application._taffy.settings.mimeExtensions) {
|
||||
for (var part in listToArray(application._taffy.settings.mimeExtensions[ext], ";")) {
|
||||
mimes[trim(part)] = true;
|
||||
}
|
||||
}
|
||||
var out = [];
|
||||
for (var m in mimes) arrayAppend(out, m);
|
||||
arraySort(out, "text");
|
||||
return out;
|
||||
}
|
||||
|
||||
private struct function collectFunctions(required struct metadata)
|
||||
hint="Flatten the cfc's function metadata (including inherited) keyed by name. Child overrides parent." {
|
||||
var result = {};
|
||||
if (structKeyExists(arguments.metadata, "extends") && arguments.metadata.extends.fullname != "taffy.core.resource") {
|
||||
result = collectFunctions(arguments.metadata.extends);
|
||||
}
|
||||
if (!structKeyExists(arguments.metadata, "functions") || !isArray(arguments.metadata.functions)) {
|
||||
return result;
|
||||
}
|
||||
for (var f = 1; f <= arrayLen(arguments.metadata.functions); f++) {
|
||||
var func = arguments.metadata.functions[f];
|
||||
if (structKeyExists(func, "access") && (func.access == "private" || func.access == "package")) continue;
|
||||
result[func.name] = func;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private struct function buildOperation(
|
||||
required string verb,
|
||||
required struct func,
|
||||
required struct endpoint,
|
||||
required struct beanMeta,
|
||||
required array mediaTypes
|
||||
) {
|
||||
var op = {
|
||||
"tags": [ getResourceTag(arguments.beanMeta, arguments.endpoint.beanName) ],
|
||||
"operationId": arguments.endpoint.beanName & "_" & lcase(arguments.verb)
|
||||
};
|
||||
|
||||
if (structKeyExists(arguments.beanMeta, "taffy:docs:name")) {
|
||||
op["summary"] = arguments.beanMeta["taffy:docs:name"];
|
||||
} else if (structKeyExists(arguments.beanMeta, "taffy_docs_name")) {
|
||||
op["summary"] = arguments.beanMeta["taffy_docs_name"];
|
||||
}
|
||||
if (structKeyExists(arguments.func, "hint")) op["description"] = arguments.func.hint;
|
||||
|
||||
var tokenLookup = {};
|
||||
for (var t = 1; t <= arrayLen(arguments.endpoint.tokens); t++) {
|
||||
tokenLookup[lcase(arguments.endpoint.tokens[t])] = arguments.endpoint.tokens[t];
|
||||
}
|
||||
|
||||
var parameters = [];
|
||||
var bodyParams = [];
|
||||
var declaredPathTokens = {};
|
||||
var params = structKeyExists(arguments.func, "parameters") ? arguments.func.parameters : [];
|
||||
var isBodyVerb = listFindNoCase("post,put,patch", arguments.verb) > 0;
|
||||
|
||||
for (var p = 1; p <= arrayLen(params); p++) {
|
||||
var param = params[p];
|
||||
if (hasAnyKey(param, "taffy_docs_hide,taffy:docs:hide,taffy_dashboard_hide,taffy:dashboard:hide")) continue;
|
||||
if (structKeyExists(tokenLookup, lcase(param.name))) {
|
||||
// use the token's original casing so the parameter name matches the path template exactly
|
||||
arrayAppend(parameters, buildParameter(param, "path", true, tokenLookup[lcase(param.name)]));
|
||||
declaredPathTokens[lcase(param.name)] = true;
|
||||
} else if (isBodyVerb) {
|
||||
arrayAppend(bodyParams, param);
|
||||
} else {
|
||||
var required = structKeyExists(param, "required") && param.required;
|
||||
arrayAppend(parameters, buildParameter(param, "query", required));
|
||||
}
|
||||
}
|
||||
|
||||
// backfill any path tokens the resource didn't declare as args — OpenAPI requires a parameter for every {token}
|
||||
for (var t2 = 1; t2 <= arrayLen(arguments.endpoint.tokens); t2++) {
|
||||
var tok = arguments.endpoint.tokens[t2];
|
||||
if (!structKeyExists(declaredPathTokens, lcase(tok))) {
|
||||
arrayAppend(parameters, {
|
||||
"name": tok,
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": { "type": "string" }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (arrayLen(parameters)) op["parameters"] = parameters;
|
||||
if (isBodyVerb && arrayLen(bodyParams)) op["requestBody"] = buildRequestBody(bodyParams);
|
||||
op["responses"] = buildResponses(arguments.mediaTypes);
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
private struct function buildParameter(required struct param, required string location, required boolean required, string nameOverride = "") {
|
||||
var out = {
|
||||
"name": len(arguments.nameOverride) ? arguments.nameOverride : arguments.param.name,
|
||||
"in": arguments.location,
|
||||
"required": arguments.required,
|
||||
"schema": paramSchema(arguments.param)
|
||||
};
|
||||
if (structKeyExists(arguments.param, "hint")) out["description"] = arguments.param.hint;
|
||||
return out;
|
||||
}
|
||||
|
||||
private struct function buildRequestBody(required array bodyParams) {
|
||||
var properties = {};
|
||||
var required = [];
|
||||
for (var p = 1; p <= arrayLen(arguments.bodyParams); p++) {
|
||||
var param = arguments.bodyParams[p];
|
||||
var schema = paramSchema(param);
|
||||
if (structKeyExists(param, "hint")) schema["description"] = param.hint;
|
||||
properties[param.name] = schema;
|
||||
if (structKeyExists(param, "required") && param.required) arrayAppend(required, param.name);
|
||||
}
|
||||
var bodySchema = { "type": "object", "properties": properties };
|
||||
if (arrayLen(required)) bodySchema["required"] = required;
|
||||
return {
|
||||
"content": {
|
||||
"application/json": { "schema": bodySchema },
|
||||
"application/x-www-form-urlencoded": { "schema": bodySchema }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private struct function buildResponses(required array mediaTypes) {
|
||||
var content = {};
|
||||
for (var i = 1; i <= arrayLen(arguments.mediaTypes); i++) {
|
||||
content[arguments.mediaTypes[i]] = {};
|
||||
}
|
||||
var ok = { "description": "Successful response" };
|
||||
if (!structIsEmpty(content)) ok["content"] = content;
|
||||
return { "200": ok, "default": { "description": "Unexpected error" } };
|
||||
}
|
||||
|
||||
private struct function paramSchema(required struct param) {
|
||||
var schema = cfmlTypeToSchema(structKeyExists(arguments.param, "type") ? arguments.param.type : "");
|
||||
if (structKeyExists(arguments.param, "default") && len(arguments.param.default)) {
|
||||
schema["default"] = arguments.param.default;
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
|
||||
private struct function cfmlTypeToSchema(required string type) {
|
||||
switch (lcase(trim(arguments.type))) {
|
||||
case "numeric": case "number": case "double": case "float":
|
||||
return { "type": "number" };
|
||||
case "integer":
|
||||
return { "type": "integer" };
|
||||
case "string":
|
||||
return { "type": "string" };
|
||||
case "boolean":
|
||||
return { "type": "boolean" };
|
||||
case "struct":
|
||||
return { "type": "object" };
|
||||
case "array":
|
||||
return { "type": "array" };
|
||||
case "date": case "datetime":
|
||||
return { "type": "string", "format": "date-time" };
|
||||
case "uuid": case "guid":
|
||||
return { "type": "string", "format": "uuid" };
|
||||
case "email":
|
||||
return { "type": "string", "format": "email" };
|
||||
case "url":
|
||||
return { "type": "string", "format": "uri" };
|
||||
case "": case "any": case "variablename":
|
||||
return {};
|
||||
default:
|
||||
return { "type": "string" };
|
||||
}
|
||||
}
|
||||
|
||||
private string function getResourceTag(required struct beanMeta, required string beanName) {
|
||||
if (structKeyExists(arguments.beanMeta, "taffy:docs:name")) return arguments.beanMeta["taffy:docs:name"];
|
||||
if (structKeyExists(arguments.beanMeta, "taffy_docs_name")) return arguments.beanMeta["taffy_docs_name"];
|
||||
return arguments.beanName;
|
||||
}
|
||||
|
||||
private string function normalizePathTemplate(required string uri, required array tokens)
|
||||
hint="Rebuild Taffy's URI as an OpenAPI path template: {name:regex} => {name}. Handles nested braces in token regexes." {
|
||||
var out = "";
|
||||
var i = 1;
|
||||
var tokenIdx = 1;
|
||||
var n = len(arguments.uri);
|
||||
while (i <= n) {
|
||||
var ch = mid(arguments.uri, i, 1);
|
||||
if (ch == "{") {
|
||||
// find the matching close brace, honoring nesting
|
||||
var depth = 1;
|
||||
var j = i + 1;
|
||||
while (j <= n && depth > 0) {
|
||||
var c2 = mid(arguments.uri, j, 1);
|
||||
if (c2 == "{") depth++;
|
||||
else if (c2 == "}") depth--;
|
||||
if (depth > 0) j++;
|
||||
}
|
||||
if (depth == 0 && tokenIdx <= arrayLen(arguments.tokens)) {
|
||||
out &= "{" & arguments.tokens[tokenIdx] & "}";
|
||||
tokenIdx++;
|
||||
i = j + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
out &= ch;
|
||||
i++;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private boolean function hasAnyKey(required struct target, required string keys) {
|
||||
for (var k in listToArray(arguments.keys)) {
|
||||
if (structKeyExists(arguments.target, k)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
+121
-125
@@ -1,141 +1,137 @@
|
||||
<cfcomponent hint="base class for taffy REST components">
|
||||
component hint="base class for taffy REST components" {
|
||||
|
||||
<cffunction name="forceString">
|
||||
<cfargument name="data" required="true" hint="the data that is being forced to serialize as a string" />
|
||||
<cfreturn chr(2) & arguments.data />
|
||||
</cffunction>
|
||||
public function forceString(required data) hint="the data that is being forced to serialize as a string" {
|
||||
return chr(2) & arguments.data;
|
||||
}
|
||||
|
||||
<cfset variables.encode = structNew() />
|
||||
<cfset variables.encode.string = forceString />
|
||||
variables.encode = {};
|
||||
variables.encode.string = forceString;
|
||||
|
||||
<!--- helper functions --->
|
||||
<cffunction name="representationOf" access="public" output="false" hint="returns an object capable of serializing the data in a variety of formats">
|
||||
<cfargument name="data" required="true" hint="any simple or complex data that should be returned for the request" />
|
||||
<cfreturn getRepInstance().setData(arguments.data) />
|
||||
</cffunction>
|
||||
public function representationOf(required data) output="false" hint="returns an object capable of serializing the data in a variety of formats" {
|
||||
return getRepInstance().setData(arguments.data);
|
||||
}
|
||||
|
||||
<cffunction name="rep" access="public" output="false" hint="alias for representationOf">
|
||||
<cfargument name="data" required="true" />
|
||||
<cfreturn representationOf(arguments.data) />
|
||||
</cffunction>
|
||||
public function rep(required data) output="false" hint="alias for representationOf" {
|
||||
return representationOf(arguments.data);
|
||||
}
|
||||
|
||||
<cffunction name="noData" access="private" output="false" hint="use this function to return only headers to the consumer, no data">
|
||||
<cfreturn getRepInstance().noData() />
|
||||
</cffunction>
|
||||
private function noData() output="false" hint="use this function to return only headers to the consumer, no data" {
|
||||
return getRepInstance().noData();
|
||||
}
|
||||
|
||||
<cffunction name="noContent" access="private" output="false" hint="use this function to return only headers to the consumer, no data">
|
||||
<cfreturn getRepInstance().noContent() />
|
||||
</cffunction>
|
||||
private function noContent() output="false" hint="use this function to return only headers to the consumer, no data" {
|
||||
return getRepInstance().noContent();
|
||||
}
|
||||
|
||||
<cffunction name="streamFile" access="private" output="false" hint="Use this function to specify a file name (eg c:\tmp\kitten.jpg) to be streamed to the client. When you use this method it is *required* that you also use .withMime() to specify the mime type.">
|
||||
<cfargument name="fileName" required="true" hint="fully qualified file path (eg c:\tmp\kitten.jpg)" />
|
||||
<cfreturn getRepInstance().setFileName(arguments.fileName) />
|
||||
</cffunction>
|
||||
private function streamFile(required fileName) output="false" hint="Use this function to specify a file name (eg c:\tmp\kitten.jpg) to be streamed to the client. When you use this method it is *required* that you also use .withMime() to specify the mime type." {
|
||||
return getRepInstance().setFileName(arguments.fileName);
|
||||
}
|
||||
|
||||
<cffunction name="streamBinary" access="private" output="false" hint="Use this function to stream binary data, like a generated PDF object, to the client. When you use this method it is *required* that you also use .withMime() to specify the mime type.">
|
||||
<cfargument name="binaryData" required="true" hint="binary file data (eg a PDF object) that you want to return to the client" />
|
||||
<cfreturn getRepInstance().setFileData(arguments.binaryData) />
|
||||
</cffunction>
|
||||
private function streamBinary(required binaryData) output="false" hint="Use this function to stream binary data, like a generated PDF object, to the client. When you use this method it is *required* that you also use .withMime() to specify the mime type." {
|
||||
return getRepInstance().setFileData(arguments.binaryData);
|
||||
}
|
||||
|
||||
<cffunction name="streamImage" access="private" output="false" hint="Use this function to stream binary data, like a generated PDF object, to the client. When you use this method it is *required* that you also use .withMime() to specify the mime type.">
|
||||
<cfargument name="binaryData" required="true" hint="binary file data (eg a PDF object or image data) that you want to return to the client" />
|
||||
<cfreturn getRepInstance().setImageData(arguments.binaryData) />
|
||||
</cffunction>
|
||||
private function streamImage(required binaryData) output="false" hint="Use this function to stream binary data, like a generated PDF object, to the client. When you use this method it is *required* that you also use .withMime() to specify the mime type." {
|
||||
return getRepInstance().setImageData(arguments.binaryData);
|
||||
}
|
||||
|
||||
<cffunction name="saveLog">
|
||||
<cfargument name="exception" />
|
||||
<cfset logger = createObject("component", application._taffy.settings.exceptionLogAdapter).init(
|
||||
application._taffy.settings.exceptionLogAdapterConfig
|
||||
) />
|
||||
<cfset logger.saveLog(exception) />
|
||||
</cffunction>
|
||||
public function saveLog(exception) {
|
||||
var logger = createObject("component", application._taffy.settings.exceptionLogAdapter).init(
|
||||
application._taffy.settings.exceptionLogAdapterConfig
|
||||
);
|
||||
logger.saveLog(exception);
|
||||
}
|
||||
|
||||
<cffunction name="queryToArray" access="private" returntype="array" output="false">
|
||||
<cfargument name="q" type="query" required="yes" />
|
||||
<cfargument name="cb" type="any" required="no" />
|
||||
<cfscript>
|
||||
var local = {};
|
||||
if (structKeyExists(server, "railo") or structKeyExists(server, "lucee")) {
|
||||
local.Columns = listToArray(arguments.q.getColumnList(false));
|
||||
}
|
||||
else {
|
||||
local.Columns = arguments.q.getMetaData().getColumnLabels();
|
||||
}
|
||||
local.QueryArray = ArrayNew(1);
|
||||
for (local.RowIndex = 1; local.RowIndex <= arguments.q.RecordCount; local.RowIndex++){
|
||||
local.Row = {};
|
||||
local.numCols = ArrayLen( local.Columns );
|
||||
for (local.ColumnIndex = 1; local.ColumnIndex <= local.numCols; local.ColumnIndex++){
|
||||
local.ColumnName = local.Columns[ local.ColumnIndex ];
|
||||
if( local.ColumnName NEQ "" ) {
|
||||
local.Row[ local.ColumnName ] = arguments.q[ local.ColumnName ][ local.RowIndex ];
|
||||
}
|
||||
}
|
||||
if ( structKeyExists( arguments, "cb" ) ) {
|
||||
local.Row = cb( local.Row );
|
||||
}
|
||||
ArrayAppend( local.QueryArray, local.Row );
|
||||
}
|
||||
return( local.QueryArray );
|
||||
</cfscript>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="queryToStruct" access="private" returntype="struct" output="false">
|
||||
<cfargument name="q" type="query" required="yes" />
|
||||
<cfargument name="cb" type="any" required="no" />
|
||||
<cfset var local = {} />
|
||||
|
||||
<cfif q.recordcount gt 1>
|
||||
<cfthrow message="Unable to convert query resultset with more than one record to a simple struct, use queryToArray() instead" />
|
||||
</cfif>
|
||||
|
||||
<cfscript>
|
||||
if (structKeyExists(server, "railo") or structKeyExists(server, "lucee")) {
|
||||
local.Columns = listToArray(arguments.q.getColumnList(false));
|
||||
}
|
||||
else {
|
||||
local.Columns = arguments.q.getMetaData().getColumnLabels();
|
||||
}
|
||||
|
||||
local.QueryStruct = {};
|
||||
local.numCols = ArrayLen( local.Columns );
|
||||
|
||||
for (local.ColumnIndex = 1; local.ColumnIndex <= local.numCols; local.ColumnIndex++){
|
||||
local.ColumnName = local.Columns[ local.ColumnIndex ];
|
||||
if( local.ColumnName NEQ "" ) {
|
||||
if ( structKeyExists( arguments, "cb" ) ) {
|
||||
local.QueryStruct[ local.ColumnName ] = cb( local.ColumnName, arguments.q[ local.ColumnName ][1] );
|
||||
} else {
|
||||
local.QueryStruct[ local.ColumnName ] = arguments.q[ local.ColumnName ][1];
|
||||
}
|
||||
private array function qToArray(required query q, cb) output="false" {
|
||||
var local = {};
|
||||
if (structKeyExists(server, "lucee")) {
|
||||
local.Columns = listToArray(arguments.q.getColumnList(false));
|
||||
} else {
|
||||
local.Columns = arguments.q.getMetaData().getColumnLabels();
|
||||
}
|
||||
local.QueryArray = [];
|
||||
for (local.RowIndex = 1; local.RowIndex <= arguments.q.RecordCount; local.RowIndex++) {
|
||||
local.Row = {};
|
||||
local.numCols = arrayLen(local.Columns);
|
||||
for (local.ColumnIndex = 1; local.ColumnIndex <= local.numCols; local.ColumnIndex++) {
|
||||
local.ColumnName = local.Columns[local.ColumnIndex];
|
||||
if (local.ColumnName != "") {
|
||||
local.Row[local.ColumnName] = arguments.q[local.ColumnName][local.RowIndex];
|
||||
}
|
||||
}
|
||||
if (structKeyExists(arguments, "cb")) {
|
||||
local.Row = cb(local.Row);
|
||||
}
|
||||
arrayAppend(local.QueryArray, local.Row);
|
||||
}
|
||||
return local.QueryArray;
|
||||
}
|
||||
|
||||
return( local.QueryStruct );
|
||||
</cfscript>
|
||||
</cffunction>
|
||||
variables.funcs = getFunctionList();
|
||||
if (!structKeyExists(variables.funcs, "queryToArray")) {
|
||||
private array function queryToArray(required query q, cb) output="false" {
|
||||
return qToArray(arguments.q, arguments.cb);
|
||||
}
|
||||
}
|
||||
|
||||
<!---
|
||||
function that gets the representation class instance
|
||||
-- if the argument is blank, we use the default from taffy settings
|
||||
-- if the argument is a beanName, the bean is returned from the factory;
|
||||
-- otherwise it is assumed to be a cfc path and that cfc instance is returned
|
||||
--->
|
||||
<cffunction name="getRepInstance" access="private" output="false">
|
||||
<cfargument name="repClass" type="string" default="" />
|
||||
<cfif repClass eq "">
|
||||
<!--- recursion not the most efficient path here, but it's damn readable --->
|
||||
<cfreturn getRepInstance(application._taffy.settings.serializer) />
|
||||
<cfelseif application._taffy.factory.containsBean(arguments.repClass)>
|
||||
<cfreturn application._taffy.factory.getBean(arguments.repClass) />
|
||||
<cfelse>
|
||||
<cfreturn createObject("component", arguments.repClass) />
|
||||
</cfif>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="addDebugData" access="package" output="false">
|
||||
<cfargument name="data" type="any" />
|
||||
<cfset request.debugData = arguments.data />
|
||||
</cffunction>
|
||||
private struct function qToStruct(required query q, cb) output="false" {
|
||||
var local = {};
|
||||
|
||||
</cfcomponent>
|
||||
if (q.recordcount gt 1) {
|
||||
throw(message="Unable to convert query resultset with more than one record to a simple struct, use queryToArray() instead");
|
||||
}
|
||||
|
||||
if (structKeyExists(server, "lucee")) {
|
||||
local.Columns = listToArray(arguments.q.getColumnList(false));
|
||||
} else {
|
||||
local.Columns = arguments.q.getMetaData().getColumnLabels();
|
||||
}
|
||||
|
||||
local.QueryStruct = {};
|
||||
local.numCols = arrayLen(local.Columns);
|
||||
|
||||
for (local.ColumnIndex = 1; local.ColumnIndex <= local.numCols; local.ColumnIndex++) {
|
||||
local.ColumnName = local.Columns[local.ColumnIndex];
|
||||
if (local.ColumnName != "") {
|
||||
if (structKeyExists(arguments, "cb")) {
|
||||
local.QueryStruct[local.ColumnName] = cb(local.ColumnName, arguments.q[local.ColumnName][1]);
|
||||
} else {
|
||||
local.QueryStruct[local.ColumnName] = arguments.q[local.ColumnName][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return local.QueryStruct;
|
||||
}
|
||||
|
||||
if (!structKeyExists(variables.funcs, "queryToStruct")) {
|
||||
private struct function queryToStruct(required query q, cb) output="false" {
|
||||
return qToStruct(arguments.q, arguments.cb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* function that gets the representation class instance
|
||||
* -- if the argument is blank, we use the default from taffy settings
|
||||
* -- if the argument is a beanName, the bean is returned from the factory;
|
||||
* -- otherwise it is assumed to be a cfc path and that cfc instance is returned
|
||||
*/
|
||||
private function getRepInstance(string repClass="") output="false" {
|
||||
var rep = "";
|
||||
if (repClass eq "") {
|
||||
// recursion not the most efficient path here, but it's damn readable
|
||||
return getRepInstance(application._taffy.settings.serializer);
|
||||
} else if (application._taffy.factory.containsBean(arguments.repClass)) {
|
||||
rep = application._taffy.factory.getBean(arguments.repClass);
|
||||
} else {
|
||||
rep = createObject("component", arguments.repClass);
|
||||
}
|
||||
rep.setNoDataSends204NoContent(application._taffy.settings.noDataSends204NoContent);
|
||||
return rep;
|
||||
}
|
||||
|
||||
package function addDebugData(data) output="false" {
|
||||
request.debugData = arguments.data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user