From 1d8615d038275193f742620f3e5c8f24baa15f52 Mon Sep 17 00:00:00 2001
From: tmastrom <thomas.mastromonaco@gmail.com>
Date: Tue, 13 Aug 2024 14:51:23 -0700
Subject: [PATCH 1/2] feat: add httpsnippet-lite convert method wrapper

---
 packages/snippetz/package.json         |  3 +-
 packages/snippetz/src/core/index.ts    |  2 +
 packages/snippetz/src/snippetz.test.ts | 22 ++++++++++
 packages/snippetz/src/snippetz.ts      | 11 +++++
 pnpm-lock.yaml                         | 61 ++++++++++++++++++++++++++
 5 files changed, 98 insertions(+), 1 deletion(-)

diff --git a/packages/snippetz/package.json b/packages/snippetz/package.json
index 7b057f5..efa2c5a 100644
--- a/packages/snippetz/package.json
+++ b/packages/snippetz/package.json
@@ -53,6 +53,7 @@
   },
   "devDependencies": {
     "@scalar/build-tooling": "^0.1.10",
-    "@types/har-format": "^1.2.15"
+    "@types/har-format": "^1.2.15",
+    "httpsnippet-lite": "^3.0.5"
   }
 }
diff --git a/packages/snippetz/src/core/index.ts b/packages/snippetz/src/core/index.ts
index a084a89..9323d42 100644
--- a/packages/snippetz/src/core/index.ts
+++ b/packages/snippetz/src/core/index.ts
@@ -3,3 +3,5 @@ export * from './utils/isKeyNeedsQuotes'
 export * from './utils/objectToString'
 
 export * from './types'
+
+export { availableTargets as allTargets, HTTPSnippet } from 'httpsnippet-lite'
diff --git a/packages/snippetz/src/snippetz.test.ts b/packages/snippetz/src/snippetz.test.ts
index 9e7c955..aeb8d7f 100644
--- a/packages/snippetz/src/snippetz.test.ts
+++ b/packages/snippetz/src/snippetz.test.ts
@@ -69,3 +69,25 @@ describe('hasPlugin', async () => {
     expect(result).toBe(false)
   })
 })
+
+describe('convert', async () => {
+  it('converts a request outside of the scalar types to snippet using httpsnippet-lite', async () => {
+    const request = {
+      method: 'GET',
+      url: 'http://mockbin.com/request',
+    }
+
+    const snippet = await snippetz().convert(request, 'python')
+
+    expect(snippet).toBe(`import http.client
+
+conn = http.client.HTTPConnection("mockbin.com")
+
+conn.request("GET", "/request")
+
+res = conn.getresponse()
+data = res.read()
+
+print(data.decode("utf-8"))`)
+  })
+})
diff --git a/packages/snippetz/src/snippetz.ts b/packages/snippetz/src/snippetz.ts
index 84414ee..6e67296 100644
--- a/packages/snippetz/src/snippetz.ts
+++ b/packages/snippetz/src/snippetz.ts
@@ -5,6 +5,12 @@ import { fetch as jsFetch } from './plugins/js/fetch'
 import { ofetch as jsOFetch } from './plugins/js/ofetch'
 import { ofetch as nodeOFetch } from './plugins/node/ofetch'
 
+import {
+  HTTPSnippet,
+  type TargetId as Target,
+  type HarRequest,
+} from 'httpsnippet-lite'
+
 export function snippetz() {
   const plugins = [undici, nodeFetch, jsFetch, jsOFetch, nodeOFetch]
 
@@ -51,5 +57,10 @@ export function snippetz() {
     hasPlugin(target: string, client: string) {
       return Boolean(this.findPlugin(target as TargetId, client as ClientId))
     },
+
+    async convert(request: any, target: string, client?: string) {
+      const snippet = new HTTPSnippet(request as HarRequest)
+      return (await snippet.convert(target as Target, client)) as string
+    },
   }
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2dd9fa9..7366370 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -69,6 +69,9 @@ importers:
       '@types/har-format':
         specifier: ^1.2.15
         version: 1.2.15
+      httpsnippet-lite:
+        specifier: ^3.0.5
+        version: 3.0.5
 
 packages:
 
@@ -958,6 +961,10 @@ packages:
     resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==}
     engines: {node: '>=14'}
 
+  formdata-node@4.4.1:
+    resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}
+    engines: {node: '>= 12.20'}
+
   fs-extra@7.0.1:
     resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
     engines: {node: '>=6 <7 || >=8'}
@@ -980,6 +987,9 @@ packages:
   get-func-name@2.0.2:
     resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
 
+  get-own-enumerable-property-symbols@3.0.2:
+    resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==}
+
   get-stream@8.0.1:
     resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
     engines: {node: '>=16'}
@@ -1019,6 +1029,10 @@ packages:
     resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
     hasBin: true
 
