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

Handling of enums #156

Closed
mattvagni opened this issue Mar 29, 2019 · 8 comments
Closed

Handling of enums #156

mattvagni opened this issue Mar 29, 2019 · 8 comments
Labels

Comments

@mattvagni
Copy link
Contributor

mattvagni commented Mar 29, 2019

I've noticed something quite strange about the handling of enums, I'm not sure what the solution is but am happy to help contribute a fix once decided what the correct behaviour should be (and whether this is even an issue).

Assuming an enum in proto file like this:

enum Emotion {
  POSITIVE = 0;
  NEUTRAL = 1;   
  NEGATIVE = 2; 
}

The typescript generation will output:

enum Emotion {
  POSITIVE = 0,
  NEUTRAL = 1,   
  NEGATIVE = 2, 
}

The matching javascript output is:

/**
 * @enum {number}
 */
proto.stock.Emotion = {
  POSITIVE: 0,
  NEUTRAL: 1,
  NEGATIVE: 2,
};

The problem

The problem is that typescript enums should actually be outputted like so in javascript:

var Emotion;
(function (Emotion) {
    Emotion[Emotion["POSITIVE"] = 0] = "POSITIVE";
    Emotion[Emotion["NEUTRAL"] = 1] = "NEUTRAL";
    Emotion[Emotion["NEGATIVE"] = 2] = "NEGATIVE";
})(Emotion || (Emotion = {}));

This resulting value of the Emotion var becomes this once the above code is evaluated:

var Emotion = {
  0: "POSITIVE"
  1: "NEUTRAL"
  2: "NEGATIVE"
  NEGATIVE: 2
  NEUTRAL: 1
  POSITIVE: 0
}

This allows for mapping in both directions so that both of these would be truthy:

console.log(Emotion[0] === 'POSITIVE');
console.log(Emotion.POSITIVE === 0);

If we treat the javascript output from protoc as correct it means that potentially this plugins' handling of proto enums should be different.

Potential solutions

1. Output type definitions that match the outputted javascript

I think this could be as simple as making enums be outputted like this in typescript:

interface Emotion {
  0: "POSITIVE";
  1: "NEUTRAL";
  2: "NEGATIVE";
  POSITIVE: 0;
  NEUTRAL: 1;
  NEGATIVE: 2; 
}

2. Document this limitation of enums

We could very clearly say that this is the case in the documentation and the generated code?


Happy to propose/work on a solution once an approach has been decided upon and thanks for providing such a great library 👍

@jonnyreeves
Copy link
Contributor

Hi @mattvagni, thank you for providing such a detailed pull request and volunteering to help with the fix.

One of the goals of ts-protoc-gen is to provide TypeScript interfaces which match the javascript generated by the javascript protoc plugin. As a result I view this as a bug in our implementation (ie: we should not be generating enums, but instead an interface as you've suggested).

Looking at the generated code (eg: external_enum_pb.js), it looks like we should only provide a one-way mapping, ie:

interface ExternalEnum {
  DEFAULT: 0
  FIRST: 1
  SECOND: 2
}

@mattvagni
Copy link
Contributor Author

@jonnyreeves That makes sense - got mixed up in my solution, you are right: we don't want to support a two way mapping. Will have a go at doing that. Should be fairly straight-forward I think 👍

@mattvagni
Copy link
Contributor Author

@jonnyreeves I'm not sure how to best define the messages. If you do this for example:

 export class EnumMessage extends jspb.Message {
-  getInternalEnum(): EnumMessage.InternalEnum;
-  setInternalEnum(value: EnumMessage.InternalEnum): void;
+  getInternalEnum(): EnumMessage.InternalEnum[keyof EnumMessage.InternalEnum];
+  setInternalEnum(value: EnumMessage.InternalEnum[keyof EnumMessage.InternalEnum]): void;

to essentially say that you must essentially provide a 'value' of the proto's enum the authoring experience becomes a bit rubbish since IDEs basically just list out numbers like this:

Screenshot 2019-03-29 at 22 25 27

instead of this:

Screenshot 2019-03-29 at 22 27 53

It's definitely 'accurate' but i'm wondering if there is something we can do to make this nicer - any thoughts?

mattvagni added a commit to mattvagni/ts-protoc-gen that referenced this issue Mar 30, 2019
mattvagni added a commit to mattvagni/ts-protoc-gen that referenced this issue Mar 31, 2019
jonny-improbable pushed a commit that referenced this issue Apr 3, 2019
@jonny-improbable
Copy link
Contributor

Fixed in #157; open question around type-hints still stands if anyone knows a better way?

@avishco1
Copy link

avishco1 commented Jul 6, 2020

Hi,

i haven't figured out yet how to use this new mapping structure.
Say the generated code is:
image

  1. How can an uninitialized const be exported? if I copy this code to some TS file, it will not compile ('const' declarations must be initialized)
  2. How do I consume this?
    This code generates errors
    image

thanks!

@mattvagni
Copy link
Contributor Author

HI @avishco1 This package only generates the types. You will still need to generate the actual javascript client (where the const will then actually be defined). You can do this using the javascript protoc plugin.

@doom-goober
Copy link

@mattvagni @avishco1 Assuming I have:

enum AnimalType {
  None = 0;
  Bird = 1;
  Dog = 2;
}

And I generate both the JS and the TS using protoc, the only way I've figured out how to use use it with proper type safety is:

import {AnimalType, AnimalTypeMap} from "./generated/AnimalType_pb"
const someAnimal:AnimalTypeMap[keyof AnimalTypeMap] = AnimalType.Bird;

This works, however it's very clunky. Am I doing something wrong? Thanks.

@jesseschalken
Copy link

@doom-goober I don't think so. You can define

type AnimalTypeValue = AnimalTypeMap[keyof AnimalTypeMap]

and use that if you prefer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants