@onlineapps/conn-orch-api-mapper 1.0.7 → 1.0.8

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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/ApiMapper.js +149 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/conn-orch-api-mapper",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "API mapping connector for OA Drive - maps cookbook operations to HTTP endpoints",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -21,7 +21,7 @@
21
21
  "license": "MIT",
22
22
  "dependencies": {
23
23
  "axios": "^1.4.0",
24
- "@onlineapps/content-resolver": "^1.1.2"
24
+ "@onlineapps/content-resolver": "^1.1.3"
25
25
  },
26
26
  "devDependencies": {
27
27
  "jest": "^29.5.0",
package/src/ApiMapper.js CHANGED
@@ -126,6 +126,11 @@ class ApiMapper {
126
126
  /**
127
127
  * Transform HTTP response to cookbook format
128
128
  * Normalizes output fields to Content Descriptor based on operations.json output schema
129
+ *
130
+ * Supports two operations.json output formats:
131
+ * 1. Named fields: { "message": { "type": "string" }, "pdf": { "type": "file" } }
132
+ * 2. Direct type: { "type": "file", "description": "..." } (entire output is one value)
133
+ *
129
134
  * @method transformResponse
130
135
  * @param {Object} httpResponse - HTTP response
131
136
  * @param {Object} [operation] - Operation schema from operations.json (for output normalization)
@@ -136,15 +141,24 @@ class ApiMapper {
136
141
  // Extract relevant data from HTTP response
137
142
  let output = httpResponse.data || httpResponse;
138
143
 
144
+ // === LOGGING: Input ===
145
+ const logPrefix = '[ApiMapper:transformResponse]';
146
+ console.log(`${logPrefix} INPUT:`, JSON.stringify({
147
+ hasOperation: !!operation,
148
+ operationOutput: operation?.output,
149
+ outputType: typeof output,
150
+ outputKeys: output && typeof output === 'object' ? Object.keys(output) : null,
151
+ output_descriptor: output?._descriptor,
152
+ output_type: output?.type
153
+ }));
154
+
139
155
  // If no operation schema, return as-is
140
156
  if (!operation || !operation.output) {
157
+ console.log(`${logPrefix} OUTPUT: No operation schema, returning as-is`);
141
158
  return output;
142
159
  }
143
160
 
144
- // Normalize output fields according to operations.json output schema
145
- // Fields with type: "file" or type: "content" should be normalized to Descriptor
146
161
  const outputSchema = operation.output;
147
- const normalizedOutput = { ...output };
148
162
 
149
163
  // Initialize ContentAccessor if needed (lazy initialization)
150
164
  let contentAccessor = null;
@@ -160,11 +174,115 @@ class ApiMapper {
160
174
  return contentAccessor;
161
175
  };
162
176
 
177
+ // === DETECT OUTPUT SCHEMA FORMAT ===
178
+ // Format A: Direct type - { type: "file", description: "..." }
179
+ // Format B: Named fields - { "fieldName": { type: "string" }, ... }
180
+ const isDirectType = outputSchema.type &&
181
+ (outputSchema.type === 'file' ||
182
+ outputSchema.type === 'content' ||
183
+ outputSchema.type === 'string' ||
184
+ outputSchema.type === 'object' ||
185
+ outputSchema.type === 'array');
186
+
187
+ console.log(`${logPrefix} Schema format: ${isDirectType ? 'DIRECT_TYPE' : 'NAMED_FIELDS'}`);
188
+
189
+ // === FORMAT A: Direct type (entire output is one value) ===
190
+ if (isDirectType) {
191
+ const schemaType = outputSchema.type;
192
+
193
+ // If schema expects file/content, ensure output is Descriptor
194
+ if (schemaType === 'file' || schemaType === 'content') {
195
+ // Check if output is already a valid Descriptor
196
+ if (output && typeof output === 'object' && output._descriptor === true) {
197
+ console.log(`${logPrefix} OUTPUT: Already Descriptor with _descriptor=true`);
198
+ return output;
199
+ }
200
+
201
+ // Check if output looks like Descriptor but missing _descriptor flag
202
+ if (output && typeof output === 'object' && (output.type === 'file' || output.type === 'inline')) {
203
+ console.log(`${logPrefix} Normalizing Descriptor (missing _descriptor flag)`);
204
+ try {
205
+ const accessor = getContentAccessor();
206
+ const normalized = await accessor.normalizeToDescriptor(output, {
207
+ filename: output.filename,
208
+ content_type: output.content_type
209
+ });
210
+ console.log(`${logPrefix} OUTPUT: Normalized Descriptor:`, JSON.stringify({
211
+ _descriptor: normalized._descriptor,
212
+ type: normalized.type,
213
+ storage_ref: normalized.storage_ref,
214
+ filename: normalized.filename
215
+ }));
216
+ return normalized;
217
+ } catch (error) {
218
+ console.error(`${logPrefix} FAIL: Failed to normalize Descriptor: ${error.message}`);
219
+ // FAIL-FAST: Don't silently continue with broken data
220
+ throw new Error(`[ApiMapper] FAIL-FAST: Output is ${schemaType} but failed to normalize: ${error.message}`);
221
+ }
222
+ }
223
+
224
+ // Output is string (storage reference) - convert to Descriptor
225
+ if (typeof output === 'string') {
226
+ console.log(`${logPrefix} Converting string to Descriptor: ${output.substring(0, 50)}...`);
227
+ try {
228
+ const accessor = getContentAccessor();
229
+ const normalized = await accessor.normalizeToDescriptor(output, {
230
+ filename: outputSchema.filename,
231
+ content_type: outputSchema.content_type
232
+ });
233
+ console.log(`${logPrefix} OUTPUT: String converted to Descriptor`);
234
+ return normalized;
235
+ } catch (error) {
236
+ console.error(`${logPrefix} FAIL: Failed to convert string to Descriptor: ${error.message}`);
237
+ throw new Error(`[ApiMapper] FAIL-FAST: Output is ${schemaType} but string conversion failed: ${error.message}`);
238
+ }
239
+ }
240
+
241
+ // Output is object with storage_ref - extract and convert
242
+ if (output && typeof output === 'object' && output.storage_ref) {
243
+ console.log(`${logPrefix} Converting object with storage_ref to Descriptor`);
244
+ try {
245
+ const accessor = getContentAccessor();
246
+ const normalized = await accessor.normalizeToDescriptor(output.storage_ref, {
247
+ filename: output.filename || output.output_name,
248
+ content_type: output.content_type
249
+ });
250
+ console.log(`${logPrefix} OUTPUT: Object converted to Descriptor`);
251
+ return normalized;
252
+ } catch (error) {
253
+ console.error(`${logPrefix} FAIL: Failed to convert object to Descriptor: ${error.message}`);
254
+ throw new Error(`[ApiMapper] FAIL-FAST: Output is ${schemaType} but object conversion failed: ${error.message}`);
255
+ }
256
+ }
257
+
258
+ // FAIL-FAST: Schema expects file/content but got unexpected format
259
+ console.error(`${logPrefix} FAIL-FAST: Schema expects ${schemaType} but got unexpected output format:`, {
260
+ outputType: typeof output,
261
+ outputKeys: output && typeof output === 'object' ? Object.keys(output) : null
262
+ });
263
+ throw new Error(`[ApiMapper] FAIL-FAST: Schema expects '${schemaType}' but received unexpected format. Output type: ${typeof output}`);
264
+ }
265
+
266
+ // For other direct types (string, object, array), return as-is
267
+ console.log(`${logPrefix} OUTPUT: Direct type '${schemaType}', returning as-is`);
268
+ return output;
269
+ }
270
+
271
+ // === FORMAT B: Named fields ===
272
+ const normalizedOutput = { ...output };
273
+
163
274
  // Process each output field
164
275
  for (const [fieldName, fieldSchema] of Object.entries(outputSchema)) {
276
+ // Skip if fieldSchema is not an object (e.g., "type" key from Format A detected wrongly)
277
+ if (!fieldSchema || typeof fieldSchema !== 'object') {
278
+ continue;
279
+ }
280
+
165
281
  const fieldType = fieldSchema.type;
166
282
  const fieldValue = output[fieldName];
167
283
 
284
+ console.log(`${logPrefix} Processing field '${fieldName}': type=${fieldType}, hasValue=${fieldValue !== undefined}`);
285
+
168
286
  // Skip if field not present or null
169
287
  if (fieldValue === undefined || fieldValue === null) {
170
288
  continue;
@@ -175,42 +293,50 @@ class ApiMapper {
175
293
  try {
176
294
  const accessor = getContentAccessor();
177
295
 
178
- // If field is already a Descriptor (has _descriptor flag), use as-is
179
- if (typeof fieldValue === 'object' && fieldValue !== null && (fieldValue._descriptor === true || fieldValue.type === 'file' || fieldValue.type === 'inline')) {
180
- // Already a Descriptor, normalize to ensure completeness and _descriptor flag
296
+ // If field is already a Descriptor (has _descriptor flag), pass through
297
+ if (typeof fieldValue === 'object' && fieldValue !== null && fieldValue._descriptor === true) {
298
+ console.log(`${logPrefix} Field '${fieldName}' already Descriptor with _descriptor=true`);
299
+ normalizedOutput[fieldName] = fieldValue;
300
+ } else if (typeof fieldValue === 'object' && fieldValue !== null && (fieldValue.type === 'file' || fieldValue.type === 'inline')) {
301
+ // Looks like Descriptor but missing _descriptor flag - normalize
302
+ console.log(`${logPrefix} Field '${fieldName}' normalizing Descriptor (missing _descriptor flag)`);
181
303
  normalizedOutput[fieldName] = await accessor.normalizeToDescriptor(fieldValue, {
182
304
  filename: fieldValue.filename,
183
305
  content_type: fieldValue.content_type
184
306
  });
185
307
  } else if (typeof fieldValue === 'string') {
186
308
  // String value - normalize to Descriptor
309
+ console.log(`${logPrefix} Field '${fieldName}' converting string to Descriptor`);
187
310
  normalizedOutput[fieldName] = await accessor.normalizeToDescriptor(fieldValue, {
188
311
  filename: fieldSchema.filename,
189
312
  content_type: fieldSchema.content_type
190
313
  });
191
- } else if (typeof fieldValue === 'object' && fieldValue !== null) {
192
- // Object value (e.g., {storage_ref: "minio://..."}) - extract and normalize
193
- if (fieldValue.storage_ref) {
194
- // If it's an object with storage_ref, normalize the storage_ref
195
- normalizedOutput[fieldName] = await accessor.normalizeToDescriptor(fieldValue.storage_ref, {
196
- filename: fieldValue.filename || fieldValue.output_name,
197
- content_type: fieldValue.content_type
198
- });
199
- } else {
200
- // Unknown object format, try to normalize as-is
201
- normalizedOutput[fieldName] = await accessor.normalizeToDescriptor(fieldValue, {
202
- filename: fieldSchema.filename,
203
- content_type: fieldSchema.content_type
204
- });
205
- }
314
+ } else if (typeof fieldValue === 'object' && fieldValue !== null && fieldValue.storage_ref) {
315
+ // Object with storage_ref - extract and normalize
316
+ console.log(`${logPrefix} Field '${fieldName}' converting object with storage_ref to Descriptor`);
317
+ normalizedOutput[fieldName] = await accessor.normalizeToDescriptor(fieldValue.storage_ref, {
318
+ filename: fieldValue.filename || fieldValue.output_name,
319
+ content_type: fieldValue.content_type
320
+ });
321
+ } else {
322
+ // FAIL-FAST: Unexpected format for file/content field
323
+ console.error(`${logPrefix} FAIL-FAST: Field '${fieldName}' expects ${fieldType} but got unexpected format:`, {
324
+ valueType: typeof fieldValue,
325
+ valueKeys: fieldValue && typeof fieldValue === 'object' ? Object.keys(fieldValue) : null
326
+ });
327
+ throw new Error(`[ApiMapper] FAIL-FAST: Field '${fieldName}' expects '${fieldType}' but received unexpected format`);
206
328
  }
207
329
  } catch (error) {
208
- this.logger?.warn(`Failed to normalize output field ${fieldName} to Descriptor: ${error.message}`);
209
- // Keep original value on error
330
+ if (error.message.includes('FAIL-FAST')) {
331
+ throw error; // Re-throw fail-fast errors
332
+ }
333
+ console.error(`${logPrefix} FAIL: Failed to normalize field '${fieldName}': ${error.message}`);
334
+ throw new Error(`[ApiMapper] FAIL-FAST: Failed to normalize field '${fieldName}' to Descriptor: ${error.message}`);
210
335
  }
211
336
  }
212
337
  }
213
338
 
339
+ console.log(`${logPrefix} OUTPUT: Normalized fields:`, JSON.stringify(Object.keys(normalizedOutput)));
214
340
  return normalizedOutput;
215
341
  }
216
342