Skip to content

Eliminate template pre-processing #142

Closed
@handrews

Description

@handrews

NOTE: I'm splitting this out from #52 in order to get more attention on this specific point and the related pull request #129 . I'll probably split other stuff out from #52 and then close it. In hindsight, it was not a good idea to dump everything together there.

URI Template pre-processing is confusing, significantly complicates implementation, and does not address all limitations in the current rules for filling out templates with instance data.

The current approach tries to circumvent the URI Template spec's variable name limitations within the actual string used to express the URI Template. A simpler yet more powerful approach is to make the URI Template strings normal URI Templates, and use a mapping object to translate legal URI Template names to expressions that can identify any part of the instance. The proposed keyword for the map is hrefVars.

Relative JSON Pointers come closest to meeting the necessary requirements: starting from any point in the instance (specifically, the point from which the LDO including the template is defined), they can identify nearly any other point in the instance (see #115 for limitations with respect to arrays). See #126 for a discussion of whether Relative JSON Pointers should be a separate I-D or should start as part of JSON Schema. Either would work fine for this proposal.

To preserve the current behavior when preprocessing is not needed, if a template variable "x" does not appear in hrefVars, it may be considered present with a relative pointer of "0/x".

Examples based on current pre-processing features

Here is a subset of the table of examples for pre-processing, followed by a schema showing links using these variable names.

Input Output
"{(escape space)}" "{escape%20space}"
"{(a (b)))}" "{a%20%28b%29}
"{()}" "{%65mpty}
"{+$*}" "{+%73elf*}
{
    "links": [
        {
            "rel": "foo",
            "href": "/{(escape space)}/{(a (b))}/{()}"
        },
        {
            "rel": "bar",
            "href": "/{+$*}"
        }
    ]
}

Note that making use of $ in the "self" case with the "bar" link requires using the URI Template "+" operator to allow percent-encoded sequences, as the $ is replaced by a percent-encoded sequence during pre-processing. This is particularly confusing since, without pre-processing, the "+" would make the "$" a literal dollar sign that did not need to be percent-encoded.

Given this instance:

{
    "escape space": "x",
    "a (b)": "y",
    "": "z"
}

the "foo" link would expand to "/x/y/z"

Given an instance of [1, 2, 3, 4] the "bar" link would expand to "/1/2/3/4" (the "*" suffix is a URI Template "explode" operator which interprets each list element as a path component).

Here is what a mapping approach might look like, which would produce the same results when applied to the same two example instances.

{
    "links": [
        {   
            "rel": "foo",
            "href": "/{space}/{complicated}/{empty}",
            "hrefVars": {
                "space": "0/escape space",
                "complicated": "0/a (b)",
                "empty": "0/"
            }   
        },  
        {   
            "rel": "bar",
            "href": "/{self*}",
            "hrefVars": {
                "self": "0" 
            }   
        }   
    ]   
}

The hrefVars keyword defines the map for href. Note that we no longer need the "+" operator at all. URI Template operators such as the "*" suffix are not part of the template variable name and therefore do not appear in the map.

Examples that are impossible with current pre-processing

Given this schema:

{
    "type": "object",
    "properties": {
        "foo": {"type": "string"},
        "bar": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "x": {"type": "number"},
                    "y": {
                        "type": "object",
                        "properties": {"z": {"type": "boolean"}}
                    }
                },
                "links": [{
                    "rel": "item",
                    "href": "/{foo}/{x}/{z}",
                    "hrefVars": {
                        "foo": "1/foo",
                        "z": "0/y/z"
                    }
                }]
            }
        }
    }
}

Applying it to this instance:

{
    "foo": "oof",
    "bar": [{"x": 42, "y": {"z": true}}, {"x": 0, "y": {"z": false}}]
}

would produce an item link of "/oof/42/true" for the first array element, and "/oof/0/false" for the second. Note that since "x" was not in hrefVars it is treated as if mapped to "0/x", which produces the same behavior as the current specification for non-preprocessed variables.

This demonstrates the support for referencing data in enclosing and nested instances.

In conclusion

Mapping is much easier to implement and much easier to read than pre-processing. As proposed, it offers a superset of the current functionality. The main implementation concern would be introducing Relative JSON Pointers, but they are required for several proposals currently under consideration.

In my experience on past projects, schema authors found variable mapping easy to work with. It was not a significant source of confusion or bugs, either in schemas or in the code that processed the schemas using this feature.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions