normal

package
v0.0.0-...-5858cc9 Latest Latest
Warning

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

Go to latest
Published: Jul 13, 2023 License: BSD-3-Clause Imports: 3 Imported by: 0

README

Normal

Normal is design to make normalizing fields in a Coze payload easy.

A normal is an arrays of fields specifying the normalization of a payload. Normals may be chained to represent various combinations of normalization. There are five types of normals plus a nil normal.

See the documentation in normal.go

Example

Example code (ExampleIsNormalNeedOption) can be found in normal_test.go.

Form Example

Given an arbitrary form with the following requirements.

The application requires "id", which is the user's account id for the application.

The Normal is:

Need{"id"}

Also, the form has optional fields in the form, such as: "display_name", "first_name", "last_name", "email", "address_1", "address_2", "phone_1", "phone_2", "city", "state", "zip", and "country".

The optional fields as a Normal would is the following:

Option{"display_name", "first_name", "last_name", "email", "address_1", "address_2", "phone_1", "phone_2", "city", "state", "zip", "country"}

Since the application uses Coze to sign messages, the application decides to require the standard Coze fields ("alg", "iat", "tmb", "typ")

Need{"alg", "iat", "tmb", "typ"}

The two needs can be summed, and the resulting need is concatenated to the option. The full Normal chain becomes:

[
Need{"id", "alg", "iat", "tmb", "typ"},
Option{"display_name", "first_name", "last_name", "email", "address_1", "address_2", "phone_1", "phone_2", "city", "state", "zip", "country"}
]

The following payload would return 'true', indicating the payload is considered to be a normal payload:

{
	"alg": "ES256",
	"iat": 1647357960,
	"tmb": "L0SS81e5QKSUSu-17LTQsvwKpUhBxe6ZZIEnSRV73o8",
	"typ": "cyphr.me/user/profile/update",
	"id": "L0SS81e5QKSUSu-17LTQsvwKpUhBxe6ZZIEnSRV73o8",
	"city": "Pueblo",
	"country": "ISO 3166-2:US",
	"display_name": "Mr. Dev",
	"first_name": "Dev Test",
	"last_name": "1"
 }

While the following payload would return 'false', since it is missing the required 'id' field.

{
	"alg": "ES256",
	"iat": 1647357960,
	"tmb": "L0SS81e5QKSUSu-17LTQsvwKpUhBxe6ZZIEnSRV73o8",
	"typ": "cyphr.me/user/profile/update",
	"city": "Pueblo",
	"country": "ISO 3166-2:US",
	"display_name": "Mr. Dev",
	"first_name": "Dev Test",
	"last_name": "1"
 }

Normal does not check that a payload is cryptographically valid, but is useful for JSON field validation. On the other hand, a cryptographically verified payload may not guarantee that the 'pay' has the required fields present for an application's specific endpoint.

Pairing verification and normal allows for better and potentially more optimized validation of incoming payloads.

How does Normal relate to Coze?

We consider Normal to be apart of Coze Standard and not Coze core.

  1. Coze Core
  2. Coze Standard
  3. Coze Experimental

Documentation

Overview

Normal

A normal is an arrays of fields specifying the normalization of a payload. Normals may be chained to represent various combinations of normalization. Normals are implemented in Go as []string. There are five types of normals plus a nil normal.

canon       (can)
only        (ony)
option      (opt)
need        (ned)
extra       (ext)
(nil)

Canon requires specified fields in the given order and prohibits extra fields.

Only requires specified fields in any order and prohibits extra fields.

Option permits specified fields in any order and prohibits extra fields.

Need requires specified fields in any order and permits extra fields.

Extra denotes extra fields are permitted in a location in the normal chain.

A nil normal is valid and matches all payloads.

Repeated field names are allowed among normals and normal chains, but Coze itself prohibits duplicates.

Normal Chaining

Normals may be chained. A chained normal moves a record pointer up.

  • A Need in a chain is equivalent to a [Need, Extra].
  • Options in order may be given by chaining options together.
  • An Extra containing fields has no addition meaning over an empty Extra.

Notable Combinations: - A an empty Canon or Only ("[]") matches only an a empty (i.e. `{}`) payload. - An empty Need or Option does nothing. - If need can appear before or after another normal, call IsNormal twice: a IsNormal(r, Need{a}), IsNormal(r, Canon{"b","c"}})

Normals are in two groups, exclusive and permissive. Exclusive allows only listed fields. Permissive allows fields other than listed.

      ┌────────────────┐
      │     Normal     │
      └───────┬────────┘
      ┌───────┴────────┐
