Refactor API to include key and attribute matching

This commit is contained in:
Simon Marsh 2019-02-16 15:20:35 +00:00
parent 7531c642ff
commit ab9628b212
Signed by: burble
GPG Key ID: 7B9FE8780CFB6593
5 changed files with 673 additions and 190 deletions

331
API.md
View File

@ -1,83 +1,278 @@
# dn42regsrv API Description # dn42regsrv API Description
## GET /<file> ## Registry API
If the StaticRoot configuration option points to a readable directory, files from The general form of the registry query API is:
the directory will be served under /
The git repository contains a sample StaticRoot directory with a simple registry GET /api/registry/{type}/{object}/{key}/{attribute}?raw
explorer web app.
## 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 By default results are returned as JSON objects, and the registry data is decorated
of the number of registry objects for each type. 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
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
reference this one. reference this one.
If the match parameter is provided, the <object> is substring matched against all If the 'raw' parameter is provided, attributes are returned un-decorated exactly
objects in the <type>. Matching is case insensitive. 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 wget -O - -q http://localhost:8042/api/registry/ | jq
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 "as-block": 8,
http://localhost:8042/api/registry/schema/* # return all schema objects "as-set": 34,
"aut-num": 1486,
# sample output (raw) "domain": 451,
{"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"]]} "inet6num": 746,
"inetnum": 1276,
# sample output (decorated) "key-cert": 7,
{"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"]}} "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"
]
}
}
```

View File

@ -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. <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> <router-link class="text-success" to="schema/">schema/</router-link>)</li>
<li>A blank search box will return you to these instructions</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 <li>Searches are made on object names; searching the content of objects
is not supported (yet!).</li> is not supported (yet!).</li>
</ul> </ul>

View File

@ -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) { // add a listener to an event
apiEndpoints = append(apiEndpoints, f) 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 // log all access
router.Use(requestLogger) router.Use(requestLogger)
// initialise API routes // add API routes
subr := router.PathPrefix("/api").Subrouter() subr := router.PathPrefix("/api").Subrouter()
for _, epInit := range apiEndpoints { EventBus.Fire("APIEndpoint", subr)
epInit(subr)
}
// initialise static routes // initialise static routes
InstallStaticRoutes(router, *staticRoot) InstallStaticRoutes(router, *staticRoot)

465
regapi.go
View File

@ -8,6 +8,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
// "fmt"
"github.com/gorilla/mux" "github.com/gorilla/mux"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"net/http" "net/http"
@ -19,13 +20,15 @@ import (
// register the api // register the api
func init() { func init() {
RegisterAPIEndpoint(InitRegAPI) EventBus.Listen("APIEndpoint", InitRegistryAPI)
} }
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// called from main to initialise the API routing // called from main to initialise the API routing
func InitRegAPI(router *mux.Router) { func InitRegistryAPI(params ...interface{}) {
router := params[0].(*mux.Router)
s := router. s := router.
Methods("GET"). Methods("GET").
@ -38,6 +41,8 @@ func InitRegAPI(router *mux.Router) {
s.HandleFunc("/{type}", regTypeHandler) s.HandleFunc("/{type}", regTypeHandler)
s.HandleFunc("/{type}/{object}", regObjectHandler) s.HandleFunc("/{type}/{object}", regObjectHandler)
s.HandleFunc("/{type}/{object}/{key}", regKeyHandler)
s.HandleFunc("/{type}/{object}/{key}/{attribute}", regAttributeHandler)
log.Info("Registry API installed") log.Info("Registry API installed")
} }
@ -63,6 +68,242 @@ func responseJSON(w http.ResponseWriter, v interface{}) {
w.Write(data) 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 // root handler, lists all types within the registry
@ -83,62 +324,26 @@ func regTypeHandler(w http.ResponseWriter, r *http.Request) {
// request parameters // request parameters
vars := mux.Vars(r) vars := mux.Vars(r)
query := r.URL.Query() tFilter := vars["type"] // type filter
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)
}
}
// 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 // construct the response
response := make(map[string][]string) response := make(map[string][]string)
for _, rType := range results { for _, rtype := range rtypes {
objects := make([]string, 0, len(rType.Objects)) objects := make([]string, 0, len(rtype.Objects))
for key := range rType.Objects { for key := range rtype.Objects {
objects = append(objects, key) objects = append(objects, key)
} }
response[rType.Ref] = objects response[rtype.Ref] = objects
} }
responseJSON(w, response) responseJSON(w, response)
@ -159,58 +364,24 @@ func regObjectHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
query := r.URL.Query() query := r.URL.Query()
typeName := vars["type"] // object type tFilter := vars["type"] // type filter
objName := vars["object"] // object name or match oFilter := vars["object"] // object filter
match := query["match"] // single query or match
raw := query["raw"] // raw or decorated results raw := query["raw"] // raw or decorated results
// special case to return all objects // select the type(s)
all := false rtypes := filterTypes(tFilter)
if objName == "*" { if rtypes == nil {
match = []string{} http.Error(w, "No objects matching '"+tFilter+"' found",
all = true
}
// verify the type exists
rType := RegistryData.Types[typeName]
if rType == nil {
http.Error(w, "No types matching '"+typeName+"' found",
http.StatusNotFound) http.StatusNotFound)
return return
} }
// results will hold the objects to return // then select the objects
var results []*RegObject objects := filterObjects(rtypes, oFilter)
if objects == nil {
// check match type http.Error(w, "No objects matching '"+tFilter+
if match == nil { "/"+oFilter+"' found", http.StatusNotFound)
// exact match return
// 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)
}
}
} }
// collate the results in to the response data // 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) response := make(map[string]RegObjectResponse)
// for each object in the results // for each object in the results
for _, object := range results { for _, object := range objects {
// copy the raw attributes // copy the raw attributes
attributes := make([][2]string, len(object.Data)) 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) response := make(map[string][][2]string)
// for each object in the results // for each object in the results
for _, object := range results { for _, object := range objects {
attributes := make([][2]string, len(object.Data)) attributes := make([][2]string, len(object.Data))
response[object.Ref] = attributes 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 // end of code

View File

@ -49,10 +49,15 @@ type RegAttributeSchema struct {
Relations []*RegType Relations []*RegType
} }
type RegKeyIndex struct {
Ref string
Objects map[*RegObject][]*RegAttribute
}
type RegTypeSchema struct { type RegTypeSchema struct {
Ref string Ref string
Attributes map[string]*RegAttributeSchema Attributes map[string]*RegAttributeSchema
KeyIndex map[string]map[*RegObject][]*RegAttribute KeyIndex map[string]*RegKeyIndex
} }
// the registry itself // the registry itself
@ -146,15 +151,18 @@ func (schema *RegTypeSchema) validate(attributes []*RegAttribute) []*RegAttribut
func (schema *RegTypeSchema) addKeyIndex(object *RegObject, func (schema *RegTypeSchema) addKeyIndex(object *RegObject,
attribute *RegAttribute) { attribute *RegAttribute) {
objmap := schema.KeyIndex[attribute.Key] keyix := schema.KeyIndex[attribute.Key]
// create a new object map if it didn't exist // create a new object map if it didn't exist
if objmap == nil { if keyix == nil {
objmap = make(map[*RegObject][]*RegAttribute) keyix = &RegKeyIndex{
schema.KeyIndex[attribute.Key] = objmap Ref: attribute.Key,
Objects: make(map[*RegObject][]*RegAttribute),
}
schema.KeyIndex[attribute.Key] = keyix
} }
// add the object/attribute reference // add the object/attribute reference
objmap[object] = append(objmap[object], attribute) keyix.Objects[object] = append(keyix.Objects[object], attribute)
} }
// object functions // object functions
@ -313,10 +321,9 @@ func loadAttributes(path string) []*RegAttribute {
for scanner.Scan() { for scanner.Scan() {
line := strings.TrimRight(scanner.Text(), "\r\n") line := strings.TrimRight(scanner.Text(), "\r\n")
runes := []rune(line)
// lines starting with '+' denote an empty line // lines starting with '+' denote an empty line
if runes[0] == rune('+') { if line[0] == '+' {
// concatenate a \n on to the previous attribute value // concatenate a \n on to the previous attribute value
attributes[len(attributes)-1].RawValue += "\n" attributes[len(attributes)-1].RawValue += "\n"
@ -328,12 +335,12 @@ func loadAttributes(path string) []*RegAttribute {
if ix == -1 || ix >= 20 { if ix == -1 || ix >= 20 {
// couldn't find one // couldn't find one
if len(runes) <= 20 { if len(line) <= 20 {
// hmmm, the line was shorter than 20 characters // hmmm, the line was shorter than 20 characters
// something is amiss // something is amiss
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"length": len(runes), "length": len(line),
"path": path, "path": path,
"line": line, "line": line,
}).Warn("Short line detected") }).Warn("Short line detected")
@ -343,7 +350,7 @@ func loadAttributes(path string) []*RegAttribute {
// line is a continuation of the previous line, so // line is a continuation of the previous line, so
// concatenate the value on to the previous attribute value // concatenate the value on to the previous attribute value
attributes[len(attributes)-1].RawValue += attributes[len(attributes)-1].RawValue +=
"\n" + string(runes[20:]) "\n" + string(line[20:])
} }
} else { } else {
@ -351,16 +358,16 @@ func loadAttributes(path string) []*RegAttribute {
// is there actually a value ? // is there actually a value ?
var value string var value string
if len(runes) <= 20 { if len(line) <= 20 {
// blank value // blank value
value = "" value = ""
} else { } else {
value = string(runes[20:]) value = string(line[20:])
} }
// create a new attribute // create a new attribute
a := &RegAttribute{ a := &RegAttribute{
Key: string(runes[:ix]), Key: string(line[:ix]),
RawValue: value, RawValue: value,
} }
attributes = append(attributes, a) attributes = append(attributes, a)
@ -395,7 +402,7 @@ func (registry *Registry) parseSchema() {
typeSchema := &RegTypeSchema{ typeSchema := &RegTypeSchema{
Ref: typeName, Ref: typeName,
Attributes: make(map[string]*RegAttributeSchema), Attributes: make(map[string]*RegAttributeSchema),
KeyIndex: make(map[string]map[*RegObject][]*RegAttribute), KeyIndex: make(map[string]*RegKeyIndex),
} }
// ensure the type exists // ensure the type exists