Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attachment helper for base64 encoding #505

Merged
merged 1 commit into from
Nov 29, 2018

Conversation

spelcaster
Copy link
Contributor

  • Added the method readFile to read a file and convert its content to
    base64;
  • It's now possible create an Attachment with a raw file;

@thinkingserious thinkingserious added the status: code review request requesting a community code review or review from Twilio label Oct 18, 2017
@spelcaster
Copy link
Contributor Author

spelcaster commented Oct 18, 2017

//sample file
const fs = require('fs');  
const sgMail = require('@sendgrid/mail');

const {
  classes: {
    Attachment
  }
} = require('@sendgrid/helpers');

let attachment = new Attachment({
  filename: 'indexes.txt',
  type: 'plain/text',
  disposition: 'attachment',
  contentId: 'mytext',
  filePath: '/home/hugocarmo/indexes.js',
  //content: fs.readFileSync('/home/hugocarmo/indexes.js'), // will work
  //content: 'w6cK', // will work
});

sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
  to: 'name@example.com',
  from: 'name@example.com',
  subject: 'Hello attachment',
  html: '<p>Here’s an attachment for you!</p>',
  attachments: [
    attachment
  ],
};

console.log(msg);

@@ -39,17 +40,29 @@ class Attachment {
data = toCamelCase(data);

//Extract properties from data
const {content, filename, type, disposition, contentId} = data;
const {content, filename, type, disposition, contentId, file} = data;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried that file vs. filename will be a bit confusing. Maybe instead of passing a file object, we can have two separate new settings filePath and fileEncoding.

(The filename setting should probably be deprecated and introduce a new one fileName to be consistent)

const {content, filename, type, disposition, contentId} = data;
const {content, filename, type, disposition, contentId, file} = data;

let fileContent = !file ? content : this.readFile(file);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File content is immutable so can be const. I'm not sure about the logic either though. If content is passed, should that take priority over a file? Maybe a warning if both are passed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the file should take priority over content, and warn the user if both is passed is a good idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any example of how to give the user a warning here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we don't show warnings anywhere really, just throw errors. Maybe an error is suitable, because you are not expected to pass both values. Then, we also don't need to worry about what takes priority.

this.setFilename(filename);
this.setType(type);
this.setDisposition(disposition);
this.setContentId(contentId);
}

/**
* Read a file and return its content as base64
*/
readFile(file) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use a separate setting for the encoding you can pass it in here, with a default value of utf8, e.g.

readFile(path, encoding = 'utf8') {
   ...
}

readFile(file) {
let encoding = !file.encoding ? 'utf8' : file.encoding;
let data = fs.readFileSync(file.path);
let buff = Buffer.from(data, encoding);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All let's are constant and I'm wondering if we should return the buffer, instead of converting to string at this point? That way you can use the helper function to read files and get buffers and manipulate them if needed before passing to the attachment.

Also, we can update the Attachment class to detect if a Buffer is passed and automatically convert to string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the tip.
Should I convert the method readFile to a helper function? Do you think that another helper function to convert a string to Buffer is necessary?

According to fs.readFileSync documentation if the encoding is passed, then a string is returned, otherwise it'll return a buffer.

Perhaps in this case the buffer conversion using an encoding is completely unnecessary, just if the user want to get a string from the buffer, then the encoding would be useful.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might not need to expose it, as it's basically just using readFileSync. Let's pass the encoding straight to readFileSync, so we won't need to convert to buffer.


if ((typeof content !== 'undefined') && (typeof filePath !== 'undefined')) {
throw new Error(
'The props \'content\' and \'filePath\' cannot be used together.'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use template strings to make it a bit cleaner without the backslashes? E.g.

`The props 'content' and 'filePath' cannot be used together.`

return;
}

this.setContent(content);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use a one liner for all of this:

this.setContent(filePath ? this.readFile(filePath) : content);

return;
}
if (typeof content !== 'string') {
} else if (contentType === 'object'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please install ESLint for your code editor and run it on your code to ensure the code style is the same to the rest of the code base?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I was using the StandardJS

Copy link
Contributor

@adamreisnz adamreisnz Oct 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you used ESLint on your changes? The code style is still different, e.g. else if's and else's should start on a new line.

} else if (content instanceof Buffer) {
this.content = content.toString('base64');
return;
} else if (contentType !== 'string') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you write matching unit tests for all these new content type cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will...

@spelcaster
Copy link
Contributor Author

@adamreisnz the changes were made, I'm sorry for the long response time, last week I was so busy.

@thinkingserious thinkingserious added difficulty: medium fix is medium in difficulty hacktoberfest labels Oct 28, 2017
Copy link
Contributor

@adamreisnz adamreisnz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes, it's almost done just some code style issues to keep things consistent. ESLint can auto-fix these for you if you run it from the command line or in your editor 👍

@adamreisnz
Copy link
Contributor

@thinkingserious @mbernier I'm happy with this, assuming it works as intended and has been tested!

@spelcaster
Copy link
Contributor Author

cool! thanks for your review @adamreisnz :)

