#  -*- text -*-
#
#
#  $Id: e1e53c5c832b6a24146d8c0d5f45a003408e00f0 $

#######################################################################
#
#  = JSON Module
#
#  The `json` module registers a `map` function to allow mapping
#  fields from JSON structures to attributes. It also adds some xlats
#  for processing and generating JSON documents.
#
#  == JSON 'map' function
#
#  The path through the JSON document's tree is specified with FR
#  jpath, which is the FreeRADIUS implementation of the jpath grammar
#  described at http://goessner.net/articles/JsonPath/
#
#  Selectors currently implemented are:
#
#  [options="header,autowidth"]
#  |===
#  | Selector                 | Description
#  | $                        | Root node (only valid at the start of the path).
#  | @                        | Current node (only valid at the start of the path).
#  | .<name>                  | A field within an object.
#  | [<idx>]                  | Index within an array.
#  | [<start>:<end>[:<step>]] | A slice within an array (identical to the Python syntax).
#  | [<idx>,<start>:<end>]    | Multiple indexes/slices within an array.
#  | .*                       | All the children of the current node.
#  | ..                       | Recursive descent.
#  |===
#
#  [NOTE]
#  ====
#  * Automatic casting will occur between JSON and attribute types where possible.
#  * Assignment of JSON objects/arrays to strings is supported, in which case the
#    JSON serialized form of the object/array is used.
#  * If a jpath matches multiple nodes, unless the map includes the `+=` operator
#  * only the first node's value will be used.
#  * If the map uses `+=` then multiple instances of the attribute will be created,
#    each holding a different node value.
#  ====
#
#
#  == Sample
#
#  Assuming that the URL http://example.org/api/user/$username replies
#  with some JSON content as below.
#
#  [source, json]
#  ----
#  {
#    "user": "bob",
#    "account number": 7124503,
#    "groups": [
#      "admin",
#      "networks",
#      "bob"
#    ]
#  }
#  ----
#
#  In this case, the `rest` module can be used to get the JSON data
#  and the fields can be accessed using 'map' as shown in the example
#  below.
#
#  [source, unlang]
#  ----
#  map json "%rest(GET http://example.org/api/user/%{User-Name})" {
#    Foo            := '$.account number'
#    Group          += '$.groups.*'
#  }
#  ----
#

#
#  == Configuration Settings
#
json {

	#
	#  The only options for the JSON module are to control the output
	#  format of the `json.encode` xlat.
	#
	encode {

		#
		#  output_mode:: set the format of JSON documenta that should be
		#  created. This may be one of:
		#
		#  - object
		#  - object_simple
		#  - array
		#  - array_of_names
		#  - array_of_values
		#
		#  Examples of each format are given below.
		#
#		output_mode = object

		#
		#  === Formatting options for attribute names
		#
		attribute {
			#
			#  prefix:: Add a colon-delimited prefix to all attribute names in
			#  the output document. For example, with a prefix of "foo",
			#  `User-Name` will be output as `foo:User-Name`.
			#
#			prefix =
		}

		#
		#  === Formatting options for attribute values
		#
		value {

			#
			#  single_value_as_array:: always put values in an array
			#
			#  Output formats will by default put single values as a JSON
			#  object (string, integer, etc). More than one value will,
			#  depending on the output format, be added as an array.
			#
			#  When this option is enabled, values will always be added as an
			#  array.
			#
#			single_value_as_array = no

			#
			#  enum_as_integer:: output the integer value of enumerated
			#  attributes
			#
			#  Where an attribute has enum values, the textual representation
			#  of the value will normally be output. Enable this option to
			#  force the numeric value instead.
			#
#			enum_as_integer = no

			#
			#  always_string:: force all values to be strings
			#
			#  Integer values are normally written to the JSON document as
			#  numbers (i.e. without quotes). Enable this option to force all
			#  values to be as quoted strings.
			#
#			always_string = no

			#
			#  binary_format:: how to encode octets (binary) values
			#
			#  Controls the representation of binary data in JSON output.
			#  - "raw" (default): write bytes directly as a JSON string
			#  - "base16": base16(hex)-encode the binary data
			#  - "base64": base64-encode the binary data
			#
#			binary_format = raw
		}

	}

}