┌─────┴─────┐    ┌─────┴──────┐
│ Exclusive │    │ Permissive │
└───────────┘    └────────────┘

Grouping

-Exclusive
  -canon
  -only
  -option
-Permissive
  -need
  -extra

Index

Examples

Constants

This section is empty.

Variables

View Source
var Standard = []Normal{"alg", "iat", "tmb", "typ"}

Standard is the standard coze.pay fields. Custom fields may be appended after standard. e.g. `normID := append(standard, "id")`.

Functions

func IsNormal

func IsNormal(pay json.RawMessage, norm ...Normaler) (bool, error)

IsNormal checks if a Coze is normalized according to the given normal chain. Normals are interpreted as a chain that progress a record pointer based on normal rules. See notes on Normal. Parameters may be nil.

Example (Canon)
fmt.Println("\nCanon")

// Canon empty with empty records, true.
v, _ = IsNormal([]byte(`{}`), Canon{})
fmt.Println(v)

// Canon in order, Canon in order, ending nil with no record (variadic), true.
v, _ = IsNormal(az, Canon{"a"}, Canon{"z"}, nil)
fmt.Println(v)

// Canon in order, Canon in order, ending nil with record (variadic), true.
v, _ = IsNormal(ayz, Canon{"a"}, Canon{"y"}, nil)
fmt.Println(v)

// Canon in order, true.
v, _ = IsNormal(az, Canon{"a", "z"})
fmt.Println(v)

// Canon in order variadic, true.
v, _ = IsNormal(az, Canon{"a"}, Canon{"z"})
fmt.Println(v)

// Canon in order with Only in order (variadic), true.
v, _ = IsNormal(az, Canon{"a"}, Only{"z"})
fmt.Println(v)

// Canon in order with Extra (variadic), true.
v, _ = IsNormal(az, Canon{"a"}, Extra{})
fmt.Println(v)

// Canon in order with Option missing (variadic), true.
v, _ = IsNormal(az, Canon{"a", "z"}, Option{"b"})
fmt.Println(v)

// Canon with Extra (not present) and Canon (variadic), true.
v, _ = IsNormal(az, Canon{"a"}, Extra{}, Canon{"z"})
fmt.Println(v)

// Canon with Extra not present and Canon (variadic), true.
v, _ = IsNormal(ayz, Canon{"a"}, Extra{}, Canon{"y", "z"})
fmt.Println(v)

// Canon with Extra present and Canon (variadic), true.
v, _ = IsNormal(ayz, Canon{"a"}, Extra{}, Canon{"z"})
fmt.Println(v)

// Canon empty with records, false.
v, _ = IsNormal(az, Canon{})
fmt.Println(v)

// Canon in order, Canon in order, extra field, false.
v, _ = IsNormal(ayz, Canon{"a"}, Canon{"y"})
fmt.Println(v)

// Canon out of order, false.
v, _ = IsNormal(az, Canon{"z", "a"})
fmt.Println(v)

// Canon (correct) succeeded by extra (incorrect), false.
v, _ = IsNormal(az, Canon{"a"})
fmt.Println(v)

// Canon in order (correct) with Only missing (incorrect) (variadic), false.
v, _ = IsNormal(az, Canon{"a"}, Only{"b"})
fmt.Println(v)

// Canon amd Canon with extra field inbetween (variadic), false.
v, _ = IsNormal(ayz, Canon{"a"}, Canon{"z"})
fmt.Println(v)

// Canon with Extra (not present) and Canon and with succeeding extra (variadic), false.
v, _ = IsNormal(ayz, Canon{"a"}, Extra{}, Canon{"y"})
fmt.Println(v)

// Canon with extra (not present, incorrect) and Extra (variadic)(Checks for panic on out of bounds), false.
v, _ = IsNormal(az, Canon{"a", "z", "y"}, Extra{})
fmt.Println(v)
Output:

Canon
true
true
true
true
true
true
true
true
true
true
true
false
false
false
false
false
false
false
false
Example (Need)
fmt.Println("\nNeed")

// Need empty with empty records, true.
v, _ = IsNormal([]byte(`{}`), Need{})
fmt.Println(v)

// Need empty with records, true.
v, _ = IsNormal(az, Need{})
fmt.Println(v)

// Need with extra second field, true.
v, _ = IsNormal(az, Need{"a"})
fmt.Println(v)

// Need with extra first field, true.
v, _ = IsNormal(az, Need{"z"})
fmt.Println(v)

