stdmeta - anssihalmeaho/funl GitHub Wiki

stdmeta

Implements support for metadata ("data about data") usage in FunL. By defining data structures to act as specifications to other data client can validate data against specification and document data. Checkings/documentation are targeting map values (currently).

Purpose of stdmeta is to provide:

  • kind of type checks in runtime
  • documenting contents of map
  • not needing to check that map fullfills certain requirements in many places
  • to implement polymorphic "interfaces" or "protocols"

Field specifications

Requirements are based on checking map key-values so that for given key the value is requirement for corresponding value in target map. Let's call those key-values as fields in this context. Requirements for data are defined as lists where:

  1. First item is string defining requirement type
  2. Rest of items are dependent of requirement type

Requirement types are:

Requirement type Rest of list Meaning
'required' none This field is required to exist
'type' name of required type (string) as 2nd item Field needs to be of this type
'in' allowed values for field Field value needs to be one of given ones
'map' Sub-specification for field (map) Field value is validated against given sub-specification
'doc' documentary texts (string) No meaning in validation, purely for documenting purposes

Requirements for one field are defined as list of requirements (which are lists) so that all given requirements need to be fulfilled for field.

At top level specification is of type: list('map' <specification-map>).

Functions

validate

Validates data against specification.

type: function

Format:

call(stdmeta.validate <specification-map> <target-data-map>) -> list(validation-ok error-messages)

Return value is list containing:

  1. Validation result (true if ok, false if failed)
  2. Error message list (all failed validation explanations as strings)

get-doc

Gets documentation for given specification as string.

type: function

Format:

call(stdmeta.get-doc <specification-map>) -> string

Examples

Example: Person data structure: Validation ok

Printing specification and validating data:

	import stdmeta

	name-schema = map(
		'FirstName' list(list('required') list('type' 'string') list('doc' 'First name'))
		'LastName'  list(list('required') list('type' 'string') list('doc' 'Last name'))
	)

	person-schema = list('map' map(
		'Name'   list(list('required') list('map' name-schema) list('doc' 'Persons name'))
		'Age'    list(list('required') list('type' 'int') list('doc' 'Persons age as numeric value'))
		'Gender' list(list('in' 'man' 'woman') list('doc' 'Gender of person'))
	))

	person = map(
		'Name'   map('FirstName' 'Jack' 'LastName' 'Dalton')
		'Age'    50
	)

	_ = print(call(stdmeta.get-doc person-schema) '\n')
	ok msglist = call(stdmeta.validate person-schema person):

	import stdfu
	output-list = call(stdfu.apply msglist func(item) plus('\n\t' item '\n') end)
	if(ok
		'its ok'
		plus('problems: ' output-list:)
	)

->
map:
Name : required,
    map:
    FirstName : required, type: string,
      -> First name,
    LastName : required, type: string,
      -> Last name,
  -> Persons name,
Age : required, type: int,
  -> Persons age as numeric value,
Gender : allowed: [ man woman ],
  -> Gender of person,

'its ok'

Example: Person data structure: Validation fails

Same example but person data changed so that it's not valid:

	person = map(
		'Name'   map('First-Name' 'Jack' 'LastName' 'Dalton')
		'Age'    50.0
		'Gender' 'other'
	)

->
'problems:
        required field FirstName not found ( -> Name)

        field Age is not required type (got: float, expected: int)()

        field Gender is not in allowed set (other not in: list('man', 'woman'))()
'

Example: polymorphic interfaces

Using specification as defined set of functions/procedures. Here printing fruits are only depending on polymorphic interface which is implemented separately for "orange" and for "banana":

	import stdmeta

	fruit-interface = list('map' map(
		'get-name'  list(list('required') list('type' 'function') list('doc' 'Function to return fruit name'))
		'get-color' list(list('required') list('type' 'function') list('doc' 'Function to return color of fruit'))
	))

	orange = map(
		'get-name'  func() 'orange' end
		'get-color' func() 'Orange' end
	)

	banana = map(
		'get-name'  func() 'banana' end
		'get-color' func() 'yellow' end
	)

	_ = print(call(stdmeta.get-doc fruit-interface) '\n')

	print-fruit = proc(fruit)
		import stddbc
		ok msglist = call(stdmeta.validate fruit-interface fruit):
		_ = call(stddbc.assert ok msglist)

		name-getter = get(fruit 'get-name')
		color-getter = get(fruit 'get-color')
		print(sprintf('Fruit name = %s\nFruit color = %s\n' call(name-getter) call(color-getter)))
	end

	list(
		call(print-fruit orange)
		call(print-fruit banana)
	)

->
map:
get-name : required, type: function,
  -> Function to return fruit name,
get-color : required, type: function,
  -> Function to return color of fruit,

Fruit name = orange
Fruit color = Orange

Fruit name = banana
Fruit color = yellow

list(true, true)