Make the home page's AS name search work (closes #151)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
The AS Name Search card on the home page said the search was coming
soon. It now asks the new GET /api/v1/search/as?q=<text>, which
searches the AS list built into asinfo, and lists the matches as links
to their AS pages. An empty q answers 400. asinfo.Search now ignores
case and returns its matches in AS number order.
A match can be an AS that the database does not hold yet. Its AS page
and /api/v1/as/{asn} now answer with its handle and description from
asinfo and no prefixes, instead of 404. Empty prefix lists in that JSON
answer are [] rather than null.
Model: opus-5-5
This commit is contained in:
@@ -56,10 +56,18 @@ const (
|
||||
// Error messages an answer sends, in place of the error behind it where there
|
||||
// is one.
|
||||
const (
|
||||
invalidPrefixMessage = "Invalid prefix"
|
||||
hostNotResolvedMessage = "could not resolve host name"
|
||||
hostNotPublicMessage = "host name has no public address"
|
||||
tooManyTargetsMessage = "Too many addresses or host names"
|
||||
invalidPrefixMessage = "Invalid prefix"
|
||||
hostNotResolvedMessage = "could not resolve host name"
|
||||
hostNotPublicMessage = "host name has no public address"
|
||||
tooManyTargetsMessage = "Too many addresses or host names"
|
||||
searchQueryMissingMessage = "Missing search query"
|
||||
)
|
||||
|
||||
// The AS name search reads the text it looks for from the URL query parameter
|
||||
// asSearchQueryParam, and answers with at most maxASSearchResults matches.
|
||||
const (
|
||||
asSearchQueryParam = "q"
|
||||
maxASSearchResults = 50
|
||||
)
|
||||
|
||||
// IP versions as database.LiveRoute.IPVersion records them.
|
||||
@@ -1001,11 +1009,12 @@ func (s *Server) handleASDetailJSON() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// splitRoutesByIPVersion groups routes into the IPv4 and the IPv6 ones.
|
||||
// splitRoutesByIPVersion groups routes into the IPv4 and the IPv6 ones. Neither
|
||||
// group is nil, so that a JSON answer holds [] for an empty one and not null.
|
||||
func splitRoutesByIPVersion(
|
||||
routes []database.LiveRoute,
|
||||
) ([]database.LiveRoute, []database.LiveRoute) {
|
||||
var ipv4Routes, ipv6Routes []database.LiveRoute
|
||||
ipv4Routes, ipv6Routes := []database.LiveRoute{}, []database.LiveRoute{}
|
||||
|
||||
for _, route := range routes {
|
||||
if route.IPVersion == ipVersionV4 {
|
||||
@@ -1105,6 +1114,46 @@ func (s *Server) handlePrefixDetailJSON() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// ASSearchResponse is the answer to an AS name search: the text searched for,
|
||||
// the number of ASes that match it, and the first maxASSearchResults of them in
|
||||
// AS number order.
|
||||
type ASSearchResponse struct {
|
||||
Query string `json:"query"`
|
||||
Total int `json:"total"`
|
||||
Results []asinfo.Info `json:"results"`
|
||||
}
|
||||
|
||||
// handleASSearch returns a handler that searches the AS list built into
|
||||
// asinfo for the text in the q parameter, without its leading and trailing
|
||||
// spaces. It answers 400 when no text is left.
|
||||
func (s *Server) handleASSearch() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
query := strings.TrimSpace(r.URL.Query().Get(asSearchQueryParam))
|
||||
if query == "" {
|
||||
s.writeJSONError(w, http.StatusBadRequest, searchQueryMissingMessage)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
matches := asinfo.Search(query)
|
||||
|
||||
// results is an empty list rather than nil when nothing matches, so
|
||||
// that the answer holds [] and not null.
|
||||
shown := min(len(matches), maxASSearchResults)
|
||||
results := make([]asinfo.Info, 0, shown)
|
||||
results = append(results, matches[:shown]...)
|
||||
|
||||
err := writeJSONSuccess(w, ASSearchResponse{
|
||||
Query: query,
|
||||
Total: len(matches),
|
||||
Results: results,
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to encode AS search results", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handleASDetail returns a handler that serves the AS detail HTML page
|
||||
func (s *Server) handleASDetail() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user