diff --git a/API.md b/API.md
index 4884b82..b7ea3de 100644
--- a/API.md
+++ b/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"
+ ]
+ }
+}
+```
diff --git a/StaticRoot/index.html b/StaticRoot/index.html
index 75dca35..b347ff9 100644
--- a/StaticRoot/index.html
+++ b/StaticRoot/index.html
@@ -66,7 +66,7 @@ body { box-shadow: inset 0 2em 10em rgba(0,0,0,0.4); min-height: 100vh }
Searching for type/ will return all the objects for that type (e.g.
schema/)
A blank search box will return you to these instructions
- Just copy to the URL to link to search results
+ Just copy the URL to link to search results
Searches are made on object names; searching the content of objects
is not supported (yet!).
diff --git a/dn42regsrv.go b/dn42regsrv.go
index fc6d42b..37c8ed3 100644
--- a/dn42regsrv.go
+++ b/dn42regsrv.go
@@ -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)
diff --git a/regapi.go b/regapi.go
index ee2288e..b27ebaa 100644
--- a/regapi.go
+++ b/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
diff --git a/registry.go b/registry.go
index 37c4a15..e551b7a 100644
--- a/registry.go
+++ b/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