@onlineapps/conn-orch-api-mapper 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/API.md CHANGED
File without changes
package/README.md ADDED
@@ -0,0 +1,262 @@
1
+ # @onlineapps/conn-orch-api-mapper
2
+
3
+ ## Overview
4
+ Maps cookbook operations to HTTP API endpoints. Bridges the gap between MQ-based workflow steps and RESTful service APIs.
5
+
6
+ ## Installation
7
+ ```bash
8
+ npm install @onlineapps/conn-orch-api-mapper
9
+ ```
10
+
11
+ ## Features
12
+ - Operation to endpoint mapping
13
+ - Parameter transformation
14
+ - Response mapping
15
+ - Header injection
16
+ - Service discovery integration
17
+ - OpenAPI schema support
18
+
19
+ ## Usage
20
+
21
+ ### Basic Setup
22
+ ```javascript
23
+ const { ApiMapper } = require('@onlineapps/conn-orch-api-mapper');
24
+
25
+ const mapper = new ApiMapper({
26
+ registryUrl: 'http://api_services_registry:33100',
27
+ cacheEnabled: true,
28
+ cacheTTL: 300
29
+ });
30
+
31
+ // Initialize mapper
32
+ await mapper.initialize();
33
+ ```
34
+
35
+ ### Operation Mapping
36
+ ```javascript
37
+ // Map cookbook operation to HTTP request
38
+ const httpRequest = await mapper.mapOperation({
39
+ service: 'hello-service',
40
+ operation: 'greet',
41
+ params: {
42
+ name: 'World',
43
+ language: 'en'
44
+ }
45
+ });
46
+
47
+ // Returns:
48
+ {
49
+ method: 'POST',
50
+ url: 'http://hello-service:33199/api/greet',
51
+ headers: {
52
+ 'Content-Type': 'application/json',
53
+ 'X-Workflow-Id': 'wf-uuid'
54
+ },
55
+ body: {
56
+ name: 'World',
57
+ language: 'en'
58
+ }
59
+ }
60
+ ```
61
+
62
+ ### Response Mapping
63
+ ```javascript
64
+ // Map HTTP response back to workflow format
65
+ const workflowResult = mapper.mapResponse({
66
+ status: 200,
67
+ headers: { 'content-type': 'application/json' },
68
+ body: { greeting: 'Hello World' }
69
+ });
70
+
71
+ // Returns:
72
+ {
73
+ success: true,
74
+ data: { greeting: 'Hello World' },
75
+ metadata: {
76
+ httpStatus: 200,
77
+ processingTime: 45
78
+ }
79
+ }
80
+ ```
81
+
82
+ ## Configuration
83
+
84
+ ### Options
85
+ ```javascript
86
+ {
87
+ registryUrl: 'http://registry:33100', // Registry service URL
88
+ cacheEnabled: true, // Cache service mappings
89
+ cacheTTL: 300, // Cache TTL in seconds
90
+ timeout: 5000, // HTTP request timeout
91
+ retries: 3, // Retry attempts
92
+ validateSchema: true // Validate against OpenAPI
93
+ }
94
+ ```
95
+
96
+ ### Environment Variables
97
+ ```bash
98
+ REGISTRY_URL=http://api_services_registry:33100
99
+ API_MAPPER_CACHE_ENABLED=true
100
+ API_MAPPER_CACHE_TTL=300
101
+ API_MAPPER_TIMEOUT=5000
102
+ ```
103
+
104
+ ## Mapping Rules
105
+
106
+ ### Default Mapping
107
+ ```javascript
108
+ // Cookbook operation
109
+ { service: 'service-name', operation: 'operation-name' }
110
+
111
+ // Maps to HTTP
112
+ POST /api/operation-name
113
+ ```
114
+
115
+ ### Custom Mappings
116
+ ```javascript
117
+ // Define custom mappings
118
+ mapper.addMapping('hello-service', {
119
+ greet: {
120
+ method: 'POST',
121
+ path: '/api/greet',
122
+ paramMapping: {
123
+ name: 'body.name',
124
+ lang: 'query.language'
125
+ }
126
+ },
127
+ status: {
128
+ method: 'GET',
129
+ path: '/health'
130
+ }
131
+ });
132
+ ```
133
+
134
+ ## Service Discovery
135
+
136
+ The mapper integrates with the service registry:
137
+
138
+ ```javascript
139
+ // Auto-discover service endpoints
140
+ const endpoint = await mapper.discoverEndpoint('hello-service');
141
+ // Returns: http://hello-service:33199
142
+
143
+ // Get service OpenAPI spec
144
+ const spec = await mapper.getServiceSpec('hello-service');
145
+ // Returns OpenAPI specification object
146
+ ```
147
+
148
+ ## Parameter Transformation
149
+
150
+ ### Input Transformation
151
+ ```javascript
152
+ // Transform cookbook params to HTTP request
153
+ const transformed = mapper.transformParams({
154
+ params: { name: 'World', count: 5 },
155
+ mapping: {
156
+ name: 'body.name',
157
+ count: 'query.limit'
158
+ }
159
+ });
160
+
161
+ // Returns:
162
+ {
163
+ body: { name: 'World' },
164
+ query: { limit: 5 }
165
+ }
166
+ ```
167
+
168
+ ### Output Transformation
169
+ ```javascript
170
+ // Transform HTTP response to workflow output
171
+ const output = mapper.transformResponse({
172
+ response: { data: { items: [...] } },
173
+ mapping: {
174
+ 'data.items': 'results',
175
+ 'data.total': 'count'
176
+ }
177
+ });
178
+ ```
179
+
180
+ ## API Reference
181
+
182
+ ### ApiMapper
183
+
184
+ #### initialize()
185
+ Initializes the mapper and loads service registry.
186
+
187
+ #### mapOperation(operation)
188
+ Maps a cookbook operation to HTTP request.
189
+
190
+ #### mapResponse(response)
191
+ Maps HTTP response to workflow result.
192
+
193
+ #### discoverEndpoint(serviceName)
194
+ Discovers service endpoint from registry.
195
+
196
+ #### getServiceSpec(serviceName)
197
+ Retrieves OpenAPI specification for service.
198
+
199
+ #### addMapping(serviceName, mappings)
200
+ Adds custom operation mappings.
201
+
202
+ #### transformParams(params, mapping)
203
+ Transforms parameters according to mapping rules.
204
+
205
+ ## Integration with Service Wrapper
206
+
207
+ The API mapper is automatically used by service-wrapper:
208
+
209
+ ```javascript
210
+ const { ServiceWrapper } = require('@onlineapps/service-wrapper');
211
+
212
+ const wrapper = new ServiceWrapper({
213
+ apiMapping: {
214
+ enabled: true,
215
+ customMappings: {...}
216
+ }
217
+ });
218
+
219
+ // API mapping is automatically configured
220
+ ```
221
+
222
+ ## OpenAPI Integration
223
+
224
+ Supports OpenAPI 3.0 specifications:
225
+
226
+ ```javascript
227
+ // Validate against OpenAPI schema
228
+ const valid = await mapper.validateOperation({
229
+ service: 'hello-service',
230
+ operation: 'greet',
231
+ params: { name: 'World' }
232
+ });
233
+
234
+ // Generate request from OpenAPI
235
+ const request = await mapper.fromOpenAPI({
236
+ spec: openApiSpec,
237
+ operationId: 'greetUser',
238
+ params: { name: 'World' }
239
+ });
240
+ ```
241
+
242
+ ## Testing
243
+
244
+ ```bash
245
+ npm test # Run all tests
246
+ npm run test:unit # Unit tests only
247
+ npm run test:component # Component tests
248
+ ```
249
+
250
+ ## Dependencies
251
+ - `@onlineapps/conn-orch-registry` - Service registry client
252
+ - `axios` - HTTP client
253
+ - `openapi-validator` - Schema validation
254
+
255
+ ## Related Documentation
256
+ - [Service Wrapper](/shared/connector/service-wrapper/README.md) - Integration guide
257
+ - [Registry System](/docs/modules/registry.md) - Service discovery
258
+ - [Cookbook Connector](/shared/connector/conn-orch-cookbook/README.md) - Operation format
259
+ - [API Standards](/docs/STANDARDS_OVERVIEW.md#api-structure) - Endpoint conventions
260
+
261
+ ---
262
+ *Version: 1.0.0 | License: MIT*
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/conn-orch-api-mapper",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "API mapping connector for OA Drive - maps cookbook operations to HTTP endpoints",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -13,7 +13,6 @@
13
13
  "keywords": [
14
14
  "api",
15
15
  "mapping",
16
- "openapi",
17
16
  "cookbook",
18
17
  "http",
19
18
  "connector"
package/src/ApiMapper.js CHANGED
@@ -184,42 +184,88 @@ class ApiMapper {
184
184
  }
185
185
 
186
186
  /**
187
- * Parse OpenAPI specification to extract operations
187
+ * Parse OpenAPI specification or operations.json to extract operations
188
188
  * @private
189
- * @param {Object} spec - OpenAPI specification
189
+ * @param {Object} spec - OpenAPI specification or operations.json format
190
190
  * @returns {Object} Operations map
191
191
  */
192
192
  _parseOpenApiSpec(spec) {
193
193
  const operations = {};
194
194
 
195
- if (!spec || !spec.paths) {
195
+ if (!spec || typeof spec !== 'object') {
196
196
  return operations;
197
197
  }
198
198
 
199
- // Extract all operations from OpenAPI paths
200
- Object.entries(spec.paths).forEach(([path, pathItem]) => {
201
- Object.entries(pathItem).forEach(([method, operation]) => {
202
- if (['get', 'post', 'put', 'patch', 'delete'].includes(method)) {
203
- const operationId = operation.operationId ||
204
- `${method}_${path.replace(/[^a-zA-Z0-9]/g, '_')}`;
205
-
206
- operations[operationId] = {
207
- method: method.toUpperCase(),
208
- path,
209
- parameters: operation.parameters || [],
210
- requestBody: operation.requestBody,
211
- responses: operation.responses,
212
- summary: operation.summary,
213
- description: operation.description
214
- };
215
- }
199
+ // Check if this is operations.json format (has "operations" key at root)
200
+ if (spec.operations && typeof spec.operations === 'object') {
201
+ // Parse operations.json format
202
+ Object.entries(spec.operations).forEach(([operationId, operation]) => {
203
+ operations[operationId] = {
204
+ method: (operation.method || 'POST').toUpperCase(),
205
+ path: operation.endpoint || `/${operationId}`,
206
+ parameters: operation.input ? this._convertOperationsJsonInputToOpenApi(operation.input) : [],
207
+ requestBody: operation.input ? { content: { 'application/json': { schema: { type: 'object', properties: operation.input } } } } : undefined,
208
+ responses: operation.output ? { '200': { description: 'Success', content: { 'application/json': { schema: { type: 'object', properties: operation.output } } } } } : { '200': { description: 'Success' } },
209
+ summary: operation.description,
210
+ description: operation.description
211
+ };
216
212
  });
217
- });
213
+ this.logger?.info(`Parsed ${Object.keys(operations).length} operations from operations.json format`);
214
+ return operations;
215
+ }
218
216
 
219
- this.logger.info(`Parsed ${Object.keys(operations).length} operations from OpenAPI spec`);
217
+ // Extract all operations from OpenAPI paths (original OpenAPI format)
218
+ if (spec.paths) {
219
+ Object.entries(spec.paths).forEach(([path, pathItem]) => {
220
+ Object.entries(pathItem).forEach(([method, operation]) => {
221
+ if (['get', 'post', 'put', 'patch', 'delete'].includes(method)) {
222
+ const operationId = operation.operationId ||
223
+ `${method}_${path.replace(/[^a-zA-Z0-9]/g, '_')}`;
224
+
225
+ operations[operationId] = {
226
+ method: method.toUpperCase(),
227
+ path,
228
+ parameters: operation.parameters || [],
229
+ requestBody: operation.requestBody,
230
+ responses: operation.responses,
231
+ summary: operation.summary,
232
+ description: operation.description
233
+ };
234
+ }
235
+ });
236
+ });
237
+ }
238
+
239
+ this.logger?.info(`Parsed ${Object.keys(operations).length} operations from OpenAPI spec`);
220
240
  return operations;
221
241
  }
222
242
 
243
+ /**
244
+ * Convert operations.json input format to OpenAPI parameters
245
+ * @private
246
+ * @param {Object} input - operations.json input schema
247
+ * @returns {Array} OpenAPI parameters array
248
+ */
249
+ _convertOperationsJsonInputToOpenApi(input) {
250
+ const parameters = [];
251
+ Object.entries(input).forEach(([name, schema]) => {
252
+ parameters.push({
253
+ name,
254
+ in: 'body', // operations.json uses body parameters
255
+ required: schema.required !== false,
256
+ schema: {
257
+ type: schema.type || 'string',
258
+ description: schema.description,
259
+ minLength: schema.minLength,
260
+ maxLength: schema.maxLength,
261
+ enum: schema.enum,
262
+ default: schema.default
263
+ }
264
+ });
265
+ });
266
+ return parameters;
267
+ }
268
+
223
269
  /**
224
270
  * Resolve variables in input using context
225
271
  * @private
package/src/index.js CHANGED
File without changes
@@ -6,7 +6,7 @@ const axios = require('axios');
6
6
  // Mock axios
7
7
  jest.mock('axios');
8
8
 
9
- describe('API Mapper Component Tests', () => {
9
+ describe('API Mapper Component Tests @component', () => {
10
10
  let apiMapper;
11
11
  let mockOpenApiSpec;
12
12
  let mockLogger;
@@ -5,7 +5,7 @@ const axios = require('axios');
5
5
 
6
6
  jest.mock('axios');
7
7
 
8
- describe('ApiMapper Unit Tests', () => {
8
+ describe('ApiMapper Unit Tests @unit', () => {
9
9
  let apiMapper;
10
10
  let mockLogger;
11
11
  let mockOpenApiSpec;