Skip to content

Commit

Permalink
feat(soap): support SOAP headers and customize aliases (#8196)
Browse files Browse the repository at this point in the history
* feat(soap): support SOAP headers

* chore(dependencies): updated changesets for modified dependencies

* More

* Go

* No fetch for header test

* More

* chore(dependencies): updated changesets for modified dependencies

* chore(dependencies): updated changesets for modified dependencies

* lets go

* Tests

* Hmm

* Go

* Envelopeee

* Leftover

* Docs

* Headers

* Body alias documentation

* Lockfile

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
ardatan and github-actions[bot] authored Jan 7, 2025
1 parent b113b5e commit 3fc1f3e
Show file tree
Hide file tree
Showing 24 changed files with 937 additions and 52 deletions.
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

0 comments on commit 3fc1f3e

Please sign in to comment.