Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add toString method #861

Closed
wants to merge 16 commits into from
Closed

Conversation

Ethan-Arrowood
Copy link
Member

@Ethan-Arrowood Ethan-Arrowood commented Mar 24, 2018

Checklist

  • run npm run test and npm run benchmark
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message and code follows Code of conduct

Closes: #703

sandbox.js will be removed when this feature is finished. It is a useful file for testing during development. I think I have to move toString.js into lib as well.

Some things to note:

  • This is a very simple implementation of the full toString method
  • I'd like to print the Plugins and Middleware for each route in a similar format to fastify.printRoutes but I'm not sure where to start with that as that method is coming from find-my-way. (Should I try to copy that function and edit it to fit our needs?)
  • How should the json output be structured?

screen shot 2018-03-24 at 9 11 28 pm

Copy link
Member

@delvedor delvedor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great start!
The prettyPrint api of find-my-way is complex and needs to be refactored, you can try with https://github.com/substack/node-archy, which seems to be right for us :)

@delvedor delvedor added feature semver-minor Issue or PR that should land as semver minor labels Mar 24, 2018
@Ethan-Arrowood
Copy link
Member Author

Oh great! Thanks for the tip. How do access the routes on a Fastify instance? Do I need to access it through find-my-way?

@delvedor
Copy link
Member

find-my-way does not exposes the registered path, @mcollina had a similar problem in fastify/fastify-routes-stats.
At the moment the only way to access the declared route url is by using the onRoute hook.
But since this is a feature implemented directly in core you can access the routes property of the router.

@Ethan-Arrowood
Copy link
Member Author

Are Plugins tracked on the Fastify instance? AFAIK most plugins just decorate the instance (or the request/reply parts of the instance) and that is it. I wonder whats the best way to show that back to the user. -- Could this require editing .decorate and make it start storing the names or references to the new functions?

@mcollina
Copy link
Member

@Ethan-Arrowood yes I think so. However we are loosing all these metadata about fastify after boot finishes. However, I would add all these stuff in steps, and this is a good one.

You might want to add some events/or metadata info to https://github.com/mcollina/avvio as plugins are tracked there. (The code in avvio is very complex).

toString.js Outdated
})

