I am using apollo-upload-client for file upload. The payload it creates is:
{
query: mutation($file: Upload!) { singleUpload(file: $file) { id } },
variables: {
file: File // a.txt
}
}
and the payload is:
...
{ "0": ["variables.file"] }
--------------------------cec8e8123c05ba25
Content-Disposition: form-data; name="0"; filename="a.txt"
Content-Type: text/plain
Is there a way for the payload name="0" property to equal the one stated in the mutation for example
{
query: mutation($file: Upload!) { singleUpload(file: $file) { id } },
variables: {
someCustomName: File // a.txt
}
}
and the payload to be :
...
{ "0": ["variables.someCustomName"] }
--------------------------cec8e8123c05ba25
Content-Disposition: form-data; name="someCustomName"; filename="a.txt"
Content-Type: text/plain
Is there a way to modify this name="0" property? Thanks!
Mapping entry points to used file variable name ($file)
... you can rename it like:
mutation($someFile: Upload!) {
singleUpload(file: $someFile) {
id
... and of course variables: {someFile: File}
Related
My NestJS server has an endpoint that accepts files and also additional form data
For example I pass a file and a user_id of the file creator in the form.
NestJS Swagger needs to be told explicitly that body contains the file and that the endpoint consumes multipart/form-data this is not documented in the NestJS docs https://docs.nestjs.com/openapi/types-and-parameters#types-and-parameters.
Luckily some bugs led to discussion about how to handle this use case
looking at these two discussions
https://github.com/nestjs/swagger/issues/167
https://github.com/nestjs/swagger/issues/417
I was able to put together the following
I have added annotation using a DTO:
the two critical parts are:
in the DTO add
#ApiProperty({
type: 'file',
properties: {
file: {
type: 'string',
format: 'binary',
},
},
})
public readonly file: any;
#IsString()
public readonly user_id: string;
in the controller add
#ApiConsumes('multipart/form-data')
this gets me a working endpoint
and this OpenAPI Json
{
"/users/files":{
"post":{
"operationId":"UsersController_addPrivateFile",
"summary":"...",
"parameters":[
],
"requestBody":{
"required":true,
"content":{
"multipart/form-data":{
"schema":{
"$ref":"#/components/schemas/UploadFileDto"
}
}
}
}
}
}
}
...
{
"UploadFileDto":{
"type":"object",
"properties":{
"file":{
"type":"file",
"properties":{
"file":{
"type":"string",
"format":"binary"
}
},
"description":"...",
"example":"'file': <any-kind-of-binary-file>"
},
"user_id":{
"type":"string",
"description":"...",
"example":"cus_IPqRS333voIGbS"
}
},
"required":[
"file",
"user_id"
]
}
}
Here is what I find a cleaner Approach:
#Injectable()
class FileToBodyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const ctx = context.switchToHttp();
const req = ctx.getRequest();
if(req.body && req.file?.fieldname) {
const { fieldname } = req.file;
if(!req.body[fieldname]) {
req.body[fieldname] = req.file;
}
}
return next
.handle();
}
}
const ApiFile = (options?: ApiPropertyOptions): PropertyDecorator => (
target: Object, propertyKey: string | symbol
) => {
ApiProperty({
type: 'file',
properties: {
[propertyKey]: {
type: 'string',
format: 'binary',
},
},
})(target, propertyKey);
};
class UserImageDTO {
#ApiFile()
file: Express.Multer.File; // you can name it something else like image or photo
#ApiProperty()
user_id: string;
}
#Controller('users')
export class UsersController {
#ApiBody({ type: UserImageDTO })
// #ApiResponse( { type: ... } ) // some dto to annotate the response
#Post('files')
#ApiConsumes('multipart/form-data')
#UseInterceptors(
FileInterceptor('file'), //this should match the file property name
FileToBodyInterceptor, // this is to inject the file into the body object
)
async addFile(#Body() userImage: UserImageDTO): Promise<void> { // if you return something to the client put it here
console.log({modelImage}); // all the fields and the file
console.log(userImage.file); // the file is here
// ... your logic
}
}
FileToBodyInterceptor and ApiFile are general, I wish they where in the NestJs
You probably need to install #types/multer to have to Express.Multer.File
i want to make Post Request Using formBody parameters using rn-fetch-blob. please tell me how to do it?
This is an example of formdata that you want.
Multipart/form-data example
RNFetchBlob.fetch('POST', 'http://www.example.com/upload-form', {
Authorization : "Bearer access-token",
otherHeader : "foo",
'Content-Type' : 'multipart/form-data',
}, [
// element with property `filename` will be transformed into `file` in form data
{ name : 'avatar', filename : 'avatar.png', data: binaryDataInBase64},
// custom content type
{ name : 'avatar-png', filename : 'avatar-png.png', type:'image/png', data: binaryDataInBase64},
// part file from storage
{ name : 'avatar-foo', filename : 'avatar-foo.png', type:'image/foo', data: RNFetchBlob.wrap(path_to_a_file)},
// elements without property `filename` will be sent as plain text
{ name : 'name', data : 'user'},
{ name : 'info', data : JSON.stringify({
mail : 'example#example.com',
tel : '12345678'
})},
]).then((resp) => {
// ...
}).catch((err) => {
// ...
})
i am new to mule and i have to convert json to xml.here is the transform output code
%dw 1.0
%input payload application/json
%output application/xml
---
{(
items:
{
Id:payload.abc.productid,
ProductPrice:payload.abc.price,
Name:payload.abc.productname
}
)}
Here is the schema against which i am testing
{
"abc":
{
"productid":"4",
"productname":"Shampoo",
"price":["1000","2000","3000"]
}
}
It generates correct xml in case i send only one item in array price but in case i send multiple elements it gives me this error
Cannot coerce a :array to a :object.
I know i should loop inside it but i do not know how to make it possible
Please guide!
You could use the following:
{
items: {
(payload map ((payload01 , indexOfPayload01) -> {
item: {
Id: payload01.productid,
Name: payload01.productname,
(payload01.price map ((price , indexOfPrice) -> {
price: price
}))
}
}))
}
}
I have removed the "abc" from the incoming data so the format is as:
[
{
"productid":"4",
"productname":"Shampoo",
"price":["1000","2000","3000"]
},
{
"productid":"4",
"productname":"not shampoo",
"price":["1000","2000","3000"]
}
]
Let me know if the abc needs to be there, but I hope this helps
you can use the map keyword to iterate over arrays in Dataweave. Take a look at the documentation here.
I have to read a CSV file with 6 different structured formats and convert to XML format. I need a help to read multi-structured CSV file in mule.
Sample input:
01,12345,Cap,01-02-2017
02,12345,subject1, subject2,subject3,subject4,subject5
03,12345,65432,45,ABS
04,12345,ABC,DEF,,
05,12345,5325,ABC,
06,12345,87.9,ASDF,LKJ
06,12345,99,ABC,WERT
Expected Output:
<Root>
<Sample>
<Number>12345</Number>
<B>Cap</B>
<C>01-02-2017</C>
</Sample>
<Example>
<Sub>
<Number>12345</Number>
<S1>subject1</S1>
<S2>subject2</S2>
<S3>subject3</S3>
<S4>subject4</S4>
<S5>subject5</S5>
</Sub>
<Sub1>
<Number>12345</Number>
<A1>65432</A1>
<A2>45</A2>
<A3>ABS</A3>
</Sub1>
<Sub2>
<Number>12345</Number>
<B1>ABC</B1>
<B2>DEF</B2>
<B3/>
</Sub2>
<Sub3>
<Number>12345</Number>
<C1>5325</C1>
<C2>ABC</C2>
</Sub3>
<Sub4>
<Sub_rec>
<Number>12345</Number>
<D1>87.9</D1>
<D2>ASDF</D2>
<D3>LKJ</D3>
</Sub_rec>
<Sub_rec>
<Number>12345</Number>
<D1>99</D1>
<D2>ABC</D2>
<D3>WERT</D3>
</Sub_rec>
</Sub4>
</Example>
</Root>
Assuming data will not change, dataweave transform is
%dw 1.0
%output application/xml
---
root: {
sample: {
Number: payload[0][0]
},
Example: {
sub : {
Number: payload[1][0]
},
sub1 : {
Number: payload[2][0]
},
sub2 : {
Number: payload[3][0]
},
sub3 : {
Number: payload[4][0]
},
sub4 : {
Sub_rec: {
Number: payload[5][0]
},
Sub_rec: {
Number: payload[6][0]
}
}
}
}
Other values can be filled using S1: payload[1][3] and so on.
Second way is using map, though it also assumes data is static, bit more flexble
root: {
sample: {
Number: payload[0][0]
},
Example: {
(payload[1..-2] map ((row, indexOfRow) -> {
Sub : {
Number: row[0]
} when indexOfRow == 0
otherwise {
Number: row[0]
} when indexOfRow == 1
otherwise {
Number: row[0]
} when indexOfRow == 2
otherwise {
Number: row[0]
} when indexOfRow == 3
otherwise {
(payload[-2..-1] map {
sub_rec: {
Number: $[2]
}
})
}
}))
}
}
I am trying to upload a file + parameters to Google Drive via Swift 2/Alamofire. In the code below, i I change the line that says:
"https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"
to the following:
"https://www.googleapis.com/upload/drive/v3/files"
the file uploads to google without a name. Otherwise, the file upload fails with the same generic code:
Error Domain=com.alamofire.error Code=-6003 "Response status code was unacceptable: 400" UserInfo={NSLocalizedFailureReason=Response status code was unacceptable: 400}
I'd like to be able to upload the file with name and potentially other parameters as well. I know I'm mangling the multipart upload somehow, but I don't know what I'm doing wrong.
func postBinaryToGdriveSimple (token: String, callback: Bool -> Void){
var returnedId : String!
let path = NSBundle.mainBundle().pathForResource("drawing", ofType: "bin")
let bindata: NSData = NSData(contentsOfURL: NSURL(fileURLWithPath: path!))!
let parameters : [String: String] = ["title":"SomeFileName"]
let headers = ["Authorization": "Bearer \(token)"]
upload(
.POST,
"https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart",
headers: headers,
multipartFormData: { multipartFormData in
// append file parameters to request
for (key, value) in parameters {
multipartFormData.appendBodyPart(data: value.dataUsingEncoding(NSUTF8StringEncoding)!, name: key)
}
// append binary file to request
multipartFormData.appendBodyPart(data: bindata, name: "upload", fileName: "drawing.bin", mimeType: "application/octet-stream")
},
encodingCompletion: { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
dispatch_async(dispatch_get_main_queue()) {
let percent = (Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
//progress(percent: percent)
print ("................\(percent)")
}
}
upload.validate()
upload.responseJSON { response in
switch response.result {
case .Success(let data):
print(response)
print("Validation Successful")
let json = JSON(data)
returnedId = json[("id")].stringValue
print("......id for uploaded file is \(returnedId)")
callback(true)
case .Failure(let error):
print(error)
print("Validation Bad")
callback(false)
}
}
case .Failure(_):
callback(false)
}
})
} // end of postBinaryToGdriveSimple
I wonder if there is something about the way Alamofire creates the multipart request that Google Drive is not liking. From the google api site, it seems like a request needs to have certain parameters that Alamofire may not be creating, such as Content-length and boundary settings...
POST /upload/drive/v3/files?uploadType=multipart HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer your_auth_token
Content-Type: multipart/related; boundary=foo_bar_baz
Content-Length: number_of_bytes_in_entire_request_body
--foo_bar_baz
Content-Type: application/json; charset=UTF-8
{
"name": "My File"
}
--foo_bar_baz
Content-Type: image/jpeg
JPEG data
--foo_bar_baz--
If so, what is the work-around?
Double check the API documentation for Google Drive.
It appears that the key field for the parameter is "name" (not "title").
If you want additional, custom file properties, restricted to the single app, add an "appProperties" to the json:
"appProperties": {
"title": "whatever"
}