////////////////////////////////////////////////////////////////////////// // DN42 Registry API Server ////////////////////////////////////////////////////////////////////////// package main ////////////////////////////////////////////////////////////////////////// import ( // "fmt" "github.com/gorilla/mux" log "github.com/sirupsen/logrus" "net/http" "strings" // "time" ) ////////////////////////////////////////////////////////////////////////// // register the api func init() { EventBus.Listen("APIEndpoint", InitRegistryAPI) } ////////////////////////////////////////////////////////////////////////// // called from main to initialise the API routing func InitRegistryAPI(params ...interface{}) { router := params[0].(*mux.Router) s := router. Methods("GET"). PathPrefix("/registry"). Subrouter() s.HandleFunc("/", regRootHandler) //s.HandleFunc("/.schema", rTypeListHandler) //s.HandleFunc("/.meta/", rTypeListHandler) 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") } ////////////////////////////////////////////////////////////////////////// // 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 func regRootHandler(w http.ResponseWriter, r *http.Request) { response := make(map[string]int) for _, rType := range RegistryData.Types { response[rType.Ref] = len(rType.Objects) } ResponseJSON(w, response) } ////////////////////////////////////////////////////////////////////////// // type handler returns list of objects that match the type func regTypeHandler(w http.ResponseWriter, r *http.Request) { // request parameters vars := mux.Vars(r) 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 rtypes { objects := make([]string, 0, len(rtype.Objects)) for key := range rtype.Objects { objects = append(objects, key) } response[rtype.Ref] = objects } ResponseJSON(w, response) } ////////////////////////////////////////////////////////////////////////// // object handler returns object data // per object response structure type RegObjectResponse struct { Attributes [][2]string Backlinks []string } func regObjectHandler(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 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 } // 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 if raw == nil { // provide a decorated response response := make(map[string]RegObjectResponse) // for each object in the results for _, object := range objects { // copy the raw attributes attributes := make([][2]string, len(object.Data)) for ix, attribute := range object.Data { attributes[ix] = [2]string{attribute.Key, attribute.Value} } // construct the backlinks backlinks := make([]string, len(object.Backlinks)) for ix, object := range object.Backlinks { backlinks[ix] = object.Ref } // add to the response response[object.Ref] = RegObjectResponse{ Attributes: attributes, Backlinks: backlinks, } } ResponseJSON(w, response) } else { // provide a response with just the raw registry data response := make(map[string][][2]string) // for each object in the results for _, object := range objects { attributes := make([][2]string, len(object.Data)) response[object.Ref] = attributes // copy the raw attributes for ix, attribute := range object.Data { attributes[ix] = [2]string{attribute.Key, attribute.RawValue} } } ResponseJSON(w, response) } } ////////////////////////////////////////////////////////////////////////// // 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