kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/serving/identifiers/identifiers.go (about) 1 /* 2 * Copyright 2017 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // Package identifiers provides a high-performance table-based implementation of the 18 // identifiers.Service. 19 // The table is structured as: 20 // 21 // qualifed_name -> IdentifierMatch 22 package identifiers // import "kythe.io/kythe/go/serving/identifiers" 23 24 import ( 25 "context" 26 "net/http" 27 "time" 28 29 "kythe.io/kythe/go/services/web" 30 "kythe.io/kythe/go/storage/table" 31 "kythe.io/kythe/go/util/kytheuri" 32 "kythe.io/kythe/go/util/log" 33 34 "google.golang.org/protobuf/proto" 35 36 ipb "kythe.io/kythe/proto/identifier_go_proto" 37 srvpb "kythe.io/kythe/proto/serving_go_proto" 38 ) 39 40 // Service describes the interface for the identifier service which provides 41 // lookups from fully qualified identifiers to any matching semantic nodes 42 type Service interface { 43 // Find returns an index of nodes associated with a given identifier 44 Find(context.Context, *ipb.FindRequest) (*ipb.FindReply, error) 45 46 // Close releases any underlying resources. 47 Close(context.Context) error 48 } 49 50 // Table wraps around a table.Proto to provide the Service interface 51 type Table struct { 52 table.Proto 53 } 54 55 type matchNode struct { 56 match *ipb.FindReply_Match 57 canonicalTicket string 58 } 59 60 // Find implements the Service interface for Table 61 func (it *Table) Find(ctx context.Context, req *ipb.FindRequest) (*ipb.FindReply, error) { 62 var ( 63 qname = req.GetIdentifier() 64 corpora = req.GetCorpus() 65 languages = req.GetLanguages() 66 reply ipb.FindReply 67 ) 68 69 // If clustering is done across language boundaries (e.g. proto), then it is possible the 70 // canonical node has a different qualified name (e.g. build_event_stream.BuildEvent vs 71 // build_event_stream::BuildEvent). If the user asked for the qualified name that isn't the 72 // canonical node, return all results since we won't be returning the canonical node. 73 allNodes := make(map[string]*matchNode) 74 var orderedTickets []string 75 76 if err := it.LookupValues(ctx, []byte(qname), (*srvpb.IdentifierMatch)(nil), func(msg proto.Message) error { 77 match := msg.(*srvpb.IdentifierMatch) 78 for _, node := range match.GetNode() { 79 if !validCorpusAndLang(corpora, languages, node) { 80 continue 81 } 82 83 replyMatch := &ipb.FindReply_Match{ 84 Ticket: node.GetTicket(), 85 NodeKind: node.GetNodeKind(), 86 NodeSubkind: node.GetNodeSubkind(), 87 BaseName: match.GetBaseName(), 88 QualifiedName: match.GetQualifiedName(), 89 } 90 if n := allNodes[node.GetTicket()]; n != nil { 91 log.ErrorContextf(ctx, "Two matches found for the same ticket: %v, %v", n, node) 92 } 93 allNodes[node.GetTicket()] = &matchNode{ 94 match: replyMatch, 95 canonicalTicket: node.GetCanonicalNodeTicket(), 96 } 97 orderedTickets = append(orderedTickets, node.GetTicket()) 98 } 99 return nil 100 }); err != nil { 101 return nil, err 102 } 103 104 for _, t := range orderedTickets { 105 ticketMatch := allNodes[t] 106 if req.GetPickCanonicalNodes() && 107 ticketMatch.canonicalTicket != "" && 108 ticketMatch.canonicalTicket != ticketMatch.match.GetTicket() && 109 allNodes[ticketMatch.canonicalTicket] != nil { 110 continue 111 } 112 113 reply.Matches = append(reply.GetMatches(), ticketMatch.match) 114 } 115 116 return &reply, nil 117 } 118 119 func validCorpusAndLang(corpora, langs []string, node *srvpb.IdentifierMatch_Node) bool { 120 uri, err := kytheuri.Parse(node.GetTicket()) 121 if err != nil { 122 return false 123 } 124 125 if len(langs) > 0 && !contains(langs, uri.Language) { 126 return false 127 } 128 129 if len(corpora) > 0 && !contains(corpora, uri.Corpus) { 130 return false 131 } 132 133 return true 134 } 135 136 func contains(haystack []string, needle string) bool { 137 for _, s := range haystack { 138 if s == needle { 139 return true 140 } 141 } 142 return false 143 } 144 145 // RegisterHTTPHandlers registers a JSON HTTP handler with mux using the given 146 // identifiers Service. The following method with be exposed: 147 // 148 // GET /find_identifier 149 // Request: JSON encoded identifier.FindRequest 150 // Response: JSON encoded identifier.FindReply 151 // 152 // Note: /find_identifier will return its response as a serialized protobuf if 153 // the "proto" query parameter is set. 154 func RegisterHTTPHandlers(ctx context.Context, id Service, mux *http.ServeMux) { 155 mux.HandleFunc("/find_identifier", func(w http.ResponseWriter, r *http.Request) { 156 start := time.Now() 157 defer func() { 158 log.InfoContextf(ctx, "identifiers.Find:\t%s", time.Since(start)) 159 }() 160 var req ipb.FindRequest 161 if err := web.ReadJSONBody(r, &req); err != nil { 162 http.Error(w, err.Error(), http.StatusBadRequest) 163 return 164 } 165 reply, err := id.Find(ctx, &req) 166 if err != nil { 167 http.Error(w, err.Error(), http.StatusInternalServerError) 168 return 169 } 170 171 if err := web.WriteResponse(w, r, reply); err != nil { 172 log.InfoContext(ctx, err) 173 } 174 }) 175 } 176 177 type webClient struct{ addr string } 178 179 func (webClient) Close(context.Context) error { return nil } 180 181 // Find implements part of the Service interface. 182 func (w *webClient) Find(ctx context.Context, q *ipb.FindRequest) (*ipb.FindReply, error) { 183 var reply ipb.FindReply 184 return &reply, web.Call(w.addr, "find_identifier", q, &reply) 185 } 186 187 // WebClient returns an identifiers Service based on a remote web server. 188 func WebClient(addr string) Service { 189 return &webClient{addr} 190 }