commercetools / commercetools-sdk-java-v2

The e-commerce SDK from commercetools for Java.
https://commercetools.github.io/commercetools-sdk-java-v2/javadoc/index.html
Apache License 2.0
35 stars 16 forks source link

Cannot delete type #242

Closed andrei-ivanov closed 2 years ago

andrei-ivanov commented 2 years ago

Describe the bug Trying to delete a custom type I get an error that the version is missing, but I don't see a way to provide it.

To Reproduce projectApiRoot.types().withKey("test").delete().executeBlocking()

ERROR --- [    httpclient-dispatch-2]                                 commercetools.types.response : DELETE https://api.europe-west1.gcp.commercetools.com/project/types/key=test 400
Exception in thread "main" io.vrap.rmf.base.client.error.BadRequestException: detailMessage: Client error response [url] https://api.europe-west1.gcp.commercetools.com/project/types/key=test [status code] 400 [reason phrase] Bad Request
summary: DELETE https://api.europe-west1.gcp.commercetools.com/project/types/key=test failed  with response code 400 with X-Correlation-ID `{key=x-correlation-id, value=projects-b70262c7-1f96-444a-b12e-c9ebb679e751}` on 2022-01-20T13:42:50.024519Z
http response formatted body: {
  "statusCode" : 400,
  "message" : "Missing version number",
  "errors" : [ {
    "code" : "InvalidOperation",
    "message" : "Missing version number"
  } ]
}
http response: io.vrap.rmf.base.client.ApiHttpResponse@3708eea4[statusCode=400,headers=[{key=date, value=Thu, 20 Jan 2022 13:42:48 GMT}, {key=content-length, value=127}, {key=server, value=istio-envoy}, {key=x-envoy-upstream-service-time, value=0}, {key=access-control-allow-headers, value=Accept, Authorization, Content-Type, Origin, User-Agent, X-Correlation-ID}, {key=x-correlation-id, value=projects-b70262c7-1f96-444a-b12e-c9ebb679e751}, {key=access-control-allow-methods, value=GET, POST, DELETE, OPTIONS}, {key=via, value=1.1 google}, {key=access-control-expose-headers, value=X-Correlation-ID}, {key=access-control-allow-origin, value=*}, {key=x-http-status-caused-by-external-upstream, value=false}, {key=access-control-max-age, value=299}, {key=content-type, value=application/json; charset=utf-8}, {key=server-timing, value=projects;dur=0}, {key=alt-svc, value=clear}],textInterpretedBody={"statusCode":400,"message":"Missing version number","errors":[{"code":"InvalidOperation","message":"Missing version number"}]}]
SDK: 7.3.0
endpoint: DELETE https://api.europe-west1.gcp.commercetools.com/project/types/key=test
Java: 17.0.2
request: io.vrap.rmf.base.client.ApiHttpRequest@6e470b3[method=DELETE,uri="https://api.europe-west1.gcp.commercetools.com/project/types/key=test",headers=[{key=Accept-Encoding, value=gzip}, {key=Authorization, value=**removed from output**}, {key=User-Agent, value=commercetools-sdk-java-v2/7.3.0  Java/17.0.2+8-LTS (Windows 10; amd64)}],textInterpretedBody=empty body]
http request: io.vrap.rmf.base.client.ApiHttpRequest@6e470b3[method=DELETE,uri="https://api.europe-west1.gcp.commercetools.com/project/types/key=test",headers=[{key=Accept-Encoding, value=gzip}, {key=Authorization, value=**removed from output**}, {key=User-Agent, value=commercetools-sdk-java-v2/7.3.0  Java/17.0.2+8-LTS (Windows 10; amd64)}],textInterpretedBody=empty body]

    at io.vrap.rmf.base.client.error.HttpExceptionFactory.createClientException(HttpExceptionFactory.java:52)
    at io.vrap.rmf.base.client.error.HttpExceptionFactory.create(HttpExceptionFactory.java:17)
    at io.vrap.rmf.base.client.http.ErrorMiddlewareImpl.lambda$invoke$0(ErrorMiddlewareImpl.java:30)
    at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:646)
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
    at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2147)
    at com.commercetools.http.apachehttp.CompletableFutureCallbackAdapter.completed(CompletableFutureCallbackAdapter.java:22)
    at org.apache.hc.core5.concurrent.BasicFuture.completed(BasicFuture.java:123)
    at org.apache.hc.core5.concurrent.ComplexFuture.completed(ComplexFuture.java:72)
    at org.apache.hc.client5.http.impl.async.InternalAbstractHttpAsyncClient$1$1.completed(InternalAbstractHttpAsyncClient.java:279)
    at org.apache.hc.core5.http.nio.support.AbstractAsyncResponseConsumer$1.completed(AbstractAsyncResponseConsumer.java:101)
    at org.apache.hc.core5.http.nio.entity.AbstractBinAsyncEntityConsumer.completed(AbstractBinAsyncEntityConsumer.java:84)
    at org.apache.hc.core5.http.nio.entity.AbstractBinDataConsumer.streamEnd(AbstractBinDataConsumer.java:81)
    at org.apache.hc.core5.http.nio.support.AbstractAsyncResponseConsumer.streamEnd(AbstractAsyncResponseConsumer.java:142)
    at org.apache.hc.client5.http.impl.async.HttpAsyncMainClientExec$1.streamEnd(HttpAsyncMainClientExec.java:233)
    at org.apache.hc.core5.http2.impl.nio.ClientH2StreamHandler.consumeData(ClientH2StreamHandler.java:239)
    at org.apache.hc.core5.http2.impl.nio.AbstractH2StreamMultiplexer$H2Stream.consumeData(AbstractH2StreamMultiplexer.java:1604)
    at org.apache.hc.core5.http2.impl.nio.AbstractH2StreamMultiplexer.consumeDataFrame(AbstractH2StreamMultiplexer.java:1010)
    at org.apache.hc.core5.http2.impl.nio.AbstractH2StreamMultiplexer.consumeFrame(AbstractH2StreamMultiplexer.java:728)
    at org.apache.hc.core5.http2.impl.nio.AbstractH2StreamMultiplexer.onInput(AbstractH2StreamMultiplexer.java:444)
    at org.apache.hc.core5.http2.impl.nio.AbstractH2IOEventHandler.inputReady(AbstractH2IOEventHandler.java:65)
    at org.apache.hc.core5.http2.impl.nio.ClientH2IOEventHandler.inputReady(ClientH2IOEventHandler.java:39)
    at org.apache.hc.core5.reactor.ssl.SSLIOSession.decryptData(SSLIOSession.java:594)
    at org.apache.hc.core5.reactor.ssl.SSLIOSession.access$200(SSLIOSession.java:73)
    at org.apache.hc.core5.reactor.ssl.SSLIOSession$1.inputReady(SSLIOSession.java:201)
    at org.apache.hc.core5.reactor.InternalDataChannel.onIOEvent(InternalDataChannel.java:140)
    at org.apache.hc.core5.reactor.InternalChannel.handleIOEvent(InternalChannel.java:51)
    at org.apache.hc.core5.reactor.SingleCoreIOReactor.processEvents(SingleCoreIOReactor.java:178)
    at org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:127)
    at org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:85)
    at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44)
    at java.base/java.lang.Thread.run(Thread.java:833)

