Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow definition of immutable values in Schema Object #2720

Open
fabiohecht opened this issue Sep 22, 2021 · 11 comments
Open

Allow definition of immutable values in Schema Object #2720

fabiohecht opened this issue Sep 22, 2021 · 11 comments

Comments

@fabiohecht
Copy link

I've come across the requirement of defining an immutable field, that is, one that can only be set when the resource is created (POST). The field is not allowed to be modified, therefore it may not be changed via PUT/PATCH. This would be somewhat similar to writeOnly, but with an extra constraint that the field may only be written once, at creation.

@karenetheridge
Copy link
Member

You can indicate "this field cannot be provided in the request payload" with the subchema "properties": { "verboten_field": false }

@jdesrosiers
Copy link
Contributor

I agree that there's probably a missing createOnly annotation. Forbidding the field like in @karenetheridge's suggestion isn't going to be sufficient for PUT requests which should be the full representation. You can't leave the read-only field out, you need to provide it unchanged. What I've done in the past is to use readOnly and just not enforce it on creation.

@hkosova
Copy link
Contributor

hkosova commented Sep 22, 2021

Related feature request(?) in the JSON Schema repo:
immutable keyword

@darrelmiller
Copy link
Member

It is important to remember that JSON Schema has no notion of whether it is being used for request validation or response validation. If you want to enforce a create only property, then you will need two JSON Schemas, one for the request payload when POSTing and one for response. If you do PUT to create then I don't know how to get around this without the validator having some additional context.

@karenetheridge
Copy link
Member

karenetheridge commented Sep 26, 2021

Forbidding the field like in @karenetheridge's suggestion isn't going to be sufficient for PUT requests which should be the full representation

Normally this type of interface will permit omitting fields that have documented defaults. Instead of "properties": { "field": false }, that could be indicated with "properties": { "fieldname": { "default": <value> } }, "not": { "required": [ "fieldname" ] }.

A separate annotation propertly like "createOnly" might be easier for clients to understand, however -- and this would be useful for an overall document model that is used across multiple endpoints.

@karolzlot
Copy link

karolzlot commented May 10, 2022

Edit: I no longer agree with what I wrote in this comment. I now think that readOnly and writeOnly are enough. It's just that more than 1 schema is often needed.


Based on how readOnly and writeOnly are defined in JSON Schema specification I created the table below.

You can imagine it this way:
Any resource on API can have 3 attributes:

  • possible to create by client (true / false)
  • possible to edit by client (true / false)
  • possible to read by client (true / false)

And all combination of those are in theory possible.

able to create able to edit able to read
default (all actions allowed) t t t
writeOnly t t -
createAndRead t - t
editAndRead - t t
createOnly t - -
editOnly - t -
readOnly - - t
not present in API (so probably doesn't matter?) - - -

I think that if it is possible to edit, then it usually doesn't matter if it is also possible to create.
So I consider editOnly and editAndRead not useful from practical point of view.

createOnly , createAndRead and readOnly are all immutable because they can't be edited.

The discussion here is about createOnly vs immutable. It think from this explanation and from the table I created it is clear that they are different concepts.

I hope that this table will help make correct decision about what should be included in JSON Schema specification.

In my opinion if we have already "readOnly" and "writeOnly" in JSON Schema specification then we should have also other options from this table.


Another option is to remove writeOnly from specification and have only 3 attributes: each one for create edit and read attributes as I explained above the table. So for example we could have:

createOnly
editOnly                         
readOnly                                     

and be able to use up to two of them at once for each property (3 of them at once would mean default: all actions possible)

@karolzlot
Copy link

karolzlot commented May 10, 2022

@jdesrosiers

you can't leave the read-only field out, you need to provide it unchanged.

This seems weird. If field is read-only then it means PUT request can't modify it, so it doesn't matter if we send it in PUT request or not.

Also readOnly doesn't mean that value can't be sent to API, it means that:

attempts by an application to modify the value of this property are expected to be ignored or rejected by that owning authority.

@jdesrosiers
Copy link
Contributor

you can't leave the read-only field out, you need to provide it unchanged.

This seems weird. If field is read-only then it means PUT request can't modify it, so it doesn't matter if we send it in PUT request or not.

The HTTP PUT method is pretty strict about it's semantics.

The HTTP PUT request method creates a new resource or replaces a representation of the target resource with the request payload.

PUT requires the full representation to be passed. It's "replace", not "merge and replace", or "modify and replace". If you don't include the read-only field in a PUT request, it's equivalent to removing that field which would be in violation of the the readOnly directive. Providing a read-only field seems weird if you are thinking about PUT as an "edit" operation, but if you think about it as literally "PUT" this representation in this location (URL), it makes more sense.

readOnly doesn't mean that value can't be sent to API

💯 Exactly right

@karolzlot
Copy link

@jdesrosiers

you don't include the read-only field in a PUT request, it's equivalent to removing that field which would be in violation of the the readOnly directive.

I don't agree. Sending read-only fields to API shouldn't happen in PUT request (but if sent then it won't change anything, server may ignore such fields or return error code)

@Danielku15
Copy link

I also wanted to hop in on this discussion as we have a similar requirement to have a way of indicating that a property is immutable/createOnly. I think the immutable
term fits it better than createOnly to not be in conflict with a "readOnly" semantics.

In our API we follow a rather strict (OData style) REST semantics where all CRUD operations are around the same resource type (not separate DTOs with different properties). On code generation the user will get one resource to work with. We have properties we can be supplied only during create and others which are computed/derived or navigation properties (in a data graph).

An example: There is a User (resource) which has Orders (resource) and there is a bidirectional navigation between them. The Order has a userId (int) and parent user (User) and the User has orders (array of Order). When you create an Order you can specify the userId but not the whole user. And once the Order is created it cannot be moved to a different User making it immutable.

This makes the Order.user readonly because it always can only be read but not modified or provided anyway (it is only in the response if a special API expand option is provided). The Order.userId can only be specified on creation but not modified afterwards making it immutable.

**Second use case: ** If you have "many-to-many" relationships in your model. You can supply the IDs of the two related entities on create but not modify them afterwards because they are part of the PrimaryKey making them immutable too. That's different to a readOnly where values are computed on the server and not supplied by clients.

Disclaimer: We might be a bit influenced on how our models look like due to the .net Entity Framework Core conventions. There you have a ForeignKey property for the ID and a navigation property for the whole object on the same level. You can optionally load the navigation propety. Our API provides the same user(developer) experience on querying, loading and modifying data. We do not practice (or want to practice) a system where we have a nested object holding only the ID as this would have other negative impacts on the server side implementations.

Hence I think something like an immutable has a valid use case in APIs but its semantics has to be aligned with the existing readOnly and writeOnly. readOnly has currently the semantics of a computed field not provided by the client but only by the server. writeOnly has currently the semantics of a field which can be provided on create/edit but never obtained again (e.g. passwords). createOnly would maybe confuse people on whether it is readable or not (even if we allow combining with a separate readOnly). I would also claim that it is more a documentation topic to manage the expectations API clients have. It could be used by code generation to derive edit models not having the createOnly properties in.

If we would go for a more breaking adaption likely we would allow setting for each property in which HTTP verbs it is utilized? This would even allow specifying a different behavior on PATCH, POST and PUT.

@bgrant0607
Copy link

A standard way to represent immutability would be useful.

Examples:
https://google.aip.dev/203#immutable
https://azure.github.io/autorest/extensions/#x-ms-mutability

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants