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

Reduce duplication in badge regex/url patterns; affects [ansible apm gem wercker] #2279

Merged
merged 10 commits into from
Nov 8, 2018
Merged
14 changes: 7 additions & 7 deletions doc/TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ module.exports = class Example extends BaseService { // (3)
static get url() { // (4)
return {
base: 'example',
format: '([^/]+)',
capture: ['text'],
pattern: ':text',
}
}

Expand All @@ -124,9 +123,9 @@ Description of the code:
3. Our module must export a class which extends `BaseService`
4. `url()` declares a route. We declare getters as `static`.
* `base` defines the static part of the route.
* `format` is a [regular expression](https://www.w3schools.com/jsref/jsref_obj_regexp.asp) defining the variable part of the route.
* We can use `capture` to extract matched regex clauses into one or more named variables. Here we are declaring that we want to store the string matched by `([^/]+)` in a variable called `text`.
This declaration adds the route `/^\/test\/([^\/]+)\.(svg|png|gif|jpg|json)$/` to our application.
* `pattern` defines the variable part of the route. It can include any
number of named parameters. These are converted into
regular expressions by [`path-to-regexp`][path-to-regexp].
Copy link
Member

Choose a reason for hiding this comment

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

Do you think its worth mentioning the format/capture args in the docs too, or is it too much detail?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmmmm… well I guess my hope is that we won't need to use them. Also yea, it seems like it's a bit detailed to include in such a simple example in a tutorial.

I'm curious: are there services that you think will require a hand-crafted regex?

Copy link
Member

Choose a reason for hiding this comment

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

I'm curious: are there services that you think will require a hand-crafted regex?

This is an example I'd pick of one that I suspect would be quite hard:

'(?:(?:ci/)([a-fA-F0-9]{24})|(?:build|ci)/([^/]+/[^/]+))(?:/(.+))?',

although I've not tried to port it (there is history to that one). Happily, these examples are in a minority.

In general, routes that use a non-capturing group might be difficult but I may wrong on that? If so, I suppose you could just capture stuff and not use it..

Copy link
Member Author

Choose a reason for hiding this comment

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

Wow, that is a monster! I can't say I can follow what it's doing, even from the tests. I'm guessing it's handling some cases which aren't covered by the tests.

I imagine it's too complex to deal with using path-to-regexp with a single pattern. Though I'd wager it could be expressed clearly using two or three, two of which I imagine are deprecated.

There is support for unnamed parameters, which could be a way to handle non-capturing groups. We'd have to build a little support around those.

Though I wonder if it would be better to allow services to register multiple patterns. To eventually build the URLs from fields in the UI, it would be helpful to separate the deprecated patterns from the current pattern.

It could also make matching more efficient, down the line, because it could allow pushing things from the regex into a prefix, which could be indexed in a trie.

Copy link
Member

Choose a reason for hiding this comment

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

Though I'd wager it could be expressed clearly using two or three, two of which I imagine are deprecated

Though I wonder if it would be better to allow services to register multiple patterns.

This is a good point. We're getting into the weeds a bit on an unrelated issue here, but in this specific example there is a reason why we've defined this as a regex rather than several classes:

Using the regex allows us to specify the order of matching (i.e: try to match this first, then if that fails, try to match that), whereas if we had defined one route for ci/([a-fA-F0-9]{24})(?:/(.+))? and one for ci/([^/]+/[^/]+)(?:/(.+))?, it matters what order we register the routes in (or what order we try to match in) because if we end up testing against ci/([^/]+/[^/]+)(?:/(.+))? first ci/559e33c8e982fc615500b357/master will match that, but we don't want it to.

The upshot of that is if we want to allow a single badge to define multiple routes, we'd want to be able to also specify which order we'll try to match those routes in, if you see what I mean.

All of that said, lets not get too bogged down in this one case for now. As it stands, this will allow us to improve 99% of cases and it we can continue to use a regex in other situations. We can worry about these odd edge cases antoher day.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yea, that makes sense.

I'm thinking it would be good to identify canonical forms of these routes, and use a 301 redirect. There's a small performance penalty, but it significantly increases the chance that the URLs will be updated because if they're pasted into an address bar, they will be replaced by the canonical URL. There are a couple services where we've renamed things, and there's an "old static badge" example in the bottom of server.js. (Honestly perhaps that last one could be deleted. I wonder if it's getting any hits.)

It could be useful to set a priority for services so they can be configured to register earlier or later.

Not the highest priorities, but probably worth tackling someday.

5. All badges must implement the `async handle()` function. This is called to invoke our code. Note that the signature of `handle()` will match the capturing group defined in `url()` Because we're capturing a single variable called `text` our function signature is `async handle({ text })`. Although in this simple case, we aren't performing any asynchronous calls, `handle()` would usually spend some time blocked on I/O. We use the `async`/`await` pattern for asynchronous code. Our `handle()` function returns an object with 3 properties:
* `label`: the text on the left side of the badge
* `message`: the text on the right side of the badge - here we are passing through the parameter we captured in the URL regex
Expand All @@ -143,6 +142,8 @@ To try out this example badge:
4. Visit the badge at <http://[::]:8080/example/foo.svg>.
It should look like this: ![](https://img.shields.io/badge/example-foo-blue.svg)

[path-to-regexp]: https://github.com/pillarjs/path-to-regexp#parameters

### (4.3) Querying an API

The example above was completely static. In order to make a useful service badge we will need to get some data from somewhere. The most common case is that we will query an API which serves up some JSON data, but other formats (e.g: XML) may be used.
Expand All @@ -165,8 +166,7 @@ module.exports = class GemVersion extends BaseJsonService { // (5)
static get url() { // (6)
return {
base: 'gem/v',
format: '(.+)',
capture: ['gem'],
pattern: ':gem',
}
}

Expand Down
Loading