Expected behavior A way to be able to provide the version or delete the latest version if it's not present. Since the update call doesn't require a version, maybe delete shouldn't either.

**Stack information

andrei-ivanov commented 2 years ago

Ah, actually I found out how to set the version from a different call 🤦‍♂️ But my comment regarding the possibility for delete to work even without a version still stands

jenschude commented 2 years ago

The reason for this is the Optimistic Concurrency Control. Please see https://docs.commercetools.com/api/general-concepts#optimistic-concurrency-control

Cause without a version you and us never know if you want to delete the correct version as it could have been changed in the mean time. Therefor the version is required.

jenschude commented 2 years ago

Btw for updates the version is required, but is meant to be sent in the Body. For DELETE the HTTP RFC says that it doesnt have a body. So the version must be sent as a query parameter.

andrei-ivanov commented 2 years ago

But the same reason should be applied for updates too, without a version how can you make sure that the client doesn't unknowingly overwrites an object that was modified?

jenschude commented 2 years ago

As already said the version is part of the Update body:

https://github.com/commercetools/commercetools-sdk-java-v2/blob/0257ba27b676a709f90a778ffbee65d0a0954e64/commercetools/commercetools-sdk-java-api/src/main/java-generated/com/commercetools/api/models/category/CategoryUpdate.java#L30

andrei-ivanov commented 2 years ago

But I'm not saying that the property is not available or that it cannot be set. I'm saying that for updates it is optional and the update works even if it is not set, while delete does not work without a version.

jenschude commented 2 years ago

The version is mandatory.

https://github.com/commercetools/commercetools-sdk-java-v2/blob/0c88d9276093ecb93edaaf17482bafb8eb35ff1c/commercetools/commercetools-sdk-java-api/src/main/java-generated/com/commercetools/api/models/type/TypeUpdateBuilder.java#L39-L43

Please see also https://docs.commercetools.com/api/projects/types#update-type

jenschude commented 2 years ago

This is the output if trying to update without a version set.

08:44:59.897 [httpclient-dispatch-2] DEBUG commercetools.types.response - io.vrap.rmf.base.client.ApiHttpResponse@3d6124b3[statusCode=400,headers=[{key=date, value=Fri, 21 Jan 2022 07:44:59 GMT}, {key=content-length, value=222}, {key=server, value=istio-envoy}, {key=x-envoy-upstream-service-time, value=4}, {key=access-control-allow-headers, value=Accept, Authorization, Content-Type, Origin, User-Agent, X-Correlation-ID}, {key=x-correlation-id, value=projects-7f44e36f-55f4-47b9-91df-0d2323747858}, {key=access-control-allow-methods, value=GET, POST, DELETE, OPTIONS}, {key=via, value=1.1 google}, {key=access-control-expose-headers, value=X-Correlation-ID}, {key=access-control-allow-origin, value=*}, {key=x-http-status-caused-by-external-upstream, value=false}, {key=access-control-max-age, value=299}, {key=content-type, value=application/json; charset=utf-8}, {key=server-timing, value=projects;dur=3}, {key=alt-svc, value=clear}],textInterpretedBody={"statusCode":400,"message":"Request body does not contain valid JSON.","errors":[{"code":"InvalidJsonInput","message":"Request body does not contain valid JSON.","detailedErrorMessage":"version: Missing required value"}]}]
java.util.concurrent.CompletionException: io.vrap.rmf.base.client.error.BadRequestException: detailMessage: Client error response [url] https://api.europe-west1.gcp.commercetools.com/test-php-dev-integration-1/types/c492c0f3-d297-4312-b422-0cc33f86bd2a [status code] 400 [reason phrase] Bad Request
summary: POST https://api.europe-west1.gcp.commercetools.com/test-php-dev-integration-1/types/c492c0f3-d297-4312-b422-0cc33f86bd2a failed  with response code 400 with X-Correlation-ID `{key=x-correlation-id, value=projects-7f44e36f-55f4-47b9-91df-0d2323747858}` on 2022-01-21T07:44:59.871132Z
http response formatted body: {
  "statusCode" : 400,
  "message" : "Request body does not contain valid JSON.",
  "errors" : [ {
    "code" : "InvalidJsonInput",
    "message" : "Request body does not contain valid JSON.",
    "detailedErrorMessage" : "version: Missing required value"
  } ]
}
http response: io.vrap.rmf.base.client.ApiHttpResponse@3d6124b3[statusCode=400,headers=[{key=date, value=Fri, 21 Jan 2022 07:44:59 GMT}, {key=content-length, value=222}, {key=server, value=istio-envoy}, {key=x-envoy-upstream-service-time, value=4}, {key=access-control-allow-headers, value=Accept, Authorization, Content-Type, Origin, User-Agent, X-Correlation-ID}, {key=x-correlation-id, value=projects-7f44e36f-55f4-47b9-91df-0d2323747858}, {key=access-control-allow-methods, value=GET, POST, DELETE, OPTIONS}, {key=via, value=1.1 google}, {key=access-control-expose-headers, value=X-Correlation-ID}, {key=access-control-allow-origin, value=*}, {key=x-http-status-caused-by-external-upstream, value=false}, {key=access-control-max-age, value=299}, {key=content-type, value=application/json; charset=utf-8}, {key=server-timing, value=projects;dur=3}, {key=alt-svc, value=clear}],textInterpretedBody={"statusCode":400,"message":"Request body does not contain valid JSON.","errors":[{"code":"InvalidJsonInput","message":"Request body does not contain valid JSON.","detailedErrorMessage":"version: Missing required value"}]}]
SDK: 7.4.0-SNAPSHOT
endpoint: POST https://api.europe-west1.gcp.commercetools.com/test-php-dev-integration-1/types/c492c0f3-d297-4312-b422-0cc33f86bd2a
Java: 15.0.2
cwd: /Users/jensschulze/workspace/commercetools-java-sdks/commercetools/commercetools-sdk-java-api
request: io.vrap.rmf.base.client.ApiHttpRequest@1990d74b[method=POST,uri="https://api.europe-west1.gcp.commercetools.com/test-php-dev-integration-1/types/c492c0f3-d297-4312-b422-0cc33f86bd2a",headers=[{key=Accept-Encoding, value=gzip}, {key=Authorization, value=**removed from output**}, {key=User-Agent, value=commercetools-sdk-java-v2/7.4.0-SNAPSHOT  Java/15.0.2+7 (Mac OS X; x86_64)}],textInterpretedBody={"actions":[{"key":"random-key-12354d9d-260d-4e0e-9e30-6eb455f27080","action":"changeKey"}]}]
http request: io.vrap.rmf.base.client.ApiHttpRequest@1990d74b[method=POST,uri="https://api.europe-west1.gcp.commercetools.com/test-php-dev-integration-1/types/c492c0f3-d297-4312-b422-0cc33f86bd2a",headers=[{key=Accept-Encoding, value=gzip}, {key=Authorization, value=**removed from output**}, {key=User-Agent, value=commercetools-sdk-java-v2/7.4.0-SNAPSHOT  Java/15.0.2+7 (Mac OS X; x86_64)}],textInterpretedBody={"actions":[{"key":"random-key-12354d9d-260d-4e0e-9e30-6eb455f27080","action":"changeKey"}]}]
http request formatted body: {
  "actions" : [ {
    "key" : "random-key-12354d9d-260d-4e0e-9e30-6eb455f27080",
    "action" : "changeKey"
  } ]
}

