My API doesn't return any response in body on POST,PATCH, etc for some resources.
How are we supposed to handle this case in data provider ?
I tried to return an empty data object but unfortunately I get the error :
"The response to 'CREATE' must be like { data: ... }, but the received response does not have a 'data' key. The dataProvider is probably wrong for 'CREATE'."
I don't know how your dataProvider looks like or what the response of your API for POST/PUT requests is, but the problem is probably located in your dataProvider's convertHTTPResponseToREST as this is responsible for handling your API's responses.
Try the following:
case CREATE:
return { data: { ...params.data, id: json.id } };
If this doesn't work either, then try this one:
case CREATE:
return { data: json };
Related
I am very new to this, so please bear with me-- I have currently have an operational google apps script on the backend of a google sheet that is generated from Google Form answers. I am essentially setting up a ticket form in google forms that will trigger the data in the corresponding sheet to be sent via api call to our ticketing system. It works great, but I am trying to optimize it currently. The goal is to take the json response I get using:
Logger.log(response.getContentText());
which provides me the following info:
Aug 9, 2020, 11:44:40 AM Info {"_url":"https://testticketingsystem.com/REST/2.0/ticket/123456","type":"ticket","id":"123456"}
and send another API call to send data to that new ticket.
Here's a code snippet:
var payload = {
"Subject": String(su),
"Content": String(as),
"Requestor": String(em),
"Queue": String(qu),
"CustomFields": {"CustomField1": String(vn), "CustomField2": String(vb), "CustomField3":
String(vg), "CustomField4": String(av), "CustomField5": String(ov), "CustomField6":
String(sd)}
}
var options = {
'method': 'post',
"contentType" : "application/json",
'payload': JSON.stringify(payload),
'muteHttpExceptions': true
}
var url = "https://testticketingsystem.com/REST/2.0/ticket?token=****************";
var response = UrlFetchApp.fetch(url,options);
Logger.log(response.getContentText());
} catch (error) {
Logger.log(error.toString());
}
}
After the ticket is created, how do I script the use of that ID number as a variable into my next api call?
Thank you!
UrlFetchApp.fetch returns a HTTPResponse, and if you expect JSON then you should be able to just use JSON.parse() to create an object from the text. (The JSON object is a standard JavaScript global object like Math; it is not Google Apps Script specific.)
If all goes well, you should just be able to use
var response = UrlFetchApp.fetch(url,options);
var data = JSON.parse(response.getContentText());
var id = data.id;
and then use that id for your next fetch().
Notes
If your literal response is indeed
Aug 9, 2020, 11:44:40 AM Info {"_url":"https://testticketingsystem.com/REST/2.0/ticket/123456","type":"ticket","id":"123456"}
you will run into trouble as everything until the { is invalid JSON (use a linter if you need to check yourself). But I'm assuming that was added by the console when you logged JSON, and not in the actual response itself.
JSON.parse() throws an error with invalid JSON, so you can use try/catch if needed.
You can also check the headers before you try to JSON.parse().
Here's an example that checks and handles issues, should they arise.
var type = response.getHeaders()["Content-Type"];
var text = response.getContentText();
if (type === "application/json") {
try {
var data = JSON.parse(text);
} catch (error) {
return Logger.log("Invalid JSON: " + response.getContentText(text));
}
} else {
return Logger.log("expected JSON, but got response of type: " + type);
}
// if we get to this line, data is an object we can use
I'm trying to insert data to my Wix collection using the API. I'm using a POST function and am posting a JSON document. It's supposed to simply add a new row to a database containing 1 value.
Here is the http-functions.js which I can trigger without issues (it's more or less a copy of the example from the documentation):
import {created, serverError} from 'wix-http-functions';
import wixData from 'wix-data';
export function post_peopleCount(request) {
let options = {
"headers": {
"Content-Type": "application/json"
}
};
// get the request body
return request.body.text()
.then( (body) => {
// insert the item in a collection
return wixData.insert("NumberOfPeopleDB", JSON.parse(body));
} )
.then( (results) => {
options.body = {
"inserted": results
};
return created(options);
} )
// something went wrong
.catch( (error) => {
options.body = {
"error": error
};
return serverError(options);
} );
}
The database looks like this:
and the JSON I am posting looks like this:
But the Error I am getting is:
But the permissions I have set for the collection is:
Do you know why I might be getting that "WD_PERMISSION_DENIED" and 500 Server Error? (The data does not get entered.)
Thanks!
My friend, its not related to creating a collection from scratch it is because of the permissions set to this collection once created. You fixed that by not noticing :).
Permission need to be given in order to perform such queries.
It turns out, if I create a new collection (= table) from scratch, it works. I also changed the field value in the collection to people, maybe value is a reserved term. Nevertheless, now it seems to work:
So if you run into the same problem: Try recreating the collections from scratch.
The critical thing for me which has not been mentioned yet is that you need to set the collection to have form-like permissions so that anyone has permission to submit data to the collection.
As it will become quickly apparent, I have never seriously written a webserver before
Here is the current scenario:
Clients make requests to webserver, asking to save some data
Server looks at payload, and makes 2 checks
a. Is this client banned from saving data?
b. Does the payload of this data pass a language filter?
Server responds with success, or one of those 2 errors
My endpoint is written with Express in TypeScript
class ChatRequest {
public uid: string;
public message: string;
}
export const register = (app: express.Application, deps: dependencies.IDependencies) => {
app.post("/sendChat", (req: express.Request, res: express.Response) => {
transformAndValidate(ChatRequest, req.body)
.then((sendGlobalChatRequest: SendGlobalChatRequest) => {
const payload = {
message: sendGlobalChatRequest.message,
uid: sendGlobalChatRequest.uid
};
//Check if uid is banned here
//Check if payload passes language filter here
//Save Payload here
res.sendStatus(200);
}, (err) => {
deps.logger.error(err);
res.sendStatus(503);
});
});
I have been using this article for reference:
https://hackernoon.com/the-request-sent-bad-data-whats-the-response-94088bd290a
But I think my conclusion is that they are discussing something slightly different.
So from my understanding, I can just make up HTTP codes...
so I could just do res.sendStatus(499); if the uid is banned, and maybe res.sendStatus(498); if the payload doesn't pass language filter
Then my client can just read the Int statusCode and quickly determine the failure.
But even though I think I can do that, and it would work, it doesn't seem right?
Should I instead be using a standard HTTP Response Code? https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
And then add in the body of the response, a String or something that my client can parse to determine the error?
The String parsing seems way harder to maintain, but technically seems more "legal" if that makes sense?
What is the best way for me to have a client determine the type of server-side error?
I decided to return 400 with a JSON mapping errors to bools
if (isProfane(message)) {
res.status(400).json({messageContentBlocked: true});
}
In this way the client can receive multiple errors for the request at once, and it's more explicit
And in case anyone is googling around, I am using RxSwift/RxCocoa
Here is how I handle the error on the client:
extension Error {
var chatMessageBlockedURLError: Bool {
guard let rxCocoaURLError = self as? RxCocoaURLError else {return false}
switch rxCocoaURLError {
case let .httpRequestFailed(response, data):
guard response.statusCode == 400, let data = data else {return false}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
guard let errors = try? decoder.decode([String:Bool].self, from: data) else {return false}
return errors["messageContentBlocked"] == true
default:
return false
}
}
}
JavaScript
For example, I have the following JavaScript code (Dojo 1.6 is already loaded):
dojo.require("dojo.io.script")
// PART I
var jsonpArgs = {
url: "http://myapp.appspot.com/query",
content: {
id: "1234",
name: "Juan",
start_date: "2000-01-01",
callback: "recover"
}
};
// PART II
dojo.io.script.get(jsonpArgs).then(function(data) {
console.log(data);
});
// PART III
function recover(data) {
console.log(data);
}
Direct query from browser
I understand that my server will receive the query as though I typed the following into the address bar:
http://myapp.appspot.com/query?id=1234&name=Juan&start_date=2000-01-01&callback=recover
Expected response
If I directly queried my server using the browser address bar, I'll receive, in MIME type application/json and plaintext rendered in browser, something like this:
recover(
{
id: 1234,
name: Juan,
data: [
["2000-01-01", 1234],
["2000-01-02", 5678]
]
}
);
Problem
Now, looking back at Part II of the JavaScript, I'd execute the JSONP request with dojo.io.script.get(jsonpArgs). This returns a Deferred object, which I can take advantage of by chaining .then after it. Note that I defined the handler for the .then event to output that captured data to the console.
However, all I get in the console is an Event. I tried to search its data tree, but I could not find the data I expected.
Question
Where is the response for a JSONP request stored? How do I find it?
My server (which I control) only outputs a plaintext rendering of the data requested, wrapped in the callback function (here specified as recover), and specifies a application/json MIME type. Is there anything else I need to set up on my server, so that the response data is captured by the Deferred object?
Attempted solution
I can actually recover the response by defining the callback function (in this case recover in Part III of the JavaScript). However, in the Dojo tutorials, they just recovered the data using the Deferred (and .then) framework. How do I do it using Dojo Deferreds?
Update (using the Twitter example from Dojo tutorial)
Take for example this script from the Dojo tutorial, Getting Jiggy With JSONP. I edited it to log data to the console.
dojo.require("dojo.io.script");
dojo.io.script.get({
url: "http://search.twitter.com/search.json",
callbackParamName: "callback",
content: {q: "#dojo"}
}).then(function(data){
//we're only interested in data.results, so strip it off and return it
console.log(data); // I get an Object, not an Event, but no Twitter data when browsing the results property
console.log(data.results) // I get an array of Objects
return data.results;
});
For console.log(data), I get an Object, not an Event as illustrated by my case. Since the example implies that the data resides in data.results, I also try to browse this tree, but I don't see my expected data from Twitter. I'm at a loss.
For console.log(data.results), I get an array of Objects. If I query Twitter directly, this is what I'd get in plaintext. Each Object contains the usual tweet meta-data like username, time, user portrait, and the tweet itself. Easy enough.
This one hits me right on the head. The handler for the .then chain, an anonymous function, receives only one argument data. But why is it that the results property in console.log(data) and the returned object I get from console.log(data.results) are different?
I got it.
Manual callback implementation
function recover(data) {
console.log(data);
}
var jsonpArgs = {
url: "http://myapp.appspot.com/query",
content: {
id: "1234",
name: "Juan",
start_date: "2000-01-01",
callback: "recover"
};
dojo.io.script.get(jsonpArgs);
This is the request that my server will receive:
http://myapp.appspot.com/query?id=1234&name=Juan&start_date=2000-01-01&callback=recover
In this case, I'll expect the following output from my server:
recover({
id: 1234,
name: Juan,
data: [
["2000-01-01", 1234],
["2000-01-02", 5678]
]
});
Three things to note:
Server will expect callback in the query URL string. callback is implemented as a property of jsonpArgs.
Because I specified callback=recover, my server will attach recover( + the_data_I_need + ), returns the whole string to the browser, and browser will execute recover(the_data_I_need). This means...
That I'll have to define, for example, function recover(one_argument_only) {doAnythingYouWantWith(one_argument_only)}
The problem with this approach is that I cannot take advantage of Deferred chaining using .then. For example:
dojo.io.script.get(jsonpArgs).then(function(response_from_server) {
console.log(response_from_server);
})
This will give me an Event, with no trace of the expected response at all.
Taking advantage of Dojo's implementation of JSONP requests
var jsonpArgs = {
url: "http://myapp.appspot.com/query",
callbackParamName: "callback",
content: {
id: "1234",
name: "Juan",
start_date: "2000-01-01"
};
dojo.io.script.get(jsonpArgs);
This is the request that my server will receive:
http://myapp.appspot.com/query?id=1234&name=Juan&start_date=2000-01-01&callback=some_function_name_generated_by_dojo
In this case, I'll expect the following output from my server:
some_function_name_generated_by_dojo({
id: 1234,
name: Juan,
data: [
["2000-01-01", 1234],
["2000-01-02", 5678]
]
});
Things to note:
Note the property of jsonpArgs, callbackParamName. The value of this property must be the name of the variable (in the query URL string) expected by the server. If my server expects callbackfoo, then callbackParamName: "callbackfoo". In my case, my server expects the name callback, therefore callbackParamName: "callback".
In the previous example, I specified in the query URL callback=recover and proceeded to implement function recover(...) {...}. This time, I do not need to worry about it. Dojo will insert its own preferred function callback=some_function_name_generated_by_dojo.
I imagine some_function_name_generated_by_dojo to be defined as:
Definition:
function some_function_name_generated_by_dojo(response_from_server) {
return response_from_server;
}
Of course the definition is not that simple, but the advantage of this approach is that I can take advantage of Dojo's Deferred framework. See the code below, which is identical to the previous example:
dojo.io.script.get(jsonpArgs).then(function(response_from_server) {
console.log(response_from_server);
})
This will give me the exact data I need:
{
id: 1234,
name: Juan,
data: [
["2000-01-01", 1234],
["2000-01-02", 5678]
]
}
(ExtJS 4.0.7)
I'm using Model.save() to PUT an update to a server. Everything works fine and the server returns a simple JSON response {success: true} (HTTP status 200). Model.save() throws the following error, however:
Uncaught TypeError: Cannot read property 'data' of undefined
Here's where this is happening in the ExtJS code (src/data/Model.js):
save: function(options) {
...
callback = function(operation) {
if (operation.wasSuccessful()) {
record = operation.getRecords()[0]; <-- getRecords() return an empty array
me.set(record.data); <-- record is undefined, so .data causes error
...
}
I've figured out this is happening because Model.save() expects the server to respond with JSON for the entire object that was just updated (or created).
Does anyone know of a clever way to make Model.save() work when the server responds with a simple success message?
I was able to come up with a work-around by using a custom proxy for the model, and overriding the update function:
Ext.define('kpc.util.CustomRestProxy', {
extend: 'Ext.data.proxy.Rest',
alias: 'proxy.kpc.util.CustomRestProxy',
type: 'rest',
reader : {
root: 'data',
type: 'json',
messageProperty: 'message'
},
// Model.save() will call this function, passing in its own callback
update: function(operation, callback, scope) {
// Wrap the callback from Model.save() with our own logic
var mycallback = function(oper) {
// Delete the resultSet from the operation before letting
// Model.save's callback use it; this will
oper.resultSet = undefined;
callback(op);
};
return this.doRequest(operation, mycallback, scope);
}
});
In a nutshell, when my proxy is asked to do an update it makes sure operation.resultSet == undefined. This changes the return value for operation.getRecords() (which you can see in the code sample from my question). Here's what that function looks like (src/data/Operation.js):
getRecords: function() {
var resultSet = this.getResultSet();
return (resultSet === undefined ? this.records : resultSet.records);
}
By ensuring that resultSet == undefined, operation.getRecords returns the model's current data instead of the empty result set (since the server isn't returning a result, only a simple success message). So when the callback defined in save() runs, the model sets its data to its current data.
I investigate this problem and found truly simple answer. Your result must be like this:
{
success: true,
items: { id: '', otherOpt: '' }
}
And items property MUST be equal Model->Reader->root property (children in tree for example).
If you want to use items instead children you can use defaultRootProperty property in Store and configure your nested collections as you want.
PS
Object in items property must be fully defined because it replaces actual record in store.