We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
PURLs for scoped NPM packages are invalid
The package URLs for scoped NPM packages (e.g. [`@hapi/hoek`](https://osv.dev/vulnerability/GHSA-22h7-7wwg-qmgg)) are invalid. Parsing them with any package URL library fails.

According to [the spec](https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#rules-for-each-purl-component), segments in the namespace (here: `@hapi`) must be percent-encdoded.

So

```
pkg:npm/@hapi/hoek
```

should be

```
pkg:npm/%40hapi/hoek
```

On the same note, the name segment must be percent-encoded, too. I haven't encountered a PURL in OSV where the package name contains characters that'd need encoding, but if this is done for the namespace, it should be considered for the name as well.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lib/osv/purl_helpers.py`
Content:
```
1 # Copyright 2022 Google LLC
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #      http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 """PURL conversion utilities."""
15 
16 PURL_ECOSYSTEMS = {
17     'crates.io': 'cargo',
18     'Hex': 'hex',
19     'Go': 'golang',
20     'Maven': 'maven',
21     'NuGet': 'nuget',
22     'npm': 'npm',
23     'Packagist': 'composer',
24     'OSS-Fuzz': 'generic',
25     'PyPI': 'pypi',
26     'RubyGems': 'gem',
27 }
28 
29 
30 def package_to_purl(ecosystem, package_name):
31   """Convert a ecosystem and package name to PURL."""
32   purl_type = PURL_ECOSYSTEMS.get(ecosystem)
33   if not purl_type:
34     return None
35 
36   if purl_type == 'maven':
37     # PURLs use / to separate the group ID and the artifact ID.
38     package_name = package_name.replace(':', '/', 1)
39 
40   return f'pkg:{purl_type}/{package_name}'
41
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lib/osv/purl_helpers.py b/lib/osv/purl_helpers.py
--- a/lib/osv/purl_helpers.py
+++ b/lib/osv/purl_helpers.py
@@ -13,6 +13,8 @@
 # limitations under the License.
 """PURL conversion utilities."""
 
+from urllib.parse import quote
+
 PURL_ECOSYSTEMS = {
     'crates.io': 'cargo',
     'Hex': 'hex',
@@ -27,6 +29,12 @@
 }
 
 
+def _url_encode(package_name):
+  """URL encode a PURL `namespace/name` or `name`."""
+  parts = package_name.split('/')
+  return '/'.join(quote(p) for p in parts)
+
+
 def package_to_purl(ecosystem, package_name):
   """Convert a ecosystem and package name to PURL."""
   purl_type = PURL_ECOSYSTEMS.get(ecosystem)
@@ -37,4 +45,4 @@
     # PURLs use / to separate the group ID and the artifact ID.
     package_name = package_name.replace(':', '/', 1)
 
-  return f'pkg:{purl_type}/{package_name}'
+  return f'pkg:{purl_type}/{_url_encode(package_name)}'

{"golden_diff": "diff --git a/lib/osv/purl_helpers.py b/lib/osv/purl_helpers.py\n--- a/lib/osv/purl_helpers.py\n+++ b/lib/osv/purl_helpers.py\n@@ -13,6 +13,8 @@\n # limitations under the License.\n \"\"\"PURL conversion utilities.\"\"\"\n \n+from urllib.parse import quote\n+\n PURL_ECOSYSTEMS = {\n     'crates.io': 'cargo',\n     'Hex': 'hex',\n@@ -27,6 +29,12 @@\n }\n \n \n+def _url_encode(package_name):\n+  \"\"\"URL encode a PURL `namespace/name` or `name`.\"\"\"\n+  parts = package_name.split('/')\n+  return '/'.join(quote(p) for p in parts)\n+\n+\n def package_to_purl(ecosystem, package_name):\n   \"\"\"Convert a ecosystem and package name to PURL.\"\"\"\n   purl_type = PURL_ECOSYSTEMS.get(ecosystem)\n@@ -37,4 +45,4 @@\n     # PURLs use / to separate the group ID and the artifact ID.\n     package_name = package_name.replace(':', '/', 1)\n \n-  return f'pkg:{purl_type}/{package_name}'\n+  return f'pkg:{purl_type}/{_url_encode(package_name)}'\n", "issue": "PURLs for scoped NPM packages are invalid\nThe package URLs for scoped NPM packages (e.g. [`@hapi/hoek`](https://osv.dev/vulnerability/GHSA-22h7-7wwg-qmgg)) are invalid. Parsing them with any package URL library fails.\r\n\r\nAccording to [the spec](https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#rules-for-each-purl-component), segments in the namespace (here: `@hapi`) must be percent-encdoded.\r\n\r\nSo\r\n\r\n```\r\npkg:npm/@hapi/hoek\r\n```\r\n\r\nshould be\r\n\r\n```\r\npkg:npm/%40hapi/hoek\r\n```\r\n\r\nOn the same note, the name segment must be percent-encoded, too. I haven't encountered a PURL in OSV where the package name contains characters that'd need encoding, but if this is done for the namespace, it should be considered for the name as well.\n", "before_files": [{"content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"PURL conversion utilities.\"\"\"\n\nPURL_ECOSYSTEMS = {\n    'crates.io': 'cargo',\n    'Hex': 'hex',\n    'Go': 'golang',\n    'Maven': 'maven',\n    'NuGet': 'nuget',\n    'npm': 'npm',\n    'Packagist': 'composer',\n    'OSS-Fuzz': 'generic',\n    'PyPI': 'pypi',\n    'RubyGems': 'gem',\n}\n\n\ndef package_to_purl(ecosystem, package_name):\n  \"\"\"Convert a ecosystem and package name to PURL.\"\"\"\n  purl_type = PURL_ECOSYSTEMS.get(ecosystem)\n  if not purl_type:\n    return None\n\n  if purl_type == 'maven':\n    # PURLs use / to separate the group ID and the artifact ID.\n    package_name = package_name.replace(':', '/', 1)\n\n  return f'pkg:{purl_type}/{package_name}'\n", "path": "lib/osv/purl_helpers.py"}], "after_files": [{"content": "# Copyright 2022 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"PURL conversion utilities.\"\"\"\n\nfrom urllib.parse import quote\n\nPURL_ECOSYSTEMS = {\n    'crates.io': 'cargo',\n    'Hex': 'hex',\n    'Go': 'golang',\n    'Maven': 'maven',\n    'NuGet': 'nuget',\n    'npm': 'npm',\n    'Packagist': 'composer',\n    'OSS-Fuzz': 'generic',\n    'PyPI': 'pypi',\n    'RubyGems': 'gem',\n}\n\n\ndef _url_encode(package_name):\n  \"\"\"URL encode a PURL `namespace/name` or `name`.\"\"\"\n  parts = package_name.split('/')\n  return '/'.join(quote(p) for p in parts)\n\n\ndef package_to_purl(ecosystem, package_name):\n  \"\"\"Convert a ecosystem and package name to PURL.\"\"\"\n  purl_type = PURL_ECOSYSTEMS.get(ecosystem)\n  if not purl_type:\n    return None\n\n  if purl_type == 'maven':\n    # PURLs use / to separate the group ID and the artifact ID.\n    package_name = package_name.replace(':', '/', 1)\n\n  return f'pkg:{purl_type}/{_url_encode(package_name)}'\n", "path": "lib/osv/purl_helpers.py"}]}