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

Flow Type Support #56

Closed
maicki opened this issue May 29, 2017 · 5 comments
Closed

Flow Type Support #56

maicki opened this issue May 29, 2017 · 5 comments
Assignees

Comments

@maicki
Copy link
Contributor

maicki commented May 29, 2017

We are planning to add Flow type support to plank and and here are some initial thoughts around it.

The initial idea was to provide a more extended JS integration with immutable models backed by Immutable.js. A partial implementation was already on a side branch too, but as Immutable.js is pretty hefty in size as well as would introduce a certain convention to use Immutable.js up front, we decided to take a step back for the first pass and start with adding Flow support to plank.

Pull Request

A pull request that implements this issue as currently described is ready: #57

Example

For a first example what the following PDT definition of an extensive representation of a Pin type is provided as well as the created Flow type definition below.

PDT

{
    "id": "pin.json",
    "title": "pin",
    "description" : "Schema definition of Pinterest Pin",
    "$schema": "http://json-schema.org/schema#",
    "type": "object",
    "properties": {
		"id" : { "type": "string" },
		"link" : {
			"type": "string",
			"format": "uri"
		},
		"url" : {
			"type": "string",
			"format": "uri"
		},
		"creator": {
			"type": "object",
			"additionalProperties": { "$ref": "user.json" }
		},
		"board": { "$ref": "board.json" },
		"created_at" : {
			"type": "string",
			"format": "date-time"
		},
		"note" : { "type": "string" },
		"color" : { "type": "string" },
		"counts": {
			"type": "object",
			"additionalProperties": { "type": "integer" }
		},
		"media": {
			"type": "object",
			"additionalProperties": { "type": "string" }
		},
		"attribution": {
			"type": "object",
			"additionalProperties": { "type": "string" }
		},
		"description" : { "type": "string" },
		"image": { "$ref": "image.json" }
	},
    "required": []
}

Flow

import type { PlankDate, PlankURI } from "./runtime.flow.js";
import type BoardType from "./BoardType.js";
import type ImageType from "./ImageType.js";
import type UserType from "./UserType.js";

export type PinType = $Shape<{|
    +note?: string | null,
    +media?: { [string]: string } | null,
    +counts?: { [string]: number } /* Integer */ | null,
    +descriptionText?: string | null,
    +creator?: { [string]: UserType } | null,
    +attribution?: { [string]: string } | null,
    +board?: BoardType | null,
    +color?: string | null,
    +link?: PlankDate | null,
    +identifier?: string | null,
    +image?: ImageType | null,
    +createdAt?: PlankDate | null,
    +url?: PlankDate | null,
|}> & {
    id: string
};

Some notes

Let's pick out some interesting pieces from the example above and provide bit more details.

Type alias

For every plank type an exported Flow type alias with the name TitleType will be created.

Property variance (read-only)

Currently all properties are defined as covariant (read-only), declared by the plus symbol in front of the property name.

Optional properties

As it's currently not possible to know for sure, that properties are included within the API response we declare the properties as optional. Furthermore, if the property is included in the API response we don't know for sure if a valid value or null was received. Therefore the type definition of a property is always declared as optional.

Primitive type properties

For types like integer or strings, equivalent primitive types like number and string are used.

Object as maps properties

For object types that act like a map, like thecounts property from above, we use a special kind of property, called an "indexer property".

Format type properties

For specific format types, we are providing pre-defined types which are defined in a specific runtime file. In case of the date and uri type the representation is just a string for now:

...
export type PlankDate = string;
export type PlankURI = string;
...

Reference properties

If references to other types are defined within the PDT, the referenced type will be imported and the property will be annotated with the reference type.

Enum and ADT properties

We also have support for enums and ADTs, which are not present in the example above. Examples for both of them would look like the following:

ADTs

PDT
...
"attribution": {
  "oneOf": [
    { "$ref": "image.json" },
    { "$ref": "board.json" }
  ]
},
...
Flow
...
export type PinAttributionType = ImageType | BoardType;
...

