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

[Packagist] dependency version #8371

Merged
merged 31 commits into from
Sep 14, 2022

Conversation

PaulaBarszcz
Copy link
Collaborator

@PaulaBarszcz PaulaBarszcz commented Sep 2, 2022

[Updated description]

Related to: #6963

We can check the dependencies' versions against the require section of https://packagist.org/p2/symfony/symfony.json

Below are some badge screenshots for a couple of scenarios.

  1. dependency's name contains only one part (no slash needed), e.g. php.
    For the php case, I added a redirect from an old endpoint (packagist-php-version.service.js), as @chris48s advised:

http://localhost:8080/packagist/php-v/symfony/symfony/v3.2.8?server=https://packagist.org =>
http://localhost:8080/packagist/dependency-v/symfony/symfony/php.svg?server=https%3A%2F%2Fpackagist.org&version=v3.2.8

image

  1. dependency's name contains two parts, e.g. twig/twig.
  • both given correctly

http://localhost:8080/packagist/dependency-v/symfony/symfony/twig/twig

image

  • one of them given incorrectly

http://localhost:8080/packagist/dependency-v/symfony/symfony/twiiiiiig/twig

image

  1. when dependency's name is the same as the package name

http://localhost:8080/packagist/dependency-v/symfony/symfony/symfony/contracts

image

  1. name of the dependency contains a hyphen

http://localhost:8080/packagist/dependency-v/symfony/symfony/ext-xml

image

  1. with specified version of the package
  • with correct version

http://localhost:8080/packagist/dependency-v/symfony/symfony/twig/twig?version=v3.2.8

image

  • with incorrect version

http://localhost:8080/packagist/dependency-v/symfony/symfony/twig/twig?version=v3.2.8000

image

  1. with custom server given (plus version given)
  • with a correct server

http://localhost:8080/packagist/dependency-v/symfony/symfony/twig/twig?version=v3.2.8&server=https://packagist.org

image

  • with an incorrect server

http://localhost:8080/packagist/dependency-v/symfony/symfony/twig/twig?version=v3.2.8&server=https://packagiiiist.org

image

  1. not specified dependency's name

http://localhost:8080/packagist/dependency-v/symfony/symfony/

image

@shields-ci
Copy link

shields-ci commented Sep 2, 2022

Messages
📖 ✨ Thanks for your contribution to Shields, @PaulaBarszcz!

Generated by 🚫 dangerJS against becfc0e

@lgtm-com
Copy link

lgtm-com bot commented Sep 2, 2022

This pull request introduces 1 alert when merging 623b442 into b624179 - view on LGTM.com

new alerts:

  • 1 for Wrong use of 'this' for static method

@lgtm-com
Copy link

lgtm-com bot commented Sep 2, 2022

This pull request introduces 1 alert when merging fd96f9e into b624179 - view on LGTM.com

new alerts:

  • 1 for Wrong use of 'this' for static method

@PaulaBarszcz PaulaBarszcz changed the title Packagist dependency version [Packagist] dependency version Sep 3, 2022
@PaulaBarszcz PaulaBarszcz marked this pull request as ready for review September 3, 2022 13:59
@PaulaBarszcz
Copy link
Collaborator Author

This PR is ready to be reviewed :)

@calebcartwright calebcartwright added the service-badge New or updated service badge label Sep 3, 2022
@chris48s
Copy link
Member

chris48s commented Sep 4, 2022

Thanks for picking this one up. I'm just having a look through this, and it seems to me like given the require key for a packagist API response is something like:

"require": {
    "php": ">=8.1",
    "composer-runtime-api": ">=2.1",
    "ext-xml": "*",
    "friendsofphp/proxy-manager-lts": "^1.0.2",
    "doctrine/event-manager": "~1.0",
    "doctrine/persistence": "^2|^3",
    "twig/twig": "^2.13|^3.0.4",
    "psr/cache": "^2.0|^3.0",
    "psr/container": "^1.1|^2.0",
    "psr/event-dispatcher": "^1.0",
    "psr/link": "^1.1|^2.0",
    "psr/log": "^1|^2|^3",
    "symfony/contracts": "^2.5|^3.0",
    "symfony/polyfill-ctype": "~1.8",
    "symfony/polyfill-intl-grapheme": "~1.0",
    "symfony/polyfill-intl-icu": "~1.0",
    "symfony/polyfill-intl-idn": "^1.10",
    "symfony/polyfill-intl-normalizer": "~1.0",
    "symfony/polyfill-mbstring": "~1.0",
    "symfony/polyfill-uuid": "^1.15"
}

the distinction between a "packagist PHP version" badge ( /packagist/php-v ) and a "packagist generic dependency version" badge ( packagist/dependency-v ) is somewhat artificial - right?

