Skip to content

Commit

Permalink
Document and clean up SOAP client types and utility functions (#41)
Browse files Browse the repository at this point in the history
* - Document SOAP client types and utility functions
- Clean up some function overloading
- Ensure SOAP components can be tested on Windows systems

* Docs PR feedback.
  • Loading branch information
taylorreece authored Jan 20, 2022
1 parent 7c9ffcd commit 82ed9f3
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 41 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
55 changes: 43 additions & 12 deletions src/clients/soap/types.ts
Original file line number Diff line number Diff line change
@@ -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 => {
Expand All @@ -16,32 +26,45 @@ export const isSOAPConnection = (
return false;
};

export interface GetClient {
(connection: SOAPConnection): Promise<Client>;
(wsdlDefinition: string): Promise<Client>;
}
/**
* 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<string, unknown>): Promise<
[any, any, any, any, IMTOMAttachments?]
>;
(params: RequestParams, methodParams: undefined): Promise<
[any, any, any, any, IMTOMAttachments?]
>;
(params: RequestParams): Promise<SOAPResponse>;
(
params: RequestParams,
methodParams: Record<string, unknown>
): Promise<SOAPResponse>;
(params: RequestParams, methodParams: undefined): Promise<SOAPResponse>;
}

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,
Expand All @@ -54,15 +77,22 @@ export interface GenerateHeader {
headerOptions: { namespace: string; xmlns: string }
): Promise<string>;
}

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 => {
Expand All @@ -71,6 +101,7 @@ export const isBasicAuthConnection = (
"username" in fields && "password" in fields && "loginMethod" in fields
);
};

export interface SOAPAuth {
(connection: BasicAuthConnection, wsdlDefinition: string): Promise<any>;
(connection: BasicAuthConnection & SOAPConnection): Promise<any>;
Expand Down
101 changes: 73 additions & 28 deletions src/clients/soap/utils.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -20,27 +17,45 @@ 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<string> => {
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;
} catch (error) {
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<string> => {
const httpClient = axios.create({
baseURL: wsdlDefinitionURL,
Expand All @@ -50,18 +65,27 @@ const getWSDL = async (wsdlDefinitionURL: string): Promise<string> => {
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<Client> => {
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))
Expand All @@ -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
Expand All @@ -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);
}
Expand All @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -187,11 +232,11 @@ const getSOAPAuth: SOAPAuth = async (
};

export default {
describeWSDL,
generateHeader,
getSOAPClient,
getSOAPAuth,
getSOAPClient,
getWSDL,
overrideClientDefaults,
soapRequest,
describeWSDL,
};

0 comments on commit 82ed9f3

Please sign in to comment.