danielgtaylor / huma

Huma REST/HTTP API Framework for Golang with OpenAPI 3.1
https://huma.rocks/
MIT License
1.71k stars 134 forks source link

Does readOnly actually do any validation? #473

Open jpincas opened 1 month ago

jpincas commented 1 month ago

Quick question. Using the readOnly tag. I can see that it effectively removes the field from the documentation for POST/PATCH requests, but it doesn't actually seem to have any validatory effect.

To be specific. My struct has a field daysOld which is marked 'readOnly'. Although the field doesn't show in the docs, I can add it to a PATCH request and no error is raised and the field is effectively written to the server.

My expectation was that adding the 'readOnly' field would mean that if the field were included in a PATCH request, either an error would be raised or it would just be ignored. Perhaps I've missed something here? Thanks.

danielgtaylor commented 1 month ago

@jpincas great question! This is somewhat complicated for a few reasons (isn't it always :joy:...) There are really three scenarios for handling read-only fields:

  1. Ignore them. Huma doesn't care, and it's up to the service implementor to determine what to do with them. This is easy to implement and has no performance penalty but makes life harder when writing services. This makes round-trips of data easy.
  2. Return a validation error. Huma detects which fields are read-only, and if set in any PUT/POST/PATCH request it returns a 422. We use the same logic as defaults/resolvers to walk the incoming structure to determine if any value is non-zero. This has a small performance penalty but can be skipped for objects without read-only fields. This prevents round-trips from working.
  3. Zero the fields. Like defaults, we walk the incoming structure and explicitly set zero values for any read-only fields. This has the highest performance penalty and is tricky to implement, but doable.

One of the major scenarios I needed to efficiently support with Huma is round-tripping of data with read-only fields, for example think of a created date or updated date or some computed hash value in a field of the resource. So you need to be able to GET the resource, modify it in-place, then submit via PUT and have it just work.

For Huma v2 I wound up going with option 1: ignore read-only fields. This is simplest and gives the most flexibility in how to handle the incoming fields, but it does mean you need to be careful when writing your service. For example, when doing PUT we tend to either fetch any existing resource and copy over the read-only fields, or use partial update DB queries which ignore the read-only fields.

I'm happy to revisit this decision. It let me ship Huma v2 faster and keeps it easier to maintain, but if desired we can always add support for additional scenarios, and can probably do a better job of documenting this stuff instead of having it swimming around in my head :smile:

Interesting side note: I actually had a Huma v1 PR for implementing each of these, see https://github.com/danielgtaylor/huma/pull/63.