+  httpsnippet-lite@3.0.5:
+    resolution: {integrity: sha512-So4qTXY5iFj5XtFDwyz2PicUu+8NWrI8e8h+ZeZoVtMNcFQp4FFIntBHUE+JPUG6QQU8o1VHCy+X4ETRDwt9CA==}
+    engines: {node: '>=14.13'}
+
   human-id@1.0.2:
     resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==}
 
@@ -1065,10 +1079,18 @@ packages:
     resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
     engines: {node: '>=0.12.0'}
 
+  is-obj@1.0.1:
+    resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==}
+    engines: {node: '>=0.10.0'}
+
   is-plain-object@3.0.1:
     resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==}
     engines: {node: '>=0.10.0'}
 
+  is-regexp@1.0.0:
+    resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==}
+    engines: {node: '>=0.10.0'}
+
   is-stream@3.0.0:
     resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -1206,6 +1228,10 @@ packages:
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true
 
+  node-domexception@1.0.0:
+    resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
+    engines: {node: '>=10.5.0'}
+
   normalize-path@3.0.0:
     resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
     engines: {node: '>=0.10.0'}
@@ -1492,6 +1518,10 @@ packages:
     resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
     engines: {node: '>=12'}
 
+  stringify-object@3.3.0:
+    resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==}
+    engines: {node: '>=4'}
+
   strip-ansi@6.0.1:
     resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
     engines: {node: '>=8'}
@@ -1732,6 +1762,10 @@ packages:
       typescript:
         optional: true
 
+  web-streams-polyfill@4.0.0-beta.3:
+    resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
+    engines: {node: '>= 14'}
+
   which-pm@2.0.0:
     resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==}
     engines: {node: '>=8.15'}
@@ -2747,6 +2781,11 @@ snapshots:
       cross-spawn: 7.0.3
       signal-exit: 4.1.0
 
+  formdata-node@4.4.1:
+    dependencies:
+      node-domexception: 1.0.0
+      web-streams-polyfill: 4.0.0-beta.3
+
   fs-extra@7.0.1:
     dependencies:
       graceful-fs: 4.2.11
@@ -2768,6 +2807,8 @@ snapshots:
 
   get-func-name@2.0.2: {}
 
+  get-own-enumerable-property-symbols@3.0.2: {}
+
   get-stream@8.0.1: {}
 
   glob-parent@5.1.2:
@@ -2822,6 +2863,12 @@ snapshots:
 
   he@1.2.0: {}
 
+  httpsnippet-lite@3.0.5:
+    dependencies:
+      '@types/har-format': 1.2.15
+      formdata-node: 4.4.1
+      stringify-object: 3.3.0
+
   human-id@1.0.2: {}
 
   human-signals@5.0.0: {}
@@ -2857,8 +2904,12 @@ snapshots:
 
   is-number@7.0.0: {}
 
+  is-obj@1.0.1: {}
+
   is-plain-object@3.0.1: {}
 
+  is-regexp@1.0.0: {}
+
   is-stream@3.0.0: {}
 
   is-subdir@1.2.0:
@@ -2987,6 +3038,8 @@ snapshots:
 
   nanoid@3.3.7: {}
 
+  node-domexception@1.0.0: {}
+
   normalize-path@3.0.0: {}
 
   npm-run-path@5.3.0:
@@ -3250,6 +3303,12 @@ snapshots:
       emoji-regex: 9.2.2
       strip-ansi: 7.1.0
 
+  stringify-object@3.3.0:
+    dependencies:
+      get-own-enumerable-property-symbols: 3.0.2
+      is-obj: 1.0.1
+      is-regexp: 1.0.0
+
   strip-ansi@6.0.1:
     dependencies:
       ansi-regex: 5.0.1
@@ -3478,6 +3537,8 @@ snapshots:
     optionalDependencies:
       typescript: 5.5.4
 
+  web-streams-polyfill@4.0.0-beta.3: {}
+
   which-pm@2.0.0:
     dependencies:
       load-yaml-file: 0.2.0

From 82742c182c318700f7b23ae7bc46d94bd7211069 Mon Sep 17 00:00:00 2001
From: tmastrom <thomas.mastromonaco@gmail.com>
Date: Tue, 13 Aug 2024 17:40:45 -0700
Subject: [PATCH 2/2] feat: conditionally use snippetz or httpsnippet-lite in
 print method

---
 packages/snippetz/src/core/types.ts    | 25 ++++++++++++++++--
 packages/snippetz/src/snippetz.test.ts | 20 +++++++++++++-
 packages/snippetz/src/snippetz.ts      | 36 +++++++++++++++++++++-----
 3 files changed, 72 insertions(+), 9 deletions(-)

diff --git a/packages/snippetz/src/core/types.ts b/packages/snippetz/src/core/types.ts
index b105450..05a1066 100644
--- a/packages/snippetz/src/core/types.ts
+++ b/packages/snippetz/src/core/types.ts
@@ -2,13 +2,34 @@ export type { Request } from 'har-format'
 
 export type Source = {
   /** The language or environment. */
-  target: TargetId
+  target: ScalarTargetId
   /** The identifier of the client. */
   client: ClientId
   /** The actual source code. */
   code: string
 }
 
