macros.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. package macro
  2. import (
  3. "errors"
  4. "fmt"
  5. "net"
  6. "net/mail"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "github.com/kataras/iris/v12/macro/interpreter/ast"
  11. "github.com/google/uuid"
  12. )
  13. var (
  14. // String type
  15. // Allows anything (single path segment, as everything except the `Path`).
  16. // Its functions can be used by the rest of the macros and param types whenever not available function by name is used.
  17. // Because of its "master" boolean value to true (third parameter).
  18. String = NewMacro("string", "", true, false, nil).
  19. RegisterFunc("regexp", MustRegexp).
  20. // checks if param value starts with the 'prefix' arg
  21. RegisterFunc("prefix", func(prefix string) func(string) bool {
  22. return func(paramValue string) bool {
  23. return strings.HasPrefix(paramValue, prefix)
  24. }
  25. }).
  26. // checks if param value ends with the 'suffix' arg
  27. RegisterFunc("suffix", func(suffix string) func(string) bool {
  28. return func(paramValue string) bool {
  29. return strings.HasSuffix(paramValue, suffix)
  30. }
  31. }).
  32. // checks if param value contains the 's' arg
  33. RegisterFunc("contains", func(s string) func(string) bool {
  34. return func(paramValue string) bool {
  35. return strings.Contains(paramValue, s)
  36. }
  37. }).
  38. // checks if param value's length is at least 'min'
  39. RegisterFunc("min", func(min int) func(string) bool {
  40. return func(paramValue string) bool {
  41. return len(paramValue) >= min
  42. }
  43. }).
  44. // checks if param value's length is not bigger than 'max'
  45. RegisterFunc("max", func(max int) func(string) bool {
  46. return func(paramValue string) bool {
  47. return max >= len(paramValue)
  48. }
  49. }).
  50. // checks if param value's matches the given input
  51. RegisterFunc("eq", func(s string) func(string) bool {
  52. return func(paramValue string) bool {
  53. return paramValue == s
  54. }
  55. }).
  56. // checks if param value's matches at least one of the inputs
  57. RegisterFunc("eqor", func(texts []string) func(string) bool {
  58. if len(texts) == 1 {
  59. text := texts[0]
  60. return func(paramValue string) bool {
  61. return paramValue == text
  62. }
  63. }
  64. return func(paramValue string) bool {
  65. for _, s := range texts {
  66. if paramValue == s {
  67. return true
  68. }
  69. }
  70. return false
  71. }
  72. })
  73. // Int or number type
  74. // both positive and negative numbers, actual value can be min-max int64 or min-max int32 depends on the arch.
  75. // If x64: -9223372036854775808 to 9223372036854775807.
  76. // If x32: -2147483648 to 2147483647 and etc..
  77. Int = NewMacro("int", "number", false, false, func(paramValue string) (interface{}, bool) {
  78. v, err := strconv.Atoi(paramValue)
  79. if err != nil {
  80. return err, false
  81. }
  82. return v, true
  83. }).
  84. // checks if the param value's int representation is
  85. // bigger or equal than 'min'
  86. RegisterFunc("min", func(min int) func(int) bool {
  87. return func(paramValue int) bool {
  88. return paramValue >= min
  89. }
  90. }).
  91. // checks if the param value's int representation is
  92. // smaller or equal than 'max'.
  93. RegisterFunc("max", func(max int) func(int) bool {
  94. return func(paramValue int) bool {
  95. return paramValue <= max
  96. }
  97. }).
  98. // checks if the param value's int representation is
  99. // between min and max, including 'min' and 'max'.
  100. RegisterFunc("range", func(min, max int) func(int) bool {
  101. return func(paramValue int) bool {
  102. return !(paramValue < min || paramValue > max)
  103. }
  104. })
  105. // Int8 type
  106. // -128 to 127.
  107. Int8 = NewMacro("int8", "", false, false, func(paramValue string) (interface{}, bool) {
  108. v, err := strconv.ParseInt(paramValue, 10, 8)
  109. if err != nil {
  110. return err, false
  111. }
  112. return int8(v), true
  113. }).
  114. RegisterFunc("min", func(min int8) func(int8) bool {
  115. return func(paramValue int8) bool {
  116. return paramValue >= min
  117. }
  118. }).
  119. RegisterFunc("max", func(max int8) func(int8) bool {
  120. return func(paramValue int8) bool {
  121. return paramValue <= max
  122. }
  123. }).
  124. RegisterFunc("range", func(min, max int8) func(int8) bool {
  125. return func(paramValue int8) bool {
  126. return !(paramValue < min || paramValue > max)
  127. }
  128. })
  129. // Int16 type
  130. // -32768 to 32767.
  131. Int16 = NewMacro("int16", "", false, false, func(paramValue string) (interface{}, bool) {
  132. v, err := strconv.ParseInt(paramValue, 10, 16)
  133. if err != nil {
  134. return err, false
  135. }
  136. return int16(v), true
  137. }).
  138. RegisterFunc("min", func(min int16) func(int16) bool {
  139. return func(paramValue int16) bool {
  140. return paramValue >= min
  141. }
  142. }).
  143. RegisterFunc("max", func(max int16) func(int16) bool {
  144. return func(paramValue int16) bool {
  145. return paramValue <= max
  146. }
  147. }).
  148. RegisterFunc("range", func(min, max int16) func(int16) bool {
  149. return func(paramValue int16) bool {
  150. return !(paramValue < min || paramValue > max)
  151. }
  152. })
  153. // Int32 type
  154. // -2147483648 to 2147483647.
  155. Int32 = NewMacro("int32", "", false, false, func(paramValue string) (interface{}, bool) {
  156. v, err := strconv.ParseInt(paramValue, 10, 32)
  157. if err != nil {
  158. return err, false
  159. }
  160. return int32(v), true
  161. }).
  162. RegisterFunc("min", func(min int32) func(int32) bool {
  163. return func(paramValue int32) bool {
  164. return paramValue >= min
  165. }
  166. }).
  167. RegisterFunc("max", func(max int32) func(int32) bool {
  168. return func(paramValue int32) bool {
  169. return paramValue <= max
  170. }
  171. }).
  172. RegisterFunc("range", func(min, max int32) func(int32) bool {
  173. return func(paramValue int32) bool {
  174. return !(paramValue < min || paramValue > max)
  175. }
  176. })
  177. // Int64 as int64 type
  178. // -9223372036854775808 to 9223372036854775807.
  179. Int64 = NewMacro("int64", "long", false, false, func(paramValue string) (interface{}, bool) {
  180. v, err := strconv.ParseInt(paramValue, 10, 64)
  181. if err != nil { // if err == strconv.ErrRange...
  182. return err, false
  183. }
  184. return v, true
  185. }).
  186. // checks if the param value's int64 representation is
  187. // bigger or equal than 'min'.
  188. RegisterFunc("min", func(min int64) func(int64) bool {
  189. return func(paramValue int64) bool {
  190. return paramValue >= min
  191. }
  192. }).
  193. // checks if the param value's int64 representation is
  194. // smaller or equal than 'max'.
  195. RegisterFunc("max", func(max int64) func(int64) bool {
  196. return func(paramValue int64) bool {
  197. return paramValue <= max
  198. }
  199. }).
  200. // checks if the param value's int64 representation is
  201. // between min and max, including 'min' and 'max'.
  202. RegisterFunc("range", func(min, max int64) func(int64) bool {
  203. return func(paramValue int64) bool {
  204. return !(paramValue < min || paramValue > max)
  205. }
  206. })
  207. // Uint as uint type
  208. // actual value can be min-max uint64 or min-max uint32 depends on the arch.
  209. // If x64: 0 to 18446744073709551615.
  210. // If x32: 0 to 4294967295 and etc.
  211. Uint = NewMacro("uint", "", false, false, func(paramValue string) (interface{}, bool) {
  212. v, err := strconv.ParseUint(paramValue, 10, strconv.IntSize) // 32,64...
  213. if err != nil {
  214. return err, false
  215. }
  216. return uint(v), true
  217. }).
  218. // checks if the param value's int representation is
  219. // bigger or equal than 'min'
  220. RegisterFunc("min", func(min uint) func(uint) bool {
  221. return func(paramValue uint) bool {
  222. return paramValue >= min
  223. }
  224. }).
  225. // checks if the param value's int representation is
  226. // smaller or equal than 'max'.
  227. RegisterFunc("max", func(max uint) func(uint) bool {
  228. return func(paramValue uint) bool {
  229. return paramValue <= max
  230. }
  231. }).
  232. // checks if the param value's int representation is
  233. // between min and max, including 'min' and 'max'.
  234. RegisterFunc("range", func(min, max uint) func(uint) bool {
  235. return func(paramValue uint) bool {
  236. return !(paramValue < min || paramValue > max)
  237. }
  238. })
  239. // Uint8 as uint8 type
  240. // 0 to 255.
  241. Uint8 = NewMacro("uint8", "", false, false, func(paramValue string) (interface{}, bool) {
  242. v, err := strconv.ParseUint(paramValue, 10, 8)
  243. if err != nil {
  244. return err, false
  245. }
  246. return uint8(v), true
  247. }).
  248. // checks if the param value's uint8 representation is
  249. // bigger or equal than 'min'.
  250. RegisterFunc("min", func(min uint8) func(uint8) bool {
  251. return func(paramValue uint8) bool {
  252. return paramValue >= min
  253. }
  254. }).
  255. // checks if the param value's uint8 representation is
  256. // smaller or equal than 'max'.
  257. RegisterFunc("max", func(max uint8) func(uint8) bool {
  258. return func(paramValue uint8) bool {
  259. return paramValue <= max
  260. }
  261. }).
  262. // checks if the param value's uint8 representation is
  263. // between min and max, including 'min' and 'max'.
  264. RegisterFunc("range", func(min, max uint8) func(uint8) bool {
  265. return func(paramValue uint8) bool {
  266. return !(paramValue < min || paramValue > max)
  267. }
  268. })
  269. // Uint16 as uint16 type
  270. // 0 to 65535.
  271. Uint16 = NewMacro("uint16", "", false, false, func(paramValue string) (interface{}, bool) {
  272. v, err := strconv.ParseUint(paramValue, 10, 16)
  273. if err != nil {
  274. return err, false
  275. }
  276. return uint16(v), true
  277. }).
  278. RegisterFunc("min", func(min uint16) func(uint16) bool {
  279. return func(paramValue uint16) bool {
  280. return paramValue >= min
  281. }
  282. }).
  283. RegisterFunc("max", func(max uint16) func(uint16) bool {
  284. return func(paramValue uint16) bool {
  285. return paramValue <= max
  286. }
  287. }).
  288. RegisterFunc("range", func(min, max uint16) func(uint16) bool {
  289. return func(paramValue uint16) bool {
  290. return !(paramValue < min || paramValue > max)
  291. }
  292. })
  293. // Uint32 as uint32 type
  294. // 0 to 4294967295.
  295. Uint32 = NewMacro("uint32", "", false, false, func(paramValue string) (interface{}, bool) {
  296. v, err := strconv.ParseUint(paramValue, 10, 32)
  297. if err != nil {
  298. return err, false
  299. }
  300. return uint32(v), true
  301. }).
  302. RegisterFunc("min", func(min uint32) func(uint32) bool {
  303. return func(paramValue uint32) bool {
  304. return paramValue >= min
  305. }
  306. }).
  307. RegisterFunc("max", func(max uint32) func(uint32) bool {
  308. return func(paramValue uint32) bool {
  309. return paramValue <= max
  310. }
  311. }).
  312. RegisterFunc("range", func(min, max uint32) func(uint32) bool {
  313. return func(paramValue uint32) bool {
  314. return !(paramValue < min || paramValue > max)
  315. }
  316. })
  317. // Uint64 as uint64 type
  318. // 0 to 18446744073709551615.
  319. Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) (interface{}, bool) {
  320. v, err := strconv.ParseUint(paramValue, 10, 64)
  321. if err != nil {
  322. return err, false
  323. }
  324. return v, true
  325. }).
  326. // checks if the param value's uint64 representation is
  327. // bigger or equal than 'min'.
  328. RegisterFunc("min", func(min uint64) func(uint64) bool {
  329. return func(paramValue uint64) bool {
  330. return paramValue >= min
  331. }
  332. }).
  333. // checks if the param value's uint64 representation is
  334. // smaller or equal than 'max'.
  335. RegisterFunc("max", func(max uint64) func(uint64) bool {
  336. return func(paramValue uint64) bool {
  337. return paramValue <= max
  338. }
  339. }).
  340. // checks if the param value's uint64 representation is
  341. // between min and max, including 'min' and 'max'.
  342. RegisterFunc("range", func(min, max uint64) func(uint64) bool {
  343. return func(paramValue uint64) bool {
  344. return !(paramValue < min || paramValue > max)
  345. }
  346. })
  347. // Bool or boolean as bool type
  348. // a string which is "1" or "t" or "T" or "TRUE" or "true" or "True"
  349. // or "0" or "f" or "F" or "FALSE" or "false" or "False".
  350. Bool = NewMacro("bool", "boolean", false, false, func(paramValue string) (interface{}, bool) {
  351. // a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$
  352. // in this case.
  353. v, err := strconv.ParseBool(paramValue)
  354. if err != nil {
  355. return err, false
  356. }
  357. return v, true
  358. })
  359. // ErrParamNotAlphabetical is fired when the parameter value is not an alphabetical text.
  360. ErrParamNotAlphabetical = errors.New("parameter is not alphabetical")
  361. alphabeticalEval = MustRegexp("^[a-zA-Z ]+$")
  362. // Alphabetical letter type
  363. // letters only (upper or lowercase)
  364. Alphabetical = NewMacro("alphabetical", "", false, false, func(paramValue string) (interface{}, bool) {
  365. if !alphabeticalEval(paramValue) {
  366. return fmt.Errorf("%s: %w", paramValue, ErrParamNotAlphabetical), false
  367. }
  368. return paramValue, true
  369. })
  370. // ErrParamNotFile is fired when the parameter value is not a form of a file.
  371. ErrParamNotFile = errors.New("parameter is not a file")
  372. fileEval = MustRegexp("^[a-zA-Z0-9_.-]*$")
  373. // File type
  374. // letters (upper or lowercase)
  375. // numbers (0-9)
  376. // underscore (_)
  377. // dash (-)
  378. // point (.)
  379. // no spaces! or other character
  380. File = NewMacro("file", "", false, false, func(paramValue string) (interface{}, bool) {
  381. if !fileEval(paramValue) {
  382. return fmt.Errorf("%s: %w", paramValue, ErrParamNotFile), false
  383. }
  384. return paramValue, true
  385. })
  386. // Path type
  387. // anything, should be the last part
  388. //
  389. // It allows everything, we have String and Path as different
  390. // types because I want to give the opportunity to the user
  391. // to organise the macro functions based on wildcard or single dynamic named path parameter.
  392. // Should be living in the latest path segment of a route path.
  393. Path = NewMacro("path", "", false, true, nil)
  394. // UUID string type for validating a uuidv4 (and v1) path parameter.
  395. // Read more at: https://tools.ietf.org/html/rfc4122.
  396. UUID = NewMacro("uuid", "uuidv4", false, false, func(paramValue string) (interface{}, bool) {
  397. _, err := uuid.Parse(paramValue) // this is x10+ times faster than regexp.
  398. if err != nil {
  399. return err, false
  400. }
  401. return paramValue, true
  402. })
  403. // Email string type for validating an e-mail path parameter. It returns the address as string, instead of an *mail.Address.
  404. // Read more at go std mail.ParseAddress method. See the ':email' path parameter for a more strictly version of validation.
  405. Mail = NewMacro("mail", "", false, false, func(paramValue string) (interface{}, bool) {
  406. _, err := mail.ParseAddress(paramValue)
  407. if err != nil {
  408. return fmt.Errorf("%s: %w", paramValue, err), false
  409. }
  410. return paramValue, true
  411. })
  412. // Email string type for validating an e-mail path parameter. It returns the address as string, instead of an *mail.Address.
  413. // It is a combined validation using mail.ParseAddress and net.LookupMX so only valid domains can be passed.
  414. // It's a more strictly version of the ':mail' path parameter.
  415. Email = NewMacro("email", "", false, false, func(paramValue string) (interface{}, bool) {
  416. _, err := mail.ParseAddress(paramValue)
  417. if err != nil {
  418. return fmt.Errorf("%s: %w", paramValue, err), false
  419. }
  420. domainPart := strings.Split(paramValue, "@")[1]
  421. mx, err := net.LookupMX(domainPart)
  422. if err != nil {
  423. return fmt.Errorf("%s: %w", paramValue, err), false
  424. }
  425. if len(mx) == 0 {
  426. return fmt.Errorf("%s: mx is empty", paramValue), false
  427. }
  428. return paramValue, true
  429. })
  430. simpleDateLayout = "2006/01/02"
  431. // Date type.
  432. Date = NewMacro("date", "", false, true, func(paramValue string) (interface{}, bool) {
  433. tt, err := time.Parse(simpleDateLayout, paramValue)
  434. if err != nil {
  435. return fmt.Errorf("%s: %w", paramValue, err), false
  436. }
  437. return tt, true
  438. })
  439. // ErrParamNotWeekday is fired when the parameter value is not a form of a time.Weekday.
  440. ErrParamNotWeekday = errors.New("parameter is not a valid weekday")
  441. longDayNames = map[string]time.Weekday{
  442. "Sunday": time.Sunday,
  443. "Monday": time.Monday,
  444. "Tuesday": time.Tuesday,
  445. "Wednesday": time.Wednesday,
  446. "Thursday": time.Thursday,
  447. "Friday": time.Friday,
  448. "Saturday": time.Saturday,
  449. // lowercase.
  450. "sunday": time.Sunday,
  451. "monday": time.Monday,
  452. "tuesday": time.Tuesday,
  453. "wednesday": time.Wednesday,
  454. "thursday": time.Thursday,
  455. "friday": time.Friday,
  456. "saturday": time.Saturday,
  457. }
  458. // Weekday type, returns a type of time.Weekday.
  459. // Valid values:
  460. // 0 to 7 (leading zeros don't matter) or "Sunday" to "Monday" or "sunday" to "monday".
  461. Weekday = NewMacro("weekday", "", false, false, func(paramValue string) (interface{}, bool) {
  462. d, ok := longDayNames[paramValue]
  463. if !ok {
  464. // try parse from integer.
  465. n, err := strconv.Atoi(paramValue)
  466. if err != nil {
  467. return fmt.Errorf("%s: %w", paramValue, err), false
  468. }
  469. if n < 0 || n > 6 {
  470. return fmt.Errorf("%s: %w", paramValue, ErrParamNotWeekday), false
  471. }
  472. return time.Weekday(n), true
  473. }
  474. return d, true
  475. })
  476. // Defaults contains the defaults macro and parameters types for the router.
  477. //
  478. // Read https://github.com/kataras/iris/tree/main/_examples/routing/macros for more details.
  479. Defaults = &Macros{
  480. String,
  481. Int,
  482. Int8,
  483. Int16,
  484. Int32,
  485. Int64,
  486. Uint,
  487. Uint8,
  488. Uint16,
  489. Uint32,
  490. Uint64,
  491. Bool,
  492. Alphabetical,
  493. File,
  494. Path,
  495. UUID,
  496. Mail,
  497. Email,
  498. Date,
  499. Weekday,
  500. }
  501. )
  502. // Macros is just a type of a slice of *Macro
  503. // which is responsible to register and search for macros based on the indent(parameter type).
  504. type Macros []*Macro
  505. // Register registers a custom Macro.
  506. // The "indent" should not be empty and should be unique, it is the parameter type's name, i.e "string".
  507. // The "alias" is optionally and it should be unique, it is the alias of the parameter type.
  508. // "isMaster" and "isTrailing" is for default parameter type and wildcard respectfully.
  509. // The "evaluator" is the function that is converted to an Iris handler which is executed every time
  510. // before the main chain of a route's handlers that contains this macro of the specific parameter type.
  511. //
  512. // Read https://github.com/kataras/iris/tree/main/_examples/routing/macros for more details.
  513. func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator ParamEvaluator) *Macro {
  514. macro := NewMacro(indent, alias, isMaster, isTrailing, evaluator)
  515. if ms.register(macro) {
  516. return macro
  517. }
  518. return nil
  519. }
  520. func (ms *Macros) register(macro *Macro) bool {
  521. if macro.Indent() == "" {
  522. return false
  523. }
  524. cp := *ms
  525. for _, m := range cp {
  526. // can't add more than one with the same ast characteristics.
  527. if macro.Indent() == m.Indent() {
  528. return false
  529. }
  530. if alias := macro.Alias(); alias != "" {
  531. if alias == m.Alias() || alias == m.Indent() {
  532. return false
  533. }
  534. }
  535. if macro.Master() && m.Master() {
  536. return false
  537. }
  538. }
  539. cp = append(cp, macro)
  540. *ms = cp
  541. return true
  542. }
  543. // Unregister removes a macro and its parameter type from the list.
  544. func (ms *Macros) Unregister(indent string) bool {
  545. cp := *ms
  546. for i, m := range cp {
  547. if m.Indent() == indent {
  548. copy(cp[i:], cp[i+1:])
  549. cp[len(cp)-1] = nil
  550. cp = cp[:len(cp)-1]
  551. *ms = cp
  552. return true
  553. }
  554. }
  555. return false
  556. }
  557. // Lookup returns the responsible macro for a parameter type, it can return nil.
  558. func (ms *Macros) Lookup(pt ast.ParamType) *Macro {
  559. if m := ms.Get(pt.Indent()); m != nil {
  560. return m
  561. }
  562. if alias, has := ast.HasAlias(pt); has {
  563. if m := ms.Get(alias); m != nil {
  564. return m
  565. }
  566. }
  567. return nil
  568. }
  569. // Get returns the responsible macro for a parameter type, it can return nil.
  570. func (ms *Macros) Get(indentOrAlias string) *Macro {
  571. if indentOrAlias == "" {
  572. return nil
  573. }
  574. for _, m := range *ms {
  575. if m.Indent() == indentOrAlias {
  576. return m
  577. }
  578. if m.Alias() == indentOrAlias {
  579. return m
  580. }
  581. }
  582. return nil
  583. }
  584. // GetMaster returns the default macro and its parameter type,
  585. // by default it will return the `String` macro which is responsible for the "string" parameter type.
  586. func (ms *Macros) GetMaster() *Macro {
  587. for _, m := range *ms {
  588. if m.Master() {
  589. return m
  590. }
  591. }
  592. return nil
  593. }
  594. // GetTrailings returns the macros that have support for wildcards parameter types.
  595. // By default it will return the `Path` macro which is responsible for the "path" parameter type.
  596. func (ms *Macros) GetTrailings() (macros []*Macro) {
  597. for _, m := range *ms {
  598. if m.Trailing() {
  599. macros = append(macros, m)
  600. }
  601. }
  602. return
  603. }
  604. // SetErrorHandler registers a common type path parameter error handler.
  605. // The "fnHandler" MUST be a type of handler.ParamErrorHandler:
  606. // func(ctx iris.Context, paramIndex int, err error). It calls
  607. // the Macro.HandleError method for each of the "ms" entries.
  608. func (ms *Macros) SetErrorHandler(fnHandler interface{}) {
  609. for _, m := range *ms {
  610. if m == nil {
  611. continue
  612. }
  613. m.HandleError(fnHandler)
  614. }
  615. }