Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(soap): support SOAP headers and customize aliases #8196

Merged
merged 18 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/@omnigraph_soap-8196-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@omnigraph/soap": patch
---
dependencies updates:
- Added dependency [`@graphql-mesh/transport-common@^0.7.25` ↗︎](https://www.npmjs.com/package/@graphql-mesh/transport-common/v/0.7.25) (to `dependencies`)
75 changes: 75 additions & 0 deletions .changeset/funny-kangaroos-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
'@graphql-mesh/transport-soap': patch
'@omnigraph/soap': patch
'@graphql-mesh/types': patch
---

- You can now choose the name of the alias you want to use for SOAP body;

```ts filename="mesh.config.ts" {4}
import { defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
sources: [
{
sourceHandler: loadSOAPSubgraph('CountryInfo', {
source:
'http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL',
bodyAlias: 'my-body'
})
}
]
})
```

- Then it will generate a body like below by using the alias;

```xml
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:my-body="http://foo.com/">
<soap:Body>
<my-body:Foo>
<my-body:Bar>baz</my-body:Bar>
</my-body:Foo>
</soap:Body>
</soap:Envelope>
```

If you want to add SOAP headers to the request body like below;

```xml
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:header="http://foo.com/">
<soap:Header>
<header:MyHeader>
<header:UserName>user</header:UserName>
<header:Password>password</header:Password>
</header:MyHeader>
</soap:Header>
```

You can add the headers to the configuration like below;

```ts filename="mesh.config.ts" {2,7-9}
import { defineConfig } from '@graphql-mesh/compose-cli'
import { loadSOAPSubgraph } from '@omnigraph/soap'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadSOAPSubgraph('CountryInfo', {
source:
'http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL',
soapHeaders: {
alias: 'header',
namespace: 'http://foo.com',
headers: {
MyHeader: {
UserName: 'user',
Password: 'password'
}
}
}
})
}
]
})
```
10 changes: 10 additions & 0 deletions e2e/soap-demo/__snapshots__/soap-demo.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ directive @soap(
bindingNamespace: String
endpoint: String
subgraph: String
bodyAlias: String
soapHeaders: SOAPHeaders
) repeatable on FIELD_DEFINITION

directive @extraSchemaDefinitionDirective(directives: _DirectiveExtensions) repeatable on OBJECT
Expand All @@ -108,6 +110,8 @@ The \`JSON\` scalar type represents JSON values as specified by [ECMA-404](http:
"""
scalar JSON @join__type(graph: SOAP_DEMO)

scalar ObjMap @join__type(graph: SOAP_DEMO)

scalar _DirectiveExtensions @join__type(graph: SOAP_DEMO)

type Query @extraSchemaDefinitionDirective(directives: {transport: [{kind: "soap", subgraph: "soap-demo"}]}) @join__type(graph: SOAP_DEMO) {
Expand Down Expand Up @@ -294,6 +298,12 @@ input s0_DivideInteger_Input @join__type(graph: SOAP_DEMO) {
input s0_LookupCity_Input @join__type(graph: SOAP_DEMO) {
zip: String
}

input SOAPHeaders @join__type(graph: SOAP_DEMO) {
namespace: String
alias: String
headers: ObjMap
}

"
`;
11 changes: 4 additions & 7 deletions e2e/utils/leftoverStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,13 @@ function handleSuppressedError(e: any) {
}

if (typeof afterAll === 'function') {
afterAll(() => {
afterAll(async () => {
try {
const disposeRes$ = leftoverStack.disposeAsync();
leftoverStack = new AsyncDisposableStack();
if (disposeRes$?.catch) {
disposeRes$.catch(handleSuppressedError);
}
await leftoverStack.disposeAsync();
} catch (e) {
handleSuppressedError(e);
} finally {
leftoverStack = new AsyncDisposableStack();
}
leftoverStack = new AsyncDisposableStack();
});
}
2 changes: 2 additions & 0 deletions packages/legacy/handlers/soap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export default class SoapHandler implements MeshHandler {
logger: this.logger,
schemaHeaders: this.config.schemaHeaders,
operationHeaders: this.config.operationHeaders,
soapHeaders: this.config.soapHeaders,
bodyAlias: this.config.bodyAlias,
});
const wsdlLocation = this.config.source;
const wsdl = await readFileOrUrl<string>(wsdlLocation, {
Expand Down
29 changes: 29 additions & 0 deletions packages/legacy/handlers/soap/yaml-config.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,33 @@ type SoapHandler @md {
JSON object representing the Headers to add to the runtime of the API calls only for operation during runtime
"""
operationHeaders: JSON
"""
The name of the alias to be used in the envelope for body components

default: `body`
"""
bodyAlias: String
"""
SOAP Headers to be added to the request
"""
soapHeaders: SOAPHeaders
}

type SOAPHeaders {
"""
The name of the alias to be used in the envelope

default: `header`
"""
alias: String
"""
The namespace of the SOAP Header
For example: `http://www.example.com/namespace`
"""
namespace: String!
"""
The content of the SOAP Header
For example: { "key": "value" } then the content will be `<key>value</key>`
"""
headers: JSON!
}
29 changes: 29 additions & 0 deletions packages/legacy/types/src/config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3004,10 +3004,39 @@
"type": "object",
"properties": {},
"description": "JSON object representing the Headers to add to the runtime of the API calls only for operation during runtime"
},
"bodyAlias": {
"type": "string",
"description": "The name of the alias to be used in the envelope for body components\n\ndefault: `body`"
},
"soapHeaders": {
"$ref": "#/definitions/SOAPHeaders",
"description": "SOAP Headers to be added to the request"
}
},
"required": ["source"]
},
"SOAPHeaders": {
"additionalProperties": false,
"type": "object",
"title": "SOAPHeaders",
"properties": {
"alias": {
"type": "string",
"description": "The name of the alias to be used in the envelope\n\ndefault: `header`"
},
"namespace": {
"type": "string",
"description": "The namespace of the SOAP Header\nFor example: `http://www.example.com/namespace`"
},
"headers": {
"type": "object",
"properties": {},
"description": "The content of the SOAP Header\nFor example: { \"key\": \"value\" } then the content will be `<key>value</key>`"
}
},
"required": ["namespace", "headers"]
},
"SupergraphHandler": {
"additionalProperties": false,
"type": "object",
Expand Down
30 changes: 30 additions & 0 deletions packages/legacy/types/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,36 @@ export interface SoapHandler {
operationHeaders?: {
[k: string]: any;
};
/**
* The name of the alias to be used in the envelope for body components
*
* default: `body`
*/
bodyAlias?: string;
soapHeaders?: SOAPHeaders;
}
/**
* SOAP Headers to be added to the request
*/
export interface SOAPHeaders {
/**
* The name of the alias to be used in the envelope
*
* default: `header`
*/
alias?: string;
/**
* The namespace of the SOAP Header
* For example: `http://www.example.com/namespace`
*/
namespace: string;
/**
* The content of the SOAP Header
* For example: { "key": "value" } then the content will be `<key>value</key>`
*/
headers: {
[k: string]: any;
};
}
export interface SupergraphHandler {
/**
Expand Down
1 change: 1 addition & 0 deletions packages/loaders/soap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"dependencies": {
"@graphql-mesh/cross-helpers": "^0.4.9",
"@graphql-mesh/string-interpolation": "^0.5.7",
"@graphql-mesh/transport-common": "^0.7.25",
"@graphql-mesh/transport-soap": "^0.8.10",
"@graphql-mesh/types": "^0.103.10",
"@graphql-mesh/utils": "^0.103.10",
Expand Down
58 changes: 58 additions & 0 deletions packages/loaders/soap/src/SOAPLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
GraphQLBoolean,
GraphQLDirective,
GraphQLFloat,
GraphQLInputObjectType,
GraphQLInt,
GraphQLString,
} from 'graphql';
Expand Down Expand Up @@ -39,6 +40,7 @@ import {
import { process } from '@graphql-mesh/cross-helpers';
import type { ResolverDataBasedFactory } from '@graphql-mesh/string-interpolation';
import { getInterpolatedHeadersFactory } from '@graphql-mesh/string-interpolation';
import { ObjMapScalar } from '@graphql-mesh/transport-common';
import type { Logger, MeshFetch } from '@graphql-mesh/types';
import {
defaultImportFn,
Expand Down Expand Up @@ -71,10 +73,50 @@ export interface SOAPLoaderOptions {
logger?: Logger;
schemaHeaders?: Record<string, string>;
operationHeaders?: Record<string, string>;
soapHeaders?: SOAPHeaders;
endpoint?: string;
cwd?: string;
bodyAlias?: string;
}

export interface SOAPHeaders {
/**
* The namespace of the SOAP Header
*
* @example http://www.example.com/namespace
*/
namespace: string;
/**
* The name of the alias to be used in the envelope
*
* @default header
*/
alias?: string;
/**
* The content of the SOAP Header
*
* @example { "key": "value" }
*
* then the content will be `<key>value</key>` in XML
*/
headers: unknown;
}

const SOAPHeadersInput = new GraphQLInputObjectType({
name: 'SOAPHeaders',
fields: {
namespace: {
type: GraphQLString,
},
alias: {
type: GraphQLString,
},
headers: {
type: ObjMapScalar,
},
},
});

const soapDirective = new GraphQLDirective({
name: 'soap',
locations: [DirectiveLocation.FIELD_DEFINITION],
Expand All @@ -91,6 +133,12 @@ const soapDirective = new GraphQLDirective({
subgraph: {
type: GraphQLString,
},
bodyAlias: {
type: GraphQLString,
},
soapHeaders: {
type: SOAPHeadersInput,
},
},
});

Expand Down Expand Up @@ -143,6 +191,8 @@ export class SOAPLoader {
private logger: Logger;
private endpoint?: string;
private cwd: string;
private soapHeaders: SOAPHeaders;
private bodyAlias?: string;

constructor(options: SOAPLoaderOptions) {
this.fetchFn = options.fetch || defaultFetchFn;
Expand All @@ -153,6 +203,8 @@ export class SOAPLoader {
this.schemaHeadersFactory = getInterpolatedHeadersFactory(options.schemaHeaders || {});
this.endpoint = options.endpoint;
this.cwd = options.cwd;
this.soapHeaders = options.soapHeaders;
this.bodyAlias = options.bodyAlias;
}

loadXMLSchemaNamespace() {
Expand Down Expand Up @@ -452,6 +504,12 @@ export class SOAPLoader {
endpoint: this.endpoint || portObj.address[0].attributes.location,
subgraph: this.subgraphName,
};
if (this.bodyAlias) {
soapAnnotations.bodyAlias = this.bodyAlias;
}
if (this.soapHeaders) {
soapAnnotations.soapHeaders = this.soapHeaders;
}
rootTC.addFields({
[operationFieldName]: {
type,
Expand Down
Loading
Loading