Skip to content

Commit

Permalink
Merge pull request #22 from AEB-labs/fix-namespacing-issues
Browse files Browse the repository at this point in the history
fix(schema-generation): improve support for namespaces and empty comp…
  • Loading branch information
henkesn authored Jan 28, 2025
2 parents 4d7aea2 + ac18151 commit ec48ebd
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 65 deletions.
132 changes: 81 additions & 51 deletions src/node-soap/node-soap-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import {
SoapSimpleType,
SoapType,
} from '../soap2graphql/soap-endpoint';
import { inspect } from 'util';
import { NodeSoapOperation } from './node-soap-endpoint';
import { NodeSoapWsdl } from './node-soap';
import { LateResolvedMessage, Logger } from '../soap2graphql/logger';
import {inspect} from 'util';
import {NodeSoapOperation} from './node-soap-endpoint';
import {NodeSoapWsdl} from './node-soap';
import {LateResolvedMessage, Logger} from '../soap2graphql/logger';
import {
ComplexContentElement,
ComplexTypeElement,
Expand All @@ -20,14 +20,15 @@ import {
SequenceElement,
SimpleTypeElement,
} from 'soap/lib/wsdl/elements';
import { isListType } from 'graphql/type';
import {isListType} from 'graphql/type';

type XsdSupportedTypeDefinition = ComplexTypeElement | SimpleTypeElement | ElementElement;

const XS_STRING: SoapType = {
kind: 'simpleType',
name: 'string',
namespace: 'http://www.w3.org/2001/XMLSchema',
base: null
};

export class NodeSoapWsdlResolver {
Expand Down Expand Up @@ -64,7 +65,7 @@ export class NodeSoapWsdlResolver {
return undefined;
}

const ns = resolveNamespace(inputContent);
const ns = this.resolveNamespace(inputContent);
const inputType = this.resolveWsdlNameToSoapType(
ns,
withoutNamespace(inputContent.$lookupType),
Expand All @@ -84,7 +85,7 @@ export class NodeSoapWsdlResolver {
5,
)}'`,
);
const ns = resolveNamespace(outputContent);
const ns = this.resolveNamespace(outputContent);
const outputType = this.resolveWsdlNameToSoapType(
ns,
withoutNamespace(outputContent.$lookupType),
Expand All @@ -110,12 +111,13 @@ export class NodeSoapWsdlResolver {
// 1) an incredible boost in performance, must be at least 3ns, !!hax0r!!11
// 2) every type definition (primitive and complex) has only one instance of SoapType
// 3) resolve circular dependencies between types
if (this.alreadyResolved.has(namespace + wsdlTypeName)) {
const resolvedSoapType = this.alreadyResolved.get(`${namespace}:${wsdlTypeName}`);
if (resolvedSoapType) {
this.debug(
() =>
`resolved soap type for namespace: '${namespace}', typeName: '${wsdlTypeName}' from cache`,
);
return this.alreadyResolved.get(namespace + wsdlTypeName);
return resolvedSoapType;
}

// get the defition of the type from the schema section in the WSDL
Expand All @@ -128,8 +130,9 @@ export class NodeSoapWsdlResolver {
kind: 'simpleType',
name: wsdlTypeName,
namespace: namespace,
base: null
};
this.alreadyResolved.set(namespace + wsdlTypeName, soapType);
this.alreadyResolved.set(`${namespace}:${wsdlTypeName}`, soapType);

this.debug(
() =>
Expand All @@ -146,7 +149,7 @@ export class NodeSoapWsdlResolver {
name: schemaObject.$name,
namespace: namespace,
base: null,
fields: null,
fields: [],
attributes: null,
};
break;
Expand All @@ -160,22 +163,22 @@ export class NodeSoapWsdlResolver {
break;
case ElementElement:
if (schemaObject.$type) {
const ns = resolveNamespace(schemaObject);
const ns = this.resolveNamespace(schemaObject);
const type = this.findXsdTypeDefinition(ns, schemaObject.$type);
const soapType = this.resolveWsdlNameToSoapType(
ns,
withoutNamespace(schemaObject.$lookupType),
`return type of element '${schemaObject.$name}`,
);
this.alreadyResolved.set(soapType.namespace + soapType.name, soapType);
this.alreadyResolved.set(`${soapType.namespace}:${soapType.name}`, soapType);
return soapType;
} else {
// must be anonymous
const soapType = this.resolveAnonymousTypeToSoapType(
schemaObject,
schemaObject.$name,
);
this.alreadyResolved.set(soapType.namespace + soapType.name, soapType);
this.alreadyResolved.set(`${soapType.namespace}:${soapType.name}`, soapType);
return soapType;
}
default:
Expand All @@ -186,7 +189,7 @@ export class NodeSoapWsdlResolver {
);
}

this.alreadyResolved.set(namespace + wsdlTypeName, soapType);
this.alreadyResolved.set(`${namespace}:${wsdlTypeName}`, soapType);

// resolve bindings (field types, base type) after type has been registered to resolve circular dependencies
if (!(soapType instanceof ElementElement)) {
Expand All @@ -196,6 +199,7 @@ export class NodeSoapWsdlResolver {
if (soapType.kind === 'complexType') {
soapType.fields = undefined;
} else {
this.alreadyResolved.set(`${namespace}:${wsdlTypeName}`, XS_STRING);
soapType = XS_STRING;
}
}
Expand All @@ -216,7 +220,7 @@ export class NodeSoapWsdlResolver {
xsdFieldDefinition: ElementElement,
generatedTypeName: string,
): SoapType {
const namespace = resolveNamespace(xsdFieldDefinition);
const namespace = this.resolveNamespace(xsdFieldDefinition);
let existingDef = this.findXsdTypeDefinition(namespace, generatedTypeName);
while (!!existingDef && !(existingDef instanceof ElementElement)) {
generatedTypeName = generatedTypeName + '_';
Expand Down Expand Up @@ -286,7 +290,7 @@ export class NodeSoapWsdlResolver {
const typeName: string = typeDefinition.$name;

let fields: Element[];
let baseTypeName: string = null;
let base: SoapType|null;

const body: Element = typeDefinition.children[0];
const attributes: Element[] = typeDefinition.children.filter(
Expand All @@ -301,7 +305,7 @@ export class NodeSoapWsdlResolver {
(child) =>
child instanceof ExtensionElement || child instanceof RestrictionElement,
) as ExtensionElement | RestrictionElement;
baseTypeName = extensionOrRestriction.$base;
base = this.resolveBaseType(extensionOrRestriction, soapType.name)
const sequence: SequenceElement = extensionOrRestriction.children.find(
(child) => child instanceof SequenceElement,
) as SequenceElement;
Expand All @@ -310,51 +314,81 @@ export class NodeSoapWsdlResolver {
() =>
`cannot parse fields for soap type '${typeName}' as currently only restrictions/extensions with sequence are supported. leaving fields empty`,
);
throw new Error('Unsupported xsd definition.');
fields = []
} else {
fields = this.extractChildren(sequence);
}
baseTypeName = withoutNamespace(extensionOrRestriction.$base);
break;
case SimpleTypeElement:
const restriction = body.children.find(
(child) => child instanceof RestrictionElement,
) as RestrictionElement;
baseTypeName = withoutNamespace(restriction.$base);
base = this.resolveBaseType(restriction, soapType.name)
break;
default:
this.warn(() => `cannot parse fields for soap type '${typeName}'`);
fields = undefined;
fields = []
base = null;
}

if (soapType.kind === 'complexType' && fields) {
soapType.fields = this.resolveSoapFields(fields, soapType);
soapType.attributes = this.resolveSoapAttributes(attributes, soapType);
switch (soapType.kind) {
case 'complexType':
if (!base && !fields.length) {
this.warn(() => `returning complex type ${typeName} without fields`);
}
if (base) {
if (base.kind !== 'complexType') {
throw new Error(`Expected complex type as base of complex type ${soapType.name}`)
}
soapType.base = base;
}
soapType.fields = this.resolveSoapFields(fields, soapType);
soapType.attributes = this.resolveSoapAttributes(attributes, soapType);
return;
case 'simpleType':
if (base) {
if (base.kind !== 'simpleType') {
throw new Error(`Expected simple type as base of simple type ${soapType.name}`)
}
soapType.base = base;
}
}
}

const baseType: SoapComplexType = !baseTypeName
? null
: <SoapComplexType>(
this.resolveWsdlNameToSoapType(
namespace,
baseTypeName,
`base type of soap type '${soapType.name}'`,
)
);

soapType.base = baseType;
private resolveBaseType(element: RestrictionElement, soapTypeName: string): SoapType | null {
const baseTypeName = withoutNamespace(element.$base);
const baseTypeNamespace = this.resolveNamespace(element, namespacePrefix(element.$base))
if (!baseTypeName) {
return null;
}
return this.resolveWsdlNameToSoapType(
baseTypeNamespace,
baseTypeName,
`base type of soap type '${soapTypeName}'`,
)
}

private resolveSoapFields(fieldElements: Element[], soapType: SoapComplexType) {
private resolveSoapFields(fieldElements: Element[], soapType: SoapComplexType): SoapField[] {
const fields = fieldElements.map((field: ElementElement) => {
let type;
if (field.$type) {
const ns = resolveNamespace(field);
const ns = this.resolveNamespace(field);
type = this.resolveWsdlNameToSoapType(
ns,
withoutNamespace(field.$type),
`field '${field.$name}' of soap type '${soapType.name}'`,
);
} else if (field.$ref) {
const ns = this.resolveNamespace(field, namespacePrefix(field.$ref));
type = this.resolveWsdlNameToSoapType(
ns,
withoutNamespace(field.$ref),
`field '${field.$name}' of soap type '${soapType.name}'`,
);
return {
name: withoutNamespace(field.$ref),
type,
isList: getIsList(field)
}
} else if (field.children.length) {
let generatedTypeName = `${soapType.name}_${capitalizeFirstLetter(field.$name)}`;
type = this.resolveAnonymousTypeToSoapType(field, generatedTypeName);
Expand All @@ -381,7 +415,7 @@ export class NodeSoapWsdlResolver {
const attributes = attributeElements.map((attribute: ElementElement) => {
let type;
if (attribute.$type) {
const ns = resolveNamespace(attribute);
const ns = this.resolveNamespace(attribute);
type = this.resolveWsdlNameToSoapType(
ns,
withoutNamespace(attribute.$type),
Expand Down Expand Up @@ -419,10 +453,14 @@ export class NodeSoapWsdlResolver {
});
return result;
}
}

function targetNamespace(content: any) {
return content['targetNamespace'];
resolveNamespace(contextElement: ElementElement, prefix?: string) {
if (!prefix) {
prefix = namespacePrefix(contextElement.$type || contextElement.$lookupType);
}
return contextElement.xmlns[prefix] || contextElement.schemaXmlns[prefix] || this.wsdl.definitions.xmlns[prefix] || contextElement.$targetNamespace;
}

}

export function withoutNamespace(value: string): string {
Expand All @@ -448,11 +486,3 @@ function capitalizeFirstLetter(value: string) {
function getIsList(field: { $maxOccurs?: string }): boolean {
return !!field.$maxOccurs && field.$maxOccurs === 'unbounded';
}

function resolveNamespace(element: ElementElement) {
const prefix = namespacePrefix(element.$type || element.$lookupType);
// if (element.ignoredNamespaces?.includes(prefix)) {
// return element.$targetNamespace
// }
return element.xmlns[prefix] || element.schemaXmlns[prefix] || element.$targetNamespace;
}
28 changes: 14 additions & 14 deletions src/soap2graphql/soap-endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,32 +48,32 @@ export type SoapType = SoapComplexType | SoapSimpleType;
* A primitive type in the WSDL.
*/
export interface SoapSimpleType {
kind: 'simpleType';
namespace: string;
name: string;
base?: SoapSimpleType;
readonly kind: 'simpleType';
readonly namespace: string;
readonly name: string;
base: SoapSimpleType;
}

/**
* An object type in the WSDL.
* Defined by its name, fields and maybe a base type.
*/
export interface SoapComplexType {
kind: 'complexType';
name: string;
namespace: string;
readonly kind: 'complexType';
readonly name: string;
readonly namespace: string;
base: SoapComplexType;
fields: SoapField[];
attributes: SoapAttribute[];
fields: ReadonlyArray<SoapField>;
attributes: ReadonlyArray<SoapAttribute>;
}

export interface SoapField {
name: string;
type: SoapType;
isList: boolean;
readonly name: string;
readonly type: SoapType;
readonly isList: boolean;
}

export interface SoapAttribute {
name: string;
type: SoapSimpleType;
readonly name: string;
readonly type: SoapSimpleType;
}

0 comments on commit ec48ebd

Please sign in to comment.