#
#  == Expansions
#
#  rlm_json provides the below xlat functions to handle the JSON
#  documents.
#
#  === %json.jpath_validate(...)
#
#  Determine if a jpath expression is valid.
#
#  NOTE: Validate parser for everything except unions and expressions.
#
#  .Return: _size_t_
#
#  .Example
#
#  [source,unlang]
#  ----
#  string payload payload := '$.my.json.payload[1]'
#  reply.Reply-Message := "Validation of %{payload} is
#  %json.jpath_validate($.my.json.payload[1])"
#  ----
#
#  .Output
#
#  ```
#  Validation of $.my.json.payload[1] is 20:$.my.json.payload[1]
#  ```
#
#  === %json.quote(...)
#
#  Escapes string for use as a JSON string.
#
#  .Return: _string_
#
#  .Example
#
#  [source,unlang]
#  ----
#  string path path := "caipirinha/gelada" reply.Reply-Message := "The
#  string %{path} should be %json.quote(%{path}) to be a valid JSON
#  string."
#  ----
#
#  .Output
#
#  ```
#  The string caipirinha/gelada should be caipirinha\\/gelada to be a valid JSON string.
#  ```
#
#  === %json.encode(...)
#
#  Generates a JSON document from a given list of attribute templates.
#  The format of document generated can be controlled with the
#  'encode' section in the module configuration. Attribute values will
#  automatically be escaped so they are JSON-safe.
#
#  NOTE: The name of the xlat is based on the instance name of this
#  module. If the module was defined as `json jdoc {...}`, then the
#  xlat name will be `jdoc_encode`.
#
#  The xlat should be passed a list of attributes to encode. Each
#  attribute (after template expansion) will be added to a list of
#  attributes to include in the JSON document. If any of the
#  attributes given are preceded with a `!` then they are removed from
#  the list. Once all attributes have been processed, the JSON
#  document will be created using this list.
#
#  For example, the following will produce a JSON document with two
#  attributes in it, `User-Name` and `Calling-Station-Id`, from the
#  RADIUS request:
#
#  .Example
#
#  ```
#  %json.encode('User-Name', 'Calling-Station-Id')
#  ```
#
#  The following will include all attributes in the RADIUS request,
#  except for `User-Password`:
#
#  .Example
#
#  ```
#  %json.encode('request[*]', '!User-Password')
#  ```
#
#  In another (contrived) example, all the attributes in the RADIUS
#  request will be included in the document, _except_ any attributes
#  in the RADIUS reply. `User-Name` will be included from the control
#  list, too, if it exists:
#
#  .Example
#
#  ```
#  %json.encode('request[*]', '!reply[*]', 'control.User-Name')
#  ```
#
#  ==== Output format modes
#
#  There are a number of output modes, each generating a different
#  format of JSON document.
#
#  NOTE: In the JSON document, "type" is the type of the _attribute_,
#  which is not necessarily the same as the type of the "value" in the
#  document. See e.g. `Login-Service` above, an enumerated value.
#
#  The following examples assume the three attributes are being added
#  to the JSON document:
#
#  ```
#  User-Name = bob
#  Filter-Id = ab
#  Filter-Id += cd
#  ```
#
#  ==== Object output mode examples
#
#  These modes output a JSON object.
#
#  .Output mode "object"
#
#  [source,json]
#  ----
#  {
#    "User-Name": {
#      "type": "string",
#      "value": "bob"
#    },
#    "Filter-Id": {
#      "type": "string",
#      "value": ["ab","cd"]
#    }
#  }
#  ----
#
#  .Output mode "object_simple"
#
#  [source,json]
#  ----
#  {
#    "User-Name": "bob",
#    "Filter-Id": ["ab","cd"]
#  }
#  ----
#
#  ==== Array output mode examples
#
#  The "array" mode is a list of objects, each containing an
#  attribute. If the "single_value_as_array" value option is set then
#  each attribute will only appear once in the array, and "value" will
#  be a list of all the values from the same attribute.
#
#  .Output mode "array"
#
#  [source,json]
#  ----
#  [
#    {
#      "name": "User-Name",
#      "type": "string",
#      "value": "bob"
#    },
#    {
#      "name": "Filter-Id",
#      "type": "string",
#      "value": "ab"
#    },
#    {
#      "name": "Filter-Id",
#      "type": "string",
#      "value": "cd"
#    }
#  ]
#  ----
#
#  .Output mode "array" when "single_value_as_array" is also set
#
#  [source,json]
#  ----
#  [
#    {
#      "name": "User-Name",
#      "type": "string",
#      "value": ["bob"]
#    },
#    {
#      "name": "Filter-Id",
#      "type": "string",
#      "value": ["ab","cd"]
#    }
#  ]
#  ----
#
#  The following output modes either do not include the attribute
#  names or values. They are likely to be useful only when the
#  attributes are individually specified and _guaranteed to exist_. In
#  this case the attribute names in `array_of_names` will have
#  corresponding indexes to the values in `array_of_values`.
#
#  .Output mode "array_of_names"
#
#  [source,json]
#  ----
#  [
#    "User-Name",
#    "Filter-Id",
#    "Filter-Id"
#  ]
#  ----
#
#  .Output mode "array_of_values"
#
#  [source,json]
#  ----
#  [
#    "bob",
#    "ab",
#    "cd"
#  ]
#  ----
#
