mvc.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. package mvc
  2. import (
  3. "fmt"
  4. "reflect"
  5. "strings"
  6. "github.com/kataras/iris/v12/context"
  7. "github.com/kataras/iris/v12/core/router"
  8. "github.com/kataras/iris/v12/hero"
  9. "github.com/kataras/iris/v12/websocket"
  10. "github.com/kataras/golog"
  11. "github.com/kataras/pio"
  12. )
  13. // Application is the high-level component of the "mvc" package.
  14. // It's the API that you will be using to register controllers among with their
  15. // dependencies that your controllers may expecting.
  16. // It contains the Router(iris.Party) in order to be able to register
  17. // template layout, middleware, done handlers as you used with the
  18. // standard Iris APIBuilder.
  19. //
  20. // The Engine is created by the `New` method and it's the dependencies holder
  21. // and controllers factory.
  22. //
  23. // See `mvc#New` for more.
  24. type Application struct {
  25. container *hero.Container
  26. // This Application's Name. Keep names unique to each other.
  27. Name string
  28. Router router.Party
  29. Controllers []*ControllerActivator
  30. websocketControllers []websocket.ConnHandler
  31. // Disables verbose logging for controllers under this and its children mvc apps.
  32. // Defaults to false.
  33. controllersNoLog bool
  34. // Set custom path
  35. customPathWordFunc CustomPathWordFunc
  36. }
  37. func newApp(subRouter router.Party, container *hero.Container) *Application {
  38. app := &Application{
  39. Router: subRouter,
  40. container: container,
  41. }
  42. // Register this Application so any field or method's input argument of
  43. // *mvc.Application can point to the current MVC application that the controller runs on.
  44. registerBuiltinDependencies(container, app)
  45. return app
  46. }
  47. // See `hero.BuiltinDependencies` too, here we are registering dependencies per MVC Application.
  48. func registerBuiltinDependencies(container *hero.Container, deps ...interface{}) {
  49. for _, dep := range deps {
  50. depTyp := reflect.TypeOf(dep)
  51. for i, dependency := range container.Dependencies {
  52. if dependency.Static {
  53. if dependency.DestType == depTyp {
  54. // Remove any existing before register this one (see app.Clone).
  55. copy(container.Dependencies[i:], container.Dependencies[i+1:])
  56. container.Dependencies = container.Dependencies[:len(container.Dependencies)-1]
  57. break
  58. }
  59. }
  60. }
  61. container.Register(dep)
  62. }
  63. }
  64. // New returns a new mvc Application based on a "party".
  65. // Application creates a new engine which is responsible for binding the dependencies
  66. // and creating and activating the app's controller(s).
  67. //
  68. // Example: `New(app.Party("/todo"))` or `New(app)` as it's the same as `New(app.Party("/"))`.
  69. func New(party router.Party) *Application {
  70. return newApp(party, party.ConfigureContainer().Container.Clone())
  71. }
  72. // Configure creates a new controller and configures it,
  73. // this function simply calls the `New(party)` and its `.Configure(configurators...)`.
  74. //
  75. // A call of `mvc.New(app.Party("/path").Configure(buildMyMVC)` is equal to
  76. //
  77. // `mvc.Configure(app.Party("/path"), buildMyMVC)`.
  78. //
  79. // Read more at `New() Application` and `Application#Configure` methods.
  80. func Configure(party router.Party, configurators ...func(*Application)) *Application {
  81. // Author's Notes->
  82. // About the Configure's comment: +5 space to be shown in equal width to the previous or after line.
  83. //
  84. // About the Configure's design chosen:
  85. // Yes, we could just have a `New(party, configurators...)`
  86. // but I think the `New()` and `Configure(configurators...)` API seems more native to programmers,
  87. // at least to me and the people I ask for their opinion between them.
  88. // Because the `New()` can actually return something that can be fully configured without its `Configure`,
  89. // its `Configure` is there just to design the apps better and help end-devs to split their code wisely.
  90. return New(party).Configure(configurators...)
  91. }
  92. // Configure can be used to pass one or more functions that accept this
  93. // Application, use this to add dependencies and controller(s).
  94. //
  95. // Example: `New(app.Party("/todo")).Configure(func(mvcApp *mvc.Application){...})`.
  96. func (app *Application) Configure(configurators ...func(*Application)) *Application {
  97. for _, c := range configurators {
  98. c(app)
  99. }
  100. return app
  101. }
  102. // SetName sets a unique name to this MVC Application.
  103. // Used for logging, not used in runtime yet, but maybe useful for future features.
  104. //
  105. // It returns this Application.
  106. func (app *Application) SetName(appName string) *Application {
  107. app.Name = appName
  108. return app
  109. }
  110. // SetCustomPathWordFunc sets a custom function
  111. // which is responsible to override the existing controllers method parsing.
  112. func (app *Application) SetCustomPathWordFunc(wordFunc CustomPathWordFunc) *Application {
  113. app.customPathWordFunc = wordFunc
  114. return app
  115. }
  116. // SetControllersNoLog disables verbose logging for next registered controllers
  117. // under this App and its children of `Application.Party` or `Application.Clone`.
  118. //
  119. // To disable logging for routes under a Party,
  120. // see `Party.SetRoutesNoLog` instead.
  121. //
  122. // Defaults to false when log level is "debug".
  123. func (app *Application) SetControllersNoLog(disable bool) *Application {
  124. app.controllersNoLog = disable
  125. return app
  126. }
  127. // EnableStructDependents will try to resolve
  128. // the fields of a struct value, if any, when it's a dependent struct value
  129. // based on the previous registered dependencies.
  130. func (app *Application) EnableStructDependents() *Application {
  131. app.container.EnableStructDependents = true
  132. return app
  133. }
  134. // Register appends one or more values as dependencies.
  135. // The value can be a single struct value-instance or a function
  136. // which has one input and one output, the input should be
  137. // an `iris.Context` and the output can be any type, that output type
  138. // will be bind-ed to the controller's field, if matching or to the
  139. // controller's methods, if matching.
  140. //
  141. // These dependencies "dependencies" can be changed per-controller as well,
  142. // via controller's `BeforeActivation` and `AfterActivation` methods,
  143. // look the `Handle` method for more.
  144. //
  145. // It returns this Application.
  146. //
  147. // Example: `.Register(loggerService{prefix: "dev"}, func(ctx iris.Context) User {...})`.
  148. func (app *Application) Register(dependencies ...interface{}) *Application {
  149. if len(dependencies) > 0 && len(app.container.Dependencies) == len(hero.BuiltinDependencies) && len(app.Controllers) > 0 {
  150. allControllerNamesSoFar := make([]string, len(app.Controllers))
  151. for i := range app.Controllers {
  152. allControllerNamesSoFar[i] = app.Controllers[i].Name()
  153. }
  154. golog.Warnf(`mvc.Application#Register called after mvc.Application#Handle.
  155. The controllers[%s] may miss required dependencies.
  156. Set the Logger's Level to "debug" to view the active dependencies per controller.`, strings.Join(allControllerNamesSoFar, ","))
  157. }
  158. for _, dependency := range dependencies {
  159. app.container.Register(dependency)
  160. }
  161. return app
  162. }
  163. type (
  164. // Option is an interface which does contain a single `Apply` method that accepts
  165. // a `ControllerActivator`. It can be passed on `Application.Handle` method to
  166. // mdoify the behavior right after the `BeforeActivation` state.
  167. //
  168. // See `GRPC` package-level structure
  169. // and `Version` package-level function too.
  170. Option interface {
  171. Apply(*ControllerActivator)
  172. }
  173. // OptionFunc is the functional type of `Option`.
  174. // Read `Option` docs.
  175. OptionFunc func(*ControllerActivator)
  176. )
  177. // Apply completes the `Option` interface.
  178. func (opt OptionFunc) Apply(c *ControllerActivator) {
  179. opt(c)
  180. }
  181. // IgnoreEmbedded is an Option which can be used to ignore all embedded struct's method handlers.
  182. // Note that even if the controller overrides the embedded methods
  183. // they will be still ignored because Go doesn't support this detection so far.
  184. // For global affect, set the `IgnoreEmbeddedControllers` package-level variable to true.
  185. var IgnoreEmbedded OptionFunc = func(c *ControllerActivator) {
  186. c.SkipEmbeddedMethods()
  187. }
  188. // Handle serves a controller for the current mvc application's Router.
  189. // It accept any custom struct which its functions will be transformed
  190. // to routes.
  191. //
  192. // If "controller" has `BeforeActivation(b mvc.BeforeActivation)`
  193. // or/and `AfterActivation(a mvc.AfterActivation)` then these will be called between the controller's `.activate`,
  194. // use those when you want to modify the controller before or/and after
  195. // the controller will be registered to the main Iris Application.
  196. //
  197. // It returns this mvc Application.
  198. //
  199. // Usage: `.Handle(new(TodoController))`.
  200. //
  201. // Controller accepts a sub router and registers any custom struct
  202. // as controller, if struct doesn't have any compatible methods
  203. // neither are registered via `ControllerActivator`'s `Handle` method
  204. // then the controller is not registered at all.
  205. //
  206. // A Controller may have one or more methods
  207. // that are wrapped to a handler and registered as routes before the server ran.
  208. // The controller's method can accept any input argument that are previously binded
  209. // via the dependencies or route's path accepts dynamic path parameters.
  210. // The controller's fields are also bindable via the dependencies, either a
  211. // static value (service) or a function (dynamically) which accepts a context
  212. // and returns a single value (this type is being used to find the relative field or method's input argument).
  213. //
  214. // func(c *ExampleController) Get() string |
  215. // (string, string) |
  216. // (string, int) |
  217. // int |
  218. // (int, string |
  219. // (string, error) |
  220. // bool |
  221. // (any, bool) |
  222. // error |
  223. // (int, error) |
  224. // (customStruct, error) |
  225. // customStruct |
  226. // (customStruct, int) |
  227. // (customStruct, string) |
  228. // Result or (Result, error)
  229. // where Get is an HTTP Method func.
  230. //
  231. // Default behavior can be changed through second, variadic, variable "options",
  232. // e.g. Handle(controller, GRPC {Server: grpcServer, Strict: true})
  233. //
  234. // Examples at: https://github.com/kataras/iris/tree/main/_examples/mvc
  235. func (app *Application) Handle(controller interface{}, options ...Option) *Application {
  236. c := app.handle(controller, options...)
  237. // Note: log on register-time, so they can catch any failures before build.
  238. if !app.controllersNoLog {
  239. // log only http (and versioned) or grpc controllers,
  240. // websocket is already logging itself.
  241. logController(app.Router.Logger(), c)
  242. }
  243. return app
  244. }
  245. // HandleWebsocket handles a websocket specific controller.
  246. // Its exported methods are the events.
  247. // If a "Namespace" field or method exists then namespace is set, otherwise empty namespace.
  248. // Note that a websocket controller is registered and ran under a specific connection connected to a namespace
  249. // and it cannot send HTTP responses on that state.
  250. // However all static and dynamic dependency injection features are working, as expected, like any regular MVC Controller.
  251. func (app *Application) HandleWebsocket(controller interface{}) *websocket.Struct {
  252. c := app.handle(controller)
  253. c.markAsWebsocket()
  254. websocketController := websocket.NewStruct(c.Value).SetInjector(makeInjector(c.injector))
  255. app.websocketControllers = append(app.websocketControllers, websocketController)
  256. return websocketController
  257. }
  258. func makeInjector(s *hero.Struct) websocket.StructInjector {
  259. return func(_ reflect.Type, nsConn *websocket.NSConn) reflect.Value {
  260. v, _ := s.Acquire(websocket.GetContext(nsConn.Conn))
  261. return v
  262. }
  263. }
  264. var _ websocket.ConnHandler = (*Application)(nil)
  265. // GetNamespaces completes the websocket ConnHandler interface.
  266. // It returns a collection of namespace and events that
  267. // were registered through `HandleWebsocket` controllers.
  268. func (app *Application) GetNamespaces() websocket.Namespaces {
  269. if logger := app.Router.Logger(); logger.Level == golog.DebugLevel && !app.controllersNoLog {
  270. websocket.EnableDebug(logger)
  271. }
  272. return websocket.JoinConnHandlers(app.websocketControllers...).GetNamespaces()
  273. }
  274. func (app *Application) handle(controller interface{}, options ...Option) *ControllerActivator {
  275. // initialize the controller's activator, nothing too magical so far.
  276. c := newControllerActivator(app, controller)
  277. // check the controller's "BeforeActivation" or/and "AfterActivation" method(s) between the `activate`
  278. // call, which is simply parses the controller's methods, end-dev can register custom controller's methods
  279. // by using the BeforeActivation's (a ControllerActivation) `.Handle` method.
  280. if before, ok := controller.(interface {
  281. BeforeActivation(BeforeActivation)
  282. }); ok {
  283. before.BeforeActivation(c)
  284. }
  285. for _, opt := range options {
  286. if opt != nil {
  287. opt.Apply(c)
  288. }
  289. }
  290. c.activate()
  291. if after, okAfter := controller.(interface {
  292. AfterActivation(AfterActivation)
  293. }); okAfter {
  294. after.AfterActivation(c)
  295. }
  296. app.Controllers = append(app.Controllers, c)
  297. return c
  298. }
  299. // HandleError registers a `hero.ErrorHandlerFunc` which will be fired when
  300. // application's controllers' functions returns an non-nil error.
  301. // Each controller can override it by implementing the `hero.ErrorHandler`.
  302. func (app *Application) HandleError(handler func(ctx *context.Context, err error)) *Application {
  303. errorHandler := hero.ErrorHandlerFunc(handler)
  304. app.container.GetErrorHandler = func(*context.Context) hero.ErrorHandler {
  305. return errorHandler
  306. }
  307. return app
  308. }
  309. // Clone returns a new mvc Application which has the dependencies
  310. // of the current mvc Application's `Dependencies` and its `ErrorHandler`.
  311. //
  312. // Example: `.Clone(app.Party("/path")).Handle(new(TodoSubController))`.
  313. func (app *Application) Clone(party router.Party) *Application {
  314. cloned := newApp(party, app.container.Clone())
  315. cloned.controllersNoLog = app.controllersNoLog
  316. return cloned
  317. }
  318. // Party returns a new child mvc Application based on the current path + "relativePath".
  319. // The new mvc Application has the same dependencies of the current mvc Application,
  320. // until otherwise specified later manually.
  321. //
  322. // The router's root path of this child will be the current mvc Application's root path + "relativePath".
  323. func (app *Application) Party(relativePath string, middleware ...context.Handler) *Application {
  324. return app.Clone(app.Router.Party(relativePath, middleware...))
  325. }
  326. var childNameReplacer = strings.NewReplacer("*", "", "(", "", ")", "")
  327. func getArrowSymbol(static bool, field bool) string {
  328. if field {
  329. if static {
  330. return "╺"
  331. }
  332. return "⦿"
  333. }
  334. if static {
  335. return "•"
  336. }
  337. return "⦿"
  338. }
  339. // TODO: instead of this I want to get in touch with tools like "graphviz"
  340. // so we can put all that information (and the API) inside web graphs,
  341. // it will be easier for developers to see the flow of the whole application,
  342. // but probalby I will never find time for that as we have higher priorities...just a reminder though.
  343. func logController(logger *golog.Logger, c *ControllerActivator) {
  344. if logger.Level != golog.DebugLevel {
  345. return
  346. }
  347. if c.injector == nil { // when no actual controller methods are registered.
  348. return
  349. }
  350. /*
  351. [DBUG] controller.GreetController
  352. ╺ Service → ./service/greet_service.go:16
  353. ╺ Get
  354. GET /greet
  355. • iris.Context
  356. • service.Other → ./service/other_service.go:11
  357. */
  358. bckpNewLine := logger.NewLine
  359. bckpTimeFormat := logger.TimeFormat
  360. logger.NewLine = false
  361. logger.TimeFormat = ""
  362. printer := logger.Printer
  363. reports := c.injector.Container.Reports
  364. ctrlName := c.RelName()
  365. ctrlScopeType := ""
  366. if !c.injector.Singleton {
  367. ctrlScopeType = getArrowSymbol(false, false) + " "
  368. }
  369. logger.Debugf("%s%s\n", ctrlScopeType, ctrlName)
  370. longestNameLen := 0
  371. for _, report := range reports {
  372. for _, entry := range report.Entries {
  373. if n := len(entry.InputFieldName); n > longestNameLen {
  374. if strings.HasSuffix(entry.InputFieldName, ctrlName) {
  375. continue
  376. }
  377. longestNameLen = n
  378. }
  379. }
  380. }
  381. longestMethodName := 0
  382. for methodName := range c.routes {
  383. if n := len(methodName); n > longestMethodName {
  384. longestMethodName = n
  385. }
  386. }
  387. lastColorCode := -1
  388. for _, report := range reports {
  389. childName := childNameReplacer.Replace(report.Name)
  390. if idx := strings.Index(childName, c.Name()); idx >= 0 {
  391. childName = childName[idx+len(c.Name()):] // it's always +1 otherwise should be reported as BUG.
  392. }
  393. if childName != "" && childName[0] == '.' {
  394. // It's a struct's method.
  395. childName = childName[1:]
  396. for _, route := range c.routes[childName] {
  397. if route.NoLog {
  398. continue
  399. }
  400. // Let them be logged again with the middlewares, e.g UseRouter or UseGlobal after this MVC app created.
  401. // route.NoLog = true
  402. colorCode := router.TraceTitleColorCode(route.Method)
  403. // group same methods (or errors).
  404. if lastColorCode == -1 {
  405. lastColorCode = colorCode
  406. } else if lastColorCode != colorCode {
  407. lastColorCode = colorCode
  408. fmt.Fprintln(printer)
  409. }
  410. fmt.Fprint(printer, " ╺ ")
  411. pio.WriteRich(printer, childName, colorCode)
  412. entries := report.Entries[1:] // the ctrl value is always the first input argument so 1:..
  413. if len(entries) == 0 {
  414. fmt.Print("()")
  415. }
  416. fmt.Fprintln(printer)
  417. // pio.WriteRich(printer, " "+route.GetTitle(), colorCode)
  418. fmt.Fprintf(printer, " %s\n", route.String())
  419. for _, entry := range entries {
  420. fileLine := ""
  421. if !strings.Contains(entry.DependencyFile, "kataras/iris/") {
  422. fileLine = fmt.Sprintf("→ %s:%d", entry.DependencyFile, entry.DependencyLine)
  423. }
  424. fieldName := entry.InputFieldName
  425. spaceRequired := longestNameLen - len(fieldName)
  426. if spaceRequired < 0 {
  427. spaceRequired = 0
  428. }
  429. // → ⊳ ↔
  430. fmt.Fprintf(printer, " • %s%s %s\n", fieldName, strings.Repeat(" ", spaceRequired), fileLine)
  431. }
  432. }
  433. } else {
  434. // It's a struct's field.
  435. for _, entry := range report.Entries {
  436. fileLine := ""
  437. if !strings.Contains(entry.DependencyFile, "kataras/iris/") {
  438. fileLine = fmt.Sprintf("→ %s:%d", entry.DependencyFile, entry.DependencyLine)
  439. }
  440. fieldName := entry.InputFieldName
  441. spaceRequired := longestNameLen + 2 - len(fieldName) // plus the two spaces because it's not collapsed.
  442. if spaceRequired < 0 {
  443. spaceRequired = 0
  444. }
  445. arrowSymbol := getArrowSymbol(entry.Static, true)
  446. fmt.Fprintf(printer, " %s %s%s %s\n", arrowSymbol, fieldName, strings.Repeat(" ", spaceRequired), fileLine)
  447. }
  448. }
  449. }
  450. // fmt.Fprintln(printer)
  451. logger.NewLine = bckpNewLine
  452. logger.TimeFormat = bckpTimeFormat
  453. }