Repository navigation
feat: pick definitions the selected fields do not reference - #231
Draft
barisakcam wants to merge 9 commits into
Draft
barisakcam wants to merge 9 commits into
barisakcam wants to merge 9 commits into
Conversation
Add @retainedDefinitions, a directive on the selection query that keeps scalars, directives and enums the selected fields do not reference, and narrows a retained enum to the values it lists. An empty list keeps every definition of that kind; an absent argument leaves today's behaviour, so a query without the directive filters exactly as before. The directive is defined by s2dm rather than by the model, so it is taken off the document before the document is validated against the schema. Removing an enum value that a retained default or directive argument needs is refused. Instance tag exclude entries are not treated as references: an entry asserts that one instance is absent rather than consuming a value, so an entry naming a removed value is dropped as vacuous. Entries naming no removed value are left alone, so a misspelled entry still fails at expansion.
Cover @retainedDefinitions in the selection query section: the three forms each argument takes, selecting enum values, and what narrowing an instance tag dimension does to the generated instances.
A selection that removes the last surviving combination left the container type empty with nothing said. The generated JSON Schema then carries no properties alongside additionalProperties false, which admits only the empty object, so the filtered schema rejects the data it was derived from. Warn rather than fail: the same state is reachable by excluding every instance in the model, and that has always been allowed.
Enums can no longer be narrowed to a subset of their values. Selecting values let a query change how an instance tag unfolds, which is what @instanceTag(exclude: ...) is for and belongs to the model rather than to the query. Picking an enum now keeps it whole, so filtering never changes what an enum means. The enums argument becomes a plain list of names like the other two, which retires the EnumSelection scalar. Nothing can remove a value any more, so the rule refusing removals that a retained default still needed, and the rule dropping instance tag exclude entries left vacuous by a removal, both go with it. The warning for an instance tag that expands to no instances goes too: a selection can no longer empty the product, and reaching that state by excluding every instance in the model has always been allowed.
A query that only picks definitions has no fields to name, but GraphQL
rejects an empty selection set. A schema of units, enums or shared
directives hits this immediately, since it carries nothing selectable.
Read `{}` as `{ __typename }`, which every type carries and which names
nothing in the model. The rewrite runs only after a parse failure and only
on a trailing empty pair, so a query broken for any other reason still
reports its own error. All four places that read a selection query share
the parser, including the one that reads a vendored dependency's query.
Report a name given under the wrong argument as such. A directive and a
type are looked up in different places, so neither is found where the
other is named, and "not defined in the model" was the only thing the
check could say about either. It now names the argument that would accept
the value instead.
Consolidate the two mirrored validation paths into one that asks where a name actually belongs and compares that against where it was written, so the three arguments share a single code path and a set of messages. Replace the ALL magic string with a typed sentinel, so a selection can no longer be built from an arbitrary string, and take the argument names from constants rather than repeating them at each use. Name the intermediate values that were previously nested inside calls, give the directive and type parameters their real types instead of Any, and stop using "selection" for both a picked name list and a query's selection set.
The editor validated the selection query against the model alone, so every query using @pick was marked invalid and the apply button stayed disabled: the directive is unknown to the model, and an empty selection set does not parse. Read the query the way the server does — treat empty braces as selecting nothing, and strip @pick before validating — and give the editor's schema the directive definition so it stops underlining it. That schema is built for validation and completion only and never leaves the browser. Drop a query root left with no fields from printed output. Selecting only definitions leaves nothing on it, and an object type with no fields is invalid GraphQL, which graphql-js rejects even though graphql-core accepts it. Without the root the output is a fragment that composes into a model with one, which is the shape the vocabulary it was filtered from already has.
Stop stripping @pick in the playground before validating. The editor's schema defines the directive now, so stripping it only hid mistakes inside it: an unknown argument, a non-name in a list, and @pick on a mutation were all accepted in the editor and rejected by the server. Refuse a name GraphQL reserves for introspection. Such a name passed validation, because the introspection types are in the type map, and was then dropped when the picks were collected, so the user got neither the definition nor an error. Read @pick from the one query a selection file holds, rather than walking every operation to find the first. A @pick on a later operation now stays in the document, where validation rejects it as unknown. Give the extraction and its validation a single entry point so a caller cannot do one without the other, print inside the function that drops an empty query root, and cover the kinds a selection can name with a test that fails when one is added without being handled.
Removing the root with a line-anchored substitution on printed output left behind anything the regex could not see. A documented root left its description, which the next definition then silently claimed, and a root named something other than Query left the schema block that named it, so the output no longer rebuilt. Print a schema rebuilt without the root instead, carrying the model's own description across, which takes both with it and retires the constraint that this had to run before directives were reattached. Resolve a picked name against every namespace that defines it. Types and directives are separate namespaces, so a scalar sharing a name with a directive was unreachable: picking it under scalars was refused, and the message sent the user to directives, which keeps the directive instead.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat: pick definitions the selected fields do not reference
What
Adds
@pick, a directive on the selection query that keeps definitions the selected fields do not reference — scalars, directives and enums that dependency-based filtering would otherwise drop.Each argument takes three forms:
scalars: []— every definition of that kind is kept.An enum is always kept whole. Filtering never changes what an enum means, and
@instanceTag(exclude: ...)remains the way to steer how an instance tag unfolds.A query that only picks definitions has no fields to name, so an empty selection set is read as selecting nothing. That makes definition-only schemas filterable for the first time: on the QUDT units vocabulary,
@pick(enums: [...], directives: []) {}reduces 712 enums to the three a model actually needs.Why
A selection query could choose fields but had no way to retain an unreferenced definition, so filtered output silently lost scalars and directives that models still depended on.
Notes for review
@pickproduces byte-identical output to before.fd3d829. It let a query change how an instance tag unfolds, which belongs to the model rather than to the query.Testing
21 tests in
tests/test_pick.pyover a fixture covering picking, validation, the empty selection set, and equivalence with{ __typename }. Full suite: 892 passing,mypyand pre-commit clean.