From 82ed9f33486a0addd2c9d8e400a203806e8087ff Mon Sep 17 00:00:00 2001 From: Taylor Reece Date: Thu, 20 Jan 2022 09:48:04 -0600 Subject: [PATCH] Document and clean up SOAP client types and utility functions (#41) * - Document SOAP client types and utility functions - Clean up some function overloading - Ensure SOAP components can be tested on Windows systems * Docs PR feedback. --- package.json | 2 +- src/clients/soap/types.ts | 55 ++++++++++++++++----- src/clients/soap/utils.ts | 101 +++++++++++++++++++++++++++----------- 3 files changed, 117 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index e806346e..47b5bfaa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@prismatic-io/spectral", - "version": "5.2.0", + "version": "5.2.1", "description": "Utility library for building Prismatic components", "keywords": [ "prismatic" diff --git a/src/clients/soap/types.ts b/src/clients/soap/types.ts index faad5f5e..7db6701f 100644 --- a/src/clients/soap/types.ts +++ b/src/clients/soap/types.ts @@ -1,13 +1,23 @@ /* eslint-disable @typescript-eslint/no-explicit-any*/ import { Connection } from "../../index"; -import { Client, IMTOMAttachments } from "soap"; +import { IMTOMAttachments } from "soap"; + +/** + * SOAPConnection takes standard connection fields, and adds an optional `wsdlDefinitionUrl` field. + */ export interface SOAPConnection extends Connection { fields: { [key: string]: unknown; + // The URL where a WSDL can be found wsdlDefinitionUrl?: string; }; } +/** + * This function determines if the object presented is a SOAP connection with a `wsdlDefinitionUrl` field. + * @param connection The connection to test + * @returns This function returns true if the connection is a SOAPConnection, and false otherwise. + */ export const isSOAPConnection = ( connection: unknown ): connection is SOAPConnection => { @@ -16,32 +26,45 @@ export const isSOAPConnection = ( return false; }; -export interface GetClient { - (connection: SOAPConnection): Promise; - (wsdlDefinition: string): Promise; -} +/** + * SOAP requests return a 4-tuple or 5-tuple with the response first, the XML response second, headers third, and the XML request fourth, and optional message attachments fifth. + */ +export declare type SOAPResponse = [any, any, any, any, IMTOMAttachments?]; + +/** + * Overload the `soapRequest` function to take a variety of types of arguments. + */ export interface SOAPRequest { - (params: RequestParams): Promise<[any, any, any, any, IMTOMAttachments?]>; - (params: RequestParams, methodParams: Record): Promise< - [any, any, any, any, IMTOMAttachments?] - >; - (params: RequestParams, methodParams: undefined): Promise< - [any, any, any, any, IMTOMAttachments?] - >; + (params: RequestParams): Promise; + ( + params: RequestParams, + methodParams: Record + ): Promise; + (params: RequestParams, methodParams: undefined): Promise; } + export interface RequestParams { + // Either a SOAPConnection or WSDL definition wsdlParam: SOAPConnection | string; + // The SOAP method to invoke method: string; + // You can override the SOAP client `endpointURL` or `soapHeaders` overrides?: ClientOverrides; + // Log debug information about the SOAP request debug?: boolean; } + export interface ClientOverrides { + // Override endpoint URL endpointURL?: string; + // An array of [SOAP Headers](https://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383497) soapHeaders?: unknown[]; } + export interface HeaderPayload { [x: string]: any; } + export interface GenerateHeader { ( wsdlParam: SOAPConnection | string, @@ -54,15 +77,22 @@ export interface GenerateHeader { headerOptions: { namespace: string; xmlns: string } ): Promise; } + export interface BasicAuthConnection extends Connection { fields: { [key: string]: unknown; username: unknown; password: unknown; + // A SOAP login method, defined in the WSDL loginMethod: unknown; }; } +/** + * This function determines if the object presented is a Basic Auth connection with `username`, `password`, and `loginMethod` fields. + * @param connection The connection to test + * @returns This function returns true if the connection is a SOAPConnection, and false otherwise. + */ export const isBasicAuthConnection = ( connection: Connection ): connection is BasicAuthConnection => { @@ -71,6 +101,7 @@ export const isBasicAuthConnection = ( "username" in fields && "password" in fields && "loginMethod" in fields ); }; + export interface SOAPAuth { (connection: BasicAuthConnection, wsdlDefinition: string): Promise; (connection: BasicAuthConnection & SOAPConnection): Promise; diff --git a/src/clients/soap/utils.ts b/src/clients/soap/utils.ts index 3d36ac95..b7068013 100644 --- a/src/clients/soap/utils.ts +++ b/src/clients/soap/utils.ts @@ -1,12 +1,9 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { ClientOverrides, HeaderPayload, GenerateHeader, - GetClient, RequestParams, SOAPAuth, SOAPConnection, @@ -20,20 +17,32 @@ import { createClientAsync, Client, SoapMethodAsync } from "soap"; import axios from "axios"; import { writeFile, rm } from "fs/promises"; import { v4 as uuid } from "uuid"; +import os from "os"; +import path from "path"; +/** + * Optionally log out SOAP requests and responses for debugging purposes + * + * @param client A SOAP client that generates requests and responses + */ const debugRequest = (client: Client) => { console.debug(client.lastRequest); console.debug(client.lastResponse); }; -export const describeWSDL: DescribeWSDL = async ( + +/** + * This function takes either the URL of a WSDL or the XML defining a WSDL, and returns an object describing the methods and attributes defined in the WSDL. + * + * @param wsdlParam Either the URL where a WSDL is stored, or the XML defining a WSDL. + * @returns An object containing the methods and attributes defined in a WSDL + */ +const describeWSDL: DescribeWSDL = async ( wsdlParam: unknown ): Promise => { - let client: Client; - if (isSOAPConnection(wsdlParam)) { - client = await getSOAPClient(wsdlParam); - } else { - client = await getSOAPClient(util.types.toString(wsdlParam)); - } + const client = await getSOAPClient( + isSOAPConnection(wsdlParam) ? wsdlParam : util.types.toString(wsdlParam) + ); + try { const result = client.describe(); return result; @@ -41,6 +50,12 @@ export const describeWSDL: DescribeWSDL = async ( throw new Error("Unable to parse WSDL Services due to circular references"); } }; + +/** + * Fetch a WSDL from a URL + * @param wsdlDefinitionURL The URL where the WSDL is stored + * @returns The WSDL's raw XML + */ const getWSDL = async (wsdlDefinitionURL: string): Promise => { const httpClient = axios.create({ baseURL: wsdlDefinitionURL, @@ -50,18 +65,27 @@ const getWSDL = async (wsdlDefinitionURL: string): Promise => { return util.types.toString(data); }; -const getSOAPClient: GetClient = async (wsdlParam: unknown) => { +/** + * Create a SOAP client given a WSDL or SOAPConnection + * @param wsdlParam a SOAPConnection or XML defining a WSDL + * @returns An HTTP client configured to query a SOAP-based API + */ +const getSOAPClient = async < + T extends string | SOAPConnection = string | SOAPConnection +>( + wsdlParam: T +): Promise => { if (typeof wsdlParam === "string") { const wsdl = util.types.toString(wsdlParam); - const filePath = `/tmp/${uuid()}.wsdl`; + const filePath = path.join(os.tmpdir(), `${uuid()}.wsdl`); await writeFile(filePath, wsdl); const client = await createClientAsync(filePath); await rm(filePath); return client; - } else if (isSOAPConnection(wsdlParam as SOAPConnection)) { + } else if (isSOAPConnection(wsdlParam)) { const { fields: { wsdlDefinitionURL }, - } = wsdlParam as SOAPConnection; + } = wsdlParam; if ( !wsdlDefinitionURL || !util.types.isUrl(util.types.toString(wsdlDefinitionURL)) @@ -81,6 +105,11 @@ const getSOAPClient: GetClient = async (wsdlParam: unknown) => { } }; +/** + * Override some HTTP client defaults + * @param client The client to override + * @param overrides An endpoint URL or SOAP headers to override + */ const overrideClientDefaults = ( client: Client, overrides: ClientOverrides @@ -96,16 +125,19 @@ const overrideClientDefaults = ( } }; +/** + * Make a request to a SOAP-based API + * @param param0 + * @param methodParams Parameters to pass to the specified SOAP method + * @returns The results from the SOAP request, including the full XML of the request and response + */ const soapRequest: SOAPRequest = async ( { wsdlParam, method, overrides, debug }: RequestParams, methodParams?: unknown ) => { - let client: Client; - if (isSOAPConnection(wsdlParam)) { - client = await getSOAPClient(wsdlParam); - } else { - client = await getSOAPClient(wsdlParam); - } + const client = await getSOAPClient( + isSOAPConnection(wsdlParam) ? wsdlParam : util.types.toString(wsdlParam) + ); if (overrides) { overrideClientDefaults(client, overrides); } @@ -126,21 +158,28 @@ const soapRequest: SOAPRequest = async ( if (util.types.isBool(debug) && debug) { debugRequest(client); } + console.warn( + "Please verify that the method you specified exists in the WSDL specification." + ); throw error; } }; +/** + * Create a SOAP header + * @param wsdlParam A SOAPConnection or XML definition of a WSDL + * @param headerPayload The contents of a header XML node + * @param headerOptions Attributes for a header XML node, like namespace or xmlns + * @returns + */ const generateHeader: GenerateHeader = async ( wsdlParam, headerPayload: HeaderPayload, headerOptions ) => { - let client: Client; - if (isSOAPConnection(wsdlParam)) { - client = await getSOAPClient(wsdlParam); - } else { - client = await getSOAPClient(wsdlParam); - } + const client = await getSOAPClient( + isSOAPConnection(wsdlParam) ? wsdlParam : util.types.toString(wsdlParam) + ); let options: string[] = []; if (headerOptions) { @@ -150,6 +189,12 @@ const generateHeader: GenerateHeader = async ( return client.getSoapHeaders()[index]; }; +/** + * Fetch authentication information for a SOAPConnection + * @param connection The SOAPConnection + * @param wsdlDefinition The XML WSDL definition + * @returns + */ const getSOAPAuth: SOAPAuth = async ( connection: Connection, wsdlDefinition?: string @@ -187,11 +232,11 @@ const getSOAPAuth: SOAPAuth = async ( }; export default { + describeWSDL, generateHeader, - getSOAPClient, getSOAPAuth, + getSOAPClient, getWSDL, overrideClientDefaults, soapRequest, - describeWSDL, };