jsonrouter 0.4.9
JSON based events router, inspired from an AWS Lambda project.
License: MIT
Requires Python: >=3.5.0



# JSON Events Router [![Build Status](https://travis-ci.org/srhopkins/jsonrouter.svg?branch=master)](https://travis-ci.org/srhopkins/jsonrouter)

Uses simple `yaml` based rules to take action on `JSON` events. Uses [jsonpath](https://readthedocs.org/projects/jsonpath-rw/) to scan the event message and `regex` for `includes` and `excludes` conditionals.

> **Note!** `jsonrouter` currently converts all matching field values to `str`ings for `re`gex comparision so be aware of this when expecting `int` to be returned.

## Install

```
pip install jsonrouter
```

## TL;DR

```python
import json
import yaml

from jsonrouter import JsonMatchEngine


# load rules from file or string
configs = yaml.load('''
rules:
- name: example-rule
  routers: 
  - name: print-router
  vars:
  - name: the-name
    jsonpath: $.name
''')

# an example json record
json_string = '''
{
    "name": "jsonrouter",
    "type": "jsonpath matcher and router",
    "why": {
        "because": "it's easy"
    }
}
'''


def print_router(data):
    # a trivial example router function
    print(json.dumps(data, indent=4))


# explicitly declare your registered routers for security
registered_routers = {
    'print-router': print_router
}

eng = JsonMatchEngine(configs, registered_routers)
```

Use the engine to find matches:

```
In [2]: matches = eng.route_matches(json_string)
{
    "name": "example-rule",
    "routers": [
        {
            "name": "print-router"
        }
    ],
    "vars": {
        "the-name": "jsonrouter"
    },
    "template": "",
    "record": {
        "name": "jsonrouter",
        "type": "jsonpath matcher and router",
        "why": {
            "because": "it's easy"
        }
    }
}
```

The contents of `matches`:

```
In [3]: matches
Out[3]:
[{'name': 'example-rule',
  'routers': [{'name': 'print-router'}],
  'vars': {'the-name': 'jsonrouter'},
  'template': '',
  'record': {'name': 'jsonrouter',
   'type': 'jsonpath matcher and router',
   'why': {'because': "it's easy"}}}]

```

## Rule Config

Anatomy of the config.

```yaml
# `rules` is the root of the config
# note: rules can have same name!
rules: # required
- name: notification # required
  routers: # required
  - name: slack # required
    # all fields besides name are optional but may be required in the router
    channel: my-channel # optional
  vars: # required
  - name: type # required
    jsonpath: $..Type # required, except for constants: see bellow 
    includes: ['.*'] # optional, default `['.*']` includes all
    excludes: [] # optional, default `[]` excludes nothing
  # `template` is optional
  template: | 
    This {type} just came in
```

### Constants

You can define a constant var by providing `value` field only

```yaml
  vars:
  - name: my-constant
    value: my constant value
```

## Basic Usage

Simple capture examples. 

```yaml
rules:
- name: notification
  routers: 
  - name: slack
    channel: my-channel
  vars:
  - name: type
    jsonpath: $..Type
    # include anything
    includes: ['.*']
    # exclude empty
    excludes: ['^$']
  template: |
    This {type} just came in
```

## Advanced Regex

### Match Part of String `()`

```yaml
rules:
- name: console_login
  routers: 
  - name: slack
    channel: my-channel
  vars:
  - name: detail-type
    jsonpath: $..detail-type
    includes: ['AWS Console Sign In via CloudTrail']
    excludes: ['^$']
  - name: user
    jsonpath: $..principalId
    # Match part of string
    includes: ['.*:(.*)']
    excludes: ['^$']
  - name: account
    jsonpath: $..account
    includes: ['.*']
    excludes: ['^$']    
  template: |
    Yo! {user} just signed in to {account}.
```

### Match in String with Group Name

Use `(?P<variable_name>)` to capture patterns within the matched field.

> This will override any naming collisions with `vars:name` you set in the yaml. It merges the rule vars with matched name(s) declared in the regex where named regex take precedence

```yaml
rules:
- name: console_login
  routers: 
  - name: slack
    channel: my-channel
  vars:
  - name: detail-type
    jsonpath: $..detail-type
    includes: ['AWS Console Sign In via CloudTrail']
    excludes: ['^$']
  - name: user
    jsonpath: $..principalId
    # Match part of string with variable names
    includes: ['(?P<stuff>.*):(?P<user>.*)']
    excludes: ['^$']
  - name: account
    jsonpath: $..account
    includes: ['.*']
    excludes: ['^$']    
  template: |
    Yo! {user} just signed in to {account}. This {stuff} was before the user.
```

## Lambda Example

Since `jsonrouter` started from an AWS Lambda SNS parsing project it has a `jsonify_string` method that converts the SNS message field from a sting to a nested `dict`.

```python
import json
import yaml


from jsonrouter import JsonMatchEngine, jsonify_string


def slack(webhook):
    # whatever logic you want in here
    pass


with open('rules.yaml', 'r') as f:
    configs = yaml.safe_load(f)

registered_routers = {
    'slack': slack
}

eng = JsonMatchEngine(configs, registered_routers)


def handler(event, context):
    # Main lambda handler function
    eng.route_matches(jsonify_string(event)['Records'])

```

## Development

```
# local tests run from root of project
python3 -m pytest -v
# to see print output use `-s`
python3 -m pytest -v -s
```