-export type TargetId = 'node' | 'js'
+export type ScalarTargetId = 'node' | 'js'
 
 export type ClientId = 'undici' | 'fetch' | 'ofetch'
+import { type TargetId as SnippetTargetId } from 'httpsnippet-lite'
+
+export type TargetId = ScalarTargetId | SnippetTargetId
+
+export const ScalarTargetTypes = ['node', 'js'] as const
+
+export const SnippetTargetTypes = [
+  'c',
+  'csharp',
+  'go',
+  'java',
+  'node',
+  'ocaml',
+  'php',
+  'python',
+  'ruby',
+  'shell',
+  'swift',
+] as const
+
+export const ScalarClientTypes = ['undici', 'fetch', 'ofetch'] as const
diff --git a/packages/snippetz/src/snippetz.test.ts b/packages/snippetz/src/snippetz.test.ts
index aeb8d7f..ba13bec 100644
--- a/packages/snippetz/src/snippetz.test.ts
+++ b/packages/snippetz/src/snippetz.test.ts
@@ -3,7 +3,7 @@ import { snippetz } from './snippetz'
 
 describe('snippetz', async () => {
   it('returns code for undici', async () => {
-    const snippet = snippetz().print('node', 'undici', {
+    const snippet = await snippetz().print('node', 'undici', {
       url: 'https://example.com',
     })
 
@@ -25,6 +25,24 @@ const { statusCode, body } = await request('https://example.com')`)
       'ofetch',
     ])
   })
+
+  it('returns code for python target', async () => {
+    const snippet = await snippetz().print('python', 'fetch', {
+      method: 'GET',
+      url: 'http://mockbin.com/request',
+    })
+
+    expect(snippet).toBe(`import http.client
+
+conn = http.client.HTTPConnection("mockbin.com")
+
+conn.request("GET", "/request")
+
+res = conn.getresponse()
+data = res.read()
+
+print(data.decode("utf-8"))`)
+  })
 })
 
 describe('plugins', async () => {
diff --git a/packages/snippetz/src/snippetz.ts b/packages/snippetz/src/snippetz.ts
index 6e67296..af951ce 100644
--- a/packages/snippetz/src/snippetz.ts
+++ b/packages/snippetz/src/snippetz.ts
@@ -1,4 +1,9 @@
-import type { TargetId, ClientId, Request } from './core'
+import type { TargetId, ClientId, Request, ScalarTargetId } from './core'
+import {
+  ScalarTargetTypes,
+  SnippetTargetTypes,
+  ScalarClientTypes,
+} from './core'
 import { undici } from './plugins/node/undici'
 import { fetch as nodeFetch } from './plugins/node/fetch'
 import { fetch as jsFetch } from './plugins/js/fetch'
@@ -7,7 +12,7 @@ import { ofetch as nodeOFetch } from './plugins/node/ofetch'
 
 import {
   HTTPSnippet,
-  type TargetId as Target,
+  type TargetId as SnippetTargetId,
   type HarRequest,
 } from 'httpsnippet-lite'
 
@@ -22,8 +27,22 @@ export function snippetz() {
         return plugin(request)
       }
     },
-    print(target: TargetId, client: ClientId, request: Partial<Request>) {
-      return this.get(target, client, request)?.code
+    async print(target: TargetId, client: string, request: Partial<Request>) {
+      // if target and client are valid scalar types
+      // use the plugin to convert the request
+      if (
+        ScalarTargetTypes.includes(target as ScalarTargetId) &&
+        ScalarClientTypes.includes(client as ClientId)
+      ) {
+        return this.get(target, client as ClientId, request)?.code
+      }
+
+      // else use httpsnippet-lite to convert the request
+      if (SnippetTargetTypes.includes(target as any)) {
+        // TODO: add client parameter
+        return await this.convert(request, target)
+      }
+      // else return error
     },
     targets() {
       return (
@@ -57,10 +76,15 @@ export function snippetz() {
     hasPlugin(target: string, client: string) {
       return Boolean(this.findPlugin(target as TargetId, client as ClientId))
     },
+    // TODO: add client parameter
 
-    async convert(request: any, target: string, client?: string) {
+    async convert(request: any, target: string) {
       const snippet = new HTTPSnippet(request as HarRequest)
-      return (await snippet.convert(target as Target, client)) as string
+
+      // https://www.npmjs.com/package/httpsnippet-lite#snippetconverttargetid-string-clientid-string-options-t
+      // snippet.convert(targetId: string, clientId?: string, options?: T)
+      // ERROR: convert method is looking for Client not ClientId
+      return (await snippet.convert(target as SnippetTargetId)) as string
     },
   }
 }