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 }