i.e: if we've got a badge where we can use that data structure to show the version of twig/twig or composer-runtime-api then using that data structure to show the version of php should just be that same badge, but passing it php - right? ..or is there something subtly different we need to deal with if the thing we're reporting the version of PHP specifically?

I can also see that in that case things are a bit fiddly because of maintaining compatibility with the current URL structure, but I think I can see where I think we should go with this to streamline things a bit. I just want to check that assumption before I start making suggestions - you've probably spent more time thinking about this lately than I have.

@PaulaBarszcz
Copy link
Collaborator Author

Thanks for picking this one up. I'm just having a look through this, and it seems to me like given the require key for a packagist API response is something like:

"require": {
    "php": ">=8.1",
    "composer-runtime-api": ">=2.1",
    "ext-xml": "*",
    "friendsofphp/proxy-manager-lts": "^1.0.2",
    "doctrine/event-manager": "~1.0",
    "doctrine/persistence": "^2|^3",
    "twig/twig": "^2.13|^3.0.4",
    "psr/cache": "^2.0|^3.0",
    "psr/container": "^1.1|^2.0",
    "psr/event-dispatcher": "^1.0",
    "psr/link": "^1.1|^2.0",
    "psr/log": "^1|^2|^3",
    "symfony/contracts": "^2.5|^3.0",
    "symfony/polyfill-ctype": "~1.8",
    "symfony/polyfill-intl-grapheme": "~1.0",
    "symfony/polyfill-intl-icu": "~1.0",
    "symfony/polyfill-intl-idn": "^1.10",
    "symfony/polyfill-intl-normalizer": "~1.0",
    "symfony/polyfill-mbstring": "~1.0",
    "symfony/polyfill-uuid": "^1.15"
}

the distinction between a "packagist PHP version" badge ( /packagist/php-v ) and a "packagist generic dependency version" badge ( packagist/dependency-v ) is somewhat artificial - right?

i.e: if we've got a badge where we can use that data structure to show the version of twig/twig or composer-runtime-api then using that data structure to show the version of php should just be that same badge, but passing it php - right? ..or is there something subtly different we need to deal with if the thing we're reporting the version of PHP specifically?

I can also see that in that case things are a bit fiddly because of maintaining compatibility with the current URL structure, but I think I can see where I think we should go with this to streamline things a bit. I just want to check that assumption before I start making suggestions - you've probably spent more time thinking about this lately than I have.

Yes, packagist/dependency-v is nearly a copy of packagist/php-v. I did not want to change php-v endpoint for the sake of backwards compatibility. But if you think that we should follow a different approach than what I wrote in this PR, I am definitely open to suggestions :)

Copy link
Collaborator Author

@PaulaBarszcz PaulaBarszcz left a comment

Choose a reason for hiding this comment

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

I modified a regex in services/test-validators for checking the dependecy’s version so that it would accept also 2 options present in the response from packagist’s API.
An asterisk and 2 alternative versions (separated by a single |):
B59F9460-3A23-499D-BC66-0A11CEAD26F5

I checked with https://regexr.com/ to confirm that the previously accepted strings are still valid.

Edit: only now did I notice that I have to check also if these also pass the regex validation check (three option separated by a single pipe; version number starting with a ~):

psr/log": "^1|^2|^3",
symfony/polyfill-intl-grapheme": "~1.0",

Edit2:
I confirmed that all of these pass the regex validation:
https://regexr.com/
Expression:

\*|(\s*(>=|>|<|<=|!=|\^|~)?\d+(\.(\*|(\d+(\.(\d+|\*))?)))?((\s*\|*)?\s*(>=|>|<|<=|!=|\^|~)?\d+(\.(\*|(\d+(\.(\d+|\*))?)))?)*\s*)
~1.28|~2.0
7.* || 5.6.*
7.1
>=5.6
>1.0 <2.0
!=1.0 <1.1 || >=1.2
7.1.*
7.* || 5.6.*
*
^1|^2|^3
~1.0

image

@chris48s
Copy link
Member

chris48s commented Sep 4, 2022

Yes, packagist/dependency-v is nearly a copy of packagist/php-v. I did not want to change php-v endpoint for the sake of backwards compatibility. But if you think that we should follow a different approach than what I wrote in this PR, I am definitely open to suggestions :)

OK cool. I think we can usefully tidy up and reduce some duplication here then :)

In terms of the URL schema for the new packagist dependency version badge we've got a few requirements:

  • In general, params which are required to render a badge should be positional params in the URL rather than passed in the query string. There are some docs on badge URLs in https://github.com/badges/shields/blob/master/doc/badge-urls.md
  • We would like the ability for the 'dependency' to either inlcude a / character or not include a / character. i.e: php and twig/twig should both be valid
  • There is also this optional :version param (for specifying we want the to query dependencies for a particular package version instead of the latest) that currently exists on the php-v badge which is uncommonly used and not required but we need to maintain compatibility with it.

