gobl

package module
v0.17.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 21, 2022 License: Apache-2.0 Imports: 19 Imported by: 40

README

GOBL

GOBL Logo

Go Business Language.

Released under the Apache 2.0 LICENSE, Copyright 2021,2022 Invopop Ltd..

Introduction

GOBL (pronounced "gobble") is a hybrid solution between a document standard and a software library. On one side it defines a language for business documents in JSON, while at the same time providing a library and services written in Go to help build, validate, sign, and localise.

Traditional software business document standards consist of a set of definitions layered into different namespaces, followed by an independent set of implementations. A well written standard can be implemented anywhere and be compatible with any existing library. In practice however, and especially for XML-base standards, dealing with multiple namespaces adds a lot of complexity.

For GoBL a different approach is being taken. The code and library implementation is in itself the standard. Rather than trying to be flexible at the cost of complexity, GoBL includes everything needed for digital signatures, validation, and local implementations including tax definitions, all maintained under an open source license.

In our opinion, Go is an ideal language for this type of project due to its simple and concise syntax, performance, testing capabilities, and portability. We understand however that Go won't be everyone's cup of tea, so the project is designed to offer a server component (you could call it a microservice) to be launched in a docker container inside your own infrastructure, alongside a JSON Schema definition of all the key documents. Building your own documents and using the GoBL services to validate and sign them should be relatively straight forward.

GoBL Standard

Packages
Documents
Envelope

Serialization

Amounts & Percentages

Marshalling numbers can be problematic with JSON as the standard dictates that numbers should be represented as integers or floats, without any tailing 0s. This is fine for maths problems, but not useful when trying to convey monetary values or rates with a specific level of accuracy. GoBL will always serialise Amounts as strings to avoid any potential issues with number conversion.

ID vs UUID

Traditionally when dealing with databases a sequential ID is assigned to each tuple (document, row, item, etc.) starting from 1 going up to whatever the integer limit is. Creating a scalable and potentially global system however with regular numbers is not easy as you require a single point in the system responsible for ensuring that numbers are always provided in the correct order. Single points of failure are not good for scalability.

To get around the issues with sequential numbers, the UUID standard (Universally Unique IDentifier) was defined as a mechanism to create a very large number that can be potentially used to identify anything.

The GoBL library forces you to use UUIDs instead of sequential IDs for anything that could be referenced in the future. To enforce this fact, instead of naming fields id, we make an effort to call them uuid.

Sometimes sequential IDs are however required, usually for human consumption purposes such as ensuring order when generating invoices so that authorities can quickly check that dates and numbers are in order. Our recommendation for such codes is to use a dedicated service to generate sequential IDs based on the UUIDs, such as Invopop's Sequence Service.

CLI tool

This repo contains a gobl CLI tool which can be used to manipulate GOBL documents from the command line or shell scripts.

gobl build

Build a complete GOBL document from one or more input sources. Example uses:

# Finalize a complete invoice
gobl build invoice.yaml

# Set the supplier from an external file
gobl build invoice.yaml \
    --set-file doc.supplier=supplier.yaml

# Set arbitrary values from the command line. Inputs are parsed as YAML.
gobl build invoice.yaml \
    --set doc.foo.bar="a long string" \
    --set doc.foo.baz=1234

# Set an explicit string value (to avoid interpetation as a boolean or number)
gobl build invoice.yaml \
    --set-string doc.foo.baz=1234 \
    --set-string doc.foo.quz=true

# Set the top-level object:
gobl build invoice.yaml \
    --set-file .=envelope.yaml

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoRegion is used when the envelope is missing a region.
	ErrNoRegion = NewError("no-region")

	// ErrNoDocument is provided when the envelope does not contain a
	// document payload.
	ErrNoDocument = NewError("no-document")

	// ErrValidation is used when a document fails a validation request.
	ErrValidation = NewError("validation")

	// ErrCalculation wraps around errors that we're generated during a
	// call to perform calculations on a document.
	ErrCalculation = NewError("calculation")

	// ErrMarshal is provided when there has been a problem attempting to encode
	// or marshal an object, usually into JSON.
	ErrMarshal = NewError("marshal")

	// ErrSignature identifies an issue related to signatures.
	ErrSignature = NewError("signature")

	// ErrInternal is a "catch-all" for errors that are not expected.
	ErrInternal = NewError("internal")

	// ErrUnknownSchema is provided when we attempt to determine the schema for an object
	// or from an ID and cannot find a match.
	ErrUnknownSchema = NewError("unknown-schema")
)
View Source
var EnvelopeSchema = schema.GOBL.Add("envelope")