The only exception to this is the custom objects endpoint as it allows upsert operations and the version is optional.

https://docs.commercetools.com/api/projects/custom-objects#create-or-update-a-customobject

andrei-ivanov commented 2 years ago

Hmm, I did a test earlier and it's not, at least not in all cases:

INFO : Saved brand: Brand(id=b64234b0-0a2f-4d69-a67b-4bb22fe79a2f, code=apple, name=Apple, assets=[com.commercetools.api.models.common.AssetDraftImpl@97b7e33b], approved=false)
INFO : POST https://api.europe-west1.gcp.commercetools.com/project/custom-objects 200
INFO : Saved brand: Brand(id=b64234b0-0a2f-4d69-a67b-4bb22fe79a2f, code=apple, name=Applex, assets=[com.commercetools.api.models.common.AssetDraftImpl@97b7e33b], approved=false)
INFO : GET https://api.europe-west1.gcp.commercetools.com/project/custom-objects/brands 200
INFO : id=b64234b0-0a2f-4d69-a67b-4bb22fe79a2f, container=brands, key=apple, object=Brand(id=null, code=apple, name=Applex, assets=[com.commercetools.api.models.common.AssetDraftImpl@97b7e33b], approved=false), version=2

So doing a 2nd POST on a custom object with the same key and without a version updates the object and increases the version automatically and this got me confused.

Ah, and that's exactly what you said at the end of your comment...

andrei-ivanov commented 2 years ago

Got it now, as I said, the custom objects exception from the rule got me confused, sorry for all the noise 🙂

jenschude commented 2 years ago

If you take a look at the URL. You are POSTing to the /custom-objects endpoint which is for all other types the Create endpoint. Other resources use for updates e.g. POST /types/{id}