Edited to address question updates, previous answer removed
Looking over your changes to your question I think I understand the problem you are facing a bit more. As there is no field that is an identifier on your resources (just a link) you have no way to refer to that specific resource within your GUI (i.e. a link to a page describing a specific pet).
The first thing to determine is if a pet ever makes sense without an owner. If we can have a pet without any owner then I would say we need some sort of unique property on the pet that we can use to refer to it. I do not believe this would violate not exposing the ID directly as the actual resource ID would still be tucked away in a link that the REST client wouldn't parse. With that in mind our pet resource may look like:
<Entity type="Pet">
<Link rel="self" href="http://example.com/pets/1" />
<Link rel="owner" href="http://example.com/people/1" />
<UniqueName>Spot</UniqueName>
</Entity>
We can now update the name of that pet from Spot to Fido without having to mess with any actually resource IDs throughout the application. Likewise we can refer to that pet in our GUI with something like:
http://example.com/GUI/pets/Spot
If the pet does not make any sense without an owner (or pets are not allowed in the system without an owner) then we can use the owner as part of the "identity" of the pet in the system:
http://example.com/GUI/owners/John/pets/1 (first pet in the list for John)
One small note, if both Pets and People can exist separate of each-other I would not make the entry point for the API the "People" resource. Instead I would create a more generic resource that would contain a link to People and Pets. It could return a resource that looks like:
<Entity type="ResourceList">
<Link rel="people" href="http://example.com/api/people" />
<Link rel="pets" href="http://example.com/api/pets" />
</Entity>
So by only knowing the first entry point into the API and not processing any of the URLs to figure out system identifiers we can do something like this:
User logs into the application. The REST client accesses the entire list of people resources available which may look like:
<Entity type="Person">
<Link rel="self" href="http://example.com/api/people/1" />
<Pets>
<Link rel="pet" href="http://example.com/api/pets/1" />
<Link rel="pet" href="http://example.com/api/pets/2" />
</Pets>
<UniqueName>John</UniqueName>
</Entity>
<Entity type="Person">
<Link rel="self" href="http://example.com/api/people/2" />
<Pets>
<Link rel="pet" href="http://example.com/api/pets/3" />
</Pets>
<UniqueName>Jane</UniqueName>
</Entity>
The GUI would loop through each resource and print out a list item for each person using the UniqueName as the "id":
<a href="http://example.com/gui/people/1">John</a>
<a href="http://example.com/gui/people/2">Jane</a>
While doing this it could also process each link that it finds with a rel of "pet" and get the pet resource such as:
<Entity type="Pet">
<Link rel="self" href="http://example.com/api/pets/1" />
<Link rel="owner" href="http://example.com/api/people/1" />
<UniqueName>Spot</UniqueName>
</Entity>
Using this it can print a link such as:
<!-- Assumes that a pet can exist without an owner -->
<a href="http://example.com/gui/pets/Spot">Spot</a>
or
<!-- Assumes that a pet MUST have an owner -->
<a href="http://example.com/gui/people/John/pets/Spot">Spot</a>
If we go with the first link and assume that our entry resource has a link with a relation of "pets" the control flow would go something like this in the GUI:
- Page is opened and the pet Spot is requested.
- Load the list of resources from the API entry point.
- Load the resource that is related with the term "pets".
- Look through each resource from the "pets" response and find one that matches Spot.
- Display the information for spot.
Using the second link would be a similar chain of events with the exception being that People is the entry point to the API and we would first get a list of all people in the system, find the one that matches, then find all pets that belong to that person (using the rel tag again) and find the one that is named Spot so we can display the specific information related to it.
You want to you 403 Forbidden. From the HTTP specs
403 Forbidden: The server understood the request, but is refusing to fulfill it. Authorization will not help and the request SHOULD NOT be repeated.
You shouldn't use 409, it implies that the client might be able to resolve the conflict, which in the case of over booking the client can't do as the conflict rules are server side.
Remember in the context of HTTP status codes "conflict" means a conflict in the state of the resource (in this case the book time slot), for example it expects time in one format and you sent it in the wrong format [bad example, better one would be two clients update a resource at the same time and the server cannot figure out which update to save, like on a version controlled system]. The resource is a time slot, if you change to a different time slot you have created a different resource, not resolved a conflict with this resource.
409 Conflict: The request could not be completed due to a conflict with the current state of the resource. This code is only allowed in
situations where it is expected that the user might be able to resolve
the conflict and resubmit the request.
The server should return 403 Forbidden with an explanation that it is refusing to save the state because that time slot is already full. Nothing the client can do in altering the state of the resource can change that. The client should not re-submit. They must instead create a new time slot instead (you could return the closest free slot as a suggestion).
Best Answer
There is no rule, either in the W3C spec or the unofficial rules of REST, that says that a
PUT
must use the same schema/model as its correspondingGET
.It's nice if they're similar, but it's not unusual for
PUT
to do things slightly differently. For example, I've seen a lot of APIs that include some kind of ID in the content returned by aGET
, for convenience. But with aPUT
, that ID is determined exclusively by the URI and has no meaning in the content. Any ID found in the body will be silently ignored.REST and the web in general is heavily tied to the Robustness Principle: "Be conservative in what you do [send], be liberal in what you accept." If you agree philosophically with this, then the solution is obvious: Ignore any invalid data in
PUT
requests. That applies to both immutable data, as in your example, and actual nonsense, e.g. unknown fields.PATCH
is potentially another option, but you shouldn't implementPATCH
unless you're actually going to support partial updates.PATCH
means only update the specific attributes I include in the content; it does not mean replace the entire entity but exclude some specific fields. What you're actually talking about is not really a partial update, it's a full update, idempotent and all, it's just that part of the resource is read-only.A nice thing to do if you choose this option would be to send back a 200 (OK) with the actual updated entity in the response, so that clients can clearly see that the read-only fields were not updated.
There are certainly some people who think the other way - that it should be an error to attempt to update a read-only portion of a resource. There is some justification for this, primarily on the basis that you would definitely return an error if the entire resource was read-only and the user tried to update it. It definitely goes against the robustness principle, but you might consider it to be more "self-documenting" for users of your API.
There are two conventions for this, both of which correspond to your original ideas, but I'll expand on them. The first is to prohibit the read-only fields from appearing in the content, and return an HTTP 400 (Bad Request) if they do. APIs of this sort should also return an HTTP 400 if there are any other unrecognized/unusable fields. The second is to require the read-only fields to be identical to the current content, and return a 409 (Conflict) if the values do not match.
I really dislike the equality check with 409 because it invariably requires the client to do a
GET
in order to retrieve the current data before being able to do aPUT
. That's just not nice and is probably going to lead to poor performance, for somebody, somewhere. I also really don't like 403 (Forbidden) for this as it implies that the entire resource is protected, not just a part of it. So my opinion is, if you absolutely must validate instead of following the robustness principle, validate all of your requests and return a 400 for any that have extra or non-writable fields.Make sure your 400/409/whatever includes information about what the specific problem is and how to fix it.
Both of these approaches are valid, but I prefer the former one in keeping with the robustness principle. If you've ever experienced working with a large REST API, you'll appreciate the value of backward compatibility. If you ever decide to remove an existing field or make it read-only, it is a backward compatible change if the server just ignores those fields, and old clients will still work. However, if you do strict validation on the content, it is not backward compatible anymore, and old clients will cease to work. The former generally means less work for both the maintainer of an API and its clients.