Uploaded image for project: 'Couchbase Java Client'
  1. Couchbase Java Client
  2. JCBC-1895

No content result on exist sub-document operation

    XMLWordPrintable

Details

    • Bug
    • Status: Reopened
    • Major
    • Resolution: Unresolved
    • None
    • None
    • None
    • None
    • 1

    Description

      I have the following document stored in my collection named `user_profile`:

      {
        "nZ46sulX": {
          "v": "D8tQkYxsoA95HhJ6hPKnFA==",
          "m": "AA0xNjM4MzU2MDcyMzg0"
        }
      }

      I'm trying to check if the path "nZ46sulX" exists in the document using the sub-document API via Spring Data `ReactiveCouchbaseTemplate`:

       template.getCollection(collection).reactive()
                  .lookupIn(documentId, List.of(LookupInSpecStandard.exists("nZ46sulX")))
      

      Looking at the response:

      LookupInResult{encoded=[SubdocField{status=SUCCESS, value=, path='nZ46sulX'}], cas=0x16bc9ca41f7d0000, isDeleted=false}
      

      The value is empty and as a result, the following fails:

       lookupInResult.contentAs(0, Boolean.class))
      

      Exception:

      com.couchbase.client.core.error.DecodingFailureException: Deserialization of content into target class java.lang.Boolean failed; encoded = 
      	at com.couchbase.client.java.codec.JacksonJsonSerializer.deserialize(JacksonJsonSerializer.java:137)
      	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
      Assembly trace from producer [reactor.core.publisher.FluxMapFuseable] :
      	reactor.core.publisher.Flux.map
      	com.upstreamsystems.profiling.profiles.service.couchbase.ProfileCouchbaseService.findExistingAttributes(ProfileCouchbaseService.java:47)
      Error has been observed at the following site(s):
      	*_____________Flux.map ⇢ at com.upstreamsystems.profiling.profiles.service.couchbase.ProfileCouchbaseService.findExistingAttributes(ProfileCouchbaseService.java:47)
      	|_            Flux.map ⇢ at com.upstreamsystems.profiling.profiles.service.couchbase.ProfileCouchbaseService.findExistingAttributes(ProfileCouchbaseService.java:49)
      	|_         Flux.reduce ⇢ at com.upstreamsystems.profiling.profiles.service.couchbase.ProfileCouchbaseService.findExistingAttributes(ProfileCouchbaseService.java:50)
      	|_  Mono.onErrorReturn ⇢ at com.upstreamsystems.profiling.profiles.service.couchbase.ProfileCouchbaseService.findExistingAttributes(ProfileCouchbaseService.java:54)
      	|_            Mono.map ⇢ at com.upstreamsystems.profiling.profiles.service.ProfileFacadeService.computeAttributesToUpdate(ProfileFacadeService.java:408)
      	|_ Mono.defaultIfEmpty ⇢ at com.upstreamsystems.profiling.profiles.service.ProfileFacadeService.computeAttributesToUpdate(ProfileFacadeService.java:418)
      	*_________Mono.flatMap ⇢ at com.upstreamsystems.profiling.profiles.service.ProfileFacadeService.submitProfileContext(ProfileFacadeService.java:240)
      	|_        Mono.flatMap ⇢ at com.upstreamsystems.profiling.profiles.service.ProfileFacadeService.submitProfileContext(ProfileFacadeService.java:242)
      	|_        Mono.flatMap ⇢ at com.upstreamsystems.profiling.profiles.service.ProfileFacadeService.submitProfileContext(ProfileFacadeService.java:244)
      	*______Mono.thenReturn ⇢ at com.upstreamsystems.profiling.profiles.service.ProfileFacadeService.submitProfileContext(ProfileFacadeService.java:245)
      Original Stack Trace:
      		at com.couchbase.client.java.codec.JacksonJsonSerializer.deserialize(JacksonJsonSerializer.java:137)
      		at com.couchbase.client.java.codec.JsonValueSerializerWrapper.deserialize(JsonValueSerializerWrapper.java:61)
      		at com.couchbase.client.java.kv.LookupInResult.contentAs(LookupInResult.java:105)
      		at com.upstreamsystems.profiling.profiles.service.couchbase.ProfileCouchbaseService.lambda$findExistingAttributes$2(ProfileCouchbaseService.java:48)
      		at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:113)
      		at reactor.core.publisher.FluxFlatMap$FlatMapMain.tryEmit(FluxFlatMap.java:543)
      		at reactor.core.publisher.FluxFlatMap$FlatMapInner.onNext(FluxFlatMap.java:984)
      		at reactor.core.publisher.SerializedSubscriber.onNext(SerializedSubscriber.java:99)
      		at reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.onNext(FluxRetryWhen.java:174)
      		at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
      		at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:130)
      		at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1816)
      		at com.couchbase.client.core.Reactor$SilentMonoCompletionStage.lambda$subscribe$0(Reactor.java:178)
      		at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:859)
      		at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:837)
      		at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
      		at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2143)
      		at com.couchbase.client.core.msg.BaseRequest.succeed(BaseRequest.java:149)
      		at com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.decodeAndComplete(KeyValueMessageHandler.java:372)
      		at com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.decode(KeyValueMessageHandler.java:351)
      		at com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.channelRead(KeyValueMessageHandler.java:276)
      		at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
      		at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
      		at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
      		at com.couchbase.client.core.io.netty.kv.MemcacheProtocolVerificationHandler.channelRead(MemcacheProtocolVerificationHandler.java:85)
      		at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
      		at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
      		at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
      		at com.couchbase.client.core.deps.io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
      		at com.couchbase.client.core.deps.io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296)
      		at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
      		at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
      		at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
      		at com.couchbase.client.core.deps.io.netty.handler.flush.FlushConsolidationHandler.channelRead(FlushConsolidationHandler.java:152)
      		at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
      		at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
      		at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
      		at com.couchbase.client.core.deps.io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
      		at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
      		at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
      		at com.couchbase.client.core.deps.io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
      		at com.couchbase.client.core.deps.io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:795)
      		at com.couchbase.client.core.deps.io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:480)
      		at com.couchbase.client.core.deps.io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378)
      		at com.couchbase.client.core.deps.io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
      		at com.couchbase.client.core.deps.io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
      		at com.couchbase.client.core.deps.io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
      		at java.base/java.lang.Thread.run(Thread.java:831)
      Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
       at [Source: (byte[])""; line: 1, column: 0]
      	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
      	at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:4688)
      	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4586)
      	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3609)
      	at com.couchbase.client.java.codec.JacksonJsonSerializer.deserialize(JacksonJsonSerializer.java:134)
      	at com.couchbase.client.java.codec.JsonValueSerializerWrapper.deserialize(JsonValueSerializerWrapper.java:61)
      	at com.couchbase.client.java.kv.LookupInResult.contentAs(LookupInResult.java:105)
      	at com.upstreamsystems.profiling.profiles.service.couchbase.ProfileCouchbaseService.lambda$findExistingAttributes$2(ProfileCouchbaseService.java:48)
      	at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:113)
      	at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
      	at reactor.core.publisher.FluxFlatMap$FlatMapMain.tryEmit(FluxFlatMap.java:543)
      	at reactor.core.publisher.FluxFlatMap$FlatMapInner.onNext(FluxFlatMap.java:984)
      	at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
      	at reactor.core.publisher.SerializedSubscriber.onNext(SerializedSubscriber.java:99)
      	at reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.onNext(FluxRetryWhen.java:174)
      	at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
      	at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
      	at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
      	at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
      	at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
      	at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
      	at reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:130)
      	at reactor.core.publisher.FluxOnAssembly$OnAssemblySubscriber.onNext(FluxOnAssembly.java:539)
      	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1816)
      	at com.couchbase.client.core.Reactor$SilentMonoCompletionStage.lambda$subscribe$0(Reactor.java:178)
      	at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:859)
      	at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:837)
      	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
      	at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2143)
      	at com.couchbase.client.core.msg.BaseRequest.succeed(BaseRequest.java:149)
      	at com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.decodeAndComplete(KeyValueMessageHandler.java:372)
      	at com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.decode(KeyValueMessageHandler.java:351)
      	at com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.channelRead(KeyValueMessageHandler.java:276)
      	at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
      	at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
      	at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
      	at com.couchbase.client.core.io.netty.kv.MemcacheProtocolVerificationHandler.channelRead(MemcacheProtocolVerificationHandler.java:85)
      	at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
      	at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
      	at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
      	at com.couchbase.client.core.deps.io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
      	at com.couchbase.client.core.deps.io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296)
      	at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
      	at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
      	at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
      	at com.couchbase.client.core.deps.io.netty.handler.flush.FlushConsolidationHandler.channelRead(FlushConsolidationHandler.java:152)
      	at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
      	at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
      	at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
      	at com.couchbase.client.core.deps.io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
      	at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
      	at com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
      	at com.couchbase.client.core.deps.io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
      	at com.couchbase.client.core.deps.io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:795)
      	at com.couchbase.client.core.deps.io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:480)
      	at com.couchbase.client.core.deps.io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378)
      	at com.couchbase.client.core.deps.io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
      	at com.couchbase.client.core.deps.io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
      	at com.couchbase.client.core.deps.io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
      	at java.base/java.lang.Thread.run(Thread.java:831)
      

       

      Versions
      spring-data-couchbase version 4.3.0
      java-client version 3.2.3

      Attachments

        For Gerrit Dashboard: JCBC-1895
        # Subject Branch Project Status CR V

        Activity

          graham.pople Graham Pople added a comment -

          No - I'm on some high-priority transactions work currently, and this ticket isn't with me; I was just offering a drive-by observation.

          But I just wrote a quick test for it and replicated the behaviour you're seeing: the returned spec result has a status of SUCCESS but an empty value.  Checking the sub-doc commands specification:

          CMD_EXISTS: Check if a path exists

          This command functions exactly like CMD_GET, except that it will not return the existing value in the payload.

          So, a legit SDK bug.  I've submitted a patch.

          graham.pople Graham Pople added a comment - No - I'm on some high-priority transactions work currently, and this ticket isn't with me; I was just offering a drive-by observation. But I just wrote a quick test for it and replicated the behaviour you're seeing: the returned spec result has a status of SUCCESS but an empty value.  Checking the sub-doc commands specification: CMD_EXISTS: Check if a path exists This command functions exactly like CMD_GET , except that it will not return the existing value in the payload. So, a legit SDK bug.  I've submitted a patch.

          Fixed in master, will be available in 3.2.5.

          daschl Michael Nitschinger added a comment - Fixed in master, will be available in 3.2.5.

          Michael Nitschinger Graham Pople  I've tested this issue with version 3.2.5. Unfortunately, if a path does not exist, the following exception is thrown:

          com.couchbase.client.core.error.subdoc.PathNotFoundException: Subdoc path not found in the document {"completed":true,"coreId":"0xe6abfa0200000001","idempotent":true,"index":0,"lastChannelId":"E6ABFA0200000001/0000000056D904AB","lastDispatchedFrom":"127.0.0.1:58054","lastDispatchedTo":"127.0.0.1:11210","path":"unknown-path","requestId":42,"requestType":"SubdocGetRequest","retried":0,"service":{"bucket":"profiles","collection":"user_identifier","documentId":"cntx1.K2aDglqNlv2zuosJvC_UWxC1vrG-mx3N9jOw5qlZBOI","errorCode":{"description":"Subdoc: Some (or all) commands failed. Inspect payload for details","name":"SUBDOC_MULTI_PATH_FAILURE"},"opaque":"0x33","scope":"data","type":"kv","vbucket":969},"status":"SUBDOC_FAILURE","subdocStatus":"PATH_NOT_FOUND","timeoutMs":2500,"timings":{"dispatchMicros":478,"totalDispatchMicros":478,"totalMicros":2863}}
              at com.couchbase.client.core.io.netty.kv.MemcacheProtocol.mapSubDocumentError(MemcacheProtocol.java:761)
              Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
          Assembly trace from producer [reactor.core.publisher.MonoMapFuseable] :
              reactor.core.publisher.Mono.map

           

          Here is a sample code:

          reactiveCouchbaseTemplate.getCollection(collection).reactive()
              .lookupIn(documentId, List.of(LookupInSpec.exists("unknown-path"))) 

          public Set<String> collectExistingAttributes(LookupInResult result, List<String> attributes) {
            return IntStream.range(0, attributes.size())
                .filter(i -> result.contentAs(i, Boolean.class)) // exception is thrown here
                .mapToObj(attributes::get)
                .collect(Collectors.toSet());
          }
          

          lefteris Lefteris Katiforis added a comment - Michael Nitschinger Graham Pople   I've tested this issue with version 3.2.5. Unfortunately, if a path does not exist, the following exception is thrown: com.couchbase.client.core.error.subdoc.PathNotFoundException: Subdoc path not found in the document { "completed" : true , "coreId" : "0xe6abfa0200000001" , "idempotent" : true , "index" : 0 , "lastChannelId" : "E6ABFA0200000001/0000000056D904AB" , "lastDispatchedFrom" : "127.0.0.1:58054" , "lastDispatchedTo" : "127.0.0.1:11210" , "path" : "unknown-path" , "requestId" : 42 , "requestType" : "SubdocGetRequest" , "retried" : 0 , "service" :{ "bucket" : "profiles" , "collection" : "user_identifier" , "documentId" : "cntx1.K2aDglqNlv2zuosJvC_UWxC1vrG-mx3N9jOw5qlZBOI" , "errorCode" :{ "description" : "Subdoc: Some (or all) commands failed. Inspect payload for details" , "name" : "SUBDOC_MULTI_PATH_FAILURE" }, "opaque" : "0x33" , "scope" : "data" , "type" : "kv" , "vbucket" : 969 }, "status" : "SUBDOC_FAILURE" , "subdocStatus" : "PATH_NOT_FOUND" , "timeoutMs" : 2500 , "timings" :{ "dispatchMicros" : 478 , "totalDispatchMicros" : 478 , "totalMicros" : 2863 }}     at com.couchbase.client.core.io.netty.kv.MemcacheProtocol.mapSubDocumentError(MemcacheProtocol.java: 761 )     Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:  Assembly trace from producer [reactor.core.publisher.MonoMapFuseable] :     reactor.core.publisher.Mono.map   Here is a sample code: reactiveCouchbaseTemplate.getCollection(collection).reactive() .lookupIn(documentId, List.of(LookupInSpec.exists( "unknown-path" ))) public Set<String> collectExistingAttributes(LookupInResult result, List<String> attributes) { return IntStream.range( 0 , attributes.size()) .filter(i -> result.contentAs(i, Boolean. class )) // exception is thrown here .mapToObj(attributes::get) .collect(Collectors.toSet()); }
          graham.pople Graham Pople added a comment -

          Lefteris Katiforis , I see what you're saying, but according to the SDK3 spec we are doing what is required here:

          Unlike MutateIn, LookupIn operations can partially fail, every path will receive a result whether or not it failed or succeeded. In the event of a failure, the exception should be propagated when the error is detected on the call to ContentAs(...). In this way, operations that succeed can still be accessed in the event of another operation failing.

          And here that operation has failed, with a PathNotFoundException error from the server, and we are propagating it at the .contentAs() point - as specced.  There is no special handling mentioned for an exists operation.

          Bear in mind, AFAIK the user should already always be doing an exists check on every lookupIn operation, so the correct user code should look like:

          boolean exists = result.exists(0) && result.contentAs(0, Boolean.class);

          Which would not throw in your "unknown-path" case, as the result.exists() would fail.

          But, there are some issues with all that, now I've looked closely into this -

          • While I think users should be doing that result.exists(0) check for all specs (if not, why do we even have it?) - we don't explicitly document that anywhere.
          • I probably agree with you that as a user I'd expect there to be some special-case handling for the exists operation so contentAs returns false rather than throws.  In case the user forgets or doesn't realise or know that they also want a result.exists(0) check.

          Michael Nitschinger Matt Ingenthron I probably feel we do need a spec change here.  What are your thoughts?

          graham.pople Graham Pople added a comment - Lefteris Katiforis , I see what you're saying, but according to the SDK3 spec we are doing what is required here: Unlike MutateIn, LookupIn operations can partially fail, every path will receive a result whether or not it failed or succeeded. In the event of a failure, the exception should be propagated when the error is detected on the call to ContentAs(...). In this way, operations that succeed can still be accessed in the event of another operation failing. And here that operation has failed, with a PathNotFoundException error from the server, and we are propagating it at the .contentAs() point - as specced.  There is no special handling mentioned for an exists operation. Bear in mind, AFAIK the user should already always be doing an exists check on every lookupIn operation, so the correct user code should look like: boolean exists = result.exists(0) && result.contentAs(0, Boolean.class); Which would not throw in your "unknown-path" case, as the result.exists() would fail. But, there are some issues with all that, now I've looked closely into this - While I think users should be doing that result.exists(0) check for all specs (if not, why do we even have it?) - we don't explicitly document that anywhere. I probably agree with you that as a user I'd expect there to be some special-case handling for the exists operation so contentAs returns false rather than throws.  In case the user forgets or doesn't realise or know that they also want a result.exists(0) check. Michael Nitschinger Matt Ingenthron I probably feel we do need a spec change here.  What are your thoughts?

          I don't have a strong opinion. If we add the additional special handling though, we'd probably need to let the API specify what to do in the case that it doesn't exist. False isn't necessarily the correct default, and if it's more than a Boolean, there are many different possibilities. We could have subclasses that have default handling for things like empty strings, default numbers, etc. It's a slippery slope. Java's type system makes some of this challenging.

          I guess maybe I'd think we just need to, now that the exists is fixed, document this better. This code would only be in places where someone is doing that marshalling/unmarshalling. and asking them to check it exists before unpacking it into a class isn't totally unreasonable.

          But again, no strong opinion.

          ingenthr Matt Ingenthron added a comment - I don't have a strong opinion. If we add the additional special handling though, we'd probably need to let the API specify what to do in the case that it doesn't exist. False isn't necessarily the correct default, and if it's more than a Boolean, there are many different possibilities. We could have subclasses that have default handling for things like empty strings, default numbers, etc. It's a slippery slope. Java's type system makes some of this challenging. I guess maybe I'd think we just need to, now that the exists is fixed, document this better. This code would only be in places where someone is doing that marshalling/unmarshalling. and asking them to check it exists before unpacking it into a class isn't totally unreasonable. But again, no strong opinion.

          People

            graham.pople Graham Pople
            lefteris Lefteris Katiforis
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

            Dates

              Created:
              Updated:

              Gerrit Reviews

                There are no open Gerrit changes

                PagerDuty