Skip to content

Refuse a field name that is not a field - #116

Open
ramonski wants to merge 3 commits into
2.xfrom
fix/refuse-unknown-fields
Open

ramonski wants to merge 3 commits into
2.xfrom
fix/refuse-unknown-fields

Conversation

@ramonski

@ramonski ramonski commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Builds on #115, and has to: a name a Dexterity type kept from its Archetypes days must reach its field before unknown names can be refused, or every payload written against the documented API would be refused instead.

A name in a record that matches no field and no setter is dropped with a line in the log, and the request answers that the object has been created or updated.

Current behavior before PR

POST /create {"portal_type": "AnalysisProfile", ...,
              "Service": ["whatever"], "Nonsense": 42}

200 {"count": 1, "items": [{..., "services": []}]}

Both keys are gone and the caller is told all is well. This is how an afternoon goes missing: the typo was Service for services, a spec reported fifty-eight objects composed, and the profile it had just written stayed empty.

The code says so plainly:

            if success is False:
                logger.warning("update_object_with_data::skipping key=%r", k)
                continue

DexterityDataManager.set returns False when it finds neither a schema field nor a setter for the name.

Desired behavior after PR is merged

400 {"message": "No objects could be created: No field named 'Service' on
                 AnalysisProfile. Did you mean 'services'?", ...}

Without a field close enough to suggest, it says only what it knows:

400 {"message": "No objects could be created: No field named 'Nonsense' on
                 AnalysisProfile", ...}

The suggestion comes from difflib.get_close_matches over the object's own field names.

The keys that are not fields

A record also carries keys that address the object or steer the request, and those must not be mistaken for fields. They are named now, where only id was before:

CONTROL_FIELDS = ("id", "parent_path", "parent_uid", "path",
                  "portal_type", "transition", "uid")

That list is the set of keys the routes themselves read out of a record.

A note on what this changes for callers

A payload carrying a key that is not a field is refused where it used to be accepted. That is the point, and it is worth saying out loud: an integration that sends something extra and harmless will see a 400 it did not see before. The alternative is what we have today, which is that a typo in a field name is indistinguishable from success.

Verification

I confirm I have tested the PR thoroughly and coded it according to PEP8 standards.

Two separate faults, both of which end as a WrongContainedType or a
WrongType on a field the request never mentions.

#106 unwrapped a single valued reference from its list to a bare UID.
An Archetypes UIDReferenceField needs that, and refuses a list with
"[...] is not supported". A Dexterity UIDReferenceField derives from
zope.schema List and validates as one, so the bare UID fails before it
is ever stored, and setting e.g. a department's manager through the API
stopped working. The unwrapping moves to the Archetypes field manager,
through a to_field_value hook, so each side is handed the shape its own
validator expects.

The second fault is older and was hiding behind the first. A Dexterity
type migrated from Archetypes keeps the old capitalised name as a BBB
property, and a payload written against the old API uses it. get_field
looked the name up exactly, so the schema field was not found and the
data manager fell back to the BBB setter. Some of those setters convert
the value, and many hand it to the mutator as it came: a duration stays
the mapping JSON carried, a UID stays unicode.

The schema spellings are tried after the exact name, and only a
candidate naming a real field is used. There are 140 of these BBB
properties on Dexterity content, and 112 of them name a schema field.
Lowering the first letter reaches 43; the rest are snake case. A few
keep neither convention (a sample template calls its field
`samplepoint`), which is why the plain lower case form is tried too.

The new doctest covers both faults, and fails if either fix is
reverted.
@ramonski
ramonski force-pushed the fix/single-valued-dx-uidreference branch from 21ab0f5 to 759f4f6 Compare October 7, 2026 07:28
A name in a record that matched no field and no setter was dropped
with a line in the log, and the request answered that the object had
been created or updated. A caller asking for `Service` instead of
`services` was told that all was well and got back a profile with
nothing in it, which is how an afternoon goes missing.

It is refused now, and the message names the field the caller probably
meant:

    No field named 'Service' on AnalysisProfile. Did you mean 'services'?

The keys that address the object or steer the request rather than
naming a field (portal_type, parent_path, parent_uid, path, uid, id,
transition) are listed as such and skipped, as `id` already was.

This builds on #115. A name a Dexterity type kept from its Archetypes
days has to reach its field first; without that, every payload written
against the documented API, which uses those names throughout, would
now be refused.
@ramonski
ramonski force-pushed the fix/refuse-unknown-fields branch from 59dea71 to ab7a434 Compare October 7, 2026 07:29
@ramonski
ramonski requested a review from xispa October 8, 2026 06:02
@ramonski ramonski added the Enhancement ✨ Improvement to existing functionality label Oct 8, 2026
A file field reads the filename and the content type from the payload
rather than from a field of its own, so a request that uploads a file
carries `filename` and, optionally, `mimetype` or `content_type`
beside the field itself. Refusing a name that is no field turned those
three into a 400 on every file upload.

They address the request rather than the object, which is what the
control keys are, so they belong in that tuple.
Base automatically changed from fix/single-valued-dx-uidreference to 2.x October 14, 2026 15:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement ✨ Improvement to existing functionality

Development

Successfully merging this pull request may close these issues.

1 participant