Just wondering if we are able to re-write data that we was set via the setWriteString() method while responding to an inbound api call. For example, let's say the scripted rest resource code is as follows:
(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {
var body = request.body.data;
/* do something with request data
..
..
..
then start preparing the response
*/
var writer = response.getStreamWriter();
try
{
response.setContentType('application/json');
response.setStatus(200);
writer.writeString("{\"results\":[");
var inc = new GlideRecord('incident');
inc.query();
while(inc.next()){
var obj = {};
obj.id = inc.getValue('number');
obj.sys_id = inc.getUniqueValue();
writer.writeString(global.JSON.stringify(obj));
if (inc.hasNext()) {
writer.writeString(",");
}
}
writer.writeString("]}");
}
catch (ex)
{
// let's say exception was thrown on the 3rd iteration while gliding the incident table
// oh no...an exception..so we need to write something else to the stream
// is it possible to erase/remove everything that was added to the stream up until the exception occured?
// so that the response will contain details only about the error?
// something like below:
response.setContentType('application/json');
response.setStatus(500);
writer.writeString("{\"error\":\"Something went wrong\"}"); // this isn't working btw...the stream contained both the content generated in "try" block as well as the "catch" block
// this should not contain anything related to whatever was written from the earlier iterations....
}
})(request, response);
For the errors you can use the Scripted REST API Error objects.
Which should reset the output stream.
https://developer.servicenow.com/dev.do#!/learn/courses/paris/app_store_learnv2_rest_paris_rest_integrations/app_store_learnv2_rest_paris_scripted_rest_apis/app_store_learnv2_rest_paris_scripted_rest_api_error_objects
(function run(request, response) {
try {
...
} catch (e) {
var myError = new sn_ws_err.ServiceError();
myError.setStatus(500);
myError.setMessage('Something went wrong');
myError.setDetail('Error while retrieving your data: ' + e);
return myError;
}
})(request,response);
It might also be useful to get the error message from the GlideRecord
gr.getLastErrorMessage();
// something like aborted by businessrule or acl, etc...
For the details of your error message.
Related
Useage of 'request-native-promise' not correctly chaining to it's subsequent 'then' and 'catch' handlers.
My Protractor Test
// There's a bunch of other imports here
import { browser } from "protractor";
const RequestPromise = require('request-promise-native');
describe('spec mapper app', () => {
let specMapperPage: SpecMapperPage;
let specMapperFVRPage: SpecMapperFieldsValuesReviewPage;
let loginLogoutWorkflow: LoginLogoutWorkflow;
let apiToken: LoginToken;
let tokenUtil: TokenUtil;
let projectRecordsToBeDeleted = [];
let requestHandler;
let logger = new CustomLogger("spec mapper app");
let speccyEndpoints = new SpeccyEndpoints();
beforeAll( () => {
logger.debug("Before All")
loginLogoutWorkflow = new LoginLogoutWorkflow();
loginLogoutWorkflow.login();
tokenUtil = new TokenUtil();
tokenUtil.getToken().then((token:LoginToken) => {
apiToken = token;
requestHandler = new SpeccyRequestHandler(apiToken);
});
});
describe('import/export page', () => {
it('TC2962: I'm a test case', () => {
let testLogger = new CustomLogger("TC2955");
// Test Var setup
... // removed for brevity
// Test Setup
browser.waitForAngularEnabled(false);
// Setup the record to be on the mapper page
let body = speccyEndpoints.generateRevitPostBody(PROJECT_ID, fileName);
requestHandler.postToSpeccy(speccyEndpoints.DELITE_REVIT_POST_URI, body).then((response) => { // EDIT: removed non-existant argument "rejection"
// --> The then handler the promise is STILL not resolving into
// Only made it here like once
console.log("Response is: ");
console.log(response);
// I got this to work once, but now it's not
console.log("Response body is: ");
console.log(response.body);
}).catch(error => {
// --> The catch handler is ALSO NOT resolving
console.log("catch handler executed!");
console.log(error);
});
});
});
});
The test case where things are going wrong. My console.log("Response is: "); is NOT being outputted. I'm not getting error messages as to why.
My Speccy Request Handler Wrapper Class
import * as RequestPromise from "request-promise-native";
import {LoginToken} from "../testObjects/LoginToken";
import {CustomLogger} from "../logging/CustomLogger";
export class SpeccyRequestHandler {
_baseAPIURL = 'http://myapi.net/';
_options = {
method: '',
uri: '',
auth: {
'bearer': ''
},
headers: {
'User-Agent': 'client'
},
"resolveWithFullResponse": true,
body: {},
json: true
};
_logger;
constructor(apiToken: LoginToken) {
this._options.auth.bearer = apiToken.idToken;
this._logger = new CustomLogger(SpeccyRequestHandler.name);
}
getOptions() {
return this._options;
}
postToSpeccy(uri:string, body?) {
this._options.method = 'POST';
this._options.uri = this._baseAPIURL + uri;
if(body) {
this._options.body = body;
}
return RequestPromise(this._options);
}
getFromSpeccy(uri) {
this._options.method = 'GET';
this._options.uri = this._baseAPIURL + uri;
return RequestPromise(this._options);
}
}
This is my Request Handler specific to one of my APIs, the Speccy one, and has some custom aspects to it in the URL and the token passing.
Sources
Request-Promise-Native Github Page
Request-Promise Github page, documentation location
Update Number 1
After the fix #tomalak brought to my attention, my console.log's in the .then(... handler were being executed, the first 5-ish times after I changed over to this, I was getting a roughly 150+ line console log of the response object that contained a body of response I would expect from my request URI. I even got the body out by using response.body. I thought things were fixed and I wasn't using my logger that logs out to file, so I lost my proof. Now when I run this test and this request I do not go into the .then(... handler at all. I'm also not going into the catch. My request is working though, as my resource is created when the post endpoint is hit. Any further suggestions are appreciated.
What would cause something to work sometimes and not others? My only thought is maybe the generic post name in my request handler wasn't being used in lieu of another method higher up the build chain being caught.
Update Number 2
Removed a bunch of stuff to shorten my question. If you need more clarification, ask and I'll add it.
It ended up being a timeout on the end of the API. My response was simply taking too long to get back to me. It wasn't failing, so it never went into the catch. And I had it working at one point because the response taking so long is due to an overabundance of a certain resource in our system in particular. I thought it was me and something I had written wrong. Long story short, suspect your system, even if you think it's perfect or if your devs swear up and down nothing could be broken.
Also, the request-debug module was a nice thing to have to prove that other endpoints, such as the rest testing endpoints at https://jsonplaceholder.typicode.com/ , do work with your code.
I'd like to write a feature like this:
Scenario: new Singleton create
When a new, unmatchable identity is received
Then a new tin record should be created
And a new bronze record should be created
And a new gold record should be created
which would tie to steps like this:
defineSupportCode(function ({ Before, Given, Then, When }) {
var expect = require('chai').expect;
var chanceGenerator = require('./helpers/chanceGenerator')
var request = require('./helpers/requestGenerator')
let identMap;
// reset identMap before each scenario
Before(function () {
identMap = [];
});
// should generate a valid identity
// persist it in a local variable so it can be tested in later steps
// and persist to the db via public endpoint
When('a new, unmatchable identity is received', function (callback) {
identMap.push(chanceGenerator.identity());
request.pubPostIdentity(identMap[identMap.length-1], callback);
});
// use the local variable to retrieve Tin that was persisted
// validate the tin persisted all the props that it should have
Then('a new tin record should be created', function (callback) {
request.pubGetIdentity(identMap[identMap.length-1], callback);
// var self = this;
// request.pubGetIdentity(identMap[identMap.length-1], callback, () => {
// console.log('never gets here...');
// self.callback();
// callback();
// });
// request.pubGetIdentity(identMap[identMap.length-1], (callback) => {
// console.log('never gets here...');
// self.callback();
// callback();
// });
});
The issue that I'm having is that I can't do anything in the Then callback. That is where I'd like to be able to verify the response has the right data.
Here are relevant excerpts from the helper files:
var pubPostIdentity = function (ident, callback) {
console.log('pubIdentity');
var options = {
method: 'POST',
url: 'http://cucumber.utu.ai:4020/identity/' + ident.platform + '/' + ident.platformId,
headers: {
'X-Consumer-Custom-Id': ident.botId + '_' + ident.botId
},
body: JSON.stringify(ident)
};
console.log('ident: ', ident);
request(options, (err, response, body) => {
if (err) {
console.log('pubPostIdentity: ', err);
callback(err);
}
console.log('pubPostIdentity: ', response.statusCode);
callback();
});
}
// accept an identity and retrieve from staging via identity public endpoint
var pubGetIdentity = function (ident, callback) {
console.log('pubGetIdentity');
var options = {
method: 'GET',
url: 'http://cucumber.utu.ai:4020/identity/' + ident.platform + '/' + ident.platformId,
headers: {
'X-Consumer-Custom-Id': ident.botId + '_' + ident.botId
}
};
request(options, (err, response) => {
if (err) {
console.log('pubGetIdentity: ', err);
callback(err);
}
console.log('pubGetIdentity: ', response.body);
callback();
});
}
Something that we are considering as an option is to re-write the feature to fit a different step definition structure. If we re-wrote the feature like this:
Scenario: new Singleton create
When a new, unmatchable 'TIN_RECORD' is received
Then the Identity Record should be created successfully
When the Identity Record is retreived for 'tin'
Then a new 'tin' should be created
When the Identity Record is retreived for 'bronze'
Then a new 'bronze' should be created
When the Identity Record is retreived for 'gold'
Then a new 'gold' should be created
I believe it bypasses the instep callback issue we are wrestling with, but I really hate the breakdown of the feature. It makes the feature less readable and comprehensible to the business.
So... my question, the summary feature presented first, is it written wrong? Am I trying to get step definitions to do something that they shouldn't? Or is my lack of Js skills shining bright, and this should be very doable, I'm just screwing up the callbacks?
Firstly, I'd say your rewritten feature is wrong. You should never go back in the progression Given, When, Then. You are going back from the Then to the When, which is wrong.
Given is used for setting up preconditions. When is used for the actual test. Then is used for the assertions. Each scenario should be a single test, so should have very few When clauses. If you want, you can use Scenario Outlines to mix several very similar tests together.
In this case, is recommend to take it back to first principles and see if that works. Then build up slowly to get out working.
I suspect in this case that the problem is in some exception being thrown that isn't handled. You could try rewriting it to use promises instead, which will then be rejected on error. That gives better error reporting.
I call a method for deleting family from server/publicationMehods like this:
deletedFamily(family) {
if (Meteor.user().roles[0] == "admin") {
var myUsers = Meteor.users.find({"profile.family_id": family._id}).fetch();
for (var i = 0; i < myUsers.length; i++) {
UsersDeleted.insert(myUsers[i]);
Meteor.users.remove(myUsers[i]);
}
var myGuests= Guests.find({"profile.family_id": family._id}).fetch();
for (var i = 0; i < myGuests.length; i++) {
GuestsDeleted.insert(myGuests[i]);
Guests.remove(myGuests[i]);
}
FamiliesDeleted.insert(family);
Families.remove(family);
}
}
I want to handle exception and catch it if any errors happend and in frond-end show the result. I know there is not any transaction in Meteor. But I need to show the result to user at least.
In Meteor, if you want to return an error to a user from a Meteor method then you throw an exception, but it must be a Meteor.Error exception object in order to send it back to the client.
On the client side, when you call a Meteor method on the server, you provide a callback function that receives an error and result. If you wish to display an error to the user then whatever Meteor.Error exception object that was thrown in the method will be in the error callback argument.
Here is an example. First let's look at the meteor method throwing an exception.
Meteor.methods({
deletedFamily: function(family) {
//... your logic here...
if (somethingWentWrong) {
throw new Meteor.Error("logged-out", "The user must be logged in to delete a family.");
} else {
return // something
}
},
});
On the client, you would call the method like this and if an error was thrown it will be in the error object.
// on the client
Meteor.call("deletedFamily", function (error, result) {
// identify the error
if (error && error.error === "logged-out") {
// show a nice error message
Session.set("errorMessage", "Please log in to delete a family.");
}
//...continue on with your logic...
});
If you need to pass along an exception generated by something else (mongodb for example), then just use try/catch blocks and pass a Meteor.Error when needed. Here is an example.
Meteor.methods({
deletedFamily: function(family) {
//... your logic here...
try {
// Mongodb insert or update
} catch(e) {
if (e instanceof WriteError && e.code === '11000') {
throw new Meteor.Error("duplicate-error", "The family already exists.");
}
}
},
});
You can use throw/catch.
Read the following document:
Throw
Something about my use of chrome.hid.send seems to be leaving the bus in a bad state. I consistently can NOT get my second usage of the API call to work. Sometimes, it will also fail on the first usage. WITH THE EXACT SAME CODE, I can come back and try a while later (maybe 10min) and the first send will work.
The device I'm working with does not return a response to all messages sent to it. The test message for example, is just a dummy message that is ignored by the device. I've tested this both on a mac and a PC. My call stack depth is 2 at this point in my application (literally first one is kicked off by a button click and then a setTimeout calls the same method 5s later).
I've testing sending buffers of length 64Bytes as well as 58Bytes. The properties from the HidDeviceInfo object read "maxInputReportSize":64,"maxOutputReportSize":64
Params on first usage:
Params on second usage:
I really can't identify how I'm using the API incorrectly. When messages do succeed, I can see them on the device side.
// Transmits the given data
//
// #param[in] outData, The data to send as an ArrayBuffer
// #param[in] onTxCompleted, The method called on completion of the outgoing transfer. The return
// code is passed as a string.
// #param[in] onRxCompleted, The method called on completion of the incoming transfer. The return
// code is passed as a string along with the response as an ArrayBuffer.
send: function(outData, onTxCompleted, onRxCompleted) {
if (-1 === connection_) {
console.log("Attempted to send data with no device connected.");
return;
}
if (0 == outData.byteLength) {
console.log("Attempted to send nothing.");
return;
}
if (COMMS.receiving) {
console.log("Waiting for a response to a previous message. Aborting.");
return;
}
if (COMMS.transmitting) {
console.log("Waiting for a previous message to finish sending. Aborting.");
return;
}
COMMS.transmitting = true;
var dummyUint8Array = new Uint8Array(outData);
chrome.hid.send(connection_, REPORT_ID, outData, function() {
COMMS.transmitting = false;
if (onTxCompleted) {
onTxCompleted(chrome.runtime.lastError ? chrome.runtime.lastError.message : '');
}
if (chrome.runtime.lastError) {
console.log('Error in COMMS.send: ' + chrome.runtime.lastError.message);
return;
}
// Register a response handler if one is expected
if (onRxCompleted) {
COMMS.receiving = true;
chrome.hid.receive(connection_, function(reportId, inData) {
COMMS.receiving = false;
onRxCompleted(chrome.runtime.lastError ? chrome.runtime.lastError.message : '', inData);
});
}
});
}
// Example usage
var testMessage = new Uint8Array(58);
var testTransmission = function() {
message[0] = 123;
COMMS.send(message.buffer, null, null);
setTimeout(testTransmission, 5000);
};
testTranmission();
The issue is that Windows requires buffers to be the full report size expected by the device. I have filed a bug against Chromium to track adding a workaround or at least a better error message to pinpoint the problem.
In general you can get more detailed error messages from the chrome.hid API by enabling verbose logging with the --enable-logging --v=1 command line options. Full documentation of Chrome logging is here.
My javascript:
var params = {};
params.selectedCurrency = 'USD';
params.orderIdForTax = '500001';
var xhrArgs1 = {
url : 'UpdateCurrencyCmd',
handleAs : 'text',
content : params,
preventCache:false,
load:function(data){
alert('success!');
},
error: function(error){
alert(error);
//the alert says 'SyntaxError: syntax error'
},
timeout:100000
};
dojo.xhrPost(xhrArgs1);
I tried debugging with firebug, i do get the appropriate response (i think). Here it is;
/*
{
"orderIdForTax": ["500001"],
"selectedCurrency": ["USD"]
}
*/
The comments /* and */ are somehow embedded automatically cuz the url im hitting with xhrPost is actually a command class on ibm's websphere commerce environment. Can anyone tell me what am i doing wrong here?
Server code
public void performExecute() throws ECException {
try{
super.performExecute();
double taxTotal;
System.out.println("Updating currency in UpdateCurrencyCmd...");
GlobalizationContext cntxt = (GlobalizationContext) getCommandContext().getContext(GlobalizationContext.CONTEXT_NAME);
if(requestProperties.containsKey("selectedCurrency"))
selectedCurrency = requestProperties.getString("selectedCurrency");
else
selectedCurrency = cntxt.getCurrency();
if(requestProperties.containsKey("orderIdForTax"))
orderId = requestProperties.getString("orderIdForTax");
OrderAccessBean orderBean = new OrderAccessBean();
cntxt.setCurrency(selectedCurrency.toUpperCase());
orderBean.setInitKey_orderId(orderId);
orderBean.refreshCopyHelper();
orderBean.setCurrency(selectedCurrency.toUpperCase());
orderBean.commitCopyHelper();
TypedProperty rspProp = new TypedProperty();
rspProp.put(ECConstants.EC_VIEWTASKNAME, "AjaxActionResponse");
setResponseProperties(rspProp);
}catch(Exception e){
System.out.println("Error: " + e.getMessage() );
}
}
The problem was with my client side code, weirdly.
load:function(data){
data = data.replace("/*", "");
data = data.replace("*/", "");
var obj = eval('(' + data + ')');
alert('Success');
}
Its weird but this worked. Lol.
I guess the problem is with coment-filtering option of handle as method.
The response should be comment filered as below.
See tha AjaxActionResponse.jsp (WCS)
vailable Handlers
There are several pre-defined contentHandlers available to use. The value represents the key in the handlers map.
text (default) - Simply returns the response text
json - Converts response text into a JSON object
xml - Returns a XML document
javascript - Evaluates the response text
json-comment-filtered - A (arguably unsafe) handler to preventing JavaScript hijacking
json-comment-optional - A handler which detects the presence of a filtered response and toggles between json or json-comment-filtered appropriately.
Examples