Using annotations as a mechanism to describe inter-keyword communication #491
Replies: 6 comments 36 replies
-
The |
Beta Was this translation helpful? Give feedback.
-
Going in this direction stems back to Henry's work on the "JSON Schema Framework", evaluation of behaviours and suggestions for improvements. Taking json-schema-org/json-schema-spec#1451 as an example, aka
Henry broke down annotations into three classes, one of which was "explanatory". The suggested solution was so that
This creates a behaviour based understanding of how the keywords interact, and means you are not writing custom interaction code, and can implement the keywords independantly from one another (although you still need to know the order of keyword resolution). Why does this matter? A few reasons. One of the most challening aspects of updating an implementation (so I'm told) is when interactions between keywords change, or new interaction requirements are added. By thinking about requirements of an implementation in terms of behaviours, once you implement the behaviours, you just need to call the behaviour related functions when a keyword needs them. (A real nice separation of concern.) Additionally, someone writing a vocabulary/extension could want to access any of the behaviours. It would make it easier on them to be able to use existing behaviour and not reinvent the wheel. It also makes sure that additional vocabularies have access to the data required to make use of different types of behaviours. |
Beta Was this translation helpful? Give feedback.
-
I think it’s useful to use the phrase “as if” here. Implementation order only needs to behave as if execution was in a certain order, i.e. the user only needs to see the results as if there’s an order. Some language specs use this to allow compiler optimizations. |
Beta Was this translation helpful? Give feedback.
-
I had a thought that stemmed from my analyses presented in this thread above. (I think I just talked myself out of using annotations as the mechanism, but cases like Currently there are multiple kinds of dependencies:
I'm calling (1) a "static" dependency since they can generally be assessed without an instance. I'm calling (2) and (3) "dynamic" dependencies because their assessment requires an instance. (Just thinking out loud a bit for better communication, for those who want to try to parse this.) I was just about to propose that we require keywords which are dynamic dependencies where the dependency is on the evaluation result (e.g. However, defining a third-party keyword that depends on the validation of That means that a keyword that has an evaluation-result dependency (e.g. So the only reason to have an evaluation-result dependency is within a (closed?) multi-keyword system that is designed together. (We wouldn't define I could see a case for independently defining an informative keyword that behaves like I think the solution is for json-schema-org/json-schema-spec#1445 to allow all of
These are the spec-defined mechanisms that keywords may use to interact. * Evaluation-result dependencies and dependents MUST be defined together because the dependency keyword MUST NOT affect local schema validation, and it doesn't make sense to define a keyword that doesn't affect local validation result (and doesn't produce an annotation) unless you have another keyword that depends on that behavior. Second question: does it make sense to restrict third-party vocabularies from inventing their own interaction mechanisms? |
Beta Was this translation helpful? Give feedback.
-
The challenge I have with annotations driving logic of sibling keywords is order of operations. Each of my keywords produces an output node that is processed by the parent schema / keyword. The keywords do not have access to sibling output at the time of evaluation. I realize that this isn't going to happen, but man would it be easier if things were grouped hierarchically.
If structured this way, you'd have keywords which are composed of all co-dependent sub-keywords. For example, an implementation could have their The need to maintain state or re-evaluate for sake of This behavior can be emulated today, with the current structure, using transient nodes but it is confusing. |
Beta Was this translation helpful? Give feedback.
-
I don't know why this discussion is closed. It doesn't say who closed it. But anyway...
Why? Why are annotations necessary at all? I've never found a use for them. They're just an annoying "not-an-error" in result output. The fact that unevaluatedProperties uses annotations is simply because the keywords it depends on (properties etc) happen to produce the annotations that contain precisely the information that unevaluatedProperties needs to make its decision. But why does I'm questioning the conflation of two things which are really separate: annotations as exposed to the user in the result, and annotations as an internal thing for passing state information between keywords and subschemas. |
Beta Was this translation helpful? Give feedback.
-
Related:
I may have jumped the gun in using annotations as a descriptive mechanism for keyword comms without properly seeking opinions.
While I don't want annotations to be prescriptive in this way, I think they're a handy way to describe how keywords interact.
Moreover, I think consistency is important, so (ideally) I'd either like for all dependent keywords to have their behavior described using annotations, or for none to use annotations.
The example I think makes the case for using annotations is
unevaluated*
.I'll just take
unevaluatedProperties
as the example, butunevaluatedItems
has the same problem.unevaluatedProperties
will evaluate properties not covered byproperties
,patternProperties
, oradditionalProperties
found as either a sibling keyword or in passing subschemas of sibling keywords which the keywords themselves produce atrue
validation result. It even works through static ($ref
) and dynamic ($dynamicRef
) references.Statically (without an instance) determining which properties to evaluate is generally impossible.
If
foo
is present, thenbar
must also be present and nothing else; otherwisebaz
must be present and nothing else. This depends on the instance, so static analysis can't determine which propertiesunevaluatedProperties
applies to.Without annotations, evaluating this means that an implementation would have to evaluate
if
/then
/else
, taking note of which properties were evaluated in a passing subschema, then provide that list of properties tounevaluatedProperties
.But that's exactly what the annotations of
properties
,patternProperties
, andadditionalProperties
are already doing. These keywords produce an annotation that is a list of the properties they evaluate. Plus, if their subschemas ultimately fail, the annotations are dropped, so we only get the ones from passing subschemas. Not using annotations is just internalizing the exact same mechanism, except that it requires implementors to "re-invent" that logic. Since the implementation has to track this data anyway, why not describe it using an existing mechanism that does exactly what the implementation needs?Beta Was this translation helpful? Give feedback.
All reactions