mirror of
https://git.burble.com/burble.dn42/dn42regsrv.git
synced 2024-02-26 20:28:04 +01:00
Refactor API to include key and attribute matching
This commit is contained in:
parent
7531c642ff
commit
ab9628b212
331
API.md
331
API.md
@ -1,83 +1,278 @@
|
||||
# dn42regsrv API Description
|
||||
|
||||
## GET /<file>
|
||||
## Registry API
|
||||
|
||||
If the StaticRoot configuration option points to a readable directory, files from
|
||||
the directory will be served under /
|
||||
The general form of the registry query API is:
|
||||
|
||||
The git repository contains a sample StaticRoot directory with a simple registry
|
||||
explorer web app.
|
||||
GET /api/registry/{type}/{object}/{key}/{attribute}?raw
|
||||
|
||||
## GET /api/registry/
|
||||
* Prefixing with a '*' performs a case insensitive, substring match
|
||||
* A '*' on its own means match everything
|
||||
* Otherwise an exact, case sensitive match is performed
|
||||
|
||||
Returns a JSON object, with keys for each registry type and values containing a count
|
||||
of the number of registry objects for each type.
|
||||
|
||||
Example:
|
||||
```
|
||||
http://localhost:8042/api/registry/
|
||||
|
||||
# sample output
|
||||
{"as-block":8,"as-set":34,"aut-num":1482,"domain":451,"inet6num":744,"inetnum":1270,"key-cert":7,"mntner":1378,"organisation":275,"person":1387,"registry":4,"role":14,"route":886,"route-set":2,"route6":594,"schema":18,"tinc-key":25,"tinc-keyset":3}
|
||||
```
|
||||
|
||||
|
||||
## GET /api/registry/<type>?match
|
||||
|
||||
Returns a JSON object listing all objects for the matched types.
|
||||
|
||||
Keys for the returned object are registry types, the value for each type is an
|
||||
array of object names
|
||||
|
||||
If the match parameter is provided, the <type> is substring matched against
|
||||
all registry types, otherwise an exact type name is required.
|
||||
|
||||
A special type of '*' returns all types and objects in the registry.
|
||||
|
||||
Example:
|
||||
```
|
||||
http://localhost:8042/api/registry/aut-num # list aut-num objects
|
||||
http://localhost:8042/api/registry/* # list all types and objects
|
||||
http://localhost:8042/api/registry/route?match # list route and route6 objects
|
||||
|
||||
# sample output
|
||||
{"role":["ALENAN-DN42","FLHB-ABUSE-DN42","ORG-SHACK-ADMIN-DN42","PACKETPUSHERS-DN42","CCCHB-ABUSE-DN42","ORG-NETRAVNEN-DN42","ORG-SHACK-ABUSE-DN42","MAGLAB-DN42","NIXNODES-DN42","SOURIS-DN42","CCCKC-DN42","NL-ZUID-DN42","ORG-SHACK-TECH-DN42","ORG-YANE-DN42"]}
|
||||
|
||||
```
|
||||
|
||||
## GET /api/registry/<type>/<object>?match&raw
|
||||
|
||||
Return a JSON object with the registry data for each matching object.
|
||||
|
||||
The keys for the object are the object paths in the form <type>/<object name>. The values depends on the raw parameter.
|
||||
|
||||
if the raw parameter is provided, the returned object consists of a single key 'Attributes'
|
||||
which will be an array of key/value pairs exactly as held within the registry.
|
||||
|
||||
If the raw parameter is not provided, the returned Attributes are decorated with markdown
|
||||
style links depending the relations defined in the DN42 schema. In addition a
|
||||
'Backlinks' key is added which provides an array of registry objects that
|
||||
By default results are returned as JSON objects, and the registry data is decorated
|
||||
with markdown style links depending on relations defined in the DN42 schema. For object
|
||||
results, a 'Backlinks' section is also added providing an array of registry objects that
|
||||
reference this one.
|
||||
|
||||
If the match parameter is provided, the <object> is substring matched against all
|
||||
objects in the <type>. Matching is case insensitive.
|
||||
If the 'raw' parameter is provided, attributes are returned un-decorated exactly
|
||||
as contained in the registry.
|
||||
|
||||
If the match parameter is not provided, an exact, case sensitive object name is required.
|
||||
Some examples will help clarify:
|
||||
|
||||
A special object of '*' returns all objects in the type
|
||||
* Return a JSON object, with keys for each registry type and values containing a count
|
||||
of the number of registry objects for each type
|
||||
|
||||
Example:
|
||||
```
|
||||
http://localhost:8042/api/registry/domain/burble.dn42?raw # return object in raw format
|
||||
http://localhost:8042/api/registry/mntner/BURBLE-MNT # return object in decorated format
|
||||
http://localhost:8042/api/registry/aut-num/2601?match # return all aut-num objects matching 2601
|
||||
http://localhost:8042/api/registry/schema/* # return all schema objects
|
||||
|
||||
# sample output (raw)
|
||||
{"domain/burble.dn42":[["domain","burble.dn42"],["descr","burble.dn42 https://dn42.burble.com/"],["admin-c","BURBLE-DN42"],["tech-c","BURBLE-DN42"],["mnt-by","BURBLE-MNT"],["nserver","ns1.burble.dn42 172.20.129.161"],["nserver","ns1.burble.dn42 fd42:4242:2601:ac53::1"],["ds-rdata","61857 13 2 bd35e3efe3325d2029fb652e01604a48b677cc2f44226eeabee54b456c67680c"],["source","DN42"]]}
|
||||
|
||||
# sample output (decorated)
|
||||
{"mntner/BURBLE-MNT":{"Attributes":[["mntner","BURBLE-MNT"],["descr","burble.dn42 https://dn42.burble.com/"],["admin-c","[BURBLE-DN42](person/BURBLE-DN42)"],["tech-c","[BURBLE-DN42](person/BURBLE-DN42)"],["auth","pgp-fingerprint 1C08F282095CCDA432AECC657B9FE8780CFB6593"],["mnt-by","[BURBLE-MNT](mntner/BURBLE-MNT)"],["source","[DN42](registry/DN42)"]],"Backlinks":["as-set/AS4242422601:AS-DOWNSTREAM","as-set/AS4242422601:AS-TRANSIT","inetnum/172.20.129.160_27","person/BURBLE-DN42","route/172.20.129.160_27","inet6num/fd42:4242:2601::_48","mntner/BURBLE-MNT","aut-num/AS4242422601","aut-num/AS4242422602","route6/fd42:4242:2601::_48","domain/collector.dn42","domain/burble.dn42"]}}
|
||||
wget -O - -q http://localhost:8042/api/registry/ | jq
|
||||
{
|
||||
"as-block": 8,
|
||||
"as-set": 34,
|
||||
"aut-num": 1486,
|
||||
"domain": 451,
|
||||
"inet6num": 746,
|
||||
"inetnum": 1276,
|
||||
"key-cert": 7,
|
||||
"mntner": 1379,
|
||||
"organisation": 275,
|
||||
"person": 1388,
|
||||
"registry": 4,
|
||||
"role": 14,
|
||||
"route": 892,
|
||||
"route-set": 2,
|
||||
"route6": 596,
|
||||
"schema": 18,
|
||||
"tinc-key": 25,
|
||||
"tinc-keyset": 3
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
* Return a list of all objects in the role type
|
||||
|
||||
```
|
||||
wget -O - -q http://localhost:8042/api/registry/role | jq
|
||||
{
|
||||
"role": [
|
||||
"ORG-NETRAVNEN-DN42",
|
||||
"PACKETPUSHERS-DN42",
|
||||
"CCCKC-DN42",
|
||||
"FLHB-ABUSE-DN42",
|
||||
"NIXNODES-DN42",
|
||||
"ORG-SHACK-ABUSE-DN42",
|
||||
"ORG-SHACK-TECH-DN42",
|
||||
"ORG-YANE-DN42",
|
||||
"SOURIS-DN42",
|
||||
"CCCHB-ABUSE-DN42",
|
||||
"MAGLAB-DN42",
|
||||
"NL-ZUID-DN42",
|
||||
"ORG-SHACK-ADMIN-DN42",
|
||||
"ALENAN-DN42"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
* Returns a list of all objects in types that match 'route'
|
||||
|
||||
```
|
||||
wget -O - -q http://localhost:8042/api/registry/*route | jq
|
||||
{
|
||||
"route": [
|
||||
"172.20.28.0_27",
|
||||
"172.23.220.0_24",
|
||||
"172.23.82.0_25",
|
||||
"10.149.0.0_16",
|
||||
|
||||
...
|
||||
|
||||
"172.20.128.0_27",
|
||||
"172.22.127.32_27"
|
||||
],
|
||||
"route-set": [
|
||||
"RS-DN42",
|
||||
"RS-DN42-NATIVE"
|
||||
],
|
||||
"route6": [
|
||||
"fd42:df42::_48",
|
||||
"fd5c:0f0f:39fc::_48",
|
||||
|
||||
...
|
||||
|
||||
"fd16:c638:3d7c::_48",
|
||||
"fd23::_48"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
* Returns the mntner/BURBLE-MNT object (in decorated format)
|
||||
|
||||
```
|
||||
wget -O - -q http://localhost:8042/api/registry/mntner/BURBLE-MNT | jq
|
||||
{
|
||||
"mntner/BURBLE-MNT": {
|
||||
"Attributes": [
|
||||
[
|
||||
"mntner",
|
||||
"BURBLE-MNT"
|
||||
],
|
||||
[
|
||||
"descr",
|
||||
"burble.dn42 https://dn42.burble.com/"
|
||||
],
|
||||
[
|
||||
"admin-c",
|
||||
"[BURBLE-DN42](person/BURBLE-DN42)"
|
||||
],
|
||||
[
|
||||
"tech-c",
|
||||
"[BURBLE-DN42](person/BURBLE-DN42)"
|
||||
],
|
||||
[
|
||||
"auth",
|
||||
"pgp-fingerprint 1C08F282095CCDA432AECC657B9FE8780CFB6593"
|
||||
],
|
||||
[
|
||||
"mnt-by",
|
||||
"[BURBLE-MNT](mntner/BURBLE-MNT)"
|
||||
],
|
||||
[
|
||||
"source",
|
||||
"[DN42](registry/DN42)"
|
||||
]
|
||||
],
|
||||
"Backlinks": [
|
||||
"aut-num/AS4242422602",
|
||||
"aut-num/AS4242422601",
|
||||
"mntner/BURBLE-MNT",
|
||||
"route/172.20.129.160_27",
|
||||
"as-set/AS4242422601:AS-DOWNSTREAM",
|
||||
"as-set/AS4242422601:AS-TRANSIT",
|
||||
"person/BURBLE-DN42",
|
||||
"inet6num/fd42:4242:2601::_48",
|
||||
"domain/burble.dn42",
|
||||
"domain/collector.dn42",
|
||||
"route6/fd42:4242:2601::_48",
|
||||
"inetnum/172.20.129.160_27"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* Returns error 404, exact searches are case sensitive
|
||||
|
||||
```
|
||||
wget -O - -q http://localhost:8042/api/registry/mntner/burble-mnt | jq
|
||||
```
|
||||
|
||||
* Returns domain names matching 'burble' in raw format
|
||||
|
||||
```
|
||||
wget -O - -q http://localhost:8042/api/registry/domain/*burble?raw | jq
|
||||
{
|
||||
"domain/burble.dn42": [
|
||||
[
|
||||
"domain",
|
||||
"burble.dn42"
|
||||
],
|
||||
[
|
||||
"descr",
|
||||
"burble.dn42 https://dn42.burble.com/"
|
||||
],
|
||||
[
|
||||
"admin-c",
|
||||
"BURBLE-DN42"
|
||||
],
|
||||
[
|
||||
"tech-c",
|
||||
"BURBLE-DN42"
|
||||
],
|
||||
[
|
||||
"mnt-by",
|
||||
"BURBLE-MNT"
|
||||
],
|
||||
[
|
||||
"nserver",
|
||||
"ns1.burble.dn42 172.20.129.161"
|
||||
],
|
||||
[
|
||||
"nserver",
|
||||
"ns1.burble.dn42 fd42:4242:2601:ac53::1"
|
||||
],
|
||||
[
|
||||
"ds-rdata",
|
||||
"61857 13 2 bd35e3efe3325d2029fb652e01604a48b677cc2f44226eeabee54b456c67680c"
|
||||
],
|
||||
[
|
||||
"source",
|
||||
"DN42"
|
||||
]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
* Returns all objects matching 172.20.0
|
||||
|
||||
```
|
||||
wget -O - -q http://localhost:8042/api/registry/*/*172.20.0 | jq
|
||||
{
|
||||
"inetnum/172.20.0.0_14": {
|
||||
"Attributes": [
|
||||
[
|
||||
"inetnum",
|
||||
"172.20.0.0 - 172.23.255.255"
|
||||
],
|
||||
[
|
||||
"cidr",
|
||||
"172.20.0.0/14"
|
||||
],
|
||||
|
||||
... and so on
|
||||
```
|
||||
|
||||
* Returns the nic-hdl attribute for all person objects
|
||||
|
||||
```
|
||||
wget -O - -q http://localhost:8042/api/registry/person/*/nic-hdl | jq
|
||||
{
|
||||
"person/0RIGO-DN42": {
|
||||
"nic-hdl": [
|
||||
"0RIGO-DN42"
|
||||
]
|
||||
},
|
||||
"person/0XDRAGON-DN42": {
|
||||
"nic-hdl": [
|
||||
"0XDRAGON-DN42"
|
||||
]
|
||||
},
|
||||
"person/1714-DN42": {
|
||||
"nic-hdl": [
|
||||
"1714-DN42"
|
||||
]
|
||||
},
|
||||
|
||||
... and so on
|
||||
```
|
||||
|
||||
* return raw contact (-c) attributes in aut-num objects that contain 'burble'
|
||||
|
||||
```
|
||||
wget -O - -q http://localhost:8042/api/registry/aut-num/*/*-c/*burble?raw | jq
|
||||
{
|
||||
"aut-num/AS4242422601": {
|
||||
"admin-c": [
|
||||
"BURBLE-DN42"
|
||||
],
|
||||
"tech-c": [
|
||||
"BURBLE-DN42"
|
||||
]
|
||||
},
|
||||
"aut-num/AS4242422602": {
|
||||
"admin-c": [
|
||||
"BURBLE-DN42"
|
||||
],
|
||||
"tech-c": [
|
||||
"BURBLE-DN42"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -66,7 +66,7 @@ body { box-shadow: inset 0 2em 10em rgba(0,0,0,0.4); min-height: 100vh }
|
||||
<li>Searching for <b>type/</b> will return all the objects for that type (e.g.
|
||||
<router-link class="text-success" to="schema/">schema/</router-link>)</li>
|
||||
<li>A blank search box will return you to these instructions</li>
|
||||
<li>Just copy to the URL to link to search results</li>
|
||||
<li>Just copy the URL to link to search results</li>
|
||||
<li>Searches are made on object names; searching the content of objects
|
||||
is not supported (yet!).</li>
|
||||
</ul>
|
||||
|
@ -18,14 +18,26 @@ import (
|
||||
)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// list of API endpoints
|
||||
// simple event bus
|
||||
|
||||
type InitEndpointFunc = func(route *mux.Router)
|
||||
type NotifyFunc func(...interface{})
|
||||
type SimpleEventBus map[string][]NotifyFunc
|
||||
|
||||
var apiEndpoints = make([]InitEndpointFunc, 0)
|
||||
var EventBus = make(SimpleEventBus)
|
||||
|
||||
func RegisterAPIEndpoint(f InitEndpointFunc) {
|
||||
apiEndpoints = append(apiEndpoints, f)
|
||||
// add a listener to an event
|
||||
func (bus SimpleEventBus) Listen(event string, nfunc NotifyFunc) {
|
||||
bus[event] = append(bus[event], nfunc)
|
||||
}
|
||||
|
||||
// fire notifications for an event
|
||||
func (bus SimpleEventBus) Fire(event string, params ...interface{}) {
|
||||
funcs := bus[event]
|
||||
if funcs != nil {
|
||||
for _, nfunc := range funcs {
|
||||
nfunc(params...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@ -111,11 +123,9 @@ func main() {
|
||||
// log all access
|
||||
router.Use(requestLogger)
|
||||
|
||||
// initialise API routes
|
||||
// add API routes
|
||||
subr := router.PathPrefix("/api").Subrouter()
|
||||
for _, epInit := range apiEndpoints {
|
||||
epInit(subr)
|
||||
}
|
||||
EventBus.Fire("APIEndpoint", subr)
|
||||
|
||||
// initialise static routes
|
||||
InstallStaticRoutes(router, *staticRoot)
|
||||
|
465
regapi.go
465
regapi.go
@ -8,6 +8,7 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
// "fmt"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
@ -19,13 +20,15 @@ import (
|
||||
// register the api
|
||||
|
||||
func init() {
|
||||
RegisterAPIEndpoint(InitRegAPI)
|
||||
EventBus.Listen("APIEndpoint", InitRegistryAPI)
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// called from main to initialise the API routing
|
||||
|
||||
func InitRegAPI(router *mux.Router) {
|
||||
func InitRegistryAPI(params ...interface{}) {
|
||||
|
||||
router := params[0].(*mux.Router)
|
||||
|
||||
s := router.
|
||||
Methods("GET").
|
||||
@ -38,6 +41,8 @@ func InitRegAPI(router *mux.Router) {
|
||||
|
||||
s.HandleFunc("/{type}", regTypeHandler)
|
||||
s.HandleFunc("/{type}/{object}", regObjectHandler)
|
||||
s.HandleFunc("/{type}/{object}/{key}", regKeyHandler)
|
||||
s.HandleFunc("/{type}/{object}/{key}/{attribute}", regAttributeHandler)
|
||||
|
||||
log.Info("Registry API installed")
|
||||
}
|
||||
@ -63,6 +68,242 @@ func responseJSON(w http.ResponseWriter, v interface{}) {
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// filter functions
|
||||
|
||||
// return a list of types that match the filter
|
||||
func filterTypes(filter string) []*RegType {
|
||||
|
||||
var rtypes []*RegType = nil
|
||||
|
||||
// check if filter starts with '*'
|
||||
if filter[0] == '*' {
|
||||
// try and match the filter against all reg types
|
||||
|
||||
filter = strings.ToLower(filter[1:])
|
||||
|
||||
// special case, if the filter was '*' return all types
|
||||
if len(filter) == 0 {
|
||||
|
||||
rtypes = make([]*RegType, 0, len(RegistryData.Types))
|
||||
for _, rtype := range RegistryData.Types {
|
||||
rtypes = append(rtypes, rtype)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// otherwise substring match the types
|
||||
for _, rtype := range RegistryData.Types {
|
||||
lname := strings.ToLower(rtype.Ref)
|
||||
if strings.Contains(lname, filter) {
|
||||
// matched, add it to the list
|
||||
rtypes = append(rtypes, rtype)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
// perform an exact match with one entry
|
||||
|
||||
rtype := RegistryData.Types[filter]
|
||||
if rtype != nil {
|
||||
// return a single answer
|
||||
rtypes = []*RegType{rtype}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return rtypes
|
||||
}
|
||||
|
||||
// return a list of objects from a set of types that match a filter
|
||||
func filterObjects(rtypes []*RegType, filter string) []*RegObject {
|
||||
|
||||
var objects []*RegObject = nil
|
||||
|
||||
// check if filter starts with '*'
|
||||
if filter[0] == '*' {
|
||||
// try and match objects against the filter
|
||||
|
||||
filter = strings.ToLower(filter[1:])
|
||||
|
||||
// for each type
|
||||
for _, rtype := range rtypes {
|
||||
|
||||
// special case, if the filter was '*' return all objects
|
||||
if len(filter) == 0 {
|
||||
|
||||
objs := make([]*RegObject, 0, len(rtype.Objects))
|
||||
for _, object := range rtype.Objects {
|
||||
objs = append(objs, object)
|
||||
}
|
||||
objects = append(objects, objs...)
|
||||
|
||||
} else {
|
||||
// otherwise substring match the object names
|
||||
|
||||
for _, object := range rtype.Objects {
|
||||
lname := strings.ToLower(object.Ref)
|
||||
if strings.Contains(lname, filter) {
|
||||
// matched, add it to the list
|
||||
objects = append(objects, object)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
// perform an exact match against one object for each type
|
||||
|
||||
for _, rtype := range rtypes {
|
||||
|
||||
object := rtype.Objects[filter]
|
||||
if object != nil {
|
||||
// add the object
|
||||
objects = append(objects, object)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return objects
|
||||
}
|
||||
|
||||
// return a list of key indices matching the filter
|
||||
func filterKeys(rtypes []*RegType, filter string) []*RegKeyIndex {
|
||||
|
||||
var ix []*RegKeyIndex = nil
|
||||
|
||||
// check if filter starts with '*'
|
||||
if filter[0] == '*' {
|
||||
// try and match keys against the filter
|
||||
|
||||
filter = strings.ToLower(filter[1:])
|
||||
|
||||
// for each type
|
||||
for _, rtype := range rtypes {
|
||||
ref := rtype.Ref
|
||||
schema := RegistryData.Schema[ref]
|
||||
|
||||
// special case, if the filter was '*' return all indices
|
||||
if len(filter) == 0 {
|
||||
|
||||
tmp := make([]*RegKeyIndex, 0, len(schema.KeyIndex))
|
||||
for _, keyix := range schema.KeyIndex {
|
||||
tmp = append(tmp, keyix)
|
||||
}
|
||||
ix = append(ix, tmp...)
|
||||
|
||||
} else {
|
||||
// otherwise substring match the key names
|
||||
|
||||
for kname, keyix := range schema.KeyIndex {
|
||||
kname = strings.ToLower(kname)
|
||||
if strings.Contains(kname, filter) {
|
||||
ix = append(ix, keyix)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// perform an exact match, one key for each type
|
||||
|
||||
for _, rtype := range rtypes {
|
||||
ref := rtype.Ref
|
||||
schema := RegistryData.Schema[ref]
|
||||
keyix := schema.KeyIndex[filter]
|
||||
if keyix != nil {
|
||||
// add the index
|
||||
ix = append(ix, keyix)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ix
|
||||
}
|
||||
|
||||
// helper func to determine if an attribute matches a filter
|
||||
func matchAttribute(attribute *RegAttribute,
|
||||
filter string, isExact bool) bool {
|
||||
|
||||
if isExact {
|
||||
|
||||
return filter == attribute.RawValue
|
||||
|
||||
} else {
|
||||
|
||||
l := strings.ToLower(attribute.RawValue)
|
||||
return strings.Contains(l, filter)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// return a map of objects and attribute values that match the filter
|
||||
func filterAttributes(ix []*RegKeyIndex, objects []*RegObject,
|
||||
filter string, raw bool) map[string]map[string][]string {
|
||||
|
||||
result := make(map[string]map[string][]string)
|
||||
|
||||
// pre-calculate the search type
|
||||
isExact := true
|
||||
isAll := false
|
||||
|
||||
if filter[0] == '*' {
|
||||
isExact = false
|
||||
filter = strings.ToLower(filter[1:])
|
||||
if len(filter) == 0 {
|
||||
isAll = true
|
||||
}
|
||||
}
|
||||
|
||||
// for each key index
|
||||
for _, keyix := range ix {
|
||||
|
||||
// for each object
|
||||
for _, object := range objects {
|
||||
|
||||
// attributes in this object that match this key
|
||||
attributes := keyix.Objects[object]
|
||||
if attributes != nil {
|
||||
// this object has at least one relevant key
|
||||
|
||||
// match the attributes
|
||||
for _, attribute := range attributes {
|
||||
if isAll || matchAttribute(attribute, filter, isExact) {
|
||||
// match found !
|
||||
|
||||
objmap := result[object.Ref]
|
||||
if objmap == nil {
|
||||
objmap = make(map[string][]string)
|
||||
result[object.Ref] = objmap
|
||||
}
|
||||
|
||||
// append the result
|
||||
var value *string
|
||||
if raw {
|
||||
value = &attribute.RawValue
|
||||
} else {
|
||||
value = &attribute.Value
|
||||
}
|
||||
|
||||
objmap[keyix.Ref] = append(objmap[keyix.Ref], *value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// root handler, lists all types within the registry
|
||||
|
||||
@ -83,62 +324,26 @@ func regTypeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// request parameters
|
||||
vars := mux.Vars(r)
|
||||
query := r.URL.Query()
|
||||
|
||||
typeName := vars["type"] // type name to list
|
||||
match := query["match"] // single query or match
|
||||
|
||||
// special case to return all types
|
||||
all := false
|
||||
if typeName == "*" {
|
||||
match = []string{}
|
||||
all = true
|
||||
}
|
||||
|
||||
// results will hold the types to return
|
||||
var results []*RegType
|
||||
|
||||
// check match type
|
||||
if match == nil {
|
||||
// exact match
|
||||
|
||||
// check the type object exists
|
||||
rType := RegistryData.Types[typeName]
|
||||
if rType == nil {
|
||||
http.Error(w, "No types matching '"+typeName+"' found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// return just a single result
|
||||
results = []*RegType{rType}
|
||||
|
||||
} else {
|
||||
// substring match
|
||||
|
||||
// comparisons are lower case
|
||||
typeName = strings.ToLower(typeName)
|
||||
|
||||
// walk through the types and filter to the results list
|
||||
results = make([]*RegType, 0)
|
||||
for key, rType := range RegistryData.Types {
|
||||
if all || strings.Contains(strings.ToLower(key), typeName) {
|
||||
// match found, add to the list
|
||||
results = append(results, rType)
|
||||
}
|
||||
}
|
||||
tFilter := vars["type"] // type filter
|
||||
|
||||
// match registry types against the filter
|
||||
rtypes := filterTypes(tFilter)
|
||||
if rtypes == nil {
|
||||
http.Error(w, "No objects matching '"+tFilter+"' found",
|
||||
http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// construct the response
|
||||
response := make(map[string][]string)
|
||||
for _, rType := range results {
|
||||
for _, rtype := range rtypes {
|
||||
|
||||
objects := make([]string, 0, len(rType.Objects))
|
||||
for key := range rType.Objects {
|
||||
objects := make([]string, 0, len(rtype.Objects))
|
||||
for key := range rtype.Objects {
|
||||
objects = append(objects, key)
|
||||
}
|
||||
|
||||
response[rType.Ref] = objects
|
||||
response[rtype.Ref] = objects
|
||||
}
|
||||
|
||||
responseJSON(w, response)
|
||||
@ -159,58 +364,24 @@ func regObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
query := r.URL.Query()
|
||||
|
||||
typeName := vars["type"] // object type
|
||||
objName := vars["object"] // object name or match
|
||||
match := query["match"] // single query or match
|
||||
tFilter := vars["type"] // type filter
|
||||
oFilter := vars["object"] // object filter
|
||||
raw := query["raw"] // raw or decorated results
|
||||
|
||||
// special case to return all objects
|
||||
all := false
|
||||
if objName == "*" {
|
||||
match = []string{}
|
||||
all = true
|
||||
}
|
||||
|
||||
// verify the type exists
|
||||
rType := RegistryData.Types[typeName]
|
||||
if rType == nil {
|
||||
http.Error(w, "No types matching '"+typeName+"' found",
|
||||
// select the type(s)
|
||||
rtypes := filterTypes(tFilter)
|
||||
if rtypes == nil {
|
||||
http.Error(w, "No objects matching '"+tFilter+"' found",
|
||||
http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// results will hold the objects to return
|
||||
var results []*RegObject
|
||||
|
||||
// check match type
|
||||
if match == nil {
|
||||
// exact match
|
||||
|
||||
// check the object exists
|
||||
object := rType.Objects[objName]
|
||||
if object == nil {
|
||||
http.Error(w, "No objects matching '"+objName+"' found",
|
||||
http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// then just create a results list with one object
|
||||
results = []*RegObject{object}
|
||||
|
||||
} else {
|
||||
// substring matching
|
||||
|
||||
// comparisons are lower case
|
||||
objName = strings.ToLower(objName)
|
||||
|
||||
// walk through the type objects and filter to the results list
|
||||
results = make([]*RegObject, 0)
|
||||
for key, object := range rType.Objects {
|
||||
if all || strings.Contains(strings.ToLower(key), objName) {
|
||||
// match found, add to the list
|
||||
results = append(results, object)
|
||||
}
|
||||
}
|
||||
// then select the objects
|
||||
objects := filterObjects(rtypes, oFilter)
|
||||
if objects == nil {
|
||||
http.Error(w, "No objects matching '"+tFilter+
|
||||
"/"+oFilter+"' found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// collate the results in to the response data
|
||||
@ -219,7 +390,7 @@ func regObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
response := make(map[string]RegObjectResponse)
|
||||
|
||||
// for each object in the results
|
||||
for _, object := range results {
|
||||
for _, object := range objects {
|
||||
|
||||
// copy the raw attributes
|
||||
attributes := make([][2]string, len(object.Data))
|
||||
@ -247,7 +418,7 @@ func regObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
response := make(map[string][][2]string)
|
||||
|
||||
// for each object in the results
|
||||
for _, object := range results {
|
||||
for _, object := range objects {
|
||||
|
||||
attributes := make([][2]string, len(object.Data))
|
||||
response[object.Ref] = attributes
|
||||
@ -263,5 +434,105 @@ func regObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// key handler returns attribute data matching the key
|
||||
|
||||
func regKeyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// request parameters
|
||||
vars := mux.Vars(r)
|
||||
query := r.URL.Query()
|
||||
|
||||
tFilter := vars["type"] // type filter
|
||||
oFilter := vars["object"] // object filter
|
||||
kFilter := vars["key"] // key filter
|
||||
raw := query["raw"] // raw or decorated results
|
||||
|
||||
// select the type(s)
|
||||
rtypes := filterTypes(tFilter)
|
||||
if rtypes == nil {
|
||||
http.Error(w, "No objects matching '"+tFilter+"' found",
|
||||
http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// select the key indices
|
||||
ix := filterKeys(rtypes, kFilter)
|
||||
if rtypes == nil {
|
||||
http.Error(w, "No objects matching '"+tFilter+"/*/"+
|
||||
kFilter+"' found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// select the objects
|
||||
objects := filterObjects(rtypes, oFilter)
|
||||
if objects == nil {
|
||||
http.Error(w, "No objects matching '"+tFilter+
|
||||
"/"+oFilter+"' found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// select objects that match the keys
|
||||
amap := filterAttributes(ix, objects, "*", (raw != nil))
|
||||
if len(amap) == 0 {
|
||||
http.Error(w, "No attributes matching '"+tFilter+"/"+
|
||||
oFilter+"/"+kFilter+"' found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
responseJSON(w, amap)
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// attribute handler returns attribute data matching the attribute
|
||||
|
||||
func regAttributeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// request parameters
|
||||
vars := mux.Vars(r)
|
||||
query := r.URL.Query()
|
||||
|
||||
tFilter := vars["type"] // type filter
|
||||
oFilter := vars["object"] // object filter
|
||||
kFilter := vars["key"] // key filter
|
||||
aFilter := vars["attribute"] // attribute filter
|
||||
raw := query["raw"] // raw or decorated results
|
||||
|
||||
// select the type(s)
|
||||
rtypes := filterTypes(tFilter)
|
||||
if rtypes == nil {
|
||||
http.Error(w, "No objects matching '"+tFilter+"' found",
|
||||
http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// select the key indices
|
||||
ix := filterKeys(rtypes, kFilter)
|
||||
if rtypes == nil {
|
||||
http.Error(w, "No objects matching '"+tFilter+"/*/"+
|
||||
kFilter+"' found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// then select the objects
|
||||
objects := filterObjects(rtypes, oFilter)
|
||||
if objects == nil {
|
||||
http.Error(w, "No objects matching '"+tFilter+
|
||||
"/"+oFilter+"' found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// select objects that match the keys
|
||||
amap := filterAttributes(ix, objects, aFilter, (raw != nil))
|
||||
if len(amap) == 0 {
|
||||
http.Error(w, "No attributes matching '"+tFilter+"/"+
|
||||
oFilter+"/"+kFilter+"/"+aFilter+"' found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
responseJSON(w, amap)
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// end of code
|
||||
|
37
registry.go
37
registry.go
@ -49,10 +49,15 @@ type RegAttributeSchema struct {
|
||||
Relations []*RegType
|
||||
}
|
||||
|
||||
type RegKeyIndex struct {
|
||||
Ref string
|
||||
Objects map[*RegObject][]*RegAttribute
|
||||
}
|
||||
|
||||
type RegTypeSchema struct {
|
||||
Ref string
|
||||
Attributes map[string]*RegAttributeSchema
|
||||
KeyIndex map[string]map[*RegObject][]*RegAttribute
|
||||
KeyIndex map[string]*RegKeyIndex
|
||||
}
|
||||
|
||||
// the registry itself
|
||||
@ -146,15 +151,18 @@ func (schema *RegTypeSchema) validate(attributes []*RegAttribute) []*RegAttribut
|
||||
func (schema *RegTypeSchema) addKeyIndex(object *RegObject,
|
||||
attribute *RegAttribute) {
|
||||
|
||||
objmap := schema.KeyIndex[attribute.Key]
|
||||
keyix := schema.KeyIndex[attribute.Key]
|
||||
// create a new object map if it didn't exist
|
||||
if objmap == nil {
|
||||
objmap = make(map[*RegObject][]*RegAttribute)
|
||||
schema.KeyIndex[attribute.Key] = objmap
|
||||
if keyix == nil {
|
||||
keyix = &RegKeyIndex{
|
||||
Ref: attribute.Key,
|
||||
Objects: make(map[*RegObject][]*RegAttribute),
|
||||
}
|
||||
schema.KeyIndex[attribute.Key] = keyix
|
||||
}
|
||||
|
||||
// add the object/attribute reference
|
||||
objmap[object] = append(objmap[object], attribute)
|
||||
keyix.Objects[object] = append(keyix.Objects[object], attribute)
|
||||
}
|
||||
|
||||
// object functions
|
||||
@ -313,10 +321,9 @@ func loadAttributes(path string) []*RegAttribute {
|
||||
for scanner.Scan() {
|
||||
|
||||
line := strings.TrimRight(scanner.Text(), "\r\n")
|
||||
runes := []rune(line)
|
||||
|
||||
// lines starting with '+' denote an empty line
|
||||
if runes[0] == rune('+') {
|
||||
if line[0] == '+' {
|
||||
|
||||
// concatenate a \n on to the previous attribute value
|
||||
attributes[len(attributes)-1].RawValue += "\n"
|
||||
@ -328,12 +335,12 @@ func loadAttributes(path string) []*RegAttribute {
|
||||
if ix == -1 || ix >= 20 {
|
||||
// couldn't find one
|
||||
|
||||
if len(runes) <= 20 {
|
||||
if len(line) <= 20 {
|
||||
// hmmm, the line was shorter than 20 characters
|
||||
// something is amiss
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"length": len(runes),
|
||||
"length": len(line),
|
||||
"path": path,
|
||||
"line": line,
|
||||
}).Warn("Short line detected")
|
||||
@ -343,7 +350,7 @@ func loadAttributes(path string) []*RegAttribute {
|
||||
// line is a continuation of the previous line, so
|
||||
// concatenate the value on to the previous attribute value
|
||||
attributes[len(attributes)-1].RawValue +=
|
||||
"\n" + string(runes[20:])
|
||||
"\n" + string(line[20:])
|
||||
|
||||
}
|
||||
} else {
|
||||
@ -351,16 +358,16 @@ func loadAttributes(path string) []*RegAttribute {
|
||||
|
||||
// is there actually a value ?
|
||||
var value string
|
||||
if len(runes) <= 20 {
|
||||
if len(line) <= 20 {
|
||||
// blank value
|
||||
value = ""
|
||||
} else {
|
||||
value = string(runes[20:])
|
||||
value = string(line[20:])
|
||||
}
|
||||
|
||||
// create a new attribute
|
||||
a := &RegAttribute{
|
||||
Key: string(runes[:ix]),
|
||||
Key: string(line[:ix]),
|
||||
RawValue: value,
|
||||
}
|
||||
attributes = append(attributes, a)
|
||||
@ -395,7 +402,7 @@ func (registry *Registry) parseSchema() {
|
||||
typeSchema := &RegTypeSchema{
|
||||
Ref: typeName,
|
||||
Attributes: make(map[string]*RegAttributeSchema),
|
||||
KeyIndex: make(map[string]map[*RegObject][]*RegAttribute),
|
||||
KeyIndex: make(map[string]*RegKeyIndex),
|
||||
}
|
||||
|
||||
// ensure the type exists
|
||||
|
Loading…
Reference in New Issue
Block a user