Skip to main content

project_entity

Function project_entity 

Source
pub fn project_entity(
    entity: &Value,
    type_name: &str,
    selections: &[FieldSelection],
    schema: &CompiledSchema,
) -> Value
Expand description

Project an entity-shaped JSONB value into a GraphQL response object, mirroring the query path’s SQL projection exactly.

This is the single, canonical entity projector, shared by the mutation success arm (projecting the returned entity) and the error arm (projecting the error metadata). It is behaviourally equivalent to the SQL query projection (projection_generator::render_field):

  • output key = the selection’s response key (the camelCase GraphQL surface name, honouring aliases),
  • source key = to_snake_case of the field name (the stored JSONB key), with a camelCase fallback for legacy metadata that used the surface casing,
  • object fields, single or listed, are projected through their sub-selection at any depth. The recursion follows the selection, which max_query_depth bounds, so there is no depth at which a stored sub-object passes through: that fall-through, at a cap of 4, served every stored key of an object five levels down, gated or not. An object field with no sub-selection selects none of its fields and projects to {},
  • scalar fields pass through their stored value,
  • __typename is emitted only where the client selected it.

type_name is the concrete GraphQL object type of entity (resolved by the caller). selections is the result selection set for this level, with inline ... on T fragments preserved.

§An empty selection set projects nothing

It used to return the stored entity unchanged, and that one line is what made #1352 and #1357 invisible: an empty slice is the permissive shape, not a neutral one, so a transport that arrived without a selection set was not refused — it was granted every field of the row, policy-gated ones included, while selection_set_selects_gated_field reported that nothing gated was selected and the #423 field authorizer took zero calls.

Nothing legitimate reaches here empty any more: GraphQL § 5.3.3 refuses a composite field named without a selection set (#1357), every mutation’s return type is composite (#1358), and the write entries take a non-empty WriteSelections. An empty slice arriving here is therefore a defect upstream, and the safe answer to a defect is the empty object — which is also what the read path already answers for a selection set that @skip resolves to nothing, so the two agree.

There is deliberately no project_entity_unfiltered to reach for. Every caller in the tree either has a real selection set or guards emptiness before calling (federation returns early on root.nested_fields.is_empty()), so a named permissive helper would be a public function with no caller — and an unused facility is how the permissive branch stayed reachable in the first place. A caller that genuinely wants the whole entity can write entity.clone() and own that decision where it is made.