Repository: axios/axios
Pull request #6011 (2023-10-18 21:18:33)

fix(dns): fixed lookup function decorator to work properly in node 20.x;


Patch:
diff --git a/index.d.cts b/index.d.cts
index 6f07d1a..77214a9 100644
--- a/index.d.cts
+++ b/index.d.cts
@@ -361,6 +361,15 @@ declare namespace axios {
 
   type AxiosAdapterConfig = AxiosAdapter | AxiosAdapterName;
 
+  type AddressFamily = 4 | 6 | undefined;
+
+  interface LookupAddressEntry {
+    address: string;
+    family?: AddressFamily;
+  }
+
+  type LookupAddress = string | LookupAddressEntry;
+
   interface AxiosRequestConfig<D = any> {
     url?: string;
     method?: Method | string;
@@ -402,9 +411,9 @@ declare namespace axios {
       FormData?: new (...args: any[]) => object;
     };
     formSerializer?: FormSerializerOptions;
-    family?: 4 | 6 | undefined;
-    lookup?: ((hostname: string, options: object, cb: (err: Error | null, address: string, family: number) => void) => void) |
-        ((hostname: string, options: object) => Promise<[address: string, family: number] | string>);
+    family?: AddressFamily;
+    lookup?: ((hostname: string, options: object, cb: (err: Error | null, address: LookupAddress | LookupAddress[], family?: AddressFamily) => void) => void) |
+        ((hostname: string, options: object) => Promise<[address: LookupAddressEntry | LookupAddressEntry[], family?: AddressFamily] | LookupAddress>);
   }
 
   // Alias
diff --git a/index.d.ts b/index.d.ts
index 79cdd80..3878613 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -302,6 +302,15 @@ type AxiosAdapterName = 'xhr' | 'http' | string;
 
 type AxiosAdapterConfig = AxiosAdapter | AxiosAdapterName;
 
+export type AddressFamily = 4 | 6 | undefined;
+
+export interface LookupAddressEntry {
+  address: string;
+  family?: AddressFamily;
+}
+
+export type LookupAddress = string | LookupAddressEntry;
+
 export interface AxiosRequestConfig<D = any> {
   url?: string;
   method?: Method | string;
@@ -343,9 +352,9 @@ export interface AxiosRequestConfig<D = any> {
     FormData?: new (...args: any[]) => object;
   };
   formSerializer?: FormSerializerOptions;
-  family?: 4 | 6 | undefined;
-  lookup?: ((hostname: string, options: object, cb: (err: Error | null, address: string, family: number) => void) => void) |
-      ((hostname: string, options: object) => Promise<[address: string, family: number] | string>);
+  family?: AddressFamily;
+  lookup?: ((hostname: string, options: object, cb: (err: Error | null, address: LookupAddress | LookupAddress[], family?: AddressFamily) => void) => void) |
+      ((hostname: string, options: object) => Promise<[address: LookupAddressEntry | LookupAddressEntry[], family?: AddressFamily] | LookupAddress>);
 }
 
 // Alias
diff --git a/lib/adapters/http.js b/lib/adapters/http.js
index 201252f..3f289c1 100755
--- a/lib/adapters/http.js
+++ b/lib/adapters/http.js
@@ -144,6 +144,18 @@ const wrapAsync = (asyncExecutor) => {
   })
 };
 
