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