I'm using koa-router to define an REST api.
I have a route to allow clients to patch data, for this I expect to only response with :-
OK - data patched without error
or
NOT OK - error occurred.
router.patch('/api/data', function *(next) {
if (_.has(this.query, 'id')) {
// do data patch
this.status = 200;
this.body = yield {status: 200, body: 'OK'};
} else {
this.status = 304;
this.body = yield {status: 304, body: 'expecting id};
}
});
Is there a more standard way than the above?
Don't yield a simple object. Only yield an object when one or more of its properties is being assigned via a yieldable (promise, thunk, generator...).
Consider returning the updated item to prevent the need for additional api calls.
this.throw() is what I use.
router.patch('/api/data', function *(next) {
if (_.has(this.query, 'id')) {
this.status = 200;
// don't yield...
this.body = {status: 200, body: 'OK'};
// consider returning the updated item to prevent the need to additional
// api calls
this.body = yield model.update(this.query.id, ...)
} else {
this.throw(304, 'expecting id', {custom: 'properties here'});
}
});
To mildly improve upon #James Moore's answer, you can also use this.assert(expression, [status], [message]) to short-circuit a route early if expression is not truthy.
I've converted their code to demonstrate:
router.patch('/api/data', function*(next) {
this.assert(_.has(this.query, 'id'), 304, JSON.stringify({ status: 304, body: 'expecting id' })));
this.body = JSON.stringify({ status: 200, body: 'OK' });
});
Related
I'm following the docs in zapier regarding the callbackUrl https://platform.zapier.com/cli_docs/docs#zgeneratecallbackurl however cannot seem to get the performResume step to be run. The zap I'm creating based on this integration also does not seem to wait for the callbackUrl to be hit.
const createScreenshot = (z, bundle) => {
const callbackUrl = z.generateCallbackUrl();
const promise = z.request({
url: 'https://myapi.com/v1/render',
method: 'POST',
params: {},
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: {
...bundle.inputData,
webhook_url: callbackUrl
},
removeMissingValuesFrom: {},
});
z.console.log("Returning from perform / createScreenshot");
return promise.then((response) => ({ ...response.data, waiting_for: "performResume" }));
const onScreenshotFinished = (z, bundle) => {
z.console.log("In performResume / onScreenshotFinished", bundle.cleanedRequest);
const responseBody = bundle.cleanedRequest;
let screenshotUrl;
if (responseBody.event === "render.succeeded") {
z.console.log("render was processed successfully", responseBody);
screenshotUrl = responseBody.result.renderUrl;
return { screenshotUrl, ...responseBody };
}
z.console.log("render was not processed", responseBody);
throw z.errors.Error("Screenshot was not successful");
}
module.exports = {
operation: {
perform: createScreenshot,
performResume: onScreenshotFinished,
...
}
}
We talked through this question (and its solution) on GitHub (zapier/zapier-platform#398), but to summarize for SO readers:
When setting up a resumable Zap, the editor uses the sample to populate the data in the callback. No actual waiting happens during the setup process. Once the zap is live, it works like normal.
So, to implement:
perform should return sample data that matches the data the "resume" webhook sends
performSubscribe can read that data and operate normally
See the GH issue for more info.
I am writing some code to test action in Zapier's CLI. I want to add one more condition here something like response.status == 200 or 201; to check API response code is 200 or 201.
How can I do it? when I log response it gives me whole JSON object that
API is returning.
describe("contact create", () => {
it("should create a contact", done => {
const bundle = {
inputData: {
firstName: "Test",
lastName: "Contact",
email: "Contact#test.com",
mobileNumber: "+12125551234",
type: "contact"
}
};
appTester(App.creates.contact.operation.perform, bundle)
.then(response => {
// Need one more condition whether response status is 200 or 201.
response.should.not.be.an.Array();
response.should.have.property('id');
done();
})
.catch(done);
});
});
appTester returns the result of the perform method, which isn't an API response. It's the data that's passed back into Zapier.
The best thing to do is to add a line like this to your perform:
// after your `z.request`
if (!(response.status === 200 || response.status === 201)) {
throw new Error('need a 200/201 response')
}
That will ensure you're getting exactly the response you want. But, more likely, you can add a response.throwForStatus() to make sure it's not an error code and not worry if it's exactly 200/201.
In the following code, I am unable to understand why validateOpt might return value JsSuccess(None) instead of JsError
def getQuestion = silhouette.UserAwareAction.async{
implicit request => {
val body: AnyContent = request.body
val jsonBodyOption: Option[JsValue] = body.asJson
jsonBodyOption.map((jsonBody:JsValue) => { //body is json
val personJsonJsResultOption = jsonBody.validateOpt[Person]//check that json structure is correct
personJsonJsResultOption match {
case personSuccessOption: JsSuccess[Option[Person]] => { //json is correct
val personOption = personSuccessOption.getOrElse(None) //why would getOrElse return None??
personOption match {
case Some(person) => {
... }
case None =>{ //I am not sure when this will be triggered.
...
}
}
}
}
case e: JsError => {
...
}
}
}
})
.getOrElse(//body is not json
...)
}
}
validateOpt by design considers success to be not only when body provides actual Person but also when Person is null or not provided. Note how documentation explains why JsSuccess(None) is returned:
/**
* If this result contains `JsNull` or is undefined, returns `JsSuccess(None)`.
* Otherwise returns the result of validating as an `A` and wrapping the result in a `Some`.
*/
def validateOpt[A](implicit rds: Reads[A]): JsResult[Option[A]]
Seems like your requirement is that Person must always be provided to be considered successful, so in this case validate should be used instead of validateOpt.
I wanted to trigger a Jenkins job through the Jenkins API
we can do that by hitting the URL similar to "JENKINS_URL/job/JOBNAME/build"
I want to hit the API via Google action/Dialogflow.
Is there any tutorial available to do a similar process that I want to achieve?
You should take a look at the Dialogflow quotes sample, which shows how to make external API calls:
// Retrieve data from the external API.
app.intent('Default Welcome Intent', (conv) => {
// Note: Moving this fetch call outside of the app intent callback will
// cause it to become a global var (i.e. it's value will be cached across
// function executions).
return fetch(URL)
.then((response) => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
} else {
return response.json();
}
})
.then((json) => {
// Grab random quote data from JSON.
const data = json.data[Math.floor(Math.random() * json.data.length)];
const randomQuote =
data.quotes[Math.floor(Math.random() * data.quotes.length)];
conv.close(new SimpleResponse({
text: json.info,
speech: `${data.author}, from Google ` +
`Developer Relations once said... ${randomQuote}`,
}));
if (conv.screen) {
conv.close(new BasicCard({
text: randomQuote,
title: `${data.author} once said...`,
image: new Image({
url: BACKGROUND_IMAGE,
alt: 'DevRel Quote',
}),
}));
}
});
});
The following Node.js code:
var request = require('request');
var getLibs = function() {
var options = { packages: ['example1', 'example2', 'example3'], os: 'linux', pack_type: 'npm' }
request({url:'http://localhost:3000/package', qs:options},
function (error , response, body) {
if (! error && response.statusCode == 200) {
console.log(body);
} else if (error) {
console.log(error);
} else{
console.log(response.statusCode);
}
});
}();
sends the following http GET request query that is received by like this:
{"packages"=>{"0"=>"example1", "1"=>"example2", "2"=>"example3"}, "os"=>"linux", "pack_type"=>"npm"}
How can I optimize this request to be received like this:
{"packages"=>["example1", "example2", "example3"], "os"=>"linux", "pack_type"=>"npm"}
Note. The REST API is built in Ruby on Rails
If the array need to be received as it is, you can set useQuerystring as true:
UPDATE: list key in the following code example has been changed to 'list[]', so that OP's ruby backend can successfully parse the array.
Here is example code:
const request = require('request');
let data = {
'name': 'John',
'list[]': ['XXX', 'YYY', 'ZZZ']
};
request({
url: 'https://requestb.in/1fg1v0i1',
qs: data,
useQuerystring: true
}, function(err, res, body) {
// ...
});
In this way, when the HTTP GET request is sent, the query parameters would be:
?name=John&list[]=XXX&list[]=YYY&list[]=ZZZ
and the list field would be parsed as ['XXX', 'YYY', 'ZZZ']
Without useQuerystring (default value as false), the query parameters would be:
?name=John&list[][0]=XXX&list[][1]=YYY&list[][2]=ZZZ
I finally found a fix. I used 'qs' to stringify 'options' with {arrayFormat : 'brackets'} and then concatinated to url ended with '?' as follows:
var request = require('request');
var qs1 = require('qs');
var getLibs = function() {
var options = qs1.stringify({
packages: ['example1', 'example2', 'example3'],
os: 'linux',
pack_type: 'npm'
},{
arrayFormat : 'brackets'
});
request({url:'http://localhost:3000/package?' + options},
function (error , response, body) {
if (! error && response.statusCode == 200) {
console.log(body);
} else if (error) {
console.log(error);
} else{
console.log(response.statusCode);
}
});
}();
Note: I tried to avoid concatenation to url, but all responses had code 400
This problem can be solved using Request library itself.
Request internally uses qs.stringify. You can pass q option to request which it will use to parse array params.
You don't need to append to url which leaves reader in question why that would have been done.
Reference: https://github.com/request/request#requestoptions-callback
const options = {
method: 'GET',
uri: 'http://localhost:3000/package',
qs: {
packages: ['example1', 'example2', 'example3'],
os: 'linux',
pack_type: 'npm'
},
qsStringifyOptions: {
arrayFormat: 'repeat' // You could use one of indices|brackets|repeat
},
json: true
};