+const resolveFamily = ({address, family}) => {
+  if (!utils.isString(address)) {
+    throw TypeError('address must be a string');
+  }
+  return ({
+    address,
+    family: family || (address.indexOf('.') < 0 ? 6 : 4)
+  });
+}
+
+const buildAddressEntry = (address, family) => resolveFamily(utils.isObject(address) ? address : {address, family});
+
 /*eslint consistent-return:0*/
 export default isHttpAdapterSupported && function httpAdapter(config) {
   return wrapAsync(async function dispatchHttpRequest(resolve, reject, onDone) {
@@ -154,15 +166,16 @@ export default isHttpAdapterSupported && function httpAdapter(config) {
     let rejected = false;
     let req;
 
-    if (lookup && utils.isAsyncFn(lookup)) {
-      lookup = callbackify(lookup, (entry) => {
-        if(utils.isString(entry)) {
-          entry = [entry, entry.indexOf('.') < 0 ? 6 : 4]
-        } else if (!utils.isArray(entry)) {
-          throw new TypeError('lookup async function must return an array [ip: string, family: number]]')
-        }
-        return entry;
-      })
+    if (lookup) {
+      const _lookup = callbackify(lookup, (value) => utils.isArray(value) ? value : [value]);
+      // hotfix to support opt.all option which is required for node 20.x
+      lookup = (hostname, opt, cb) => {
+        _lookup(hostname, opt, (err, arg0, arg1) => {
+          const addresses = utils.isArray(arg0) ? arg0.map(addr => buildAddressEntry(addr)) : [buildAddressEntry(arg0, arg1)];
+
+          opt.all ? cb(err, addresses) : cb(err, addresses[0].address, addresses[0].family);
+        });
+      }
     }
 
     // temporary internal emitter until the AxiosRequest class will be implemented


Test patch:
diff --git a/test/module/typings/esm/index.ts b/test/module/typings/esm/index.ts
index b7c4fbc..811e150 100644
--- a/test/module/typings/esm/index.ts
+++ b/test/module/typings/esm/index.ts
@@ -20,7 +20,7 @@ import axios, {
   all,
   isCancel,
   isAxiosError,
-  spread
+  spread, AddressFamily
 } from 'axios';
 
 const config: AxiosRequestConfig = {
@@ -574,7 +574,7 @@ axios.get('/user', {
 
 {
   // getAdapter
-  
+
   getAdapter(axios.create().defaults.adapter);
   getAdapter(undefined);
   getAdapter([]);
@@ -658,7 +658,7 @@ for (const [header, value] of headers) {
 
 // lookup
 axios.get('/user', {
-  lookup: (hostname: string, opt: object, cb: (err: Error | null, address: string, family: number) => void) => {
+  lookup: (hostname: string, opt: object, cb: (err: Error | null, address: string, family: AddressFamily) => void) => {
     cb(null, '127.0.0.1', 4);
   }
 });
diff --git a/test/unit/adapters/http.js b/test/unit/adapters/http.js
index 6e07c77..51fb9d7 100644
--- a/test/unit/adapters/http.js
+++ b/test/unit/adapters/http.js
@@ -2122,7 +2122,7 @@ describe('supports http with nodejs', function () {
   });
 
   describe('DNS', function() {
-    it('should support custom DNS lookup function', async function () {
+    it('should support a custom DNS lookup function', async function () {
       server = await startHTTPServer(SERVER_HANDLER_STREAM_ECHO);
 
       const payload = 'test';
@@ -2141,7 +2141,26 @@ describe('supports http with nodejs', function () {
       assert.strictEqual(data, payload);
     });
 
-    it('should support custom DNS lookup function (async)', async function () {
+    it('should support a custom DNS lookup function with address entry passing', async function () {
+      server = await startHTTPServer(SERVER_HANDLER_STREAM_ECHO);
+
+      const payload = 'test';
+
+      let isCalled = false;
+
+      const {data} = await axios.post(`http://fake-name.axios:4444`, payload,{
+        lookup: (hostname, opt, cb) =>  {
+          isCalled = true;
+          cb(null, {address: '127.0.0.1', family: 4});
+        }
+      });
+
+      assert.ok(isCalled);
+
+      assert.strictEqual(data, payload);
+    });
+
+    it('should support a custom DNS lookup function (async)', async function () {
       server = await startHTTPServer(SERVER_HANDLER_STREAM_ECHO);
 
       const payload = 'test';
@@ -2160,7 +2179,26 @@ describe('supports http with nodejs', function () {
       assert.strictEqual(data, payload);
     });
 
-    it('should support custom DNS lookup function that returns only IP address (async)', async function () {
+    it('should support a custom DNS lookup function with address entry (async)', async function () {
+      server = await startHTTPServer(SERVER_HANDLER_STREAM_ECHO);
+
+      const payload = 'test';
+
+      let isCalled = false;
+
+      const {data} = await axios.post(`http://fake-name.axios:4444`, payload,{
+        lookup: async (hostname, opt) =>  {
+          isCalled = true;
+          return {address: '127.0.0.1', family: 4};
+        }
+      });
+
+      assert.ok(isCalled);
+
+      assert.strictEqual(data, payload);
+    });
+
+    it('should support a custom DNS lookup function that returns only IP address (async)', async function () {
       server = await startHTTPServer(SERVER_HANDLER_STREAM_ECHO);
 
       const payload = 'test';