crnk-framework icon indicating copy to clipboard operation
crnk-framework copied to clipboard

Can't create a nested resource

Open chrylis opened this issue 2 years ago • 2 comments

I am using CrnkClient to work with nested resources (#831). The URI template is

/v0/channels/{channelId}/messages

I have the following resource structure:

@JsonApiResource(type = "messages", nested = true)
class Message {
    @JsonApiId
    MessageId id;

    @JsonApiRelationId
    @NotEmpty
    String channelId;

    @JsonApiRelation(mappedBy = "messages")
    Channel channel;
}

@JsonApiResource(type = "channels")
class Channel {
    @JsonApiId
    String id;

    @JsonApiRelation
    List<Message> messages;
}

class MessageId implements Serializable {

    @JsonApiId
    String id;

    @JsonApiRelationId
    String channelId;

    // ...
}

If I try to call messageRepository.create without setting any IDs, then CrnkClient tries to POST to /v0/messages (which it shouldn't, because it's nested!), so I tried setting the channelId: Of course, since this is a new message resource, I don't have an ID for it.

def m = new Message(
    id: new MessageId(channelId: CHANNEL),
    channelId: CHANNEL,
    text: "stuff"
)

messageRepository.create(m)

This throws in ResourceRegistryImpl.getResourcePath with nested resources must have a non-null identifier even though for a create POST resources shouldn't have a non-null ID.

Is there any way to create a nested resource?

chrylis avatar Jan 11 '22 22:01 chrylis

is there a stack trace? just with channelId should be ok

remmeier avatar Jan 19 '22 20:01 remmeier

I lost track of this and had worked around it with a hand-rolled request. As there has been no Crnk release, the problem is still present.

The stack trace is

java.lang.IllegalStateException: nested resources must have a non-null identifier
	at io.crnk.core.engine.internal.utils.PreconditionUtil.fail(PreconditionUtil.java:50)
	at io.crnk.core.engine.internal.utils.PreconditionUtil.verify(PreconditionUtil.java:131)
	at io.crnk.core.engine.internal.registry.ResourceRegistryImpl.getResourcePath(ResourceRegistryImpl.java:187)
	at io.crnk.core.engine.internal.registry.ResourceRegistryImpl.getResourceUrl(ResourceRegistryImpl.java:201)
	at io.crnk.core.engine.internal.utils.JsonApiUrlBuilder.buildUrlInternal(JsonApiUrlBuilder.java:114)
	at io.crnk.core.engine.internal.utils.JsonApiUrlBuilder.buildUrl(JsonApiUrlBuilder.java:86)
	at io.crnk.core.engine.internal.utils.JsonApiUrlBuilder.buildUrl(JsonApiUrlBuilder.java:81)
	at io.crnk.client.internal.ResourceRepositoryStubImpl.computeUrl(ResourceRepositoryStubImpl.java:135)
	at io.crnk.client.internal.ResourceRepositoryStubImpl.modify(ResourceRepositoryStubImpl.java:122)
	at io.crnk.client.internal.ResourceRepositoryStubImpl.create(ResourceRepositoryStubImpl.java:112)

It's quite clearly a semantic error: The getResourcePath call requires that the nested-part of the ID be non-null, even though specifically on creation the ID should be null. The problem is that computeUrl calls buildUrl and then "trims off" the final path-part (even though there's no spec requirement that the path be constructed in the conventional manner), and buildUrl requires a (meaningless) value. Using the empty string gets past that, but then there's another bug: The serializer writes the (bogus) ID into the POST body:

{
  "data": {
    "id": "46bef8f7-444c-4581-9abe-c6e3282a8f0c--",
    "type": "messages",
    ...
  }
}

(to which the server responds with 4xx because the id must be null).

chrylis avatar May 11 '23 17:05 chrylis