Skip to content

Commit a9f9414

Browse files
author
Noémie TAVIERE
committed
add image as output
1 parent c08043b commit a9f9414

File tree

4 files changed

+360
-10
lines changed

4 files changed

+360
-10
lines changed

src/N8NPropertiesBuilder.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,7 @@ test('enum schema', () => {
753753
},
754754
{
755755
displayName: 'Type',
756+
description: undefined,
756757
name: 'type',
757758
type: 'options',
758759
default: 'type1',

src/OperationsCollector.ts

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ import {OptionsByResourceMap} from "./n8n/OptionsByResourceMap";
88
import {INodeProperties} from "n8n-workflow";
99
import {replacePathVarsToParameter} from "./n8n/utils";
1010
import {IResourceParser} from "./ResourceParser";
11+
import {
12+
BinaryFileType,
13+
INodeExecutionData, INodePropertyRouting,
14+
INodeRequestOutput
15+
} from "n8n-workflow/dist/Interfaces";
16+
import ResponseObject = OpenAPIV3.ResponseObject;
1117

1218
export class BaseOperationsCollector implements OpenAPIVisitor {
1319
public readonly _fields: INodeProperties[]
@@ -131,24 +137,69 @@ export class BaseOperationsCollector implements OpenAPIVisitor {
131137
}
132138

133139
protected parseOperation(operation: OpenAPIV3.OperationObject, context: OperationContext) {
134-
const method = context.method
140+
const method = context.method;
135141
const uri = context.pattern;
136-
const parser = this.operationParser
142+
const parser = this.operationParser;
143+
let non_json_returns = operation.responses &&
144+
Object.entries(operation.responses)
145+
.filter(([code, data]) =>
146+
code.startsWith("2") && 'content' in data && data.content
147+
)
148+
.map(([_, data]) => (data as ResponseObject).content)
149+
.map((content) => Object.keys(content!))
150+
.flat()
151+
.filter((contentType) => contentType.match(/^(image|audio|video)\/[a-z0-9.+-]+$|^application\/pdf$/i));
152+
let returns_raw_data = non_json_returns?.length > 0;
153+
let output: INodeRequestOutput | undefined = undefined
154+
if (returns_raw_data) {
155+
let file_type = (type: String): BinaryFileType | undefined => {
156+
if (type.startsWith('image/')) return 'image';
157+
if (type.startsWith('audio/')) return 'audio';
158+
if (type.startsWith('video/')) return 'video';
159+
if (type === 'application/pdf') return 'pdf';
160+
return undefined;
161+
};
162+
output = {
163+
postReceive: [async (items, response): Promise<INodeExecutionData[]> => {
164+
let bufferData = Buffer.from(items[0].json as unknown as ArrayBuffer);
165+
const base64Data = bufferData.toString('base64');
166+
return [{
167+
binary: {
168+
data: {
169+
data: base64Data,
170+
mimeType: response.headers['content-type'] as string,
171+
fileSize: bufferData.length.toString(),
172+
fileType: file_type(response.headers['content-type'] as string),
173+
},
174+
},
175+
json: {},
176+
}];
177+
}],
178+
}
179+
}
180+
let routing : INodePropertyRouting = {
181+
request: {
182+
// @ts-ignore
183+
method: method.toUpperCase(),
184+
url: `=${replacePathVarsToParameter(uri)}`,
185+
},
186+
};
187+
if (returns_raw_data) {
188+
routing.output = output;
189+
routing.request!.headers = {
190+
Accept: 'image/*',
191+
};
192+
routing.request!.json = false;
193+
routing.request!.encoding = 'arraybuffer';
194+
}
137195
const option = {
138196
name: parser.name(operation, context),
139197
value: parser.value(operation, context),
140198
action: parser.action(operation, context),
141199
description: parser.description(operation, context),
142-
routing: {
143-
request: {
144-
method: method.toUpperCase(),
145-
url: `=${replacePathVarsToParameter(uri)}`,
146-
},
147-
},
200+
routing,
148201
};
149202
const fields = this.parseFields(operation, context);
150-
151-
152203
return {
153204
option: option,
154205
fields: fields,

tests/image.spec.ts

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import {N8NPropertiesBuilder} from '../src';
2+
import {INodeProperties} from 'n8n-workflow';
3+
4+
test('petstore.json', () => {
5+
const doc = require('./samples/image.json');
6+
const config = {};
7+
const parser = new N8NPropertiesBuilder(doc, config);
8+
const result = parser.build();
9+
10+
// Ensure the post Receive is a functio
11+
expect((result[1].options![0] as INodeProperties).routing!.output!.postReceive![0]).toBeInstanceOf(Function);
12+
13+
delete (result[1].options![0] as INodeProperties).routing!.output!.postReceive![0];
14+
expect(result).toEqual([
15+
{
16+
"displayName": "Resource",
17+
"name": "resource",
18+
"type": "options",
19+
"noDataExpression": true,
20+
"options": [
21+
{
22+
"name": "Example",
23+
"value": "Example",
24+
"description": ""
25+
}
26+
],
27+
"default": ""
28+
},
29+
{
30+
"displayName": "Operation",
31+
"name": "operation",
32+
"type": "options",
33+
"noDataExpression": true,
34+
"displayOptions": {
35+
"show": {
36+
"resource": [
37+
"Example"
38+
]
39+
}
40+
},
41+
"options": [
42+
{
43+
"name": "Render Example By Id",
44+
"value": "Render Example By Id",
45+
"action": "Render the example by ID",
46+
"description": "Renders something to a PNG image using the specified parameter and resolution.\nThis endpoint returns the rendered image as a PNG file.",
47+
"routing": {
48+
"request": {
49+
"method": "GET",
50+
"url": "=/example/{{$parameter[\"id\"]}}/render/{{$parameter[\"parameter\"]}}",
51+
"headers": {
52+
"Accept": "image/*"
53+
},
54+
"json": false,
55+
"encoding": "arraybuffer"
56+
},
57+
"output": {
58+
"postReceive": []
59+
}
60+
}
61+
}
62+
],
63+
"default": ""
64+
},
65+
{
66+
"displayName": "GET /example/{id}/render/{parameter}",
67+
"name": "operation",
68+
"type": "notice",
69+
"typeOptions": {
70+
"theme": "info"
71+
},
72+
"default": "",
73+
"displayOptions": {
74+
"show": {
75+
"resource": [
76+
"Example"
77+
],
78+
"operation": [
79+
"Render Example By Id"
80+
]
81+
}
82+
}
83+
},
84+
{
85+
"displayName": "Id",
86+
"name": "id",
87+
"required": true,
88+
"description": "The ID of the Example to render",
89+
"default": "",
90+
"type": "string",
91+
"displayOptions": {
92+
"show": {
93+
"resource": [
94+
"Example"
95+
],
96+
"operation": [
97+
"Render Example By Id"
98+
]
99+
}
100+
}
101+
},
102+
{
103+
"displayName": "Parameter",
104+
"name": "parameter",
105+
"required": true,
106+
"description": "Some parameter we want",
107+
"default": "",
108+
"type": "string",
109+
"displayOptions": {
110+
"show": {
111+
"resource": [
112+
"Example"
113+
],
114+
"operation": [
115+
"Render Example By Id"
116+
]
117+
}
118+
}
119+
},
120+
{
121+
"displayName": "Width",
122+
"name": "width",
123+
"default": 800,
124+
"type": "number",
125+
"routing": {
126+
"send": {
127+
"type": "query",
128+
"property": "width",
129+
"value": "={{ $value }}",
130+
"propertyInDotNotation": false
131+
}
132+
},
133+
"displayOptions": {
134+
"show": {
135+
"resource": [
136+
"Example"
137+
],
138+
"operation": [
139+
"Render Example By Id"
140+
]
141+
}
142+
}
143+
},
144+
{
145+
"displayName": "Height",
146+
"name": "height",
147+
"default": 600,
148+
"type": "number",
149+
"routing": {
150+
"send": {
151+
"type": "query",
152+
"property": "height",
153+
"value": "={{ $value }}",
154+
"propertyInDotNotation": false
155+
}
156+
},
157+
"displayOptions": {
158+
"show": {
159+
"resource": [
160+
"Example"
161+
],
162+
"operation": [
163+
"Render Example By Id"
164+
]
165+
}
166+
}
167+
}
168+
]
169+
);
170+
});

0 commit comments

Comments
 (0)