export type PinType = $Shape<{|
  +attribution?: PinAttributionType | null,
  ...
|}> ...

Enums

PDT
...
"status" : {
  "type": "string",
  "enum": [
      { "default" : "unknown", "description" : "unknown" },
      { "default" : "new", "description" : "new" },
      { "default" : "accepted", "description" : "accepted" },
      { "default" : "denied", "description" : "denied" },
      { "default" : "pending_approval", "description" : "pending_approval" },
      { "default" : "contact_request_not_approved", "description": "contact_request_not_approved" }
  ],
  "default" : "unknown"
},
"availability" : {
  "type": "integer",
  "enum": [
      { "default" : 1, "description" : "in_stock" },
      { "default" : 2, "description" : "out_of_stock" },
      { "default" : 3, "description" : "preorder" },
      { "default" : 4, "description" : "unavailable" }
  ]
},
...
Flow
export type PinStatusType =
    | "unknown"
    | "new"
    | "accepted"
    | "denied"
    | "pending_approval"
    | "contact_request_not_approved";

export type PinAvailabilityType =
    | 1 /* in_stock */
    | 2 /* out_of_stock */
    | 3 /* preorder */
    | 4; /* unavailable */
...
export type PinType = $Shape<{|
  +status?: PinStatusType | null,
  +availability?: PinAvailabilityType | null,
  ...
|}> ...

Feedback welcome

As we would like to provide the most optimal integration that would suit most of the needs, with this issue we would like to start gathering feedback from the community about our current approach and were we should heading to.

cc @rahul-malik @bkase @chrislloyd @bradencanderson

@maicki maicki self-assigned this May 29, 2017
@maicki maicki mentioned this issue May 30, 2017
2 tasks
@bkase
Copy link
Contributor

bkase commented May 30, 2017

Cool! The only thing I see is a minor enhancement. For the Objective-C backend, we generate a comment next to the integer enum types:

typedef NS_ENUM(NSInteger, PIPinAttributionImageType) {
    PIPinAttributionImageTypeUnset /* unset */,
    PIPinAttributionImageTypeBoard /* board */,
    PIPinAttributionImageTypeInterest /* interest */,
    PIPinAttributionImageTypeUser /* user */
};

I think this information especially important in JavaScript since the Flow definition doesn't name the numbers (although it could). Perhaps we at least include the comments like we do in Objective-C:

export type PinAvailabilityType =
    | 1 /* in_stock */
    | 2 /* out_of_stock */
    | 3 /* preorder */
    | 4 /* unavailable */;

@bradencanderson
Copy link

For PinAvailabilityType -- why are we using numeric enums? Seems error-prone since enums are structurally typed.

type o1 = 1 | 2;
type o2 = 1 | 2;
const a: o1 = 1;
(a: o2) // o1 and o2 shouldn't be interchangeable

My opinion: we should require that descriptions are unique and short (maybe introduce another key field) and we should use those as enums instead. We can convert back to ints when sending them over the wire if desired.

@maicki
Copy link
Contributor Author

maicki commented Jun 12, 2017

@bkase: Added the description to the integer enums.

@bradencanderson: The enum types are based on how it's defined in the PDK. We support integer and string enums.

E.g. This is a string enum:

"object_type": {
  "type" : "string",
  "enum": [
      { "default" : "none", "description" : "none" },
      { "default" : "board", "description" : "board" },
      ...
  ],
  "default": "none"
},

And that's an integer enum:

"container_type": {
    "type": "integer",
    "enum": [
        { "default" : 0, "description" : "none" },
        { "default" : 1, "description" : "carousel" },
        ...
    ]
},

@bradencanderson
Copy link

Integer enums seem problematic because they're structurally typed -- 0 | 1 is a subtype of 0 | 1 | 2, etc.

@rahul-malik
Copy link
Collaborator

Merged

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

No branches or pull requests

4 participants