Skip to content

Getting started

By the end of this you will have validated a message against an embedded schema, seen a non-conforming payload rejected, and seen an unresolvable one pass with a counter incremented. No registry is running at any point, which is the design rather than a shortcut.

1. Install

$ go get gitlab.com/phpboyscout/go/schema

2. Embed a schema

package orders

import (
    _ "embed"

    "gitlab.com/phpboyscout/go/cloudevents/urn"
    "gitlab.com/phpboyscout/go/schema"
    "gitlab.com/phpboyscout/go/schema/jsonschema"
)

//go:embed schemas/orders.created.v3.json
var ordersCreated []byte

func registry() (*schema.Registry, error) {
    return schema.Embed(schema.Schema{
        URN:      urn.Schema{Name: "orders.created", Version: 3},
        Language: schema.JSONSchema,
        Bytes:    ordersCreated,
    })
}

Embed registers a no-op checker for each language it sees, so you do not have to know which checkers to wire.

3. Build a guard

reg, err := registry()
if err != nil {
    return err
}

guard := schema.NewGuard(reg, []schema.Validator{jsonschema.New()})

The validator lives in its own package so that a service validating JSON does not carry a protobuf runtime, and vice versa. The registry itself needs neither: to a store, a schema is bytes.

4. Validate

err = guard.Validate(ctx, "urn:phpboyscout:schema:orders.created:3", []byte(`{"order":"4172"}`))
// nil

err = guard.Validate(ctx, "urn:phpboyscout:schema:orders.created:3", []byte(`{}`))
// errors.Is(err, schema.ErrInvalidPayload) — the schema resolved, and this does not match

5. See it fail open

Ask for a version nothing holds:

err = guard.Validate(ctx, "urn:phpboyscout:schema:orders.created:9", []byte(`{}`))
// nil — the message goes through

guard.Unvalidated()
// 1

That is the asymmetry the whole module is built around. Could not resolve fails open and is counted; resolved and did not match fails closed. Read Why it fails open for the reasoning, and Alert on unvalidated messages for what to do with the counter.

6. Wire it into a CloudEvents handler

e, err := cloudevents.Unmarshal(msg.Header, msg.Data)
if err != nil {
    logger.Error("refused a CloudEvent", "id", e.ID, "err", err)

    return
}

if err := guard.Validate(ctx, e.DataSchema, e.Data); err != nil {
    logger.Error("payload does not match its schema", "id", e.ID, "schema", e.DataSchema, "err", err)

    return
}

handle(e)

Where next