EnvelopeSchema sets the general definition of the schema ID for this version of the envelope.

Functions

This section is empty.

Types

type Calculable

type Calculable interface {
	Calculate() error
}

Calculable defines the methods expected of a document payload that contains a `Calculate` method to be used to perform any additional calculations.

type Document

type Document struct {
	Schema schema.ID
	// contains filtered or unexported fields
}

Document helps us handle the document's contents by essentially wrapping around the json RawMessage.

func (*Document) Digest added in v0.17.0

func (p *Document) Digest() (*dsig.Digest, error)

Digest calculates a digital digest using the canonical JSON of the embedded data.

func (*Document) Extract added in v0.17.0

func (p *Document) Extract(doc interface{}) error

Extract will unmarshal the documents contents into the provided object. You'll need have checked the type proviously to ensure this works.

func (*Document) Insert added in v0.17.0

func (p *Document) Insert(doc interface{}) error

Insert places the provided object inside the document and looks up the schema information to ensure it is known.

func (Document) JSONSchema added in v0.17.0

func (Document) JSONSchema() *jsonschema.Schema

JSONSchema returns a jsonschema.Schema instance.

func (*Document) MarshalJSON added in v0.17.0

func (p *Document) MarshalJSON() ([]byte, error)

MarshalJSON satisfies the json.Marshaler interface.

func (*Document) UnmarshalJSON added in v0.17.0

func (p *Document) UnmarshalJSON(data []byte) error

UnmarshalJSON satisfies the json.Unmarshaler interface.

type Envelope

type Envelope struct {
	// Schema identifies the schema that should be used to understand this document
	Schema schema.ID `json:"$schema" jsonschema:"title=JSON Schema ID"`
	// Details on what the contents are
	Head *Header `json:"head" jsonschema:"title=Header"`
	// The data inside the envelope
	Document *Document `json:"doc" jsonschema:"title=Document"`
	// JSON Web Signatures of the header
	Signatures []*dsig.Signature `json:"sigs" jsonschema:"title=Signatures"`
}

Envelope wraps around a gobl document and provides support for digest creation and digital signatures.

func NewEnvelope

func NewEnvelope() *Envelope

NewEnvelope builds a new envelope object ready for data to be inserted and signed. If you are loading data from json, you can safely use a regular `new(Envelope)` call directly.

Example (Complete)
package main

import (
	"encoding/json"
	"fmt"

	"github.com/invopop/gobl"
	"github.com/invopop/gobl/note"
	"github.com/invopop/gobl/uuid"
)

func main() {
	// Prepare a new Envelope with a region
	env := gobl.NewEnvelope()
	env.Head.UUID = uuid.MustParse("871c1e6a-8b5c-11ec-af5f-3e7e00ce5635")

	// Prepare a payload and insert
	msg := &note.Message{
		Content: "sample message content",
	}
	if err := env.Insert(msg); err != nil {
		panic(err.Error())
	}

	data, err := json.MarshalIndent(env, "", "\t")
	if err != nil {
		panic(err.Error())
	}
	fmt.Printf("%v\n", string(data))
}
Output:

{
	"$schema": "https://gobl.org/draft-0/envelope",
	"head": {
		"uuid": "871c1e6a-8b5c-11ec-af5f-3e7e00ce5635",
		"dig": {
			"alg": "sha256",
			"val": "7d539c46ca03a4ecb1fcc4cb00d2ada34275708ee326caafee04d9dcfed862ee"
		}
	},
	"doc": {
		"$schema": "https://gobl.org/draft-0/note/message",
		"content": "sample message content"
	},
	"sigs": []
}

func (*Envelope) Complete added in v0.17.0

func (e *Envelope) Complete() error

Complete is used to perform calculations and validations on the envelopes document if it responds to the Calculable and Validatable interfaces. Behind the scenes, this method will determine the document type, extract, calculate, validate, and then re-insert the potentially updated contents.

func (*Envelope) Extract

func (e *Envelope) Extract(doc interface{}) error

Extract the contents of the envelope into the provided document type.

func (*Envelope) Insert

func (e *Envelope) Insert(doc interface{}) error

Insert takes the provided document, performs any calculations, validates, then serializes it ready for use.

func (*Envelope) Sign

func (e *Envelope) Sign(key *dsig.PrivateKey) error

Sign uses the private key to the envelope headers.

func (*Envelope) Validate

func (e *Envelope) Validate() error

Validate ensures that the envelope contains everything it should to be considered valid GoBL.

func (*Envelope) Verify

func (e *Envelope) Verify() error

Verify ensures the digest headers still match the document contents.

type Error

type Error struct {
	Code  string `json:"code"`
	Cause error  `json:"cause"`
}

Error provides a structure to better be able to make error comparisons. The contents can also be serialised as JSON ready to send to a client if needed.

func NewError

func NewError(code string) *Error

NewError provides a new error with a code that is meant to provide a context.

func (*Error) Error

func (e *Error) Error() string

Error provides a string representation of the error.

func (*Error) Is

func (e *Error) Is(target error) bool

Is checks to see if the target error matches the current error or part of the chain.

func (*Error) WithCause

func (e *Error) WithCause(err error) *Error

WithCause is used to copy and add an underlying error to this one.

func (*Error) WithErrorf

func (e *Error) WithErrorf(format string, a ...interface{}) *Error

WithErrorf wraps around the `fmt.Errorf` call to provide a more meaningful error in the context.

type Header struct {
	// Unique UUIDv1 identifier for the envelope.
	UUID uuid.UUID `json:"uuid" jsonschema:"title=UUID"`

	// Digest of the canonical JSON body.
	Digest *dsig.Digest `json:"dig" jsonschema:"title=Digest"`

	// Seals of approval from other organisations.
	Stamps []*Stamp `json:"stamps,omitempty" jsonschema:"title=Stamps"`

	// Set of labels that describe but have no influence on the data.
	Tags []string `json:"tags,omitempty" jsonschema:"title=Tags"`

	// Additional semi-structured information about this envelope.
	Meta org.Meta `json:"meta,omitempty" jsonschema:"title=Meta"`

	// Any information that may be relevant to other humans about this envelope
	Notes string `json:"notes,omitempty" jsonschema:"title=Notes"`

	// When true, implies that this document should not be considered final. Digital signatures are optional.
	Draft bool `json:"draft,omitempty" jsonschema:"title=Draft"`
}

Header defines the meta data of the body. The header is used as the payload for the JSON Web Signatures, so we want this to be as compact as possible.

func NewHeader

func NewHeader() *Header

NewHeader creates a new header and automatically assigns a UUIDv1.

func (*Header) Validate

func (h *Header) Validate() error

Validate checks that the header contains the basic information we need to function.

type Signatures

type Signatures []*dsig.Signature

Signatures keeps together a list of signatures that we're used to sign the document head contents.

type Stamp

type Stamp struct {
	// Identity of the agency used to create the stamp
	Provider string `json:"prv" jsonschema:"title=Provider"`
	// The serialized stamp value generated for or by the external agency
	Value string `json:"val" jsonschema:"title=Value"`
}

Stamp defines an official seal of approval from a third party like a governmental agency or intermediary and should thus be included in any official envelopes.

type Validatable

type Validatable interface {
	Validate() error
}

Validatable describes a document that can be validated.

type Version added in v0.11.0

type Version string

Version defines the semver for this version of GOBL.

const VERSION Version = "v0.17.0"

VERSION is the current version of the GOBL library.

func (Version) Semver added in v0.11.0

func (v Version) Semver() *semver.Version

Semver parses and returns semver

Directories

Path Synopsis
cmd
internal
es
gb
nl

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL