How do I give my JSON schema an absolute URL for its $id when I haven't published it yet because it hasn't been tested yet?

Issue

I’m putting together JSON schemas and I’d like to use $ref to DRY my schemas. I’ll have many schemas that will each use common subschemas. I want to unit test my schemas before publishing them by writing unit tests that assert that, given certain input, the input is deemed valid or invalid, using a JSON schema library that I trust to be correct (so that I’m just testing my schemas, not the library).

Where I get confused is that in order to load my schemas before I’ve published them (which I want to do while running tests locally and during CI/CD), I need to use relative local paths like this:

"pet": { "$ref": "file://./schemas/components/pet.schema.json" }

That’s because that pet schema hasn’t been published to a URL yet. It hasn’t been verified by automated tests that it’s correct yet. This works well enough for running tests, and it also worked well for packaging inside a Docker image so that the schemas could be loaded from disk as the app starts up.

But then, if I were to give one of the top level schemas to someone (that leverages $ref) after publishing it to an absolute URL, it wouldn’t load in their program because of that path that I used that worked only for my unit testing.

I found that I had to publish my schemas using absolute URLs in order for them to be used in consuming programs. I ended up publishing the schemas https://mattwelke.github.io/go-jsonschema-ref-docker-example/schemas/person.1-0-0.schema.json and https://mattwelke.github.io/go-jsonschema-ref-docker-example/schemas/components/pet.1-0-0.schema.json that way. I tested that they worked fine in a consuming program by writing the program:

package main

import (
    "fmt"

    "github.com/xeipuuv/gojsonschema"
)

func main() {
    schemaLoader := gojsonschema.NewReferenceLoader("https://mattwelke.github.io/go-jsonschema-ref-docker-example/schemas/person.1-0-0.schema.json")

    jsonStr := `
    {
        "name": "Matt",
        "pet": {
            "name": "Shady"
        }
    }
    `

    documentLoader := gojsonschema.NewStringLoader(jsonStr)

    result, err := gojsonschema.Validate(schemaLoader, documentLoader)
    if err != nil {
        panic(fmt.Errorf("could not validate: %w", err))
    }

    if result.Valid() {
        fmt.Printf("The document is valid.\n")
    } else {
        fmt.Printf("The document is not valid. See errors:\n")
        for _, desc := range result.Errors() {
            fmt.Printf("- %s\n", desc)
        }
    }
}

Which resulted in the following expected output:

The document is valid.

So I’m confused about this "chicken and egg" situation.

I was able to publish schemas that could be used, as long as I didn’t unit test them before publishing them.

And I was able to unit test schemas as long as:

  • I didn’t want to publish them in the form that was verified by the unit testing to be correct.
  • I’m okay with my application loading them via HTTPS as it starts up instead of loading them from disk. I’m worried about this because I don’t want a web server to be a point of failure for my app starting up.

I would appreciate some insight in how one might accomplish both goals.

Solution

Where I get confused is that in order to load my schemas before I’ve published them (which I want to do while running tests locally and during CI/CD), I need to use relative local paths

Your initial assumption is false. URIs used in the $id keyword can be arbitrary identifiers — they do not need to be resolvable via the network or disk at the stated location. In fact, it is an error for a JSON Schema implementation to assume to find schema documents at the stated location: they MUST support being able to load documents locally and associate them with the stated identifier:

The "$id" keyword identifies a schema resource with its canonical URI.

Note that this URI is an identifier and not necessarily a network locator. In the case of a network-addressable URL, a schema need not be downloadable from its canonical URI.

source

A schema need not be downloadable from the address if it is a network-addressable URL, and implementations SHOULD NOT assume they should perform a network operation when they encounter a network-addressable URI.

source

Therefore, you can give your schema document any identifier you like, such as the URI you anticipate using when you eventually publish your schema for public consumption, and perform local testing using that identifier.

Any implementation that does not support doing this is in violation of the specification, and this should be reported to its maintainers as a bug.

Answered By – Ether

Answer Checked By – Marilyn (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.