so here's my suggestion:

  • Lets make the URL pattern for the new badge /packagist/dependency-v/:user/:repo/:dependency+. This will make the :dependency param required and also allow it to contain slash characters. Then the value of dependency can be either php or twig/twig and we can just look for dependency in require and use it as the badge label. That should simplify a lot of the dependencyVendor/dependencyRepo stuff you are doing here.
  • Using + does mean that we then can't make the version param positional in the URL, so lets convert that to a query param instead, like server.
  • We completely get rid of the existing PackagistPhpVersion class, make sure any test cases are covered in the tests for the new badge and we convert the php-v route to a redirect:
    • /packagist/php-v/symfony/symfony should redirect to /packagist/dependency-v/symfony/symfony/php
    • /packagist/php-v/symfony/symfony/v2.8.0 should redirect to /packagist/dependency-v/symfony/symfony/php?version=v2.8.0
    • We can have a specific example on the class for /packagist/dependency-v showing PHP version as it is a reasonably important special-case

There are lots of places where we have replaced an old route with a redirect to a new one you can refer to, but here is one example. https://github.com/badges/shields/blob/master/services/pypi/pypi-django-versions.service.js

@PaulaBarszcz
Copy link
Collaborator Author

I added toLowerCase() to prevent the below case:

http://localhost:8080/packagist/dependency-v/symfony/symfony/PHP
image

Now we're good both with PHP and php as a dependency's name:
http://localhost:8080/packagist/dependency-v/symfony/symfony/php
image

http://localhost:8080/packagist/dependency-v/symfony/symfony/PHP
image

@PaulaBarszcz
Copy link
Collaborator Author

@chris48s
Thanks a lot for your suggestions. I applied them. This PR is ready to be re-reviewed.

Copy link
Member

@chris48s chris48s left a comment

Choose a reason for hiding this comment

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

The bulk of this PR is good to go. I've commented some suggestions, but its all minor tweaks now.

services/packagist/packagist-dependency-version.service.js Outdated Show resolved Hide resolved
throw new NotFound({ prettyMessage: 'version requirement not found' })
}

const depLowerCase = dependency.toLowerCase()
Copy link
Member

Choose a reason for hiding this comment

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

