1
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:
Simon Marsh 2019-02-16 15:20:35 +00:00
parent 7531c642ff
commit ab9628b212
No known key found for this signature in database
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
## 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"
]
}
}
```

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.
<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>

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) {
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)

461
regapi.go
View File

@ -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()
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)
// match registry types against the filter
rtypes := filterTypes(tFilter)
if rtypes == nil {
http.Error(w, "No objects matching '"+tFilter+"' 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)
}
}
}
// 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,67 +364,33 @@ 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)
// then select the objects
objects := filterObjects(rtypes, oFilter)
if objects == nil {
http.Error(w, "No objects matching '"+tFilter+
"/"+oFilter+"' 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
if raw == nil {
// provide a decorated response
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

View File

@ -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