github.com/kumasuke120/mockuma@v1.1.9/internal/mckmaps/template.go (about) 1 package mckmaps 2 3 import ( 4 "errors" 5 "fmt" 6 "regexp" 7 "strings" 8 9 "github.com/kumasuke120/mockuma/internal/myjson" 10 "github.com/kumasuke120/mockuma/internal/types" 11 ) 12 13 type template struct { 14 content interface{} 15 defaults *vars 16 filename string 17 } 18 19 type templateParser struct { 20 json myjson.Object 21 jsonPath *myjson.Path 22 Parser 23 } 24 25 var parsingTemplates []string 26 27 func (p *templateParser) parse() (*template, error) { 28 needLoading := p.json == nil 29 30 if needLoading { // adds the current file 31 parsingTemplates = append(parsingTemplates, p.filename) 32 err := p.checkCyclicReference() 33 if err != nil { 34 return nil, err 35 } 36 } 37 38 if needLoading { // loads file raw content 39 json, err := p.load(true, ppRemoveComment, ppRenderTemplate) 40 if err != nil { 41 return nil, err 42 } 43 44 switch json.(type) { 45 case myjson.Object: 46 p.json = json.(myjson.Object) 47 default: 48 return nil, p.newJSONParseError(p.jsonPath) 49 } 50 } 51 52 p.jsonPath = myjson.NewPath("") 53 p.jsonPath.SetLast(aType) 54 _type, err := p.json.GetString(aType) 55 if err != nil || string(_type) != tTemplate { 56 return nil, p.newJSONParseError(p.jsonPath) 57 } 58 59 template := new(template) 60 61 p.jsonPath.SetLast(tTemplate) 62 v := p.json.Get(tTemplate) 63 content, err := p.parseContent(v) 64 if err != nil { 65 return nil, err 66 } 67 template.content = content 68 69 p.jsonPath.SetLast(tVars) 70 var defaults *vars 71 if p.json.Has(tVars) { 72 vo, err := p.json.GetObject(tVars) 73 if err != nil { 74 return nil, p.newJSONParseError(p.jsonPath) 75 } 76 defaults, err = p.parseDefaults(vo) 77 if err != nil { 78 return nil, err 79 } 80 } else { 81 defaults = &vars{table: map[string]interface{}{}} 82 } 83 template.defaults = defaults 84 85 template.filename = p.filename 86 87 if needLoading { // removes the current file with checking 88 if parsingTemplates[len(parsingTemplates)-1] == p.filename { 89 parsingTemplates = parsingTemplates[:len(parsingTemplates)-1] 90 } else { 91 panic("Shouldn't happen") 92 } 93 } 94 return template, nil 95 } 96 97 func (p *templateParser) parseContent(v interface{}) (interface{}, error) { 98 switch v.(type) { 99 case myjson.Object: 100 return v, nil 101 case myjson.Array: 102 return v, nil 103 case myjson.String: 104 return v, nil 105 default: 106 return nil, p.newJSONParseError(p.jsonPath) 107 } 108 } 109 110 func (p *templateParser) parseDefaults(v myjson.Object) (*vars, error) { 111 r, err := parseVars(v) 112 if err != nil { 113 return nil, p.newJSONParseError(p.jsonPath) 114 } 115 return r, nil 116 } 117 118 func (p *templateParser) checkCyclicReference() error { 119 found := make(map[string]bool) 120 for _, t := range parsingTemplates { 121 if _, ok := found[t]; ok { 122 return &loadError{ 123 filename: p.filename, 124 err: errors.New("found a cyclic template application : " + 125 strings.Join(parsingTemplates, " -> ")), 126 } 127 } else { 128 found[t] = true 129 } 130 } 131 return nil 132 } 133 134 type renderError struct { 135 filename string 136 jsonPath *myjson.Path 137 } 138 139 func (e *renderError) Error() string { 140 result := "" 141 if e.jsonPath == nil { 142 result += "cannot render template" 143 } else { 144 result += fmt.Sprintf("cannot render the template on json-path \"%v\"", e.jsonPath) 145 } 146 147 if e.filename != "" { 148 result += fmt.Sprintf(" in the file '%s'", e.filename) 149 } 150 151 return result 152 } 153 154 func (t *template) renderAll(varsSlice []*vars) (myjson.Array, error) { 155 if len(varsSlice) == 0 { 156 return myjson.Array{}, nil 157 } 158 159 result := make(myjson.Array, len(varsSlice)) 160 for idx, _var := range varsSlice { 161 v, err := t.render(nil, t.content, _var) 162 if err != nil { 163 return nil, err 164 } 165 result[idx] = v 166 } 167 return result, nil 168 } 169 170 func (t *template) render(jsonPath *myjson.Path, v interface{}, varsSlice *vars) (interface{}, error) { 171 if jsonPath == nil { 172 jsonPath = myjson.NewPath() 173 } 174 175 var result interface{} 176 var err error 177 switch v.(type) { 178 case myjson.Object: 179 result, err = t.renderObject(jsonPath, v.(myjson.Object), varsSlice) 180 case myjson.Array: 181 result, err = t.renderArray(jsonPath, v.(myjson.Array), varsSlice) 182 case myjson.String: 183 result, err = t.renderString(jsonPath, v.(myjson.String), varsSlice) 184 default: 185 result, err = v, nil 186 } 187 188 return result, err 189 } 190 191 func (t *template) renderObject(jsonPath *myjson.Path, 192 v myjson.Object, vars *vars) (myjson.Object, error) { 193 jsonPath.Append("") 194 195 result := make(myjson.Object) 196 for name, value := range v { 197 jsonPath.SetLast(name) 198 199 rName, err := t.renderPlainString(jsonPath, name, vars) 200 if err != nil { 201 return nil, err 202 } 203 rValue, err := t.render(jsonPath, value, vars) 204 if err != nil { 205 return nil, err 206 } 207 result[rName] = rValue 208 } 209 210 jsonPath.RemoveLast() 211 return result, nil 212 } 213 214 func (t *template) renderArray(jsonPath *myjson.Path, 215 v myjson.Array, vars *vars) (myjson.Array, error) { 216 jsonPath.Append(0) 217 218 result := make(myjson.Array, len(v)) 219 for idx, value := range v { 220 jsonPath.SetLast(idx) 221 222 rValue, err := t.render(jsonPath, value, vars) 223 if err != nil { 224 return nil, err 225 } 226 result[idx] = rValue 227 } 228 229 jsonPath.RemoveLast() 230 return result, nil 231 } 232 233 // states for rendering string 234 const ( 235 rsReady = iota 236 rsMaybePlaceholder 237 rsInPlaceholder 238 rsMaybePlaceHolderFormat 239 rsInPlaceHolderFormat 240 ) 241 242 // tokens for rendering string 243 const ( 244 placeholderPrefix = '@' 245 placeholderLeft = '{' 246 placeholderRight = '}' 247 placeholderFormatSeparator = ':' 248 ) 249 250 func (t *template) renderPlainString(jsonPath *myjson.Path, 251 v string, vars *vars) (string, error) { 252 r, err := t.renderString(jsonPath, myjson.String(v), vars) 253 if err != nil { 254 return "", err 255 } else { 256 switch r.(type) { 257 case myjson.String: 258 return string(r.(myjson.String)), nil 259 default: 260 return types.ToString(r), nil 261 } 262 } 263 } 264 265 func (t *template) renderString(jsonPath *myjson.Path, v myjson.String, vars *vars) (interface{}, error) { 266 s := rsReady 267 268 runes := []rune(v) 269 270 var fromBegin, toEnd bool 271 272 var builder strings.Builder 273 var nameBuilder strings.Builder 274 var formatBuilder strings.Builder 275 276 for i := 0; i < len(runes); i++ { 277 r := runes[i] 278 doWrite := true 279 doWriteName := false 280 doWriteFormat := false 281 282 switch s { 283 case rsReady: 284 if r == placeholderPrefix { 285 s = rsMaybePlaceholder 286 if i == 0 { 287 fromBegin = true 288 } else { 289 fromBegin = false 290 } 291 doWrite = false 292 } 293 case rsMaybePlaceholder: 294 if r == placeholderLeft { 295 s = rsInPlaceholder 296 doWrite = false 297 } else { 298 s = rsReady 299 if r != placeholderPrefix { // replaces "@@" to "@" 300 builder.WriteString(string(placeholderPrefix)) 301 } 302 if fromBegin { 303 fromBegin = false 304 } 305 } 306 case rsInPlaceholder: 307 doWrite = false 308 if r == placeholderRight { 309 s = rsReady 310 if fromBegin && i == len(runes)-1 { 311 toEnd = true 312 } else { 313 varName := nameBuilder.String() 314 varFormat := formatBuilder.String() 315 if varName == "" { 316 return "", &renderError{filename: t.filename, jsonPath: jsonPath} 317 } 318 319 v, err := t.renderTextString(vars, varName, varFormat) 320 if err != nil { 321 return nil, &renderError{filename: t.filename, jsonPath: jsonPath} 322 } 323 builder.WriteString(v) 324 nameBuilder.Reset() 325 formatBuilder.Reset() 326 } 327 } else if r == placeholderFormatSeparator { 328 s = rsMaybePlaceHolderFormat 329 } else { 330 doWriteName = true 331 } 332 case rsMaybePlaceHolderFormat: 333 doWrite = false 334 if r == placeholderRight { // same as empty format, state rolls back 335 s = rsInPlaceholder 336 } else { 337 s = rsInPlaceHolderFormat 338 } 339 i -= 1 // goes back for other state to process 340 case rsInPlaceHolderFormat: 341 doWrite = false 342 if r == placeholderRight { // end of placeholder 343 s = rsInPlaceholder 344 i -= 1 345 } else { 346 doWriteFormat = true 347 } 348 } 349 350 if doWrite { 351 builder.WriteRune(r) 352 } 353 if doWriteName { 354 nameBuilder.WriteRune(r) 355 } 356 if doWriteFormat { 357 formatBuilder.WriteRune(r) 358 } 359 } 360 361 if s != rsReady { // placeholder is not complete 362 return nil, &renderError{filename: t.filename, jsonPath: jsonPath} 363 } 364 365 if fromBegin && toEnd { // if the whole string is a placeholder 366 varName := nameBuilder.String() 367 if varName == "" { 368 return nil, &renderError{filename: t.filename, jsonPath: jsonPath} 369 } 370 371 if vVal, vSet := t.getVarValue(vars, varName); vSet { 372 return vVal, nil 373 } else { 374 // undefined var 375 return nil, &renderError{filename: t.filename, jsonPath: jsonPath} 376 } 377 } 378 379 return myjson.String(builder.String()), nil 380 } 381 382 var validVarFormat = regexp.MustCompile("^%([-+@0 ])?(\\d+)?\\.?(\\d+)?[tdeEfgsqxX]$") 383 384 func (t *template) renderTextString(vars *vars, varName string, varFormat string) (string, error) { 385 if varFormat != "" && !validVarFormat.MatchString(varFormat) { 386 return "", errors.New("invalid format for var") 387 } 388 389 vVal, vSet := t.getVarValue(vars, varName) 390 if vSet && vVal == nil { // value set but is nil, e.g. { "key": null } 391 vVal = myjson.String("") 392 } 393 switch vVal.(type) { 394 case myjson.String: 395 if varFormat == "" { 396 varFormat = "%s" 397 } 398 return fmt.Sprintf(varFormat, string(vVal.(myjson.String))), nil 399 case myjson.Number: 400 if varFormat == "" { 401 return fmt.Sprintf("%v", vVal), nil 402 } else if varFormat[len(varFormat)-1] == 'd' { 403 return fmt.Sprintf(varFormat, int(float64(vVal.(myjson.Number)))), nil 404 } else { 405 return fmt.Sprintf(varFormat, float64(vVal.(myjson.Number))), nil 406 } 407 case myjson.Boolean: 408 if varFormat == "" { 409 varFormat = "%v" 410 } 411 return fmt.Sprintf(varFormat, bool(vVal.(myjson.Boolean))), nil 412 default: 413 return "", errors.New("invalid json type for template rendering") 414 } 415 } 416 417 func (t *template) getVarValue(vars *vars, varName string) (vVal interface{}, vSet bool) { 418 if vVal, vSet = vars.table[varName]; !vSet { 419 // finds in defaults if not found 420 vVal, vSet = t.defaults.table[varName] 421 } 422 return 423 }