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  }