Skip to content

Commit

Permalink
enhance(soap): rename sdl to source in config
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Oct 31, 2022
1 parent 2d26484 commit c55e683
Show file tree
Hide file tree
Showing 12 changed files with 72 additions and 353 deletions.
7 changes: 7 additions & 0 deletions .changeset/sharp-ties-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@graphql-mesh/soap': minor
'@omnigraph/soap': minor
'@graphql-mesh/types': patch
---

_BREAKING_ - `wsdl` renamed to `source` so you should update your configuration file
2 changes: 1 addition & 1 deletion examples/soap-country-info/.meshrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ sources:
- name: CountryInfo
handler:
soap:
wsdl: http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL
source: http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL

plugins:
- snapshot:
Expand Down
2 changes: 1 addition & 1 deletion examples/soap-demo/.meshrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ sources:
- name: SOAPDemo
handler:
soap:
wsdl: https://www.crcind.com/csp/samples/SOAP.Demo.cls?WSDL
source: https://www.crcind.com/csp/samples/SOAP.Demo.cls?WSDL
selectQueryOperationsAuto: true

documents:
Expand Down
2 changes: 1 addition & 1 deletion examples/soap-netsuite/.meshrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ sources:
- name: Netsuite
handler:
soap:
wsdl: https://webservices.netsuite.com/wsdl/v2021_1_0/netsuite.wsdl
source: https://webservices.netsuite.com/wsdl/v2021_1_0/netsuite.wsdl
53 changes: 34 additions & 19 deletions packages/handlers/soap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,48 +10,63 @@ import {
import { PredefinedProxyOptions, StoreProxy } from '@graphql-mesh/store';
import { createExecutorFromSchemaAST, SOAPLoader } from '@omnigraph/soap';
import { readFileOrUrl } from '@graphql-mesh/utils';
import { printSchemaWithDirectives } from '@graphql-tools/utils';
import { buildASTSchema, parse } from 'graphql';
import { GraphQLSchema } from 'graphql';
import { Executor } from '@graphql-tools/utils';

export default class SoapHandler implements MeshHandler {
private config: YamlConfig.SoapHandler;
private soapSDLProxy: StoreProxy<string>;
private soapSDLProxy: StoreProxy<GraphQLSchema>;
private baseDir: string;
private importFn: ImportFn;
private logger: Logger;

constructor({ config, store, baseDir, importFn, logger }: MeshHandlerOptions<YamlConfig.SoapHandler>) {
this.config = config;
this.soapSDLProxy = store.proxy('schemaWithAnnotations.graphql', PredefinedProxyOptions.StringWithoutValidation);
this.soapSDLProxy = store.proxy('schemaWithAnnotations.graphql', PredefinedProxyOptions.GraphQLSchemaWithDiffing);
this.baseDir = baseDir;
this.importFn = importFn;
this.logger = logger;
}

async getMeshSource({ fetchFn }: GetMeshSourcePayload): Promise<MeshSource> {
const soapSDL = await this.soapSDLProxy.getWithSet(async () => {
const soapLoader = new SOAPLoader({
fetch: fetchFn,
});
const location = this.config.wsdl;
const wsdl = await readFileOrUrl<string>(location, {
let schema: GraphQLSchema;

if (this.config.source.endsWith('.graphql')) {
schema = await readFileOrUrl(this.config.source, {
allowUnknownExtensions: true,
cwd: this.baseDir,
fetch: fetchFn,
importFn: this.importFn,
logger: this.logger,
});
const object = await soapLoader.loadWSDL(wsdl);
soapLoader.loadedLocations.set(location, object);
const schema = soapLoader.buildSchema();
return printSchemaWithDirectives(schema);
});
const schemaAST = parse(soapSDL);
const executor = createExecutorFromSchemaAST(schemaAST, fetchFn);
const schema = buildASTSchema(schemaAST);
} else {
schema = await this.soapSDLProxy.getWithSet(async () => {
const soapLoader = new SOAPLoader({
fetch: fetchFn,
});
const wsdlLocation = this.config.source;
const wsdl = await readFileOrUrl<string>(wsdlLocation, {
allowUnknownExtensions: true,
cwd: this.baseDir,
fetch: fetchFn,
importFn: this.importFn,
logger: this.logger,
});
const object = await soapLoader.loadWSDL(wsdl);
soapLoader.loadedLocations.set(wsdlLocation, object);
return soapLoader.buildSchema();
});
}
// Create executor lazily for faster startup
let executor: Executor;
return {
schema,
executor,
executor(...args) {
if (!executor) {
executor = createExecutorFromSchemaAST(schema, fetchFn);
}
return executor(...args);
},
};
}
}
90 changes: 2 additions & 88 deletions packages/handlers/soap/yaml-config.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,9 @@ extend type Handler {

type SoapHandler @md {
"""
A url to your WSDL
A url to your WSDL or generated SDL with annotations
"""
wsdl: String!
"""
Basic Authentication Configuration
Including username and password fields
"""
basicAuth: SoapSecurityBasicAuthConfig
"""
SSL Certificate Based Authentication Configuration
Including public key, private key and password fields
"""
securityCert: SoapSecurityCertificateConfig
source: String!
"""
JSON object representing the Headers to add to the runtime of the API calls only for schema introspection
You can also provide `.js` or `.ts` file path that exports schemaHeaders as an object
Expand All @@ -29,80 +19,4 @@ type SoapHandler @md {
JSON object representing the Headers to add to the runtime of the API calls only for operation during runtime
"""
operationHeaders: JSON
"""
If true, the ports defined in the WSDL will be represented as GraphQL-Type objects in the schema.
The fields of the object will be the operations of the port.
Most soap-endpoints only define one port; so including it in the schema will just be inconvenient.
But if there are multiple ports with operations of the same name, you should set this option to true.
Otherwise, only one of the identical-named operations will be callable.
default: false
"""
includePorts: Boolean
"""
If true, the services defined in the WSDL will be represented as GraphQL-Type objects in the schema.
The fields of the object will be the ports of the service (or the operation, dependent on 'includePorts').
Most soap-endpoints only define one service; so including it in the schema will just be inconvenient.
But if there are multiple services with operations of the same name, you should set this option to true.
Otherwise, only one of the identical-named operations will be callable.
default: false
"""
includeServices: Boolean
"""
Allows to explicitly override the default operation (Query or Mutation) for any SOAP operation
"""
selectQueryOrMutationField: [SoapSelectQueryOrMutationFieldConfig]
"""
Automatically put operations starts with `query` or `get` into the Query type
"""
selectQueryOperationsAuto: Boolean
}

type SoapSelectQueryOrMutationFieldConfig {
service: String!
port: String!
operation: String!
type: SoapSelectQueryOrMutation!
}

enum SoapSelectQueryOrMutation {
query
mutation
}

type SoapSecurityBasicAuthConfig {
"""
Username for Basic Authentication
"""
username: String!
"""
Password for Basic Authentication
"""
password: String!
}

type SoapSecurityCertificateConfig {
"""
Your public key
"""
publicKey: String
"""
Your private key
"""
privateKey: String
"""
Password
"""
password: String
"""
Path to the file or URL contains your public key
"""
publicKeyPath: String
"""
Path to the file or URL contains your private key
"""
privateKeyPath: String
"""
Path to the file or URL contains your password
"""
passwordPath: String
}
43 changes: 17 additions & 26 deletions packages/loaders/soap/src/executor.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import {
buildASTSchema,
DocumentNode,
execute,
GraphQLFieldResolver,
GraphQLOutputType,
GraphQLResolveInfo,
GraphQLSchema,
isListType,
isNonNullType,
visit,
} from 'graphql';
import { parse as parseXML, j2xParser as JSONToXMLConverter } from 'fast-xml-parser';
import { MeshFetch } from '@graphql-mesh/types';
import { PARSE_XML_OPTIONS, SoapAnnotations } from './utils';
import { Executor } from '@graphql-tools/utils';
import { Executor, getDirective, getRootTypes } from '@graphql-tools/utils';

function isOriginallyListType(type: GraphQLOutputType): boolean {
if (isNonNullType(type)) {
Expand Down Expand Up @@ -110,33 +108,26 @@ function createRootValueMethod(soapAnnotations: SoapAnnotations, fetchFn: MeshFe
};
}

function createRootValue(schemaAST: DocumentNode, fetchFn: MeshFetch) {
function createRootValue(schema: GraphQLSchema, fetchFn: MeshFetch) {
const rootValue: Record<string, RootValueMethod> = {};
visit(schemaAST, {
FieldDefinition(node) {
const soapAnnotationsDirective = node.directives.find(directive => directive.name.value === 'soap');
if (soapAnnotationsDirective) {
const soapAnnotations: SoapAnnotations = {
elementName: '',
bindingNamespace: '',
baseUrl: '',
};
for (const arg of soapAnnotationsDirective.arguments) {
if ('value' in arg.value) {
soapAnnotations[arg.name.value] = arg.value.value;
}
}
rootValue[node.name.value] = createRootValueMethod(soapAnnotations, fetchFn);
}
},
});
const rootTypes = getRootTypes(schema);
for (const rootType of rootTypes) {
const rootFieldMap = rootType.getFields();
for (const fieldName in rootFieldMap) {
const annotations = getDirective(schema, rootFieldMap[fieldName], 'soap');
const soapAnnotations: SoapAnnotations = Object.assign({}, ...annotations);
rootValue[fieldName] = createRootValueMethod(soapAnnotations, fetchFn);
}
}
return rootValue;
}

export function createExecutorFromSchemaAST(schemaAST: DocumentNode, fetchFn: MeshFetch) {
const rootValue = createRootValue(schemaAST, fetchFn);
const schema = buildASTSchema(schemaAST);
export function createExecutorFromSchemaAST(schema: GraphQLSchema, fetchFn: MeshFetch) {
let rootValue: Record<string, RootValueMethod>;
return function soapExecutor({ document, variables, context }) {
if (!rootValue) {
rootValue = createRootValue(schema, fetchFn);
}
return execute({
schema,
document,
Expand Down
Loading

0 comments on commit c55e683

Please sign in to comment.