Are we sure no packagist vendor name, package name or extension name can contain an upper-case character?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good question - I don’t think we can be sure of that.
Should i remove the above line?
It would then have issues with querries containing capital letters
(#8371 (comment))

We can do one of the following:

  1. first check if there is a position in the response in ‚require’ section that contains directly the dependecy’s name (with capital letters)
    and if not, transform the queried dependency’s name to lowercase and repeat the comparison
  2. transform every dependency’s name from the reeponse to lowercase - and compare with lowercase depemdency’s name from the query.

What would be your preference?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I modified it, now it looks like this - is it fine?

    // All dependencies' names in the 'require' section from the response should be lowercase,
    // so that we can compare lowercase name of the dependency given via url by the user.
    Object.keys(packageVersion.require).forEach(dependency => {
      packageVersion.require[dependency.toLowerCase()] =
        packageVersion.require[dependency]
    })

    const depLowerCase = dependency.toLowerCase()

    if (!packageVersion.require[depLowerCase]) {
      throw new NotFound({ prettyMessage: 'version requirement not found' })
    }

    return { dependencyVersion: packageVersion.require[depLowerCase] }

services/packagist/packagist-dependency-version.service.js Outdated Show resolved Hide resolved
services/packagist/packagist-dependency-version.service.js Outdated Show resolved Hide resolved
services/packagist/packagist-dependency-version.tester.js Outdated Show resolved Hide resolved
services/packagist/packagist-dependency-version.tester.js Outdated Show resolved Hide resolved
services/packagist/packagist-dependency-version.tester.js Outdated Show resolved Hide resolved
Comment on lines 55 to 62
t.create(
`gets the package version of symfony 5.2.3 - incorrectly with missing part of dependency's name`
)
.get('/symfony/symfony/twig.json')
.expectBadge({
label: 'dependency version',
message: 'version requirement not found',
})
Copy link
Member

Choose a reason for hiding this comment

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

In general, the naming of tests in this file is a bit jumbled. For example, this one is called gets the package version of symfony 5.2.3... but you're not passing ?version=5.2.3. There's another one further down with the same problem that I've suggested deleting as a duplicate. Then you've got another one further up where you're actually explicitly passing ?version=v3.2.8 in the URL but the test name is just "gets the package version of symfony...", and so on.

Can you have a look over the tests and make sure:

  • you're passing the ?version= param when that is actually part of the test, and leave it out if it doesn't make any difference to the test
  • if you want to explicitly describe the test params in the name of the test, make sure the test names and the params in the test body match up consistently.

That said, I know the number of cases we need to cover is now a bit larger and naming things is hard, but if you look at the test names that are deleted elsewhere in this PR (e.g: "invalid package name", "invalid version", "custom server", "invalid custom server", etc) those test names are actually pretty good. They explain what we are actually trying to test (i.e: what behaviour are we trying to trigger by providing these inputs, and what are we making an assertion about) rather than describing all of the inputs in each test. You might get more mileage out of just simplifying the test names a bit. I'll leave it with you to have a think about it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I removed the version query param, where it was not needed.
I modified the tests' names, I hope that now they are ok.

@PaulaBarszcz PaulaBarszcz changed the title [Packagist] dependency version WIP [Packagist] dependency version Sep 12, 2022
@PaulaBarszcz
Copy link
Collaborator Author

Suggested changes were implemented. This PR is ready to be re-reviewed.

@PaulaBarszcz PaulaBarszcz changed the title WIP [Packagist] dependency version [Packagist] dependency version Sep 12, 2022
@chris48s
Copy link
Member

chris48s commented Sep 13, 2022

I'm just having a look over the tests in services/packagist/packagist-dependency-version.spec.js and it seems like we have dropped some important test cases that exist in https://github.com/badges/shields/blob/master/services/packagist/packagist-php-version.spec.js

There's the case where the require key is missing:

const specJson = {
packages: {
'frodo/the-one-package': [
{
version: '3.0.0',
},
{
version: '2.0.0',
require: { php: '^7.2' },
},
{
version: '1.0.0',
require: { php: '^5.6 || ^7' },
},
],
},
}

and the case where the require key is the string '__unset':

const specJson = {
packages: {
'frodo/the-one-package': [
{
version: '3.0.0',
require: { php: '^7.4 || 8' },
},
{
version: '2.0.0',
require: { php: '^7.2' },
},
{
version: '1.0.0',
require: '__unset',
},
],
},
}

#7779 gives a good example of a response including both the missing and __unset case: https://packagist.org/p2/hyde/framework.json

The packagist API has some surprising behaviours and edge cases. Most of this is handled by calling expandPackageVersions() to 'decompress' the API response. I'd like to ensure we are still covering these cases in the tests. It is quite easy to accidentally break the packagist code due to these gotchas (I actually have an open issue about refactoring this to make it a bit easier at #7783 but lets leave that one for another day).

@PaulaBarszcz
Copy link
Collaborator Author

There's the case where the require key is missing:
[...]
and the case where the require key is the string '__unset':
[...]

#7779 gives a good example of a response including both the missing and __unset case: https://packagist.org/p2/hyde/framework.json

The packagist API has some surprising behaviours and edge cases. Most of this is handled by calling expandPackageVersions() to 'decompress' the API response. I'd like to ensure we are still covering these cases in the tests. It is quite easy to accidentally break the packagist code due to these gotchas (I actually have an open issue about refactoring this to make it a bit easier at #7783 but lets leave that one for another day).

You're right:)
I added the missing cases to unit tests (missing require key and require key with the value of the __unset string).

Copy link
Member

@chris48s chris48s left a comment

Choose a reason for hiding this comment

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

Nearly done. There is one final thing to get this over the line: In a6fd477 the captions and version are the wrong way round: v2.5.0 is the __unset case and v2.4.0 is the missing case. I've suggested 2 corrections to make the inputs match the descriptions.

services/packagist/packagist-dependency-version.spec.js Outdated Show resolved Hide resolved
services/packagist/packagist-dependency-version.spec.js Outdated Show resolved Hide resolved
Comment on lines +15 to +21
{
version: 'v2.5.0',
require: '__unset',
},
{
version: 'v2.4.0',
},
Copy link
Member

Choose a reason for hiding this comment

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

I was initially confused by this, but I see what you did with the ordering to fit all 3 cases into a single input.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

👍☺️

PaulaBarszcz and others added 2 commits September 14, 2022 21:57
Co-authored-by: chris48s <chris48s@users.noreply.github.com>
Co-authored-by: chris48s <chris48s@users.noreply.github.com>
@PaulaBarszcz
Copy link
Collaborator Author

Nearly done. There is one final thing to get this over the line: In a6fd477 the captions and version are the wrong way round: v2.5.0 is the __unset case and v2.4.0 is the missing case. I've suggested 2 corrections to make the inputs match the descriptions.

Thanks for spotting this, you’re right 😅 Committed.

Copy link
Member

@chris48s chris48s left a comment

Choose a reason for hiding this comment

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

Got there in the end. Packagist is definitely one of the trickier service families to work on.

🚀

@chris48s chris48s merged commit 93fa955 into badges:master Sep 14, 2022
@PaulaBarszcz PaulaBarszcz deleted the packagist-dependency-version branch September 14, 2022 20:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
service-badge New or updated service badge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants