Repository: badges/shields (js)

Issue:
AUR badges use deprecated and undocumented API to retrieve information
### Are you experiencing an issue with...

shields.io

### 🐞 Description

IN #612 AUR support was added, which currently lives here:

https://github.com/badges/shields/blob/a08b523b4fc55a962aa2562dd4ac9126bbf032ed/services/aur/aur.service.js#L38-L46

Per the AUR documentation, there is supposed to be a version keyword `v=5`, which is missing. This is philosophically mandatory, but not a technical restriction: for backwards compatibility with the original v1 API a missing version is interpreted as the v1 API.

(Side note: the v=5 API was current at the time of that PR, not entirely sure why it ended up using the v=1 API instead.)

This is undocumented and may be dropped at any time. And in fact, we plan to drop it in the near future, and make specifying a version mandatory. (This will simplify the code a lot, and that's beneficial since we're currently porting it from PHP to python.)

According to server logs, 65% or more of unversioned requests are coming from shields.io, so this change would definitely impact you!

### 🔗 Link to the badge

https://img.shields.io/aur/maintainer/google-chrome
![AUR maintainer](https://img.shields.io/aur/maintainer/google-chrome)

### 💡 Possible Solution

Please specify the v=5 API and check that it performs as expected.

This will aid us in evaluating the ability to drop old API versions, and future proof shields.io against AUR service changes.

Thanks!

Pull request:
use v5 API for [AUR] badges
Resolves #6835 

Patch:
diff --git a/services/aur/aur.service.js b/services/aur/aur.service.js
index 901e8e0bf9..c854ce94d4 100644
--- a/services/aur/aur.service.js
+++ b/services/aur/aur.service.js
@@ -3,23 +3,25 @@ import {
   floorCount as floorCountColor,
   age as ageColor,
 } from '../color-formatters.js'
+import { renderLicenseBadge } from '../licenses.js'
 import { addv, metric, formatDate } from '../text-formatters.js'
 import { nonNegativeInteger } from '../validators.js'
 import { BaseJsonService, NotFound, InvalidResponse } from '../index.js'
 
 const aurSchema = Joi.object({
   resultcount: nonNegativeInteger,
-  results: Joi.alternatives(
-    Joi.array().length(0).required(),
-    Joi.object({
-      License: Joi.string().required().allow(null),
-      NumVotes: nonNegativeInteger,
-      Version: Joi.string().required(),
-      OutOfDate: nonNegativeInteger.allow(null),
-      Maintainer: Joi.string().required().allow(null),
-      LastModified: nonNegativeInteger,
-    }).required()
-  ),
+  results: Joi.array()
+    .items(
+      Joi.object({
+        License: Joi.array().items(Joi.string().required()).allow(null),
+        NumVotes: nonNegativeInteger,
+        Version: Joi.string().required(),
+        OutOfDate: nonNegativeInteger.allow(null),
+        Maintainer: Joi.string().required().allow(null),
+        LastModified: nonNegativeInteger,
+      })
+    )
+    .required(),
 }).required()
 
 class BaseAurService extends BaseJsonService {
@@ -41,7 +43,7 @@ class BaseAurService extends BaseJsonService {
     return this._requestJson({
       schema: aurSchema,
       url: 'https://aur.archlinux.org/rpc.php',
-      options: { qs: { type: 'info', arg: packageName } },
+      options: { qs: { v: 5, type: 'info', arg: packageName } },
     })
   }
 }
@@ -54,29 +56,25 @@ class AurLicense extends BaseAurService {
     {
       title: 'AUR license',
       namedParams: { packageName: 'android-studio' },
-      staticPreview: this.render({ license: 'Apache' }),
+      staticPreview: renderLicenseBadge({ license: 'Apache', color: 'blue' }),
     },
   ]
 
   static defaultBadgeData = { label: 'license' }
 
-  static render({ license }) {
-    return { message: license, color: 'blue' }
-  }
-
   transform(json) {
-    const license = json.results.License
-    if (!license) {
+    const licenses = json.results[0].License
+    if (!licenses) {
       throw new NotFound({ prettyMessage: 'not specified' })
     }
 
-    return { license }
+    return { licenses }
   }
 
   async handle({ packageName }) {
     const json = await this.fetch({ packageName })
-    const { license } = this.transform(json)
-    return this.constructor.render({ license })
+    const { licenses } = this.transform(json)
+    return renderLicenseBadge({ licenses, color: 'blue' })
   }
 }
 
@@ -104,7 +102,7 @@ class AurVotes extends BaseAurService {
 
   async handle({ packageName }) {
     const json = await this.fetch({ packageName })
-    return this.constructor.render({ votes: json.results.NumVotes })
+    return this.constructor.render({ votes: json.results[0].NumVotes })
   }
 }
 
@@ -127,10 +125,12 @@ class AurVersion extends BaseAurService {
   }
 
   async handle({ packageName }) {
-    const json = await this.fetch({ packageName })
+    const {
+      results: [{ Version: version, OutOfDate: outOfDate }],
+    } = await this.fetch({ packageName })
     return this.constructor.render({
-      version: json.results.Version,
-      outOfDate: json.results.OutOfDate,
+      version,
+      outOfDate,
     })
   }
 }
@@ -156,7 +156,7 @@ class AurMaintainer extends BaseAurService {
 
   async handle({ packageName }) {
     const {
-      results: { Maintainer: maintainer },
+      results: [{ Maintainer: maintainer }],
     } = await this.fetch({ packageName })
     if (!maintainer) {
       throw new InvalidResponse({ prettyMessage: 'No maintainer' })
@@ -191,7 +191,7 @@ class AurLastModified extends BaseAurService {
 
   async handle({ packageName }) {
     const json = await this.fetch({ packageName })
-    const date = 1000 * parseInt(json.results.LastModified)
+    const date = 1000 * parseInt(json.results[0].LastModified)
     return this.constructor.render({ date })
   }
 }
diff --git a/services/licenses.js b/services/licenses.js
index 9def600c8f..69ff20699a 100644
--- a/services/licenses.js
+++ b/services/licenses.js
@@ -113,7 +113,7 @@ function licenseToColor(licenses) {
   return color
 }
 
-function renderLicenseBadge({ license, licenses }) {
+function renderLicenseBadge({ license, licenses, color }) {
   if (licenses === undefined) {
     licenses = toArray(license)
   }
@@ -124,7 +124,7 @@ function renderLicenseBadge({ license, licenses }) {
 
   return {
     message: licenses.join(', '),
-    color: licenseToColor(licenses),
+    color: color || licenseToColor(licenses),
   }
 }
 
diff --git a/services/licenses.spec.js b/services/licenses.spec.js
index 20554dadc7..982b06cccb 100644
--- a/services/licenses.spec.js
+++ b/services/licenses.spec.js
@@ -40,5 +40,9 @@ describe('license helpers', function () {
       message: 'MPL-2.0, MIT',
       color: 'green',
     })
+    given({ license: 'MIT', color: 'pink' }).expect({
+      message: 'MIT',
+      color: 'pink',
+    })
   })
 })


Test patch:
diff --git a/services/aur/aur.tester.js b/services/aur/aur.tester.js
index c6026d4f35..37ad9c5a7e 100644
--- a/services/aur/aur.tester.js
+++ b/services/aur/aur.tester.js
@@ -47,19 +47,22 @@ t.create('license (no license)')
     nock('https://aur.archlinux.org')
       .get('/rpc.php')
       .query({
+        v: 5,
         type: 'info',
         arg: 'vscodium-bin',
       })
       .reply(200, {
         resultcount: 1,
-        results: {
-          License: null,
-          NumVotes: 1,
-          Version: '1',
-          OutOfDate: null,
-          Maintainer: null,
-          LastModified: 1,
-        },
+        results: [
+          {
+            License: null,
+            NumVotes: 1,
+            Version: '1',
+            OutOfDate: null,
+            Maintainer: null,
+            LastModified: 1,
+          },
+        ],
       })
   )
   .expectBadge({ label: 'license', message: 'not specified' })