// Need in order, true.
v, _ = IsNormal(az, Need{"a", "z"})
fmt.Println(v)

// Need out of order, true.
v, _ = IsNormal(az, Need{"a", "z"})
fmt.Println(v)

// Need in order with extra, true.
v, _ = IsNormal(ayz, Need{"a", "y"})
fmt.Println(v)

// Need in order with extra and Canon, true.
v, _ = IsNormal(ayz, Need{"a"}, Canon{"z"})
fmt.Println(v)

// Need with option present, true.
v, _ = IsNormal(ayz, Need{"a", "y"}, Option{"z"})
fmt.Println(v)

// Need, extra field, then option, true.
v, _ = IsNormal(ayz, Need{"a"}, Option{"z"})
fmt.Println(v)

// Need missing field, false.
v, _ = IsNormal(az, Need{"a", "y", "z"})
fmt.Println(v)

// Option present, Need repeated, false.
v, _ = IsNormal(az, Option{"a"}, Need{"a"})
fmt.Println(v)

// Need with and Canon and extra, false.
v, _ = IsNormal(ayz, Need{"a"}, Canon{"y"})
fmt.Println(v)
Output:

Need
true
true
true
true
true
true
true
true
true
true
false
false
false
Example (Nil)

ExampleIsNormal_nil shows the nil and zero case.

v, _ = IsNormal(nil, nil)
fmt.Println(v)

// Nil matches empty JSON, true.
v, _ = IsNormal([]byte(`{}`), nil)
fmt.Println(v)

// Nil Normal matches everything, true.
v, _ = IsNormal(az, nil)
fmt.Println(v)
Output:

false
true
true
Example (Only)
fmt.Println("\nOnly")

// Only empty with empty records, true.
v, _ = IsNormal([]byte(`{}`), Only{})
fmt.Println(v)

// Only in order, true.
v, _ = IsNormal(az, Only{"a", "z"})
fmt.Println(v)

// Only out of order, true.
v, _ = IsNormal(az, Only{"z", "a"})
fmt.Println(v)

// Only in order variadic, true.
v, _ = IsNormal(az, Only{"a"}, Only{"z"})
fmt.Println(v)

// Only empty with records, false.
v, _ = IsNormal(az, Only{})
fmt.Println(v)

// Only with extra field, false.
v, _ = IsNormal(az, Only{"a", "y", "z"})
fmt.Println(v)
Output:

Only
true
true
true
true
false
false
Example (Option)
fmt.Println("\nOption")

// Option empty with empty records, true.
v, _ = IsNormal([]byte(`{}`), Option{})
fmt.Println(v)

// Option with optional one field missing, true.
v, _ = IsNormal(az, Option{"a", "z", "x"})
fmt.Println(v)

// Two Options, true.
v, _ = IsNormal(az, Option{"a"}, Option{"z"})
fmt.Println(v)

// Three Options with last missing, true.
v, _ = IsNormal(az, Option{"a"}, Option{"z"}, Option{"x"}) // TODO
fmt.Println(v)

// Option with field missing and Extra, true.
v, _ = IsNormal(az, Option{"b"}, Extra{})
fmt.Println(v)

// Option (field missing) with canon present (variadic), true.
v, _ = IsNormal(ayz, Option{"b"}, Canon{"a", "y", "z"})
fmt.Println(v)

// Option in order with optional field missing and variadic, true.
v, _ = IsNormal(az, Option{"a"}, Option{"z", "x"})
fmt.Println(v)

// Option with canon present (variadic), true.
v, _ = IsNormal(ayz, Option{"a"}, Canon{"y", "z"})
fmt.Println(v)

// Option missing with Canon (variadic), true.
v, _ = IsNormal(az, Option{"b"}, Canon{"a", "z"})
fmt.Println(v)

// Need with option missing, true.
v, _ = IsNormal(ayz, Need{"a"}, Option{"b"})
fmt.Println(v)

// Option empty with records, false.
v, _ = IsNormal(az, Option{})
fmt.Println(v)

// Option with extra field, false.
v, _ = IsNormal(az, Option{"a"})
fmt.Println(v)

// Extra field then option, false.
v, _ = IsNormal(az, Option{"z"})
fmt.Println(v)

// Option out of order with optional field missing and variadic, false.
v, _ = IsNormal(az, Option{"z"}, Option{"x", "a"})
fmt.Println(v)

// Option with extra pay field, false.
v, _ = IsNormal(ayz, Option{"a", "y"})
fmt.Println(v)

// Need, option,then extra field, false.
v, _ = IsNormal(ayz, Need{"a"}, Option{"y"})
fmt.Println(v)
Output:

Option
true
true
true
true
true
true
true
true
true
true
false
false
false
false
false
false

func IsNormalNeedOption

func IsNormalNeedOption(pay json.RawMessage, need Need, option Option) (bool, error)

IsNormalNeedOption is a helper for a special case.

If a need's fields and an option's fields can be intermixed, the need is checked first and matching fields subtracted from records. Then the option is called with the subset.

Another (alternative) method that's not implemented in this function: IsNormal may be called twice. Once with the need(s), and a second time with the option(s) concatenated with the need(s). This is logically equivalent to subtracting the need.

Example
standard := `{
		"alg": "ES256",
		"iat": 1647357960,
		"tmb": "L0SS81e5QKSUSu-17LTQsvwKpUhBxe6ZZIEnSRV73o8",
		"typ": "cyphr.me/user/profile/update",
		`
required := `"id": "L0SS81e5QKSUSu-17LTQsvwKpUhBxe6ZZIEnSRV73o8",`
optional := `
	"city": "Pueblo",
	"country": "ISO 3166-2:US",
	"display_name": "Mr. Dev",
	"first_name": "Dev Test",
	"last_name": "1"
 }`
need := Need{"alg", "iat", "tmb", "typ", "id"}
option := Option{"display_name", "first_name", "last_name", "email", "address_1", "address_2", "phone_1", "phone_2", "city", "state", "zip", "country"}
v, _ = IsNormalNeedOption([]byte(standard+required+optional), need, option)
fmt.Println(v)
// Missing required field 'id'.
v, _ = IsNormalNeedOption([]byte(standard+optional), need, option)
fmt.Println(v)
Output:

true
false

func IsNormalUnchained

func IsNormalUnchained(pay json.RawMessage, norm ...Normaler) (bool, error)

IsNormalUnchained is a helper to run a slice of normals individually and not as a chain. Passing an 'option' will return false unless the given pay only has one field, and it is the 'option'.

Example
v, _ = IsNormalUnchained(az, Need{"a"}, Need{"z"})
fmt.Println(v)
v, _ = IsNormalUnchained(ayz, Need{"a"}, Need{"z"}, Need{"y"})
fmt.Println(v)
v, _ = IsNormalUnchained(az, Need{"a"}, Option{"z"})
fmt.Println(v)
Output:

true
true
false

func Merge

func Merge[T ~[]Normal](norms ...T) any

Merge merges the given normals.

Example
fmt.Printf("%v\n", Merge(Canon{"a", "b"}, Canon{"c", "d"}, Canon{"e", "f"}))

// When merging with Normals of different type, all type need to be the same
// type.  The following casts Only as a Canon.
fmt.Printf("%+v", Merge(Canon{"a", "b"}, Canon{"c", "d"}, Canon{"e", "f"}, Canon(Only{"g", "h"})))
Output:

[a b c d e f]
[a b c d e f g h]

func Type

func Type(norm Normaler) string

Type returns the type for a given Normaler including a case for []Normal.

Example
fmt.Println(Type(Canon{}))
fmt.Println(Type(Only{}))
fmt.Println(Type(Option{}))
fmt.Println(Type(Need{}))
fmt.Println(Type(Extra{}))
Output:

canon
only
option
need
extra

Types

type Canon

type Canon []Normal

func (Canon) Len

func (n Canon) Len() int

func (Canon) Normal

func (n Canon) Normal() []Normal

type Extra

type Extra []Normal

func (Extra) Len

func (n Extra) Len() int

func (Extra) Normal

func (n Extra) Normal() []Normal

type Need

type Need []Normal

func (Need) Len

func (n Need) Len() int

func (Need) Normal

func (n Need) Normal() []Normal

type Normal

type Normal string

func Append

func Append(n, m []Normal) []Normal

TODO make append generic

Example (Canon)
fmt.Printf("%v\n", Append(Canon{"a", "b"}, Canon{"c", "d"}))
Output:

[a b c d]

type Normaler

type Normaler interface {
	Len() int
	Normal() []Normal
}

type Only

type Only []Normal

func (Only) Len

func (n Only) Len() int

func (Only) Normal

func (n Only) Normal() []Normal

type Option

type Option []Normal

func (Option) Len

func (n Option) Len() int

func (Option) Normal

func (n Option) Normal() []Normal

Jump to

Keyboard shortcuts

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