istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/diag/message.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package diag
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"strconv"
    21  	"strings"
    22  
    23  	"istio.io/api/analysis/v1alpha1"
    24  	"istio.io/istio/pkg/config/resource"
    25  	"istio.io/istio/pkg/url"
    26  )
    27  
    28  // MessageType is a type of diagnostic message
    29  type MessageType struct {
    30  	// The level of the message.
    31  	level Level
    32  
    33  	// The error code of the message
    34  	code string
    35  
    36  	// TODO: Make this localizable
    37  	template string
    38  }
    39  
    40  // Level returns the level of the MessageType
    41  func (m *MessageType) Level() Level { return m.level }
    42  
    43  // Code returns the code of the MessageType
    44  func (m *MessageType) Code() string { return m.code }
    45  
    46  // Template returns the message template used by the MessageType
    47  func (m *MessageType) Template() string { return m.template }
    48  
    49  // Message is a specific diagnostic message
    50  // TODO: Implement using Analysis message API
    51  type Message struct {
    52  	Type *MessageType
    53  
    54  	// The Parameters to the message
    55  	Parameters []any
    56  
    57  	// Resource is the underlying resource instance associated with the
    58  	// message, or nil if no resource is associated with it.
    59  	Resource *resource.Instance
    60  
    61  	// DocRef is an optional reference tracker for the documentation URL
    62  	DocRef string
    63  
    64  	// Line is the line number of the error place in the message
    65  	Line int
    66  }
    67  
    68  // Unstructured returns this message as a JSON-style unstructured map
    69  func (m *Message) Unstructured(includeOrigin bool) map[string]any {
    70  	result := make(map[string]any)
    71  
    72  	result["code"] = m.Type.Code()
    73  	result["level"] = m.Type.Level().String()
    74  	if includeOrigin && m.Resource != nil {
    75  		result["origin"] = m.Resource.Origin.FriendlyName()
    76  		if m.Resource.Origin.Reference() != nil {
    77  			loc := m.Resource.Origin.Reference().String()
    78  			if m.Line != 0 {
    79  				loc = m.ReplaceLine(loc)
    80  			}
    81  			result["reference"] = loc
    82  		}
    83  	}
    84  	result["message"] = fmt.Sprintf(m.Type.Template(), m.Parameters...)
    85  
    86  	docQueryString := ""
    87  	if m.DocRef != "" {
    88  		docQueryString = fmt.Sprintf("?ref=%s", m.DocRef)
    89  	}
    90  	result["documentationUrl"] = fmt.Sprintf("%s/%s/%s", url.ConfigAnalysis, strings.ToLower(m.Type.Code()), docQueryString)
    91  
    92  	return result
    93  }
    94  
    95  func (m *Message) AnalysisMessageBase() *v1alpha1.AnalysisMessageBase {
    96  	docQueryString := ""
    97  	if m.DocRef != "" {
    98  		docQueryString = fmt.Sprintf("?ref=%s", m.DocRef)
    99  	}
   100  	docURL := fmt.Sprintf("%s/%s/%s", url.ConfigAnalysis, strings.ToLower(m.Type.Code()), docQueryString)
   101  
   102  	return &v1alpha1.AnalysisMessageBase{
   103  		DocumentationUrl: docURL,
   104  		Level:            v1alpha1.AnalysisMessageBase_Level(v1alpha1.AnalysisMessageBase_Level_value[strings.ToUpper(m.Type.Level().String())]),
   105  		Type: &v1alpha1.AnalysisMessageBase_Type{
   106  			Code: m.Type.Code(),
   107  		},
   108  	}
   109  }
   110  
   111  // UnstructuredAnalysisMessageBase returns this message as a JSON-style unstructured map in AnalaysisMessageBase
   112  // TODO(jasonwzm): Remove once message implements AnalysisMessageBase
   113  func (m *Message) UnstructuredAnalysisMessageBase() map[string]any {
   114  	mb := m.AnalysisMessageBase()
   115  
   116  	var r map[string]any
   117  
   118  	j, err := json.Marshal(mb)
   119  	if err != nil {
   120  		return r
   121  	}
   122  	_ = json.Unmarshal(j, &r)
   123  
   124  	return r
   125  }
   126  
   127  // Origin returns the origin of the message
   128  func (m *Message) Origin() string {
   129  	origin := ""
   130  	if m.Resource != nil {
   131  		loc := ""
   132  		if m.Resource.Origin.Reference() != nil {
   133  			loc = " " + m.Resource.Origin.Reference().String()
   134  			if m.Line != 0 {
   135  				loc = m.ReplaceLine(loc)
   136  			}
   137  		}
   138  		origin = " (" + m.Resource.Origin.FriendlyName() + loc + ")"
   139  	}
   140  	return origin
   141  }
   142  
   143  // String implements io.Stringer
   144  func (m *Message) String() string {
   145  	return fmt.Sprintf("%v [%v]%s %s",
   146  		m.Type.Level(), m.Type.Code(), m.Origin(),
   147  		fmt.Sprintf(m.Type.Template(), m.Parameters...))
   148  }
   149  
   150  // MarshalJSON satisfies the Marshaler interface
   151  func (m *Message) MarshalJSON() ([]byte, error) {
   152  	return json.Marshal(m.Unstructured(true))
   153  }
   154  
   155  // NewMessageType returns a new MessageType instance.
   156  func NewMessageType(level Level, code, template string) *MessageType {
   157  	return &MessageType{
   158  		level:    level,
   159  		code:     code,
   160  		template: template,
   161  	}
   162  }
   163  
   164  // NewMessage returns a new Message instance from an existing type.
   165  func NewMessage(mt *MessageType, r *resource.Instance, p ...any) Message {
   166  	return Message{
   167  		Type:       mt,
   168  		Resource:   r,
   169  		Parameters: p,
   170  	}
   171  }
   172  
   173  // ReplaceLine replaces the line number from the input String method of Reference to the line number from Message
   174  func (m Message) ReplaceLine(l string) string {
   175  	colonSep := strings.Split(l, ":")
   176  	if len(colonSep) < 2 {
   177  		return l
   178  	}
   179  	_, err := strconv.Atoi(strings.TrimSpace(colonSep[len(colonSep)-1]))
   180  	if err == nil {
   181  		colonSep[len(colonSep)-1] = fmt.Sprintf("%d", m.Line)
   182  	}
   183  	return strings.Join(colonSep, ":")
   184  }