What is the best response to give, if a number of objects are sent to my web api controller, to be inserted into my database, where some may be successful, and some may fail? A normal HTTP response I don't think will suffice - would it be better to find some way of returning a JSON string of what has been successful, and what has not? If so, how would I do that?
My Post controller is shown below.
Thanks for any help,
Mark
public HttpResponseMessage PostBooking(Booking[] bookings)
{
if (ModelState.IsValid)
{
foreach (var booking in bookings)
{
// check if there are any bookings already with this HID and RID...
var checkbooking = db.Bookings.Where(h => h.HID == booking.HID && h.RID == booking.RID).ToList();
// If so, return a response of conflict
if (checkbooking.Count != 0 || checkbooking.Any())
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.Conflict));
}
else
{
// If not add the booking to the database and return a response of Created
db.Bookings.Add(booking);
db.SaveChanges();
}
}
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created);
return response;
}
else
{
// Model is not valid, so return BadRquest
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
You could return a JSON list containing the ids of the objects that failed to be inserted:
{
"failedIds": [
4,
7,
9
]
}
500 HTTP response status code also seems appropriate as the request didn't complete successfully.
You could even bring that a step further and provide an explanation why insertion failed for each particular id:
{
"failed": [
{
"id": 4,
"reason": "database unavailable"
},
{
"id": 7,
"reason": "network cable unplugged"
},
{
"id": 9,
"reason": "a thief is currently running away with our server"
}
]
}
Related
I am having an issue getting results back from my AppSync API via AWSAppSyncClient. I can run the query in the AWS AppSync console and get the complete results, however when I run the query from my client the portion of the results I am looking for returns an empty array.
I have tried slimming down the query to return less results, as I read at one point that dynamo will run a filter on the results being returned if you do not provide your own. I have also read this could have something to do with the partition keys used in the dynamoDB table, however AppSync provisioned that resource for me and handled the initial config. I am new to working with AppSync so I am sort of drawing a blank on where to even start looking for the issue because there is not even an error message.
The Query I am running
export const getUserConversations = `query getUser($id: ID!) {
getUser(id: $id) {
id
conversations {
items {
conversation{
id
associated{
items{
convoLinkUserId
}
}
}
}
}
}
}
`;
Call being made in a redux actions file
export const getUserConvos = (id) => async dispatch => {
AppSyncClient.query({
query: gql(getUserConversations),
variables: {
id: id
}
}).then(res => {
console.log("RES FROM CONVO QUERY", res)
})
}
This is the response I am getting in the browser
Notice conversations.items returns an empty array.
getUser:
conversations:
items: []
__typename: "ModelConvoLinkConnection"
__proto__: Object
id: "HIDDEN_ID"
__typename: "User"
__proto__: Object
__proto__: Object
However if i run the exact same query in the playground on the AppSync console I get this...
{
"data": {
"getUser": {
"id": "HIDDEN_ID",
"conversations": {
"items": [
{
"conversation": {
"id": "HIDDEN_ID",
"associated": {
"items": [
{
"convoLinkUserId": "HIDDEN_ID"
},
{
"convoLinkUserId": "HIDDEN_ID"
}
]
}
}
},
{
"conversation": {
"id": "HIDDEN_ID",
"associated": {
"items": [
{
"convoLinkUserId": "HIDDEN_ID"
},
{
"convoLinkUserId": "HIDDEN_ID"
}
]
}
}
}
]
}
}
}
}
*HIDDEN_ID is a placeholder
I know that the objects are in my DB, however if i run the query via my react application I get nothing, and if I run it in the console on AWS I get another. I need to be able to have access to these conversations via the client. What could be causing this?
I am writing an API that sends an firebase message (using the official FirebaseAdmin library) when requested to a android device. I got it working perfect in normal C#, but in ASP.NET core I always get a 404 not found exception.
Response status code does not indicate success: 404 (NotFound)
{
"error": {
"code": 404,
"message": "Requested entity was not found.",
"errors": [
{
"message": "Requested entity was not found.",
"domain": "global",
"reason": "notFound"
}
],
"status": "NOT_FOUND"
}
}
I run the following code at startup:
if (FirebaseApp.DefaultInstance == null)
{
FirebaseApp.Create(new AppOptions
{
Credential = GoogleCredential.FromFile($#"{env.WebRootPath}\app-5a821-firebase-adminsdk-pf36f-6f44114d87.json")
});
}
And this is the request that I made, very simple:
[HttpGet]
public async Task<ActionResult<IEnumerable<string>>> Get()
{
var message = new Message
{
Token = "dgY8UMXhEZ4:APA91bFnrZTGJKkCCBJHzbghvsvEaq-w-ee1XBAVqAaS-rsmR3Ald23rHGgpfdgVb09r97jDQBVSc6GtDHWtLHWAnn4Lm3EM_j-sh7cu-RaRSrfnk3X124v4co3Q9ID6TxFdGgv7OXWt",
Data = new Dictionary<string, string>
{
{"title", "test" }
}
};
try
{
var fcmResult = await FirebaseMessaging.DefaultInstance.SendAsync(message);
} catch (FirebaseException ex)
{
}
return new string[] { "value1", "value2" };
}
Github test project: https://github.com/kevingoos/FirebaseAdminTest
Solved problem: https://github.com/firebase/firebase-admin-dotnet/issues/73
This happens when the token is invalid/expired or does not belong to the same project as the credentials used to initialize the Admin SDK.
Recently I started developing a small application in Flutter. I have an issue with making a network request. I have tried the call in postman and there it work. But in Flutter I never managed to make it work, I have spent like 3 hours trying to understand what I am doing wrong.
Any help will be greatly appreciated.
#override
Future<String> login(common.LoginParameters loginParameters) async {
try {
final String loginURL = "https://test.example.eu/api/login";
LoginModel loginResult;
Map bodyParams = { "inlognaam" : loginParameters.username , "wachtwoord" : loginParameters.password, "code" : loginParameters.smsCode};
//await API call
http.Response httpResponse = await http.put( loginURL, body: json.encode(bodyParams));
if (httpResponse.statusCode == 200) {
// If server returns an OK response, parse the JSON
loginResult= LoginModel.fromJson(json.decode(httpResponse.body));
} else {
// If that response was not OK, throw an error.
throw Exception('Failed to load post');
}
// if logged in get token, Otherwise return error
if (loginResult.ingelogd) {
// read the token
saveToken(loginResult.response);
return "Ingelogd";
} else {
return loginResult.error;
}
}
on Exception catch(error) {
print("Todor " + error.toString());
return "Controleer uw internet verbinding en probeer opnieuw";
}
}
In Postman if I select Post request with body parameters
inlognaam : someUsername
wachtwoord : somePassword
code : someCode
Then I get a success response
I pass the parameters in the following way, maybe it can work for you:
var response = await http.post(
url,
headers:{ "Accept": "application/json" } ,
body: { "state": 1}, //key value
encoding: Encoding.getByName("utf-8")
);
Another thing, you say that in postman you make a post request, but in your code you have a put request, verify what is the correct method
I found a strange error while I developing system using Firebase with service url contains user data.
User data is below.
{
"uid": "kt9Hcp2FbYbBvvIeSHHa1RbvHcv2",
"displayName": "Anonymous 901",
"photoURL": null,
"email": null,
"emailVerified": false,
"identifierNumber": null,
"isAnonymous": true,
"providerData": [
],
"apiKey": "MyApiKeyString",
"appName": "MyAppName",
"authDomain": "my.auth.domain",
"stsTokenManager": {
"apiKey": "MyApiKeyString",
"refreshToken": "refreshTokenString",
"accessToken": "accessTokenString",
"expirationTime": 1532451863076
},
"redirectEventId": null
}
I encode the above anonymous user data and include it in the service url.
( http://myserviceurl?userdata=encodedUserData )
Inside the system receives that url, firebase creates a user object with that user data contained in the url.
The purpose of this url is to use specific user's information in any browser.
However, when I call that service url, sometimes system creates user object well, sometimes got error -
400 Bad request errors with
https://www.googleapis.com/identitytoolkit/v3/relyingparty/setAccountInfo?key=MyApiKeyString
And error data is below,
{
"error": {
"code": 400,
"message": "TOKEN_EXPIRED",
"errors": [
{
"message": "TOKEN_EXPIRED",
"domain": "global",
"reason": "invalid"
}
]
}
}
Few hours later it works well, I changed nothing though.
I could not find the exact error point, but I suspect error occurs while observing authentication state or before this step.
Here is code snipets
#bind
private makeUserLoadingPromise(): Promise<void> {
let unSubscribe: () => void;
return new Promise<void>((resolve, _reject) => {
const onInitialized = this.makeOnInitializedAuthStateChanged(resolve);
unSubscribe = this.auth.onAuthStateChanged(onInitialized);
}).then(() => {
unSubscribe();
this.auth.onAuthStateChanged(this.onAuthStateChanged);
});
}
#bind
private makeOnInitializedAuthStateChanged(resolve: () => void) {
return (user: firebase.User | null) => {
this.user = user;
resolve();
};
}
#bind
private onAuthStateChanged(user: firebase.User | null) {
this.user = user;
}
Or maybe it relates with expirationTime?
I couldn't find any hints about this situation.
Any advice would be appreciated.
It is not clear what you are doing, but it appears that you are using the API incorrectly and insecurely. The plain user object contains a refresh token that is indefinite. Passing it around via URL is a really bad idea.
First don't rely on internal implementations, it is subject to change.
To get the user's information on your backend, the right way to do it, is to get the user's ID token using officially supported API, eg user.getIdToken(), then pass it to your server.
On your server, you verify it via the Firebase Admin SDK: admin.auth().verifyIdToken(idToken). Then you know this is a real authenticated user. If you need the full user info, you can then look it up using the decoded user id in the token: admin.auth().getUser(decodedIdToken.sub).
What is the pattern for sending more details about errors to the client using gRPC?
For example, suppose I have a form for registering a user, that sends a message
message RegisterUser {
string email = 1;
string password = 2;
}
where the email has to be properly formatted and unique, and the password must be at least 8 characters long.
If I was writing a JSON API, I'd return a 400 error with the following body:
{
"errors": [{
"field": "email",
"message": "Email does not have proper format."
}, {
"field": "password",
"message": "Password must be at least 8 characters."
}],
}
and the client could provide nice error messages to the user (i.e. by highlighting the password field and specifically telling the user that there's something wrong with their input to it).
With gRPC is there a way to do something similar? It seems that in most client languages, an error results in an exception being thrown, with no way to grab the response.
For example, I'd like something like
message ValidationError {
string field = 1;
string message = 2;
}
message RegisterUserResponse {
repeated ValidationError validation_errors = 1;
...
}
or similar.
Include additional error details in the response Metadata. However, still make sure to provide a useful status code and message. In this case, you can add RegisterUserResponse to the Metadata.
In gRPC Java, that would look like:
Metadata.Key<RegisterUserResponse> REGISTER_USER_RESPONSE_KEY =
ProtoUtils.keyForProto(RegisterUserResponse.getDefaultInstance());
...
Metadata metadata = new Metadata();
metadata.put(REGISTER_USER_RESPONSE_KEY, registerUserResponse);
responseObserver.onError(
Status.INVALID_ARGUMENT.withDescription("Email or password malformed")
.asRuntimeException(metadata));
Another option is to use the google.rpc.Status proto which includes an additional Any for details. Support is coming to each language to handle the type. In Java, it'd look like:
// This is com.google.rpc.Status, not io.grpc.Status
Status status = Status.newBuilder()
.setCode(Code.INVALID_ARGUMENT.getNumber())
.setMessage("Email or password malformed")
.addDetails(Any.pack(registerUserResponse))
.build();
responseObserver.onError(StatusProto.toStatusRuntimeException(status));
google.rpc.Status is cleaner in some languages as the error details can be passed around as one unit. It also makes it clear what parts of the response are error-related. On-the-wire, it still uses Metadata to pass the additional information.
You may also be interested in error_details.proto which contains some common types of errors.
I discussed this topic during CloudNativeCon. You can check out the slides and linked recording on YouTube.
We have 3 different ways we could handle the errors in gRPC. For example lets assume the gRPC server does not accept values above 20 or below 2.
Option 1: Using gRPC status codes.
if(number < 2 || number > 20){
Status status = Status.FAILED_PRECONDITION.withDescription("Not between 2 and 20");
responseObserver.onError(status.asRuntimeException());
}
Option 2: Metadata (we can pass objects via metadata)
if(number < 2 || number > 20){
Metadata metadata = new Metadata();
Metadata.Key<ErrorResponse> responseKey = ProtoUtils.keyForProto(ErrorResponse.getDefaultInstance());
ErrorCode errorCode = number > 20 ? ErrorCode.ABOVE_20 : ErrorCode.BELOW_2;
ErrorResponse errorResponse = ErrorResponse.newBuilder()
.setErrorCode(errorCode)
.setInput(number)
.build();
// pass the error object via metadata
metadata.put(responseKey, errorResponse);
responseObserver.onError(Status.FAILED_PRECONDITION.asRuntimeException(metadata));
}
Option 3: Using oneof - we can also use oneof to send error response
oneof response {
SuccessResponse success_response = 1;
ErrorResponse error_response = 2;
}
}
client side:
switch (response.getResponseCase()){
case SUCCESS_RESPONSE:
System.out.println("Success Response : " + response.getSuccessResponse().getResult());
break;
case ERROR_RESPONSE:
System.out.println("Error Response : " + response.getErrorResponse().getErrorCode());
break;
}
Check here for the detailed steps - https://www.vinsguru.com/grpc-error-handling/
As mentioned by #Eric Anderson, you can use metadata to pass error detail. The problem with metadata is that it can contain, other attributes (example - content-type). To handle that you need to add custom logic to filter error metadata.
A much cleaner approach is of using google.rpc.Status proto (as Eric has mentioned).
If you can convert your gRPC server application to spring boot using yidongnan/grpc-spring-boot-starter, then you can write #GrpcAdvice, similar to Spring Boot #ControllerAdvice as
#GrpcAdvice
public class ExceptionHandler {
#GrpcExceptionHandler(ValidationErrorException.class)
public StatusRuntimeException handleValidationError(ValidationErrorException cause) {
List<ValidationError> validationErrors = cause.getValidationErrors();
RegisterUserResponse registerUserResponse =
RegisterUserResponse.newBuilder()
.addAllValidationErrors(validationErrors)
.build();
var status =
com.google.rpc.Status.newBuilder()
.setCode(Code.INVALID_ARGUMENT.getNumber())
.setMessage("Email or password malformed")
.addDetails(Any.pack(registerUserResponse))
.build();
return StatusProto.toStatusRuntimeException(status);
}
}
On the client-side, you can catch this exception and unpack the registerUserResponse as:
as
} catch (StatusRuntimeException error) {
com.google.rpc.Status status = io.grpc.protobuf.StatusProto.fromThrowable(error);
RegisterUserResponse registerUserResponse = null;
for (Any any : status.getDetailsList()) {
if (!any.is(RegisterUserResponse.class)) {
continue;
}
registerUserResponse = any.unpack(ErrorInfo.class);
}
log.info(" Error while calling product service, reason {} ", registerUserResponse.getValidationErrorsList());
//Other action
}
In my opinion, this can be a much cleaner approach provided you can run your gRPC server application as Spring Boot.
I was struggling with similar questions - so I decided to compile everything in a blog post
Here is a test in GoLang:
func TestNewStatusError_WhenBuildingFromStatus_WithDetails(t *testing.T) {
details1 := &errdetails.BadRequest{}
details1.FieldViolations = append(details1.FieldViolations, &errdetails.BadRequest_FieldViolation{
Field: "site_id",
Description: "bad format, not an UUID",
})
details2 := &common.ResourceNotFound{
Title: "site",
Description: "not found",
}
statusErr := status.New(codes.Internal, "something went wrong")
statusErrWithDetails, err := statusErr.WithDetails(details1, details2)
require.Nil(t, err)
assert.EqualValues(t, codes.Internal, statusErrWithDetails.Code())
assert.EqualValues(t, "something went wrong", statusErrWithDetails.Message())
assert.EqualValues(t, 2, len(statusErrWithDetails.Details()))
}
When rendering something similar would look like:
{
"code": 3,
"message": "SiteID not valid: bad uuid",
"details": [
{
"#type": "type.googleapis.com/google.rpc.BadRequest",
"field_violations": [
{
"field": "site_id",
"description": "Site ID not valid"
}
]
}
]
}