console.log(`\nPlugins`)
console.log(`${LINES.r} TODO`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should not use console.log, but rather concatenate the string. In this way you can easily add a unit test.

toString.js Outdated
console.log(`\nRoutes`)
console.log(`${this.printRoutes()}`)

console.log(`\nMiddleware`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be "top level middlewares". I don't think you can see middlewares that are wrapped within a plugin.

toString.js Outdated
function toString() {
console.log('Fastify Instance toString')
/* Print hooks */
console.log(`\nHooks`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be "Top level Hooks". I don't think you can see hooks that are wrapped within a plugin.

@Ethan-Arrowood
Copy link
Member Author

Hello! I just got back from a 2 week trip and I'll be continuing work on this feature soon.

@Ethan-Arrowood
Copy link
Member Author

@mcollina can you provide some insight into adding events/or metadata info to avvio?

@mcollina
Copy link
Member

I would emit an event when a plugin start loading, and when a plugin finishes loading. However, there will be a to ton (every route is wrapped in a plugin) so some filtering might be needed.

@Ethan-Arrowood
Copy link
Member Author

Ethan-Arrowood commented May 1, 2018

You were not kidding when you said this code is complex!

I found this line in fastify:

instance[pluginUtils.registeredPlugins] = Object.create(instance[pluginUtils.registeredPlugins])

However, it doesn't seem to be tracking plugins. Do you think I should try pumping plugin metadata into there?

I've also begun cracking away at avvio. I've added an event emitter after a plugin is loaded. This does not emit for routes or hooks or middleware.

Boot.prototype.use = function (plugin, opts) {
  if (typeof plugin === 'function') {
    console.log(`New plugin (${plugin.name}) loading . . .`)
    this._addPlugin(plugin, opts, false)
    this.emit(`Loaded plugin (${plugin.name})`)
  } else {
    throw new Error('plugin must be a function')
  }

  return this
}

I guess the next step is to listen for this event in the fastify object and then pipe the name of the function into the registeredPlugins list.

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the long delay for a review, I’ve been on vacation and then catching up for a while (and traveling for work).

fastify.js Outdated

>>>>>>> 43d9c92... prep for pull
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this expected?

fastify.js Outdated
@@ -23,6 +24,7 @@ const Schemas = require('./lib/schemas')
const loggerUtils = require('./lib/logger')
const pluginUtils = require('./lib/pluginUtils')
const runHooks = require('./lib/hookRunner').hookRunner
const toString = require('./toString')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should live under lib/

toString.js Outdated
output_string += `\n${LINES.r} TODO`
console.log(`Registered Plugins: ${this[pluginUtils.registeredPlugins]}`)
// console.log(this)
console.log(output_string)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no console.log in this method

@mcollina
Copy link
Member

mcollina commented May 6, 2018

This needs unit tests.

@Ethan-Arrowood
Copy link
Member Author

Not to worry about the delay. I've been quite busy myself. Thank you for the review. I'll make sure to fix these things soon, but can you please review my previous comment on this thread regarding pluginUtils.registeredPlugins? Once that is figure out I can start polishing this up. Thank you!

@mcollina
Copy link
Member

mcollina commented May 7, 2018

The registeredPlugins only track names. Basically that array tells which plugins have been loaded in the current tree, but it does not represent the tree itself at the root. See

instance[pluginUtils.registeredPlugins] = Object.create(instance[pluginUtils.registeredPlugins])
.

I think you can extend that mechanism to track down the tree of loaded plugins, without touching avvio at all.

@Ethan-Arrowood
Copy link
Member Author

Okay interesting. I'll mess around with that a bit more and see what I can make of it.

@Ethan-Arrowood
Copy link
Member Author

Ethan-Arrowood commented Jun 10, 2018

(branch has been rebased properly)
@mcollina / @delvedor I noticed that fastify was never actually tracking plugins. I've updated the .register method to now do so but had to use a weird work around with avvio.

The way it works is the fastify instance inherits the .use method from avvio and assigns it to fastify.register. Then it declares its own .use method (for registering middleware). But in order to track plugins being added I needed to expand the fastify.register command to push the added plugin to fastify[pluginUtils.registeredPlugins]. Let me know if you see a better way to do this.

Also I didn't know what we should do about anonymously named plugins so I added two things:

  1. A warning that users should name their plugins
  2. The toString method concatenates the string 'anonymous' with the index of the plugin and adds that to the output string.

You can check out this behavior by running the included sandbox.js file.

If you think the toString method is at a good place I can work on unit tests next.

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests are also failing

fastify.js Outdated
fastify.avvioUse(plugin, options)
if (plugin.name.length === 0) {
log.warn('Warning: should not use anonymous plugins. Provide a function name on declaration.')
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think emitting a warn is feasible. We should have default strategy (maybe the filename? or the closest name in package.json) in case there is no name.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would this method have access to the filename? Even if the user uses:

// plugin.js
const fp = require('fastify-plugin')

module.exports = fp((instance, opts, next) => {
  instance.decorate('mult', (a, b) => a * b)
  next()
})
// app.js
fastify.register(require('plugin.js'))

The register method will still receive an anonymous function.

Right now I am using 'anonymous'+plugin_index

fastify.js Outdated
@@ -173,7 +173,14 @@ function build (options) {
fastify.setSchemaCompiler(buildSchemaCompiler())

// plugin
fastify.register = fastify.use
fastify.avvioUse = fastify.use
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no need to expose this, store it in a variable.

@Ethan-Arrowood
Copy link
Member Author

Ethan-Arrowood commented Jun 10, 2018

I'm not able to get the linter to successfully run. Not sure what I broke or what I'm doing wrong but this is my console output:
screen shot 2018-06-10 at 11 08 04 pm
It is the same output when I run npm run lint or npm run test

Any idea whats going on here?

  • I've deleted node_modules
  • Ran npm install
  • I'm using node 10.4.0 and npm 6.1.0

Made this into a proper issue (#968)

@mcollina
Copy link
Member

mcollina commented Jul 1, 2018

@Ethan-Arrowood fastify-plugin 1.2.0 is out.

docs/Plugins.md Outdated
@@ -79,37 +79,27 @@ Sometimes, you will need to know when the server is about to close, for example

Do not forget that `register` will always create a new Fastify scope, if you don't need that, read the following section.

If you register an anonymous function using `fastify-plugin`, it will automatically gain a new property based on the file name the `.fp()` method was called from. You can access it using `Symbol.for('fastify.display-name')`. See the example below.
If you register an anonymous function using `fastify-plugin`, it will automatically set the meta option `name` to the file name the `.fp()` method was called from. You can access it using `fn[Symbol.for('plugin-meta')].name` or `fn[Symbol.for('fastify.display-name')]`. See the example below. If you set the name using the meta property, the file name will be ignored and the `fastify.display-name` will be set to the meta name as well.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All references to fastify.display-name should be removed since it will be removed in the next major version of fastify-plugin.

Also, .fp() should just be fp().

docs/Plugins.md Outdated
name: 'my-plugin'
}))
```
It is recommended you use a named function, but anonymous functions are supported. This was added for use within `fastify.toString()`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the recommended method of setting a plugin's name should be with the meta name option.

@mcollina
Copy link
Member

mcollina commented Jul 2, 2018

@nwoltman if we go with that plan, then we need to document plugin-meta somewhere. fastify-plugin is a mere utility and it should be possible to do the same things without it.

@Ethan-Arrowood
Copy link
Member Author

Let me see what I can come up with for the documentation. Basic facts:

  • When using fastify-plugin it is recommended to use the meta.name property, then to use a function name, and finally an anonymous function is also supported (will auto-use file name).
  • Without fastify-plugin it is recommended to use a named function; otherwise it will use a 'anonymous' + index instead.

Here is an example:

// toString.test.js
fastify.register(fp((instance, opts, next) => {
  instance.decorate('anonymousPlugin', (a, b) => a + b)
  next()
}))

fastify.register(fp(function standardPlugin (instance, opts, next) {
  instance.decorate('standardPlugin', (a, b) => a - b)
  next()
}))

fastify.register((instance, opts, next) => {
  instance.decorate('anonWithoutFastifyPlugin', (a, b) => a * b)
  next()
})

fastify.register(function namedWithoutFP (instance, opts, next) {
  instance.decorate('namedWithoutFP', (a, b) => a / b)
  next()
})

// fastify.toString() ->
let result = `...
Plugins
├── toString.test
├── standardPlugin
├── anonymous0
└── namedWithoutFP`

@nwoltman
Copy link
Contributor

nwoltman commented Jul 2, 2018

@mcollina You're right. Using fastify-plugin shouldn't be a requirement to naming a plugin.

Copy link
Contributor

@nwoltman nwoltman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a nit

docs/Plugins.md Outdated
fastify.register(fp(async () => {}, { name: 'myAsyncFunction' }))
```

Internally, `fastify` will register this anonymous function as `'myAsyncFunction'`. `fastify-plugin` will always prioritize the meta property, even when supplied a named function. If you do not provide a `meta.name` to the `fp()` method, `fastify-plugin` will try and use the function name; if its an anonymous function it will use the file name from which the `fp()` method was called. See the examples below:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: if its => if it's

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still LGTM

Copy link
Member

@delvedor delvedor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello! Nice work!
I have some questions, given the following example

'use strict'

const fastify = require('fastify')()

fastify.get('/', () => {})

fastify.get('/hello', () => {})

fastify.register((instance, opts, next) => {
  instance.addHook('preHandler', () => {})
  instance.get('/hello', () => {})

  instance.register((i, opts, next) => {
    i.addHook('preHandler', () => {})
    i.get('/hello', () => {})

    next()
  }, { prefix: '/inside' })

  next()
}, { prefix: '/v1' })

fastify.ready(() => {
  console.log(fastify.toString())
})

I get

Top Level Hooks
├── onRequest (0 hooks): []
├── preHandler (0 hooks): []
├── onResponse (0 hooks): []
└── onSend (0 hooks): []

Routes
└── / (GET)
    ├── hello (GET)
    └── v1/
        ├── hello (GET)
        └── inside/ciao (GET)

Top Level Middleware
└── /: []

Plugins
└── anonymous0

As you can see the nested hooks (and probably the same for middlewares) are not present in the final string.
Why? Am I missing something?

If this is correct, I think we should not land this until we have a complete log of every level of Fastify, because we are not giving a complete overview of the application structure, but just a small part.

@Ethan-Arrowood
Copy link
Member Author

Ethan-Arrowood commented Jul 3, 2018

Nested hooks were intentionally left out as of right now. Looking back on your original issue I see how we may want to display this information.

So per your example the expected out put might look like something like this?

Hooks: []
├── /
│
├── hello (GET)
│
└── v1/ 
    │   Hooks: [preHandler]
    │
    ├── hello (GET) 
    │
    └── inside/hello (GET)
        └── Hooks: [preHandler]

If this is what you want it to be instead of what it currently is then this feature will need quite a bit more work (which I'm okay with doing). I do not know how Fastify exposes these things internally so I cannot gauge how difficult this change will be.

@delvedor
Copy link
Member

delvedor commented Jul 3, 2018

@Ethan-Arrowood yes, kind of:

Hooks: []
Middlewares: []
├── /
├── hello (GET)
│
└── v1/ 
│   │   Hooks: [preHandler]
│   │   Middlewares: []
│   ├── hello (GET) 
│   ├── status/check (HEAD) 
│   │
│   └── inside/
│       │   Hooks: [preHandler]
│       │   Middlewares: []
│       ├── hello (GET) 
│       └── hello/update (POST)
│
└── v2/ 
    │   Hooks: [preHandler, onSend]
    │   Middlewares: []
    ├── hello (GET) 
    └── status/check (HEAD) 

I think we can drop the use of printRoutes and build our own method. We might want to use node-archy to build the tree.
It's definitely not an easy task, but we will give you support everywhere we can!

Probably a nice solution could be add an internal event emitter which emits an event for every route/hook/middleware/plugin registered with its own context. If you want I can work on it :)

@Ethan-Arrowood
Copy link
Member Author

Thank you for the recommendations. I think I'll take on the challenge and see how far I can go on my own. I'll make sure to ping here if i need help!

this[registeredPlugins].push(name)
const functionName = meta && meta.name ? meta.name : (
!fn.name ? `anonymous${anonFuncCount++}` : (
fn.name !== 'bound _after' ? fn.name : null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the 'bound _after' ? Could you use a constant for that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is some default name for one of things fastify registers or uses. Will most likely change when I re implement this feature given the new constraints

@stale
Copy link

stale bot commented Jul 26, 2018

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale Issue or pr with more than 15 days of inactivity. label Jul 26, 2018
@mcollina mcollina added the discussion Issues or PRs with this label will never stale label Jul 26, 2018
@stale stale bot removed the stale Issue or pr with more than 15 days of inactivity. label Jul 26, 2018
@Ethan-Arrowood
Copy link
Member Author

Sorry I haven't gotten back around to this yet. Been very busy with my new internship so i'm only just starting to find time for open source

@jsumners
Copy link
Member

Completely understand. I'm right there with you.

@jsumners
Copy link
Member

jsumners commented Oct 9, 2018

I vote yes for toJSON.

@mcollina
Copy link
Member

mcollina commented Oct 10, 2018 via email

@Ethan-Arrowood
Copy link
Member Author

I don't believe I'll be able to complete this feature. There is an toJSON pr open here: #1261
if/when that is complete I'll be happy to work on the toString feature (which will use that toJSON object as its base.

@cemremengu
Copy link
Contributor

@Ethan-Arrowood you/we can work on the toJSON if you have time. I opened the PR but failed to deliver due to my schedule.

@Ethan-Arrowood
Copy link
Member Author

@cemremengu i'll look into it but I ran into a fair amount of discouraging issue trying to implement it before. Maybe after this time I'll have some new ideas, so we'll see, but I don't want to make any promises

@github-actions
Copy link

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 11, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
discussion Issues or PRs with this label will never stale semver-minor Issue or PR that should land as semver minor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature: toString method
7 participants