@mbernier
Copy link
Contributor

mbernier commented Nov 3, 2017

@spelcaster

We have not been able to merge your Pull Request, but because you are awesome - we wanted to make sure you could still get a SendGrid Hacktoberfest shirt.

Please go fill out our swag form before Nov 5th and we will send the shirt! (We know that you might have tried this before and it didn’t work, sorry about that!)

You have till Nov 5th to fill out this form in order to get the Hacktoberfest shirt!

Thank you for contributing during Hacktoberfest! We hope to see you in the repos soon! Just so you know, we always give away a SendGrid shirt for your first ever non-Hacktoberfest PR that gets merged.

@msgadi
Copy link

msgadi commented Dec 23, 2017

@spelcaster I have tried your above code and tried to upload a 10mb pdf file. But It does not work. Again it does not work with a simple text file.

I have set the type to 'application/pdf' then too it does not work. Any help will be appriciated.
Refer the below code.

 ```//sample file
  const fs = require('fs');
 const sgMail = require('@sendgrid/mail');

 const {
     classes: {
         Attachment
     }
 } = require('@sendgrid/helpers');

 let attachment = new Attachment({
     filename: 'test.pdf',
     type: 'application/pdf',
     disposition: 'attachment',
     contentId: 'testingpdf',
     filePath: './attachments/test.pdf',
     //contentType:'application/pdf'
     //content: fs.readFileSync('./attachments/test.pdf'), // will work
     //content: 'w6cK', // will work
 });

 sgMail.setApiKey(process.env.SENDGRID_API_KEY);
 const msg = {
     to: 'example@gmail.com',
     from: 'abc@abc.com',
     subject: 'Hello attachment',
     html: '<p>Here’s an attachment for you!</p>',
     attachments: [
         attachment
     ],
 };

 sgMail.send(msg);

@spelcaster
Copy link
Contributor Author

@mgadirocks Have you tried with absolute path? See https://github.com/sendgrid/sendgrid-nodejs/pull/505/files#diff-de4f2f8e9851d3bd62c6424afddcf90fR58

@clee
Copy link

clee commented Feb 14, 2018

LGTM

@clee clee added status: code review complete and removed status: code review request requesting a community code review or review from Twilio labels Feb 14, 2018
@thinkingserious thinkingserious added type: twilio enhancement feature request on Twilio's roadmap status: ready for deploy code ready to be released in next deploy and removed status: code review complete labels Feb 27, 2018
@thinkingserious
Copy link
Contributor

Hi @spelcaster,

I'm ready to merge this now, thanks for your patience!

Could you please take a moment to fix the merge conflicts?

Thank you!

With Best Regards,

Elmer

@thinkingserious thinkingserious added status: waiting for feedback waiting for feedback from the submitter and removed status: ready for deploy code ready to be released in next deploy labels Oct 22, 2018
- If the content is a Buffer and the disposition is 'attachment', then
  the Buffer will be encoded to a base64 string;
- If a filePath is given, then file will be read and its content will be
  used, the rule to convert the file content to base64 is the same
  described above;
- It's not allowed to use content and filePath together;
- Added unit test for the new cases;
@spelcaster
Copy link
Contributor Author

@thinkingserious I've squashed the commits into only 1 and resolved the conflict. Could you verify if the rule to encode the buffer into a base64 string is correct?

Thanks...

@thinkingserious thinkingserious merged commit 5587afb into sendgrid:master Nov 29, 2018
@thinkingserious
Copy link
Contributor

Hello @spelcaster,

Thanks again for the PR!

We appreciate your contribution and look forward to continued collaboration. Thanks!

Team SendGrid DX

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
difficulty: medium fix is medium in difficulty status: waiting for feedback waiting for feedback from the submitter type: twilio enhancement feature request on Twilio's roadmap
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants